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