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