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