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
36 #include "ContainerWindow.h"
37
38 #include <Alert.h>
39 #include <AppFileInfo.h>
40 #include <Application.h>
41 #include <Catalog.h>
42 #include <ControlLook.h>
43 #include <Debug.h>
44 #include <Directory.h>
45 #include <Entry.h>
46 #include <FindDirectory.h>
47 #include <GridView.h>
48 #include <GroupLayout.h>
49 #include <Keymap.h>
50 #include <Locale.h>
51 #include <MenuBar.h>
52 #include <MenuItem.h>
53 #include <NodeMonitor.h>
54 #include <Path.h>
55 #include <PopUpMenu.h>
56 #include <Roster.h>
57 #include <Screen.h>
58 #include <UnicodeChar.h>
59 #include <Volume.h>
60 #include <VolumeRoster.h>
61 #include <WindowPrivate.h>
62
63 #include <fs_attr.h>
64 #include <image.h>
65 #include <strings.h>
66 #include <stdlib.h>
67
68 #include "Attributes.h"
69 #include "AutoDeleter.h"
70 #include "AutoLock.h"
71 #include "BackgroundImage.h"
72 #include "Commands.h"
73 #include "CountView.h"
74 #include "DeskWindow.h"
75 #include "DraggableContainerIcon.h"
76 #include "FSClipboard.h"
77 #include "FSUndoRedo.h"
78 #include "FSUtils.h"
79 #include "FavoritesMenu.h"
80 #include "FindPanel.h"
81 #include "IconMenuItem.h"
82 #include "MimeTypes.h"
83 #include "Model.h"
84 #include "MountMenu.h"
85 #include "NavMenu.h"
86 #include "Navigator.h"
87 #include "OpenWithWindow.h"
88 #include "PoseView.h"
89 #include "QueryContainerWindow.h"
90 #include "SelectionWindow.h"
91 #include "TemplatesMenu.h"
92 #include "Thread.h"
93 #include "TitleView.h"
94 #include "Tracker.h"
95 #include "TrackerSettings.h"
96
97
98 #undef B_TRANSLATION_CONTEXT
99 #define B_TRANSLATION_CONTEXT "ContainerWindow"
100
101
102 #ifdef _IMPEXP_BE
103 _IMPEXP_BE
104 #endif
105 void do_minimize_team(BRect zoomRect, team_id team, bool zoom);
106
107
108 struct AddOneAddOnParams {
109 BObjectList<BMenuItem>* primaryList;
110 BObjectList<BMenuItem>* secondaryList;
111 };
112
113 struct StaggerOneParams {
114 bool rectFromParent;
115 };
116
117
118 BRect BContainerWindow::sNewWindRect;
119 static int32 sWindowStaggerBy;
120
121 LockingList<AddOnShortcut>* BContainerWindow::fAddOnsList
122 = new LockingList<struct AddOnShortcut>(10, true);
123
124
125 namespace BPrivate {
126
127
128 int
CompareContainerWindowNodeRef(const BContainerWindow * item1,const BContainerWindow * item2)129 CompareContainerWindowNodeRef(const BContainerWindow* item1, const BContainerWindow* item2)
130 {
131 const node_ref* ref1 = item1->TargetModel()->NodeRef();
132 const node_ref* ref2 = item2->TargetModel()->NodeRef();
133 if (*ref1 < *ref2)
134 return -1;
135 else if (*ref1 == *ref2)
136 return 0;
137 else
138 return 1;
139 }
140
141
142 filter_result
ActivateWindowFilter(BMessage *,BHandler ** target,BMessageFilter *)143 ActivateWindowFilter(BMessage*, BHandler** target, BMessageFilter*)
144 {
145 BView* view = dynamic_cast<BView*>(*target);
146
147 // activate the window if no PoseView or DraggableContainerIcon had been
148 // pressed (those will activate the window themselves, if necessary)
149 if (view != NULL
150 && dynamic_cast<BPoseView*>(view) == NULL
151 && dynamic_cast<DraggableContainerIcon*>(view) == NULL
152 && view->Window() != NULL) {
153 view->Window()->Activate(true);
154 }
155
156 return B_DISPATCH_MESSAGE;
157 }
158
159
160 } // namespace BPrivate
161
162
163 static int32
AddOnMenuGenerate(const entry_ref * addOnRef,BMenu * menu,BContainerWindow * window)164 AddOnMenuGenerate(const entry_ref* addOnRef, BMenu* menu, BContainerWindow* window)
165 {
166 BEntry entry(addOnRef);
167 BPath path;
168 status_t result = entry.InitCheck();
169 if (result != B_OK)
170 return result;
171
172 result = entry.GetPath(&path);
173 if (result != B_OK)
174 return result;
175
176 image_id addOnImage = load_add_on(path.Path());
177 if (addOnImage < 0)
178 return addOnImage;
179
180 void (*populateMenu)(BMessage*, BMenu*, BHandler*);
181 result = get_image_symbol(addOnImage, "populate_menu", 2,
182 (void**)&populateMenu);
183 if (result < 0) {
184 PRINT(("Couldn't find populate_menu\n"));
185 unload_add_on(addOnImage);
186 return result;
187 }
188
189 BMessage* message = window->AddOnMessage(B_TRACKER_ADDON_MESSAGE);
190 message->AddRef("addon_ref", addOnRef);
191
192 // call add-on code
193 (*populateMenu)(message, menu, window->PoseView());
194
195 unload_add_on(addOnImage);
196 return B_OK;
197 }
198
199
200 static status_t
RunAddOnMessageThread(BMessage * message,void *)201 RunAddOnMessageThread(BMessage *message, void *)
202 {
203 entry_ref addOnRef;
204 BEntry entry;
205 BPath path;
206 status_t result = message->FindRef("addon_ref", &addOnRef);
207 image_id addOnImage;
208
209 if (result != B_OK)
210 goto end;
211
212 entry = BEntry(&addOnRef);
213 result = entry.InitCheck();
214 if (result != B_OK)
215 goto end;
216
217 result = entry.GetPath(&path);
218 if (result != B_OK)
219 goto end;
220
221 addOnImage = load_add_on(path.Path());
222 if (addOnImage < 0) {
223 result = addOnImage;
224 goto end;
225 }
226 void (*messageReceived)(BMessage*);
227 result = get_image_symbol(addOnImage, "message_received", 2,
228 (void**)&messageReceived);
229
230 if (result < 0) {
231 PRINT(("Couldn't find message_received\n"));
232 unload_add_on(addOnImage);
233 goto end;
234 }
235 // call add-on code
236 (*messageReceived)(message);
237 unload_add_on(addOnImage);
238 return B_OK;
239
240 end:
241 BString buffer(B_TRANSLATE("Error %error loading add-On %name."));
242 buffer.ReplaceFirst("%error", strerror(result));
243 buffer.ReplaceFirst("%name", addOnRef.name);
244
245 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
246 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
247 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
248 alert->Go();
249
250 return result;
251 }
252
253
254 static void
AddOneAddOn(const Model * model,const char * name,uint32 shortcut,uint32 modifiers,bool primary,void * context,BContainerWindow * window,BMenu * menu)255 AddOneAddOn(const Model* model, const char* name, uint32 shortcut,
256 uint32 modifiers, bool primary, void* context,
257 BContainerWindow* window, BMenu* menu)
258 {
259 AddOneAddOnParams* params = (AddOneAddOnParams*)context;
260
261 BMessage* message = new BMessage(kLoadAddOn);
262 message->AddRef("refs", model->EntryRef());
263
264 ModelMenuItem* item;
265 try {
266 item = new ModelMenuItem(model, name, message,
267 (char)shortcut, modifiers);
268 } catch (...) {
269 delete message;
270 return;
271 }
272
273 const entry_ref* addOnRef = model->EntryRef();
274 AddOnMenuGenerate(addOnRef, menu, window);
275
276 if (primary)
277 params->primaryList->AddItem(item);
278 else
279 params->secondaryList->AddItem(item);
280 }
281
282
283 static int32
AddOnThread(BMessage * refsMessage,entry_ref addOnRef,entry_ref directoryRef)284 AddOnThread(BMessage* refsMessage, entry_ref addOnRef, entry_ref directoryRef)
285 {
286 ObjectDeleter<BMessage> _(refsMessage);
287
288 BEntry entry(&addOnRef);
289 BPath path;
290 status_t result = entry.InitCheck();
291 if (result == B_OK)
292 result = entry.GetPath(&path);
293
294 if (result == B_OK) {
295 image_id addOnImage = load_add_on(path.Path());
296 if (addOnImage >= 0) {
297 void (*processRefs)(entry_ref, BMessage*, void*);
298 result = get_image_symbol(addOnImage, "process_refs", 2,
299 (void**)&processRefs);
300
301 if (result >= 0) {
302 // call add-on code
303 (*processRefs)(directoryRef, refsMessage, NULL);
304
305 unload_add_on(addOnImage);
306 return B_OK;
307 } else
308 PRINT(("couldn't find process_refs\n"));
309
310 unload_add_on(addOnImage);
311 } else
312 result = addOnImage;
313 }
314
315 BString buffer(B_TRANSLATE("Error %error loading Add-On %name."));
316 buffer.ReplaceFirst("%error", strerror(result));
317 buffer.ReplaceFirst("%name", addOnRef.name);
318
319 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
320 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
321 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
322 alert->Go();
323
324 return result;
325 }
326
327
328 static bool
NodeHasSavedState(const BNode * node)329 NodeHasSavedState(const BNode* node)
330 {
331 attr_info info;
332 return node->GetAttrInfo(kAttrWindowFrame, &info) == B_OK;
333 }
334
335
336 static bool
OffsetFrameOne(const char * DEBUG_ONLY (name),uint32,off_t,void * castToRect,void * castToParams)337 OffsetFrameOne(const char* DEBUG_ONLY(name), uint32, off_t, void* castToRect,
338 void* castToParams)
339 {
340 ASSERT(strcmp(name, kAttrWindowFrame) == 0);
341 StaggerOneParams* params = (StaggerOneParams*)castToParams;
342
343 if (!params->rectFromParent)
344 return false;
345
346 if (!castToRect)
347 return false;
348
349 ((BRect*)castToRect)->OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
350
351 return true;
352 }
353
354
355 static void
AddMimeTypeString(BStringList & list,Model * model)356 AddMimeTypeString(BStringList& list, Model* model)
357 {
358 if (model == NULL)
359 return;
360
361 const char* modelMimeType = model->MimeType();
362 if (modelMimeType == NULL || *modelMimeType == '\0')
363 return;
364
365 BString type = BString(modelMimeType);
366 if (list.HasString(type, true))
367 return;
368
369 list.Add(type);
370 }
371
372
373 // #pragma mark - BContainerWindow
374
375
BContainerWindow(LockingList<BWindow> * list,uint32 openFlags,window_look look,window_feel feel,uint32 windowFlags,uint32 workspace,bool useLayout)376 BContainerWindow::BContainerWindow(LockingList<BWindow>* list, uint32 openFlags, window_look look,
377 window_feel feel, uint32 windowFlags, uint32 workspace, bool useLayout)
378 :
379 BWindow(InitialWindowRect(feel), "TrackerWindow", look, feel, windowFlags, workspace),
380 fWindowList(list),
381 fOpenFlags(openFlags),
382 fUsesLayout(useLayout),
383 fMenuContainer(NULL),
384 fPoseContainer(NULL),
385 fBorderedView(NULL),
386 fVScrollBarContainer(NULL),
387 fCountContainer(NULL),
388 fContextMenu(NULL),
389 fFileContextMenu(NULL),
390 fWindowContextMenu(NULL),
391 fDropContextMenu(NULL),
392 fVolumeContextMenu(NULL),
393 fTrashContextMenu(NULL),
394 fDragContextMenu(NULL),
395 fMoveToItem(NULL),
396 fCopyToItem(NULL),
397 fCreateLinkItem(NULL),
398 fOpenWithItem(NULL),
399 fNavigationItem(NULL),
400 fMenuBar(NULL),
401 fDraggableIcon(NULL),
402 fNavigator(NULL),
403 fPoseView(NULL),
404 fAttrMenu(NULL),
405 fWindowMenu(NULL),
406 fFileMenu(NULL),
407 fArrangeByMenu(NULL),
408 fSelectionWindow(NULL),
409 fTaskLoop(NULL),
410 fStateNeedsSaving(false),
411 fBackgroundImage(NULL),
412 fSavedZoomRect(0, 0, -1, -1),
413 fDragMessage(NULL),
414 fCachedTypesList(NULL),
415 fSaveStateIsEnabled(true),
416 fIsWatchingPath(false)
417 {
418 InitIconPreloader();
419
420 if (list != NULL) {
421 ASSERT(list->IsLocked());
422 list->AddItem(this);
423 }
424
425 if (fUsesLayout) {
426 SetFlags(Flags() | B_AUTO_UPDATE_SIZE_LIMITS);
427
428 fRootLayout = new BGroupLayout(B_VERTICAL, 0);
429 fRootLayout->SetInsets(0);
430 SetLayout(fRootLayout);
431 fRootLayout->Owner()->AdoptSystemColors();
432
433 fMenuContainer = new BGroupView(B_HORIZONTAL, 0);
434 fRootLayout->AddView(fMenuContainer);
435
436 fPoseContainer = new BGridView(0.0, 0.0);
437 fRootLayout->AddView(fPoseContainer);
438
439 fBorderedView = new BorderedView;
440 fPoseContainer->GridLayout()->AddView(fBorderedView, 0, 1);
441
442 fCountContainer = new BGroupView(B_HORIZONTAL, 0);
443 fPoseContainer->GridLayout()->AddView(fCountContainer, 0, 2);
444 }
445
446 AddCommonFilter(new BMessageFilter(B_MOUSE_DOWN, ActivateWindowFilter));
447
448 Run();
449
450 // watch out for settings changes
451 TTracker* tracker = dynamic_cast<TTracker*>(be_app);
452 if (tracker != NULL && tracker->Lock()) {
453 tracker->StartWatching(this, kWindowsShowFullPathChanged);
454 tracker->StartWatching(this, kSingleWindowBrowseChanged);
455 tracker->StartWatching(this, kShowNavigatorChanged);
456 tracker->Unlock();
457 }
458
459 // ToDo: remove me once we have undo/redo menu items
460 // (that is, move them to AddShortcuts())
461 AddShortcut('Z', B_COMMAND_KEY, new BMessage(B_UNDO), this);
462 AddShortcut('Z', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(B_REDO), this);
463 }
464
465
~BContainerWindow()466 BContainerWindow::~BContainerWindow()
467 {
468 ASSERT(IsLocked());
469
470 // stop the watchers
471 TTracker* tracker = dynamic_cast<TTracker*>(be_app);
472 if (tracker != NULL && tracker->Lock()) {
473 tracker->StopWatching(this, kWindowsShowFullPathChanged);
474 tracker->StopWatching(this, kSingleWindowBrowseChanged);
475 tracker->StopWatching(this, kShowNavigatorChanged);
476 tracker->Unlock();
477 }
478
479 delete fTaskLoop;
480 delete fBackgroundImage;
481 delete fDragMessage;
482 delete fCachedTypesList;
483
484 if (fSelectionWindow != NULL && fSelectionWindow->Lock())
485 fSelectionWindow->Quit();
486 }
487
488
489 BRect
InitialWindowRect(window_feel feel)490 BContainerWindow::InitialWindowRect(window_feel feel)
491 {
492 if (!sNewWindRect.IsValid()) {
493 const float labelSpacing = be_control_look->DefaultLabelSpacing();
494 // approximately (85, 50, 548, 280) with default spacing
495 sNewWindRect = BRect(labelSpacing * 14, labelSpacing * 8,
496 labelSpacing * 91, labelSpacing * 46);
497 sWindowStaggerBy = (int32)(labelSpacing * 3.0f);
498 }
499
500 if (feel != kDesktopWindowFeel)
501 return sNewWindRect;
502
503 // do not offset desktop window
504 BRect result = sNewWindRect;
505 result.OffsetTo(0, 0);
506 return result;
507 }
508
509
510 void
Minimize(bool minimize)511 BContainerWindow::Minimize(bool minimize)
512 {
513 if (minimize && (modifiers() & B_OPTION_KEY) != 0)
514 do_minimize_team(BRect(0, 0, 0, 0), be_app->Team(), true);
515 else
516 _inherited::Minimize(minimize);
517 }
518
519
520 bool
QuitRequested()521 BContainerWindow::QuitRequested()
522 {
523 // this is a response to the DeskBar sending us a B_QUIT, when it really
524 // means to say close all your windows. It might be better to have it
525 // send a kCloseAllWindows message and have windowless apps stay running,
526 // which is what we will do for the Tracker
527 if (CurrentMessage() != NULL
528 && ((CurrentMessage()->FindInt32("modifiers") & B_CONTROL_KEY)) != 0) {
529 be_app->PostMessage(kCloseAllWindows);
530 }
531
532 Hide();
533 // this will close the window instantly, even if
534 // the file system is very busy right now
535 return true;
536 }
537
538
539 void
Quit()540 BContainerWindow::Quit()
541 {
542 // get rid of context menus
543 if (fNavigationItem) {
544 BMenu* menu = fNavigationItem->Menu();
545 if (menu != NULL)
546 menu->RemoveItem(fNavigationItem);
547
548 delete fNavigationItem;
549 fNavigationItem = NULL;
550 }
551
552 if (fOpenWithItem != NULL && fOpenWithItem->Menu() == NULL) {
553 delete fOpenWithItem;
554 fOpenWithItem = NULL;
555 }
556
557 if (fMoveToItem != NULL && fMoveToItem->Menu() == NULL) {
558 delete fMoveToItem;
559 fMoveToItem = NULL;
560 }
561
562 if (fCopyToItem != NULL && fCopyToItem->Menu() == NULL) {
563 delete fCopyToItem;
564 fCopyToItem = NULL;
565 }
566
567 if (fCreateLinkItem != NULL && fCreateLinkItem->Menu() == NULL) {
568 delete fCreateLinkItem;
569 fCreateLinkItem = NULL;
570 }
571
572 if (fAttrMenu != NULL && fAttrMenu->Supermenu() == NULL) {
573 delete fAttrMenu;
574 fAttrMenu = NULL;
575 }
576
577 delete fFileContextMenu;
578 fFileContextMenu = NULL;
579
580 delete fWindowContextMenu;
581 fWindowContextMenu = NULL;
582
583 delete fDropContextMenu;
584 fDropContextMenu = NULL;
585
586 delete fVolumeContextMenu;
587 fVolumeContextMenu = NULL;
588
589 delete fDragContextMenu;
590 fDragContextMenu = NULL;
591
592 delete fTrashContextMenu;
593 fTrashContextMenu = NULL;
594
595 int32 windowCount = 0;
596
597 // This is a deadlock code sequence - need to change this
598 // to acquire the window list while this container window is unlocked
599 if (fWindowList != NULL) {
600 AutoLock<LockingList<BWindow> > lock(fWindowList);
601 if (lock.IsLocked()) {
602 fWindowList->RemoveItem(this, false);
603 windowCount = fWindowList->CountItems();
604 }
605 }
606
607 if (StateNeedsSaving())
608 SaveState();
609
610 if (fWindowList != NULL && windowCount == 0)
611 be_app->PostMessage(B_QUIT_REQUESTED);
612
613 _inherited::Quit();
614 }
615
616
617 BPoseView*
NewPoseView(Model * model,uint32 viewMode)618 BContainerWindow::NewPoseView(Model* model, uint32 viewMode)
619 {
620 return new BPoseView(model, viewMode);
621 }
622
623
624 void
CreatePoseView(Model * model)625 BContainerWindow::CreatePoseView(Model* model)
626 {
627 fPoseView = NewPoseView(model, kListMode);
628 fBorderedView->GroupLayout()->AddView(fPoseView);
629 fBorderedView->GroupLayout()->SetInsets(1, 0, 1, 1);
630 fBorderedView->EnableBorderHighlight(false);
631
632 TrackerSettings settings;
633 if (settings.SingleWindowBrowse() && model->IsDirectory() && !PoseView()->IsFilePanel()) {
634 fNavigator = new BNavigator(model);
635 fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2);
636 if (!settings.ShowNavigator())
637 fNavigator->Hide();
638 }
639
640 SetPathWatchingEnabled(settings.ShowNavigator()
641 || settings.ShowFullPathInTitleBar());
642 }
643
644
645 void
AddContextMenus()646 BContainerWindow::AddContextMenus()
647 {
648 // create context sensitive menus
649 fFileContextMenu = new BPopUpMenu("FileContext", false, false);
650 AddFileContextMenus(fFileContextMenu);
651
652 fVolumeContextMenu = new BPopUpMenu("VolumeContext", false, false);
653 AddVolumeContextMenus(fVolumeContextMenu);
654
655 fWindowContextMenu = new BPopUpMenu("WindowContext", false, false);
656 AddWindowContextMenus(fWindowContextMenu);
657
658 fDropContextMenu = new BPopUpMenu("DropContext", false, false);
659 AddDropContextMenus(fDropContextMenu);
660
661 fDragContextMenu = new BPopUpNavMenu("DragContext");
662 // will get added and built dynamically in ShowContextMenu
663
664 fTrashContextMenu = new BPopUpMenu("TrashContext", false, false);
665 AddTrashContextMenus(fTrashContextMenu);
666 }
667
668
669 void
RepopulateMenus()670 BContainerWindow::RepopulateMenus()
671 {
672 // Avoid these menus to be destroyed:
673 if (fMoveToItem != NULL && fMoveToItem->Menu() != NULL)
674 fMoveToItem->Menu()->RemoveItem(fMoveToItem);
675
676 if (fCopyToItem != NULL && fCopyToItem->Menu() != NULL)
677 fCopyToItem->Menu()->RemoveItem(fCopyToItem);
678
679 if (fCreateLinkItem != NULL && fCreateLinkItem->Menu() != NULL)
680 fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem);
681
682 if (fOpenWithItem != NULL && fOpenWithItem->Menu() != NULL) {
683 fOpenWithItem->Menu()->RemoveItem(fOpenWithItem);
684 delete fOpenWithItem;
685 fOpenWithItem = NULL;
686 }
687
688 if (fNavigationItem != NULL) {
689 BMenu* menu = fNavigationItem->Menu();
690 if (menu != NULL) {
691 menu->RemoveItem(fNavigationItem);
692 BMenuItem* item = menu->RemoveItem((int32)0);
693 ASSERT(item != fNavigationItem);
694 delete item;
695 }
696 }
697
698 delete fFileContextMenu;
699 fFileContextMenu = new BPopUpMenu("FileContext", false, false);
700 AddFileContextMenus(fFileContextMenu);
701
702 delete fWindowContextMenu;
703 fWindowContextMenu = new BPopUpMenu("WindowContext", false, false);
704 AddWindowContextMenus(fWindowContextMenu);
705
706 if (fMenuBar != NULL) {
707 fMenuBar->RemoveItem(fFileMenu);
708 delete fFileMenu;
709 fFileMenu = new BMenu(B_TRANSLATE("File"));
710 AddFileMenu(fFileMenu);
711 fMenuBar->AddItem(fFileMenu);
712
713 fMenuBar->RemoveItem(fWindowMenu);
714 delete fWindowMenu;
715 fWindowMenu = new BMenu(B_TRANSLATE("Window"));
716 fMenuBar->AddItem(fWindowMenu);
717 AddWindowMenu(fWindowMenu);
718
719 // just create the attribute, decide to add it later
720 fMenuBar->RemoveItem(fAttrMenu);
721 delete fAttrMenu;
722 fAttrMenu = new BMenu(B_TRANSLATE("Attributes"));
723 NewAttributesMenu(fAttrMenu);
724 if (PoseView()->ViewMode() == kListMode)
725 ShowAttributesMenu();
726
727 PopulateArrangeByMenu(fArrangeByMenu);
728
729 int32 selectCount = PoseView()->CountSelected();
730
731 SetupOpenWithMenu(fFileMenu);
732 SetupMoveCopyMenus(selectCount ? PoseView()->SelectionList()
733 ->FirstItem()->TargetModel()->EntryRef() : NULL,
734 fFileMenu);
735 }
736 }
737
738
739 void
Init(const BMessage * message)740 BContainerWindow::Init(const BMessage* message)
741 {
742 // pose view is expected to be setup at this point
743 if (PoseView() == NULL)
744 return;
745
746 // deal with new unconfigured folders
747 if (NeedsDefaultStateSetup())
748 SetupDefaultState();
749
750 if (ShouldAddScrollBars())
751 PoseView()->AddScrollBars();
752
753 fMoveToItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Move to"),
754 kMoveSelectionTo, this));
755 fCopyToItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Copy to"),
756 kCopySelectionTo, this));
757 fCreateLinkItem = new BMenuItem(new BNavMenu(B_TRANSLATE("Create link"),
758 kCreateLink, this), new BMessage(kCreateLink));
759
760 TrackerSettings settings;
761
762 if (ShouldAddMenus()) {
763 fMenuBar = new BMenuBar("MenuBar");
764 fMenuContainer->GroupLayout()->AddView(fMenuBar);
765 AddMenus();
766
767 if (!TargetModel()->IsRoot() && !TargetModel()->IsTrash())
768 _AddFolderIcon();
769 } else {
770 // add equivalents of the menu shortcuts to the menuless
771 // desktop window
772 AddShortcuts();
773 }
774
775 AddContextMenus();
776 AddShortcut('T', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kDeleteSelection), PoseView());
777 AddShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY, new BMessage(kCleanupAll), PoseView());
778 AddShortcut('Q', B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY | B_CONTROL_KEY,
779 new BMessage(kQuitTracker));
780
781 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY, new BMessage(kOpenSelection), PoseView());
782
783 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
784
785 #if DEBUG
786 // add some debugging shortcuts
787 AddShortcut('D', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dbug'),
788 PoseView());
789 AddShortcut('C', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpcc'),
790 PoseView());
791 AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY, new BMessage('dpfl'),
792 PoseView());
793 AddShortcut('F', B_COMMAND_KEY | B_CONTROL_KEY | B_OPTION_KEY,
794 new BMessage('dpfL'), PoseView());
795 #endif
796
797 BKeymap keymap;
798 if (keymap.SetToCurrent() == B_OK) {
799 BObjectList<const char> unmodified(3, true);
800 if (keymap.GetModifiedCharacters("+", B_SHIFT_KEY, 0, &unmodified)
801 == B_OK) {
802 int32 count = unmodified.CountItems();
803 for (int32 i = 0; i < count; i++) {
804 uint32 key = BUnicodeChar::FromUTF8(unmodified.ItemAt(i));
805 if (!HasShortcut(key, 0)) {
806 // Add semantic zoom in shortcut, bug #6692
807 BMessage* increaseSize = new BMessage(kIconMode);
808 increaseSize->AddInt32("scale", 1);
809 AddShortcut(key, B_COMMAND_KEY, increaseSize, PoseView());
810 }
811 }
812 }
813 unmodified.MakeEmpty();
814 }
815
816 if (message != NULL)
817 RestoreState(*message);
818 else
819 RestoreState();
820
821 if (ShouldAddMenus() && PoseView()->ViewMode() == kListMode) {
822 // for now only show attributes in list view
823 // eventually enable attribute menu to allow users to select
824 // using different attributes as titles in icon view modes
825 ShowAttributesMenu();
826 }
827 CheckScreenIntersect();
828
829 bool isListMode = PoseView()->ViewMode() == kListMode;
830 if (fBackgroundImage != NULL && !PoseView()->IsDesktop() && !isListMode)
831 fBackgroundImage->Show(PoseView(), current_workspace());
832
833 Show();
834
835 // done showing, turn the B_NO_WORKSPACE_ACTIVATION flag off;
836 // it was on to prevent workspace jerking during boot
837 SetFlags(Flags() & ~B_NO_WORKSPACE_ACTIVATION);
838 }
839
840
841 void
InitLayout()842 BContainerWindow::InitLayout()
843 {
844 fBorderedView->GroupLayout()->AddView(0, PoseView()->TitleView());
845
846 fCountContainer->GroupLayout()->AddView(PoseView()->CountView(), 0.25f);
847
848 bool forFilePanel = PoseView()->IsFilePanel();
849 if (!forFilePanel) {
850 // Eliminate the extra borders
851 fPoseContainer->GridLayout()->SetInsets(-1, 0, -1, -1);
852 fCountContainer->GroupLayout()->SetInsets(0, -1, 0, 0);
853 }
854
855 if (PoseView()->VScrollBar() != NULL) {
856 fVScrollBarContainer = new BGroupView(B_VERTICAL, 0);
857 fVScrollBarContainer->GroupLayout()->AddView(PoseView()->VScrollBar());
858 fVScrollBarContainer->GroupLayout()->SetInsets(-1, forFilePanel ? 0 : -1,
859 0, 0);
860 fPoseContainer->GridLayout()->AddView(fVScrollBarContainer, 1, 1);
861 }
862 if (PoseView()->HScrollBar() != NULL) {
863 BGroupView* hScrollBarContainer = new BGroupView(B_VERTICAL, 0);
864 hScrollBarContainer->GroupLayout()->AddView(PoseView()->HScrollBar());
865 hScrollBarContainer->GroupLayout()->SetInsets(0, -1, 0,
866 forFilePanel ? 0 : -1);
867 fCountContainer->GroupLayout()->AddView(hScrollBarContainer);
868
869 BSize size = PoseView()->HScrollBar()->MinSize();
870 if (forFilePanel) {
871 // Count view height is 1px smaller than scroll bar because it has
872 // no upper border.
873 size.height -= 1;
874 }
875 PoseView()->CountView()->SetExplicitMinSize(size);
876 }
877 }
878
879
880 void
RestoreState()881 BContainerWindow::RestoreState()
882 {
883 UpdateTitle();
884
885 WindowStateNodeOpener opener(this, false);
886 RestoreWindowState(opener.StreamNode());
887 PoseView()->Init(opener.StreamNode());
888
889 RestoreStateCommon();
890 }
891
892
893 void
RestoreState(const BMessage & message)894 BContainerWindow::RestoreState(const BMessage &message)
895 {
896 UpdateTitle();
897
898 RestoreWindowState(message);
899 PoseView()->Init(message);
900
901 RestoreStateCommon();
902 }
903
904
905 void
RestoreStateCommon()906 BContainerWindow::RestoreStateCommon()
907 {
908 if (fUsesLayout)
909 InitLayout();
910
911 if (BootedInSafeMode())
912 // don't pick up backgrounds in safe mode
913 return;
914
915 bool isDesktop = PoseView()->IsDesktop();
916
917 WindowStateNodeOpener opener(this, false);
918 if (!TargetModel()->IsRoot() && opener.Node() != NULL) {
919 // don't pick up background image for root disks
920 // to do this, would have to have a unique attribute for the
921 // disks window that doesn't collide with the desktop
922 // for R4 this was not done to make things simpler
923 // the default image will still work though
924 fBackgroundImage = BackgroundImage::GetBackgroundImage(opener.Node(), isDesktop);
925 // look for background image info in the window's node
926 }
927
928 BNode defaultingNode;
929 if (fBackgroundImage == NULL && !isDesktop
930 && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) {
931 // look for background image info in the source for defaults
932 fBackgroundImage = BackgroundImage::GetBackgroundImage(&defaultingNode, isDesktop);
933 }
934 }
935
936
937 void
UpdateTitle()938 BContainerWindow::UpdateTitle()
939 {
940 // set title to full path, if necessary
941 if (TrackerSettings().ShowFullPathInTitleBar()) {
942 // use the Entry's full path
943 BPath path;
944 TargetModel()->GetPath(&path);
945 SetTitle(path.Path());
946 } else {
947 // use the default look
948 SetTitle(TargetModel()->Name());
949 }
950
951 if (Navigator() != NULL)
952 Navigator()->UpdateLocation(TargetModel(), kActionUpdatePath);
953 }
954
955
956 void
UpdateBackgroundImage()957 BContainerWindow::UpdateBackgroundImage()
958 {
959 if (BootedInSafeMode())
960 return;
961
962 WindowStateNodeOpener opener(this, false);
963
964 if (!TargetModel()->IsRoot() && opener.Node() != NULL) {
965 fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, opener.Node(),
966 PoseView()->IsDesktop(), PoseView());
967 }
968
969 // look for background image info in the window's node
970 BNode defaultingNode;
971 if (!fBackgroundImage && !PoseView()->IsDesktop()
972 && DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode)) {
973 // look for background image info in the source for defaults
974 fBackgroundImage = BackgroundImage::Refresh(fBackgroundImage, &defaultingNode,
975 PoseView()->IsDesktop(), PoseView());
976 }
977 }
978
979
980 void
FrameResized(float,float)981 BContainerWindow::FrameResized(float, float)
982 {
983 if (PoseView() != NULL && !PoseView()->IsDesktop()) {
984 BRect extent = PoseView()->Extent();
985 float offsetX = extent.left - PoseView()->Bounds().left;
986 float offsetY = extent.top - PoseView()->Bounds().top;
987
988 // scroll when the size augmented, there is a negative offset
989 // and we have resized over the bottom right corner of the extent
990 BPoint scroll(B_ORIGIN);
991 if (offsetX < 0 && PoseView()->Bounds().right > extent.right
992 && Bounds().Width() > fPreviousBounds.Width()) {
993 scroll.x = std::max(fPreviousBounds.Width() - Bounds().Width(),
994 offsetX);
995 }
996
997 if (offsetY < 0 && PoseView()->Bounds().bottom > extent.bottom
998 && Bounds().Height() > fPreviousBounds.Height()) {
999 scroll.y = std::max(fPreviousBounds.Height() - Bounds().Height(),
1000 offsetY);
1001 }
1002
1003 if (scroll != B_ORIGIN)
1004 PoseView()->ScrollBy(scroll.x, scroll.y);
1005
1006 PoseView()->UpdateScrollRange();
1007 PoseView()->ResetPosePlacementHint();
1008 }
1009
1010 fPreviousBounds = Bounds();
1011 if (IsActive())
1012 fStateNeedsSaving = true;
1013 }
1014
1015
1016 void
FrameMoved(BPoint)1017 BContainerWindow::FrameMoved(BPoint)
1018 {
1019 if (IsActive())
1020 fStateNeedsSaving = true;
1021 }
1022
1023
1024 void
WorkspacesChanged(uint32,uint32)1025 BContainerWindow::WorkspacesChanged(uint32, uint32)
1026 {
1027 if (IsActive())
1028 fStateNeedsSaving = true;
1029 }
1030
1031
1032 void
ViewModeChanged(uint32 oldMode,uint32 newMode)1033 BContainerWindow::ViewModeChanged(uint32 oldMode, uint32 newMode)
1034 {
1035 if (fBackgroundImage == NULL)
1036 return;
1037
1038 if (newMode == kListMode)
1039 fBackgroundImage->Remove();
1040 else if (oldMode == kListMode)
1041 fBackgroundImage->Show(PoseView(), current_workspace());
1042 }
1043
1044
1045 void
CheckScreenIntersect()1046 BContainerWindow::CheckScreenIntersect()
1047 {
1048 BScreen screen(this);
1049 BRect screenFrame(screen.Frame());
1050 BRect frame(Frame());
1051
1052 if (sNewWindRect.bottom > screenFrame.bottom)
1053 sNewWindRect.OffsetTo(85, 50);
1054
1055 if (sNewWindRect.right > screenFrame.right)
1056 sNewWindRect.OffsetTo(85, 50);
1057
1058 if (!frame.Intersects(screenFrame))
1059 MoveTo(sNewWindRect.LeftTop());
1060 }
1061
1062
1063 void
SaveState(bool hide)1064 BContainerWindow::SaveState(bool hide)
1065 {
1066 if (SaveStateIsEnabled()) {
1067 WindowStateNodeOpener opener(this, true);
1068 if (opener.StreamNode() != NULL)
1069 SaveWindowState(opener.StreamNode());
1070
1071 if (hide)
1072 Hide();
1073
1074 if (opener.StreamNode())
1075 PoseView()->SaveState(opener.StreamNode());
1076
1077 fStateNeedsSaving = false;
1078 }
1079 }
1080
1081
1082 void
SaveState(BMessage & message) const1083 BContainerWindow::SaveState(BMessage& message) const
1084 {
1085 if (SaveStateIsEnabled()) {
1086 SaveWindowState(message);
1087 PoseView()->SaveState(message);
1088 }
1089 }
1090
1091
1092 bool
StateNeedsSaving() const1093 BContainerWindow::StateNeedsSaving() const
1094 {
1095 return PoseView() != NULL && (fStateNeedsSaving || PoseView()->StateNeedsSaving());
1096 }
1097
1098
1099 status_t
GetLayoutState(BNode * node,BMessage * message)1100 BContainerWindow::GetLayoutState(BNode* node, BMessage* message)
1101 {
1102 if (node == NULL || message == NULL)
1103 return B_BAD_VALUE;
1104
1105 status_t result = node->InitCheck();
1106 if (result != B_OK)
1107 return result;
1108
1109 // ToDo: get rid of this, use AttrStream instead
1110 node->RewindAttrs();
1111 char attrName[256];
1112 while (node->GetNextAttrName(attrName) == B_OK) {
1113 attr_info info;
1114 if (node->GetAttrInfo(attrName, &info) != B_OK)
1115 continue;
1116
1117 // filter out attributes that are not related to window position
1118 // and column resizing
1119 // more can be added as needed
1120 if (strcmp(attrName, kAttrWindowFrame) != 0
1121 && strcmp(attrName, kAttrColumns) != 0
1122 && strcmp(attrName, kAttrViewState) != 0
1123 && strcmp(attrName, kAttrColumnsForeign) != 0
1124 && strcmp(attrName, kAttrViewStateForeign) != 0) {
1125 continue;
1126 }
1127
1128 char* buffer = new char[info.size];
1129 if (node->ReadAttr(attrName, info.type, 0, buffer,
1130 (size_t)info.size) == info.size) {
1131 message->AddData(attrName, info.type, buffer, (ssize_t)info.size);
1132 }
1133 delete[] buffer;
1134 }
1135
1136 return B_OK;
1137 }
1138
1139
1140 status_t
SetLayoutState(BNode * node,const BMessage * message)1141 BContainerWindow::SetLayoutState(BNode* node, const BMessage* message)
1142 {
1143 status_t result = node->InitCheck();
1144 if (result != B_OK)
1145 return result;
1146
1147 for (int32 globalIndex = 0; ;) {
1148 #if B_BEOS_VERSION_DANO
1149 const char* name;
1150 #else
1151 char* name;
1152 #endif
1153 type_code type;
1154 int32 count;
1155 status_t result = message->GetInfo(B_ANY_TYPE, globalIndex, &name,
1156 &type, &count);
1157 if (result != B_OK)
1158 break;
1159
1160 for (int32 index = 0; index < count; index++) {
1161 const void* buffer;
1162 ssize_t size;
1163 result = message->FindData(name, type, index, &buffer, &size);
1164 if (result != B_OK) {
1165 PRINT(("error reading %s \n", name));
1166 return result;
1167 }
1168
1169 if (node->WriteAttr(name, type, 0, buffer,
1170 (size_t)size) != size) {
1171 PRINT(("error writing %s \n", name));
1172 return result;
1173 }
1174 globalIndex++;
1175 }
1176 }
1177
1178 return B_OK;
1179 }
1180
1181
1182 bool
ShouldAddMenus() const1183 BContainerWindow::ShouldAddMenus() const
1184 {
1185 return true;
1186 }
1187
1188
1189 bool
ShouldAddScrollBars() const1190 BContainerWindow::ShouldAddScrollBars() const
1191 {
1192 return true;
1193 }
1194
1195
1196 Model*
TargetModel() const1197 BContainerWindow::TargetModel() const
1198 {
1199 return PoseView()->TargetModel();
1200 }
1201
1202
1203 void
SelectionChanged()1204 BContainerWindow::SelectionChanged()
1205 {
1206 }
1207
1208
1209 void
Zoom(BPoint,float,float)1210 BContainerWindow::Zoom(BPoint, float, float)
1211 {
1212 BRect oldZoomRect(fSavedZoomRect);
1213 fSavedZoomRect = Frame();
1214 ResizeToFit();
1215
1216 if (fSavedZoomRect == Frame() && oldZoomRect.IsValid())
1217 ResizeTo(oldZoomRect.Width(), oldZoomRect.Height());
1218 }
1219
1220
1221 void
ResizeToFit()1222 BContainerWindow::ResizeToFit()
1223 {
1224 BScreen screen(this);
1225 BRect screenFrame(screen.Frame());
1226
1227 screenFrame.InsetBy(5, 5);
1228 BMessage decoratorSettings;
1229 GetDecoratorSettings(&decoratorSettings);
1230
1231 float tabHeight = 15;
1232 BRect tabRect;
1233 if (decoratorSettings.FindRect("tab frame", &tabRect) == B_OK)
1234 tabHeight = tabRect.Height();
1235 screenFrame.top += tabHeight;
1236
1237 BRect frame(Frame());
1238
1239 float widthDiff = frame.Width() - PoseView()->Frame().Width();
1240 float heightDiff = frame.Height() - PoseView()->Frame().Height();
1241
1242 // move frame left top on screen
1243 BPoint leftTop(frame.LeftTop());
1244 leftTop.ConstrainTo(screenFrame);
1245 frame.OffsetTo(leftTop);
1246
1247 // resize to extent size
1248 BRect extent(PoseView()->Extent());
1249 frame.right = frame.left + extent.Width() + widthDiff;
1250 frame.bottom = frame.top + extent.Height() + heightDiff;
1251
1252 // make sure entire window fits on screen
1253 frame = frame & screenFrame;
1254
1255 ResizeTo(frame.Width(), frame.Height());
1256 MoveTo(frame.LeftTop());
1257 PoseView()->DisableScrollBars();
1258
1259 // scroll if there is an offset
1260 PoseView()->ScrollBy(
1261 extent.left - PoseView()->Bounds().left,
1262 extent.top - PoseView()->Bounds().top);
1263
1264 PoseView()->UpdateScrollRange();
1265 PoseView()->EnableScrollBars();
1266 }
1267
1268
1269 void
MessageReceived(BMessage * message)1270 BContainerWindow::MessageReceived(BMessage* message)
1271 {
1272 switch (message->what) {
1273 case B_CUT:
1274 case B_COPY:
1275 case B_PASTE:
1276 case B_SELECT_ALL:
1277 {
1278 BView* view = CurrentFocus();
1279 if (dynamic_cast<BTextView*>(view) == NULL) {
1280 // The selected item is not a BTextView, so forward the
1281 // message to the PoseView.
1282 if (PoseView() != NULL)
1283 PostMessage(message, PoseView());
1284 } else {
1285 // Since we catch the generic clipboard shortcuts in a way that
1286 // means the BTextView will never get them, we must
1287 // manually forward them ourselves.
1288 //
1289 // However, we have to take care to not forward the custom
1290 // clipboard messages, else we would wind up in infinite
1291 // recursion.
1292 PostMessage(message, view);
1293 }
1294 break;
1295 }
1296
1297 case kCutMoreSelectionToClipboard:
1298 case kCopyMoreSelectionToClipboard:
1299 case kPasteLinksFromClipboard:
1300 if (PoseView() != NULL)
1301 PostMessage(message, PoseView());
1302 break;
1303
1304 case B_UNDO: {
1305 BView* view = CurrentFocus();
1306 if (dynamic_cast<BTextView*>(view) == NULL) {
1307 FSUndo();
1308 } else {
1309 view->MessageReceived(message);
1310 }
1311 break;
1312 }
1313
1314 case B_REDO: {
1315 BView* view = CurrentFocus();
1316 if (dynamic_cast<BTextView*>(view) == NULL) {
1317 FSRedo();
1318 } else {
1319 view->MessageReceived(message);
1320 }
1321 break;
1322 }
1323
1324 case kOpenParentDir:
1325 PostMessage(message, PoseView());
1326 break;
1327
1328 case kNewFolder:
1329 PostMessage(message, PoseView());
1330 break;
1331
1332 case kNewTemplateSubmenu:
1333 {
1334 entry_ref ref;
1335 if (message->FindRef("refs", &ref) == B_OK)
1336 _NewTemplateSubmenu(ref);
1337 break;
1338 }
1339
1340 case kRestoreState:
1341 if (message->HasMessage("state")) {
1342 BMessage state;
1343 message->FindMessage("state", &state);
1344 Init(&state);
1345 } else
1346 Init();
1347 break;
1348
1349 case kResizeToFit:
1350 ResizeToFit();
1351 break;
1352
1353 case kLoadAddOn:
1354 LoadAddOn(message);
1355 break;
1356
1357 case kCopySelectionTo:
1358 {
1359 entry_ref ref;
1360 if (message->FindRef("refs", &ref) != B_OK)
1361 break;
1362
1363 BRoster().AddToRecentFolders(&ref);
1364
1365 Model model(&ref);
1366 if (model.InitCheck() != B_OK)
1367 break;
1368
1369 if (*model.NodeRef() == *TargetModel()->NodeRef())
1370 PoseView()->DuplicateSelection();
1371 else
1372 PoseView()->MoveSelectionInto(&model, this, true);
1373 break;
1374 }
1375
1376 case kMoveSelectionTo:
1377 {
1378 entry_ref ref;
1379 if (message->FindRef("refs", &ref) != B_OK)
1380 break;
1381
1382 BRoster().AddToRecentFolders(&ref);
1383
1384 Model model(&ref);
1385 if (model.InitCheck() != B_OK)
1386 break;
1387
1388 PoseView()->MoveSelectionInto(&model, this, false, true);
1389 break;
1390 }
1391
1392 case kCreateLink:
1393 case kCreateRelativeLink:
1394 {
1395 entry_ref ref;
1396 if (message->FindRef("refs", &ref) == B_OK) {
1397 BRoster().AddToRecentFolders(&ref);
1398
1399 Model model(&ref);
1400 if (model.InitCheck() != B_OK)
1401 break;
1402
1403 PoseView()->MoveSelectionInto(&model, this, false, false,
1404 message->what == kCreateLink,
1405 message->what == kCreateRelativeLink);
1406 } else if (!TargetModel()->IsQuery() && !TargetModel()->IsVirtualDirectory()) {
1407 // no destination specified, create link in same dir as item
1408 PoseView()->MoveSelectionInto(TargetModel(), this, false, false,
1409 message->what == kCreateLink,
1410 message->what == kCreateRelativeLink);
1411 }
1412 break;
1413 }
1414
1415 case kShowSelectionWindow:
1416 ShowSelectionWindow();
1417 break;
1418
1419 case kSelectMatchingEntries:
1420 PoseView()->SelectMatchingEntries(message);
1421 break;
1422
1423 case kFindButton:
1424 (new FindWindow())->Show();
1425 break;
1426
1427 case kQuitTracker:
1428 be_app->PostMessage(B_QUIT_REQUESTED);
1429 break;
1430
1431 case kRestoreBackgroundImage:
1432 UpdateBackgroundImage();
1433 break;
1434
1435 case kSwitchDirectory:
1436 {
1437 entry_ref ref;
1438 if (message->FindRef("refs", &ref) != B_OK)
1439 break;
1440
1441 BEntry entry;
1442 if (entry.SetTo(&ref) != B_OK)
1443 break;
1444
1445 if (StateNeedsSaving())
1446 SaveState(false);
1447
1448 bool wasInTrash = TargetModel()->IsTrash() || TargetModel()->InTrash();
1449 bool wasRoot = TargetModel()->IsRoot();
1450 bool wasVolume = TargetModel()->IsVolume();
1451
1452 // Switch dir and apply new state
1453 WindowStateNodeOpener opener(this, false);
1454 opener.SetTo(&entry, false);
1455
1456 // Update pose view and set directory type
1457 PoseView()->SwitchDir(&ref, opener.StreamNode());
1458
1459 if (wasInTrash ^ (TargetModel()->IsTrash() || TargetModel()->InTrash())
1460 || wasRoot != TargetModel()->IsRoot() || wasVolume != TargetModel()->IsVolume()) {
1461 RepopulateMenus();
1462 }
1463
1464 // skip the rest for file panel
1465 if (PoseView()->IsFilePanel())
1466 break;
1467
1468 if (Navigator() != NULL) {
1469 // update Navigation bar
1470 int32 action = message->GetInt32("action", kActionSet);
1471 Navigator()->UpdateLocation(TargetModel(), action);
1472 }
1473
1474 TrackerSettings settings;
1475 if (settings.ShowNavigator() || settings.ShowFullPathInTitleBar())
1476 SetPathWatchingEnabled(true);
1477
1478 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
1479
1480 // Update draggable folder icon
1481 if (fMenuBar != NULL) {
1482 if (!TargetModel()->IsRoot() && !TargetModel()->IsTrash()) {
1483 // Folder icon should be visible, but in single
1484 // window navigation, it might not be.
1485 if (fDraggableIcon != NULL) {
1486 IconCache::sIconCache->IconChanged(TargetModel());
1487 if (fDraggableIcon->IsHidden())
1488 fDraggableIcon->Show();
1489 fDraggableIcon->Invalidate();
1490 } else {
1491 // draggable icon visible
1492 _AddFolderIcon();
1493 }
1494 } else if (fDraggableIcon != NULL) {
1495 // hide for Root or Trash
1496 fDraggableIcon->Hide();
1497 }
1498 }
1499
1500 // Update window title
1501 UpdateTitle();
1502 break;
1503 }
1504
1505 case B_REFS_RECEIVED:
1506 if (Dragging()) {
1507 // ref in this message is the target,
1508 // the end point of the drag
1509
1510 entry_ref ref;
1511 if (message->FindRef("refs", &ref) == B_OK) {
1512 fWaitingForRefs = false;
1513 BEntry entry(&ref, true);
1514 // don't copy to printers dir
1515 if (!FSIsPrintersDir(&entry)) {
1516 if (entry.InitCheck() == B_OK
1517 && entry.IsDirectory()) {
1518 Model targetModel(&entry, true, false);
1519 BPoint dropPoint;
1520 uint32 buttons;
1521 PoseView()->GetMouse(&dropPoint, &buttons, true);
1522 PoseView()->HandleDropCommon(fDragMessage,
1523 &targetModel, NULL, PoseView(), dropPoint);
1524 }
1525 }
1526 }
1527 DragStop();
1528 }
1529 break;
1530
1531 case B_TRACKER_ADDON_MESSAGE:
1532 {
1533 _PassMessageToAddOn(message);
1534 break;
1535 }
1536
1537 case B_OBSERVER_NOTICE_CHANGE:
1538 {
1539 int32 observerWhat;
1540 if (message->FindInt32("be:observe_change_what", &observerWhat)
1541 == B_OK) {
1542 TrackerSettings settings;
1543 switch (observerWhat) {
1544 case kWindowsShowFullPathChanged:
1545 UpdateTitle();
1546 if (!IsPathWatchingEnabled()
1547 && settings.ShowFullPathInTitleBar()) {
1548 SetPathWatchingEnabled(true);
1549 }
1550 if (IsPathWatchingEnabled()
1551 && !(settings.ShowNavigator() || settings.ShowFullPathInTitleBar())) {
1552 SetPathWatchingEnabled(false);
1553 }
1554 break;
1555
1556 case kSingleWindowBrowseChanged:
1557 if (PoseView()->IsFilePanel() || PoseView()->IsDesktop()) {
1558 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
1559 break;
1560 }
1561
1562 if (settings.SingleWindowBrowse() && Navigator() == NULL
1563 && TargetModel()->IsDirectory()) {
1564 fNavigator = new BNavigator(TargetModel());
1565 fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2);
1566 fNavigator->Hide();
1567 SetPathWatchingEnabled(settings.ShowNavigator()
1568 || settings.ShowFullPathInTitleBar());
1569 }
1570
1571 if (!settings.SingleWindowBrowse() && fWindowList != NULL) {
1572 // close duplicate windows
1573 int32 windowCount = fWindowList->CountItems();
1574 BObjectList<BContainerWindow> containerList(windowCount);
1575 for (int32 index = 0; index < windowCount; index++) {
1576 BContainerWindow* window
1577 = dynamic_cast<BContainerWindow*>(fWindowList->ItemAt(index));
1578 if (window != NULL && window->TargetModel() != NULL
1579 && window->TargetModel()->NodeRef() != NULL) {
1580 containerList.AddItem(window);
1581 }
1582 }
1583
1584 windowCount = containerList.CountItems();
1585 // get the window count again as it may have changed
1586 if (windowCount > 1) {
1587 // sort containerList by node ref
1588 containerList.SortItems(CompareContainerWindowNodeRef);
1589
1590 // go backwards from second to last item to first
1591 for (int32 index = windowCount - 2; index >= 0; --index) {
1592 BContainerWindow* window = containerList.ItemAt(index);
1593 BContainerWindow* second = containerList.ItemAt(index + 1);
1594 if (window == NULL || second == NULL)
1595 continue;
1596
1597 const node_ref* windowRef = window->TargetModel()->NodeRef();
1598 const node_ref* secondRef = second->TargetModel()->NodeRef();
1599 if (*windowRef == *secondRef) {
1600 // duplicate windows found, close second window
1601 // use BMessenger::SendMessage(), safer than PostMessage()
1602 BMessenger(second).SendMessage(B_QUIT_REQUESTED);
1603 }
1604 }
1605 }
1606 }
1607
1608 if (!settings.SingleWindowBrowse() && TargetModel()->IsDesktop()) {
1609 // close the "Desktop" window, but not the Desktop
1610 this->Quit();
1611 }
1612
1613 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
1614 break;
1615
1616 case kShowNavigatorChanged:
1617 ShowNavigator(settings.ShowNavigator());
1618 if (!IsPathWatchingEnabled() && settings.ShowNavigator())
1619 SetPathWatchingEnabled(true);
1620 if (IsPathWatchingEnabled()
1621 && !(settings.ShowNavigator() || settings.ShowFullPathInTitleBar())) {
1622 SetPathWatchingEnabled(false);
1623 }
1624 SetSingleWindowBrowseShortcuts(settings.SingleWindowBrowse());
1625 break;
1626
1627 default:
1628 _inherited::MessageReceived(message);
1629 break;
1630 }
1631 }
1632 break;
1633 }
1634
1635 case B_NODE_MONITOR:
1636 UpdateTitle();
1637 break;
1638
1639 default:
1640 _inherited::MessageReceived(message);
1641 break;
1642 }
1643 }
1644
1645
1646 void
SetCutItem(BMenu * menu)1647 BContainerWindow::SetCutItem(BMenu* menu)
1648 {
1649 BMenuItem* item;
1650 if ((item = menu->FindItem(B_CUT)) == NULL
1651 && (item = menu->FindItem(kCutMoreSelectionToClipboard)) == NULL) {
1652 return;
1653 }
1654
1655 if (PoseView() != CurrentFocus())
1656 item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1657 else {
1658 if (TargetModel()->IsRoot() || TargetModel()->IsTrash()
1659 || TargetModel()->IsVirtualDirectory()) {
1660 // cannot cut files in root, trash or in a virtual directory
1661 item->SetEnabled(false);
1662 } else {
1663 item->SetEnabled(PoseView()->CountSelected() > 0
1664 && !PoseView()->SelectedVolumeIsReadOnly());
1665 }
1666 }
1667
1668 if ((modifiers() & B_SHIFT_KEY) != 0) {
1669 item->SetLabel(B_TRANSLATE("Cut more"));
1670 item->SetShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY);
1671 item->SetMessage(new BMessage(kCutMoreSelectionToClipboard));
1672 } else {
1673 item->SetLabel(B_TRANSLATE("Cut"));
1674 item->SetShortcut('X', B_COMMAND_KEY);
1675 item->SetMessage(new BMessage(B_CUT));
1676 }
1677 }
1678
1679
1680 void
SetCopyItem(BMenu * menu)1681 BContainerWindow::SetCopyItem(BMenu* menu)
1682 {
1683 BMenuItem* item;
1684 if ((item = menu->FindItem(B_COPY)) == NULL
1685 && (item = menu->FindItem(kCopyMoreSelectionToClipboard)) == NULL) {
1686 return;
1687 }
1688
1689 if (PoseView() != CurrentFocus())
1690 item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1691 else
1692 item->SetEnabled(PoseView()->CountSelected() > 0);
1693
1694 if ((modifiers() & B_SHIFT_KEY) != 0) {
1695 item->SetLabel(B_TRANSLATE("Copy more"));
1696 item->SetShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY);
1697 item->SetMessage(new BMessage(kCopyMoreSelectionToClipboard));
1698 } else {
1699 item->SetLabel(B_TRANSLATE("Copy"));
1700 item->SetShortcut('C', B_COMMAND_KEY);
1701 item->SetMessage(new BMessage(B_COPY));
1702 }
1703 }
1704
1705
1706 void
SetPasteItem(BMenu * menu)1707 BContainerWindow::SetPasteItem(BMenu* menu)
1708 {
1709 BMenuItem* item;
1710 if ((item = menu->FindItem(B_PASTE)) == NULL
1711 && (item = menu->FindItem(kPasteLinksFromClipboard)) == NULL) {
1712 return;
1713 }
1714
1715 if (PoseView() != CurrentFocus())
1716 item->SetEnabled(dynamic_cast<BTextView*>(CurrentFocus()) != NULL);
1717 else {
1718 item->SetEnabled(FSClipboardHasRefs()
1719 && !PoseView()->TargetVolumeIsReadOnly());
1720 }
1721
1722 if ((modifiers() & B_SHIFT_KEY) != 0) {
1723 item->SetLabel(B_TRANSLATE("Paste links"));
1724 item->SetShortcut('V', B_COMMAND_KEY | B_SHIFT_KEY);
1725 item->SetMessage(new BMessage(kPasteLinksFromClipboard));
1726 } else {
1727 item->SetLabel(B_TRANSLATE("Paste"));
1728 item->SetShortcut('V', B_COMMAND_KEY);
1729 item->SetMessage(new BMessage(B_PASTE));
1730 }
1731 }
1732
1733
1734 void
SetArrangeMenu(BMenu * menu)1735 BContainerWindow::SetArrangeMenu(BMenu* menu)
1736 {
1737 BMenuItem* item;
1738 if ((item = menu->FindItem(kCleanup)) == NULL
1739 && (item = menu->FindItem(kCleanupAll)) == NULL) {
1740 return;
1741 }
1742
1743 item->Menu()->SetEnabled(PoseView()->CountItems() > 0
1744 && (PoseView()->ViewMode() != kListMode));
1745
1746 BMenu* arrangeMenu;
1747
1748 if ((modifiers() & B_SHIFT_KEY) != 0) {
1749 item->SetLabel(B_TRANSLATE("Clean up all"));
1750 item->SetShortcut('K', B_COMMAND_KEY | B_SHIFT_KEY);
1751 item->SetMessage(new BMessage(kCleanupAll));
1752 arrangeMenu = item->Menu();
1753 } else {
1754 item->SetLabel(B_TRANSLATE("Clean up"));
1755 item->SetShortcut('K', B_COMMAND_KEY);
1756 item->SetMessage(new BMessage(kCleanup));
1757 arrangeMenu = item->Menu();
1758 }
1759
1760 MarkArrangeByMenu(arrangeMenu);
1761 }
1762
1763
1764 void
SetCloseItem(BMenu * menu)1765 BContainerWindow::SetCloseItem(BMenu* menu)
1766 {
1767 BMenuItem* item;
1768 if ((item = menu->FindItem(B_QUIT_REQUESTED)) == NULL
1769 && (item = menu->FindItem(kCloseAllWindows)) == NULL) {
1770 return;
1771 }
1772
1773 if ((modifiers() & B_SHIFT_KEY) != 0) {
1774 item->SetLabel(B_TRANSLATE("Close all"));
1775 item->SetShortcut('W', B_COMMAND_KEY | B_SHIFT_KEY);
1776 item->SetTarget(be_app);
1777 item->SetMessage(new BMessage(kCloseAllWindows));
1778 } else {
1779 item->SetLabel(B_TRANSLATE("Close"));
1780 item->SetShortcut('W', B_COMMAND_KEY);
1781 item->SetTarget(this);
1782 item->SetMessage(new BMessage(B_QUIT_REQUESTED));
1783 }
1784 }
1785
1786
1787 bool
IsShowing(const node_ref * node) const1788 BContainerWindow::IsShowing(const node_ref* node) const
1789 {
1790 return PoseView()->Represents(node);
1791 }
1792
1793
1794 bool
IsShowing(const entry_ref * entry) const1795 BContainerWindow::IsShowing(const entry_ref* entry) const
1796 {
1797 return PoseView()->Represents(entry);
1798 }
1799
1800
1801 void
AddMenus()1802 BContainerWindow::AddMenus()
1803 {
1804 fFileMenu = new BMenu(B_TRANSLATE("File"));
1805 AddFileMenu(fFileMenu);
1806 fMenuBar->AddItem(fFileMenu);
1807 fWindowMenu = new BMenu(B_TRANSLATE("Window"));
1808 fMenuBar->AddItem(fWindowMenu);
1809 AddWindowMenu(fWindowMenu);
1810 // just create the attribute, decide to add it later
1811 fAttrMenu = new BMenu(B_TRANSLATE("Attributes"));
1812 NewAttributesMenu(fAttrMenu);
1813 PopulateArrangeByMenu(fArrangeByMenu);
1814 }
1815
1816
1817 void
AddFileMenu(BMenu * menu)1818 BContainerWindow::AddFileMenu(BMenu* menu)
1819 {
1820 BMenuItem* item;
1821
1822 if (!PoseView()->IsFilePanel()) {
1823 menu->AddItem(new BMenuItem(B_TRANSLATE("Find" B_UTF8_ELLIPSIS),
1824 new BMessage(kFindButton), 'F'));
1825 }
1826
1827 if (!TargetModel()->IsQuery() && !TargetModel()->IsVirtualDirectory()
1828 && !TargetModel()->IsTrash() && !TargetModel()->IsPrintersDir()
1829 && !TargetModel()->IsRoot()) {
1830 if (!PoseView()->IsFilePanel()) {
1831 TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(),
1832 B_TRANSLATE("New"));
1833 menu->AddItem(templatesMenu);
1834 templatesMenu->SetTargetForItems(PoseView());
1835 } else {
1836 item = new BMenuItem(B_TRANSLATE("New folder"),
1837 new BMessage(kNewFolder), 'N');
1838 menu->AddItem(item);
1839 }
1840 }
1841 menu->AddSeparatorItem();
1842
1843 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
1844 new BMessage(kOpenSelection), 'O'));
1845 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
1846 new BMessage(kGetInfo), 'I'));
1847 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
1848 new BMessage(kEditItem), 'E'));
1849
1850 if (TargetModel()->IsTrash() || TargetModel()->InTrash()) {
1851 menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"),
1852 new BMessage(kRestoreFromTrash)));
1853 if (TargetModel()->IsTrash()) {
1854 // add as first item in menu
1855 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
1856 new BMessage(kEmptyTrash)), 0);
1857 menu->AddItem(new BSeparatorItem(), 1);
1858 }
1859 } else if (TargetModel()->IsPrintersDir()) {
1860 menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS),
1861 new BMessage(kAddPrinter), 'N'), 0);
1862 menu->AddItem(new BSeparatorItem(), 1);
1863 menu->AddItem(new BMenuItem(B_TRANSLATE("Make active printer"),
1864 new BMessage(kMakeActivePrinter)));
1865 } else if (TargetModel()->IsRoot()) {
1866 item = new BMenuItem(B_TRANSLATE("Unmount"),
1867 new BMessage(kUnmountVolume), 'U');
1868 item->SetEnabled(false);
1869 menu->AddItem(item);
1870 menu->AddItem(new BMenuItem(
1871 B_TRANSLATE("Mount settings" B_UTF8_ELLIPSIS),
1872 new BMessage(kRunAutomounterSettings)));
1873 } else {
1874 item = new BMenuItem(B_TRANSLATE("Duplicate"),
1875 new BMessage(kDuplicateSelection), 'D');
1876 item->SetEnabled(PoseView()->CanMoveToTrashOrDuplicate());
1877 menu->AddItem(item);
1878
1879 item = new BMenuItem(B_TRANSLATE("Move to Trash"),
1880 new BMessage(kMoveSelectionToTrash), 'T');
1881 item->SetEnabled(PoseView()->CanMoveToTrashOrDuplicate());
1882 menu->AddItem(item);
1883
1884 menu->AddSeparatorItem();
1885
1886 // The "Move To", "Copy To", "Create Link" menus are inserted
1887 // at this place, have a look at:
1888 // BContainerWindow::SetupMoveCopyMenus()
1889 }
1890
1891 BMenuItem* cutItem = NULL;
1892 BMenuItem* copyItem = NULL;
1893 BMenuItem* pasteItem = NULL;
1894 if (!TargetModel()->IsPrintersDir()) {
1895 menu->AddSeparatorItem();
1896
1897 if (!TargetModel()->IsRoot()) {
1898 cutItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Cut"),
1899 new BMessage(B_CUT), 'X');
1900 menu->AddItem(cutItem);
1901 copyItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Copy"),
1902 new BMessage(B_COPY), 'C');
1903 menu->AddItem(copyItem);
1904 pasteItem = new(std::nothrow) BMenuItem(B_TRANSLATE("Paste"),
1905 new BMessage(B_PASTE), 'V');
1906 menu->AddItem(pasteItem);
1907 menu->AddSeparatorItem();
1908
1909 menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"),
1910 new BMessage(kIdentifyEntry)));
1911 }
1912 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
1913 menu->AddItem(addOnMenuItem);
1914 }
1915
1916 menu->SetTargetForItems(PoseView());
1917 if (cutItem != NULL)
1918 cutItem->SetTarget(this);
1919
1920 if (copyItem != NULL)
1921 copyItem->SetTarget(this);
1922
1923 if (pasteItem != NULL)
1924 pasteItem->SetTarget(this);
1925 }
1926
1927
1928 void
AddWindowMenu(BMenu * menu)1929 BContainerWindow::AddWindowMenu(BMenu* menu)
1930 {
1931 BMenuItem* item;
1932
1933 BMenu* iconSizeMenu = new BMenu(B_TRANSLATE("Icon view"));
1934
1935 static const uint32 kIconSizes[] = { 32, 40, 48, 64, 96, 128 };
1936 BMessage* message;
1937
1938 for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); ++i) {
1939 uint32 iconSize = kIconSizes[i];
1940 message = new BMessage(kIconMode);
1941 message->AddInt32("size", iconSize);
1942 BString label;
1943 label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32,
1944 "The '×' is the Unicode multiplication sign U+00D7"),
1945 iconSize, iconSize);
1946 item = new BMenuItem(label, message);
1947 item->SetTarget(PoseView());
1948 iconSizeMenu->AddItem(item);
1949 }
1950
1951 iconSizeMenu->AddSeparatorItem();
1952
1953 message = new BMessage(kIconMode);
1954 message->AddInt32("scale", 0);
1955 item = new BMenuItem(B_TRANSLATE("Decrease size"), message, '-');
1956 item->SetTarget(PoseView());
1957 iconSizeMenu->AddItem(item);
1958
1959 message = new BMessage(kIconMode);
1960 message->AddInt32("scale", 1);
1961 item = new BMenuItem(B_TRANSLATE("Increase size"), message, '+');
1962 item->SetTarget(PoseView());
1963 iconSizeMenu->AddItem(item);
1964
1965 // A sub menu where the super item can be invoked.
1966 menu->AddItem(iconSizeMenu);
1967 iconSizeMenu->Superitem()->SetShortcut('1', B_COMMAND_KEY);
1968 iconSizeMenu->Superitem()->SetMessage(new BMessage(kIconMode));
1969 iconSizeMenu->Superitem()->SetTarget(PoseView());
1970
1971 item = new BMenuItem(B_TRANSLATE("Mini icon view"),
1972 new BMessage(kMiniIconMode), '2');
1973 item->SetTarget(PoseView());
1974 menu->AddItem(item);
1975
1976 item = new BMenuItem(B_TRANSLATE("List view"),
1977 new BMessage(kListMode), '3');
1978 item->SetTarget(PoseView());
1979 menu->AddItem(item);
1980
1981 menu->AddSeparatorItem();
1982
1983 item = new BMenuItem(B_TRANSLATE("Resize to fit"),
1984 new BMessage(kResizeToFit), 'Y');
1985 item->SetTarget(this);
1986 menu->AddItem(item);
1987
1988 fArrangeByMenu = new BMenu(B_TRANSLATE("Arrange by"));
1989 menu->AddItem(fArrangeByMenu);
1990
1991 item = new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
1992 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY);
1993 item->SetTarget(PoseView());
1994 menu->AddItem(item);
1995
1996 item = new BMenuItem(B_TRANSLATE("Select all"),
1997 new BMessage(B_SELECT_ALL), 'A');
1998 item->SetTarget(this);
1999 menu->AddItem(item);
2000
2001 item = new BMenuItem(B_TRANSLATE("Invert selection"),
2002 new BMessage(kInvertSelection), 'S');
2003 item->SetTarget(PoseView());
2004 menu->AddItem(item);
2005
2006 if (!TargetModel()->IsTrash()) {
2007 item = new BMenuItem(B_TRANSLATE("Open parent"),
2008 new BMessage(kOpenParentDir), B_UP_ARROW);
2009 item->SetTarget(PoseView());
2010 menu->AddItem(item);
2011 }
2012
2013 item = new BMenuItem(B_TRANSLATE("Close"),
2014 new BMessage(B_QUIT_REQUESTED), 'W');
2015 item->SetTarget(this);
2016 menu->AddItem(item);
2017
2018 item = new BMenuItem(B_TRANSLATE("Close all in workspace"),
2019 new BMessage(kCloseAllInWorkspace), 'Q');
2020 item->SetTarget(be_app);
2021 menu->AddItem(item);
2022
2023 menu->AddSeparatorItem();
2024
2025 item = new BMenuItem(B_TRANSLATE("Preferences" B_UTF8_ELLIPSIS),
2026 new BMessage(kShowSettingsWindow), ',');
2027 item->SetTarget(be_app);
2028 menu->AddItem(item);
2029 }
2030
2031
2032 void
AddShortcuts()2033 BContainerWindow::AddShortcuts()
2034 {
2035 // add equivalents of the menu shortcuts to the menuless desktop window
2036 ASSERT(!TargetModel()->IsTrash());
2037 ASSERT(!PoseView()->IsFilePanel());
2038 ASSERT(!TargetModel()->IsQuery());
2039 ASSERT(!TargetModel()->IsVirtualDirectory());
2040
2041 AddShortcut('X', B_COMMAND_KEY | B_SHIFT_KEY,
2042 new BMessage(kCutMoreSelectionToClipboard), this);
2043 AddShortcut('C', B_COMMAND_KEY | B_SHIFT_KEY,
2044 new BMessage(kCopyMoreSelectionToClipboard), this);
2045 AddShortcut('F', B_COMMAND_KEY,
2046 new BMessage(kFindButton), PoseView());
2047 AddShortcut('N', B_COMMAND_KEY,
2048 new BMessage(kNewFolder), PoseView());
2049 AddShortcut('O', B_COMMAND_KEY,
2050 new BMessage(kOpenSelection), PoseView());
2051 AddShortcut('I', B_COMMAND_KEY,
2052 new BMessage(kGetInfo), PoseView());
2053 AddShortcut('E', B_COMMAND_KEY,
2054 new BMessage(kEditItem), PoseView());
2055 AddShortcut('D', B_COMMAND_KEY,
2056 new BMessage(kDuplicateSelection), PoseView());
2057 AddShortcut('T', B_COMMAND_KEY,
2058 new BMessage(kMoveSelectionToTrash), PoseView());
2059 AddShortcut('K', B_COMMAND_KEY,
2060 new BMessage(kCleanup), PoseView());
2061 AddShortcut('A', B_COMMAND_KEY,
2062 new BMessage(B_SELECT_ALL), PoseView());
2063 AddShortcut('S', B_COMMAND_KEY,
2064 new BMessage(kInvertSelection), PoseView());
2065 AddShortcut('A', B_COMMAND_KEY | B_SHIFT_KEY,
2066 new BMessage(kShowSelectionWindow), PoseView());
2067 AddShortcut('G', B_COMMAND_KEY,
2068 new BMessage(kEditQuery), PoseView());
2069 // it is ok to add a global Edit query shortcut here, PoseView will
2070 // filter out cases where selected pose is not a query
2071 AddShortcut('U', B_COMMAND_KEY,
2072 new BMessage(kUnmountVolume), PoseView());
2073 AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
2074 new BMessage(kOpenParentDir), PoseView());
2075 AddShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY,
2076 new BMessage(kOpenSelectionWith), PoseView());
2077
2078 BMessage* decreaseSize = new BMessage(kIconMode);
2079 decreaseSize->AddInt32("scale", 0);
2080 AddShortcut('-', B_COMMAND_KEY, decreaseSize, PoseView());
2081
2082 BMessage* increaseSize = new BMessage(kIconMode);
2083 increaseSize->AddInt32("scale", 1);
2084 AddShortcut('+', B_COMMAND_KEY, increaseSize, PoseView());
2085 }
2086
2087
2088 void
MenusBeginning()2089 BContainerWindow::MenusBeginning()
2090 {
2091 if (fMenuBar == NULL)
2092 return;
2093
2094 if (CurrentMessage() != NULL && CurrentMessage()->what == B_MOUSE_DOWN) {
2095 // don't commit active pose if only a keyboard shortcut is
2096 // invoked - this would prevent Cut/Copy/Paste from working
2097 PoseView()->CommitActivePose();
2098 }
2099
2100 // File menu
2101 int32 selectCount = PoseView()->SelectionList()->CountItems();
2102
2103 SetupOpenWithMenu(fFileMenu);
2104 SetupMoveCopyMenus(selectCount
2105 ? PoseView()->SelectionList()->FirstItem()->TargetModel()->EntryRef()
2106 : NULL, fFileMenu);
2107
2108 if (TargetModel()->IsRoot()) {
2109 BVolume boot;
2110 BVolumeRoster().GetBootVolume(&boot);
2111
2112 bool ejectableVolumeSelected = false;
2113 for (int32 index = 0; index < selectCount; index++) {
2114 Model* model
2115 = PoseView()->SelectionList()->ItemAt(index)->TargetModel();
2116 if (model->IsVolume()) {
2117 BVolume volume;
2118 volume.SetTo(model->NodeRef()->device);
2119 if (volume != boot) {
2120 ejectableVolumeSelected = true;
2121 break;
2122 }
2123 }
2124 }
2125 BMenuItem* item = fMenuBar->FindItem(kUnmountVolume);
2126 if (item != NULL)
2127 item->SetEnabled(ejectableVolumeSelected);
2128 }
2129
2130 UpdateMenu(fMenuBar, kMenuBarContext);
2131
2132 AddMimeTypesToMenu(fAttrMenu);
2133
2134 if (TargetModel()->IsPrintersDir())
2135 EnableNamedMenuItem(fFileMenu, kMakeActivePrinter, selectCount == 1);
2136 }
2137
2138
2139 void
MenusEnded()2140 BContainerWindow::MenusEnded()
2141 {
2142 // when we're done we want to clear nav menus for next time
2143 DeleteSubmenu(fNavigationItem);
2144 DeleteSubmenu(fMoveToItem);
2145 DeleteSubmenu(fCopyToItem);
2146 DeleteSubmenu(fCreateLinkItem);
2147 DeleteSubmenu(fOpenWithItem);
2148 }
2149
2150
2151 void
SetupNavigationMenu(const entry_ref * ref,BMenu * parent)2152 BContainerWindow::SetupNavigationMenu(const entry_ref* ref, BMenu* parent)
2153 {
2154 // start by removing nav item (and separator) from old menu
2155 if (fNavigationItem != NULL) {
2156 BMenu* menu = fNavigationItem->Menu();
2157 if (menu != NULL) {
2158 menu->RemoveItem(fNavigationItem);
2159 BMenuItem* item = menu->RemoveItem((int32)0);
2160 ASSERT(item != fNavigationItem);
2161 delete item;
2162 }
2163 }
2164
2165 // if we weren't passed a ref then we're navigating this window
2166 if (ref == NULL)
2167 ref = TargetModel()->EntryRef();
2168
2169 BEntry entry;
2170 if (entry.SetTo(ref) != B_OK)
2171 return;
2172
2173 // only navigate directories and queries (check for symlink here)
2174 Model model(&entry);
2175 entry_ref resolvedRef;
2176
2177 if (model.InitCheck() != B_OK)
2178 return;
2179 else if (!model.IsContainer() && !model.IsSymLink())
2180 return;
2181
2182 if (model.IsSymLink()) {
2183 if (entry.SetTo(model.EntryRef(), true) != B_OK)
2184 return;
2185
2186 Model resolvedModel(&entry);
2187 if (resolvedModel.InitCheck() != B_OK || !resolvedModel.IsContainer())
2188 return;
2189
2190 entry.GetRef(&resolvedRef);
2191 ref = &resolvedRef;
2192 }
2193
2194 if (fNavigationItem == NULL) {
2195 fNavigationItem = new ModelMenuItem(&model,
2196 new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, this));
2197 }
2198
2199 // setup a navigation menu item which will dynamically load items
2200 // as menu items are traversed
2201 BNavMenu* navMenu = dynamic_cast<BNavMenu*>(fNavigationItem->Submenu());
2202 navMenu->SetNavDir(ref);
2203 fNavigationItem->SetLabel(model.Name());
2204 fNavigationItem->SetEntry(&entry);
2205
2206 parent->AddItem(fNavigationItem, 0);
2207 parent->AddItem(new BSeparatorItem(), 1);
2208
2209 BMessage* message = new BMessage(B_REFS_RECEIVED);
2210 message->AddRef("refs", ref);
2211 fNavigationItem->SetMessage(message);
2212 fNavigationItem->SetTarget(be_app);
2213
2214 if (!Dragging())
2215 parent->SetTrackingHook(NULL, NULL);
2216 }
2217
2218
2219 void
SetupEditQueryItem(BMenu * menu)2220 BContainerWindow::SetupEditQueryItem(BMenu* menu)
2221 {
2222 ASSERT(menu);
2223 // File menu
2224 int32 selectCount = PoseView()->CountSelected();
2225
2226 // add Edit query if appropriate
2227 bool queryInSelection = false;
2228 if (selectCount && selectCount < 100) {
2229 // only do this for a limited number of selected poses
2230
2231 // if any queries selected, add an edit query menu item
2232 for (int32 index = 0; index < selectCount; index++) {
2233 BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2234 Model model(pose->TargetModel()->EntryRef(), true);
2235 if (model.InitCheck() != B_OK)
2236 continue;
2237
2238 if (model.IsQuery() || model.IsQueryTemplate()) {
2239 queryInSelection = true;
2240 break;
2241 }
2242 }
2243 }
2244
2245 bool poseViewIsQuery = TargetModel()->IsQuery();
2246 // if the view is a query pose view, add edit query menu item
2247
2248 BMenuItem* item = menu->FindItem(kEditQuery);
2249 if (!poseViewIsQuery && !queryInSelection && item != NULL)
2250 item->Menu()->RemoveItem(item);
2251 else if ((poseViewIsQuery || queryInSelection) && item == NULL) {
2252 // add edit query item after Open
2253 item = menu->FindItem(kOpenSelection);
2254 if (item) {
2255 int32 itemIndex = item->Menu()->IndexOf(item);
2256 BMenuItem* query = new BMenuItem(B_TRANSLATE("Edit query"),
2257 new BMessage(kEditQuery), 'G');
2258 item->Menu()->AddItem(query, itemIndex + 1);
2259 query->SetTarget(PoseView());
2260 }
2261 }
2262 }
2263
2264
2265 void
SetupOpenWithMenu(BMenu * parent)2266 BContainerWindow::SetupOpenWithMenu(BMenu* parent)
2267 {
2268 // start by removing nav item (and separator) from old menu
2269 if (fOpenWithItem) {
2270 BMenu* menu = fOpenWithItem->Menu();
2271 if (menu != NULL)
2272 menu->RemoveItem(fOpenWithItem);
2273
2274 delete fOpenWithItem;
2275 fOpenWithItem = 0;
2276 }
2277
2278 int32 selectCount = PoseView()->CountSelected();
2279 if (selectCount <= 0) {
2280 // no selection, nothing to open
2281 return;
2282 }
2283
2284 if (TargetModel()->IsRoot()) {
2285 // don't add ourselves if we are root
2286 return;
2287 }
2288
2289 // ToDo:
2290 // check if only item in selection list is the root
2291 // and do not add if true
2292
2293 // add after "Open"
2294 BMenuItem* item = parent->FindItem(kOpenSelection);
2295
2296 // build a list of all refs to open
2297 BMessage message(B_REFS_RECEIVED);
2298 for (int32 index = 0; index < selectCount; index++) {
2299 BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2300 message.AddRef("refs", pose->TargetModel()->EntryRef());
2301 }
2302
2303 // add Tracker token so that refs received recipients can script us
2304 message.AddMessenger("TrackerViewToken", BMessenger(PoseView()));
2305
2306 int32 index = item->Menu()->IndexOf(item);
2307 fOpenWithItem = new BMenuItem(
2308 new OpenWithMenu(B_TRANSLATE("Open with" B_UTF8_ELLIPSIS),
2309 &message, this, be_app), new BMessage(kOpenSelectionWith));
2310 fOpenWithItem->SetTarget(PoseView());
2311 fOpenWithItem->SetShortcut('O', B_COMMAND_KEY | B_CONTROL_KEY);
2312
2313 item->Menu()->AddItem(fOpenWithItem, index + 1);
2314 }
2315
2316
2317 void
PopulateMoveCopyNavMenu(BNavMenu * navMenu,uint32 what,const entry_ref * ref,bool addLocalOnly)2318 BContainerWindow::PopulateMoveCopyNavMenu(BNavMenu* navMenu, uint32 what,
2319 const entry_ref* ref, bool addLocalOnly)
2320 {
2321 BVolume volume;
2322 BVolumeRoster volumeRoster;
2323 BDirectory directory;
2324 BEntry entry;
2325 BPath path;
2326 Model model;
2327 dev_t device = ref->device;
2328
2329 int32 volumeCount = 0;
2330
2331 navMenu->RemoveItems(0, navMenu->CountItems(), true);
2332
2333 // count persistent writable volumes
2334 volumeRoster.Rewind();
2335 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2336 if (!volume.IsReadOnly() && volume.IsPersistent())
2337 volumeCount++;
2338 }
2339
2340 // add the current folder
2341 if (entry.SetTo(ref) == B_OK
2342 && entry.GetParent(&entry) == B_OK
2343 && model.SetTo(&entry) == B_OK) {
2344 BNavMenu* menu = new BNavMenu(B_TRANSLATE("Current folder"), what,
2345 this);
2346 menu->SetNavDir(model.EntryRef());
2347 menu->SetShowParent(true);
2348
2349 BMenuItem* item = new SpecialModelMenuItem(&model, menu);
2350 item->SetMessage(new BMessage((uint32)what));
2351
2352 navMenu->AddItem(item);
2353 }
2354
2355 // add the recent folder menu
2356 // the "Tracker" settings directory is only used to get its icon
2357 if (find_directory(B_USER_SETTINGS_DIRECTORY, &path) == B_OK) {
2358 path.Append("Tracker");
2359 if (entry.SetTo(path.Path()) == B_OK
2360 && model.SetTo(&entry) == B_OK) {
2361 BMenu* menu = new RecentsMenu(B_TRANSLATE("Recent folders"),
2362 kRecentFolders, what, this);
2363
2364 BMenuItem* item = new SpecialModelMenuItem(&model, menu);
2365 item->SetMessage(new BMessage((uint32)what));
2366
2367 navMenu->AddItem(item);
2368 }
2369 }
2370
2371 // add Desktop
2372 FSGetBootDeskDir(&directory);
2373 if (directory.InitCheck() == B_OK && directory.GetEntry(&entry) == B_OK
2374 && model.SetTo(&entry) == B_OK) {
2375 navMenu->AddNavDir(&model, what, this, true);
2376 // ask NavMenu to populate submenu for us
2377 }
2378
2379 // add the home dir
2380 if (find_directory(B_USER_DIRECTORY, &path) == B_OK
2381 && entry.SetTo(path.Path()) == B_OK && model.SetTo(&entry) == B_OK) {
2382 navMenu->AddNavDir(&model, what, this, true);
2383 }
2384
2385 navMenu->AddSeparatorItem();
2386
2387 // either add all mounted volumes (for copy), or all the top-level
2388 // directories from the same device (for move)
2389 // ToDo: can be changed if cross-device moves are implemented
2390
2391 if (addLocalOnly || volumeCount < 2) {
2392 // add volume this item lives on
2393 if (volume.SetTo(device) == B_OK
2394 && volume.GetRootDirectory(&directory) == B_OK
2395 && directory.GetEntry(&entry) == B_OK
2396 && model.SetTo(&entry) == B_OK) {
2397 navMenu->AddNavDir(&model, what, this, false);
2398 // do not have submenu populated
2399
2400 navMenu->SetNavDir(model.EntryRef());
2401 }
2402 } else {
2403 // add all persistent writable volumes
2404 volumeRoster.Rewind();
2405 while (volumeRoster.GetNextVolume(&volume) == B_OK) {
2406 if (volume.IsReadOnly() || !volume.IsPersistent())
2407 continue;
2408
2409 // add root dir
2410 if (volume.GetRootDirectory(&directory) == B_OK
2411 && directory.GetEntry(&entry) == B_OK
2412 && model.SetTo(&entry) == B_OK) {
2413 navMenu->AddNavDir(&model, what, this, true);
2414 // ask NavMenu to populate submenu for us
2415 }
2416 }
2417 }
2418 }
2419
2420
2421 void
SetupMoveCopyMenus(const entry_ref * item_ref,BMenu * parent)2422 BContainerWindow::SetupMoveCopyMenus(const entry_ref* item_ref, BMenu* parent)
2423 {
2424 if (TargetModel()->IsTrash() || TargetModel()->InTrash() || TargetModel()->IsPrintersDir()
2425 || fMoveToItem == NULL || fCopyToItem == NULL || fCreateLinkItem == NULL
2426 || TargetModel()->IsRoot()) {
2427 return;
2428 }
2429
2430 // re-parent items to this menu since they're shared
2431 BMenuItem* trash = parent->FindItem(kMoveSelectionToTrash);
2432 int32 index = trash != NULL ? parent->IndexOf(trash) + 2 : 0;
2433
2434 if (fMoveToItem->Menu() != parent) {
2435 if (fMoveToItem->Menu() != NULL)
2436 fMoveToItem->Menu()->RemoveItem(fMoveToItem);
2437
2438 parent->AddItem(fMoveToItem, index++);
2439 }
2440
2441 if (fCopyToItem->Menu() != parent) {
2442 if (fCopyToItem->Menu() != NULL)
2443 fCopyToItem->Menu()->RemoveItem(fCopyToItem);
2444
2445 parent->AddItem(fCopyToItem, index++);
2446 }
2447
2448 if (fCreateLinkItem->Menu() != parent) {
2449 if (fCreateLinkItem->Menu() != NULL)
2450 fCreateLinkItem->Menu()->RemoveItem(fCreateLinkItem);
2451
2452 parent->AddItem(fCreateLinkItem, index);
2453 }
2454
2455 // Set the "Create Link" item label here so it
2456 // appears correctly when menus are disabled, too.
2457 if ((modifiers() & B_SHIFT_KEY) != 0)
2458 fCreateLinkItem->SetLabel(B_TRANSLATE("Create relative link"));
2459 else
2460 fCreateLinkItem->SetLabel(B_TRANSLATE("Create link"));
2461
2462 // only enable once the menus are built
2463 fMoveToItem->SetEnabled(false);
2464 fCopyToItem->SetEnabled(false);
2465 fCreateLinkItem->SetEnabled(false);
2466
2467 // get ref for item which is selected
2468 BEntry entry;
2469 if (entry.SetTo(item_ref) != B_OK)
2470 return;
2471
2472 Model tempModel(&entry);
2473 if (tempModel.InitCheck() != B_OK)
2474 return;
2475
2476 if (tempModel.IsRoot() || tempModel.IsVolume())
2477 return;
2478
2479 // configure "Move to" menu item
2480 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fMoveToItem->Submenu()),
2481 kMoveSelectionTo, item_ref, true);
2482
2483 // configure "Copy to" menu item
2484 // add all mounted volumes (except the one this item lives on)
2485 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>(fCopyToItem->Submenu()),
2486 kCopySelectionTo, item_ref, false);
2487
2488 // Set "Create Link" menu item message and
2489 // add all mounted volumes (except the one this item lives on)
2490 if ((modifiers() & B_SHIFT_KEY) != 0) {
2491 fCreateLinkItem->SetMessage(new BMessage(kCreateRelativeLink));
2492 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>
2493 (fCreateLinkItem->Submenu()),
2494 kCreateRelativeLink, item_ref, false);
2495 } else {
2496 fCreateLinkItem->SetMessage(new BMessage(kCreateLink));
2497 PopulateMoveCopyNavMenu(dynamic_cast<BNavMenu*>
2498 (fCreateLinkItem->Submenu()),
2499 kCreateLink, item_ref, false);
2500 }
2501
2502 fMoveToItem->SetEnabled(PoseView()->CountSelected() > 0
2503 && !PoseView()->SelectedVolumeIsReadOnly());
2504 fCopyToItem->SetEnabled(PoseView()->CountSelected() > 0);
2505 fCreateLinkItem->SetEnabled(PoseView()->CountSelected() > 0);
2506
2507 // Set the "Identify" item label
2508 BMenuItem* identifyItem = parent->FindItem(kIdentifyEntry);
2509 if (identifyItem != NULL) {
2510 if ((modifiers() & B_SHIFT_KEY) != 0) {
2511 identifyItem->SetLabel(B_TRANSLATE("Force identify"));
2512 identifyItem->Message()->ReplaceBool("force", true);
2513 } else {
2514 identifyItem->SetLabel(B_TRANSLATE("Identify"));
2515 identifyItem->Message()->ReplaceBool("force", false);
2516 }
2517 }
2518 }
2519
2520
2521 uint32
ShowDropContextMenu(BPoint where,BPoseView * source)2522 BContainerWindow::ShowDropContextMenu(BPoint where, BPoseView* source)
2523 {
2524 BPoint global(where);
2525
2526 PoseView()->ConvertToScreen(&global);
2527 PoseView()->CommitActivePose();
2528
2529 // Change the "Create Link" item - allow user to
2530 // create relative links with the Shift key down.
2531 BMenuItem* item = fDropContextMenu->FindItem(kCreateLink);
2532 if (item == NULL)
2533 item = fDropContextMenu->FindItem(kCreateRelativeLink);
2534
2535 if (item != NULL && (modifiers() & B_SHIFT_KEY) != 0) {
2536 item->SetLabel(B_TRANSLATE("Create relative link here"));
2537 item->SetMessage(new BMessage(kCreateRelativeLink));
2538 } else if (item != NULL) {
2539 item->SetLabel(B_TRANSLATE("Create link here"));
2540 item->SetMessage(new BMessage(kCreateLink));
2541 }
2542
2543 int32 itemCount = fDropContextMenu->CountItems();
2544 for(int32 i = 0; i < itemCount - 2; i++) {
2545 // separator item and Cancel item are skipped
2546 item = fDropContextMenu->ItemAt(i);
2547 if (item == NULL)
2548 break;
2549
2550 if (item->Command() == kMoveSelectionTo && source != NULL) {
2551 item->SetEnabled(!source->SelectedVolumeIsReadOnly()
2552 && !PoseView()->TargetVolumeIsReadOnly());
2553 } else
2554 item->SetEnabled(!PoseView()->TargetVolumeIsReadOnly());
2555 }
2556
2557 item = fDropContextMenu->Go(global, true, true);
2558 if (item != NULL)
2559 return item->Command();
2560
2561 return 0;
2562 }
2563
2564
2565 void
ShowContextMenu(BPoint where,const entry_ref * ref)2566 BContainerWindow::ShowContextMenu(BPoint where, const entry_ref* ref)
2567 {
2568 ASSERT(IsLocked());
2569 BPoint global(where);
2570 PoseView()->ConvertToScreen(&global);
2571 PoseView()->CommitActivePose();
2572
2573 if (ref != NULL) {
2574 // clicked on a pose, show file or volume context menu
2575 Model model(ref);
2576
2577 if (model.IsTrash()) {
2578 if (fTrashContextMenu->Window() || Dragging())
2579 return;
2580
2581 DeleteSubmenu(fNavigationItem);
2582
2583 // selected item was trash, show the trash context menu instead
2584
2585 EnableNamedMenuItem(fTrashContextMenu, kEmptyTrash,
2586 static_cast<TTracker*>(be_app)->TrashFull());
2587
2588 SetupNavigationMenu(ref, fTrashContextMenu);
2589
2590 fContextMenu = fTrashContextMenu;
2591 } else {
2592 bool showAsVolume = false;
2593 bool isFilePanel = PoseView()->IsFilePanel();
2594
2595 if (Dragging()) {
2596 fContextMenu = NULL;
2597
2598 BEntry entry;
2599 model.GetEntry(&entry);
2600
2601 // only show for directories (directory, volume, root)
2602 //
2603 // don't show a popup for the trash or printers
2604 // trash is handled in DeskWindow
2605 //
2606 // since this menu is opened asynchronously
2607 // we need to make sure we don't open it more
2608 // than once, the IsShowing flag is set in
2609 // SlowContextPopup::AttachedToWindow and
2610 // reset in DetachedFromWindow
2611 // see the notes in SlowContextPopup::AttachedToWindow
2612
2613 if (!FSIsPrintersDir(&entry)
2614 && !fDragContextMenu->IsShowing()) {
2615 //printf("ShowContextMenu - target is %s %i\n",
2616 // ref->name, IsShowing(ref));
2617 fDragContextMenu->ClearMenu();
2618
2619 // in case the ref is a symlink, resolve it
2620 // only pop open for directories
2621 BEntry resolvedEntry(ref, true);
2622 if (!resolvedEntry.IsDirectory())
2623 return;
2624
2625 entry_ref resolvedRef;
2626 resolvedEntry.GetRef(&resolvedRef);
2627
2628 // use the resolved ref for the menu
2629 fDragContextMenu->SetNavDir(&resolvedRef);
2630 fDragContextMenu->SetTypesList(fCachedTypesList);
2631 fDragContextMenu->SetTarget(BMessenger(this));
2632 BPoseView* poseView = PoseView();
2633 if (poseView != NULL) {
2634 BMessenger target(poseView);
2635 fDragContextMenu->InitTrackingHook(
2636 &BPoseView::MenuTrackingHook, &target,
2637 fDragMessage);
2638 }
2639
2640 // this is now asynchronous so that we don't
2641 // deadlock in Window::Quit,
2642 fDragContextMenu->Go(global);
2643 }
2644
2645 return;
2646 } else if (model.IsRoot() || model.IsVolume()) {
2647 fContextMenu = fVolumeContextMenu;
2648 showAsVolume = true;
2649 } else {
2650 fContextMenu = fFileContextMenu;
2651 }
2652
2653 if (fContextMenu == NULL)
2654 return;
2655
2656 // bail out before cleanup if popup window is already open
2657 if (fContextMenu->Window() != NULL)
2658 return;
2659
2660 // clean up items from last context menu
2661 MenusEnded();
2662
2663 if (fContextMenu == fFileContextMenu) {
2664 // Add all mounted volumes (except the one this item lives on.)
2665 BNavMenu* navMenu = dynamic_cast<BNavMenu*>(
2666 fCreateLinkItem->Submenu());
2667 PopulateMoveCopyNavMenu(navMenu,
2668 fCreateLinkItem->Message()->what, ref, false);
2669 } else if (showAsVolume) {
2670 // non-volume enable/disable copy, move, identify
2671 EnableNamedMenuItem(fContextMenu, kDuplicateSelection, false);
2672 EnableNamedMenuItem(fContextMenu, kMoveSelectionToTrash, false);
2673 EnableNamedMenuItem(fContextMenu, kIdentifyEntry, false);
2674
2675 // volume model, enable/disable the Unmount item
2676 bool ejectableVolumeSelected = false;
2677
2678 BVolume boot;
2679 BVolumeRoster().GetBootVolume(&boot);
2680 BVolume volume;
2681 volume.SetTo(model.NodeRef()->device);
2682 if (volume != boot)
2683 ejectableVolumeSelected = true;
2684
2685 EnableNamedMenuItem(fContextMenu,
2686 B_TRANSLATE("Unmount"), ejectableVolumeSelected);
2687 }
2688
2689 SetupNavigationMenu(ref, fContextMenu);
2690 if (!showAsVolume && !isFilePanel) {
2691 SetupMoveCopyMenus(ref, fContextMenu);
2692 SetupOpenWithMenu(fContextMenu);
2693 }
2694
2695 UpdateMenu(fContextMenu, kPosePopUpContext);
2696 }
2697 } else if (fWindowContextMenu != NULL) {
2698 // clicked on a window, show window context menu
2699 fContextMenu = fWindowContextMenu;
2700
2701 // bail out before cleanup if popup window is already open
2702 if (fContextMenu->Window() != NULL)
2703 return;
2704
2705 // clean up items from last context menu
2706 MenusEnded();
2707
2708 // setup nav menu
2709 SetupNavigationMenu(TargetModel()->EntryRef(), fContextMenu);
2710
2711 // update the rest
2712 UpdateMenu(fContextMenu, kWindowPopUpContext);
2713 }
2714
2715 // context menu invalid or popup window is already open
2716 if (fContextMenu == NULL || fContextMenu->Window() != NULL)
2717 return;
2718
2719 fContextMenu->Go(global, true, true, true);
2720 fContextMenu = NULL;
2721 }
2722
2723
2724 void
AddFileContextMenus(BMenu * menu)2725 BContainerWindow::AddFileContextMenus(BMenu* menu)
2726 {
2727 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2728 new BMessage(kOpenSelection), 'O'));
2729 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2730 new BMessage(kGetInfo), 'I'));
2731 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2732 new BMessage(kEditItem), 'E'));
2733
2734 BMessage* message = new BMessage(kDuplicateSelection);
2735 if (!(TargetModel()->IsTrash() || TargetModel()->InTrash() || TargetModel()->IsPrintersDir()))
2736 menu->AddItem(new BMenuItem(B_TRANSLATE("Duplicate"), message, 'D'));
2737
2738 if (!(TargetModel()->IsTrash() || TargetModel()->InTrash())) {
2739 message = new BMessage(kMoveSelectionToTrash);
2740 menu->AddItem( new BMenuItem(B_TRANSLATE("Move to Trash"), message, 'T'));
2741 if (!TargetModel()->IsPrintersDir()) {
2742 // add separator for copy to/move to items (navigation items)
2743 menu->AddSeparatorItem();
2744 }
2745 } else {
2746 message = new BMessage(kDeleteSelection);
2747 menu->AddItem(new BMenuItem(B_TRANSLATE("Delete"), message, 0));
2748 message = new BMessage(kRestoreSelectionFromTrash);
2749 menu->AddItem(new BMenuItem(B_TRANSLATE("Restore"), message, 0));
2750 }
2751
2752 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2753 menu->AddSeparatorItem();
2754 BMenuItem* cutItem = new BMenuItem(B_TRANSLATE("Cut"),
2755 new BMessage(B_CUT), 'X');
2756 menu->AddItem(cutItem);
2757 BMenuItem* copyItem = new BMenuItem(B_TRANSLATE("Copy"),
2758 new BMessage(B_COPY), 'C');
2759 menu->AddItem(copyItem);
2760 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
2761 new BMessage(B_PASTE), 'V');
2762 menu->AddItem(pasteItem);
2763 #endif
2764 menu->AddSeparatorItem();
2765
2766 message = new BMessage(kIdentifyEntry);
2767 message->AddBool("force", false);
2768 menu->AddItem(new BMenuItem(B_TRANSLATE("Identify"), message));
2769
2770 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
2771 menu->AddItem(addOnMenuItem);
2772
2773 // set targets as needed
2774 menu->SetTargetForItems(PoseView());
2775 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2776 cutItem->SetTarget(this);
2777 copyItem->SetTarget(this);
2778 pasteItem->SetTarget(this);
2779 #endif
2780 }
2781
2782
2783 void
AddVolumeContextMenus(BMenu * menu)2784 BContainerWindow::AddVolumeContextMenus(BMenu* menu)
2785 {
2786 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2787 new BMessage(kOpenSelection), 'O'));
2788 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2789 new BMessage(kGetInfo), 'I'));
2790 menu->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
2791 new BMessage(kEditItem), 'E'));
2792
2793 menu->AddSeparatorItem();
2794 menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
2795
2796 BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"),
2797 new BMessage(kUnmountVolume), 'U');
2798 item->SetEnabled(false);
2799 menu->AddItem(item);
2800 menu->AddSeparatorItem();
2801
2802 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2803 menu->AddItem(new BMenuItem(B_TRANSLATE("Paste"),
2804 new BMessage(B_PASTE), 'V'));
2805 menu->AddSeparatorItem();
2806 #endif
2807
2808 menu->AddItem(new BMenu(B_TRANSLATE("Add-ons")));
2809
2810 menu->SetTargetForItems(PoseView());
2811 }
2812
2813
2814 void
AddWindowContextMenus(BMenu * menu)2815 BContainerWindow::AddWindowContextMenus(BMenu* menu)
2816 {
2817 // create context sensitive menu for empty area of window
2818 // since we check view mode before display, this should be a radio
2819 // mode menu
2820
2821 Model* targetModel = TargetModel();
2822 ASSERT(targetModel != NULL);
2823
2824 bool needSeparator = true;
2825 if (TargetModel()->IsTrash()) {
2826 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
2827 new BMessage(kEmptyTrash)));
2828 } else if (TargetModel()->IsPrintersDir()) {
2829 menu->AddItem(new BMenuItem(B_TRANSLATE("Add printer" B_UTF8_ELLIPSIS),
2830 new BMessage(kAddPrinter), 'N'));
2831 } else if (targetModel->InTrash() || targetModel->IsRoot()) {
2832 needSeparator = false;
2833 } else if (!PoseView()->IsFilePanel()) {
2834 TemplatesMenu* templatesMenu = new TemplatesMenu(PoseView(), B_TRANSLATE("New"));
2835 menu->AddItem(templatesMenu);
2836 templatesMenu->SetTargetForItems(PoseView());
2837 } else {
2838 menu->AddItem(new BMenuItem(B_TRANSLATE("New folder"), new BMessage(kNewFolder), 'N'));
2839 }
2840
2841 if (needSeparator)
2842 menu->AddSeparatorItem();
2843
2844 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2845 BMenuItem* pasteItem = new BMenuItem(B_TRANSLATE("Paste"),
2846 new BMessage(B_PASTE), 'V');
2847 pasteItem->SetEnabled(FSClipboardHasRefs()
2848 && !PoseView()->TargetVolumeIsReadOnly());
2849 menu->AddItem(pasteItem);
2850 menu->AddSeparatorItem();
2851 #endif
2852
2853 BMenu* arrangeBy = new BMenu(B_TRANSLATE("Arrange by"));
2854 PopulateArrangeByMenu(arrangeBy);
2855 menu->AddItem(arrangeBy);
2856
2857 menu->AddItem(new BMenuItem(B_TRANSLATE("Select" B_UTF8_ELLIPSIS),
2858 new BMessage(kShowSelectionWindow), 'A', B_SHIFT_KEY));
2859 menu->AddItem(new BMenuItem(B_TRANSLATE("Select all"),
2860 new BMessage(B_SELECT_ALL), 'A'));
2861 if (!TargetModel()->IsTrash()) {
2862 menu->AddItem(new BMenuItem(B_TRANSLATE("Open parent"),
2863 new BMessage(kOpenParentDir), B_UP_ARROW));
2864 }
2865
2866 if (targetModel->IsRoot()) {
2867 menu->AddSeparatorItem();
2868 menu->AddItem(new MountMenu(B_TRANSLATE("Mount")));
2869 }
2870
2871 menu->AddSeparatorItem();
2872 BMenu* addOnMenuItem = new BMenu(B_TRANSLATE("Add-ons"));
2873 menu->AddItem(addOnMenuItem);
2874
2875 #if DEBUG
2876 menu->AddSeparatorItem();
2877 BMenuItem* testing = new BMenuItem("Test icon cache",
2878 new BMessage(kTestIconCache));
2879 menu->AddItem(testing);
2880 #endif
2881
2882 // target items as needed
2883 menu->SetTargetForItems(PoseView());
2884 #ifdef CUT_COPY_PASTE_IN_CONTEXT_MENU
2885 pasteItem->SetTarget(this);
2886 #endif
2887 }
2888
2889
2890 void
AddDropContextMenus(BMenu * menu)2891 BContainerWindow::AddDropContextMenus(BMenu* menu)
2892 {
2893 menu->AddItem(new BMenuItem(B_TRANSLATE("Move here"),
2894 new BMessage(kMoveSelectionTo)));
2895 menu->AddItem(new BMenuItem(B_TRANSLATE("Copy here"),
2896 new BMessage(kCopySelectionTo)));
2897 menu->AddItem(new BMenuItem(B_TRANSLATE("Create link here"),
2898 new BMessage(kCreateLink)));
2899 menu->AddSeparatorItem();
2900 menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"),
2901 new BMessage(kCancelButton)));
2902 }
2903
2904
2905 void
AddTrashContextMenus(BMenu * menu)2906 BContainerWindow::AddTrashContextMenus(BMenu* menu)
2907 {
2908 // setup special trash context menu
2909 menu->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
2910 new BMessage(kEmptyTrash)));
2911 menu->AddItem(new BMenuItem(B_TRANSLATE("Open"),
2912 new BMessage(kOpenSelection), 'O'));
2913 menu->AddItem(new BMenuItem(B_TRANSLATE("Get info"),
2914 new BMessage(kGetInfo), 'I'));
2915 menu->SetTargetForItems(PoseView());
2916 }
2917
2918
2919 void
EachAddOn(void (* eachAddOn)(const Model *,const char *,uint32 shortcut,uint32 modifiers,bool primary,void * context,BContainerWindow * window,BMenu * menu),void * passThru,BStringList & mimeTypes,BMenu * menu)2920 BContainerWindow::EachAddOn(void (*eachAddOn)(const Model*, const char*,
2921 uint32 shortcut, uint32 modifiers, bool primary, void* context,
2922 BContainerWindow* window, BMenu* menu),
2923 void* passThru, BStringList& mimeTypes, BMenu* menu)
2924 {
2925 AutoLock<LockingList<AddOnShortcut> > lock(fAddOnsList);
2926 if (!lock.IsLocked())
2927 return;
2928
2929 for (int i = fAddOnsList->CountItems() - 1; i >= 0; i--) {
2930 struct AddOnShortcut* item = fAddOnsList->ItemAt(i);
2931 bool primary = false;
2932
2933 if (mimeTypes.CountStrings() > 0) {
2934 BFile file(item->model->EntryRef(), B_READ_ONLY);
2935 if (file.InitCheck() == B_OK) {
2936 BAppFileInfo info(&file);
2937 if (info.InitCheck() == B_OK) {
2938 bool secondary = true;
2939
2940 // does this add-on has types set at all?
2941 BMessage message;
2942 if (info.GetSupportedTypes(&message) == B_OK) {
2943 type_code typeCode;
2944 int32 count;
2945 if (message.GetInfo("types", &typeCode,
2946 &count) == B_OK) {
2947 secondary = false;
2948 }
2949 }
2950
2951 // check all supported types if it has some set
2952 if (!secondary) {
2953 for (int32 i = mimeTypes.CountStrings();
2954 !primary && i-- > 0;) {
2955 BString type = mimeTypes.StringAt(i);
2956 if (info.IsSupportedType(type.String())) {
2957 BMimeType mimeType(type.String());
2958 if (info.Supports(&mimeType))
2959 primary = true;
2960 else
2961 secondary = true;
2962 }
2963 }
2964 }
2965
2966 if (!secondary && !primary)
2967 continue;
2968 }
2969 }
2970 }
2971 ((eachAddOn)(item->model, item->model->Name(), item->key,
2972 item->modifiers, primary, passThru, this, menu));
2973 }
2974 }
2975
2976
2977 void
BuildMimeTypeList(BStringList & mimeTypes)2978 BContainerWindow::BuildMimeTypeList(BStringList& mimeTypes)
2979 {
2980 int32 selectCount = PoseView()->CountSelected();
2981 if (selectCount <= 0) {
2982 // just add the type of the current directory
2983 AddMimeTypeString(mimeTypes, TargetModel());
2984 } else {
2985 _UpdateSelectionMIMEInfo();
2986 for (int32 index = 0; index < selectCount; index++) {
2987 BPose* pose = PoseView()->SelectionList()->ItemAt(index);
2988 AddMimeTypeString(mimeTypes, pose->TargetModel());
2989 // If it's a symlink, resolves it and add the Target's MimeType
2990 if (pose->TargetModel()->IsSymLink()) {
2991 Model* resolved = new Model(
2992 pose->TargetModel()->EntryRef(), true, true);
2993 if (resolved->InitCheck() == B_OK)
2994 AddMimeTypeString(mimeTypes, resolved);
2995
2996 delete resolved;
2997 }
2998 }
2999 }
3000 }
3001
3002
3003 void
BuildAddOnsMenu(BMenu * parentMenu)3004 BContainerWindow::BuildAddOnsMenu(BMenu* parentMenu)
3005 {
3006 BMenuItem* item = parentMenu->FindItem(B_TRANSLATE("Add-ons"));
3007 if (parentMenu->IndexOf(item) == 0) {
3008 // the folder of the context menu seems to be named "Add-Ons"
3009 // so we just take the last menu item, which is correct if not
3010 // build with debug option
3011 item = parentMenu->ItemAt(parentMenu->CountItems() - 1);
3012 }
3013 if (item == NULL)
3014 return;
3015
3016 BFont font; {
3017 AutoLock<BLooper> _(parentMenu->Looper());
3018 parentMenu->GetFont(&font);
3019 }
3020
3021 BMenu* menu = item->Submenu();
3022 if (menu == NULL)
3023 return;
3024
3025 menu->SetFont(&font);
3026
3027 // found add-ons menu, empty it first
3028 for (;;) {
3029 item = menu->RemoveItem((int32)0);
3030 if (!item)
3031 break;
3032 delete item;
3033 }
3034
3035 BObjectList<BMenuItem> primaryList;
3036 BObjectList<BMenuItem> secondaryList;
3037 BStringList mimeTypes(10);
3038 BuildMimeTypeList(mimeTypes);
3039
3040 AddOneAddOnParams params;
3041 params.primaryList = &primaryList;
3042 params.secondaryList = &secondaryList;
3043
3044 // build a list of the MIME types of the selected items
3045
3046 EachAddOn(AddOneAddOn, ¶ms, mimeTypes, parentMenu);
3047
3048 primaryList.SortItems(CompareLabels);
3049 secondaryList.SortItems(CompareLabels);
3050
3051 int32 count = primaryList.CountItems();
3052 for (int32 index = 0; index < count; index++)
3053 menu->AddItem(primaryList.ItemAt(index));
3054
3055 if (count > 0)
3056 menu->AddSeparatorItem();
3057
3058 count = secondaryList.CountItems();
3059 for (int32 index = 0; index < count; index++)
3060 menu->AddItem(secondaryList.ItemAt(index));
3061
3062 menu->SetTargetForItems(this);
3063 }
3064
3065
3066 void
UpdateMenu(BMenu * menu,UpdateMenuContext context)3067 BContainerWindow::UpdateMenu(BMenu* menu, UpdateMenuContext context)
3068 {
3069 const int32 selectCount = PoseView()->CountSelected();
3070 const int32 poseCount = PoseView()->CountItems();
3071
3072 if (context == kMenuBarContext) {
3073 EnableNamedMenuItem(menu, kOpenSelection, selectCount > 0);
3074 EnableNamedMenuItem(menu, kIdentifyEntry, selectCount > 0);
3075 EnableNamedMenuItem(menu, kRestoreFromTrash, selectCount > 0);
3076 EnableNamedMenuItem(menu, kDeleteSelection, PoseView()->CanMoveToTrashOrDuplicate());
3077 }
3078
3079 if (context == kMenuBarContext || context == kPosePopUpContext) {
3080 SetupEditQueryItem(menu);
3081
3082 EnableNamedMenuItem(menu, kEditItem, PoseView()->CanEditName());
3083 EnableNamedMenuItem(menu, kMoveSelectionToTrash, PoseView()->CanMoveToTrashOrDuplicate());
3084 EnableNamedMenuItem(menu, kDuplicateSelection, PoseView()->CanMoveToTrashOrDuplicate());
3085
3086 SetCutItem(menu);
3087 SetCopyItem(menu);
3088 SetPasteItem(menu);
3089 }
3090
3091 if (context == kMenuBarContext || context == kWindowPopUpContext) {
3092 if (!PoseView()->IsFilePanel())
3093 EnableNamedMenuItem(menu, B_TRANSLATE("New"), !PoseView()->TargetVolumeIsReadOnly());
3094 else
3095 EnableNamedMenuItem(menu, kNewFolder, !PoseView()->TargetVolumeIsReadOnly());
3096
3097 uint32 viewMode = PoseView()->ViewMode();
3098
3099 BMenu* iconSizeMenu = NULL;
3100 if (BMenuItem* item = menu->FindItem(kIconMode))
3101 iconSizeMenu = item->Submenu();
3102
3103 if (iconSizeMenu != NULL) {
3104 if (viewMode == kIconMode) {
3105 int32 iconSize = PoseView()->UnscaledIconSizeInt();
3106 BMenuItem* item = iconSizeMenu->ItemAt(0);
3107 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL;
3108 i++) {
3109 BMessage* message = item->Message();
3110 if (message == NULL) {
3111 item->SetMarked(false);
3112 continue;
3113 }
3114 int32 size;
3115 if (message->FindInt32("size", &size) != B_OK)
3116 size = -1;
3117 item->SetMarked(iconSize == size);
3118 }
3119 } else {
3120 BMenuItem* item;
3121 for (int32 i = 0; (item = iconSizeMenu->ItemAt(i)) != NULL; i++)
3122 item->SetMarked(false);
3123 }
3124 }
3125
3126 MarkNamedMenuItem(menu, kIconMode, viewMode == kIconMode);
3127 MarkNamedMenuItem(menu, kListMode, viewMode == kListMode);
3128 MarkNamedMenuItem(menu, kMiniIconMode, viewMode == kMiniIconMode);
3129
3130 SetCloseItem(menu);
3131 SetArrangeMenu(menu);
3132 SetPasteItem(menu);
3133
3134 EnableNamedMenuItem(menu, kOpenParentDir, !PoseView()->ParentIsRoot());
3135 EnableNamedMenuItem(menu, kEmptyTrash, poseCount > 0);
3136 EnableNamedMenuItem(menu, B_SELECT_ALL, poseCount > 0);
3137
3138 BMenuItem* item = menu->FindItem(B_TRANSLATE("New"));
3139 if (item != NULL) {
3140 TemplatesMenu* templatesMenu = dynamic_cast<TemplatesMenu*>(
3141 item->Submenu());
3142 if (templatesMenu != NULL)
3143 templatesMenu->UpdateMenuState();
3144 }
3145 }
3146
3147 BuildAddOnsMenu(menu);
3148 }
3149
3150
3151 BMessage*
AddOnMessage(int32 what)3152 BContainerWindow::AddOnMessage(int32 what)
3153 {
3154 BMessage* message = new BMessage(what);
3155
3156 // add selected refs to message
3157 BObjectList<BPose>* selectionList = PoseView()->SelectionList();
3158
3159 int32 index = 0;
3160 BPose* pose;
3161 while ((pose = selectionList->ItemAt(index++)) != NULL)
3162 message->AddRef("refs", pose->TargetModel()->EntryRef());
3163
3164 message->AddRef("dir_ref", TargetModel()->EntryRef());
3165 message->AddMessenger("TrackerViewToken", BMessenger(PoseView()));
3166
3167 return message;
3168 }
3169
3170
3171 void
LoadAddOn(BMessage * message)3172 BContainerWindow::LoadAddOn(BMessage* message)
3173 {
3174 UpdateIfNeeded();
3175
3176 entry_ref addOnRef;
3177 status_t result = message->FindRef("refs", &addOnRef);
3178 if (result != B_OK) {
3179 BString buffer(B_TRANSLATE("Error %error loading add-On %name."));
3180 buffer.ReplaceFirst("%error", strerror(result));
3181 buffer.ReplaceFirst("%name", addOnRef.name);
3182
3183 BAlert* alert = new BAlert("", buffer.String(), B_TRANSLATE("Cancel"),
3184 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
3185 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
3186 alert->Go();
3187 return;
3188 }
3189
3190 // add selected refs to message
3191 BMessage* refs = AddOnMessage(B_REFS_RECEIVED);
3192
3193 LaunchInNewThread("Add-on", B_NORMAL_PRIORITY, &AddOnThread, refs,
3194 addOnRef, *TargetModel()->EntryRef());
3195 }
3196
3197
3198 // #pragma mark - BContainerWindow private methods
3199
3200
3201 void
_UpdateSelectionMIMEInfo()3202 BContainerWindow::_UpdateSelectionMIMEInfo()
3203 {
3204 BPose* pose;
3205 int32 index = 0;
3206 while ((pose = PoseView()->SelectionList()->ItemAt(index++)) != NULL) {
3207 BString mimeType(pose->TargetModel()->MimeType());
3208 if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3209 pose->TargetModel()->Mimeset(true);
3210 if (pose->TargetModel()->IsSymLink()) {
3211 Model* resolved = new Model(pose->TargetModel()->EntryRef(),
3212 true, true);
3213 if (resolved->InitCheck() == B_OK) {
3214 mimeType.SetTo(resolved->MimeType());
3215 if (!mimeType.Length()
3216 || mimeType.ICompare(B_FILE_MIMETYPE) == 0) {
3217 resolved->Mimeset(true);
3218 }
3219 }
3220 delete resolved;
3221 }
3222 }
3223 }
3224 }
3225
3226
3227 void
_AddFolderIcon()3228 BContainerWindow::_AddFolderIcon()
3229 {
3230 if (fMenuBar == NULL) {
3231 // We don't want to add the icon if there's no menubar
3232 return;
3233 }
3234
3235 float baseIconSize = be_control_look->ComposeIconSize(16).Height() + 1,
3236 iconSize = fMenuBar->Bounds().Height() - 2;
3237 if (iconSize < baseIconSize)
3238 iconSize = baseIconSize;
3239
3240 fDraggableIcon = new(std::nothrow)
3241 DraggableContainerIcon(BSize(iconSize - 1, iconSize - 1));
3242 if (fDraggableIcon != NULL) {
3243 fMenuContainer->GroupLayout()->AddView(fDraggableIcon);
3244 fMenuBar->SetBorders(
3245 BControlLook::B_ALL_BORDERS & ~BControlLook::B_RIGHT_BORDER);
3246 }
3247 }
3248
3249
3250 void
_PassMessageToAddOn(BMessage * message)3251 BContainerWindow::_PassMessageToAddOn(BMessage* message)
3252 {
3253 LaunchInNewThread("Add-on-Pass-Message", B_NORMAL_PRIORITY,
3254 &RunAddOnMessageThread, new BMessage(*message), (void*)NULL);
3255 }
3256
3257
3258 void
_NewTemplateSubmenu(entry_ref dirRef)3259 BContainerWindow::_NewTemplateSubmenu(entry_ref dirRef)
3260 {
3261 entry_ref submenuRef;
3262 BPath path(&dirRef);
3263 path.Append(B_TRANSLATE_COMMENT("New submenu", "Folder name of New-template submenu"));
3264 get_ref_for_path(path.Path(), &submenuRef);
3265
3266 if (FSCreateNewFolder(&submenuRef) != B_OK)
3267 return;
3268
3269 // kAttrTemplateSubMenu shows the folder to be a submenu
3270 BNode node(&submenuRef);
3271 if (node.InitCheck() != B_OK)
3272 return;
3273 bool flag = true;
3274 node.WriteAttr(kAttrTemplateSubMenu, B_BOOL_TYPE, 0, &flag, sizeof(bool));
3275
3276 // show and select new submenu in Tracker
3277 BEntry entry(&submenuRef);
3278 node_ref nref;
3279 if (entry.GetNodeRef(&nref) != B_OK)
3280 return;
3281
3282 BMessage message(B_REFS_RECEIVED);
3283 message.AddRef("refs", &dirRef);
3284 message.AddData("nodeRefToSelect", B_RAW_TYPE, (void*)&nref, sizeof(node_ref));
3285 be_app->PostMessage(&message);
3286 }
3287
3288
3289 BMenuItem*
NewAttributeMenuItem(const char * label,const char * name,int32 type,float width,int32 align,bool editable,bool statField)3290 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3291 int32 type, float width, int32 align, bool editable, bool statField)
3292 {
3293 return NewAttributeMenuItem(label, name, type, NULL, width, align,
3294 editable, statField);
3295 }
3296
3297
3298 BMenuItem*
NewAttributeMenuItem(const char * label,const char * name,int32 type,const char * displayAs,float width,int32 align,bool editable,bool statField)3299 BContainerWindow::NewAttributeMenuItem(const char* label, const char* name,
3300 int32 type, const char* displayAs, float width, int32 align,
3301 bool editable, bool statField)
3302 {
3303 BMessage* message = new BMessage(kAttributeItem);
3304 message->AddString("attr_name", name);
3305 message->AddInt32("attr_type", type);
3306 message->AddInt32("attr_hash", (int32)AttrHashString(name, (uint32)type));
3307 message->AddFloat("attr_width", width);
3308 message->AddInt32("attr_align", align);
3309 if (displayAs != NULL)
3310 message->AddString("attr_display_as", displayAs);
3311 message->AddBool("attr_editable", editable);
3312 message->AddBool("attr_statfield", statField);
3313
3314 BMenuItem* menuItem = new BMenuItem(label, message);
3315 menuItem->SetTarget(PoseView());
3316
3317 return menuItem;
3318 }
3319
3320
3321 void
NewAttributesMenu(BMenu * menu)3322 BContainerWindow::NewAttributesMenu(BMenu* menu)
3323 {
3324 ASSERT(PoseView());
3325
3326 BMenuItem* item;
3327 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Copy layout"),
3328 new BMessage(kCopyAttributes)));
3329 item->SetTarget(PoseView());
3330 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Paste layout"),
3331 new BMessage(kPasteAttributes)));
3332 item->SetTarget(PoseView());
3333 menu->AddSeparatorItem();
3334
3335 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Name"),
3336 kAttrStatName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3337
3338 if (gLocalizedNamePreferred) {
3339 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Real name"),
3340 kAttrRealName, B_STRING_TYPE, 145, B_ALIGN_LEFT, true, true));
3341 }
3342
3343 menu->AddItem(NewAttributeMenuItem (B_TRANSLATE("Size"), kAttrStatSize,
3344 B_OFF_T_TYPE, 80, B_ALIGN_RIGHT, false, true));
3345
3346 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Modified"),
3347 kAttrStatModified, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3348
3349 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Created"),
3350 kAttrStatCreated, B_TIME_TYPE, 150, B_ALIGN_LEFT, false, true));
3351
3352 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Kind"),
3353 kAttrMIMEType, B_MIME_STRING_TYPE, 145, B_ALIGN_LEFT, false, false));
3354
3355 if (TargetModel()->IsTrash() || TargetModel()->InTrash()) {
3356 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Original name"),
3357 kAttrOriginalPath, B_STRING_TYPE, 225, B_ALIGN_LEFT, false,
3358 false));
3359 } else {
3360 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Location"), kAttrPath,
3361 B_STRING_TYPE, 225, B_ALIGN_LEFT, false, false));
3362 }
3363
3364 #ifdef OWNER_GROUP_ATTRIBUTES
3365 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Owner"), kAttrStatOwner,
3366 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3367
3368 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Group"), kAttrStatGroup,
3369 B_STRING_TYPE, 60, B_ALIGN_LEFT, false, true));
3370 #endif
3371
3372 menu->AddItem(NewAttributeMenuItem(B_TRANSLATE("Permissions"),
3373 kAttrStatMode, B_STRING_TYPE, 80, B_ALIGN_LEFT, false, true));
3374 }
3375
3376
3377 void
ShowAttributesMenu()3378 BContainerWindow::ShowAttributesMenu()
3379 {
3380 ASSERT(fAttrMenu);
3381 fMenuBar->AddItem(fAttrMenu);
3382 }
3383
3384
3385 void
HideAttributesMenu()3386 BContainerWindow::HideAttributesMenu()
3387 {
3388 ASSERT(fAttrMenu);
3389 fMenuBar->RemoveItem(fAttrMenu);
3390 }
3391
3392
3393 void
MarkAttributesMenu()3394 BContainerWindow::MarkAttributesMenu()
3395 {
3396 MarkAttributesMenu(fAttrMenu);
3397 }
3398
3399
3400 void
MarkAttributesMenu(BMenu * menu)3401 BContainerWindow::MarkAttributesMenu(BMenu* menu)
3402 {
3403 if (menu == NULL)
3404 return;
3405
3406 int32 count = menu->CountItems();
3407 for (int32 index = 0; index < count; index++) {
3408 BMenuItem* item = menu->ItemAt(index);
3409 int32 attrHash;
3410 if (item->Message() != NULL) {
3411 if (item->Message()->FindInt32("attr_hash", &attrHash) == B_OK)
3412 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash) != 0);
3413 else
3414 item->SetMarked(false);
3415 }
3416
3417 BMenu* submenu = item->Submenu();
3418 if (submenu != NULL) {
3419 int32 count2 = submenu->CountItems();
3420 for (int32 subindex = 0; subindex < count2; subindex++) {
3421 item = submenu->ItemAt(subindex);
3422 if (item->Message() != NULL) {
3423 if (item->Message()->FindInt32("attr_hash", &attrHash)
3424 == B_OK) {
3425 item->SetMarked(PoseView()->ColumnFor((uint32)attrHash)
3426 != 0);
3427 } else
3428 item->SetMarked(false);
3429 }
3430 }
3431 }
3432 }
3433 }
3434
3435
3436 void
MarkArrangeByMenu(BMenu * menu)3437 BContainerWindow::MarkArrangeByMenu(BMenu* menu)
3438 {
3439 if (menu == NULL)
3440 return;
3441
3442 int32 count = menu->CountItems();
3443 for (int32 index = 0; index < count; index++) {
3444 BMenuItem* item = menu->ItemAt(index);
3445 if (item->Message() != NULL) {
3446 uint32 attrHash;
3447 if (item->Message()->FindInt32("attr_hash",
3448 (int32*)&attrHash) == B_OK) {
3449 item->SetMarked(PoseView()->PrimarySort() == attrHash);
3450 } else if (item->Command() == kArrangeReverseOrder)
3451 item->SetMarked(PoseView()->ReverseSort());
3452 }
3453 }
3454 }
3455
3456
3457 void
AddMimeTypesToMenu()3458 BContainerWindow::AddMimeTypesToMenu()
3459 {
3460 AddMimeTypesToMenu(fAttrMenu);
3461 }
3462
3463
3464 // Adds a menu for a specific MIME type if it doesn't exist already.
3465 // Returns the menu, if it existed or not.
3466 BMenu*
AddMimeMenu(const BMimeType & mimeType,bool isSuperType,BMenu * menu,int32 start)3467 BContainerWindow::AddMimeMenu(const BMimeType& mimeType, bool isSuperType,
3468 BMenu* menu, int32 start)
3469 {
3470 AutoLock<BLooper> _(menu->Looper());
3471
3472 if (!mimeType.IsValid())
3473 return NULL;
3474
3475 // Check if we already have an entry for this MIME type in the menu.
3476 for (int32 i = start; BMenuItem* item = menu->ItemAt(i); i++) {
3477 BMessage* message = item->Message();
3478 if (message == NULL)
3479 continue;
3480
3481 const char* type;
3482 if (message->FindString("mimetype", &type) == B_OK
3483 && !strcmp(mimeType.Type(), type)) {
3484 return item->Submenu();
3485 }
3486 }
3487
3488 BMessage attrInfo;
3489 char description[B_MIME_TYPE_LENGTH];
3490 const char* label = mimeType.Type();
3491
3492 if (!mimeType.IsInstalled())
3493 return NULL;
3494
3495 // only add things to menu which have "user-visible" data
3496 if (mimeType.GetAttrInfo(&attrInfo) != B_OK)
3497 return NULL;
3498
3499 if (mimeType.GetShortDescription(description) == B_OK && description[0])
3500 label = description;
3501
3502 // go through each field in meta mime and add it to a menu
3503 BMenu* mimeMenu = NULL;
3504 if (isSuperType) {
3505 // If it is a supertype, we create the menu anyway as it may have
3506 // submenus later on.
3507 mimeMenu = new BMenu(label);
3508 BFont font;
3509 menu->GetFont(&font);
3510 mimeMenu->SetFont(&font);
3511 }
3512
3513 int32 index = -1;
3514 const char* publicName;
3515 while (attrInfo.FindString("attr:public_name", ++index, &publicName)
3516 == B_OK) {
3517 if (!attrInfo.FindBool("attr:viewable", index)) {
3518 // don't add if attribute not viewable
3519 continue;
3520 }
3521
3522 int32 type;
3523 int32 align;
3524 int32 width;
3525 bool editable;
3526 const char* attrName;
3527 if (attrInfo.FindString("attr:name", index, &attrName) != B_OK
3528 || attrInfo.FindInt32("attr:type", index, &type) != B_OK
3529 || attrInfo.FindBool("attr:editable", index, &editable) != B_OK
3530 || attrInfo.FindInt32("attr:width", index, &width) != B_OK
3531 || attrInfo.FindInt32("attr:alignment", index, &align) != B_OK)
3532 continue;
3533
3534 BString displayAs;
3535 attrInfo.FindString("attr:display_as", index, &displayAs);
3536
3537 if (mimeMenu == NULL) {
3538 // do a lazy allocation of the menu
3539 mimeMenu = new BMenu(label);
3540 BFont font;
3541 menu->GetFont(&font);
3542 mimeMenu->SetFont(&font);
3543 }
3544 mimeMenu->AddItem(NewAttributeMenuItem(publicName, attrName, type,
3545 displayAs.String(), width, align, editable, false));
3546 }
3547
3548 if (mimeMenu == NULL)
3549 return NULL;
3550
3551 BMessage* message = new BMessage(kMIMETypeItem);
3552 message->AddString("mimetype", mimeType.Type());
3553 menu->AddItem(new IconMenuItem(mimeMenu, message, mimeType.Type()));
3554
3555 return mimeMenu;
3556 }
3557
3558
3559 void
AddMimeTypesToMenu(BMenu * menu)3560 BContainerWindow::AddMimeTypesToMenu(BMenu* menu)
3561 {
3562 if (menu == NULL)
3563 return;
3564
3565 // Remove old mime type menus
3566 int32 start = menu->CountItems();
3567 while (start > 0 && menu->ItemAt(start - 1)->Submenu() != NULL) {
3568 delete menu->RemoveItem(start - 1);
3569 start--;
3570 }
3571
3572 // Add a separator item if there is none yet
3573 if (start > 0
3574 && dynamic_cast<BSeparatorItem*>(menu->ItemAt(start - 1)) == NULL)
3575 menu->AddSeparatorItem();
3576
3577 // Add MIME type in case we're a default query type window
3578 BPath path;
3579 if (TargetModel() != NULL) {
3580 TargetModel()->GetPath(&path);
3581 if (path.InitCheck() == B_OK
3582 && strstr(path.Path(), "/" kQueryTemplates "/") != NULL) {
3583 // demangle MIME type name
3584 BString name(TargetModel()->Name());
3585 name.ReplaceFirst('_', '/');
3586
3587 PoseView()->AddMimeType(name.String());
3588 }
3589 }
3590
3591 // Add MIME type menus
3592
3593 int32 typeCount = PoseView()->CountMimeTypes();
3594
3595 for (int32 index = 0; index < typeCount; index++) {
3596 BMimeType mimeType(PoseView()->MimeTypeAt(index));
3597 if (mimeType.InitCheck() == B_OK) {
3598 BMimeType superType;
3599 mimeType.GetSupertype(&superType);
3600 if (superType.InitCheck() == B_OK) {
3601 BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3602 if (superMenu != NULL) {
3603 // We have a supertype menu.
3604 AddMimeMenu(mimeType, false, superMenu, 0);
3605 }
3606 }
3607 }
3608 }
3609
3610 // remove empty super menus, promote sub-types if needed
3611
3612 for (int32 index = 0; index < typeCount; index++) {
3613 BMimeType mimeType(PoseView()->MimeTypeAt(index));
3614 BMimeType superType;
3615 mimeType.GetSupertype(&superType);
3616
3617 BMenu* superMenu = AddMimeMenu(superType, true, menu, start);
3618 if (superMenu == NULL)
3619 continue;
3620
3621 int32 itemsFound = 0;
3622 int32 menusFound = 0;
3623 for (int32 i = 0; BMenuItem* item = superMenu->ItemAt(i); i++) {
3624 if (item->Submenu() != NULL)
3625 menusFound++;
3626 else
3627 itemsFound++;
3628 }
3629
3630 if (itemsFound == 0) {
3631 if (menusFound != 0) {
3632 // promote types to the top level
3633 while (BMenuItem* item = superMenu->RemoveItem((int32)0)) {
3634 menu->AddItem(item);
3635 }
3636 }
3637
3638 menu->RemoveItem(superMenu->Superitem());
3639 delete superMenu->Superitem();
3640 }
3641 }
3642
3643 // remove separator if it's the only item in menu
3644 BMenuItem* separator = menu->ItemAt(menu->CountItems() - 1);
3645 if (dynamic_cast<BSeparatorItem*>(separator) != NULL) {
3646 menu->RemoveItem(separator);
3647 delete separator;
3648 }
3649
3650 MarkAttributesMenu(menu);
3651 }
3652
3653
3654 BHandler*
ResolveSpecifier(BMessage * message,int32 index,BMessage * specifier,int32 form,const char * property)3655 BContainerWindow::ResolveSpecifier(BMessage* message, int32 index,
3656 BMessage* specifier, int32 form, const char* property)
3657 {
3658 if (strcmp(property, "Poses") == 0) {
3659 // PRINT(("BContainerWindow::ResolveSpecifier %s\n", property));
3660 message->PopSpecifier();
3661 return PoseView();
3662 }
3663
3664 return _inherited::ResolveSpecifier(message, index, specifier,
3665 form, property);
3666 }
3667
3668
3669 PiggybackTaskLoop*
DelayedTaskLoop()3670 BContainerWindow::DelayedTaskLoop()
3671 {
3672 if (!fTaskLoop)
3673 fTaskLoop = new PiggybackTaskLoop;
3674
3675 return fTaskLoop;
3676 }
3677
3678
3679 bool
NeedsDefaultStateSetup()3680 BContainerWindow::NeedsDefaultStateSetup()
3681 {
3682 if (TargetModel() == NULL)
3683 return false;
3684
3685 if (TargetModel()->IsRoot()) {
3686 // don't try to set up anything if we are root
3687 return false;
3688 }
3689
3690 WindowStateNodeOpener opener(this, false);
3691 if (opener.StreamNode() == NULL) {
3692 // can't read state, give up
3693 return false;
3694 }
3695
3696 return !NodeHasSavedState(opener.Node());
3697 }
3698
3699
3700 bool
DefaultStateSourceNode(const char * name,BNode * result,bool createNew,bool createFolder)3701 BContainerWindow::DefaultStateSourceNode(const char* name, BNode* result,
3702 bool createNew, bool createFolder)
3703 {
3704 //PRINT(("looking for default state in tracker settings dir\n"));
3705 BPath settingsPath;
3706 if (FSFindTrackerSettingsDir(&settingsPath) != B_OK)
3707 return false;
3708
3709 BDirectory dir(settingsPath.Path());
3710
3711 BPath path(settingsPath);
3712 path.Append(name);
3713 if (!BEntry(path.Path()).Exists()) {
3714 if (!createNew)
3715 return false;
3716
3717 BPath tmpPath(settingsPath);
3718 for (;;) {
3719 // deal with several levels of folders
3720 const char* nextSlash = strchr(name, '/');
3721 if (!nextSlash)
3722 break;
3723
3724 BString tmp;
3725 tmp.SetTo(name, nextSlash - name);
3726 tmpPath.Append(tmp.String());
3727
3728 mkdir(tmpPath.Path(), 0777);
3729
3730 name = nextSlash + 1;
3731 if (!name[0]) {
3732 // can't deal with a slash at end
3733 return false;
3734 }
3735 }
3736
3737 if (createFolder) {
3738 if (mkdir(path.Path(), 0777) < 0)
3739 return false;
3740 } else {
3741 BFile file;
3742 if (dir.CreateFile(name, &file) != B_OK)
3743 return false;
3744 }
3745 }
3746
3747 //PRINT(("using default state from %s\n", path.Path()));
3748 result->SetTo(path.Path());
3749 return result->InitCheck() == B_OK;
3750 }
3751
3752
3753 void
SetupDefaultState()3754 BContainerWindow::SetupDefaultState()
3755 {
3756 BNode defaultingNode;
3757 // this is where we'll ulitimately get the state from
3758 bool gotDefaultingNode = 0;
3759 bool shouldStagger = false;
3760
3761 ASSERT(TargetModel() != NULL);
3762
3763 PRINT(("folder %s does not have any saved state\n", TargetModel()->Name()));
3764
3765 WindowStateNodeOpener opener(this, true);
3766 // this is our destination node, whatever it is for this window
3767 if (opener.StreamNode() == NULL)
3768 return;
3769
3770 if (!TargetModel()->IsRoot()) {
3771 BDirectory deskDir;
3772 FSGetDeskDir(&deskDir);
3773
3774 // try copying state from our parent directory, unless it is the
3775 // desktop folder
3776 BEntry entry(TargetModel()->EntryRef());
3777 BNode parent;
3778 if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK
3779 && parent != deskDir) {
3780 PRINT(("looking at parent for state\n"));
3781 if (NodeHasSavedState(&parent)) {
3782 PRINT(("got state from parent\n"));
3783 defaultingNode = parent;
3784 gotDefaultingNode = true;
3785 // when getting state from parent, stagger the window
3786 shouldStagger = true;
3787 }
3788 }
3789 }
3790
3791 if (!gotDefaultingNode
3792 // parent didn't have any state, use the template directory from
3793 // tracker settings folder for what our state should be
3794 // For simplicity we are not picking up the most recent
3795 // changes that didn't get committed if home is still open in
3796 // a window, that's probably not a problem; would be OK if state
3797 // got committed after every change
3798 && !DefaultStateSourceNode(kDefaultFolderTemplate, &defaultingNode,
3799 true)) {
3800 return;
3801 }
3802
3803 if (PoseView()->IsDesktop()) {
3804 // don't copy over the attributes if we are the Desktop
3805 return;
3806 }
3807
3808 // copy over the attributes
3809
3810 // set up a filter of the attributes we want copied
3811 const char* allowAttrs[] = {
3812 kAttrWindowFrame,
3813 kAttrWindowWorkspace,
3814 kAttrViewState,
3815 kAttrViewStateForeign,
3816 kAttrColumns,
3817 kAttrColumnsForeign,
3818 0
3819 };
3820
3821 // copy over attributes that apply; transform them properly, stripping
3822 // parts that do not apply, adding a window stagger, etc.
3823
3824 StaggerOneParams params;
3825 params.rectFromParent = shouldStagger;
3826 SelectiveAttributeTransformer frameOffsetter(kAttrWindowFrame,
3827 OffsetFrameOne, ¶ms);
3828 SelectiveAttributeTransformer scrollOriginCleaner(kAttrViewState,
3829 ClearViewOriginOne, ¶ms);
3830
3831 // do it
3832 AttributeStreamMemoryNode memoryNode;
3833 NamesToAcceptAttrFilter filter(allowAttrs);
3834 AttributeStreamFileNode fileNode(&defaultingNode);
3835
3836 *opener.StreamNode() << scrollOriginCleaner << frameOffsetter
3837 << memoryNode << filter << fileNode;
3838 }
3839
3840
3841 void
RestoreWindowState(AttributeStreamNode * node)3842 BContainerWindow::RestoreWindowState(AttributeStreamNode* node)
3843 {
3844 if (node == NULL || PoseView()->IsDesktop()) {
3845 // don't restore any window state if we are the Desktop
3846 return;
3847 }
3848
3849 const char* rectAttributeName;
3850 const char* workspaceAttributeName;
3851 if (TargetModel()->IsRoot()) {
3852 rectAttributeName = kAttrDisksFrame;
3853 workspaceAttributeName = kAttrDisksWorkspace;
3854 } else {
3855 rectAttributeName = kAttrWindowFrame;
3856 workspaceAttributeName = kAttrWindowWorkspace;
3857 }
3858
3859 BRect frame(Frame());
3860 if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
3861 == sizeof(BRect)) {
3862 const float scalingFactor = be_plain_font->Size() / 12.0f;
3863 frame.left *= scalingFactor;
3864 frame.top *= scalingFactor;
3865 frame.right *= scalingFactor;
3866 frame.bottom *= scalingFactor;
3867
3868 MoveTo(frame.LeftTop());
3869 ResizeTo(frame.Width(), frame.Height());
3870 } else
3871 sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
3872
3873 fPreviousBounds = Bounds();
3874
3875 uint32 workspace;
3876 if (((fOpenFlags & kRestoreWorkspace) != 0)
3877 && node->Read(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
3878 &workspace) == sizeof(uint32))
3879 SetWorkspaces(workspace);
3880
3881 if ((fOpenFlags & kIsHidden) != 0)
3882 Minimize(true);
3883
3884 // restore window decor settings
3885 int32 size = node->Contains(kAttrWindowDecor, B_RAW_TYPE);
3886 if (size > 0) {
3887 char buffer[size];
3888 if (((fOpenFlags & kRestoreDecor) != 0)
3889 && node->Read(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer)
3890 == size) {
3891 BMessage decorSettings;
3892 if (decorSettings.Unflatten(buffer) == B_OK)
3893 SetDecoratorSettings(decorSettings);
3894 }
3895 }
3896 }
3897
3898
3899 void
RestoreWindowState(const BMessage & message)3900 BContainerWindow::RestoreWindowState(const BMessage& message)
3901 {
3902 if (PoseView()->IsDesktop()) {
3903 // don't restore any window state if we are the Desktop
3904 return;
3905 }
3906
3907 const char* rectAttributeName;
3908 const char* workspaceAttributeName;
3909 if (TargetModel()->IsRoot()) {
3910 rectAttributeName = kAttrDisksFrame;
3911 workspaceAttributeName = kAttrDisksWorkspace;
3912 } else {
3913 rectAttributeName = kAttrWindowFrame;
3914 workspaceAttributeName = kAttrWindowWorkspace;
3915 }
3916
3917 BRect frame(Frame());
3918 if (message.FindRect(rectAttributeName, &frame) == B_OK) {
3919 const float scalingFactor = be_plain_font->Size() / 12.0f;
3920 frame.left *= scalingFactor;
3921 frame.top *= scalingFactor;
3922 frame.right *= scalingFactor;
3923 frame.bottom *= scalingFactor;
3924
3925 MoveTo(frame.LeftTop());
3926 ResizeTo(frame.Width(), frame.Height());
3927 } else
3928 sNewWindRect.OffsetBy(sWindowStaggerBy, sWindowStaggerBy);
3929
3930 uint32 workspace;
3931 if (((fOpenFlags & kRestoreWorkspace) != 0)
3932 && message.FindInt32(workspaceAttributeName,
3933 (int32*)&workspace) == B_OK) {
3934 SetWorkspaces(workspace);
3935 }
3936
3937 if ((fOpenFlags & kIsHidden) != 0)
3938 Minimize(true);
3939
3940 // restore window decor settings
3941 BMessage decorSettings;
3942 if (((fOpenFlags & kRestoreDecor) != 0)
3943 && message.FindMessage(kAttrWindowDecor, &decorSettings) == B_OK) {
3944 SetDecoratorSettings(decorSettings);
3945 }
3946
3947 fStateNeedsSaving = false;
3948 // Undo the effect of the above MoveTo and ResizeTo calls
3949 }
3950
3951
3952 void
SaveWindowState(AttributeStreamNode * node)3953 BContainerWindow::SaveWindowState(AttributeStreamNode* node)
3954 {
3955 if (TargetModel() != NULL && PoseView()->IsDesktop()) {
3956 // don't save window state if we are the Desktop
3957 return;
3958 }
3959
3960 ASSERT(node != NULL);
3961
3962 const char* rectAttributeName;
3963 const char* workspaceAttributeName;
3964 if (TargetModel()->IsRoot()) {
3965 rectAttributeName = kAttrDisksFrame;
3966 workspaceAttributeName = kAttrDisksWorkspace;
3967 } else {
3968 rectAttributeName = kAttrWindowFrame;
3969 workspaceAttributeName = kAttrWindowWorkspace;
3970 }
3971
3972 // node is null if it already got deleted
3973 BRect frame(Frame());
3974 const float scalingFactor = be_plain_font->Size() / 12.0f;
3975 frame.left /= scalingFactor;
3976 frame.top /= scalingFactor;
3977 frame.right /= scalingFactor;
3978 frame.bottom /= scalingFactor;
3979 node->Write(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame);
3980
3981 uint32 workspaces = Workspaces();
3982 node->Write(workspaceAttributeName, 0, B_INT32_TYPE, sizeof(uint32),
3983 &workspaces);
3984
3985 BMessage decorSettings;
3986 if (GetDecoratorSettings(&decorSettings) == B_OK) {
3987 int32 size = decorSettings.FlattenedSize();
3988 char buffer[size];
3989 if (decorSettings.Flatten(buffer, size) == B_OK) {
3990 node->Write(kAttrWindowDecor, 0, B_RAW_TYPE, size, buffer);
3991 }
3992 }
3993 }
3994
3995
3996 void
SaveWindowState(BMessage & message) const3997 BContainerWindow::SaveWindowState(BMessage& message) const
3998 {
3999 const char* rectAttributeName;
4000 const char* workspaceAttributeName;
4001
4002 if (TargetModel() != NULL && TargetModel()->IsRoot()) {
4003 rectAttributeName = kAttrDisksFrame;
4004 workspaceAttributeName = kAttrDisksWorkspace;
4005 } else {
4006 rectAttributeName = kAttrWindowFrame;
4007 workspaceAttributeName = kAttrWindowWorkspace;
4008 }
4009
4010 // node is null if it already got deleted
4011 BRect frame(Frame());
4012 const float scalingFactor = be_plain_font->Size() / 12.0f;
4013 frame.left /= scalingFactor;
4014 frame.top /= scalingFactor;
4015 frame.right /= scalingFactor;
4016 frame.bottom /= scalingFactor;
4017 message.AddRect(rectAttributeName, frame);
4018 message.AddInt32(workspaceAttributeName, (int32)Workspaces());
4019
4020 BMessage decorSettings;
4021 if (GetDecoratorSettings(&decorSettings) == B_OK) {
4022 message.AddMessage(kAttrWindowDecor, &decorSettings);
4023 }
4024 }
4025
4026
4027 status_t
DragStart(const BMessage * dragMessage)4028 BContainerWindow::DragStart(const BMessage* dragMessage)
4029 {
4030 if (dragMessage == NULL)
4031 return B_ERROR;
4032
4033 // if already dragging, or
4034 // if all the refs match
4035 if (Dragging()
4036 && SpringLoadedFolderCompareMessages(dragMessage, fDragMessage)) {
4037 return B_OK;
4038 }
4039
4040 // cache the current drag message
4041 // build a list of the mimetypes in the message
4042 SpringLoadedFolderCacheDragData(dragMessage, &fDragMessage,
4043 &fCachedTypesList);
4044
4045 fWaitingForRefs = true;
4046
4047 return B_OK;
4048 }
4049
4050
4051 void
DragStop()4052 BContainerWindow::DragStop()
4053 {
4054 delete fDragMessage;
4055 fDragMessage = NULL;
4056
4057 delete fCachedTypesList;
4058 fCachedTypesList = NULL;
4059
4060 fWaitingForRefs = false;
4061 }
4062
4063
4064 void
ShowSelectionWindow()4065 BContainerWindow::ShowSelectionWindow()
4066 {
4067 if (fSelectionWindow == NULL) {
4068 fSelectionWindow = new SelectionWindow(this);
4069 fSelectionWindow->Show();
4070 } else if (fSelectionWindow->Lock()) {
4071 // The window is already there, just bring it close
4072 fSelectionWindow->MoveCloseToMouse();
4073 if (fSelectionWindow->IsHidden())
4074 fSelectionWindow->Show();
4075
4076 fSelectionWindow->Unlock();
4077 }
4078 }
4079
4080
4081 void
ShowNavigator(bool show)4082 BContainerWindow::ShowNavigator(bool show)
4083 {
4084 if (PoseView()->IsDesktop() || !TargetModel()->IsDirectory() || PoseView()->IsFilePanel())
4085 return;
4086
4087 if (show) {
4088 if (Navigator() != NULL && !Navigator()->IsHidden())
4089 return;
4090
4091 if (Navigator() == NULL) {
4092 fNavigator = new BNavigator(TargetModel());
4093 fPoseContainer->GridLayout()->AddView(fNavigator, 0, 0, 2);
4094 }
4095
4096 if (Navigator()->IsHidden())
4097 Navigator()->Show();
4098
4099 if (PoseView()->VScrollBar())
4100 PoseView()->UpdateScrollRange();
4101 } else {
4102 if (Navigator() == NULL || Navigator()->IsHidden())
4103 return;
4104
4105 if (PoseView()->VScrollBar())
4106 PoseView()->UpdateScrollRange();
4107
4108 fNavigator->Hide();
4109 }
4110 }
4111
4112
4113 void
SetSingleWindowBrowseShortcuts(bool enabled)4114 BContainerWindow::SetSingleWindowBrowseShortcuts(bool enabled)
4115 {
4116 if (PoseView()->IsDesktop())
4117 return;
4118
4119 if (enabled) {
4120 if (Navigator() == NULL)
4121 return;
4122
4123 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4124 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4125 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4126
4127 AddShortcut(B_LEFT_ARROW, B_COMMAND_KEY,
4128 new BMessage(kNavigatorCommandBackward), Navigator());
4129 AddShortcut(B_RIGHT_ARROW, B_COMMAND_KEY,
4130 new BMessage(kNavigatorCommandForward), Navigator());
4131 AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4132 new BMessage(kNavigatorCommandUp), Navigator());
4133
4134 AddShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4135 new BMessage(kNavigatorCommandBackward), Navigator());
4136 AddShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4137 new BMessage(kNavigatorCommandForward), Navigator());
4138 AddShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4139 new BMessage(kNavigatorCommandUp), Navigator());
4140 AddShortcut(B_DOWN_ARROW, B_OPTION_KEY | B_COMMAND_KEY,
4141 new BMessage(kOpenSelection), PoseView());
4142 AddShortcut('L', B_COMMAND_KEY,
4143 new BMessage(kNavigatorCommandSetFocus), Navigator());
4144 } else {
4145 RemoveShortcut(B_LEFT_ARROW, B_COMMAND_KEY);
4146 RemoveShortcut(B_RIGHT_ARROW, B_COMMAND_KEY);
4147 RemoveShortcut(B_UP_ARROW, B_COMMAND_KEY);
4148 // This is added again, below, with a new meaning.
4149
4150 RemoveShortcut(B_LEFT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4151 RemoveShortcut(B_RIGHT_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4152 RemoveShortcut(B_UP_ARROW, B_OPTION_KEY | B_COMMAND_KEY);
4153 RemoveShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY);
4154 // This also changes meaning, added again below.
4155
4156 AddShortcut(B_DOWN_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4157 new BMessage(kOpenSelection), PoseView());
4158 AddShortcut(B_UP_ARROW, B_COMMAND_KEY,
4159 new BMessage(kOpenParentDir), PoseView());
4160 // We change the meaning from kNavigatorCommandUp
4161 // to kOpenParentDir.
4162 AddShortcut(B_UP_ARROW, B_COMMAND_KEY | B_OPTION_KEY,
4163 new BMessage(kOpenParentDir), PoseView());
4164 // command + option results in closing the parent window
4165 RemoveShortcut('L', B_COMMAND_KEY);
4166 }
4167 }
4168
4169
4170 void
SetPathWatchingEnabled(bool enable)4171 BContainerWindow::SetPathWatchingEnabled(bool enable)
4172 {
4173 if (IsPathWatchingEnabled()) {
4174 stop_watching(this);
4175 fIsWatchingPath = false;
4176 }
4177
4178 if (enable) {
4179 if (TargetModel() != NULL) {
4180 BEntry entry;
4181
4182 TargetModel()->GetEntry(&entry);
4183 status_t err;
4184 do {
4185 err = entry.GetParent(&entry);
4186 if (err != B_OK)
4187 break;
4188
4189 char name[B_FILE_NAME_LENGTH];
4190 entry.GetName(name);
4191 if (strcmp(name, "/") == 0)
4192 break;
4193
4194 node_ref ref;
4195 entry.GetNodeRef(&ref);
4196 watch_node(&ref, B_WATCH_NAME, this);
4197 } while (err == B_OK);
4198
4199 fIsWatchingPath = err == B_OK;
4200 } else
4201 fIsWatchingPath = false;
4202 }
4203 }
4204
4205
4206 void
PulseTaskLoop()4207 BContainerWindow::PulseTaskLoop()
4208 {
4209 if (fTaskLoop)
4210 fTaskLoop->PulseMe();
4211 }
4212
4213
4214 void
PopulateArrangeByMenu(BMenu * menu)4215 BContainerWindow::PopulateArrangeByMenu(BMenu* menu)
4216 {
4217 if (!fAttrMenu || !menu)
4218 return;
4219 // empty fArrangeByMenu...
4220 BMenuItem* item;
4221 while ((item = menu->RemoveItem((int32)0)) != NULL)
4222 delete item;
4223
4224 int32 itemCount = fAttrMenu->CountItems();
4225 for (int32 i = 0; i < itemCount; i++) {
4226 item = fAttrMenu->ItemAt(i);
4227 if (item->Command() == kAttributeItem) {
4228 BMessage* message = new BMessage(*(item->Message()));
4229 message->what = kArrangeBy;
4230 BMenuItem* newItem = new BMenuItem(item->Label(), message);
4231 newItem->SetTarget(PoseView());
4232 menu->AddItem(newItem);
4233 }
4234 }
4235
4236 menu->AddSeparatorItem();
4237
4238 item = new BMenuItem(B_TRANSLATE("Reverse order"),
4239 new BMessage(kArrangeReverseOrder));
4240
4241 item->SetTarget(PoseView());
4242 menu->AddItem(item);
4243
4244 menu->AddSeparatorItem();
4245
4246 item = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(kCleanup), 'K');
4247 item->SetTarget(PoseView());
4248 menu->AddItem(item);
4249 }
4250
4251
4252 // #pragma mark - WindowStateNodeOpener
4253
4254
WindowStateNodeOpener(BContainerWindow * window,bool forWriting)4255 WindowStateNodeOpener::WindowStateNodeOpener(BContainerWindow* window,
4256 bool forWriting)
4257 :
4258 fModelOpener(NULL),
4259 fNode(NULL),
4260 fStreamNode(NULL)
4261 {
4262 if (window->TargetModel() != NULL && window->TargetModel()->IsRoot()) {
4263 BDirectory dir;
4264 if (FSGetDeskDir(&dir) == B_OK) {
4265 fNode = new BDirectory(dir);
4266 fStreamNode = new AttributeStreamFileNode(fNode);
4267 }
4268 } else if (window->TargetModel() != NULL) {
4269 fModelOpener = new ModelNodeLazyOpener(window->TargetModel(),
4270 forWriting, false);
4271 if (fModelOpener->IsOpen(forWriting)) {
4272 fStreamNode = new AttributeStreamFileNode(
4273 fModelOpener->TargetModel()->Node());
4274 }
4275 }
4276 }
4277
~WindowStateNodeOpener()4278 WindowStateNodeOpener::~WindowStateNodeOpener()
4279 {
4280 delete fModelOpener;
4281 delete fNode;
4282 delete fStreamNode;
4283 }
4284
4285
4286 void
SetTo(const BDirectory * node)4287 WindowStateNodeOpener::SetTo(const BDirectory* node)
4288 {
4289 delete fModelOpener;
4290 delete fNode;
4291 delete fStreamNode;
4292
4293 fModelOpener = NULL;
4294 fNode = new BDirectory(*node);
4295 fStreamNode = new AttributeStreamFileNode(fNode);
4296 }
4297
4298
4299 void
SetTo(const BEntry * entry,bool forWriting)4300 WindowStateNodeOpener::SetTo(const BEntry* entry, bool forWriting)
4301 {
4302 delete fModelOpener;
4303 delete fNode;
4304 delete fStreamNode;
4305
4306 fModelOpener = NULL;
4307 fNode = new BFile(entry, (uint32)(forWriting ? O_RDWR : O_RDONLY));
4308 fStreamNode = new AttributeStreamFileNode(fNode);
4309 }
4310
4311
4312 void
SetTo(Model * model,bool forWriting)4313 WindowStateNodeOpener::SetTo(Model* model, bool forWriting)
4314 {
4315 delete fModelOpener;
4316 delete fNode;
4317 delete fStreamNode;
4318
4319 fNode = NULL;
4320 fStreamNode = NULL;
4321 fModelOpener = new ModelNodeLazyOpener(model, forWriting, false);
4322 if (fModelOpener->IsOpen(forWriting)) {
4323 fStreamNode = new AttributeStreamFileNode(
4324 fModelOpener->TargetModel()->Node());
4325 }
4326 }
4327
4328
4329 AttributeStreamNode*
StreamNode() const4330 WindowStateNodeOpener::StreamNode() const
4331 {
4332 return fStreamNode;
4333 }
4334
4335
4336 BNode*
Node() const4337 WindowStateNodeOpener::Node() const
4338 {
4339 if (!fStreamNode)
4340 return NULL;
4341
4342 if (fNode)
4343 return fNode;
4344
4345 return fModelOpener->TargetModel()->Node();
4346 }
4347
4348
4349 // #pragma mark - BorderedView
4350
4351
BorderedView()4352 BorderedView::BorderedView()
4353 :
4354 BGroupView(B_VERTICAL, 0),
4355 fEnableBorderHighlight(true)
4356 {
4357 GroupLayout()->SetInsets(1);
4358 }
4359
4360
4361 void
WindowActivated(bool active)4362 BorderedView::WindowActivated(bool active)
4363 {
4364 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4365 if (window == NULL)
4366 return;
4367
4368 if (window->PoseView()->IsFocus())
4369 PoseViewFocused(active); // Update border color
4370 }
4371
4372
EnableBorderHighlight(bool enable)4373 void BorderedView::EnableBorderHighlight(bool enable)
4374 {
4375 fEnableBorderHighlight = enable;
4376 PoseViewFocused(false);
4377 }
4378
4379
4380 void
PoseViewFocused(bool focused)4381 BorderedView::PoseViewFocused(bool focused)
4382 {
4383 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4384 if (window == NULL)
4385 return;
4386
4387 color_which base = B_DOCUMENT_BACKGROUND_COLOR;
4388 float tint = B_DARKEN_2_TINT;
4389 if (focused && window->IsActive() && fEnableBorderHighlight) {
4390 base = B_KEYBOARD_NAVIGATION_COLOR;
4391 tint = B_NO_TINT;
4392 }
4393
4394 BScrollBar* hScrollBar = window->PoseView()->HScrollBar();
4395 if (hScrollBar != NULL)
4396 hScrollBar->SetBorderHighlighted(focused);
4397
4398 BScrollBar* vScrollBar = window->PoseView()->VScrollBar();
4399 if (vScrollBar != NULL)
4400 vScrollBar->SetBorderHighlighted(focused);
4401
4402 SetViewUIColor(base, tint);
4403 Invalidate();
4404 }
4405
4406
4407 void
Pulse()4408 BorderedView::Pulse()
4409 {
4410 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4411 if (window != NULL)
4412 window->PulseTaskLoop();
4413 }
4414