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