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