xref: /haiku/src/apps/deskbar/DeskbarMenu.cpp (revision d63ed5844d997a61afd7a6c4b7c9fad0dc01e9f3)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
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:
14 
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.
17 
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.
24 
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.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30 trademarks of Be Incorporated in the United States and other countries. Other
31 brand product names are registered trademarks or trademarks of their respective
32 holders.
33 All 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
64 class DeskbarMountMenu : public BPrivate::MountMenu {
65 public:
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 
73 namespace BPrivate {
74 	BMenu* TrackerBuildRecentFindItemsMenu(const char*);
75 }
76 
77 using namespace BPrivate;
78 
79 
80 //	#pragma mark - TDeskbarMenu
81 
82 
83 TDeskbarMenu::TDeskbarMenu(TBarView* barView)
84 	:
85 	BNavMenu("DeskbarMenu", B_REFS_RECEIVED, DefaultTarget()),
86 	fAddState(kStart),
87 	fBarView(barView)
88 {
89 }
90 
91 
92 void
93 TDeskbarMenu::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 
115 void
116 TDeskbarMenu::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 
132 bool
133 TDeskbarMenu::StartBuildingItemList()
134 {
135 	RemoveItems(0, CountItems(), true);
136 	fAddState = kStart;
137 	return BNavMenu::StartBuildingItemList();
138 }
139 
140 
141 void
142 TDeskbarMenu::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 
154 bool
155 TDeskbarMenu::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 
234 bool
235 TDeskbarMenu::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.
246 B_TRANSLATE_MARK_VOID("About Haiku")
247 B_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("Restart system"),
298 		new BMessage(kRebootSystem));
299 	item->SetEnabled(!dragging);
300 	shutdownMenu->AddItem(item);
301 
302 	B_TRANSLATE_MARK_VOID("Suspend");
303 
304 #ifdef APM_SUPPORT
305 	if (_kapm_control_(APM_CHECK_ENABLED) == B_OK) {
306 		item = new BMenuItem(B_TRANSLATE_NOCOLLECT("Suspend"),
307 			new BMessage(kSuspendSystem));
308 		item->SetEnabled(!dragging);
309 		shutdownMenu->AddItem(item);
310 	}
311 #endif
312 
313 	item = new BMenuItem(B_TRANSLATE("Power off"),
314 		new BMessage(kShutdownSystem));
315 	item->SetEnabled(!dragging);
316 	shutdownMenu->AddItem(item);
317 	shutdownMenu->SetFont(be_plain_font);
318 
319 	shutdownMenu->SetTargetForItems(be_app);
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 
330 void
331 TDeskbarMenu::ClearMenuBuildingState()
332 {
333 	fAddState = kDone;
334 	fMenuBuilt = false;
335 		// force the menu to get rebuilt each time
336 	BNavMenu::ClearMenuBuildingState();
337 }
338 
339 
340 void
341 TDeskbarMenu::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 kShowHideTime:
383 				case kShowSeconds:
384 				case kShowDayOfWeek:
385 				case kShowTimeZone:
386 				case kGetClockSettings:
387 					item->SetTarget(be_app);
388 					break;
389 			}
390 		}
391 	}
392 }
393 
394 
395 BPoint
396 TDeskbarMenu::ScreenLocation()
397 {
398 	bool vertical = fBarView->Vertical();
399 	int32 expando = (fBarView->State() == kExpandoState);
400 	BPoint point;
401 
402 	BRect rect = Supermenu()->Bounds();
403 	Supermenu()->ConvertToScreen(&rect);
404 
405 	if (expando && vertical && fBarView->Left()) {
406 		PRINT(("Left\n"));
407 		point = rect.RightTop() + BPoint(0, 3);
408 	} else if (expando && vertical && !fBarView->Left()) {
409 		PRINT(("Right\n"));
410 		point = rect.LeftTop() - BPoint(Bounds().Width(), 0) + BPoint(0, 3);
411 	} else
412 		point = BMenu::ScreenLocation();
413 
414 	return point;
415 }
416 
417 
418 /*static*/
419 BMessenger
420 TDeskbarMenu::DefaultTarget()
421 {
422 	// if Tracker is not available we target the BarApp
423 	BMessenger target(kTrackerSignature);
424 	if (target.IsValid())
425 		return target;
426 
427 	return BMessenger(be_app);
428 }
429 
430 
431 //	#pragma mark -
432 
433 
434 TRecentsMenu::TRecentsMenu(const char* name, TBarView* bar, int32 which,
435 		const char* signature, entry_ref* appRef)
436 	: BNavMenu(name, B_REFS_RECEIVED, TDeskbarMenu::DefaultTarget()),
437 	fWhich(which),
438 	fAppRef(NULL),
439 	fSignature(NULL),
440 	fRecentsCount(0),
441 	fRecentsEnabled(false),
442 	fItemIndex(0),
443 	fBarView(bar)
444 {
445 	TBarApp* app = dynamic_cast<TBarApp*>(be_app);
446 	if (app == NULL)
447 		return;
448 
449 	switch (which) {
450 		case kRecentDocuments:
451 			fRecentsCount = app->Settings()->recentDocsCount;
452 			fRecentsEnabled = app->Settings()->recentDocsEnabled;
453 			break;
454 		case kRecentApplications:
455 			fRecentsCount = app->Settings()->recentAppsCount;
456 			fRecentsEnabled = app->Settings()->recentAppsEnabled;
457 			break;
458 		case kRecentAppDocuments:
459 			fRecentsCount = app->Settings()->recentDocsCount;
460 			fRecentsEnabled = app->Settings()->recentDocsEnabled;
461 			if (signature != NULL)
462 				fSignature = strdup(signature);
463 			if (appRef != NULL)
464 				fAppRef = new entry_ref(*appRef);
465 			break;
466 		case kRecentFolders:
467 			fRecentsCount = app->Settings()->recentFoldersCount;
468 			fRecentsEnabled = app->Settings()->recentFoldersEnabled;
469 			break;
470 	}
471 }
472 
473 
474 TRecentsMenu::~TRecentsMenu()
475 {
476 	delete fAppRef;
477 	free(fSignature);
478 }
479 
480 
481 void
482 TRecentsMenu::DetachedFromWindow()
483 {
484 	// BNavMenu::DetachedFromWindow sets the TypesList to NULL
485 	BMenu::DetachedFromWindow();
486 }
487 
488 
489 bool
490 TRecentsMenu::StartBuildingItemList()
491 {
492 	RemoveItems(0, CountItems(), true);
493 
494 	// !! note: don't call inherited from here
495 	// the navref is not set for this menu
496 	// but it still needs to be a draggable navmenu
497 	// simply return true so that AddNextItem is called
498 	//
499 	// return BNavMenu::StartBuildingItemList();
500 	return true;
501 }
502 
503 
504 bool
505 TRecentsMenu::AddNextItem()
506 {
507 	if (fRecentsCount > 0 && fRecentsEnabled && AddRecents(fRecentsCount))
508 		return true;
509 
510 	fItemIndex = 0;
511 	return false;
512 }
513 
514 
515 bool
516 TRecentsMenu::AddRecents(int32 count)
517 {
518 	if (fItemIndex == 0) {
519 		fRecentList.MakeEmpty();
520 		BRoster roster;
521 
522 		switch (fWhich) {
523 			case kRecentDocuments:
524 				roster.GetRecentDocuments(&fRecentList, count);
525 				break;
526 			case kRecentApplications:
527 				roster.GetRecentApps(&fRecentList, count);
528 				break;
529 			case kRecentAppDocuments:
530 				roster.GetRecentDocuments(&fRecentList, count, NULL,
531 					fSignature);
532 				break;
533 			case kRecentFolders:
534 				roster.GetRecentFolders(&fRecentList, count);
535 				break;
536 			default:
537 				return false;
538 		}
539 	}
540 
541 	for (;;) {
542 		entry_ref ref;
543 		if (fRecentList.FindRef("refs", fItemIndex++, &ref) != B_OK)
544 			break;
545 
546 		if (ref.name && strlen(ref.name) > 0) {
547 			Model model(&ref, true);
548 
549 			if (fWhich != kRecentApplications) {
550 				BMessage* message = new BMessage(B_REFS_RECEIVED);
551 				if (fWhich == kRecentAppDocuments) {
552 					// add application as handler
553 					message->AddRef("handler", fAppRef);
554 				}
555 
556 				ModelMenuItem* item = BNavMenu::NewModelItem(&model,
557 					message, Target(), false, NULL, TypesList());
558 
559 				if (item)
560 					AddItem(item);
561 			} else {
562 				// The application items expand to a list of recent documents
563 				// for that application - so they must be handled extra
564 				BFile file(&ref, B_READ_ONLY);
565 				char signature[B_MIME_TYPE_LENGTH];
566 
567 				BAppFileInfo appInfo(&file);
568 				if (appInfo.InitCheck() != B_OK
569 					|| appInfo.GetSignature(signature) != B_OK)
570 					continue;
571 
572 				ModelMenuItem* item = NULL;
573 				BMessage doc;
574 				be_roster->GetRecentDocuments(&doc, 1, NULL, signature);
575 					// ToDo: check if the documents do exist at all to
576 					//		avoid the creation of the submenu.
577 
578 				if (doc.CountNames(B_REF_TYPE) > 0) {
579 					// create recents menu that will contain the recent docs of
580 					// this app
581 					TRecentsMenu* docs = new TRecentsMenu(model.Name(),
582 						fBarView, kRecentAppDocuments, signature, &ref);
583 					docs->SetTypesList(TypesList());
584 					docs->SetTarget(Target());
585 
586 					item = new ModelMenuItem(&model, docs);
587 				} else
588 					item = new ModelMenuItem(&model, model.Name(), NULL);
589 
590 				if (item) {
591 					// add refs-message so that the recent app can be launched
592 					BMessage* msg = new BMessage(B_REFS_RECEIVED);
593 					msg->AddRef("refs", &ref);
594 					item->SetMessage(msg);
595 					item->SetTarget(Target());
596 
597 					AddItem(item);
598 				}
599 			}
600 
601 			// return true so that we know to reenter this list
602 			return true;
603 		}
604 	}
605 
606 	// return false if we are done with this list
607 	return false;
608 }
609 
610 
611 void
612 TRecentsMenu::DoneBuildingItemList()
613 {
614 	// !! note: don't call inherited here
615 	// the object list is not built
616 	// and this list does not need to be sorted
617 	// BNavMenu::DoneBuildingItemList();
618 
619 	if (CountItems() > 0)
620 		SetTargetForItems(Target());
621 }
622 
623 
624 void
625 TRecentsMenu::ClearMenuBuildingState()
626 {
627 	fMenuBuilt = false;
628 	BNavMenu::ClearMenuBuildingState();
629 }
630 
631 
632 void
633 TRecentsMenu::ResetTargets()
634 {
635 	BNavMenu::ResetTargets();
636 
637 	// if we are dragging, set the target to whatever was set
638 	// else set it to the default (Tracker)
639 	if (!fBarView->Dragging())
640 		SetTarget(TDeskbarMenu::DefaultTarget());
641 
642 	// now set the target for the menuitems to the currently
643 	// set target, which may or may not be tracker
644 	SetTargetForItems(Target());
645 }
646 
647 
648 //	#pragma mark - DeskbarMountMenu
649 
650 
651 #ifdef MOUNT_MENU_IN_DESKBAR
652 DeskbarMountMenu::DeskbarMountMenu(const char* name)
653 	: BPrivate::MountMenu(name)
654 {
655 	SetFont(be_plain_font);
656 }
657 
658 
659 bool
660 DeskbarMountMenu::AddDynamicItem(add_state s)
661 {
662 	BPrivate::MountMenu::AddDynamicItem(s);
663 
664 	SetTargetForItems(BMessenger(kTrackerSignature));
665 
666 	return false;
667 }
668 #endif	// MOUNT_MENU_IN_DESKBAR
669