1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35#include "Attributes.h"
36#include "AutoLock.h"
37#include "Commands.h"
38#include "FSUtils.h"
39#include "IconMenuItem.h"
40#include "OpenWithWindow.h"
41#include "MimeTypes.h"
42#include "StopWatch.h"
43#include "Tracker.h"
44
45#include <map>
46
47#include <Alert.h>
48#include <Button.h>
49#include <Catalog.h>
50#include <ControlLook.h>
51#include <Collator.h>
52#include <GroupView.h>
53#include <GridView.h>
54#include <Locale.h>
55#include <Mime.h>
56#include <NodeInfo.h>
57#include <Path.h>
58#include <Roster.h>
59#include <SpaceLayoutItem.h>
60#include <Volume.h>
61#include <VolumeRoster.h>
62
63#include <stdlib.h>
64#include <stdio.h>
65#include <strings.h>
66
67
68const char* kDefaultOpenWithTemplate = "OpenWithSettings";
69
70// ToDo:
71// filter out trash
72// allow column configuring
73// make SaveState/RestoreState save the current window setting for
74// other windows
75
76const float kMaxMenuWidthFactor = 33.0f;
77
78const int32 kDocumentKnobWidth = 16;
79const int32 kOpenAndMakeDefault = 'OpDf';
80
81
82//	#pragma mark - OpenWithContainerWindow
83
84
85#undef B_TRANSLATION_CONTEXT
86#define B_TRANSLATION_CONTEXT "OpenWithWindow"
87
88
89OpenWithContainerWindow::OpenWithContainerWindow(BMessage* entriesToOpen,
90	LockingList<BWindow>* windowList)
91	:
92	BContainerWindow(windowList, 0),
93	fEntriesToOpen(entriesToOpen)
94{
95	AutoLock<BWindow> lock(this);
96
97	BRect windowRect(85, 50, 718, 296);
98	MoveTo(windowRect.LeftTop());
99	ResizeTo(windowRect.Width(), windowRect.Height());
100
101	// Create controls
102	fButtonContainer = new BGroupView(B_HORIZONTAL, B_USE_ITEM_SPACING);
103	fButtonContainer->GroupLayout()->SetInsets(0, B_USE_ITEM_INSETS,
104		B_USE_ITEM_INSETS, 0);
105
106	fLaunchButton = new BButton("ok", B_TRANSLATE("Open"),
107		new BMessage(kDefaultButton));
108
109	fLaunchButton->MakeDefault(true);
110
111	fLaunchAndMakeDefaultButton = new BButton("make default",
112		B_TRANSLATE("Open and make preferred"),
113		new BMessage(kOpenAndMakeDefault));
114	// wide button, have to resize to fit text
115	fLaunchAndMakeDefaultButton->SetEnabled(false);
116
117	fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
118		new BMessage(kCancelButton));
119
120	// Add pose view
121	fPoseView = NewPoseView(NULL, kListMode);
122	fBorderedView->GroupLayout()->AddView(fPoseView);
123
124	fPoseView->SetFlags(fPoseView->Flags() | B_NAVIGABLE);
125	fPoseView->SetPoseEditing(false);
126
127	// set the window title
128	if (CountRefs(fEntriesToOpen) == 1) {
129		// if opening just one file, use it in the title
130		entry_ref ref;
131		fEntriesToOpen->FindRef("refs", &ref);
132		BString buffer(B_TRANSLATE("Open %name with:"));
133		buffer.ReplaceFirst("%name", ref.name);
134
135		SetTitle(buffer.String());
136	} else {
137		// use generic title
138		SetTitle(B_TRANSLATE("Open selection with:"));
139	}
140
141	AddCommonFilter(new BMessageFilter(B_KEY_DOWN,
142		&OpenWithContainerWindow::KeyDownFilter));
143}
144
145
146OpenWithContainerWindow::~OpenWithContainerWindow()
147{
148	delete fEntriesToOpen;
149}
150
151
152BPoseView*
153OpenWithContainerWindow::NewPoseView(Model*, uint32)
154{
155	return new OpenWithPoseView;
156}
157
158
159OpenWithPoseView*
160OpenWithContainerWindow::PoseView() const
161{
162	ASSERT(dynamic_cast<OpenWithPoseView*>(fPoseView) != NULL);
163
164	return static_cast<OpenWithPoseView*>(fPoseView);
165}
166
167
168const BMessage*
169OpenWithContainerWindow::EntryList() const
170{
171	return fEntriesToOpen;
172}
173
174
175void
176OpenWithContainerWindow::OpenWithSelection()
177{
178	int32 count = PoseView()->SelectionList()->CountItems();
179	ASSERT(count == 1);
180	if (count == 0)
181		return;
182
183	PoseView()->OpenSelection(PoseView()->SelectionList()->FirstItem(), 0);
184}
185
186
187static const BString*
188FindOne(const BString* element, void* castToString)
189{
190	if (strcasecmp(element->String(), (const char*)castToString) == 0)
191		return element;
192
193	return 0;
194}
195
196
197static const entry_ref*
198AddOneUniqueDocumentType(const entry_ref* ref, void* castToList)
199{
200	BObjectList<BString>* list = (BObjectList<BString>*)castToList;
201
202	BEntry entry(ref, true);
203		// traverse symlinks
204
205	// get this documents type
206	char type[B_MIME_TYPE_LENGTH];
207	BFile file(&entry, O_RDONLY);
208	if (file.InitCheck() != B_OK)
209		return 0;
210
211	BNodeInfo info(&file);
212	if (info.GetType(type) != B_OK)
213		return 0;
214
215	if (list->EachElement(FindOne, &type))
216		// type already in list, bail
217		return 0;
218
219	// add type to list
220	list->AddItem(new BString(type));
221
222	return 0;
223}
224
225
226static const BString*
227SetDefaultAppForOneType(const BString* element, void* castToEntryRef)
228{
229	const entry_ref* appRef = (const entry_ref*)castToEntryRef;
230
231	// set entry as default handler for one mime string
232	BMimeType mime(element->String());
233	if (!mime.IsInstalled())
234		return 0;
235
236	// first set it's app signature as the preferred type
237	BFile appFile(appRef, O_RDONLY);
238	if (appFile.InitCheck() != B_OK)
239		return 0;
240
241	char appSignature[B_MIME_TYPE_LENGTH];
242	if (GetAppSignatureFromAttr(&appFile, appSignature) != B_OK)
243		return 0;
244
245	if (mime.SetPreferredApp(appSignature) != B_OK)
246		return 0;
247
248	// set the app hint on the metamime for this signature
249	mime.SetTo(appSignature);
250#if xDEBUG
251	status_t result =
252#endif
253	mime.SetAppHint(appRef);
254
255#if xDEBUG
256	BEntry debugEntry(appRef);
257	BPath debugPath;
258	debugEntry.GetPath(&debugPath);
259
260	PRINT(("setting %s, sig %s as default app for %s, result %s\n",
261		debugPath.Path(), appSignature, element->String(), strerror(result)));
262#endif
263
264	return 0;
265}
266
267
268void
269OpenWithContainerWindow::MakeDefaultAndOpen()
270{
271	int32 count = PoseView()->SelectionList()->CountItems();
272	ASSERT(count == 1);
273	if (count == 0)
274		return;
275
276	BPose* selectedAppPose = PoseView()->SelectionList()->FirstItem();
277	ASSERT(selectedAppPose != NULL);
278	if (selectedAppPose == NULL)
279		return;
280
281	// collect all the types of all the opened documents into a list
282	BObjectList<BString> openedFileTypes(10, true);
283	EachEntryRef(EntryList(), AddOneUniqueDocumentType, &openedFileTypes, 100);
284
285	// set the default application to be the selected pose for all the
286	// mime types in the list
287	openedFileTypes.EachElement(SetDefaultAppForOneType,
288		(void*)selectedAppPose->TargetModel()->EntryRef());
289
290	// done setting the default application, now launch the app with the
291	// documents
292	OpenWithSelection();
293}
294
295
296void
297OpenWithContainerWindow::MessageReceived(BMessage* message)
298{
299	switch (message->what) {
300		case kDefaultButton:
301			OpenWithSelection();
302			PostMessage(B_QUIT_REQUESTED);
303			return;
304
305		case kOpenAndMakeDefault:
306			MakeDefaultAndOpen();
307			PostMessage(B_QUIT_REQUESTED);
308			return;
309
310		case kCancelButton:
311			PostMessage(B_QUIT_REQUESTED);
312			return;
313
314		case B_OBSERVER_NOTICE_CHANGE:
315			return;
316
317		case kResizeToFit:
318			ResizeToFit();
319			break;
320	}
321
322	_inherited::MessageReceived(message);
323}
324
325
326filter_result
327OpenWithContainerWindow::KeyDownFilter(BMessage* message, BHandler**,
328	BMessageFilter* filter)
329{
330	uchar key;
331	if (message->FindInt8("byte", (int8*)&key) != B_OK)
332		return B_DISPATCH_MESSAGE;
333
334	int32 modifiers = 0;
335	message->FindInt32("modifiers", &modifiers);
336	if (modifiers == 0 && key == B_ESCAPE) {
337		filter->Looper()->PostMessage(kCancelButton);
338		return B_SKIP_MESSAGE;
339	}
340
341	return B_DISPATCH_MESSAGE;
342}
343
344
345bool
346OpenWithContainerWindow::ShouldAddMenus() const
347{
348	return false;
349}
350
351
352void
353OpenWithContainerWindow::ShowContextMenu(BPoint, const entry_ref*)
354{
355	// do nothing here so open with context menu doesn't get shown
356}
357
358
359void
360OpenWithContainerWindow::AddShortcuts()
361{
362	AddShortcut('I', B_COMMAND_KEY, new BMessage(kGetInfo), PoseView());
363	AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView());
364}
365
366
367void
368OpenWithContainerWindow::NewAttributesMenu(BMenu* menu)
369{
370	_inherited::NewAttributesMenu(menu);
371
372	BMessage* message = new BMessage(kAttributeItem);
373	message->AddString("attr_name", kAttrOpenWithRelation);
374	message->AddInt32("attr_type", B_STRING_TYPE);
375	message->AddInt32("attr_hash",
376		(int32)AttrHashString(kAttrOpenWithRelation, B_STRING_TYPE));
377	message->AddFloat("attr_width", 180);
378	message->AddInt32("attr_align", B_ALIGN_LEFT);
379	message->AddBool("attr_editable", false);
380	message->AddBool("attr_statfield", false);
381
382	BMenuItem* item = new BMenuItem(B_TRANSLATE("Relation"), message);
383	menu->AddItem(item);
384	message = new BMessage(kAttributeItem);
385	message->AddString("attr_name", kAttrAppVersion);
386	message->AddInt32("attr_type", B_STRING_TYPE);
387	message->AddInt32("attr_hash",
388		(int32)AttrHashString(kAttrAppVersion, B_STRING_TYPE));
389	message->AddFloat("attr_width", 70);
390	message->AddInt32("attr_align", B_ALIGN_LEFT);
391	message->AddBool("attr_editable", false);
392	message->AddBool("attr_statfield", false);
393
394	item = new BMenuItem(B_TRANSLATE("Version"), message);
395	menu->AddItem(item);
396}
397
398
399void
400OpenWithContainerWindow::SaveState(bool)
401{
402	BNode defaultingNode;
403	if (DefaultStateSourceNode(kDefaultOpenWithTemplate, &defaultingNode,
404			true, false)) {
405		AttributeStreamFileNode streamNodeDestination(&defaultingNode);
406		SaveWindowState(&streamNodeDestination);
407		fPoseView->SaveState(&streamNodeDestination);
408	}
409}
410
411
412void
413OpenWithContainerWindow::SaveState(BMessage &message) const
414{
415	_inherited::SaveState(message);
416}
417
418
419void
420OpenWithContainerWindow::Init(const BMessage* message)
421{
422	_inherited::Init(message);
423}
424
425
426void
427OpenWithContainerWindow::InitLayout()
428{
429	_inherited::InitLayout();
430
431	// Remove the menu container, since we don't have a menu bar
432	fMenuContainer->RemoveSelf();
433
434	// Reset insets
435	fRootLayout->SetInsets(B_USE_ITEM_INSETS);
436	fPoseContainer->GridLayout()->SetInsets(0);
437	fVScrollBarContainer->GroupLayout()->SetInsets(-1, 0, 0, 0);
438
439	fRootLayout->AddView(fButtonContainer);
440	fButtonContainer->GroupLayout()->AddItem(BSpaceLayoutItem::CreateGlue());
441	fButtonContainer->GroupLayout()->AddView(fCancelButton);
442	fButtonContainer->GroupLayout()->AddView(fLaunchAndMakeDefaultButton);
443	fButtonContainer->GroupLayout()->AddView(fLaunchButton);
444}
445
446
447void
448OpenWithContainerWindow::RestoreState()
449{
450	BNode defaultingNode;
451	if (DefaultStateSourceNode(kDefaultOpenWithTemplate, &defaultingNode,
452			false)) {
453		AttributeStreamFileNode streamNodeSource(&defaultingNode);
454		RestoreWindowState(&streamNodeSource);
455		fPoseView->Init(&streamNodeSource);
456	} else {
457		RestoreWindowState(NULL);
458		fPoseView->Init(NULL);
459	}
460	InitLayout();
461}
462
463
464void
465OpenWithContainerWindow::RestoreState(const BMessage &message)
466{
467	_inherited::RestoreState(message);
468}
469
470
471void
472OpenWithContainerWindow::RestoreWindowState(AttributeStreamNode* node)
473{
474	if (node == NULL)
475		return;
476
477	const char* rectAttributeName = kAttrWindowFrame;
478	BRect frame(Frame());
479	if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
480			== sizeof(BRect)) {
481		MoveTo(frame.LeftTop());
482		ResizeTo(frame.Width(), frame.Height());
483	}
484}
485
486
487void
488OpenWithContainerWindow::RestoreWindowState(const BMessage &message)
489{
490	_inherited::RestoreWindowState(message);
491}
492
493
494bool
495OpenWithContainerWindow::NeedsDefaultStateSetup()
496{
497	return true;
498}
499
500
501void
502OpenWithContainerWindow::SetupDefaultState()
503{
504}
505
506
507bool
508OpenWithContainerWindow::IsShowing(const node_ref*) const
509{
510	return false;
511}
512
513
514bool
515OpenWithContainerWindow::IsShowing(const entry_ref*) const
516{
517	return false;
518}
519
520
521void
522OpenWithContainerWindow::SetCanSetAppAsDefault(bool on)
523{
524	fLaunchAndMakeDefaultButton->SetEnabled(on);
525}
526
527
528void
529OpenWithContainerWindow::SetCanOpen(bool on)
530{
531	fLaunchButton->SetEnabled(on);
532}
533
534
535//	#pragma mark - OpenWithPoseView
536
537
538OpenWithPoseView::OpenWithPoseView()
539	:
540	BPoseView(new Model(), kListMode),
541	fHaveCommonPreferredApp(false),
542	fIterator(NULL),
543	fRefFilter(NULL)
544{
545	fSavePoseLocations = false;
546	fMultipleSelection = false;
547	fDragEnabled = false;
548}
549
550
551OpenWithPoseView::~OpenWithPoseView()
552{
553	delete fRefFilter;
554	delete fIterator;
555}
556
557
558OpenWithContainerWindow*
559OpenWithPoseView::ContainerWindow() const
560{
561	OpenWithContainerWindow* window
562		= dynamic_cast<OpenWithContainerWindow*>(Window());
563	ASSERT(window != NULL);
564
565	return window;
566}
567
568
569void
570OpenWithPoseView::AttachedToWindow()
571{
572	_inherited::AttachedToWindow();
573
574	SetViewUIColor(B_TOOL_TIP_BACKGROUND_COLOR);
575	SetLowUIColor(B_TOOL_TIP_TEXT_COLOR);
576}
577
578
579bool
580OpenWithPoseView::CanHandleDragSelection(const Model*, const BMessage*, bool)
581{
582	return false;
583}
584
585
586static void
587AddSupportingAppForTypeToQuery(SearchForSignatureEntryList* queryIterator,
588	const char* type)
589{
590	// get supporting apps for type
591	BMimeType mime(type);
592	if (!mime.IsInstalled())
593		return;
594
595	BMessage message;
596	mime.GetSupportingApps(&message);
597
598	// push each of the supporting apps signature uniquely
599
600	const char* signature;
601	for (int32 index = 0; message.FindString("applications", index,
602			&signature) == B_OK; index++) {
603		queryIterator->PushUniqueSignature(signature);
604	}
605}
606
607
608static const entry_ref*
609AddOneRefSignatures(const entry_ref* ref, void* castToIterator)
610{
611	// TODO: resolve cases where each entry has a different type and
612	// their supporting apps are disjoint sets
613
614	SearchForSignatureEntryList* queryIterator =
615		(SearchForSignatureEntryList*)castToIterator;
616
617	Model model(ref, true, true);
618	if (model.InitCheck() != B_OK)
619		return NULL;
620
621	BString mimeType(model.MimeType());
622
623	if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0)
624		// if model is of unknown type, try mimeseting it first
625		model.Mimeset(true);
626
627	entry_ref preferredRef;
628
629	// add preferred app for file, if any
630	if (model.PreferredAppSignature()[0]) {
631		// got one, mark it as preferred for this node
632		if (be_roster->FindApp(model.PreferredAppSignature(), &preferredRef)
633				== B_OK) {
634			queryIterator->PushUniqueSignature(model.PreferredAppSignature());
635			queryIterator->TrySettingPreferredAppForFile(&preferredRef);
636		}
637	}
638
639	mimeType = model.MimeType();
640	mimeType.ToLower();
641
642	if (mimeType.Length() && mimeType.ICompare(B_FILE_MIMETYPE) != 0)
643		queryIterator->NonGenericFileFound();
644
645	// get supporting apps for type
646	AddSupportingAppForTypeToQuery(queryIterator, mimeType.String());
647
648	// find the preferred app for this type
649	if (be_roster->FindApp(mimeType.String(), &preferredRef) == B_OK)
650		queryIterator->TrySettingPreferredApp(&preferredRef);
651
652	return NULL;
653}
654
655
656EntryListBase*
657OpenWithPoseView::InitDirentIterator(const entry_ref*)
658{
659	OpenWithContainerWindow* window = ContainerWindow();
660
661	const BMessage* entryList = window->EntryList();
662
663	fIterator = new SearchForSignatureEntryList(true);
664
665	// push all the supporting apps from all the entries into the
666	// search for signature iterator
667	EachEntryRef(entryList, AddOneRefSignatures, fIterator, 100);
668
669	// push superhandlers
670	AddSupportingAppForTypeToQuery(fIterator, B_FILE_MIMETYPE);
671	fHaveCommonPreferredApp = fIterator->GetPreferredApp(&fPreferredRef);
672
673	if (fIterator->Rewind() != B_OK) {
674		delete fIterator;
675		fIterator = NULL;
676		HideBarberPole();
677		return NULL;
678	}
679
680	fRefFilter = new OpenWithRefFilter(fIterator, entryList,
681		fHaveCommonPreferredApp ? &fPreferredRef : 0);
682	SetRefFilter(fRefFilter);
683
684	return fIterator;
685}
686
687
688void
689OpenWithPoseView::ReturnDirentIterator(EntryListBase* iterator)
690{
691	// Do nothing. We keep our fIterator around as it is used by fRefFilter.
692}
693
694
695void
696OpenWithPoseView::OpenSelection(BPose* pose, int32*)
697{
698	OpenWithContainerWindow* window = ContainerWindow();
699
700	int32 count = SelectionList()->CountItems();
701	if (count == 0)
702		return;
703
704	if (pose == NULL)
705		pose = SelectionList()->FirstItem();
706
707	ASSERT(pose != NULL);
708
709	BEntry entry(pose->TargetModel()->EntryRef());
710	if (entry.InitCheck() != B_OK) {
711		BString errorString(
712			B_TRANSLATE("Could not find application \"%appname\""));
713		errorString.ReplaceFirst("%appname", pose->TargetModel()->Name());
714
715		BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"),
716			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
717		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
718		alert->Go();
719		return;
720	}
721
722	if (OpenWithRelation(pose->TargetModel()) == kNoRelation) {
723		if (!fIterator->GenericFilesOnly()) {
724			BString warning(B_TRANSLATE(
725				"The application \"%appname\" does not support the type of "
726				"document you are about to open.\nAre you sure you want to "
727				"proceed?\n\nIf you know that the application supports the "
728				"document type, you should contact the publisher of the "
729				"application and ask them to update their application to list "
730				"the type of your document as supported."));
731			warning.ReplaceFirst("%appname", pose->TargetModel()->Name());
732
733			BAlert* alert = new BAlert("", warning.String(),
734				B_TRANSLATE("Cancel"), B_TRANSLATE("Open"),	0, B_WIDTH_AS_USUAL,
735				B_WARNING_ALERT);
736			alert->SetShortcut(0, B_ESCAPE);
737			if (alert->Go() == 0)
738				return;
739		}
740		// else - once we have an extensible sniffer, tell users to ask
741		// publishers to fix up sniffers
742	}
743
744	BMessage message(*window->EntryList());
745		// make a clone to send
746	message.RemoveName("launchUsingSelector");
747		// make sure the old selector is not in the message
748	message.AddRef("handler", pose->TargetModel()->EntryRef());
749		// add ref of the selected handler
750
751	ASSERT(fSelectionHandler != NULL);
752	if (fSelectionHandler != NULL)
753		fSelectionHandler->PostMessage(&message);
754
755	window->PostMessage(B_QUIT_REQUESTED);
756}
757
758
759void
760OpenWithPoseView::Pulse()
761{
762	// disable the Open and make default button if the default
763	// app matches the selected app
764	//
765	// disable the Open button if no apps selected
766
767	OpenWithContainerWindow* window = ContainerWindow();
768
769	if (!SelectionList()->CountItems()) {
770		window->SetCanSetAppAsDefault(false);
771		window->SetCanOpen(false);
772		_inherited::Pulse();
773		return;
774	}
775
776	// if we selected a non-handling application, don't allow setting
777	// it as preferred
778	Model* firstSelected = SelectionList()->FirstItem()->TargetModel();
779	if (OpenWithRelation(firstSelected) == kNoRelation) {
780		window->SetCanSetAppAsDefault(false);
781		window->SetCanOpen(true);
782		_inherited::Pulse();
783		return;
784	}
785
786	// make the open button enabled, because we have na app selected
787	window->SetCanOpen(true);
788	if (!fHaveCommonPreferredApp) {
789		window->SetCanSetAppAsDefault(true);
790		_inherited::Pulse();
791		return;
792	}
793
794	ASSERT(SelectionList()->CountItems() == 1);
795
796	// enable the Open and make default if selected application different
797	// from preferred app ref
798	window->SetCanSetAppAsDefault((*SelectionList()->FirstItem()->
799		TargetModel()->EntryRef()) != fPreferredRef);
800
801	_inherited::Pulse();
802}
803
804
805void
806OpenWithPoseView::SetupDefaultColumnsIfNeeded()
807{
808	// in case there were errors getting some columns
809	if (CountColumns() != 0)
810		return;
811
812	BColumn* nameColumn = new BColumn(B_TRANSLATE("Name"), 125,
813		B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true);
814	AddColumn(nameColumn);
815
816	BColumn* relationColumn = new BColumn(B_TRANSLATE("Relation"), 100,
817		B_ALIGN_LEFT, kAttrOpenWithRelation, B_STRING_TYPE, false, false);
818	AddColumn(relationColumn);
819
820	AddColumn(new BColumn(B_TRANSLATE("Location"), 225,
821		B_ALIGN_LEFT, kAttrPath, B_STRING_TYPE, true, false));
822	AddColumn(new BColumn(B_TRANSLATE("Version"), 70,
823		B_ALIGN_LEFT, kAttrAppVersion, B_STRING_TYPE, false, false));
824
825	// sort by relation and by name
826	SetPrimarySort(relationColumn->AttrHash());
827	SetSecondarySort(nameColumn->AttrHash());
828}
829
830
831bool
832OpenWithPoseView::AddPosesThreadValid(const entry_ref*) const
833{
834	return true;
835}
836
837
838void
839OpenWithPoseView::CreatePoses(Model** models, PoseInfo* poseInfoArray,
840	int32 count, BPose** resultingPoses, bool insertionSort,
841	int32* lastPoseIndexPtr, BRect* boundsPtr, bool forceDraw)
842{
843	// overridden to try to select the preferred handling app
844	_inherited::CreatePoses(models, poseInfoArray, count, resultingPoses,
845		insertionSort, lastPoseIndexPtr, boundsPtr, forceDraw);
846
847	if (resultingPoses != NULL) {
848		for (int32 index = 0; index < count; index++) {
849			if (resultingPoses[index] && fHaveCommonPreferredApp
850				&& *(models[index]->EntryRef()) == fPreferredRef) {
851				// this is our preferred app, select it's pose
852				SelectPose(resultingPoses[index],
853					IndexOfPose(resultingPoses[index]));
854			}
855		}
856	}
857}
858
859
860void
861OpenWithPoseView::KeyDown(const char* bytes, int32 count)
862{
863	if (bytes[0] == B_TAB) {
864		// just shift the focus, don't tab to the next pose
865		BView::KeyDown(bytes, count);
866	} else
867		_inherited::KeyDown(bytes, count);
868}
869
870
871void
872OpenWithPoseView::SaveState(AttributeStreamNode* node)
873{
874	_inherited::SaveState(node);
875}
876
877
878void
879OpenWithPoseView::RestoreState(AttributeStreamNode* node)
880{
881	_inherited::RestoreState(node);
882	fViewState->SetViewMode(kListMode);
883}
884
885
886void
887OpenWithPoseView::SaveState(BMessage &message) const
888{
889	_inherited::SaveState(message);
890}
891
892
893void
894OpenWithPoseView::RestoreState(const BMessage &message)
895{
896	_inherited::RestoreState(message);
897	fViewState->SetViewMode(kListMode);
898}
899
900
901void
902OpenWithPoseView::SavePoseLocations(BRect*)
903{
904}
905
906
907void
908OpenWithPoseView::MoveSelectionToTrash(bool)
909{
910}
911
912
913void
914OpenWithPoseView::MoveSelectionTo(BPoint, BPoint, BContainerWindow*)
915{
916}
917
918
919void
920OpenWithPoseView::MoveSelectionInto(Model*, BContainerWindow*, bool, bool)
921{
922}
923
924
925bool
926OpenWithPoseView::Represents(const node_ref*) const
927{
928	return false;
929}
930
931
932bool
933OpenWithPoseView::Represents(const entry_ref*) const
934{
935	return false;
936}
937
938
939bool
940OpenWithPoseView::HandleMessageDropped(BMessage* DEBUG_ONLY(message))
941{
942#if DEBUG
943	// in debug mode allow tweaking the colors
944	const rgb_color* color;
945	ssize_t size;
946	// handle roColour-style color drops
947	if (message->FindData("RGBColor", 'RGBC', (const void**)&color, &size)
948			== B_OK) {
949		SetViewColor(*color);
950		SetLowColor(*color);
951		Invalidate();
952		return true;
953	}
954#endif
955	return false;
956}
957
958
959int32
960OpenWithPoseView::OpenWithRelation(const Model* model) const
961{
962	OpenWithContainerWindow* window = ContainerWindow();
963
964	return SearchForSignatureEntryList::Relation(window->EntryList(),
965		model, fHaveCommonPreferredApp ? &fPreferredRef : 0, 0);
966}
967
968
969void
970OpenWithPoseView::OpenWithRelationDescription(const Model* model,
971	BString* description) const
972{
973	OpenWithContainerWindow* window = ContainerWindow();
974
975	SearchForSignatureEntryList::RelationDescription(window->EntryList(),
976		model, description, fHaveCommonPreferredApp ? &fPreferredRef : 0, 0);
977}
978
979
980//  #pragma mark - OpenWithRefFilter
981
982
983OpenWithRefFilter::OpenWithRefFilter(SearchForSignatureEntryList* iterator,
984	const BMessage *entryList, entry_ref* preferredRef)
985	:
986	fIterator(iterator),
987	fEntryList(entryList),
988	fPreferredRef(preferredRef)
989{
990}
991
992
993bool
994OpenWithRefFilter::Filter(const entry_ref* ref, BNode* node, stat_beos* st,
995	const char* filetype)
996{
997	Model *model = new Model(ref, true, true);
998	bool canOpen = fIterator->CanOpenWithFilter(model, fEntryList,
999		fPreferredRef);
1000	delete model;
1001
1002	return canOpen;
1003}
1004
1005
1006//	#pragma mark -
1007
1008
1009RelationCachingModelProxy::RelationCachingModelProxy(Model* model)
1010	:
1011	fModel(model),
1012	fRelation(kUnknownRelation)
1013{
1014}
1015
1016
1017RelationCachingModelProxy::~RelationCachingModelProxy()
1018{
1019	delete fModel;
1020}
1021
1022
1023int32
1024RelationCachingModelProxy::Relation(SearchForSignatureEntryList* iterator,
1025	BMessage* entries) const
1026{
1027	if (fRelation == kUnknownRelation)
1028		fRelation = iterator->Relation(entries, fModel);
1029
1030	return fRelation;
1031}
1032
1033
1034//	#pragma mark - OpenWithMenu
1035
1036
1037OpenWithMenu::OpenWithMenu(const char* label, const BMessage* entriesToOpen,
1038	BWindow* parentWindow, BHandler* target)
1039	:
1040	BSlowMenu(label),
1041	fEntriesToOpen(*entriesToOpen),
1042	target(target),
1043	fIterator(NULL),
1044	fSupportingAppList(NULL),
1045	fParentWindow(parentWindow)
1046{
1047	InitIconPreloader();
1048
1049	SetFont(be_plain_font);
1050
1051	// too long to have triggers
1052	SetTriggersEnabled(false);
1053}
1054
1055
1056OpenWithMenu::OpenWithMenu(const char* label, const BMessage* entriesToOpen,
1057	BWindow* parentWindow, const BMessenger &messenger)
1058	:
1059	BSlowMenu(label),
1060	fEntriesToOpen(*entriesToOpen),
1061	target(NULL),
1062	fMessenger(messenger),
1063	fIterator(NULL),
1064	fSupportingAppList(NULL),
1065	fParentWindow(parentWindow)
1066{
1067	InitIconPreloader();
1068
1069	SetFont(be_plain_font);
1070
1071	// too long to have triggers
1072	SetTriggersEnabled(false);
1073}
1074
1075
1076namespace BPrivate {
1077
1078int
1079SortByRelation(const RelationCachingModelProxy* proxy1,
1080	const RelationCachingModelProxy* proxy2, void* castToMenu)
1081{
1082	OpenWithMenu* menu = (OpenWithMenu*)castToMenu;
1083
1084	// find out the relations of app models to the opened entries
1085	int32 relation1 = proxy1->Relation(menu->fIterator, &menu->fEntriesToOpen);
1086	int32 relation2 = proxy2->Relation(menu->fIterator, &menu->fEntriesToOpen);
1087
1088	// relation with the lowest number goes first
1089	if (relation1 < relation2)
1090		return 1;
1091	else if (relation1 > relation2)
1092		return -1;
1093
1094	// relations match
1095	return 0;
1096}
1097
1098
1099int
1100SortByName(const RelationCachingModelProxy* proxy1,
1101	const RelationCachingModelProxy* proxy2, void* castToMenu)
1102{
1103	BCollator collator;
1104	BLocale::Default()->GetCollator(&collator);
1105
1106	// sort by app name
1107	int nameDiff = collator.Compare(proxy1->fModel->Name(),
1108		proxy2->fModel->Name());
1109	if (nameDiff < 0)
1110		return -1;
1111	else if (nameDiff > 0)
1112		return 1;
1113
1114	// if app names match, sort by volume name
1115	BVolume volume1(proxy1->fModel->NodeRef()->device);
1116	BVolume volume2(proxy2->fModel->NodeRef()->device);
1117	char volumeName1[B_FILE_NAME_LENGTH];
1118	char volumeName2[B_FILE_NAME_LENGTH];
1119	if (volume1.InitCheck() == B_OK && volume2.InitCheck() == B_OK
1120		&& volume1.GetName(volumeName1) == B_OK
1121		&& volume2.GetName(volumeName2) == B_OK) {
1122		int volumeNameDiff = collator.Compare(volumeName1, volumeName2);
1123		if (volumeNameDiff < 0)
1124			return -1;
1125		else if (volumeNameDiff > 0)
1126			return 1;
1127	}
1128
1129	// app names and volume names match
1130	return 0;
1131}
1132
1133
1134int
1135SortByRelationAndName(const RelationCachingModelProxy* proxy1,
1136	const RelationCachingModelProxy* proxy2, void* castToMenu)
1137{
1138	int relationDiff = SortByRelation(proxy1, proxy2, castToMenu);
1139	if (relationDiff != 0)
1140		return relationDiff;
1141
1142	int nameDiff = SortByName(proxy1, proxy2, castToMenu);
1143	if (nameDiff != 0)
1144		return nameDiff;
1145
1146	// relations, app names and volume names all match
1147	return 0;
1148}
1149
1150} // namespace BPrivate
1151
1152
1153bool
1154OpenWithMenu::StartBuildingItemList()
1155{
1156	fIterator = new SearchForSignatureEntryList(false);
1157	// push all the supporting apps from all the entries into the
1158	// search for signature iterator
1159	EachEntryRef(&fEntriesToOpen, AddOneRefSignatures, fIterator, 100);
1160	// add superhandlers
1161	AddSupportingAppForTypeToQuery(fIterator, B_FILE_MIMETYPE);
1162
1163	fHaveCommonPreferredApp = fIterator->GetPreferredApp(&fPreferredRef);
1164	status_t error = fIterator->Rewind();
1165	if (error != B_OK) {
1166		PRINT(("failed to initialize iterator %s\n", strerror(error)));
1167		return false;
1168	}
1169
1170	fSupportingAppList = new BObjectList<RelationCachingModelProxy>(20, true);
1171
1172	//queryRetrieval = new BStopWatch("get next entry on BQuery");
1173	return true;
1174}
1175
1176
1177bool
1178OpenWithMenu::AddNextItem()
1179{
1180	BEntry entry;
1181	if (fIterator->GetNextEntry(&entry) != B_OK)
1182		return false;
1183
1184	Model* model = new Model(&entry, true);
1185	if (model->InitCheck() != B_OK
1186		|| !fIterator->CanOpenWithFilter(model, &fEntriesToOpen,
1187			(fHaveCommonPreferredApp ? &fPreferredRef : 0))) {
1188		// only allow executables, filter out multiple copies of the Tracker,
1189		// filter out version that don't list the correct types, etc.
1190		delete model;
1191	} else
1192		fSupportingAppList->AddItem(new RelationCachingModelProxy(model));
1193
1194	return true;
1195}
1196
1197
1198void
1199OpenWithMenu::DoneBuildingItemList()
1200{
1201	// sort list by name and volume name first to fill out labels
1202	fSupportingAppList->SortItems(SortByName, this);
1203
1204	int32 count = fSupportingAppList->CountItems();
1205
1206	bool nameRepeats[count];
1207	bool volumeRepeats[count];
1208	// initialize to false
1209	memset(nameRepeats, 0, sizeof(bool) * count);
1210	memset(volumeRepeats, 0, sizeof(bool) * count);
1211
1212	std::map<RelationCachingModelProxy*, BString> labels;
1213
1214	BCollator collator;
1215	BLocale::Default()->GetCollator(&collator);
1216
1217	// check if each app is unique
1218	for (int32 index = 0; index < count - 1; index++) {
1219		// the list is sorted, compare adjacent models
1220		Model* model = fSupportingAppList->ItemAt(index)->fModel;
1221		Model* next = fSupportingAppList->ItemAt(index + 1)->fModel;
1222
1223		// check if name repeats
1224		if (collator.Compare(model->Name(), next->Name()) == 0) {
1225			nameRepeats[index] = nameRepeats[index + 1] = true;
1226
1227			// check if volume name repeats
1228			BVolume volume(model->NodeRef()->device);
1229			BVolume nextVol(next->NodeRef()->device);
1230			char volumeName[B_FILE_NAME_LENGTH];
1231			char nextVolName[B_FILE_NAME_LENGTH];
1232			if (volume.InitCheck() == B_OK && nextVol.InitCheck() == B_OK
1233				&& volume.GetName(volumeName) == B_OK
1234				&& nextVol.GetName(nextVolName) == B_OK
1235				&& collator.Compare(volumeName, nextVolName) == 0) {
1236				volumeRepeats[index] = volumeRepeats[index + 1] = true;
1237			}
1238		}
1239	}
1240
1241	BFont font;
1242	GetFont(&font);
1243
1244	// fill out the item labels
1245	for (int32 index = 0; index < count; index++) {
1246		RelationCachingModelProxy* proxy
1247			= fSupportingAppList->ItemAt(index);
1248		Model* model = proxy->fModel;
1249		BString label;
1250
1251		if (!nameRepeats[index]) {
1252			// one of a kind, print the app name
1253			label = model->Name();
1254		} else {
1255			// name repeats, check if same volume
1256			if (!volumeRepeats[index]) {
1257				// different volume, print
1258				// [volume name] app name
1259				BVolume volume(model->NodeRef()->device);
1260				if (volume.InitCheck() == B_OK) {
1261					char volumeName[B_FILE_NAME_LENGTH];
1262					if (volume.GetName(volumeName) == B_OK)
1263						label << "[" << volumeName << "] ";
1264				}
1265				label << model->Name();
1266			} else {
1267				// same volume, print full path
1268				BPath path;
1269				BEntry entry(model->EntryRef());
1270				if (entry.GetPath(&path) != B_OK) {
1271					PRINT(("stale entry ref %s\n", model->Name()));
1272					continue;
1273				}
1274				label = path.Path();
1275			}
1276			font.TruncateString(&label, B_TRUNCATE_MIDDLE,
1277				kMaxMenuWidthFactor * be_control_look->DefaultLabelSpacing());
1278		}
1279
1280#if DEBUG
1281		BString relationDescription;
1282		fIterator->RelationDescription(&fEntriesToOpen, model,
1283			&relationDescription);
1284		label << " (" << relationDescription << ")";
1285#endif
1286
1287		labels[proxy] = label;
1288	}
1289
1290	// sort again by relation and name after labels are filled out
1291	fSupportingAppList->SortItems(SortByRelationAndName, this);
1292
1293	// add apps as menu items
1294	int32 lastRelation = -1;
1295	for (int32 index = 0; index < count; index++) {
1296		RelationCachingModelProxy* proxy = fSupportingAppList->ItemAt(index);
1297		Model* model = proxy->fModel;
1298
1299		// divide different relations of opening with a separator
1300		int32 relation = proxy->Relation(fIterator, &fEntriesToOpen);
1301		if (lastRelation != -1 && relation != lastRelation)
1302			AddSeparatorItem();
1303
1304		lastRelation = relation;
1305
1306		// build message
1307		BMessage* message = new BMessage(fEntriesToOpen);
1308		message->AddRef("handler", model->EntryRef());
1309		BContainerWindow* window
1310			= dynamic_cast<BContainerWindow*>(fParentWindow);
1311		if (window != NULL) {
1312			message->AddData("nodeRefsToClose", B_RAW_TYPE,
1313				window->TargetModel()->NodeRef(), sizeof(node_ref));
1314		}
1315
1316		// add item
1317		ModelMenuItem* item = new ModelMenuItem(model,
1318			labels.find(proxy)->second.String(), message);
1319		AddItem(item);
1320
1321		// mark preferred app item
1322		if (fHaveCommonPreferredApp && *(model->EntryRef()) == fPreferredRef) {
1323			//PRINT(("marking item for % as preferred", model->Name()));
1324			item->SetMarked(true);
1325		}
1326	}
1327
1328	// target the menu
1329	if (target != NULL)
1330		SetTargetForItems(target);
1331	else
1332		SetTargetForItems(fMessenger);
1333
1334	if (CountItems() == 0) {
1335		BMenuItem* item = new BMenuItem(B_TRANSLATE("no supporting apps"), 0);
1336		item->SetEnabled(false);
1337		AddItem(item);
1338	}
1339}
1340
1341
1342void
1343OpenWithMenu::ClearMenuBuildingState()
1344{
1345	delete fIterator;
1346	fIterator = NULL;
1347	delete fSupportingAppList;
1348	fSupportingAppList = NULL;
1349}
1350
1351
1352//	#pragma mark - SearchForSignatureEntryList
1353
1354
1355SearchForSignatureEntryList::SearchForSignatureEntryList(bool canAddAllApps)
1356	:
1357	fIteratorList(NULL),
1358	fSignatures(20, true),
1359	fPreferredAppCount(0),
1360	fPreferredAppForFileCount(0),
1361	fGenericFilesOnly(true),
1362	fCanAddAllApps(canAddAllApps),
1363	fFoundOneNonSuperHandler(false)
1364{
1365}
1366
1367
1368SearchForSignatureEntryList::~SearchForSignatureEntryList()
1369{
1370	delete fIteratorList;
1371}
1372
1373
1374void
1375SearchForSignatureEntryList::PushUniqueSignature(const char* str)
1376{
1377	// do a unique add
1378	if (fSignatures.EachElement(FindOne, (void*)str))
1379		return;
1380
1381	fSignatures.AddItem(new BString(str));
1382}
1383
1384
1385status_t
1386SearchForSignatureEntryList::GetNextEntry(BEntry* entry, bool)
1387{
1388	return fIteratorList->GetNextEntry(entry);
1389}
1390
1391
1392status_t
1393SearchForSignatureEntryList::GetNextRef(entry_ref* ref)
1394{
1395	return fIteratorList->GetNextRef(ref);
1396}
1397
1398
1399int32
1400SearchForSignatureEntryList::GetNextDirents(struct dirent* buffer,
1401	size_t length, int32 count)
1402{
1403	return fIteratorList->GetNextDirents(buffer, length, count);
1404}
1405
1406
1407struct AddOneTermParams {
1408	BString* result;
1409	bool first;
1410};
1411
1412
1413static const BString*
1414AddOnePredicateTerm(const BString* item, void* castToParams)
1415{
1416	AddOneTermParams* params = (AddOneTermParams*)castToParams;
1417	if (!params->first)
1418		(*params->result) << " || ";
1419	(*params->result) << kAttrAppSignature << " = " << item->String();
1420
1421	params->first = false;
1422
1423	return 0;
1424}
1425
1426
1427status_t
1428SearchForSignatureEntryList::Rewind()
1429{
1430	if (fIteratorList)
1431		return fIteratorList->Rewind();
1432
1433	if (!fSignatures.CountItems())
1434		return ENOENT;
1435
1436	// build up the iterator
1437	fIteratorList = new CachedEntryIteratorList(false);
1438		// We cannot sort the cached inodes, as CanOpenWithFilter() relies
1439		// on the fact that ConditionalAllAppsIterator results come last.
1440
1441	// build the predicate string by oring queries for the individual
1442	// signatures
1443	BString predicateString;
1444
1445	AddOneTermParams params;
1446	params.result = &predicateString;
1447	params.first = true;
1448
1449	fSignatures.EachElement(AddOnePredicateTerm, &params);
1450
1451	ASSERT(predicateString.Length());
1452//	PRINT(("query predicate %s\n", predicateString.String()));
1453	fIteratorList->AddItem(new TWalkerWrapper(
1454		new BTrackerPrivate::TQueryWalker(predicateString.String())));
1455	fIteratorList->AddItem(new ConditionalAllAppsIterator(this));
1456
1457	return fIteratorList->Rewind();
1458}
1459
1460
1461int32
1462SearchForSignatureEntryList::CountEntries()
1463{
1464	return 0;
1465}
1466
1467
1468bool
1469SearchForSignatureEntryList::GetPreferredApp(entry_ref* ref) const
1470{
1471	if (fPreferredAppCount == 1)
1472		*ref = fPreferredRef;
1473
1474	return fPreferredAppCount == 1;
1475}
1476
1477
1478void
1479SearchForSignatureEntryList::TrySettingPreferredApp(const entry_ref* ref)
1480{
1481	if (!fPreferredAppCount) {
1482		fPreferredRef = *ref;
1483		fPreferredAppCount++;
1484	} else if (fPreferredRef != *ref) {
1485		// if more than one, will not return any
1486		fPreferredAppCount++;
1487	}
1488}
1489
1490
1491void
1492SearchForSignatureEntryList::TrySettingPreferredAppForFile(const entry_ref* ref)
1493{
1494	if (!fPreferredAppForFileCount) {
1495		fPreferredRefForFile = *ref;
1496		fPreferredAppForFileCount++;
1497	} else if (fPreferredRefForFile != *ref) {
1498		// if more than one, will not return any
1499		fPreferredAppForFileCount++;
1500	}
1501}
1502
1503
1504void
1505SearchForSignatureEntryList::NonGenericFileFound()
1506{
1507	fGenericFilesOnly = false;
1508}
1509
1510
1511bool
1512SearchForSignatureEntryList::GenericFilesOnly() const
1513{
1514	return fGenericFilesOnly;
1515}
1516
1517
1518bool
1519SearchForSignatureEntryList::ShowAllApplications() const
1520{
1521	return fCanAddAllApps && !fFoundOneNonSuperHandler;
1522}
1523
1524
1525int32
1526SearchForSignatureEntryList::Relation(const Model* nodeModel,
1527	const Model* applicationModel)
1528{
1529	int32 supportsMimeType = applicationModel->SupportsMimeType(
1530		nodeModel->MimeType(), 0, true);
1531	switch (supportsMimeType) {
1532		case kDoesNotSupportType:
1533			return kNoRelation;
1534
1535		case kSuperhandlerModel:
1536			return kSuperhandler;
1537
1538		case kModelSupportsSupertype:
1539			return kSupportsSupertype;
1540
1541		case kModelSupportsType:
1542			return kSupportsType;
1543	}
1544
1545	TRESPASS();
1546	return kNoRelation;
1547}
1548
1549
1550int32
1551SearchForSignatureEntryList::Relation(const BMessage* entriesToOpen,
1552	const Model* model) const
1553{
1554	return Relation(entriesToOpen, model,
1555		fPreferredAppCount == 1 ? &fPreferredRef : 0,
1556		fPreferredAppForFileCount == 1 ? &fPreferredRefForFile : 0);
1557}
1558
1559
1560void
1561SearchForSignatureEntryList::RelationDescription(const BMessage* entriesToOpen,
1562	const Model* model, BString* description) const
1563{
1564	RelationDescription(entriesToOpen, model, description,
1565		fPreferredAppCount == 1 ? &fPreferredRef : 0,
1566		fPreferredAppForFileCount == 1 ? &fPreferredRefForFile : 0);
1567}
1568
1569
1570int32
1571SearchForSignatureEntryList::Relation(const BMessage* entriesToOpen,
1572	const Model* applicationModel, const entry_ref* preferredApp,
1573	const entry_ref* preferredAppForFile)
1574{
1575	for (int32 index = 0; ; index++) {
1576		entry_ref ref;
1577		if (entriesToOpen->FindRef("refs", index, &ref) != B_OK)
1578			break;
1579
1580		// need to init a model so that typeless folders etc. will still
1581		// appear to have a mime type
1582
1583		Model model(&ref, true, true);
1584		if (model.InitCheck())
1585			continue;
1586
1587		int32 result = Relation(&model, applicationModel);
1588		if (result != kNoRelation) {
1589			if (preferredAppForFile
1590				&& *applicationModel->EntryRef() == *preferredAppForFile) {
1591				return kPreferredForFile;
1592			}
1593
1594			if (result == kSupportsType && preferredApp
1595				&& *applicationModel->EntryRef() == *preferredApp) {
1596				// application matches cached preferred app, we are done
1597				return kPreferredForType;
1598			}
1599
1600			return result;
1601		}
1602	}
1603
1604	return kNoRelation;
1605}
1606
1607
1608void
1609SearchForSignatureEntryList::RelationDescription(const BMessage* entriesToOpen,
1610	const Model* applicationModel, BString* description,
1611	const entry_ref* preferredApp, const entry_ref* preferredAppForFile)
1612{
1613	for (int32 index = 0; ;index++) {
1614		entry_ref ref;
1615		if (entriesToOpen->FindRef("refs", index, &ref) != B_OK)
1616			break;
1617
1618		if (preferredAppForFile && ref == *preferredAppForFile) {
1619			description->SetTo(B_TRANSLATE("Preferred for file"));
1620			return;
1621		}
1622
1623		Model model(&ref, true, true);
1624		if (model.InitCheck())
1625			continue;
1626
1627		BMimeType mimeType;
1628		int32 result = Relation(&model, applicationModel);
1629		switch (result) {
1630			case kDoesNotSupportType:
1631				continue;
1632
1633			case kSuperhandler:
1634				description->SetTo(B_TRANSLATE("Handles any file"));
1635				return;
1636
1637			case kSupportsSupertype:
1638			{
1639				mimeType.SetTo(model.MimeType());
1640				// status_t result = mimeType.GetSupertype(&mimeType);
1641
1642				char* type = (char*)mimeType.Type();
1643				char* tmp = strchr(type, '/');
1644				if (tmp != NULL)
1645					*tmp = '\0';
1646
1647				//PRINT(("getting supertype for %s, result %s, got %s\n",
1648				//	model.MimeType(), strerror(result), mimeType.Type()));
1649				description->SetTo(B_TRANSLATE("Handles any %type"));
1650				//*description += mimeType.Type();
1651				description->ReplaceFirst("%type", type);
1652				return;
1653			}
1654
1655			case kSupportsType:
1656			{
1657				mimeType.SetTo(model.MimeType());
1658
1659				if (preferredApp != NULL
1660					&& *applicationModel->EntryRef() == *preferredApp) {
1661					// application matches cached preferred app, we are done
1662					description->SetTo(B_TRANSLATE("Preferred for %type"));
1663				} else
1664					description->SetTo(B_TRANSLATE("Handles %type"));
1665
1666				char shortDescription[256];
1667				if (mimeType.GetShortDescription(shortDescription) == B_OK)
1668					description->ReplaceFirst("%type", shortDescription);
1669				else
1670					description->ReplaceFirst("%type", mimeType.Type());
1671
1672				return;
1673			}
1674		}
1675	}
1676
1677	description->SetTo(B_TRANSLATE("Does not handle file"));
1678}
1679
1680
1681bool
1682SearchForSignatureEntryList::CanOpenWithFilter(const Model* appModel,
1683	const BMessage* entriesToOpen, const entry_ref* preferredApp)
1684{
1685	ThrowOnAssert(appModel != NULL);
1686
1687	if (!appModel->IsExecutable() || !appModel->Node()) {
1688		// weed out non-executable
1689#if xDEBUG
1690		BPath path;
1691		BEntry entry(appModel->EntryRef());
1692		entry.GetPath(&path);
1693		PRINT(("filtering out %s- not executable \n", path.Path()));
1694#endif
1695		return false;
1696	}
1697
1698	if (strcasecmp(appModel->MimeType(), B_APP_MIME_TYPE) != 0) {
1699		// filter out pe containers on PPC etc.
1700		return false;
1701	}
1702
1703	BFile* file = dynamic_cast<BFile*>(appModel->Node());
1704	ASSERT(file != NULL);
1705
1706	char signature[B_MIME_TYPE_LENGTH];
1707	if (GetAppSignatureFromAttr(file, signature) == B_OK
1708		&& strcasecmp(signature, kTrackerSignature) == 0) {
1709		// special case the Tracker - make sure only the running copy is
1710		// in the list
1711		app_info trackerInfo;
1712		if (*appModel->EntryRef() != trackerInfo.ref) {
1713			// this is an inactive copy of the Tracker, remove it
1714
1715#if xDEBUG
1716			BPath path1;
1717			BPath path2;
1718			BEntry entry(appModel->EntryRef());
1719			entry.GetPath(&path1);
1720
1721			BEntry entry2(&trackerInfo.ref);
1722			entry2.GetPath(&path2);
1723
1724			PRINT(("filtering out %s, sig %s, active Tracker at %s, "
1725				   "result %s, refName %s\n",
1726				path1.Path(), signature, path2.Path(),
1727				strerror(be_roster->GetActiveAppInfo(&trackerInfo)),
1728				trackerInfo.ref.name));
1729#endif
1730			return false;
1731		}
1732	}
1733
1734	if (FSInTrashDir(appModel->EntryRef()))
1735		return false;
1736
1737	if (ShowAllApplications()) {
1738		// don't check for these if we didn't look for every single app
1739		// to not slow filtering down
1740		uint32 flags;
1741		BAppFileInfo appFileInfo(dynamic_cast<BFile*>(appModel->Node()));
1742		if (appFileInfo.GetAppFlags(&flags) != B_OK)
1743			return false;
1744
1745		if ((flags & B_BACKGROUND_APP) || (flags & B_ARGV_ONLY))
1746			return false;
1747
1748		if (!signature[0])
1749			// weed out apps with empty signatures
1750			return false;
1751	}
1752
1753	int32 relation = Relation(entriesToOpen, appModel, preferredApp, 0);
1754	if (relation == kNoRelation && !ShowAllApplications()) {
1755#if xDEBUG
1756		BPath path;
1757		BEntry entry(appModel->EntryRef());
1758		entry.GetPath(&path);
1759
1760		PRINT(("filtering out %s, does not handle any of opened files\n",
1761			path.Path()));
1762#endif
1763		return false;
1764	}
1765
1766	if (relation != kNoRelation && relation != kSuperhandler
1767		&& !fGenericFilesOnly) {
1768		// we hit at least one app that is not a superhandler and
1769		// handles the document
1770		fFoundOneNonSuperHandler = true;
1771	}
1772
1773	return true;
1774}
1775
1776
1777//	#pragma mark - ConditionalAllAppsIterator
1778
1779
1780ConditionalAllAppsIterator::ConditionalAllAppsIterator(
1781	SearchForSignatureEntryList* parent)
1782	:
1783	fParent(parent),
1784	fWalker(NULL)
1785{
1786}
1787
1788
1789void
1790ConditionalAllAppsIterator::Instantiate()
1791{
1792	if (fWalker != NULL)
1793		return;
1794
1795	BString lookForAppsPredicate;
1796	lookForAppsPredicate << "(" << kAttrAppSignature << " = \"*\" ) && ( "
1797		<< kAttrMIMEType << " = " << B_APP_MIME_TYPE << " ) ";
1798	fWalker
1799		= new BTrackerPrivate::TQueryWalker(lookForAppsPredicate.String());
1800}
1801
1802
1803ConditionalAllAppsIterator::~ConditionalAllAppsIterator()
1804{
1805	delete fWalker;
1806}
1807
1808
1809status_t
1810ConditionalAllAppsIterator::GetNextEntry(BEntry* entry, bool traverse)
1811{
1812	if (!Iterate())
1813		return B_ENTRY_NOT_FOUND;
1814
1815	Instantiate();
1816	return fWalker->GetNextEntry(entry, traverse);
1817}
1818
1819
1820status_t
1821ConditionalAllAppsIterator::GetNextRef(entry_ref* ref)
1822{
1823	if (!Iterate())
1824		return B_ENTRY_NOT_FOUND;
1825
1826	Instantiate();
1827	return fWalker->GetNextRef(ref);
1828}
1829
1830
1831int32
1832ConditionalAllAppsIterator::GetNextDirents(struct dirent* buffer,
1833	size_t length, int32 count)
1834{
1835	if (!Iterate())
1836		return 0;
1837
1838	Instantiate();
1839	return fWalker->GetNextDirents(buffer, length, count);
1840}
1841
1842
1843status_t
1844ConditionalAllAppsIterator::Rewind()
1845{
1846	if (!Iterate())
1847		return B_OK;
1848
1849	Instantiate();
1850	return fWalker->Rewind();
1851}
1852
1853
1854int32
1855ConditionalAllAppsIterator::CountEntries()
1856{
1857	if (!Iterate())
1858		return 0;
1859
1860	Instantiate();
1861	return fWalker->CountEntries();
1862}
1863
1864
1865bool
1866ConditionalAllAppsIterator::Iterate() const
1867{
1868	return fParent->ShowAllApplications();
1869}
1870