vfs: check userland buffers before reading them.
[haiku.git] / src / kits / tracker / SlowContextPopup.cpp
blob8876a973306b46e1e30eaceeb9dd346be4a72023
1 /*
2 Open Tracker License
4 Terms and Conditions
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
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:
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.
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.
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.
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.
35 #include <string.h>
36 #include <stdlib.h>
38 #include <Debug.h>
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>
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 "PoseView.h"
58 #include "QueryPoseView.h"
59 #include "SlowContextPopup.h"
60 #include "Thread.h"
61 #include "Tracker.h"
62 #include "VirtualDirectoryEntryList.h"
65 #undef B_TRANSLATION_CONTEXT
66 #define B_TRANSLATION_CONTEXT "SlowContextPopup"
69 // #pragma mark - BSlowContextMenu
72 BSlowContextMenu::BSlowContextMenu(const char* title)
74 BPopUpMenu(title, false, false),
75 fMenuBuilt(false),
76 fMessage(B_REFS_RECEIVED),
77 fParentWindow(NULL),
78 fVolsOnly(false),
79 fItemList(NULL),
80 fContainer(NULL),
81 fIteratingDesktop(false),
82 fTypesList(NULL),
83 fIsShowing(false)
85 InitIconPreloader();
87 SetFont(be_plain_font);
88 SetTriggersEnabled(false);
92 BSlowContextMenu::~BSlowContextMenu()
97 void
98 BSlowContextMenu::AttachedToWindow()
100 // showing flag is set immediately as
101 // it may take a while to build the menu's
102 // contents.
104 // it should get set only once when Go is called
105 // and will get reset in DetachedFromWindow
107 // this flag is used in ContainerWindow::ShowContextMenu
108 // to determine whether we should show this menu, and
109 // the only reason we need to do this is because this
110 // menu is spawned ::Go as an asynchronous menu, which
111 // is done because we will deadlock if the target's
112 // window is open... so there
113 fIsShowing = true;
115 BPopUpMenu::AttachedToWindow();
117 SpringLoadedFolderSetMenuStates(this, fTypesList);
119 // allow an opportunity to reset the target for each of the items
120 SetTargetForItems(Target());
124 void
125 BSlowContextMenu::DetachedFromWindow()
127 // see note above in AttachedToWindow
128 fIsShowing = false;
129 // does this need to set this to null?
130 // the parent, handling dnd should set this
131 // appropriately
133 // if this changes, BeMenu and RecentsMenu
134 // in Deskbar should also change
135 fTypesList = NULL;
139 void
140 BSlowContextMenu::SetNavDir(const entry_ref* ref)
142 ForceRebuild();
143 // reset the slow menu building mechanism so we can add more stuff
145 fNavDir = *ref;
149 void
150 BSlowContextMenu::ForceRebuild()
152 ClearMenuBuildingState();
153 fMenuBuilt = false;
157 bool
158 BSlowContextMenu::NeedsToRebuild() const
160 return !fMenuBuilt;
164 void
165 BSlowContextMenu::ClearMenu()
167 RemoveItems(0, CountItems(), true);
169 fMenuBuilt = false;
173 void
174 BSlowContextMenu::ClearMenuBuildingState()
176 delete fContainer;
177 fContainer = NULL;
179 // item list is non-owning, need to delete the items because
180 // they didn't get added to the menu
181 if (fItemList) {
182 RemoveItems(0, fItemList->CountItems(), true);
183 delete fItemList;
184 fItemList = NULL;
189 const int32 kItemsToAddChunk = 20;
190 const bigtime_t kMaxTimeBuildingMenu = 200000;
193 bool
194 BSlowContextMenu::AddDynamicItem(add_state state)
196 if (fMenuBuilt)
197 return false;
199 if (state == B_ABORT) {
200 ClearMenuBuildingState();
201 return false;
204 if (state == B_INITIAL_ADD && !StartBuildingItemList()) {
205 ClearMenuBuildingState();
206 return false;
209 bigtime_t timeToBail = system_time() + kMaxTimeBuildingMenu;
210 for (int32 count = 0; count < kItemsToAddChunk; count++) {
211 if (!AddNextItem()) {
212 fMenuBuilt = true;
213 DoneBuildingItemList();
214 ClearMenuBuildingState();
215 return false;
216 // done with menu, don't call again
218 if (system_time() > timeToBail)
219 // we have been in here long enough, come back later
220 break;
223 return true;
224 // call me again, got more to show
228 bool
229 BSlowContextMenu::StartBuildingItemList()
231 // return false when done building
232 BEntry entry;
234 if (fNavDir.device < 0 || entry.SetTo(&fNavDir) != B_OK
235 || !entry.Exists()) {
236 return false;
239 fIteratingDesktop = false;
241 BDirectory parent;
242 status_t err = entry.GetParent(&parent);
243 fItemList = new BObjectList<BMenuItem>(50);
245 // if ref is the root item then build list of volume root dirs
246 fVolsOnly = (err == B_ENTRY_NOT_FOUND);
248 if (fVolsOnly)
249 return true;
251 Model startModel(&entry, true);
252 if (startModel.InitCheck() == B_OK) {
253 if (!startModel.IsContainer())
254 return false;
256 if (startModel.IsQuery())
257 fContainer = new QueryEntryListCollection(&startModel);
258 else if (startModel.IsVirtualDirectory())
259 fContainer = new VirtualDirectoryEntryList(&startModel);
260 else if (startModel.IsDesktop()) {
261 fIteratingDesktop = true;
262 fContainer = DesktopPoseView::InitDesktopDirentIterator(0,
263 startModel.EntryRef());
264 AddRootItemsIfNeeded();
265 AddTrashItem();
266 } else {
267 BDirectory* directory = dynamic_cast<BDirectory*>(
268 startModel.Node());
270 ASSERT(directory != NULL);
272 if (directory != NULL)
273 fContainer = new DirectoryEntryList(*directory);
276 if (fContainer->InitCheck() != B_OK)
277 return false;
279 fContainer->Rewind();
282 return true;
286 void
287 BSlowContextMenu::AddRootItemsIfNeeded()
289 BVolumeRoster roster;
290 roster.Rewind();
291 BVolume volume;
292 while (roster.GetNextVolume(&volume) == B_OK) {
294 BDirectory root;
295 BEntry entry;
296 if (!volume.IsPersistent()
297 || volume.GetRootDirectory(&root) != B_OK
298 || root.GetEntry(&entry) != B_OK)
299 continue;
301 Model model(&entry);
302 AddOneItem(&model);
307 void
308 BSlowContextMenu::AddTrashItem()
310 BPath path;
311 if (find_directory(B_TRASH_DIRECTORY, &path) == B_OK) {
312 BEntry entry(path.Path());
313 Model model(&entry);
314 AddOneItem(&model);
319 bool
320 BSlowContextMenu::AddNextItem()
322 if (fVolsOnly) {
323 BuildVolumeMenu();
324 return false;
327 // limit nav menus to 500 items only
328 if (fItemList->CountItems() > 500)
329 return false;
331 BEntry entry;
332 if (fContainer->GetNextEntry(&entry) != B_OK)
333 // we're finished
334 return false;
336 Model model(&entry, true);
337 if (model.InitCheck() != B_OK) {
338 // PRINT(("not showing hidden item %s, wouldn't open\n", model->Name()));
339 return true;
342 PoseInfo poseInfo;
344 if (model.Node()) {
345 model.Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
346 &poseInfo, sizeof(poseInfo));
349 model.CloseNode();
351 if (!BPoseView::PoseVisible(&model, &poseInfo)) {
352 return true;
355 AddOneItem(&model);
356 return true;
360 void
361 BSlowContextMenu::AddOneItem(Model* model)
363 BMenuItem* item = NewModelItem(model, &fMessage, fMessenger, false,
364 dynamic_cast<BContainerWindow*>(fParentWindow), fTypesList,
365 &fTrackingHook);
366 if (item != NULL)
367 fItemList->AddItem(item);
371 ModelMenuItem*
372 BSlowContextMenu::NewModelItem(Model* model, const BMessage* invokeMessage,
373 const BMessenger &target, bool suppressFolderHierarchy,
374 BContainerWindow* parentWindow, const BObjectList<BString>* typeslist,
375 TrackingHookData* hook)
377 if (model->InitCheck() != B_OK)
378 return NULL;
380 entry_ref ref;
381 bool container = false;
382 if (model->IsSymLink()) {
384 Model* newResolvedModel = NULL;
385 Model* result = model->LinkTo();
387 if (!result) {
388 newResolvedModel = new Model(model->EntryRef(), true, true);
390 if (newResolvedModel->InitCheck() != B_OK) {
391 // broken link, still can show though, bail
392 delete newResolvedModel;
393 newResolvedModel = NULL;
396 result = newResolvedModel;
399 if (result) {
400 BModelOpener opener(result);
401 // open the model, if it ain't open already
403 PoseInfo poseInfo;
405 if (result->Node()) {
406 result->Node()->ReadAttr(kAttrPoseInfo, B_RAW_TYPE, 0,
407 &poseInfo, sizeof(poseInfo));
410 result->CloseNode();
412 ref = *result->EntryRef();
413 container = result->IsContainer();
415 model->SetLinkTo(result);
416 } else {
417 ref = *model->EntryRef();
418 container = model->IsContainer();
421 BMessage* message = new BMessage(*invokeMessage);
422 message->AddRef("refs", model->EntryRef());
424 // Truncate the name if necessary
425 BString truncatedString(model->Name());
426 be_plain_font->TruncateString(&truncatedString, B_TRUNCATE_END,
427 BNavMenu::GetMaxMenuWidth());
429 ModelMenuItem* item = NULL;
430 if (!container || suppressFolderHierarchy) {
431 item = new ModelMenuItem(model, truncatedString.String(), message);
432 if (invokeMessage->what != B_REFS_RECEIVED)
433 item->SetEnabled(false);
434 } else {
435 BNavMenu* menu = new BNavMenu(truncatedString.String(),
436 invokeMessage->what, target, parentWindow, typeslist);
438 menu->SetNavDir(&ref);
439 if (hook)
440 menu->InitTrackingHook(hook->fTrackingHook, &(hook->fTarget),
441 hook->fDragMessage);
443 item = new ModelMenuItem(model, menu);
444 item->SetMessage(message);
447 return item;
451 void
452 BSlowContextMenu::BuildVolumeMenu()
454 BVolumeRoster roster;
455 BVolume volume;
457 roster.Rewind();
458 while (roster.GetNextVolume(&volume) == B_OK) {
460 if (!volume.IsPersistent())
461 continue;
463 BDirectory startDir;
464 if (volume.GetRootDirectory(&startDir) == B_OK) {
465 BEntry entry;
466 startDir.GetEntry(&entry);
468 Model* model = new Model(&entry);
469 if (model->InitCheck() != B_OK) {
470 delete model;
471 continue;
474 BNavMenu* menu = new BNavMenu(model->Name(), fMessage.what,
475 fMessenger, fParentWindow, fTypesList);
477 menu->SetNavDir(model->EntryRef());
478 menu->InitTrackingHook(fTrackingHook.fTrackingHook,
479 &(fTrackingHook.fTarget), fTrackingHook.fDragMessage);
481 ASSERT(menu->Name());
483 ModelMenuItem* item = new ModelMenuItem(model, menu);
484 BMessage* message = new BMessage(fMessage);
486 message->AddRef("refs", model->EntryRef());
487 item->SetMessage(message);
488 fItemList->AddItem(item);
489 ASSERT(item->Label());
495 void
496 BSlowContextMenu::DoneBuildingItemList()
498 // add sorted items to menu
499 if (TrackerSettings().SortFolderNamesFirst())
500 fItemList->SortItems(&BNavMenu::CompareFolderNamesFirstOne);
501 else
502 fItemList->SortItems(&BNavMenu::CompareOne);
504 int32 count = fItemList->CountItems();
505 for (int32 index = 0; index < count; index++)
506 AddItem(fItemList->ItemAt(index));
508 fItemList->MakeEmpty();
510 if (!count) {
511 BMenuItem* item = new BMenuItem(B_TRANSLATE("Empty folder"), 0);
512 item->SetEnabled(false);
513 AddItem(item);
516 SetTargetForItems(fMessenger);
520 void
521 BSlowContextMenu::SetTypesList(const BObjectList<BString>* list)
523 fTypesList = list;
527 void
528 BSlowContextMenu::SetTarget(const BMessenger &target)
530 fMessenger = target;
534 TrackingHookData*
535 BSlowContextMenu::InitTrackingHook(bool (*hook)(BMenu*, void*),
536 const BMessenger* target, const BMessage* dragMessage)
538 fTrackingHook.fTrackingHook = hook;
539 if (target)
540 fTrackingHook.fTarget = *target;
541 fTrackingHook.fDragMessage = dragMessage;
542 SetTrackingHookDeep(this, hook, &fTrackingHook);
543 return &fTrackingHook;
547 void
548 BSlowContextMenu::SetTrackingHookDeep(BMenu* menu,
549 bool (*func)(BMenu*, void*), void* state)
551 menu->SetTrackingHook(func, state);
552 int32 count = menu->CountItems();
553 for (int32 index = 0; index < count; index++) {
554 BMenuItem* item = menu->ItemAt(index);
555 if (!item)
556 continue;
558 BMenu* submenu = item->Submenu();
559 if (submenu)
560 SetTrackingHookDeep(submenu, func, state);