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