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 30 trademarks of Be Incorporated in the United States and other countries. 31 Other brand product names are registered trademarks or trademarks of 32 their respective holders. All rights reserved. 33 */ 34 35 // NavMenu is a hierarchical menu of volumes, folders, files and queries 36 // displays icons, uses the SlowMenu API for full interruptability 37 38 39 #include "NavMenu.h" 40 41 #include <algorithm> 42 43 #include <stdlib.h> 44 #include <string.h> 45 #include <strings.h> 46 47 #include <Application.h> 48 #include <Catalog.h> 49 #include <Debug.h> 50 #include <Directory.h> 51 #include <Locale.h> 52 #include <Path.h> 53 #include <Query.h> 54 #include <Screen.h> 55 #include <StopWatch.h> 56 #include <Volume.h> 57 #include <VolumeRoster.h> 58 59 #include "Attributes.h" 60 #include "Commands.h" 61 #include "ContainerWindow.h" 62 #include "DesktopPoseView.h" 63 #include "FunctionObject.h" 64 #include "FSUtils.h" 65 #include "IconMenuItem.h" 66 #include "MimeTypes.h" 67 #include "PoseView.h" 68 #include "QueryPoseView.h" 69 #include "Thread.h" 70 #include "Tracker.h" 71 #include "VirtualDirectoryEntryList.h" 72 73 74 namespace BPrivate { 75 76 const int32 kMinMenuWidth = 150; 77 78 enum nav_flags { 79 kVolumesOnly = 1, 80 kShowParent = 2 81 }; 82 83 84 bool 85 SpringLoadedFolderCompareMessages(const BMessage* incoming, 86 const BMessage* dragMessage) 87 { 88 if (incoming == NULL || dragMessage == NULL) 89 return false; 90 91 bool refsMatch = false; 92 for (int32 inIndex = 0; incoming->HasRef("refs", inIndex); inIndex++) { 93 entry_ref inRef; 94 if (incoming->FindRef("refs", inIndex, &inRef) != B_OK) { 95 refsMatch = false; 96 break; 97 } 98 99 bool inRefMatch = false; 100 for (int32 dragIndex = 0; dragMessage->HasRef("refs", dragIndex); 101 dragIndex++) { 102 entry_ref dragRef; 103 if (dragMessage->FindRef("refs", dragIndex, &dragRef) != B_OK) { 104 inRefMatch = false; 105 break; 106 } 107 // if the incoming ref matches any ref in the drag ref 108 // then we can try the next incoming ref 109 if (inRef == dragRef) { 110 inRefMatch = true; 111 break; 112 } 113 } 114 refsMatch = inRefMatch; 115 if (!inRefMatch) 116 break; 117 } 118 119 if (refsMatch) { 120 // If all the refs match try and see if this is a new drag with 121 // the same drag contents. 122 refsMatch = false; 123 BPoint incomingPoint; 124 BPoint dragPoint; 125 if (incoming->FindPoint("click_pt", &incomingPoint) == B_OK 126 && dragMessage->FindPoint("click_pt", &dragPoint) == B_OK) { 127 refsMatch = (incomingPoint == dragPoint); 128 } 129 } 130 131 return refsMatch; 132 } 133 134 135 void 136 SpringLoadedFolderSetMenuStates(const BMenu* menu, 137 const BObjectList<BString>* typeslist) 138 { 139 if (menu == NULL || typeslist == NULL || typeslist->IsEmpty()) 140 return; 141 142 // If a types list exists iterate through the list and see if each item 143 // can support any item in the list and set the enabled state of the item. 144 int32 count = menu->CountItems(); 145 for (int32 index = 0 ; index < count ; index++) { 146 ModelMenuItem* item = dynamic_cast<ModelMenuItem*>(menu->ItemAt(index)); 147 if (item == NULL) 148 continue; 149 150 const Model* model = item->TargetModel(); 151 if (!model) 152 continue; 153 154 if (model->IsSymLink()) { 155 // find out what the model is, resolve if symlink 156 BEntry entry(model->EntryRef(), true); 157 if (entry.InitCheck() == B_OK) { 158 if (entry.IsDirectory()) { 159 // folder? always keep enabled 160 item->SetEnabled(true); 161 } else { 162 // other, check its support 163 Model resolvedModel(&entry); 164 int32 supported 165 = resolvedModel.SupportsMimeType(NULL, typeslist); 166 item->SetEnabled(supported != kDoesNotSupportType); 167 } 168 } else { 169 // bad entry ref (bad symlink?), disable 170 item->SetEnabled(false); 171 } 172 } else if (model->IsDirectory() || model->IsRoot() || model->IsVolume()) 173 // always enabled if a container 174 item->SetEnabled(true); 175 else if (model->IsFile() || model->IsExecutable()) { 176 int32 supported = model->SupportsMimeType(NULL, typeslist); 177 item->SetEnabled(supported != kDoesNotSupportType); 178 } else 179 item->SetEnabled(false); 180 } 181 } 182 183 184 void 185 SpringLoadedFolderAddUniqueTypeToList(entry_ref* ref, 186 BObjectList<BString>* typeslist) 187 { 188 if (ref == NULL || typeslist == NULL) 189 return; 190 191 // get the mime type for the current ref 192 BNodeInfo nodeinfo; 193 BNode node(ref); 194 if (node.InitCheck() != B_OK) 195 return; 196 197 nodeinfo.SetTo(&node); 198 199 char mimestr[B_MIME_TYPE_LENGTH]; 200 // add it to the list 201 if (nodeinfo.GetType(mimestr) == B_OK && strlen(mimestr) > 0) { 202 // If this is a symlink, add symlink to the list (below) 203 // resolve the symlink, add the resolved type to the list. 204 if (strcmp(B_LINK_MIMETYPE, mimestr) == 0) { 205 BEntry entry(ref, true); 206 if (entry.InitCheck() == B_OK) { 207 entry_ref resolvedRef; 208 if (entry.GetRef(&resolvedRef) == B_OK) 209 SpringLoadedFolderAddUniqueTypeToList(&resolvedRef, 210 typeslist); 211 } 212 } 213 // scan the current list, don't add dups 214 bool isUnique = true; 215 int32 count = typeslist->CountItems(); 216 for (int32 index = 0 ; index < count ; index++) { 217 if (typeslist->ItemAt(index)->Compare(mimestr) == 0) { 218 isUnique = false; 219 break; 220 } 221 } 222 223 if (isUnique) 224 typeslist->AddItem(new BString(mimestr)); 225 } 226 } 227 228 229 void 230 SpringLoadedFolderCacheDragData(const BMessage* incoming, BMessage** message, 231 BObjectList<BString>** typeslist) 232 { 233 if (incoming == NULL) 234 return; 235 236 delete* message; 237 delete* typeslist; 238 239 BMessage* localMessage = new BMessage(*incoming); 240 BObjectList<BString>* localTypesList = new BObjectList<BString>(10, true); 241 242 for (int32 index = 0; incoming->HasRef("refs", index); index++) { 243 entry_ref ref; 244 if (incoming->FindRef("refs", index, &ref) != B_OK) 245 continue; 246 247 SpringLoadedFolderAddUniqueTypeToList(&ref, localTypesList); 248 } 249 250 *message = localMessage; 251 *typeslist = localTypesList; 252 } 253 254 } 255 256 257 // #pragma mark - BNavMenu 258 259 260 #undef B_TRANSLATION_CONTEXT 261 #define B_TRANSLATION_CONTEXT "NavMenu" 262 263 264 BNavMenu::BNavMenu(const char* title, uint32 message, const BHandler* target, 265 BWindow* parentWindow, const BObjectList<BString>* list) 266 : 267 BSlowMenu(title), 268 fMessage(message), 269 fMessenger(target, target->Looper()), 270 fParentWindow(parentWindow), 271 fFlags(0), 272 fItemList(NULL), 273 fContainer(NULL), 274 fIteratingDesktop(false), 275 fTypesList(new BObjectList<BString>(10, true)) 276 { 277 if (list != NULL) 278 *fTypesList = *list; 279 280 InitIconPreloader(); 281 282 // add the parent window to the invocation message so that it 283 // can be closed if option modifier held down during invocation 284 BContainerWindow* originatingWindow = 285 dynamic_cast<BContainerWindow*>(fParentWindow); 286 if (originatingWindow != NULL) { 287 fMessage.AddData("nodeRefsToClose", B_RAW_TYPE, 288 originatingWindow->TargetModel()->NodeRef(), sizeof(node_ref)); 289 } 290 291 // too long to have triggers 292 SetTriggersEnabled(false); 293 } 294 295 296 BNavMenu::BNavMenu(const char* title, uint32 message, 297 const BMessenger& messenger, BWindow* parentWindow, 298 const BObjectList<BString>* list) 299 : 300 BSlowMenu(title), 301 fMessage(message), 302 fMessenger(messenger), 303 fParentWindow(parentWindow), 304 fFlags(0), 305 fItemList(NULL), 306 fContainer(NULL), 307 fIteratingDesktop(false), 308 fTypesList(new BObjectList<BString>(10, true)) 309 { 310 if (list != NULL) 311 *fTypesList = *list; 312 313 InitIconPreloader(); 314 315 // add the parent window to the invocation message so that it 316 // can be closed if option modifier held down during invocation 317 BContainerWindow* originatingWindow = 318 dynamic_cast<BContainerWindow*>(fParentWindow); 319 if (originatingWindow != NULL) { 320 fMessage.AddData("nodeRefsToClose", B_RAW_TYPE, 321 originatingWindow->TargetModel()->NodeRef(), sizeof (node_ref)); 322 } 323 324 // too long to have triggers 325 SetTriggersEnabled(false); 326 } 327 328 329 BNavMenu::~BNavMenu() 330 { 331 delete fTypesList; 332 } 333 334 335 void 336 BNavMenu::AttachedToWindow() 337 { 338 BSlowMenu::AttachedToWindow(); 339 340 SpringLoadedFolderSetMenuStates(this, fTypesList); 341 // If dragging, (fTypesList != NULL) set the menu items enabled state 342 // relative to the ability to handle an item in the drag message. 343 ResetTargets(); 344 // allow an opportunity to reset the target for each of the items 345 } 346 347 348 void 349 BNavMenu::DetachedFromWindow() 350 { 351 } 352 353 354 void 355 BNavMenu::ResetTargets() 356 { 357 SetTargetForItems(Target()); 358 } 359 360 361 void 362 BNavMenu::ForceRebuild() 363 { 364 ClearMenuBuildingState(); 365 fMenuBuilt = false; 366 } 367 368 369 bool 370 BNavMenu::NeedsToRebuild() const 371 { 372 return !fMenuBuilt; 373 } 374 375 376 void 377 BNavMenu::SetNavDir(const entry_ref* ref) 378 { 379 ForceRebuild(); 380 // reset the slow menu building mechanism so we can add more stuff 381 382 fNavDir = *ref; 383 } 384 385 386 void 387 BNavMenu::ClearMenuBuildingState() 388 { 389 delete fContainer; 390 fContainer = NULL; 391 392 // item list is non-owning, need to delete the items because 393 // they didn't get added to the menu 394 if (fItemList != NULL) { 395 RemoveItems(0, fItemList->CountItems(), true); 396 397 delete fItemList; 398 fItemList = NULL; 399 } 400 } 401 402 403 bool 404 BNavMenu::StartBuildingItemList() 405 { 406 BEntry entry; 407 408 if (fNavDir.device < 0 || entry.SetTo(&fNavDir, true) != B_OK 409 || !entry.Exists()) { 410 return false; 411 } 412 413 fItemList = new BObjectList<BMenuItem>(50); 414 415 fIteratingDesktop = false; 416 417 BDirectory parent; 418 status_t status = entry.GetParent(&parent); 419 420 // if ref is the root item then build list of volume root dirs 421 fFlags = uint8((fFlags & ~kVolumesOnly) 422 | (status == B_ENTRY_NOT_FOUND ? kVolumesOnly : 0)); 423 if (fFlags & kVolumesOnly) 424 return true; 425 426 Model startModel(&entry, true); 427 if (startModel.InitCheck() != B_OK || !startModel.IsContainer()) 428 return false; 429 430 if (startModel.IsQuery()) { 431 fContainer = new QueryEntryListCollection(&startModel); 432 } else if (startModel.IsVirtualDirectory()) { 433 fContainer = new VirtualDirectoryEntryList(&startModel); 434 } else if (startModel.IsDesktop()) { 435 fIteratingDesktop = true; 436 fContainer = DesktopPoseView::InitDesktopDirentIterator(0, 437 startModel.EntryRef()); 438 AddRootItemsIfNeeded(); 439 AddTrashItem(); 440 } else if (startModel.IsTrash()) { 441 // the trash window needs to display a union of all the 442 // trash folders from all the mounted volumes 443 BVolumeRoster volRoster; 444 volRoster.Rewind(); 445 BVolume volume; 446 fContainer = new EntryIteratorList(); 447 448 while (volRoster.GetNextVolume(&volume) == B_OK) { 449 if (volume.IsReadOnly() || !volume.IsPersistent()) 450 continue; 451 452 BDirectory trashDir; 453 454 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK) { 455 EntryIteratorList* iteratorList 456 = dynamic_cast<EntryIteratorList*>(fContainer); 457 458 ASSERT(iteratorList != NULL); 459 460 if (iteratorList != NULL) 461 iteratorList->AddItem(new DirectoryEntryList(trashDir)); 462 } 463 } 464 } else { 465 BDirectory* directory = dynamic_cast<BDirectory*>(startModel.Node()); 466 467 ASSERT(directory != NULL); 468 469 if (directory != NULL) 470 fContainer = new DirectoryEntryList(*directory); 471 } 472 473 if (fContainer == NULL || fContainer->InitCheck() != B_OK) 474 return false; 475 476 fContainer->Rewind(); 477 478 return true; 479 } 480 481 482 void 483 BNavMenu::AddRootItemsIfNeeded() 484 { 485 BVolumeRoster roster; 486 roster.Rewind(); 487 BVolume volume; 488 while (roster.GetNextVolume(&volume) == B_OK) { 489 BDirectory root; 490 BEntry entry; 491 if (!volume.IsPersistent() 492 || volume.GetRootDirectory(&root) != B_OK 493 || root.GetEntry(&entry) != B_OK) { 494 continue; 495 } 496 497 Model model(&entry); 498 AddOneItem(&model); 499 } 500 } 501 502 503 void 504 BNavMenu::AddTrashItem() 505 { 506 BPath path; 507 if (find_directory(B_TRASH_DIRECTORY, &path) == B_OK) { 508 BEntry entry(path.Path()); 509 Model model(&entry); 510 AddOneItem(&model); 511 } 512 } 513 514 515 bool 516 BNavMenu::AddNextItem() 517 { 518 if ((fFlags & kVolumesOnly) != 0) { 519 BuildVolumeMenu(); 520 return false; 521 } 522 523 BEntry entry; 524 if (fContainer->GetNextEntry(&entry) != B_OK) { 525 // we're finished 526 return false; 527 } 528 529 if (TrackerSettings().HideDotFiles()) { 530 char name[B_FILE_NAME_LENGTH]; 531 if (entry.GetName(name) == B_OK && name[0] == '.') 532 return true; 533 } 534 535 Model model(&entry, true); 536 if (model.InitCheck() != B_OK) { 537 // PRINT(("not showing hidden item %s, wouldn't open\n", model->Name())); 538 return true; 539 } 540 541 QueryEntryListCollection* queryContainer 542 = dynamic_cast<QueryEntryListCollection*>(fContainer); 543 if (queryContainer != NULL && !queryContainer->ShowResultsFromTrash() 544 && FSInTrashDir(model.EntryRef())) { 545 // query entry is in trash and shall not be shown 546 return true; 547 } 548 549 ssize_t size = -1; 550 PoseInfo poseInfo; 551 if (model.Node() != NULL) { 552 size = model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 553 &poseInfo, sizeof(poseInfo)); 554 } 555 556 model.CloseNode(); 557 558 // item might be in invisible 559 if (size == sizeof(poseInfo) 560 && !BPoseView::PoseVisible(&model, &poseInfo)) { 561 return true; 562 } 563 564 AddOneItem(&model); 565 566 return true; 567 } 568 569 570 void 571 BNavMenu::AddOneItem(Model* model) 572 { 573 BMenuItem* item = NewModelItem(model, &fMessage, fMessenger, false, 574 dynamic_cast<BContainerWindow*>(fParentWindow), 575 fTypesList, &fTrackingHook); 576 577 if (item != NULL) 578 fItemList->AddItem(item); 579 } 580 581 582 ModelMenuItem* 583 BNavMenu::NewModelItem(Model* model, const BMessage* invokeMessage, 584 const BMessenger& target, bool suppressFolderHierarchy, 585 BContainerWindow* parentWindow, const BObjectList<BString>* typeslist, 586 TrackingHookData* hook) 587 { 588 if (model->InitCheck() != B_OK) 589 return 0; 590 591 entry_ref ref; 592 bool isContainer = false; 593 if (model->IsSymLink()) { 594 Model* newResolvedModel = 0; 595 Model* result = model->LinkTo(); 596 597 if (result == NULL) { 598 newResolvedModel = new Model(model->EntryRef(), true, true); 599 600 if (newResolvedModel->InitCheck() != B_OK) { 601 // broken link, still can show though, bail 602 delete newResolvedModel; 603 result = NULL; 604 } else 605 result = newResolvedModel; 606 } 607 608 if (result != NULL) { 609 BModelOpener opener(result); 610 // open the model, if it ain't open already 611 612 PoseInfo poseInfo; 613 ssize_t size = -1; 614 615 if (result->Node() != NULL) { 616 size = result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 617 &poseInfo, sizeof(poseInfo)); 618 } 619 620 result->CloseNode(); 621 622 if (size == sizeof(poseInfo) && !BPoseView::PoseVisible(result, 623 &poseInfo)) { 624 // link target does not want to be visible 625 delete newResolvedModel; 626 return NULL; 627 } 628 629 ref = *result->EntryRef(); 630 isContainer = result->IsContainer(); 631 } 632 633 model->SetLinkTo(result); 634 } else { 635 ref = *model->EntryRef(); 636 isContainer = model->IsContainer(); 637 } 638 639 BMessage* message = new BMessage(*invokeMessage); 640 message->AddRef("refs", model->EntryRef()); 641 642 menu_info info; 643 get_menu_info(&info); 644 BFont menuFont; 645 menuFont.SetFamilyAndStyle(info.f_family, info.f_style); 646 menuFont.SetSize(info.font_size); 647 648 // truncate name if necessary 649 BString truncatedString(model->Name()); 650 menuFont.TruncateString(&truncatedString, B_TRUNCATE_END, GetMaxMenuWidth()); 651 652 ModelMenuItem* item = NULL; 653 if (!isContainer || suppressFolderHierarchy) { 654 item = new ModelMenuItem(model, truncatedString.String(), message); 655 if (invokeMessage->what != B_REFS_RECEIVED) 656 item->SetEnabled(false); 657 // the above is broken for FavoritesMenu::AddNextItem, which uses a 658 // workaround - should fix this 659 } else { 660 BNavMenu* menu = new BNavMenu(truncatedString.String(), 661 invokeMessage->what, target, parentWindow, typeslist); 662 menu->SetNavDir(&ref); 663 if (hook != NULL) { 664 menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget), 665 hook->fDragMessage); 666 } 667 668 item = new ModelMenuItem(model, menu); 669 item->SetMessage(message); 670 } 671 672 return item; 673 } 674 675 676 void 677 BNavMenu::BuildVolumeMenu() 678 { 679 BVolumeRoster roster; 680 BVolume volume; 681 682 roster.Rewind(); 683 while (roster.GetNextVolume(&volume) == B_OK) { 684 if (!volume.IsPersistent()) 685 continue; 686 687 BDirectory startDir; 688 if (volume.GetRootDirectory(&startDir) == B_OK) { 689 BEntry entry; 690 startDir.GetEntry(&entry); 691 692 Model* model = new Model(&entry); 693 if (model->InitCheck() != B_OK) { 694 delete model; 695 continue; 696 } 697 698 BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what, 699 fMessenger, fParentWindow, fTypesList); 700 701 menu->SetNavDir(model->EntryRef()); 702 703 ASSERT(menu->Name() != NULL); 704 705 ModelMenuItem* item = new ModelMenuItem(model, menu); 706 BMessage* message = new BMessage(fMessage); 707 708 message->AddRef("refs", model->EntryRef()); 709 710 item->SetMessage(message); 711 fItemList->AddItem(item); 712 ASSERT(item->Label() != NULL); 713 } 714 } 715 } 716 717 718 int 719 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem* i1, const BMenuItem* i2) 720 { 721 ThrowOnAssert(i1 != NULL && i2 != NULL); 722 723 const ModelMenuItem* item1 = dynamic_cast<const ModelMenuItem*>(i1); 724 const ModelMenuItem* item2 = dynamic_cast<const ModelMenuItem*>(i2); 725 726 if (item1 != NULL && item2 != NULL) { 727 return item1->TargetModel()->CompareFolderNamesFirst( 728 item2->TargetModel()); 729 } 730 731 return strcasecmp(i1->Label(), i2->Label()); 732 } 733 734 735 int 736 BNavMenu::CompareOne(const BMenuItem* i1, const BMenuItem* i2) 737 { 738 ThrowOnAssert(i1 != NULL && i2 != NULL); 739 740 return strcasecmp(i1->Label(), i2->Label()); 741 } 742 743 744 void 745 BNavMenu::DoneBuildingItemList() 746 { 747 // add sorted items to menu 748 if (TrackerSettings().SortFolderNamesFirst()) 749 fItemList->SortItems(CompareFolderNamesFirstOne); 750 else 751 fItemList->SortItems(CompareOne); 752 753 // if the parent link should be shown, it will be the first 754 // entry in the menu - but don't add the item if we're already 755 // at the file system's root 756 if ((fFlags & kShowParent) != 0) { 757 BDirectory directory(&fNavDir); 758 BEntry entry(&fNavDir); 759 if (!directory.IsRootDirectory() 760 && entry.GetParent(&entry) == B_OK) { 761 Model model(&entry, true); 762 BLooper* looper; 763 AddNavParentDir(&model, fMessage.what, 764 fMessenger.Target(&looper)); 765 } 766 } 767 768 int32 count = fItemList->CountItems(); 769 for (int32 index = 0; index < count; index++) 770 AddItem(fItemList->ItemAt(index)); 771 772 fItemList->MakeEmpty(); 773 774 if (count == 0) { 775 BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0); 776 item->SetEnabled(false); 777 AddItem(item); 778 } 779 780 SetTargetForItems(fMessenger); 781 } 782 783 784 int32 785 BNavMenu::GetMaxMenuWidth(void) 786 { 787 return std::max((int32)(BScreen().Frame().Width() / 4), kMinMenuWidth); 788 } 789 790 791 void 792 BNavMenu::AddNavDir(const Model* model, uint32 what, BHandler* target, 793 bool populateSubmenu) 794 { 795 BMessage* message = new BMessage((uint32)what); 796 message->AddRef("refs", model->EntryRef()); 797 ModelMenuItem* item = NULL; 798 799 if (populateSubmenu) { 800 BNavMenu* navMenu = new BNavMenu(model->Name(), what, target); 801 navMenu->SetNavDir(model->EntryRef()); 802 navMenu->InitTrackingHook(fTrackingHook.fTrackingHook, 803 &(fTrackingHook.fTarget), fTrackingHook.fDragMessage); 804 item = new ModelMenuItem(model, navMenu); 805 item->SetMessage(message); 806 } else 807 item = new ModelMenuItem(model, model->Name(), message); 808 809 AddItem(item); 810 } 811 812 813 void 814 BNavMenu::AddNavParentDir(const char* name,const Model* model, 815 uint32 what, BHandler* target) 816 { 817 BNavMenu* menu = new BNavMenu(name, what, target); 818 menu->SetNavDir(model->EntryRef()); 819 menu->SetShowParent(true); 820 menu->InitTrackingHook(fTrackingHook.fTrackingHook, 821 &(fTrackingHook.fTarget), fTrackingHook.fDragMessage); 822 823 BMenuItem* item = new SpecialModelMenuItem(model, menu); 824 BMessage* message = new BMessage(what); 825 message->AddRef("refs", model->EntryRef()); 826 item->SetMessage(message); 827 828 AddItem(item); 829 } 830 831 832 void 833 BNavMenu::AddNavParentDir(const Model* model, uint32 what, BHandler* target) 834 { 835 AddNavParentDir(B_TRANSLATE("parent folder"),model, what, target); 836 } 837 838 839 void 840 BNavMenu::SetShowParent(bool show) 841 { 842 fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0)); 843 } 844 845 846 void 847 BNavMenu::SetTypesList(const BObjectList<BString>* list) 848 { 849 if (list != NULL) 850 *fTypesList = *list; 851 else 852 fTypesList->MakeEmpty(); 853 } 854 855 856 const BObjectList<BString>* 857 BNavMenu::TypesList() const 858 { 859 return fTypesList; 860 } 861 862 863 void 864 BNavMenu::SetTarget(const BMessenger& messenger) 865 { 866 fMessenger = messenger; 867 } 868 869 870 BMessenger 871 BNavMenu::Target() 872 { 873 return fMessenger; 874 } 875 876 877 TrackingHookData* 878 BNavMenu::InitTrackingHook(bool (*hook)(BMenu*, void*), 879 const BMessenger* target, const BMessage* dragMessage) 880 { 881 fTrackingHook.fTrackingHook = hook; 882 if (target != NULL) 883 fTrackingHook.fTarget = *target; 884 885 fTrackingHook.fDragMessage = dragMessage; 886 SetTrackingHookDeep(this, hook, &fTrackingHook); 887 888 return &fTrackingHook; 889 } 890 891 892 void 893 BNavMenu::SetTrackingHookDeep(BMenu* menu, bool (*func)(BMenu*, void*), 894 void* state) 895 { 896 menu->SetTrackingHook(func, state); 897 int32 count = menu->CountItems(); 898 for (int32 index = 0 ; index < count; index++) { 899 BMenuItem* item = menu->ItemAt(index); 900 if (item == NULL) 901 continue; 902 903 BMenu* submenu = item->Submenu(); 904 if (submenu != NULL) 905 SetTrackingHookDeep(submenu, func, state); 906 } 907 } 908 909 910 // #pragma mark - BPopUpNavMenu 911 912 913 BPopUpNavMenu::BPopUpNavMenu(const char* title) 914 : 915 BNavMenu(title, B_REFS_RECEIVED, BMessenger(), NULL, NULL), 916 fTrackThread(-1) 917 { 918 } 919 920 921 BPopUpNavMenu::~BPopUpNavMenu() 922 { 923 _WaitForTrackThread(); 924 } 925 926 927 void 928 BPopUpNavMenu::_WaitForTrackThread() 929 { 930 if (fTrackThread >= 0) { 931 status_t status; 932 while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED) 933 ; 934 } 935 } 936 937 938 void 939 BPopUpNavMenu::ClearMenu() 940 { 941 RemoveItems(0, CountItems(), true); 942 943 fMenuBuilt = false; 944 } 945 946 947 void 948 BPopUpNavMenu::Go(BPoint where) 949 { 950 _WaitForTrackThread(); 951 952 fWhere = where; 953 954 fTrackThread = spawn_thread(_TrackThread, "popup", B_DISPLAY_PRIORITY, this); 955 } 956 957 958 bool 959 BPopUpNavMenu::IsShowing() const 960 { 961 return Window() != NULL && !Window()->IsHidden(); 962 } 963 964 965 BPoint 966 BPopUpNavMenu::ScreenLocation() 967 { 968 return fWhere; 969 } 970 971 972 int32 973 BPopUpNavMenu::_TrackThread(void* _menu) 974 { 975 BPopUpNavMenu* menu = static_cast<BPopUpNavMenu*>(_menu); 976 977 menu->Show(); 978 979 BMenuItem* result = menu->Track(); 980 if (result != NULL) 981 static_cast<BInvoker*>(result)->Invoke(); 982 983 menu->Hide(); 984 985 return 0; 986 } 987