xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision 1e60bdeab63fa7a57bc9a55b032052e95a18bd2c)
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 "ExpandoMenuBar.h"
38 
39 #include <strings.h>
40 
41 #include <map>
42 
43 #include <Autolock.h>
44 #include <Bitmap.h>
45 #include <Collator.h>
46 #include <ControlLook.h>
47 #include <Debug.h>
48 #include <MenuPrivate.h>
49 #include <NodeInfo.h>
50 #include <Roster.h>
51 #include <Screen.h>
52 #include <Thread.h>
53 #include <Window.h>
54 
55 #include "icons.h"
56 
57 #include "BarApp.h"
58 #include "BarMenuTitle.h"
59 #include "BarView.h"
60 #include "BarWindow.h"
61 #include "DeskbarMenu.h"
62 #include "DeskbarUtils.h"
63 #include "InlineScrollView.h"
64 #include "ResourceSet.h"
65 #include "ShowHideMenuItem.h"
66 #include "StatusView.h"
67 #include "TeamMenu.h"
68 #include "TeamMenuItem.h"
69 #include "WindowMenu.h"
70 #include "WindowMenuItem.h"
71 
72 
73 const float kMinMenuItemWidth = 50.0f;
74 const float kSepItemWidth = 5.0f;
75 const float kIconPadding = 8.0f;
76 
77 const uint32 kMinimizeTeam = 'mntm';
78 const uint32 kBringTeamToFront = 'bftm';
79 
80 bool TExpandoMenuBar::sDoMonitor = false;
81 thread_id TExpandoMenuBar::sMonThread = B_ERROR;
82 BLocker TExpandoMenuBar::sMonLocker("expando monitor");
83 
84 typedef std::map<BString, TTeamMenuItem*> TeamMenuItemMap;
85 
86 
87 //	#pragma mark - TExpandoMenuBar
88 
89 
90 TExpandoMenuBar::TExpandoMenuBar(TBarView* barView, bool vertical)
91 	:
92 	BMenuBar(BRect(0, 0, 0, 0), "ExpandoMenuBar", B_FOLLOW_NONE,
93 		vertical ? B_ITEMS_IN_COLUMN : B_ITEMS_IN_ROW),
94 	fBarView(barView),
95 	fVertical(vertical),
96 	fOverflow(false),
97 	fFirstBuild(true),
98 	fDeskbarMenuWidth(kMinMenuItemWidth),
99 	fPreviousDragTargetItem(NULL),
100 	fLastMousedOverItem(NULL),
101 	fLastClickedItem(NULL)
102 {
103 	SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f);
104 	SetFont(be_plain_font);
105 	SetMaxItemWidth();
106 
107 	// top or bottom mode, add deskbar menu and sep for menubar tracking
108 	// consistency
109 	// TODO: this is broken code
110 	fDeskbarMenuWidth = 63 + 16;
111 //	const BBitmap* logoBitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE,
112 //		R_LeafLogoBitmap);
113 //	if (logoBitmap != NULL)
114 //		fDeskbarMenuWidth = logoBitmap->Bounds().Width() + 16;
115 }
116 
117 
118 void
119 TExpandoMenuBar::AllAttached()
120 {
121 	BMenuBar::AllAttached();
122 
123 	SizeWindow(0);
124 }
125 
126 
127 void
128 TExpandoMenuBar::AttachedToWindow()
129 {
130 	BMenuBar::AttachedToWindow();
131 
132 	fTeamList.MakeEmpty();
133 
134 	if (fVertical)
135 		StartMonitoringWindows();
136 }
137 
138 
139 void
140 TExpandoMenuBar::DetachedFromWindow()
141 {
142 	BMenuBar::DetachedFromWindow();
143 
144 	StopMonitoringWindows();
145 
146 	BMessenger self(this);
147 	BMessage message(kUnsubscribe);
148 	message.AddMessenger("messenger", self);
149 	be_app->PostMessage(&message);
150 
151 	RemoveItems(0, CountItems(), true);
152 }
153 
154 
155 void
156 TExpandoMenuBar::MessageReceived(BMessage* message)
157 {
158 	int32 index;
159 	TTeamMenuItem* item;
160 
161 	switch (message->what) {
162 		case B_SOME_APP_LAUNCHED:
163 		{
164 			BList* teams = NULL;
165 			message->FindPointer("teams", (void**)&teams);
166 
167 			BBitmap* icon = NULL;
168 			message->FindPointer("icon", (void**)&icon);
169 
170 			const char* signature = NULL;
171 			message->FindString("sig", &signature);
172 
173 			uint32 flags = 0;
174 			message->FindInt32("flags", ((int32*) &flags));
175 
176 			const char* name = NULL;
177 			message->FindString("name", &name);
178 
179 			AddTeam(teams, icon, strdup(name), strdup(signature));
180 			break;
181 		}
182 
183 		case B_MOUSE_WHEEL_CHANGED:
184 		{
185 			float deltaY = 0;
186 			message->FindFloat("be:wheel_delta_y", &deltaY);
187 			if (deltaY == 0)
188 				return;
189 
190 			TInlineScrollView* scrollView
191 				= dynamic_cast<TInlineScrollView*>(Parent());
192 			if (scrollView == NULL)
193 				return;
194 
195 			float largeStep;
196 			float smallStep;
197 			scrollView->GetSteps(&smallStep, &largeStep);
198 
199 			// pressing the option/command/control key scrolls faster
200 			if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY))
201 				deltaY *= largeStep;
202 			else
203 				deltaY *= smallStep;
204 
205 			scrollView->ScrollBy(deltaY);
206 			break;
207 		}
208 
209 		case kAddTeam:
210 			AddTeam(message->FindInt32("team"), message->FindString("sig"));
211 			break;
212 
213 		case kRemoveTeam:
214 		{
215 			team_id team = -1;
216 			message->FindInt32("team", &team);
217 
218 			RemoveTeam(team, true);
219 			break;
220 		}
221 
222 		case B_SOME_APP_QUIT:
223 		{
224 			team_id team = -1;
225 			message->FindInt32("team", &team);
226 
227 			RemoveTeam(team, false);
228 			break;
229 		}
230 
231 		case kMinimizeTeam:
232 		{
233 			index = message->FindInt32("itemIndex");
234 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
235 			if (item == NULL)
236 				break;
237 
238 			TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW,
239 				item->Teams(),
240 				item->Menu()->ConvertToScreen(item->Frame()),
241 				true);
242 			break;
243 		}
244 
245 		case kBringTeamToFront:
246 		{
247 			index = message->FindInt32("itemIndex");
248 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
249 			if (item == NULL)
250 				break;
251 
252 			TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT,
253 				item->Teams(), item->Menu()->ConvertToScreen(item->Frame()),
254 				true);
255 			break;
256 		}
257 
258 		default:
259 			BMenuBar::MessageReceived(message);
260 			break;
261 	}
262 }
263 
264 
265 void
266 TExpandoMenuBar::MouseDown(BPoint where)
267 {
268 	BMessage* message = Window()->CurrentMessage();
269 	BMenuItem* menuItem;
270 	TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem);
271 
272 	if (message == NULL || item == NULL || fBarView->Dragging()) {
273 		BMenuBar::MouseDown(where);
274 		return;
275 	}
276 
277 	int32 modifiers = 0;
278 	message->FindInt32("modifiers", &modifiers);
279 
280 	// check for three finger salute, a.k.a. Vulcan Death Grip
281 	if ((modifiers & B_COMMAND_KEY) != 0
282 		&& (modifiers & B_CONTROL_KEY) != 0
283 		&& (modifiers & B_SHIFT_KEY) != 0) {
284 		const BList* teams = item->Teams();
285 		int32 teamCount = teams->CountItems();
286 		team_id teamID;
287 		for (int32 team = 0; team < teamCount; team++) {
288 			teamID = (addr_t)teams->ItemAt(team);
289 			kill_team(teamID);
290 			RemoveTeam(teamID, false);
291 				// remove the team from display immediately
292 		}
293 		return;
294 			// absorb the message
295 	}
296 
297 	// control click - show all/hide all shortcut
298 	if ((modifiers & B_CONTROL_KEY) != 0) {
299 		// show/hide item's teams
300 		BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
301 			? kMinimizeTeam : kBringTeamToFront);
302 		showMessage.AddInt32("itemIndex", IndexOf(item));
303 		Window()->PostMessage(&showMessage, this);
304 		return;
305 			// absorb the message
306 	}
307 
308 	// check if within expander bounds to expand window items
309 	if (fVertical && static_cast<TBarApp*>(be_app)->Settings()->superExpando
310 		&& item->ExpanderBounds().Contains(where)) {
311 		// start the animation here, finish on mouse up
312 		fLastClickedItem = item;
313 		MouseDownThread<TExpandoMenuBar>::TrackMouse(this,
314 			&TExpandoMenuBar::_DoneTracking, &TExpandoMenuBar::_Track);
315 		Invalidate(item->ExpanderBounds());
316 		return;
317 			// absorb the message
318 	}
319 
320 	// double-click on an item brings the team to front
321 	int32 clicks;
322 	if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1
323 		&& item == menuItem && item == fLastClickedItem) {
324 		be_roster->ActivateApp((addr_t)item->Teams()->ItemAt(0));
325 			// activate this team
326 		return;
327 			// absorb the message
328 	}
329 
330 	fLastClickedItem = item;
331 	BMenuBar::MouseDown(where);
332 }
333 
334 
335 void
336 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message)
337 {
338 	int32 buttons;
339 	BMessage* currentMessage = Window()->CurrentMessage();
340 	if (currentMessage == NULL
341 		|| currentMessage->FindInt32("buttons", &buttons) != B_OK) {
342 		buttons = 0;
343 	}
344 
345 	if (message == NULL) {
346 		// force a cleanup
347 		_FinishedDrag();
348 
349 		switch (code) {
350 			case B_INSIDE_VIEW:
351 			{
352 				BMenuItem* menuItem;
353 				TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem);
354 				TWindowMenuItem* windowMenuItem
355 					= dynamic_cast<TWindowMenuItem*>(menuItem);
356 
357 				if (item == NULL || menuItem == NULL) {
358 					// item is NULL, remove the tooltip and break out
359 					fLastMousedOverItem = NULL;
360 					SetToolTip((const char*)NULL);
361 					break;
362 				}
363 
364 				if (menuItem == fLastMousedOverItem) {
365 					// already set the tooltip for this item, break out
366 					break;
367 				}
368 
369 				if (windowMenuItem != NULL && fBarView->Vertical()
370 					&& fBarView->ExpandoState() && item->IsExpanded()) {
371 					// expando mode window menu item
372 					fLastMousedOverItem = menuItem;
373 					if (strcasecmp(windowMenuItem->TruncatedLabel(),
374 							windowMenuItem->Label()) > 0) {
375 						// label is truncated, set tooltip
376 						SetToolTip(windowMenuItem->Label());
377 					} else
378 						SetToolTip((const char*)NULL);
379 
380 					break;
381 				}
382 
383 				if (!dynamic_cast<TBarApp*>(be_app)->Settings()->hideLabels) {
384 					// item has a visible label, set tool tip if truncated
385 					fLastMousedOverItem = menuItem;
386 					if (strcasecmp(item->TruncatedLabel(), item->Label()) > 0) {
387 						// label is truncated, set tooltip
388 						SetToolTip(item->Label());
389 					} else
390 						SetToolTip((const char*)NULL);
391 
392 					break;
393 				}
394 
395 				SetToolTip(item->Label());
396 					// new item, set the tooltip to the item label
397 				fLastMousedOverItem = menuItem;
398 					// save the current menuitem for the next MouseMoved() call
399 				break;
400 			}
401 		}
402 
403 		BMenuBar::MouseMoved(where, code, message);
404 		return;
405 	}
406 
407 	if (buttons == 0)
408 		return;
409 
410 	switch (code) {
411 		case B_ENTERED_VIEW:
412 			// fPreviousDragTargetItem should always be NULL here anyways.
413 			if (fPreviousDragTargetItem != NULL)
414 				_FinishedDrag();
415 
416 			fBarView->CacheDragData(message);
417 			fPreviousDragTargetItem = NULL;
418 			break;
419 
420 		case B_OUTSIDE_VIEW:
421 			// NOTE: Should not be here, but for the sake of defensive
422 			// programming... fall-through
423 		case B_EXITED_VIEW:
424 			_FinishedDrag();
425 			break;
426 
427 		case B_INSIDE_VIEW:
428 			if (fBarView->Dragging()) {
429 				TTeamMenuItem* item = NULL;
430 				int32 itemCount = CountItems();
431 				for (int32 i = 0; i < itemCount; i++) {
432 					BMenuItem* _item = ItemAt(i);
433 					if (_item->Frame().Contains(where)) {
434 						item = dynamic_cast<TTeamMenuItem*>(_item);
435 						break;
436 					}
437 				}
438 				if (item == fPreviousDragTargetItem)
439 					break;
440 				if (fPreviousDragTargetItem != NULL)
441 					fPreviousDragTargetItem->SetOverrideSelected(false);
442 				if (item != NULL)
443 					item->SetOverrideSelected(true);
444 				fPreviousDragTargetItem = item;
445 			}
446 			break;
447 	}
448 }
449 
450 
451 void
452 TExpandoMenuBar::MouseUp(BPoint where)
453 {
454 	if (fBarView->Dragging()) {
455 		_FinishedDrag(true);
456 		return;
457 			// absorb the message
458 	}
459 
460 	BMenuBar::MouseUp(where);
461 }
462 
463 
464 void
465 TExpandoMenuBar::BuildItems()
466 {
467 	BMessenger self(this);
468 	TBarApp::Subscribe(self, &fTeamList);
469 
470 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
471 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
472 
473 	float itemWidth = -1.0f;
474 	if (fVertical) {
475 		itemWidth = Frame().Width();
476 		SetMaxContentWidth(itemWidth);
477 	} else {
478 		itemWidth = iconSize;
479 		if (!settings->hideLabels)
480 			itemWidth += gMinimumWindowWidth - kMinimumIconSize;
481 		else
482 			itemWidth += kIconPadding * 2;
483 	}
484 	float itemHeight = -1.0f;
485 
486 	TeamMenuItemMap items;
487 	int32 itemCount = CountItems();
488 	BList itemList(itemCount);
489 	for (int32 i = 0; i < itemCount; i++) {
490 		BMenuItem* menuItem = RemoveItem((int32)0);
491 		itemList.AddItem(menuItem);
492 		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem);
493 		if (item != NULL)
494 			items[BString(item->Signature()).ToLower()] = item;
495 	}
496 
497 	if (settings->sortRunningApps)
498 		fTeamList.SortItems(TTeamMenu::CompareByName);
499 
500 	int32 teamCount = fTeamList.CountItems();
501 	for (int32 i = 0; i < teamCount; i++) {
502 		BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i);
503 		TeamMenuItemMap::const_iterator iter
504 			= items.find(BString(barInfo->sig).ToLower());
505 		if (iter == items.end()) {
506 			// new team
507 			TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams,
508 				barInfo->icon, barInfo->name, barInfo->sig, itemWidth,
509 				itemHeight);
510 
511 			if (settings->trackerAlwaysFirst
512 				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
513 				AddItem(item, 0);
514 			} else
515 				AddItem(item);
516 
517 			if (fFirstBuild && fVertical && settings->expandNewTeams)
518 				item->ToggleExpandState(true);
519 		} else {
520 			// existing team, update info and add it
521 			TTeamMenuItem* item = iter->second;
522 			item->SetIcon(barInfo->icon);
523 			item->SetOverrideWidth(itemWidth);
524 			item->SetOverrideHeight(itemHeight);
525 
526 			if (settings->trackerAlwaysFirst
527 				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
528 				AddItem(item, 0);
529 			} else
530 				AddItem(item);
531 
532 			// add window items back
533 			int32 index = itemList.IndexOf(item);
534 			TWindowMenuItem* windowItem;
535 			TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu());
536 			bool hasWindowItems = false;
537 			while ((windowItem = dynamic_cast<TWindowMenuItem*>(
538 					(BMenuItem*)(itemList.ItemAt(++index)))) != NULL) {
539 				if (fVertical)
540 					AddItem(windowItem);
541 				else {
542 					delete windowItem;
543 					hasWindowItems = submenu != NULL;
544 				}
545 			}
546 
547 			// unexpand if turn off show team expander
548 			if (fVertical && !settings->superExpando && item->IsExpanded())
549 				item->ToggleExpandState(false);
550 
551 			if (hasWindowItems) {
552 				// add (new) window items in submenu
553 				submenu->SetExpanded(false, 0);
554 				submenu->AttachedToWindow();
555 			}
556 		}
557 	}
558 
559 	if (CountItems() == 0) {
560 		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
561 		// weird value - we just override it again
562 		ResizeTo(itemWidth, 0);
563 	}
564 
565 	fFirstBuild = false;
566 }
567 
568 
569 bool
570 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const
571 {
572 	TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
573 	if (window != NULL) {
574 		if (TDeskbarMenu* bemenu = window->DeskbarMenu()) {
575 			bool inDeskbarMenu = false;
576 			if (bemenu->LockLooper()) {
577 				inDeskbarMenu = bemenu->Frame().Contains(loc);
578 				bemenu->UnlockLooper();
579 			}
580 			return inDeskbarMenu;
581 		}
582 	}
583 
584 	return false;
585 }
586 
587 
588 /*!	Returns the team menu item that belongs to the item under the
589 	specified \a point.
590 	If \a _item is given, it will return the exact menu item under
591 	that point (which might be a window item when the expander is on).
592 */
593 TTeamMenuItem*
594 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
595 {
596 	TTeamMenuItem* lastApp = NULL;
597 	int32 count = CountItems();
598 
599 	for (int32 i = 0; i < count; i++) {
600 		BMenuItem* item = ItemAt(i);
601 
602 		if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
603 			lastApp = (TTeamMenuItem*)item;
604 
605 		if (item && item->Frame().Contains(point)) {
606 			if (_item != NULL)
607 				*_item = item;
608 
609 			return lastApp;
610 		}
611 	}
612 
613 	// no item found
614 
615 	if (_item != NULL)
616 		*_item = NULL;
617 
618 	return NULL;
619 }
620 
621 
622 void
623 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name,
624 	char* signature)
625 {
626 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
627 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
628 
629 	float itemWidth = -1.0f;
630 	if (fVertical)
631 		itemWidth = fBarView->Bounds().Width();
632 	else {
633 		itemWidth = iconSize;
634 		if (!settings->hideLabels)
635 			itemWidth += gMinimumWindowWidth - kMinimumIconSize;
636 		else
637 			itemWidth += kIconPadding * 2;
638 	}
639 	float itemHeight = -1.0f;
640 
641 	TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature,
642 		itemWidth, itemHeight);
643 
644 	if (settings->trackerAlwaysFirst
645 		&& strcasecmp(signature, kTrackerSignature) == 0) {
646 		AddItem(item, 0);
647 	} else if (settings->sortRunningApps) {
648 		TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(0));
649 		int32 firstApp = 0;
650 
651 		// if Tracker should always be the first item, we need to skip it
652 		// when sorting in the current item
653 		if (settings->trackerAlwaysFirst && teamItem != NULL
654 			&& strcasecmp(teamItem->Signature(), kTrackerSignature) == 0) {
655 			firstApp++;
656 		}
657 
658 		BCollator collator;
659 		BLocale::Default()->GetCollator(&collator);
660 
661 		int32 i = firstApp;
662 		int32 itemCount = CountItems();
663 		while (i < itemCount) {
664 			teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
665 			if (teamItem != NULL && collator.Compare(teamItem->Label(), name)
666 					> 0) {
667 				AddItem(item, i);
668 				break;
669 			}
670 			i++;
671 		}
672 		// was the item added to the list yet?
673 		if (i == itemCount)
674 			AddItem(item);
675 	} else
676 		AddItem(item);
677 
678 	if (fVertical && settings->superExpando && settings->expandNewTeams)
679 		item->ToggleExpandState(false);
680 
681 	SizeWindow(1);
682 	Window()->UpdateIfNeeded();
683 }
684 
685 
686 void
687 TExpandoMenuBar::AddTeam(team_id team, const char* signature)
688 {
689 	int32 itemCount = CountItems();
690 	for (int32 i = 0; i < itemCount; i++) {
691 		// Only add to team menu items
692 		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
693 		if (item != NULL && strcasecmp(item->Signature(), signature) == 0
694 			&& !(item->Teams()->HasItem((void*)(addr_t)team))) {
695 			item->Teams()->AddItem((void*)(addr_t)team);
696 			break;
697 		}
698 	}
699 }
700 
701 
702 void
703 TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
704 {
705 	TWindowMenuItem* windowItem = NULL;
706 
707 	for (int32 i = CountItems() - 1; i >= 0; i--) {
708 		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
709 		if (item != NULL && item->Teams()->HasItem((void*)(addr_t)team)) {
710 			item->Teams()->RemoveItem(team);
711 			if (partial)
712 				return;
713 
714 			BAutolock locker(sMonLocker);
715 				// make the update thread wait
716 			RemoveItem(i);
717 			if (item == fPreviousDragTargetItem)
718 				fPreviousDragTargetItem = NULL;
719 
720 			if (item == fLastMousedOverItem)
721 				fLastMousedOverItem = NULL;
722 
723 			if (item == fLastClickedItem)
724 				fLastClickedItem = NULL;
725 
726 			delete item;
727 			while ((windowItem = dynamic_cast<TWindowMenuItem*>(
728 					ItemAt(i))) != NULL) {
729 				// Also remove window items (if there are any)
730 				RemoveItem(i);
731 				if (windowItem == fLastMousedOverItem)
732 					fLastMousedOverItem = NULL;
733 
734 				if (windowItem == fLastClickedItem)
735 					fLastClickedItem = NULL;
736 
737 				delete windowItem;
738 			}
739 			SizeWindow(-1);
740 			Window()->UpdateIfNeeded();
741 			return;
742 		}
743 	}
744 }
745 
746 
747 void
748 TExpandoMenuBar::CheckItemSizes(int32 delta)
749 {
750 	if (fBarView->Vertical())
751 		return;
752 
753 	bool drawLabels = !static_cast<TBarApp*>(be_app)->Settings()->hideLabels;
754 
755 	float maxWidth = fBarView->DragRegion()->Frame().left
756 		- fDeskbarMenuWidth - kSepItemWidth;
757 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
758 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
759 	float minItemWidth = drawLabels
760 		? iconOnlyWidth + kMinMenuItemWidth
761 		: iconOnlyWidth - kIconPadding;
762 	float maxItemWidth = drawLabels
763 		? gMinimumWindowWidth + iconSize - kMinimumIconSize
764 		: iconOnlyWidth;
765 	float menuWidth = maxItemWidth * CountItems() + fDeskbarMenuWidth
766 		+ kSepItemWidth;
767 
768 	bool reset = false;
769 	float newWidth = -1.0f;
770 
771 	if (delta >= 0 && menuWidth > maxWidth) {
772 		fOverflow = true;
773 		reset = true;
774 		newWidth = floorf(maxWidth / CountItems());
775 	} else if (delta < 0 && fOverflow) {
776 		reset = true;
777 		if (menuWidth > maxWidth)
778 			newWidth = floorf(maxWidth / CountItems());
779 		else
780 			newWidth = maxItemWidth;
781 	}
782 
783 	if (reset) {
784 		if (newWidth > maxItemWidth)
785 			newWidth = maxItemWidth;
786 		else if (newWidth < minItemWidth)
787 			newWidth = minItemWidth;
788 
789 		SetMaxContentWidth(newWidth);
790 		if (newWidth == maxItemWidth)
791 			fOverflow = false;
792 
793 		InvalidateLayout();
794 
795 		for (int32 index = 0; ; index++) {
796 			TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index);
797 			if (item == NULL)
798 				break;
799 
800 			item->SetOverrideWidth(newWidth);
801 		}
802 
803 		Invalidate();
804 		Window()->UpdateIfNeeded();
805 		fBarView->CheckForScrolling();
806 	}
807 }
808 
809 
810 menu_layout
811 TExpandoMenuBar::MenuLayout() const
812 {
813 	return Layout();
814 }
815 
816 
817 void
818 TExpandoMenuBar::SetMenuLayout(menu_layout layout)
819 {
820 	fVertical = layout == B_ITEMS_IN_COLUMN;
821 	BPrivate::MenuPrivate(this).SetLayout(layout);
822 	SetMaxItemWidth();
823 		// when the menu layout changes, make sure to set the max width
824 }
825 
826 
827 void
828 TExpandoMenuBar::Draw(BRect updateRect)
829 {
830 	BMenu::Draw(updateRect);
831 }
832 
833 
834 void
835 TExpandoMenuBar::DrawBackground(BRect updateRect)
836 {
837 	if (fVertical)
838 		return;
839 
840 	BRect bounds(Bounds());
841 	rgb_color menuColor = ui_color(B_MENU_BACKGROUND_COLOR);
842 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
843 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
844 
845 	int32 count = CountItems() - 1;
846 	if (count >= 0)
847 		bounds.left = ItemAt(count)->Frame().right + 1;
848 	else
849 		bounds.left = 0;
850 
851 	if (be_control_look != NULL) {
852 		SetHighColor(tint_color(menuColor, 1.22));
853 		StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
854 		bounds.left++;
855 		uint32 borders = BControlLook::B_TOP_BORDER
856 			| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
857 
858 		be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
859 			0, borders);
860 	} else {
861 		SetHighColor(vlight);
862 		StrokeLine(bounds.LeftTop(), bounds.RightTop());
863 		StrokeLine(BPoint(bounds.left, bounds.top + 1), bounds.LeftBottom());
864 		SetHighColor(hilite);
865 		StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
866 			bounds.RightBottom());
867 	}
868 }
869 
870 
871 /*!	Something to help determine if we are showing too many apps
872 	need to add in scrolling functionality.
873 */
874 bool
875 TExpandoMenuBar::CheckForSizeOverrun()
876 {
877 	if (fVertical) {
878 		if (Window() == NULL)
879 			return false;
880 
881 		BRect screenFrame = (BScreen(Window())).Frame();
882 		return Window()->Frame().bottom > screenFrame.bottom;
883 	}
884 
885 	// horizontal
886 	int32 count = CountItems() - 1;
887 	if (count < 0)
888 		return false;
889 
890 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
891 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
892 	float minItemWidth = !static_cast<TBarApp*>(be_app)->Settings()->hideLabels
893 		? iconOnlyWidth + kMinMenuItemWidth
894 		: iconOnlyWidth - kIconPadding;
895 	float menuWidth = minItemWidth * CountItems() + fDeskbarMenuWidth
896 		+ kSepItemWidth;
897 	float maxWidth = fBarView->DragRegion()->Frame().left
898 		- fDeskbarMenuWidth - kSepItemWidth;
899 
900 	return menuWidth > maxWidth;
901 }
902 
903 
904 void
905 TExpandoMenuBar::SetMaxItemWidth()
906 {
907 	if (fVertical)
908 		SetMaxContentWidth(static_cast<TBarApp*>(be_app)->Settings()->width);
909 	else {
910 		// Make more room for the icon in horizontal mode
911 		int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
912 		SetMaxContentWidth(gMinimumWindowWidth + iconSize
913 			- kMinimumIconSize);
914 	}
915 }
916 
917 
918 void
919 TExpandoMenuBar::SizeWindow(int32 delta)
920 {
921 	// instead of resizing the window here and there in the
922 	// code the resize method will be centered in one place
923 	// thus, the same behavior (good or bad) will be used
924 	// wherever window sizing is done
925 	if (fVertical) {
926 		BRect screenFrame = (BScreen(Window())).Frame();
927 		fBarView->SizeWindow(screenFrame);
928 		fBarView->PositionWindow(screenFrame);
929 		fBarView->CheckForScrolling();
930 	} else
931 		CheckItemSizes(delta);
932 }
933 
934 
935 void
936 TExpandoMenuBar::StartMonitoringWindows()
937 {
938 	if (sMonThread != B_ERROR)
939 		return;
940 
941 	sDoMonitor = true;
942 	sMonThread = spawn_thread(monitor_team_windows,
943 		"Expando Window Watcher", B_LOW_PRIORITY, this);
944 	resume_thread(sMonThread);
945 }
946 
947 
948 void
949 TExpandoMenuBar::StopMonitoringWindows()
950 {
951 	if (sMonThread == B_ERROR)
952 		return;
953 
954 	sDoMonitor = false;
955 	status_t returnCode;
956 	wait_for_thread(sMonThread, &returnCode);
957 
958 	sMonThread = B_ERROR;
959 }
960 
961 
962 int32
963 TExpandoMenuBar::monitor_team_windows(void* arg)
964 {
965 	TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg;
966 
967 	while (teamMenu->sDoMonitor) {
968 		sMonLocker.Lock();
969 
970 		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
971 			int32 totalItems = teamMenu->CountItems();
972 
973 			// Set all WindowMenuItems to require an update.
974 			TWindowMenuItem* item = NULL;
975 			for (int32 i = 0; i < totalItems; i++) {
976 				if (!teamMenu->SubmenuAt(i)) {
977 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
978 					item->SetRequireUpdate(true);
979 				}
980 			}
981 
982 			// Perform SetTo() on all the items that still exist as well as add
983 			// new items.
984 			bool itemModified = false;
985 			bool resize = false;
986 			TTeamMenuItem* teamItem = NULL;
987 
988 			for (int32 i = 0; i < totalItems; i++) {
989 				if (teamMenu->SubmenuAt(i) == NULL)
990 					continue;
991 
992 				teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i));
993 				if (teamItem->IsExpanded()) {
994 					int32 teamCount = teamItem->Teams()->CountItems();
995 					for (int32 j = 0; j < teamCount; j++) {
996 						// The following code is almost a copy/paste from
997 						// WindowMenu.cpp
998 						team_id theTeam = (addr_t)teamItem->Teams()->ItemAt(j);
999 						int32 count = 0;
1000 						int32* tokens = get_token_list(theTeam, &count);
1001 
1002 						for (int32 k = 0; k < count; k++) {
1003 							client_window_info* wInfo
1004 								= get_window_info(tokens[k]);
1005 							if (wInfo == NULL)
1006 								continue;
1007 
1008 							BString windowName(wInfo->name);
1009 
1010 							BString teamPrefix(teamItem->Label());
1011 							teamPrefix.Append(": ");
1012 
1013 							BString teamSuffix(" - ");
1014 							teamSuffix.Append(teamItem->Label());
1015 
1016 							if (windowName.StartsWith(teamPrefix))
1017 								windowName.RemoveFirst(teamPrefix);
1018 							if (windowName.EndsWith(teamSuffix))
1019 								windowName.RemoveLast(teamSuffix);
1020 
1021 							if (TWindowMenu::WindowShouldBeListed(wInfo)) {
1022 								// Check if we have a matching window item...
1023 								item = teamItem->ExpandedWindowItem(
1024 									wInfo->server_token);
1025 								if (item != NULL) {
1026 									item->SetTo(windowName,
1027 										wInfo->server_token, wInfo->is_mini,
1028 										((1 << current_workspace())
1029 											& wInfo->workspaces) != 0);
1030 
1031 									if (strcasecmp(item->Label(), windowName) > 0)
1032 										item->SetLabel(windowName);
1033 
1034 									if (item->Modified())
1035 										itemModified = true;
1036 								} else if (teamItem->IsExpanded()) {
1037 									// Add the item
1038 									item = new TWindowMenuItem(windowName,
1039 										wInfo->server_token, wInfo->is_mini,
1040 										((1 << current_workspace())
1041 											& wInfo->workspaces) != 0, false);
1042 									item->SetExpanded(true);
1043 									teamMenu->AddItem(item,
1044 										TWindowMenuItem::InsertIndexFor(
1045 											teamMenu, i + 1, item));
1046 									resize = true;
1047 								}
1048 							}
1049 							free(wInfo);
1050 						}
1051 						free(tokens);
1052 					}
1053 				}
1054 			}
1055 
1056 			// Remove any remaining items which require an update.
1057 			for (int32 i = 0; i < totalItems; i++) {
1058 				if (!teamMenu->SubmenuAt(i)) {
1059 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
1060 					if (item && item->RequiresUpdate()) {
1061 						item = static_cast<TWindowMenuItem*>
1062 							(teamMenu->RemoveItem(i));
1063 						delete item;
1064 						totalItems--;
1065 
1066 						resize = true;
1067 					}
1068 				}
1069 			}
1070 
1071 			// If any of the WindowMenuItems changed state, we need to force a
1072 			// repaint.
1073 			if (itemModified || resize) {
1074 				teamMenu->Invalidate();
1075 				if (resize)
1076 					teamMenu->SizeWindow(1);
1077 			}
1078 
1079 			teamMenu->Window()->Unlock();
1080 		}
1081 
1082 		sMonLocker.Unlock();
1083 
1084 		// sleep for a bit...
1085 		snooze(150000);
1086 	}
1087 	return B_OK;
1088 }
1089 
1090 
1091 void
1092 TExpandoMenuBar::_FinishedDrag(bool invoke)
1093 {
1094 	if (fPreviousDragTargetItem != NULL) {
1095 		if (invoke)
1096 			fPreviousDragTargetItem->Invoke();
1097 
1098 		fPreviousDragTargetItem->SetOverrideSelected(false);
1099 		fPreviousDragTargetItem = NULL;
1100 	}
1101 
1102 	if (!invoke && fBarView->Dragging())
1103 		fBarView->DragStop(true);
1104 }
1105 
1106 
1107 void
1108 TExpandoMenuBar::_DoneTracking(BPoint where)
1109 {
1110 	TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
1111 	if (lastItem == NULL)
1112 		return;
1113 
1114 	if (!lastItem->ExpanderBounds().Contains(where))
1115 		return;
1116 
1117 	lastItem->ToggleExpandState(true);
1118 	lastItem->SetArrowDirection(lastItem->IsExpanded()
1119 		? BControlLook::B_DOWN_ARROW
1120 		: BControlLook::B_RIGHT_ARROW);
1121 
1122 	Invalidate(lastItem->ExpanderBounds());
1123 }
1124 
1125 
1126 void
1127 TExpandoMenuBar::_Track(BPoint where, uint32)
1128 {
1129 	TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
1130 	if (lastItem == NULL)
1131 		return;
1132 
1133 	if (lastItem->ExpanderBounds().Contains(where))
1134 		lastItem->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW);
1135 	else {
1136 		lastItem->SetArrowDirection(lastItem->IsExpanded()
1137 			? BControlLook::B_DOWN_ARROW
1138 			: BControlLook::B_RIGHT_ARROW);
1139 	}
1140 
1141 	Invalidate(lastItem->ExpanderBounds());
1142 }
1143