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