1/* 2Open Tracker License 3 4Terms and Conditions 5 6Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8Permission is hereby granted, free of charge, to any person obtaining a copy of 9this software and associated documentation files (the "Software"), to deal in 10the Software without restriction, including without limitation the rights to 11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12of the Software, and to permit persons to whom the Software is furnished to do 13so, subject to the following conditions: 14 15The above copyright notice and this permission notice applies to all licensees 16and shall be included in all copies or substantial portions of the Software. 17 18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25Except as contained in this notice, the name of Be Incorporated shall not be 26used in advertising or otherwise to promote the sale, use or other dealings in 27this Software without prior written authorization from Be Incorporated. 28 29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30of Be Incorporated in the United States and other countries. Other brand product 31names are registered trademarks or trademarks of their respective holders. 32All rights reserved. 33*/ 34 35#include <string.h> 36#include <stdlib.h> 37 38#include <Debug.h> 39 40#include <Application.h> 41#include <Catalog.h> 42#include <Directory.h> 43#include <Locale.h> 44#include <Path.h> 45#include <Query.h> 46#include <StopWatch.h> 47#include <VolumeRoster.h> 48#include <Volume.h> 49 50#include "Attributes.h" 51#include "Commands.h" 52#include "ContainerWindow.h" 53#include "DesktopPoseView.h" 54#include "FSUtils.h" 55#include "FunctionObject.h" 56#include "IconMenuItem.h" 57#include "NavMenu.h" 58#include "PoseView.h" 59#include "QueryPoseView.h" 60#include "SlowContextPopup.h" 61#include "Thread.h" 62#include "Tracker.h" 63 64 65#undef B_TRANSLATION_CONTEXT 66#define B_TRANSLATION_CONTEXT "SlowContextPopup" 67 68BSlowContextMenu::BSlowContextMenu(const char* title) 69 : BPopUpMenu(title, false, false), 70 fMenuBuilt(false), 71 fMessage(B_REFS_RECEIVED), 72 fParentWindow(NULL), 73 fItemList(NULL), 74 fContainer(NULL), 75 fTypesList(NULL), 76 fIsShowing(false) 77{ 78 InitIconPreloader(); 79 80 SetFont(be_plain_font); 81 SetTriggersEnabled(false); 82} 83 84 85BSlowContextMenu::~BSlowContextMenu() 86{ 87} 88 89 90void 91BSlowContextMenu::AttachedToWindow() 92{ 93 // showing flag is set immediately as 94 // it may take a while to build the menu's 95 // contents. 96 // 97 // it should get set only once when Go is called 98 // and will get reset in DetachedFromWindow 99 // 100 // this flag is used in ContainerWindow::ShowContextMenu 101 // to determine whether we should show this menu, and 102 // the only reason we need to do this is because this 103 // menu is spawned ::Go as an asynchronous menu, which 104 // is done because we will deadlock if the target's 105 // window is open... so there 106 fIsShowing = true; 107 108 BPopUpMenu::AttachedToWindow(); 109 110 SpringLoadedFolderSetMenuStates(this, fTypesList); 111 112 // allow an opportunity to reset the target for each of the items 113 SetTargetForItems(Target()); 114} 115 116 117void 118BSlowContextMenu::DetachedFromWindow() 119{ 120 // see note above in AttachedToWindow 121 fIsShowing = false; 122 // does this need to set this to null? 123 // the parent, handling dnd should set this 124 // appropriately 125 // 126 // if this changes, BeMenu and RecentsMenu 127 // in Deskbar should also change 128 fTypesList = NULL; 129} 130 131 132void 133BSlowContextMenu::SetNavDir(const entry_ref* ref) 134{ 135 ForceRebuild(); 136 // reset the slow menu building mechanism so we can add more stuff 137 138 fNavDir = *ref; 139} 140 141 142void 143BSlowContextMenu::ForceRebuild() 144{ 145 ClearMenuBuildingState(); 146 fMenuBuilt = false; 147} 148 149 150bool 151BSlowContextMenu::NeedsToRebuild() const 152{ 153 return !fMenuBuilt; 154} 155 156 157void 158BSlowContextMenu::ClearMenu() 159{ 160 RemoveItems(0, CountItems(), true); 161 162 fMenuBuilt = false; 163} 164 165 166void 167BSlowContextMenu::ClearMenuBuildingState() 168{ 169 delete fContainer; 170 fContainer = NULL; 171 172 // item list is non-owning, need to delete the items because 173 // they didn't get added to the menu 174 if (fItemList) { 175 RemoveItems(0, fItemList->CountItems(), true); 176 delete fItemList; 177 fItemList = NULL; 178 } 179} 180 181 182const int32 kItemsToAddChunk = 20; 183const bigtime_t kMaxTimeBuildingMenu = 200000; 184 185 186bool 187BSlowContextMenu::AddDynamicItem(add_state state) 188{ 189 if (fMenuBuilt) 190 return false; 191 192 if (state == B_ABORT) { 193 ClearMenuBuildingState(); 194 return false; 195 } 196 197 if (state == B_INITIAL_ADD && !StartBuildingItemList()) { 198 ClearMenuBuildingState(); 199 return false; 200 } 201 202 bigtime_t timeToBail = system_time() + kMaxTimeBuildingMenu; 203 for (int32 count = 0; count < kItemsToAddChunk; count++) { 204 if (!AddNextItem()) { 205 fMenuBuilt = true; 206 DoneBuildingItemList(); 207 ClearMenuBuildingState(); 208 return false; 209 // done with menu, don't call again 210 } 211 if (system_time() > timeToBail) 212 // we have been in here long enough, come back later 213 break; 214 } 215 216 return true; // call me again, got more to show 217} 218 219 220bool 221BSlowContextMenu::StartBuildingItemList() 222{ 223 // return false when done building 224 BEntry entry; 225 226 if (fNavDir.device < 0 || entry.SetTo(&fNavDir) != B_OK 227 || !entry.Exists()) { 228 return false; 229 } 230 231 fIteratingDesktop = false; 232 233 BDirectory parent; 234 status_t err = entry.GetParent(&parent); 235 fItemList = new BObjectList<BMenuItem>(50); 236 237 // if ref is the root item then build list of volume root dirs 238 fVolsOnly = (err == B_ENTRY_NOT_FOUND); 239 240 if (fVolsOnly) 241 return true; 242 243 Model startModel(&entry, true); 244 if (startModel.InitCheck() == B_OK) { 245 if (!startModel.IsContainer()) 246 return false; 247 248 if (startModel.IsQuery()) 249 fContainer = new QueryEntryListCollection(&startModel); 250 else if (startModel.IsDesktop()) { 251 fIteratingDesktop = true; 252 fContainer = DesktopPoseView::InitDesktopDirentIterator(0, 253 startModel.EntryRef()); 254 AddRootItemsIfNeeded(); 255 AddTrashItem(); 256 } else { 257 fContainer = new DirectoryEntryList(*dynamic_cast<BDirectory*> 258 (startModel.Node())); 259 } 260 261 if (fContainer->InitCheck() != B_OK) 262 return false; 263 264 fContainer->Rewind(); 265 } 266 267 return true; 268} 269 270 271void 272BSlowContextMenu::AddRootItemsIfNeeded() 273{ 274 BVolumeRoster roster; 275 roster.Rewind(); 276 BVolume volume; 277 while (roster.GetNextVolume(&volume) == B_OK) { 278 279 BDirectory root; 280 BEntry entry; 281 if (!volume.IsPersistent() 282 || volume.GetRootDirectory(&root) != B_OK 283 || root.GetEntry(&entry) != B_OK) 284 continue; 285 286 Model model(&entry); 287 AddOneItem(&model); 288 } 289} 290 291 292void 293BSlowContextMenu::AddTrashItem() 294{ 295 BPath path; 296 if (find_directory(B_TRASH_DIRECTORY, &path) == B_OK) { 297 BEntry entry(path.Path()); 298 Model model(&entry); 299 AddOneItem(&model); 300 } 301} 302 303 304bool 305BSlowContextMenu::AddNextItem() 306{ 307 if (fVolsOnly) { 308 BuildVolumeMenu(); 309 return false; 310 } 311 312 // limit nav menus to 500 items only 313 if (fItemList->CountItems() > 500) 314 return false; 315 316 BEntry entry; 317 if (fContainer->GetNextEntry(&entry) != B_OK) 318 // we're finished 319 return false; 320 321 Model model(&entry, true); 322 if (model.InitCheck() != B_OK) { 323// PRINT(("not showing hidden item %s, wouldn't open\n", model->Name())); 324 return true; 325 } 326 327 PoseInfo poseInfo; 328 329 if (model.Node()) { 330 model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 331 &poseInfo, sizeof(poseInfo)); 332 } 333 334 model.CloseNode(); 335 336 if (!BPoseView::PoseVisible(&model, &poseInfo)) { 337 return true; 338 } 339 340 AddOneItem(&model); 341 return true; 342} 343 344 345void 346BSlowContextMenu::AddOneItem(Model* model) 347{ 348 BMenuItem* item = NewModelItem(model, &fMessage, fMessenger, false, 349 dynamic_cast<BContainerWindow*>(fParentWindow) ? 350 dynamic_cast<BContainerWindow*>(fParentWindow) : 0, 351 fTypesList, &fTrackingHook); 352 353 if (item) 354 fItemList->AddItem(item); 355} 356 357 358ModelMenuItem* 359BSlowContextMenu::NewModelItem(Model* model, const BMessage* invokeMessage, 360 const BMessenger &target, bool suppressFolderHierarchy, 361 BContainerWindow* parentWindow, const BObjectList<BString>* typeslist, 362 TrackingHookData* hook) 363{ 364 if (model->InitCheck() != B_OK) 365 return NULL; 366 367 entry_ref ref; 368 bool container = false; 369 if (model->IsSymLink()) { 370 371 Model* newResolvedModel = NULL; 372 Model* result = model->LinkTo(); 373 374 if (!result) { 375 newResolvedModel = new Model(model->EntryRef(), true, true); 376 377 if (newResolvedModel->InitCheck() != B_OK) { 378 // broken link, still can show though, bail 379 delete newResolvedModel; 380 newResolvedModel = NULL; 381 } 382 383 result = newResolvedModel; 384 } 385 386 if (result) { 387 BModelOpener opener(result); 388 // open the model, if it ain't open already 389 390 PoseInfo poseInfo; 391 392 if (result->Node()) { 393 result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0, 394 &poseInfo, sizeof(poseInfo)); 395 } 396 397 result->CloseNode(); 398 399 ref = *result->EntryRef(); 400 container = result->IsContainer(); 401 } 402 model->SetLinkTo(result); 403 } else { 404 ref = *model->EntryRef(); 405 container = model->IsContainer(); 406 } 407 408 BMessage* message = new BMessage(*invokeMessage); 409 message->AddRef("refs", model->EntryRef()); 410 411 // Truncate the name if necessary 412 BString truncatedString(model->Name()); 413 be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END, 414 BNavMenu::GetMaxMenuWidth()); 415 416 ModelMenuItem* item = NULL; 417 if (!container || suppressFolderHierarchy) { 418 item = new ModelMenuItem(model, truncatedString.String(), message); 419 if (invokeMessage->what != B_REFS_RECEIVED) 420 item->SetEnabled(false); 421 } else { 422 BNavMenu* menu = new BNavMenu(truncatedString.String(), 423 invokeMessage->what, target, parentWindow, typeslist); 424 425 menu->SetNavDir(&ref); 426 if (hook) 427 menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget), 428 hook->fDragMessage); 429 430 item = new ModelMenuItem(model, menu); 431 item->SetMessage(message); 432 } 433 434 return item; 435} 436 437 438void 439BSlowContextMenu::BuildVolumeMenu() 440{ 441 BVolumeRoster roster; 442 BVolume volume; 443 444 roster.Rewind(); 445 while (roster.GetNextVolume(&volume) == B_OK) { 446 447 if (!volume.IsPersistent()) 448 continue; 449 450 BDirectory startDir; 451 if (volume.GetRootDirectory(&startDir) == B_OK) { 452 BEntry entry; 453 startDir.GetEntry(&entry); 454 455 Model* model = new Model(&entry); 456 if (model->InitCheck() != B_OK) { 457 delete model; 458 continue; 459 } 460 461 BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what, 462 fMessenger, fParentWindow, fTypesList); 463 464 menu->SetNavDir(model->EntryRef()); 465 menu->InitTrackingHook(fTrackingHook.fTrackingHook, 466 &(fTrackingHook.fTarget), fTrackingHook.fDragMessage); 467 468 ASSERT(menu->Name()); 469 470 ModelMenuItem* item = new ModelMenuItem(model, menu); 471 BMessage* message = new BMessage(fMessage); 472 473 message->AddRef("refs", model->EntryRef()); 474 item->SetMessage(message); 475 fItemList->AddItem(item); 476 ASSERT(item->Label()); 477 } 478 } 479} 480 481 482void 483BSlowContextMenu::DoneBuildingItemList() 484{ 485 // add sorted items to menu 486 if (TrackerSettings().SortFolderNamesFirst()) 487 fItemList->SortItems(&BNavMenu::CompareFolderNamesFirstOne); 488 else 489 fItemList->SortItems(&BNavMenu::CompareOne); 490 491 int32 count = fItemList->CountItems(); 492 for (int32 index = 0; index < count; index++) 493 AddItem(fItemList->ItemAt(index)); 494 495 fItemList->MakeEmpty(); 496 497 if (!count) { 498 BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0); 499 item->SetEnabled(false); 500 AddItem(item); 501 } 502 503 SetTargetForItems(fMessenger); 504} 505 506 507void 508BSlowContextMenu::SetTypesList(const BObjectList<BString>* list) 509{ 510 fTypesList = list; 511} 512 513 514void 515BSlowContextMenu::SetTarget(const BMessenger &target) 516{ 517 fMessenger = target; 518} 519 520 521TrackingHookData* 522BSlowContextMenu::InitTrackingHook(bool (*hook)(BMenu*, void*), 523 const BMessenger* target, const BMessage* dragMessage) 524{ 525 fTrackingHook.fTrackingHook = hook; 526 if (target) 527 fTrackingHook.fTarget = *target; 528 fTrackingHook.fDragMessage = dragMessage; 529 SetTrackingHookDeep(this, hook, &fTrackingHook); 530 return &fTrackingHook; 531} 532 533 534void 535BSlowContextMenu::SetTrackingHookDeep(BMenu* menu, 536 bool (*func)(BMenu*, void*), void* state) 537{ 538 menu->SetTrackingHook(func, state); 539 int32 count = menu->CountItems(); 540 for (int32 index = 0; index < count; index++) { 541 BMenuItem* item = menu->ItemAt(index); 542 if (!item) 543 continue; 544 545 BMenu* submenu = item->Submenu(); 546 if (submenu) 547 SetTrackingHookDeep(submenu, func, state); 548 } 549} 550