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