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