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