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