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 // truncate name if necessary 643 BString truncatedString(model->Name()); 644 be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END, 645 GetMaxMenuWidth()); 646 647 ModelMenuItem* item = NULL; 648 if (!isContainer || suppressFolderHierarchy) { 649 item = new ModelMenuItem(model, truncatedString.String(), message); 650 if (invokeMessage->what != B_REFS_RECEIVED) 651 item->SetEnabled(false); 652 // the above is broken for FavoritesMenu::AddNextItem, which uses a 653 // workaround - should fix this 654 } else { 655 BNavMenu* menu = new BNavMenu(truncatedString.String(), 656 invokeMessage->what, target, parentWindow, typeslist); 657 menu->SetNavDir(&ref); 658 if (hook != NULL) { 659 menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget), 660 hook->fDragMessage); 661 } 662 663 item = new ModelMenuItem(model, menu); 664 item->SetMessage(message); 665 } 666 667 return item; 668 } 669 670 671 void 672 BNavMenu::BuildVolumeMenu() 673 { 674 BVolumeRoster roster; 675 BVolume volume; 676 677 roster.Rewind(); 678 while (roster.GetNextVolume(&volume) == B_OK) { 679 if (!volume.IsPersistent()) 680 continue; 681 682 BDirectory startDir; 683 if (volume.GetRootDirectory(&startDir) == B_OK) { 684 BEntry entry; 685 startDir.GetEntry(&entry); 686 687 Model* model = new Model(&entry); 688 if (model->InitCheck() != B_OK) { 689 delete model; 690 continue; 691 } 692 693 BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what, 694 fMessenger, fParentWindow, fTypesList); 695 696 menu->SetNavDir(model->EntryRef()); 697 698 ASSERT(menu->Name() != NULL); 699 700 ModelMenuItem* item = new ModelMenuItem(model, menu); 701 BMessage* message = new BMessage(fMessage); 702 703 message->AddRef("refs", model->EntryRef()); 704 705 item->SetMessage(message); 706 fItemList->AddItem(item); 707 ASSERT(item->Label() != NULL); 708 } 709 } 710 } 711 712 713 int 714 BNavMenu::CompareFolderNamesFirstOne(const BMenuItem* i1, const BMenuItem* i2) 715 { 716 ThrowOnAssert(i1 != NULL && i2 != NULL); 717 718 const ModelMenuItem* item1 = dynamic_cast<const ModelMenuItem*>(i1); 719 const ModelMenuItem* item2 = dynamic_cast<const ModelMenuItem*>(i2); 720 721 if (item1 != NULL && item2 != NULL) { 722 return item1->TargetModel()->CompareFolderNamesFirst( 723 item2->TargetModel()); 724 } 725 726 return strcasecmp(i1->Label(), i2->Label()); 727 } 728 729 730 int 731 BNavMenu::CompareOne(const BMenuItem* i1, const BMenuItem* i2) 732 { 733 ThrowOnAssert(i1 != NULL && i2 != NULL); 734 735 return strcasecmp(i1->Label(), i2->Label()); 736 } 737 738 739 void 740 BNavMenu::DoneBuildingItemList() 741 { 742 // add sorted items to menu 743 if (TrackerSettings().SortFolderNamesFirst()) 744 fItemList->SortItems(CompareFolderNamesFirstOne); 745 else 746 fItemList->SortItems(CompareOne); 747 748 // if the parent link should be shown, it will be the first 749 // entry in the menu - but don't add the item if we're already 750 // at the file system's root 751 if ((fFlags & kShowParent) != 0) { 752 BDirectory directory(&fNavDir); 753 BEntry entry(&fNavDir); 754 if (!directory.IsRootDirectory() 755 && entry.GetParent(&entry) == B_OK) { 756 Model model(&entry, true); 757 BLooper* looper; 758 AddNavParentDir(&model, fMessage.what, 759 fMessenger.Target(&looper)); 760 } 761 } 762 763 int32 count = fItemList->CountItems(); 764 for (int32 index = 0; index < count; index++) 765 AddItem(fItemList->ItemAt(index)); 766 767 fItemList->MakeEmpty(); 768 769 if (count == 0) { 770 BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0); 771 item->SetEnabled(false); 772 AddItem(item); 773 } 774 775 SetTargetForItems(fMessenger); 776 } 777 778 779 int32 780 BNavMenu::GetMaxMenuWidth(void) 781 { 782 return std::max((int32)(BScreen().Frame().Width() / 4), kMinMenuWidth); 783 } 784 785 786 void 787 BNavMenu::AddNavDir(const Model* model, uint32 what, BHandler* target, 788 bool populateSubmenu) 789 { 790 BMessage* message = new BMessage((uint32)what); 791 message->AddRef("refs", model->EntryRef()); 792 ModelMenuItem* item = NULL; 793 794 if (populateSubmenu) { 795 BNavMenu* navMenu = new BNavMenu(model->Name(), what, target); 796 navMenu->SetNavDir(model->EntryRef()); 797 navMenu->InitTrackingHook(fTrackingHook.fTrackingHook, 798 &(fTrackingHook.fTarget), fTrackingHook.fDragMessage); 799 item = new ModelMenuItem(model, navMenu); 800 item->SetMessage(message); 801 } else 802 item = new ModelMenuItem(model, model->Name(), message); 803 804 AddItem(item); 805 } 806 807 808 void 809 BNavMenu::AddNavParentDir(const char* name,const Model* model, 810 uint32 what, BHandler* target) 811 { 812 BNavMenu* menu = new BNavMenu(name, what, target); 813 menu->SetNavDir(model->EntryRef()); 814 menu->SetShowParent(true); 815 menu->InitTrackingHook(fTrackingHook.fTrackingHook, 816 &(fTrackingHook.fTarget), fTrackingHook.fDragMessage); 817 818 BMenuItem* item = new SpecialModelMenuItem(model, menu); 819 BMessage* message = new BMessage(what); 820 message->AddRef("refs", model->EntryRef()); 821 item->SetMessage(message); 822 823 AddItem(item); 824 } 825 826 827 void 828 BNavMenu::AddNavParentDir(const Model* model, uint32 what, BHandler* target) 829 { 830 AddNavParentDir(B_TRANSLATE("parent folder"),model, what, target); 831 } 832 833 834 void 835 BNavMenu::SetShowParent(bool show) 836 { 837 fFlags = uint8((fFlags & ~kShowParent) | (show ? kShowParent : 0)); 838 } 839 840 841 void 842 BNavMenu::SetTypesList(const BObjectList<BString>* list) 843 { 844 if (list != NULL) 845 *fTypesList = *list; 846 else 847 fTypesList->MakeEmpty(); 848 } 849 850 851 const BObjectList<BString>* 852 BNavMenu::TypesList() const 853 { 854 return fTypesList; 855 } 856 857 858 void 859 BNavMenu::SetTarget(const BMessenger& messenger) 860 { 861 fMessenger = messenger; 862 } 863 864 865 BMessenger 866 BNavMenu::Target() 867 { 868 return fMessenger; 869 } 870 871 872 TrackingHookData* 873 BNavMenu::InitTrackingHook(bool (*hook)(BMenu*, void*), 874 const BMessenger* target, const BMessage* dragMessage) 875 { 876 fTrackingHook.fTrackingHook = hook; 877 if (target != NULL) 878 fTrackingHook.fTarget = *target; 879 880 fTrackingHook.fDragMessage = dragMessage; 881 SetTrackingHookDeep(this, hook, &fTrackingHook); 882 883 return &fTrackingHook; 884 } 885 886 887 void 888 BNavMenu::SetTrackingHookDeep(BMenu* menu, bool (*func)(BMenu*, void*), 889 void* state) 890 { 891 menu->SetTrackingHook(func, state); 892 int32 count = menu->CountItems(); 893 for (int32 index = 0 ; index < count; index++) { 894 BMenuItem* item = menu->ItemAt(index); 895 if (item == NULL) 896 continue; 897 898 BMenu* submenu = item->Submenu(); 899 if (submenu != NULL) 900 SetTrackingHookDeep(submenu, func, state); 901 } 902 } 903 904 905 // #pragma mark - BPopUpNavMenu 906 907 908 BPopUpNavMenu::BPopUpNavMenu(const char* title) 909 : 910 BNavMenu(title, B_REFS_RECEIVED, BMessenger(), NULL, NULL), 911 fTrackThread(-1) 912 { 913 } 914 915 916 BPopUpNavMenu::~BPopUpNavMenu() 917 { 918 _WaitForTrackThread(); 919 } 920 921 922 void 923 BPopUpNavMenu::_WaitForTrackThread() 924 { 925 if (fTrackThread >= 0) { 926 status_t status; 927 while (wait_for_thread(fTrackThread, &status) == B_INTERRUPTED) 928 ; 929 } 930 } 931 932 933 void 934 BPopUpNavMenu::ClearMenu() 935 { 936 RemoveItems(0, CountItems(), true); 937 938 fMenuBuilt = false; 939 } 940 941 942 void 943 BPopUpNavMenu::Go(BPoint where) 944 { 945 _WaitForTrackThread(); 946 947 fWhere = where; 948 949 fTrackThread = spawn_thread(_TrackThread, "popup", B_DISPLAY_PRIORITY, this); 950 } 951 952 953 bool 954 BPopUpNavMenu::IsShowing() const 955 { 956 return Window() != NULL && !Window()->IsHidden(); 957 } 958 959 960 BPoint 961 BPopUpNavMenu::ScreenLocation() 962 { 963 return fWhere; 964 } 965 966 967 int32 968 BPopUpNavMenu::_TrackThread(void* _menu) 969 { 970 BPopUpNavMenu* menu = static_cast<BPopUpNavMenu*>(_menu); 971 972 menu->Show(); 973 974 BMenuItem* result = menu->Track(); 975 if (result != NULL) 976 static_cast<BInvoker*>(result)->Invoke(); 977 978 menu->Hide(); 979 980 return 0; 981 } 982