xref: /haiku/src/apps/deskbar/DeskbarMenu.cpp (revision 97dfeb96704e5dbc5bec32ad7b21379d0125e031)
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 		}
200 
201 		AddSeparatorItem();
202 		fAddState = kAddingDeskbarMenu;
203 		return true;
204 	}
205 
206 	if (fAddState == kAddingDeskbarMenu) {
207 		// keep reentering and adding items
208 		// until this returns false
209 		bool done = BNavMenu::AddNextItem();
210 		BMenuItem* item = ItemAt(CountItems() - 1);
211 		if (item) {
212 			BNavMenu* menu = dynamic_cast<BNavMenu*>(item->Menu());
213 			if (menu) {
214 				if (data && fBarView->Dragging()) {
215 					menu->InitTrackingHook(data->fTrackingHook,
216 						&data->fTarget, data->fDragMessage);
217 				} else
218 					menu->InitTrackingHook(0, NULL, NULL);
219 			}
220 		}
221 
222 		if (!done)
223 			fAddState = kDone;
224 		return done;
225 	}
226 
227 	return false;
228 }
229 
230 
231 bool
232 TDeskbarMenu::AddStandardDeskbarMenuItems()
233 {
234 	bool dragging = false;
235 	if (fBarView)
236 		dragging = fBarView->Dragging();
237 
238 	BMenuItem* item;
239 
240 // One of them is used if HAIKU_DISTRO_COMPATIBILITY_OFFICIAL, and the other if
241 // not. However, we want both of them to end up in the catalog, so we have to
242 // make them visible to collectcatkeys in either case.
243 B_TRANSLATE_MARK_VOID("About Haiku")
244 B_TRANSLATE_MARK_VOID("About this system")
245 
246 	item = new BMenuItem(
247 #ifdef HAIKU_DISTRO_COMPATIBILITY_OFFICIAL
248 	B_TRANSLATE_NOCOLLECT("About Haiku")
249 #else
250 	B_TRANSLATE_NOCOLLECT("About this system")
251 #endif
252 		, new BMessage(kShowSplash));
253 	item->SetEnabled(!dragging);
254 	AddItem(item);
255 
256 	static const char* kFindMenuItemStr
257 		= B_TRANSLATE_MARK("Find" B_UTF8_ELLIPSIS);
258 
259 #ifdef SHOW_RECENT_FIND_ITEMS
260 	item = new BMenuItem(
261 		TrackerBuildRecentFindItemsMenu(kFindMenuItemStr),
262 		new BMessage(kFindButton));
263 #else
264 	item = new BMenuItem(B_TRANSLATE_NOCOLLECT(kFindMenuItemStr),
265 		new BMessage(kFindButton));
266 #endif
267 	item->SetEnabled(!dragging);
268 	AddItem(item);
269 
270 	item = new BMenuItem(B_TRANSLATE("Show replicants"),
271 		new BMessage(kToggleDraggers));
272 	item->SetEnabled(!dragging);
273 	item->SetMarked(BDragger::AreDraggersDrawn());
274 	AddItem(item);
275 
276 	static const char* kMountMenuStr = B_TRANSLATE_MARK("Mount");
277 
278 #ifdef MOUNT_MENU_IN_DESKBAR
279 	DeskbarMountMenu* mountMenu = new DeskbarMountMenu(
280 		B_TRANSLATE_NOCOLLECT(kMountMenuStr));
281 	mountMenu->SetEnabled(!dragging);
282 	AddItem(mountMenu);
283 #endif
284 
285 	item = new BMenuItem(B_TRANSLATE("Deskbar preferences" B_UTF8_ELLIPSIS),
286 		new BMessage(kConfigShow));
287 	item->SetTarget(be_app);
288 	AddItem(item);
289 
290 	AddSeparatorItem();
291 
292 	BMenu* shutdownMenu = new BMenu(B_TRANSLATE("Shutdown" B_UTF8_ELLIPSIS));
293 
294 	item = new BMenuItem(B_TRANSLATE("Restart system"),
295 		new BMessage(kRebootSystem));
296 	item->SetEnabled(!dragging);
297 	shutdownMenu->AddItem(item);
298 
299 	B_TRANSLATE_MARK_VOID("Suspend");
300 
301 #ifdef APM_SUPPORT
302 	if (_kapm_control_(APM_CHECK_ENABLED) == B_OK) {
303 		item = new BMenuItem(B_TRANSLATE_NOCOLLECT("Suspend"),
304 			new BMessage(kSuspendSystem));
305 		item->SetEnabled(!dragging);
306 		shutdownMenu->AddItem(item);
307 	}
308 #endif
309 
310 	item = new BMenuItem(B_TRANSLATE("Power off"),
311 		new BMessage(kShutdownSystem));
312 	item->SetEnabled(!dragging);
313 	shutdownMenu->AddItem(item);
314 	shutdownMenu->SetFont(be_plain_font);
315 
316 	shutdownMenu->SetTargetForItems(be_app);
317 	BMessage* message = new BMessage(kShutdownSystem);
318 	message->AddBool("confirm", true);
319 	AddItem(new BMenuItem(shutdownMenu, message));
320 
321 	fAddState = kAddingRecents;
322 
323 	return true;
324 }
325 
326 
327 void
328 TDeskbarMenu::ClearMenuBuildingState()
329 {
330 	fAddState = kDone;
331 	fMenuBuilt = false;
332 		// force the menu to get rebuilt each time
333 	BNavMenu::ClearMenuBuildingState();
334 }
335 
336 
337 void
338 TDeskbarMenu::ResetTargets()
339 {
340 	// This method does not recurse into submenus
341 	// and does not affect menu items in submenus.
342 	// (e.g. "Restart System" and "Power Off")
343 
344 	BNavMenu::ResetTargets();
345 
346 	// if we are dragging, set the target to whatever was set
347 	// else set it to the default (Tracker)
348 	if (!fBarView->Dragging())
349 		SetTarget(DefaultTarget());
350 
351 	// now set the target for the menuitems to the currently
352 	// set target, which may or may not be tracker
353 	SetTargetForItems(Target());
354 
355 	for (int32 i = 0; ; i++) {
356 		BMenuItem* item = ItemAt(i);
357 		if (item == NULL)
358 			break;
359 
360 		if (item->Message()) {
361 			switch (item->Message()->what) {
362 				case kFindButton:
363 					item->SetTarget(BMessenger(kTrackerSignature));
364 					break;
365 
366 				case kShowSplash:
367 				case kToggleDraggers:
368 				case kConfigShow:
369 				case kConfigQuit:
370 				case kAlwaysTop:
371 				case kExpandNewTeams:
372 				case kHideLabels:
373 				case kResizeTeamIcons:
374 				case kSortRunningApps:
375 				case kTrackerFirst:
376 				case kRebootSystem:
377 				case kSuspendSystem:
378 				case kShutdownSystem:
379 				case kShowHideTime:
380 				case kShowSeconds:
381 				case kShowDayOfWeek:
382 				case kShowTimeZone:
383 				case kGetClockSettings:
384 					item->SetTarget(be_app);
385 					break;
386 			}
387 		}
388 	}
389 }
390 
391 
392 BPoint
393 TDeskbarMenu::ScreenLocation()
394 {
395 	bool vertical = fBarView->Vertical();
396 	int32 expando = (fBarView->State() == kExpandoState);
397 	BPoint point;
398 
399 	BRect rect = Supermenu()->Bounds();
400 	Supermenu()->ConvertToScreen(&rect);
401 
402 	if (expando && vertical && fBarView->Left()) {
403 		PRINT(("Left\n"));
404 		point = rect.RightTop() + BPoint(0, 3);
405 	} else if (expando && vertical && !fBarView->Left()) {
406 		PRINT(("Right\n"));
407 		point = rect.LeftTop() - BPoint(Bounds().Width(), 0) + BPoint(0, 3);
408 	} else
409 		point = BMenu::ScreenLocation();
410 
411 	return point;
412 }
413 
414 
415 /*static*/
416 BMessenger
417 TDeskbarMenu::DefaultTarget()
418 {
419 	// if Tracker is not available we target the BarApp
420 	BMessenger target(kTrackerSignature);
421 	if (target.IsValid())
422 		return target;
423 
424 	return BMessenger(be_app);
425 }
426 
427 
428 //	#pragma mark -
429 
430 
431 TRecentsMenu::TRecentsMenu(const char* name, TBarView* bar, int32 which,
432 		const char* signature, entry_ref* appRef)
433 	: BNavMenu(name, B_REFS_RECEIVED, TDeskbarMenu::DefaultTarget()),
434 	fWhich(which),
435 	fAppRef(NULL),
436 	fSignature(NULL),
437 	fRecentsCount(0),
438 	fRecentsEnabled(false),
439 	fItemIndex(0),
440 	fBarView(bar)
441 {
442 	TBarApp* app = dynamic_cast<TBarApp*>(be_app);
443 	if (app == NULL)
444 		return;
445 
446 	switch (which) {
447 		case kRecentDocuments:
448 			fRecentsCount = app->Settings()->recentDocsCount;
449 			fRecentsEnabled = app->Settings()->recentDocsEnabled;
450 			break;
451 		case kRecentApplications:
452 			fRecentsCount = app->Settings()->recentAppsCount;
453 			fRecentsEnabled = app->Settings()->recentAppsEnabled;
454 			break;
455 		case kRecentAppDocuments:
456 			fRecentsCount = app->Settings()->recentDocsCount;
457 			fRecentsEnabled = app->Settings()->recentDocsEnabled;
458 			if (signature != NULL)
459 				fSignature = strdup(signature);
460 			if (appRef != NULL)
461 				fAppRef = new entry_ref(*appRef);
462 			break;
463 		case kRecentFolders:
464 			fRecentsCount = app->Settings()->recentFoldersCount;
465 			fRecentsEnabled = app->Settings()->recentFoldersEnabled;
466 			break;
467 	}
468 }
469 
470 
471 TRecentsMenu::~TRecentsMenu()
472 {
473 	delete fAppRef;
474 	free(fSignature);
475 }
476 
477 
478 void
479 TRecentsMenu::DetachedFromWindow()
480 {
481 	// BNavMenu::DetachedFromWindow sets the TypesList to NULL
482 	BMenu::DetachedFromWindow();
483 }
484 
485 
486 bool
487 TRecentsMenu::StartBuildingItemList()
488 {
489 	RemoveItems(0, CountItems(), true);
490 
491 	// !! note: don't call inherited from here
492 	// the navref is not set for this menu
493 	// but it still needs to be a draggable navmenu
494 	// simply return true so that AddNextItem is called
495 	//
496 	// return BNavMenu::StartBuildingItemList();
497 	return true;
498 }
499 
500 
501 bool
502 TRecentsMenu::AddNextItem()
503 {
504 	if (fRecentsCount > 0 && fRecentsEnabled && AddRecents(fRecentsCount))
505 		return true;
506 
507 	fItemIndex = 0;
508 	return false;
509 }
510 
511 
512 bool
513 TRecentsMenu::AddRecents(int32 count)
514 {
515 	if (fItemIndex == 0) {
516 		fRecentList.MakeEmpty();
517 		BRoster roster;
518 
519 		switch (fWhich) {
520 			case kRecentDocuments:
521 				roster.GetRecentDocuments(&fRecentList, count);
522 				break;
523 			case kRecentApplications:
524 				roster.GetRecentApps(&fRecentList, count);
525 				break;
526 			case kRecentAppDocuments:
527 				roster.GetRecentDocuments(&fRecentList, count, NULL,
528 					fSignature);
529 				break;
530 			case kRecentFolders:
531 				roster.GetRecentFolders(&fRecentList, count);
532 				break;
533 			default:
534 				return false;
535 		}
536 	}
537 
538 	for (;;) {
539 		entry_ref ref;
540 		if (fRecentList.FindRef("refs", fItemIndex++, &ref) != B_OK)
541 			break;
542 
543 		if (ref.name && strlen(ref.name) > 0) {
544 			Model model(&ref, true);
545 
546 			if (fWhich != kRecentApplications) {
547 				BMessage* message = new BMessage(B_REFS_RECEIVED);
548 				if (fWhich == kRecentAppDocuments) {
549 					// add application as handler
550 					message->AddRef("handler", fAppRef);
551 				}
552 
553 				ModelMenuItem* item = BNavMenu::NewModelItem(&model,
554 					message, Target(), false, NULL, TypesList());
555 
556 				if (item)
557 					AddItem(item);
558 			} else {
559 				// The application items expand to a list of recent documents
560 				// for that application - so they must be handled extra
561 				BFile file(&ref, B_READ_ONLY);
562 				char signature[B_MIME_TYPE_LENGTH];
563 
564 				BAppFileInfo appInfo(&file);
565 				if (appInfo.InitCheck() != B_OK
566 					|| appInfo.GetSignature(signature) != B_OK)
567 					continue;
568 
569 				ModelMenuItem* item = NULL;
570 				BMessage doc;
571 				be_roster->GetRecentDocuments(&doc, 1, NULL, signature);
572 					// ToDo: check if the documents do exist at all to
573 					//		avoid the creation of the submenu.
574 
575 				if (doc.CountNames(B_REF_TYPE) > 0) {
576 					// create recents menu that will contain the recent docs of
577 					// this app
578 					TRecentsMenu* docs = new TRecentsMenu(model.Name(),
579 						fBarView, kRecentAppDocuments, signature, &ref);
580 					docs->SetTypesList(TypesList());
581 					docs->SetTarget(Target());
582 
583 					item = new ModelMenuItem(&model, docs);
584 				} else
585 					item = new ModelMenuItem(&model, model.Name(), NULL);
586 
587 				if (item) {
588 					// add refs-message so that the recent app can be launched
589 					BMessage* msg = new BMessage(B_REFS_RECEIVED);
590 					msg->AddRef("refs", &ref);
591 					item->SetMessage(msg);
592 					item->SetTarget(Target());
593 
594 					AddItem(item);
595 				}
596 			}
597 
598 			// return true so that we know to reenter this list
599 			return true;
600 		}
601 	}
602 
603 	// return false if we are done with this list
604 	return false;
605 }
606 
607 
608 void
609 TRecentsMenu::DoneBuildingItemList()
610 {
611 	// !! note: don't call inherited here
612 	// the object list is not built
613 	// and this list does not need to be sorted
614 	// BNavMenu::DoneBuildingItemList();
615 
616 	if (CountItems() > 0)
617 		SetTargetForItems(Target());
618 }
619 
620 
621 void
622 TRecentsMenu::ClearMenuBuildingState()
623 {
624 	fMenuBuilt = false;
625 	BNavMenu::ClearMenuBuildingState();
626 }
627 
628 
629 void
630 TRecentsMenu::ResetTargets()
631 {
632 	BNavMenu::ResetTargets();
633 
634 	// if we are dragging, set the target to whatever was set
635 	// else set it to the default (Tracker)
636 	if (!fBarView->Dragging())
637 		SetTarget(TDeskbarMenu::DefaultTarget());
638 
639 	// now set the target for the menuitems to the currently
640 	// set target, which may or may not be tracker
641 	SetTargetForItems(Target());
642 }
643 
644 
645 //	#pragma mark - DeskbarMountMenu
646 
647 
648 #ifdef MOUNT_MENU_IN_DESKBAR
649 DeskbarMountMenu::DeskbarMountMenu(const char* name)
650 	: BPrivate::MountMenu(name)
651 {
652 	SetFont(be_plain_font);
653 }
654 
655 
656 bool
657 DeskbarMountMenu::AddDynamicItem(add_state s)
658 {
659 	BPrivate::MountMenu::AddDynamicItem(s);
660 
661 	SetTargetForItems(BMessenger(kTrackerSignature));
662 
663 	return false;
664 }
665 #endif	// MOUNT_MENU_IN_DESKBAR
666