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