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