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