xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision 362f1bd35b70e0c904bacb00f4708fbd6bc0173c)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30 trademarks of Be Incorporated in the United States and other countries. Other
31 brand product names are registered trademarks or trademarks of their respective
32 holders.
33 All rights reserved.
34 */
35 
36 
37 #include "ExpandoMenuBar.h"
38 
39 #include <strings.h>
40 
41 #include <map>
42 
43 #include <Autolock.h>
44 #include <Bitmap.h>
45 #include <Collator.h>
46 #include <ControlLook.h>
47 #include <Debug.h>
48 #include <MenuPrivate.h>
49 #include <MessengerPrivate.h>
50 #include <NodeInfo.h>
51 #include <Roster.h>
52 #include <Screen.h>
53 #include <Thread.h>
54 #include <Window.h>
55 
56 #include "icons.h"
57 
58 #include "BarApp.h"
59 #include "BarMenuTitle.h"
60 #include "BarView.h"
61 #include "BarWindow.h"
62 #include "DeskbarMenu.h"
63 #include "DeskbarUtils.h"
64 #include "InlineScrollView.h"
65 #include "ResourceSet.h"
66 #include "ShowHideMenuItem.h"
67 #include "StatusView.h"
68 #include "TeamMenu.h"
69 #include "TeamMenuItem.h"
70 #include "WindowMenu.h"
71 #include "WindowMenuItem.h"
72 
73 
74 const float kIconPadding = 8.0f;
75 const float kDeskbarMenuWidth = gMinimumWindowWidth / 2 + kIconPadding;
76 
77 const uint32 kMinimizeTeam = 'mntm';
78 const uint32 kBringTeamToFront = 'bftm';
79 
80 bool TExpandoMenuBar::sDoMonitor = false;
81 thread_id TExpandoMenuBar::sMonThread = B_ERROR;
82 BLocker TExpandoMenuBar::sMonLocker("expando monitor");
83 
84 typedef std::map<BString, TTeamMenuItem*> TeamMenuItemMap;
85 
86 
87 //	#pragma mark - TExpandoMenuBar
88 
89 
90 TExpandoMenuBar::TExpandoMenuBar(menu_layout layout, TBarView* barView)
91 	:
92 	BMenuBar(BRect(0, 0, 0, 0), "ExpandoMenuBar", B_FOLLOW_NONE, layout),
93 	fBarView(barView),
94 	fOverflow(false),
95 	fUnderflow(false),
96 	fFirstBuild(true),
97 	fPreviousDragTargetItem(NULL),
98 	fLastMousedOverItem(NULL),
99 	fLastClickedItem(NULL)
100 {
101 	SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f);
102 	SetFont(be_plain_font);
103 }
104 
105 
106 void
107 TExpandoMenuBar::AllAttached()
108 {
109 	BMenuBar::AllAttached();
110 
111 	SizeWindow(0);
112 }
113 
114 
115 void
116 TExpandoMenuBar::AttachedToWindow()
117 {
118 	BMenuBar::AttachedToWindow();
119 
120 	fTeamList.MakeEmpty();
121 
122 	if (Vertical())
123 		StartMonitoringWindows();
124 }
125 
126 
127 void
128 TExpandoMenuBar::DetachedFromWindow()
129 {
130 	BMenuBar::DetachedFromWindow();
131 
132 	StopMonitoringWindows();
133 
134 	BMessenger self(this);
135 	BMessage message(kUnsubscribe);
136 	message.AddMessenger("messenger", self);
137 	be_app->PostMessage(&message);
138 
139 	RemoveItems(0, CountItems(), true);
140 }
141 
142 
143 void
144 TExpandoMenuBar::MessageReceived(BMessage* message)
145 {
146 	int32 index;
147 	TTeamMenuItem* item;
148 
149 	switch (message->what) {
150 		case B_SOME_APP_LAUNCHED:
151 		{
152 			BList* teams = NULL;
153 			message->FindPointer("teams", (void**)&teams);
154 
155 			BBitmap* icon = NULL;
156 			message->FindPointer("icon", (void**)&icon);
157 
158 			const char* signature = NULL;
159 			message->FindString("sig", &signature);
160 
161 			uint32 flags = 0;
162 			message->FindInt32("flags", ((int32*) &flags));
163 
164 			const char* name = NULL;
165 			message->FindString("name", &name);
166 
167 			AddTeam(teams, icon, strdup(name), strdup(signature));
168 			break;
169 		}
170 
171 		case B_MOUSE_WHEEL_CHANGED:
172 		{
173 			float deltaY = 0;
174 			message->FindFloat("be:wheel_delta_y", &deltaY);
175 			if (deltaY == 0)
176 				return;
177 
178 			TInlineScrollView* scrollView
179 				= dynamic_cast<TInlineScrollView*>(Parent());
180 			if (scrollView == NULL || !scrollView->HasScrollers())
181 				return;
182 
183 			float largeStep;
184 			float smallStep;
185 			scrollView->GetSteps(&smallStep, &largeStep);
186 
187 			// pressing the option/command/control key scrolls faster
188 			if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY))
189 				deltaY *= largeStep;
190 			else
191 				deltaY *= smallStep;
192 
193 			scrollView->ScrollBy(deltaY);
194 			break;
195 		}
196 
197 		case kAddTeam:
198 			AddTeam(message->FindInt32("team"), message->FindString("sig"));
199 			break;
200 
201 		case kRemoveTeam:
202 		{
203 			team_id team = -1;
204 			message->FindInt32("team", &team);
205 
206 			RemoveTeam(team, true);
207 			break;
208 		}
209 
210 		case B_SOME_APP_QUIT:
211 		{
212 			team_id team = -1;
213 			message->FindInt32("team", &team);
214 
215 			RemoveTeam(team, false);
216 			break;
217 		}
218 
219 		case kMinimizeTeam:
220 		{
221 			index = message->FindInt32("itemIndex");
222 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
223 			if (item == NULL)
224 				break;
225 
226 			TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW,
227 				item->Teams(),
228 				item->Menu()->ConvertToScreen(item->Frame()),
229 				true);
230 			break;
231 		}
232 
233 		case kBringTeamToFront:
234 		{
235 			index = message->FindInt32("itemIndex");
236 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
237 			if (item == NULL)
238 				break;
239 
240 			TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT,
241 				item->Teams(), item->Menu()->ConvertToScreen(item->Frame()),
242 				true);
243 			break;
244 		}
245 
246 		default:
247 			BMenuBar::MessageReceived(message);
248 			break;
249 	}
250 }
251 
252 
253 void
254 TExpandoMenuBar::MouseDown(BPoint where)
255 {
256 	BMessage* message = Window()->CurrentMessage();
257 	BMenuItem* menuItem;
258 	TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem);
259 
260 	if (message == NULL || item == NULL || fBarView == NULL
261 		|| fBarView->Dragging()) {
262 		BMenuBar::MouseDown(where);
263 		return;
264 	}
265 
266 	int32 modifiers = 0;
267 	int32 buttons = 0;
268 	message->FindInt32("modifiers", &modifiers);
269 	message->FindInt32("buttons", &buttons);
270 
271 	// check for three finger salute, a.k.a. Vulcan Death Grip
272 	if ((modifiers & B_COMMAND_KEY) != 0
273 		&& (modifiers & B_CONTROL_KEY) != 0
274 		&& (modifiers & B_SHIFT_KEY) != 0) {
275 		const BList* teams = item->Teams();
276 		int32 teamCount = teams->CountItems();
277 		team_id teamID;
278 		for (int32 team = 0; team < teamCount; team++) {
279 			teamID = (addr_t)teams->ItemAt(team);
280 			kill_team(teamID);
281 			RemoveTeam(teamID, false);
282 				// remove the team from display immediately
283 		}
284 		return;
285 			// absorb the message
286 	} else if (item != NULL
287 		&& (modifiers & B_SHIFT_KEY) == 0
288 		&& (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
289 		be_roster->Launch(item->Signature());
290 		return;
291 			// absorb the message
292 	} else {
293 		TWindowMenuItem* wndItem = dynamic_cast<TWindowMenuItem*>(menuItem);
294 		if (wndItem != NULL
295 			&& (modifiers & B_SHIFT_KEY) != 0
296 			&& (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
297 			// close window
298 			client_window_info* info;
299 			BMessenger wnd;
300 			info = get_window_info(wndItem->ID());
301 			if (info == NULL) return;
302 			BMessenger::Private(wnd).SetTo(
303 				info->team, info->client_port, info->client_token);
304 			free(info); info = NULL;
305 			wnd.SendMessage(B_QUIT_REQUESTED);
306 			return;
307 				// absorb the message
308 		}
309 	}
310 
311 	// control click - show all/hide all shortcut
312 	if ((modifiers & B_CONTROL_KEY) != 0) {
313 		// show/hide item's teams
314 		BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
315 			? kMinimizeTeam : kBringTeamToFront);
316 		showMessage.AddInt32("itemIndex", IndexOf(item));
317 		Window()->PostMessage(&showMessage, this);
318 		return;
319 			// absorb the message
320 	}
321 
322 	// check if within expander bounds to expand window items
323 	if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando
324 		&& item->ExpanderBounds().Contains(where)) {
325 		// start the animation here, finish on mouse up
326 		fLastClickedItem = item;
327 		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 	fLastClickedItem = item;
345 	BMenuBar::MouseDown(where);
346 }
347 
348 
349 void
350 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message)
351 {
352 	int32 buttons;
353 	BMessage* currentMessage = Window()->CurrentMessage();
354 	if (currentMessage == NULL
355 		|| currentMessage->FindInt32("buttons", &buttons) != B_OK) {
356 		buttons = 0;
357 	}
358 
359 	if (message == NULL) {
360 		// force a cleanup
361 		_FinishedDrag();
362 
363 		if (buttons != 0) {
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 
377 			Invalidate(lastItem->ExpanderBounds());
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 	if (fBarView != NULL && fBarView->Dragging()) {
486 		_FinishedDrag(true);
487 		return;
488 			// absorb the message
489 	}
490 
491 	TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
492 	if (lastItem != NULL && lastItem->ExpanderBounds().Contains(where)) {
493 		lastItem->ToggleExpandState(true);
494 		lastItem->SetArrowDirection(lastItem->IsExpanded()
495 			? BControlLook::B_DOWN_ARROW
496 			: BControlLook::B_RIGHT_ARROW);
497 
498 		Invalidate(lastItem->ExpanderBounds());
499 	}
500 	BMenuBar::MouseUp(where);
501 }
502 
503 
504 void
505 TExpandoMenuBar::BuildItems()
506 {
507 	BMessenger self(this);
508 	TBarApp::Subscribe(self, &fTeamList);
509 
510 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
511 
512 	float itemWidth = -1.0f;
513 	if (Vertical() && (fBarView->ExpandoState() || fBarView->FullState())) {
514 		itemWidth = settings->width;
515 	}
516 	SetMaxContentWidth(itemWidth);
517 
518 	TeamMenuItemMap items;
519 	int32 itemCount = CountItems();
520 	BList itemList(itemCount);
521 	for (int32 i = 0; i < itemCount; i++) {
522 		BMenuItem* menuItem = RemoveItem((int32)0);
523 		itemList.AddItem(menuItem);
524 		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem);
525 		if (item != NULL)
526 			items[BString(item->Signature()).ToLower()] = item;
527 	}
528 
529 	if (settings->sortRunningApps)
530 		fTeamList.SortItems(TTeamMenu::CompareByName);
531 
532 	int32 teamCount = fTeamList.CountItems();
533 	for (int32 i = 0; i < teamCount; i++) {
534 		BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i);
535 		TeamMenuItemMap::const_iterator iter
536 			= items.find(BString(barInfo->sig).ToLower());
537 		if (iter == items.end()) {
538 			// new team
539 			TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams,
540 				barInfo->icon, barInfo->name, barInfo->sig, itemWidth);
541 
542 			if (settings->trackerAlwaysFirst
543 				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
544 				AddItem(item, 0);
545 			} else
546 				AddItem(item);
547 
548 			if (fFirstBuild && Vertical() && settings->expandNewTeams)
549 				item->ToggleExpandState(true);
550 		} else {
551 			// existing team, update info and add it
552 			TTeamMenuItem* item = iter->second;
553 			item->SetIcon(barInfo->icon);
554 			item->SetOverrideWidth(itemWidth);
555 
556 			if (settings->trackerAlwaysFirst
557 				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
558 				AddItem(item, 0);
559 			} else
560 				AddItem(item);
561 
562 			// add window items back
563 			int32 index = itemList.IndexOf(item);
564 			TWindowMenuItem* windowItem;
565 			TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu());
566 			bool hasWindowItems = false;
567 			while ((windowItem = dynamic_cast<TWindowMenuItem*>(
568 					(BMenuItem*)(itemList.ItemAt(++index)))) != NULL) {
569 				if (Vertical())
570 					AddItem(windowItem);
571 				else {
572 					delete windowItem;
573 					hasWindowItems = submenu != NULL;
574 				}
575 			}
576 
577 			// unexpand if turn off show team expander
578 			if (Vertical() && !settings->superExpando && item->IsExpanded())
579 				item->ToggleExpandState(false);
580 
581 			if (hasWindowItems) {
582 				// add (new) window items in submenu
583 				submenu->SetExpanded(false, 0);
584 				submenu->AttachedToWindow();
585 			}
586 		}
587 	}
588 
589 	if (CountItems() == 0) {
590 		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
591 		// weird value - we just override it again
592 		ResizeTo(gMinimumWindowWidth, 0);
593 	} else {
594 		// first build isn't complete until we've gotten here with an item
595 		fFirstBuild = false;
596 	}
597 }
598 
599 
600 bool
601 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const
602 {
603 	TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
604 	if (window != NULL) {
605 		if (TDeskbarMenu* bemenu = window->DeskbarMenu()) {
606 			bool inDeskbarMenu = false;
607 			if (bemenu->LockLooper()) {
608 				inDeskbarMenu = bemenu->Frame().Contains(loc);
609 				bemenu->UnlockLooper();
610 			}
611 			return inDeskbarMenu;
612 		}
613 	}
614 
615 	return false;
616 }
617 
618 
619 /*!	Returns the team menu item that belongs to the item under the
620 	specified \a point.
621 	If \a _item is given, it will return the exact menu item under
622 	that point (which might be a window item when the expander is on).
623 */
624 TTeamMenuItem*
625 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
626 {
627 	TTeamMenuItem* lastApp = NULL;
628 	int32 itemCount = CountItems();
629 
630 	for (int32 i = 0; i < itemCount; i++) {
631 		BMenuItem* item = ItemAt(i);
632 
633 		if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
634 			lastApp = (TTeamMenuItem*)item;
635 
636 		if (item && item->Frame().Contains(point)) {
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 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
830 	float iconOnlyWidth = iconSize + kIconPadding;
831 
832 	return static_cast<TBarApp*>(be_app)->Settings()->hideLabels
833 		? iconOnlyWidth
834 		: (iconSize - kMinimumIconSize) + gMinimumWindowWidth
835 			+ (be_plain_font->Size() - 12) * 4;
836 }
837 
838 
839 float
840 TExpandoMenuBar::MaxHorizontalItemWidth()
841 {
842 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
843 	float iconOnlyWidth = iconSize + kIconPadding;
844 
845 	// hide labels
846 	if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels)
847 		return iconOnlyWidth + kIconPadding; // add an extra icon padding
848 
849 	// set max item width to 1.25x min item width
850 	return floorf(MinHorizontalItemWidth() * 1.25);
851 }
852 
853 
854 menu_layout
855 TExpandoMenuBar::MenuLayout() const
856 {
857 	return Layout();
858 }
859 
860 
861 void
862 TExpandoMenuBar::SetMenuLayout(menu_layout layout)
863 {
864 	BPrivate::MenuPrivate(this).SetLayout(layout);
865 	InvalidateLayout();
866 }
867 
868 
869 void
870 TExpandoMenuBar::Draw(BRect updateRect)
871 {
872 	BMenu::Draw(updateRect);
873 }
874 
875 
876 void
877 TExpandoMenuBar::DrawBackground(BRect updateRect)
878 {
879 	if (Vertical())
880 		return;
881 
882 	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 1.22));
883 	StrokeLine(Bounds().RightTop(), Bounds().RightBottom());
884 }
885 
886 
887 /*!	Some methods to help determine if we are showing too many apps
888 	and need to add or remove in scroll arrows.
889 */
890 bool
891 TExpandoMenuBar::CheckForSizeOverrun()
892 {
893 	if (Vertical())
894 		return CheckForSizeOverrunVertical();
895 	else
896 		return CheckForSizeOverrunHorizontal();
897 }
898 
899 
900 bool
901 TExpandoMenuBar::CheckForSizeOverrunVertical()
902 {
903 	if (Window() == NULL || !Vertical())
904 		return false;
905 
906 	return Window()->Frame().bottom > (BScreen(Window())).Frame().bottom;
907 
908 }
909 
910 
911 bool
912 TExpandoMenuBar::CheckForSizeOverrunHorizontal()
913 {
914 	if (fBarView == NULL || Vertical())
915 		return false;
916 
917 	// minimum two items before size overrun can occur
918 	int32 itemCount = CountItems();
919 	if (itemCount < 2)
920 		return false;
921 
922 	float minMenuWidth = MinHorizontalItemWidth() * itemCount;
923 	float maxWidth = MaxHorizontalWidth();
924 
925 	return minMenuWidth > maxWidth;
926 }
927 
928 
929 float
930 TExpandoMenuBar::MaxHorizontalWidth()
931 {
932 	return (fBarView->DragRegion()->Frame().left - 1) - kDeskbarMenuWidth;
933 }
934 
935 
936 void
937 TExpandoMenuBar::SizeWindow(int32 delta)
938 {
939 	// instead of resizing the window here and there in the
940 	// code the resize method will be centered in one place
941 	// thus, the same behavior (good or bad) will be used
942 	// wherever window sizing is done
943 	if (fBarView == NULL || Window() == NULL)
944 		return;
945 
946 	BRect screenFrame = (BScreen(Window())).Frame();
947 	fBarView->SizeWindow(screenFrame);
948 	fBarView->PositionWindow(screenFrame);
949 
950 	if (!Vertical())
951 		CheckItemSizes(delta);
952 
953 	fBarView->CheckForScrolling();
954 	Window()->UpdateIfNeeded();
955 	Invalidate();
956 }
957 
958 
959 void
960 TExpandoMenuBar::StartMonitoringWindows()
961 {
962 	if (sMonThread != B_ERROR)
963 		return;
964 
965 	sDoMonitor = true;
966 	sMonThread = spawn_thread(monitor_team_windows,
967 		"Expando Window Watcher", B_LOW_PRIORITY, this);
968 	resume_thread(sMonThread);
969 }
970 
971 
972 void
973 TExpandoMenuBar::StopMonitoringWindows()
974 {
975 	if (sMonThread == B_ERROR)
976 		return;
977 
978 	sDoMonitor = false;
979 	status_t returnCode;
980 	wait_for_thread(sMonThread, &returnCode);
981 
982 	sMonThread = B_ERROR;
983 }
984 
985 
986 int32
987 TExpandoMenuBar::monitor_team_windows(void* arg)
988 {
989 	TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg;
990 
991 	while (teamMenu->sDoMonitor) {
992 		sMonLocker.Lock();
993 
994 		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
995 			int32 totalItems = teamMenu->CountItems();
996 
997 			// Set all WindowMenuItems to require an update.
998 			TWindowMenuItem* item = NULL;
999 			for (int32 i = 0; i < totalItems; i++) {
1000 				if (!teamMenu->SubmenuAt(i)) {
1001 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
1002 					item->SetRequireUpdate(true);
1003 				}
1004 			}
1005 
1006 			// Perform SetTo() on all the items that still exist as well as add
1007 			// new items.
1008 			bool itemModified = false;
1009 			bool resize = false;
1010 			TTeamMenuItem* teamItem = NULL;
1011 
1012 			for (int32 i = 0; i < totalItems; i++) {
1013 				if (teamMenu->SubmenuAt(i) == NULL)
1014 					continue;
1015 
1016 				teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i));
1017 				if (teamItem->IsExpanded()) {
1018 					int32 teamCount = teamItem->Teams()->CountItems();
1019 					for (int32 j = 0; j < teamCount; j++) {
1020 						// The following code is almost a copy/paste from
1021 						// WindowMenu.cpp
1022 						team_id theTeam = (addr_t)teamItem->Teams()->ItemAt(j);
1023 						int32 count = 0;
1024 						int32* tokens = get_token_list(theTeam, &count);
1025 
1026 						for (int32 k = 0; k < count; k++) {
1027 							client_window_info* wInfo
1028 								= get_window_info(tokens[k]);
1029 							if (wInfo == NULL)
1030 								continue;
1031 
1032 							BString windowName(wInfo->name);
1033 
1034 							BString teamPrefix(teamItem->Label());
1035 							teamPrefix.Append(": ");
1036 
1037 							BString teamSuffix(" - ");
1038 							teamSuffix.Append(teamItem->Label());
1039 
1040 							if (windowName.StartsWith(teamPrefix))
1041 								windowName.RemoveFirst(teamPrefix);
1042 							if (windowName.EndsWith(teamSuffix))
1043 								windowName.RemoveLast(teamSuffix);
1044 
1045 							if (TWindowMenu::WindowShouldBeListed(wInfo)) {
1046 								// Check if we have a matching window item...
1047 								item = teamItem->ExpandedWindowItem(
1048 									wInfo->server_token);
1049 								if (item != NULL) {
1050 									item->SetTo(windowName,
1051 										wInfo->server_token, wInfo->is_mini,
1052 										((1 << current_workspace())
1053 											& wInfo->workspaces) != 0);
1054 
1055 									if (strcasecmp(item->Label(), windowName)
1056 											> 0) {
1057 										item->SetLabel(windowName);
1058 									}
1059 									if (item->Modified())
1060 										itemModified = true;
1061 								} else if (teamItem->IsExpanded()) {
1062 									// Add the item
1063 									item = new TWindowMenuItem(windowName,
1064 										wInfo->server_token, wInfo->is_mini,
1065 										((1 << current_workspace())
1066 											& wInfo->workspaces) != 0, false);
1067 									item->SetExpanded(true);
1068 									teamMenu->AddItem(item,
1069 										TWindowMenuItem::InsertIndexFor(
1070 											teamMenu, i + 1, item));
1071 									resize = true;
1072 								}
1073 							}
1074 							free(wInfo);
1075 						}
1076 						free(tokens);
1077 					}
1078 				}
1079 			}
1080 
1081 			// Remove any remaining items which require an update.
1082 			for (int32 i = 0; i < totalItems; i++) {
1083 				if (!teamMenu->SubmenuAt(i)) {
1084 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
1085 					if (item && item->RequiresUpdate()) {
1086 						item = static_cast<TWindowMenuItem*>
1087 							(teamMenu->RemoveItem(i));
1088 						delete item;
1089 						totalItems--;
1090 
1091 						resize = true;
1092 					}
1093 				}
1094 			}
1095 
1096 			// If any of the WindowMenuItems changed state, we need to force a
1097 			// repaint.
1098 			if (itemModified || resize) {
1099 				teamMenu->Invalidate();
1100 				if (resize)
1101 					teamMenu->SizeWindow(1);
1102 			}
1103 
1104 			teamMenu->Window()->Unlock();
1105 		}
1106 
1107 		sMonLocker.Unlock();
1108 
1109 		// sleep for a bit...
1110 		snooze(150000);
1111 	}
1112 	return B_OK;
1113 }
1114 
1115 
1116 void
1117 TExpandoMenuBar::_FinishedDrag(bool invoke)
1118 {
1119 	if (fPreviousDragTargetItem != NULL) {
1120 		if (invoke)
1121 			fPreviousDragTargetItem->Invoke();
1122 
1123 		fPreviousDragTargetItem->SetOverrideSelected(false);
1124 		fPreviousDragTargetItem = NULL;
1125 	}
1126 
1127 	if (!invoke && fBarView != NULL && fBarView->Dragging())
1128 		fBarView->DragStop(true);
1129 }
1130