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
30trademarks of Be Incorporated in the United States and other countries. Other
31brand product names are registered trademarks or trademarks of their respective
32holders.
33All rights reserved.
34*/
35
36
37#include "DeskbarMenu.h"
38
39#include <Debug.h>
40#include <Bitmap.h>
41#include <Catalog.h>
42#include <Dragger.h>
43#include <Locale.h>
44#include <Menu.h>
45#include <MenuItem.h>
46#include <Roster.h>
47
48#include "BarApp.h"
49#include "BarView.h"
50#include "DeskbarUtils.h"
51#include "IconMenuItem.h"
52#include "MountMenu.h"
53#include "RecentItems.h"
54#include "StatusView.h"
55
56#include "tracker_private.h"
57
58#undef B_TRANSLATION_CONTEXT
59#define B_TRANSLATION_CONTEXT "DeskbarMenu"
60
61#define ROSTER_SIG "application/x-vnd.Be-ROST"
62
63#ifdef MOUNT_MENU_IN_DESKBAR
64class DeskbarMountMenu : public BPrivate::MountMenu {
65public:
66	DeskbarMountMenu(const char* name);
67	virtual bool AddDynamicItem(add_state s);
68};
69#endif	// MOUNT_MENU_IN_DESKBAR
70
71//#define SHOW_RECENT_FIND_ITEMS
72
73namespace BPrivate {
74	BMenu* TrackerBuildRecentFindItemsMenu(const char*);
75}
76
77using namespace BPrivate;
78
79
80//	#pragma mark - TDeskbarMenu
81
82
83TDeskbarMenu::TDeskbarMenu(TBarView* barView)
84	:
85	BNavMenu("DeskbarMenu", B_REFS_RECEIVED, DefaultTarget()),
86	fAddState(kStart),
87	fBarView(barView)
88{
89}
90
91
92void
93TDeskbarMenu::AttachedToWindow()
94{
95	if (fBarView != NULL && fBarView->LockLooper()) {
96		if (fBarView->Dragging()) {
97			SetTypesList(fBarView->CachedTypesList());
98			SetTarget(BMessenger(fBarView));
99			SetTrackingHookDeep(this, fBarView->MenuTrackingHook,
100				fBarView->GetTrackingHookData());
101			fBarView->DragStart();
102		} else {
103			SetTypesList(NULL);
104			SetTarget(DefaultTarget());
105			SetTrackingHookDeep(this, NULL, NULL);
106		}
107
108		fBarView->UnlockLooper();
109	}
110
111	BNavMenu::AttachedToWindow();
112}
113
114
115void
116TDeskbarMenu::DetachedFromWindow()
117{
118	if (fBarView != NULL) {
119		BLooper* looper = fBarView->Looper();
120		if (looper != NULL && looper->Lock()) {
121			fBarView->DragStop();
122			looper->Unlock();
123		}
124	}
125
126	// don't call BNavMenu::DetachedFromWindow
127	// it sets the TypesList to NULL
128	BMenu::DetachedFromWindow();
129}
130
131
132bool
133TDeskbarMenu::StartBuildingItemList()
134{
135	RemoveItems(0, CountItems(), true);
136	fAddState = kStart;
137	return BNavMenu::StartBuildingItemList();
138}
139
140
141void
142TDeskbarMenu::DoneBuildingItemList()
143{
144	if (fItemList->CountItems() <= 0) {
145		BMenuItem* item
146			= new BMenuItem(B_TRANSLATE("<Deskbar folder is empty>"), 0);
147		item->SetEnabled(false);
148		AddItem(item);
149	} else
150		BNavMenu::DoneBuildingItemList();
151}
152
153
154bool
155TDeskbarMenu::AddNextItem()
156{
157	if (fAddState == kStart)
158		return AddStandardDeskbarMenuItems();
159
160	TrackingHookData* data = fBarView->GetTrackingHookData();
161	if (fAddState == kAddingRecents) {
162		static const char* recentTitle[] = {
163			B_TRANSLATE_MARK("Recent documents"),
164			B_TRANSLATE_MARK("Recent folders"),
165			B_TRANSLATE_MARK("Recent applications")};
166		const int recentType[] = {kRecentDocuments, kRecentFolders,
167			kRecentApplications};
168		const int recentTypes = 3;
169		TRecentsMenu* recentItem[recentTypes];
170
171		bool enabled = false;
172
173		for (int i = 0; i < recentTypes; i++) {
174			recentItem[i]
175				= new TRecentsMenu(B_TRANSLATE_NOCOLLECT(recentTitle[i]),
176					fBarView, recentType[i]);
177
178			if (recentItem[i])
179				enabled |= recentItem[i]->RecentsEnabled();
180		}
181		if (enabled) {
182			AddSeparatorItem();
183
184			for (int i = 0; i < recentTypes; i++) {
185				if (!recentItem[i])
186					continue;
187
188				if (recentItem[i]->RecentsEnabled()) {
189					recentItem[i]->SetTypesList(TypesList());
190					recentItem[i]->SetTarget(Target());
191					AddItem(recentItem[i]);
192				}
193
194				if (data && fBarView && fBarView->Dragging()) {
195					recentItem[i]->InitTrackingHook(data->fTrackingHook,
196						&data->fTarget, data->fDragMessage);
197				}
198			}
199		} else {
200			for (int i = 0; i < recentTypes; i++)
201				delete recentItem[i];
202		}
203
204		AddSeparatorItem();
205		fAddState = kAddingDeskbarMenu;
206		return true;
207	}
208
209	if (fAddState == kAddingDeskbarMenu) {
210		// keep reentering and adding items
211		// until this returns false
212		bool done = BNavMenu::AddNextItem();
213		BMenuItem* item = ItemAt(CountItems() - 1);
214		if (item) {
215			BNavMenu* menu = dynamic_cast<BNavMenu*>(item->Menu());
216			if (menu) {
217				if (data && fBarView->Dragging()) {
218					menu->InitTrackingHook(data->fTrackingHook,
219						&data->fTarget, data->fDragMessage);
220				} else
221					menu->InitTrackingHook(0, NULL, NULL);
222			}
223		}
224
225		if (!done)
226			fAddState = kDone;
227		return done;
228	}
229
230	return false;
231}
232
233
234bool
235TDeskbarMenu::AddStandardDeskbarMenuItems()
236{
237	bool dragging = false;
238	if (fBarView)
239		dragging = fBarView->Dragging();
240
241	BMenuItem* item;
242
243// One of them is used if HAIKU_DISTRO_COMPATIBILITY_OFFICIAL, and the other if
244// not. However, we want both of them to end up in the catalog, so we have to
245// make them visible to collectcatkeys in either case.
246B_TRANSLATE_MARK_VOID("About Haiku")
247B_TRANSLATE_MARK_VOID("About this system")
248
249	item = new BMenuItem(
250#ifdef HAIKU_DISTRO_COMPATIBILITY_OFFICIAL
251	B_TRANSLATE_NOCOLLECT("About Haiku")
252#else
253	B_TRANSLATE_NOCOLLECT("About this system")
254#endif
255		, new BMessage(kShowSplash));
256	item->SetEnabled(!dragging);
257	AddItem(item);
258
259	static const char* kFindMenuItemStr
260		= B_TRANSLATE_MARK("Find" B_UTF8_ELLIPSIS);
261
262#ifdef SHOW_RECENT_FIND_ITEMS
263	item = new BMenuItem(
264		TrackerBuildRecentFindItemsMenu(kFindMenuItemStr),
265		new BMessage(kFindButton));
266#else
267	item = new BMenuItem(B_TRANSLATE_NOCOLLECT(kFindMenuItemStr),
268		new BMessage(kFindButton));
269#endif
270	item->SetEnabled(!dragging);
271	AddItem(item);
272
273	item = new BMenuItem(B_TRANSLATE("Show replicants"),
274		new BMessage(kToggleDraggers));
275	item->SetEnabled(!dragging);
276	item->SetMarked(BDragger::AreDraggersDrawn());
277	AddItem(item);
278
279	static const char* kMountMenuStr = B_TRANSLATE_MARK("Mount");
280
281#ifdef MOUNT_MENU_IN_DESKBAR
282	DeskbarMountMenu* mountMenu = new DeskbarMountMenu(
283		B_TRANSLATE_NOCOLLECT(kMountMenuStr));
284	mountMenu->SetEnabled(!dragging);
285	AddItem(mountMenu);
286#endif
287
288	item = new BMenuItem(B_TRANSLATE("Deskbar preferences" B_UTF8_ELLIPSIS),
289		new BMessage(kConfigShow));
290	item->SetTarget(be_app);
291	AddItem(item);
292
293	AddSeparatorItem();
294
295	BMenu* shutdownMenu = new BMenu(B_TRANSLATE("Shutdown" B_UTF8_ELLIPSIS));
296
297	item = new BMenuItem(B_TRANSLATE("Power off"),
298		new BMessage(kShutdownSystem));
299	item->SetEnabled(!dragging);
300	shutdownMenu->AddItem(item);
301
302	item = new BMenuItem(B_TRANSLATE("Restart system"),
303		new BMessage(kRebootSystem));
304	item->SetEnabled(!dragging);
305	shutdownMenu->AddItem(item);
306
307	B_TRANSLATE_MARK_VOID("Suspend");
308
309#ifdef APM_SUPPORT
310	if (_kapm_control_(APM_CHECK_ENABLED) == B_OK) {
311		item = new BMenuItem(B_TRANSLATE_NOCOLLECT("Suspend"),
312			new BMessage(kSuspendSystem));
313		item->SetEnabled(!dragging);
314		shutdownMenu->AddItem(item);
315	}
316#endif
317
318	shutdownMenu->SetTargetForItems(be_app);
319
320	BMessage* message = new BMessage(kShutdownSystem);
321	message->AddBool("confirm", true);
322	AddItem(new BMenuItem(shutdownMenu, message));
323
324	fAddState = kAddingRecents;
325
326	return true;
327}
328
329
330void
331TDeskbarMenu::ClearMenuBuildingState()
332{
333	fAddState = kDone;
334	fMenuBuilt = false;
335		// force the menu to get rebuilt each time
336	BNavMenu::ClearMenuBuildingState();
337}
338
339
340void
341TDeskbarMenu::ResetTargets()
342{
343	// This method does not recurse into submenus
344	// and does not affect menu items in submenus.
345	// (e.g. "Restart System" and "Power Off")
346
347	BNavMenu::ResetTargets();
348
349	// if we are dragging, set the target to whatever was set
350	// else set it to the default (Tracker)
351	if (!fBarView->Dragging())
352		SetTarget(DefaultTarget());
353
354	// now set the target for the menuitems to the currently
355	// set target, which may or may not be tracker
356	SetTargetForItems(Target());
357
358	for (int32 i = 0; ; i++) {
359		BMenuItem* item = ItemAt(i);
360		if (item == NULL)
361			break;
362
363		if (item->Message()) {
364			switch (item->Message()->what) {
365				case kFindButton:
366					item->SetTarget(BMessenger(kTrackerSignature));
367					break;
368
369				case kShowSplash:
370				case kToggleDraggers:
371				case kConfigShow:
372				case kConfigQuit:
373				case kAlwaysTop:
374				case kExpandNewTeams:
375				case kHideLabels:
376				case kResizeTeamIcons:
377				case kSortRunningApps:
378				case kTrackerFirst:
379				case kRebootSystem:
380				case kSuspendSystem:
381				case kShutdownSystem:
382				case kRealignReplicants:
383				case kShowHideTime:
384				case kShowSeconds:
385				case kShowDayOfWeek:
386				case kShowTimeZone:
387				case kGetClockSettings:
388					item->SetTarget(be_app);
389					break;
390			}
391		}
392	}
393}
394
395
396BPoint
397TDeskbarMenu::ScreenLocation()
398{
399	bool vertical = fBarView->Vertical();
400	int32 expando = fBarView->ExpandoState();
401	bool left = fBarView->Left();
402	BPoint point;
403
404	BRect rect = Supermenu()->Bounds();
405	Supermenu()->ConvertToScreen(&rect);
406
407	if (vertical && expando && left) {
408		PRINT(("Left\n"));
409		point = rect.RightTop() + BPoint(0, 3);
410	} else if (vertical && expando && !left) {
411		PRINT(("Right\n"));
412		point = rect.LeftTop() - BPoint(Bounds().Width(), 0) + BPoint(0, 3);
413	} else
414		point = BMenu::ScreenLocation();
415
416	return point;
417}
418
419
420/*static*/
421BMessenger
422TDeskbarMenu::DefaultTarget()
423{
424	// if Tracker is not available we target the BarApp
425	BMessenger target(kTrackerSignature);
426	if (target.IsValid())
427		return target;
428
429	return BMessenger(be_app);
430}
431
432
433//	#pragma mark -
434
435
436TRecentsMenu::TRecentsMenu(const char* name, TBarView* bar, int32 which,
437		const char* signature, entry_ref* appRef)
438	: BNavMenu(name, B_REFS_RECEIVED, TDeskbarMenu::DefaultTarget()),
439	fWhich(which),
440	fAppRef(NULL),
441	fSignature(NULL),
442	fRecentsCount(0),
443	fRecentsEnabled(false),
444	fItemIndex(0),
445	fBarView(bar)
446{
447	TBarApp* app = dynamic_cast<TBarApp*>(be_app);
448	if (app == NULL)
449		return;
450
451	switch (which) {
452		case kRecentDocuments:
453			fRecentsCount = app->Settings()->recentDocsCount;
454			fRecentsEnabled = app->Settings()->recentDocsEnabled;
455			break;
456		case kRecentApplications:
457			fRecentsCount = app->Settings()->recentAppsCount;
458			fRecentsEnabled = app->Settings()->recentAppsEnabled;
459			break;
460		case kRecentAppDocuments:
461			fRecentsCount = app->Settings()->recentDocsCount;
462			fRecentsEnabled = app->Settings()->recentDocsEnabled;
463			if (signature != NULL)
464				fSignature = strdup(signature);
465			if (appRef != NULL)
466				fAppRef = new entry_ref(*appRef);
467			break;
468		case kRecentFolders:
469			fRecentsCount = app->Settings()->recentFoldersCount;
470			fRecentsEnabled = app->Settings()->recentFoldersEnabled;
471			break;
472	}
473}
474
475
476TRecentsMenu::~TRecentsMenu()
477{
478	delete fAppRef;
479	free(fSignature);
480}
481
482
483void
484TRecentsMenu::DetachedFromWindow()
485{
486	// BNavMenu::DetachedFromWindow sets the TypesList to NULL
487	BMenu::DetachedFromWindow();
488}
489
490
491bool
492TRecentsMenu::StartBuildingItemList()
493{
494	RemoveItems(0, CountItems(), true);
495
496	// !! note: don't call inherited from here
497	// the navref is not set for this menu
498	// but it still needs to be a draggable navmenu
499	// simply return true so that AddNextItem is called
500	//
501	// return BNavMenu::StartBuildingItemList();
502	return true;
503}
504
505
506bool
507TRecentsMenu::AddNextItem()
508{
509	if (fRecentsCount > 0 && fRecentsEnabled && AddRecents(fRecentsCount))
510		return true;
511
512	fItemIndex = 0;
513	return false;
514}
515
516
517bool
518TRecentsMenu::AddRecents(int32 count)
519{
520	if (fItemIndex == 0) {
521		fRecentList.MakeEmpty();
522		BRoster roster;
523
524		switch (fWhich) {
525			case kRecentDocuments:
526				roster.GetRecentDocuments(&fRecentList, count);
527				break;
528			case kRecentApplications:
529				roster.GetRecentApps(&fRecentList, count);
530				break;
531			case kRecentAppDocuments:
532				roster.GetRecentDocuments(&fRecentList, count, NULL,
533					fSignature);
534				break;
535			case kRecentFolders:
536				roster.GetRecentFolders(&fRecentList, count);
537				break;
538			default:
539				return false;
540		}
541	}
542
543	for (;;) {
544		entry_ref ref;
545		if (fRecentList.FindRef("refs", fItemIndex++, &ref) != B_OK)
546			break;
547
548		if (ref.name && strlen(ref.name) > 0) {
549			Model model(&ref, true);
550
551			if (fWhich != kRecentApplications) {
552				BMessage* message = new BMessage(B_REFS_RECEIVED);
553				if (fWhich == kRecentAppDocuments) {
554					// add application as handler
555					message->AddRef("handler", fAppRef);
556				}
557
558				ModelMenuItem* item = BNavMenu::NewModelItem(&model,
559					message, Target(), false, NULL, TypesList());
560
561				if (item)
562					AddItem(item);
563			} else {
564				// The application items expand to a list of recent documents
565				// for that application - so they must be handled extra
566				BFile file(&ref, B_READ_ONLY);
567				char signature[B_MIME_TYPE_LENGTH];
568
569				BAppFileInfo appInfo(&file);
570				if (appInfo.InitCheck() != B_OK
571					|| appInfo.GetSignature(signature) != B_OK)
572					continue;
573
574				ModelMenuItem* item = NULL;
575				BMessage doc;
576				be_roster->GetRecentDocuments(&doc, 1, NULL, signature);
577					// ToDo: check if the documents do exist at all to
578					//		avoid the creation of the submenu.
579
580				if (doc.CountNames(B_REF_TYPE) > 0) {
581					// create recents menu that will contain the recent docs of
582					// this app
583					TRecentsMenu* docs = new TRecentsMenu(model.Name(),
584						fBarView, kRecentAppDocuments, signature, &ref);
585					docs->SetTypesList(TypesList());
586					docs->SetTarget(Target());
587
588					item = new ModelMenuItem(&model, docs);
589				} else
590					item = new ModelMenuItem(&model, model.Name(), NULL);
591
592				if (item) {
593					// add refs-message so that the recent app can be launched
594					BMessage* msg = new BMessage(B_REFS_RECEIVED);
595					msg->AddRef("refs", &ref);
596					item->SetMessage(msg);
597					item->SetTarget(Target());
598
599					AddItem(item);
600				}
601			}
602
603			// return true so that we know to reenter this list
604			return true;
605		}
606	}
607
608	// return false if we are done with this list
609	return false;
610}
611
612
613void
614TRecentsMenu::DoneBuildingItemList()
615{
616	// !! note: don't call inherited here
617	// the object list is not built
618	// and this list does not need to be sorted
619	// BNavMenu::DoneBuildingItemList();
620
621	if (CountItems() > 0)
622		SetTargetForItems(Target());
623}
624
625
626void
627TRecentsMenu::ClearMenuBuildingState()
628{
629	fMenuBuilt = false;
630	BNavMenu::ClearMenuBuildingState();
631}
632
633
634void
635TRecentsMenu::ResetTargets()
636{
637	BNavMenu::ResetTargets();
638
639	// if we are dragging, set the target to whatever was set
640	// else set it to the default (Tracker)
641	if (!fBarView->Dragging())
642		SetTarget(TDeskbarMenu::DefaultTarget());
643
644	// now set the target for the menuitems to the currently
645	// set target, which may or may not be tracker
646	SetTargetForItems(Target());
647}
648
649
650//	#pragma mark - DeskbarMountMenu
651
652
653#ifdef MOUNT_MENU_IN_DESKBAR
654DeskbarMountMenu::DeskbarMountMenu(const char* name)
655	: BPrivate::MountMenu(name)
656{
657}
658
659
660bool
661DeskbarMountMenu::AddDynamicItem(add_state s)
662{
663	BPrivate::MountMenu::AddDynamicItem(s);
664
665	SetTargetForItems(BMessenger(kTrackerSignature));
666
667	return false;
668}
669#endif	// MOUNT_MENU_IN_DESKBAR
670