xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision 7d48219b470e22b5147f0ae9adc8ee2049c981d3)
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 <string.h>
40 
41 #include <Autolock.h>
42 #include <Bitmap.h>
43 #include <ControlLook.h>
44 #include <Debug.h>
45 #include <NodeInfo.h>
46 #include <Roster.h>
47 #include <Screen.h>
48 
49 #include "BarApp.h"
50 #include "BarMenuTitle.h"
51 #include "BarView.h"
52 #include "BarWindow.h"
53 #include "DeskbarMenu.h"
54 #include "DeskbarUtils.h"
55 #include "icons.h"
56 #include "icons_logo.h"
57 #include "ResourceSet.h"
58 #include "ShowHideMenuItem.h"
59 #include "StatusView.h"
60 #include "TeamMenuItem.h"
61 #include "WindowMenu.h"
62 #include "WindowMenuItem.h"
63 
64 
65 const float kDefaultDeskbarMenuWidth = 50.0f;
66 const float kSepItemWidth = 5.0f;
67 const float kIconPadding = 8.0f;
68 
69 const uint32 kMinimizeTeam = 'mntm';
70 const uint32 kBringTeamToFront = 'bftm';
71 
72 bool TExpandoMenuBar::sDoMonitor = false;
73 thread_id TExpandoMenuBar::sMonThread = B_ERROR;
74 BLocker TExpandoMenuBar::sMonLocker("expando monitor");
75 
76 
77 TExpandoMenuBar::TExpandoMenuBar(TBarView* bar, BRect frame, const char* name,
78 	bool vertical, bool drawLabel)
79 	:
80 	BMenuBar(frame, name, B_FOLLOW_NONE,
81 		vertical ? B_ITEMS_IN_COLUMN : B_ITEMS_IN_ROW, vertical),
82 	fVertical(vertical),
83 	fOverflow(false),
84 	fDrawLabel(drawLabel),
85 	fIsScrolling(false),
86 	fShowTeamExpander(static_cast<TBarApp*>(be_app)->Settings()->superExpando),
87 	fExpandNewTeams(static_cast<TBarApp*>(be_app)->Settings()->expandNewTeams),
88 	fDeskbarMenuWidth(kDefaultDeskbarMenuWidth),
89 	fBarView(bar),
90 	fFirstApp(0),
91 	fPreviousDragTargetItem(NULL),
92 	fLastClickItem(NULL)
93 {
94 	SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f);
95 	SetFont(be_plain_font);
96 	if (fVertical)
97 		SetMaxContentWidth(sMinimumWindowWidth);
98 	else {
99 		// Make more room for the icon in horizontal mode
100 		int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
101 		float maxContentWidth = sMinimumWindowWidth + iconSize
102 			- kMinimumIconSize;
103 		SetMaxContentWidth(maxContentWidth);
104 	}
105 }
106 
107 
108 int
109 TExpandoMenuBar::CompareByName(const void* first, const void* second)
110 {
111 	return strcasecmp((*(static_cast<BarTeamInfo* const*>(first)))->name,
112 		(*(static_cast<BarTeamInfo* const*>(second)))->name);
113 }
114 
115 
116 void
117 TExpandoMenuBar::AttachedToWindow()
118 {
119 	BMessenger self(this);
120 	BList teamList;
121 	TBarApp::Subscribe(self, &teamList);
122 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
123 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
124 
125 	float itemWidth = -0.1f;
126 	if (fVertical)
127 		itemWidth = Frame().Width();
128 	else {
129 		itemWidth = iconSize;
130 		if (fDrawLabel)
131 			itemWidth += sMinimumWindowWidth - kMinimumIconSize;
132 		else
133 			itemWidth += kIconPadding * 2;
134 	}
135 	float itemHeight = -1.0f;
136 
137 	// top or bottom mode, add deskbar menu and sep for menubar tracking
138 	// consistency
139 	if (!fVertical) {
140 		TDeskbarMenu* beMenu = new TDeskbarMenu(fBarView);
141 		TBarWindow::SetDeskbarMenu(beMenu);
142 		const BBitmap* logoBitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE,
143 			R_LeafLogoBitmap);
144 		if (logoBitmap != NULL)
145 			fDeskbarMenuWidth = logoBitmap->Bounds().Width() + 16;
146 		fDeskbarMenuItem = new TBarMenuTitle(fDeskbarMenuWidth, Frame().Height(),
147 			logoBitmap, beMenu, true);
148 		AddItem(fDeskbarMenuItem);
149 
150 		fSeparatorItem = new TTeamMenuItem(kSepItemWidth, itemHeight, fVertical);
151 		AddItem(fSeparatorItem);
152 		fSeparatorItem->SetEnabled(false);
153 		fFirstApp = 2;
154 	} else {
155 		fDeskbarMenuItem = NULL;
156 		fSeparatorItem = NULL;
157 	}
158 
159 	if (settings->sortRunningApps)
160 		teamList.SortItems(CompareByName);
161 
162 	int32 count = teamList.CountItems();
163 	for (int32 i = 0; i < count; i++) {
164 		BarTeamInfo* barInfo = (BarTeamInfo*)teamList.ItemAt(i);
165 		if ((barInfo->flags & B_BACKGROUND_APP) == 0
166 			&& strcasecmp(barInfo->sig, kDeskbarSignature) != 0) {
167 			if (settings->trackerAlwaysFirst
168 				&& !strcmp(barInfo->sig, kTrackerSignature)) {
169 				AddItem(new TTeamMenuItem(barInfo->teams, barInfo->icon,
170 					barInfo->name, barInfo->sig, itemWidth, itemHeight,
171 					fDrawLabel, fVertical), fFirstApp);
172 			} else {
173 				AddItem(new TTeamMenuItem(barInfo->teams, barInfo->icon,
174 					barInfo->name, barInfo->sig, itemWidth, itemHeight,
175 					fDrawLabel, fVertical));
176 			}
177 
178 			barInfo->teams = NULL;
179 			barInfo->icon = NULL;
180 			barInfo->name = NULL;
181 			barInfo->sig = NULL;
182 		}
183 
184 		delete barInfo;
185 	}
186 
187 	BMenuBar::AttachedToWindow();
188 
189 	if (CountItems() == 0) {
190 		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
191 		// weird value - we just override it again
192 		ResizeTo(itemWidth, 0);
193 	}
194 
195 	if (fVertical) {
196 		sDoMonitor = true;
197 		sMonThread = spawn_thread(monitor_team_windows,
198 			"Expando Window Watcher", B_LOW_PRIORITY, this);
199 		resume_thread(sMonThread);
200 	}
201 }
202 
203 
204 void
205 TExpandoMenuBar::DetachedFromWindow()
206 {
207 	BMenuBar::DetachedFromWindow();
208 
209 	if (sMonThread != B_ERROR) {
210 		sDoMonitor = false;
211 
212 		status_t returnCode;
213 		wait_for_thread(sMonThread, &returnCode);
214 
215 		sMonThread = B_ERROR;
216 	}
217 
218 	BMessenger self(this);
219 	BMessage message(kUnsubscribe);
220 	message.AddMessenger("messenger", self);
221 	be_app->PostMessage(&message);
222 
223 	RemoveItems(0, CountItems(), true);
224 }
225 
226 
227 void
228 TExpandoMenuBar::MessageReceived(BMessage* message)
229 {
230 	int32 index;
231 	TTeamMenuItem* item;
232 
233 	switch (message->what) {
234 		case B_SOME_APP_LAUNCHED: {
235 			BList* teams = NULL;
236 			message->FindPointer("teams", (void**)&teams);
237 
238 			BBitmap* icon = NULL;
239 			message->FindPointer("icon", (void**)&icon);
240 
241 			const char* signature;
242 			if (message->FindString("sig", &signature) == B_OK
243 				&&strcasecmp(signature, kDeskbarSignature) == 0) {
244 				delete teams;
245 				delete icon;
246 				break;
247 			}
248 
249 			uint32 flags;
250 			if (message->FindInt32("flags", ((int32*) &flags)) == B_OK
251 				&& (flags & B_BACKGROUND_APP) != 0) {
252 				delete teams;
253 				delete icon;
254 				break;
255 			}
256 
257 			const char* name = NULL;
258 			message->FindString("name", &name);
259 
260 			AddTeam(teams, icon, strdup(name), strdup(signature));
261 			break;
262 		}
263 
264 		case kAddTeam:
265 			AddTeam(message->FindInt32("team"), message->FindString("sig"));
266 			break;
267 
268 		case kRemoveTeam:
269 		{
270 			team_id team = -1;
271 			message->FindInt32("team", &team);
272 
273 			RemoveTeam(team, true);
274 			break;
275 		}
276 
277 		case B_SOME_APP_QUIT:
278 		{
279 			team_id team = -1;
280 			message->FindInt32("team", &team);
281 
282 			RemoveTeam(team, false);
283 			break;
284 		}
285 
286 		case kMinimizeTeam:
287 		{
288 			index = message->FindInt32("itemIndex");
289 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
290 			if (item == NULL)
291 				break;
292 
293 			TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW,
294 				item->Teams(),
295 				item->Menu()->ConvertToScreen(item->Frame()),
296 				true);
297 			break;
298 		}
299 
300 		case kBringTeamToFront:
301 		{
302 			index = message->FindInt32("itemIndex");
303 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
304 			if (item == NULL)
305 				break;
306 
307 			TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT,
308 				item->Teams(), item->Menu()->ConvertToScreen(item->Frame()),
309 				true);
310 			break;
311 		}
312 
313 		default:
314 			BMenuBar::MessageReceived(message);
315 			break;
316 	}
317 }
318 
319 
320 void
321 TExpandoMenuBar::MouseDown(BPoint where)
322 {
323 	BMessage* message = Window()->CurrentMessage();
324 	BMenuItem* menuItem;
325 	TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem);
326 
327 	// check for three finger salute, a.k.a. Vulcan Death Grip
328 	if (message != NULL && item != NULL && !fBarView->Dragging()) {
329 		int32 modifiers = 0;
330 		message->FindInt32("modifiers", &modifiers);
331 
332 		if ((modifiers & B_COMMAND_KEY) != 0
333 			&& (modifiers & B_CONTROL_KEY) != 0
334 			&& (modifiers & B_SHIFT_KEY) != 0) {
335 			const BList* teams = item->Teams();
336 			int32 teamCount = teams->CountItems();
337 
338 			team_id teamID;
339 			for (int32 team = 0; team < teamCount; team++) {
340 				teamID = (team_id)teams->ItemAt(team);
341 				kill_team(teamID);
342 				// remove the team immediately from display
343 				RemoveTeam(teamID, false);
344 			}
345 
346 			return;
347 		}
348 
349 		// control click - show all/hide all shortcut
350 		if ((modifiers & B_CONTROL_KEY) != 0) {
351 			// show/hide item's teams
352 			BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
353 				? kMinimizeTeam : kBringTeamToFront);
354 			showMessage.AddInt32("itemIndex", IndexOf(item));
355 			Window()->PostMessage(&showMessage, this);
356 			return;
357 		}
358 
359 		// Check the bounds of the expand Team icon
360 		if (fShowTeamExpander && fVertical) {
361 			BRect expanderRect = item->ExpanderBounds();
362 			if (expanderRect.Contains(where)) {
363 				// Let the update thread wait...
364 				BAutolock locker(sMonLocker);
365 
366 				// Toggle the item
367 				item->ToggleExpandState(true);
368 				item->Draw();
369 
370 				// Absorb the message.
371 				return;
372 			}
373 		}
374 
375 		// double-click on an item brings the team to front
376 		int32 clicks;
377 		if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1
378 			&& item == menuItem && item == fLastClickItem) {
379 			// activate this team
380 			be_roster->ActivateApp((team_id)item->Teams()->ItemAt(0));
381 			return;
382 		}
383 
384 		fLastClickItem = item;
385 	}
386 
387 	BMenuBar::MouseDown(where);
388 }
389 
390 
391 void
392 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message)
393 {
394 	if (message == NULL) {
395 		// force a cleanup
396 		_FinishedDrag();
397 
398 		switch (code) {
399 			case B_ENTERED_VIEW:
400 			case B_INSIDE_VIEW:
401 			{
402 				TTeamMenuItem* item = TeamItemAtPoint(where);
403 				if (item == NULL) {
404 					// item is NULL, break out
405 					fLastMousedOverItem = NULL;
406 					break;
407 				}
408 
409 				if (item->HasLabel()) {
410 					// item has a visible label, set the item and break out
411 					fLastMousedOverItem = item;
412 					break;
413 				}
414 
415 				if (item == fLastMousedOverItem) {
416 					// already set the tooltip for this item, break out
417 					break;
418 				}
419 
420 				// new item, update the tooltip with the item name
421 				SetToolTip(item->Name());
422 
423 				// save the current item for the next MouseMoved() call
424 				fLastMousedOverItem = item;
425 
426 				break;
427 			}
428 
429 			case B_OUTSIDE_VIEW:
430 			case B_EXITED_VIEW:
431 				fLastMousedOverItem = NULL;
432 				break;
433 		}
434 
435 		BMenuBar::MouseMoved(where, code, message);
436 		return;
437 	}
438 
439 	uint32 buttons;
440 	if (Window()->CurrentMessage() == NULL
441 		|| Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons)
442 			< B_OK) {
443 		buttons = 0;
444 	}
445 
446 	if (buttons == 0)
447 		return;
448 
449 	switch (code) {
450 		case B_ENTERED_VIEW:
451 			// fPreviousDragTargetItem should always be NULL here anyways.
452 			if (fPreviousDragTargetItem)
453 				_FinishedDrag();
454 
455 			fBarView->CacheDragData(message);
456 			fPreviousDragTargetItem = NULL;
457 			break;
458 
459 		case B_OUTSIDE_VIEW:
460 			// NOTE: Should not be here, but for the sake of defensive
461 			// programming...
462 		case B_EXITED_VIEW:
463 			_FinishedDrag();
464 			break;
465 
466 		case B_INSIDE_VIEW:
467 			if (fBarView->Dragging()) {
468 				TTeamMenuItem* item = NULL;
469 				int32 itemCount = CountItems();
470 				for (int32 i = 0; i < itemCount; i++) {
471 					BMenuItem* _item = ItemAt(i);
472 					if (_item->Frame().Contains(where)) {
473 						item = dynamic_cast<TTeamMenuItem*>(_item);
474 						break;
475 					}
476 				}
477 				if (item == fPreviousDragTargetItem)
478 					break;
479 				if (fPreviousDragTargetItem != NULL)
480 					fPreviousDragTargetItem->SetOverrideSelected(false);
481 				if (item != NULL)
482 					item->SetOverrideSelected(true);
483 				fPreviousDragTargetItem = item;
484 			}
485 			break;
486 	}
487 }
488 
489 
490 void
491 TExpandoMenuBar::MouseUp(BPoint where)
492 {
493 	if (!fBarView->Dragging()) {
494 		BMenuBar::MouseUp(where);
495 		return;
496 	}
497 
498 	_FinishedDrag(true);
499 }
500 
501 
502 bool
503 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const
504 {
505 	if (!fVertical) {
506 		if (fDeskbarMenuItem && fDeskbarMenuItem->Frame().Contains(loc))
507 			return true;
508 	} else {
509 		TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
510 		if (window) {
511 			if (TDeskbarMenu* bemenu = window->DeskbarMenu()) {
512 				bool inDeskbarMenu = false;
513 				if (bemenu->LockLooper()) {
514 					inDeskbarMenu = bemenu->Frame().Contains(loc);
515 					bemenu->UnlockLooper();
516 				}
517 				return inDeskbarMenu;
518 			}
519 		}
520 	}
521 
522 	return false;
523 }
524 
525 
526 /*!	Returns the team menu item that belongs to the item under the
527 	specified \a point.
528 	If \a _item is given, it will return the exact menu item under
529 	that point (which might be a window item when the expander is on).
530 */
531 TTeamMenuItem*
532 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
533 {
534 	TTeamMenuItem* lastApp = NULL;
535 	int32 count = CountItems();
536 
537 	for (int32 i = fFirstApp; i < count; i++) {
538 		BMenuItem* item = ItemAt(i);
539 
540 		if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
541 			lastApp = (TTeamMenuItem*)item;
542 
543 		if (item && item->Frame().Contains(point)) {
544 			if (_item != NULL)
545 				*_item = item;
546 
547 			return lastApp;
548 		}
549 	}
550 
551 	// no item found
552 
553 	if (_item != NULL)
554 		*_item = NULL;
555 
556 	return NULL;
557 }
558 
559 
560 void
561 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name,
562 	char* signature)
563 {
564 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
565 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
566 
567 	float itemWidth = -1.0f;
568 	if (fVertical)
569 		itemWidth = fBarView->Bounds().Width();
570 	else {
571 		itemWidth = iconSize;
572 		if (fDrawLabel)
573 			itemWidth += sMinimumWindowWidth - kMinimumIconSize;
574 		else
575 			itemWidth += kIconPadding * 2;
576 	}
577 	float itemHeight = -1.0f;
578 
579 	TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature,
580 		itemWidth, itemHeight, fDrawLabel, fVertical);
581 
582 	if (settings->trackerAlwaysFirst && !strcmp(signature, kTrackerSignature))
583 		AddItem(item, fFirstApp);
584 	else if (settings->sortRunningApps) {
585 		TTeamMenuItem* teamItem
586 			= dynamic_cast<TTeamMenuItem*>(ItemAt(fFirstApp));
587 		int32 firstApp = fFirstApp;
588 
589 		// if Tracker should always be the first item, we need to skip it
590 		// when sorting in the current item
591 		if (settings->trackerAlwaysFirst && teamItem != NULL
592 			&& !strcmp(teamItem->Signature(), kTrackerSignature)) {
593 			firstApp++;
594 		}
595 
596 		int32 i = firstApp;
597 		int32 itemCount = CountItems();
598 		while (i < itemCount) {
599 			teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
600 			if (teamItem != NULL && strcasecmp(teamItem->Name(), name) > 0) {
601 				AddItem(item, i);
602 				break;
603 			}
604 			i++;
605 		}
606 		// was the item added to the list yet?
607 		if (i == itemCount)
608 			AddItem(item);
609 	} else
610 		AddItem(item);
611 
612 	if (fVertical) {
613 		if (item && fShowTeamExpander && fExpandNewTeams)
614 			item->ToggleExpandState(false);
615 
616 		fBarView->SizeWindow(BScreen(Window()).Frame());
617 	} else
618 		CheckItemSizes(1);
619 
620 	Window()->UpdateIfNeeded();
621 }
622 
623 
624 void
625 TExpandoMenuBar::AddTeam(team_id team, const char* signature)
626 {
627 	int32 count = CountItems();
628 	for (int32 i = fFirstApp; i < count; i++) {
629 		// Only add to team menu items
630 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
631 			if (strcasecmp(item->Signature(), signature) == 0) {
632 				if (!(item->Teams()->HasItem((void*)team)))
633 					item->Teams()->AddItem((void*)team);
634 				break;
635 			}
636 		}
637 	}
638 }
639 
640 
641 void
642 TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
643 {
644 	int32 count = CountItems();
645 	for (int32 i = fFirstApp; i < count; i++) {
646 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
647 			if (item->Teams()->HasItem((void*)team)) {
648 				item->Teams()->RemoveItem(team);
649 
650 				if (partial)
651 					return;
652 
653 #ifdef DOUBLECLICKBRINGSTOFRONT
654 				if (fLastClickItem == i)
655 					fLastClickItem = -1;
656 #endif
657 
658 				RemoveItem(i);
659 
660 				if (fVertical) {
661 					// instead of resizing the window here and there in the
662 					// code the resize method will be centered in one place
663 					// thus, the same behavior (good or bad) will be used
664 					// whereever window sizing is done
665 					fBarView->SizeWindow(BScreen(Window()).Frame());
666 				} else
667 					CheckItemSizes(-1);
668 
669 				Window()->UpdateIfNeeded();
670 
671 				delete item;
672 				return;
673 			}
674 		}
675 	}
676 }
677 
678 
679 void
680 TExpandoMenuBar::CheckItemSizes(int32 delta)
681 {
682 	if (fBarView->Vertical())
683 		return;
684 
685 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
686 	float maxContentWidth = sMinimumWindowWidth + iconSize - kMinimumIconSize;
687 
688 	// There are 2 extra items:
689 	//     The Be Menu
690 	//     The little separator item
691 	int32 count = CountItems() - 2;
692 	float maxWidth = Frame().Width() - fDeskbarMenuWidth - kSepItemWidth * 2;
693 	float fullWidth = maxContentWidth * count + fDeskbarMenuWidth
694 		+ kSepItemWidth;
695 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
696 
697 	bool reset = false;
698 	float newWidth = 0.0f;
699 
700 	if (delta >= 0 && fullWidth > maxWidth) {
701 		fOverflow = true;
702 		reset = true;
703 		if (fDrawLabel)
704 			newWidth = floorf(maxWidth / count);
705 		else
706 			newWidth = iconOnlyWidth;
707 	} else if (delta < 0 && fOverflow) {
708 		reset = true;
709 		if (fullWidth > maxWidth) {
710 			if (fDrawLabel)
711 				newWidth = floorf(maxWidth / count);
712 			else
713 				newWidth = iconOnlyWidth;
714 		} else
715 			newWidth = maxContentWidth;
716 	}
717 
718 	if (newWidth > maxContentWidth)
719 		newWidth = maxContentWidth;
720 
721 	if (reset) {
722 		SetMaxContentWidth(newWidth);
723 		if (newWidth == maxContentWidth)
724 			fOverflow = false;
725 		InvalidateLayout();
726 
727 		for (int32 index = fFirstApp; ; index++) {
728 			TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index);
729 			if (!item)
730 				break;
731 
732 			if (!fDrawLabel && newWidth > iconOnlyWidth) {
733 				item->SetOverrideWidth(iconOnlyWidth);
734 			} else {
735 				item->SetOverrideWidth(newWidth);
736 			}
737 		}
738 
739 		Invalidate();
740 		Window()->UpdateIfNeeded();
741 	}
742 }
743 
744 
745 menu_layout
746 TExpandoMenuBar::MenuLayout() const
747 {
748 	return Layout();
749 }
750 
751 
752 void
753 TExpandoMenuBar::Draw(BRect update)
754 {
755 	BMenu::Draw(update);
756 }
757 
758 
759 void
760 TExpandoMenuBar::DrawBackground(BRect)
761 {
762 	if (fVertical)
763 		return;
764 
765 	BRect bounds(Bounds());
766 	rgb_color menuColor = ViewColor();
767 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
768 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
769 
770 	int32 last = CountItems() - 1;
771 	if (last >= 0)
772 		bounds.left = ItemAt(last)->Frame().right + 1;
773 	else
774 		bounds.left = 0;
775 
776 	if (be_control_look != NULL) {
777 		SetHighColor(tint_color(menuColor, 1.22));
778 		StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
779 		bounds.left++;
780 		uint32 borders = BControlLook::B_TOP_BORDER
781 			| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
782 
783 		be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
784 			0, borders);
785 	} else {
786 		SetHighColor(vlight);
787 		StrokeLine(bounds.LeftTop(), bounds.RightTop());
788 		StrokeLine(BPoint(bounds.left, bounds.top + 1), bounds.LeftBottom());
789 		SetHighColor(hilite);
790 		StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
791 			bounds.RightBottom());
792 	}
793 }
794 
795 
796 /*!	Something to help determine if we are showing too many apps
797 	need to add in scrolling functionality.
798 */
799 void
800 TExpandoMenuBar::CheckForSizeOverrun()
801 {
802 	BRect screenFrame = (BScreen(Window())).Frame();
803 
804 	fIsScrolling = fVertical ? Window()->Frame().bottom > screenFrame.bottom
805 		: false;
806 }
807 
808 
809 void
810 TExpandoMenuBar::SizeWindow()
811 {
812 	if (fVertical)
813 		fBarView->SizeWindow(BScreen(Window()).Frame());
814 	else
815 		CheckItemSizes(1);
816 }
817 
818 
819 int32
820 TExpandoMenuBar::monitor_team_windows(void* arg)
821 {
822 	TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg;
823 
824 	while (teamMenu->sDoMonitor) {
825 		sMonLocker.Lock();
826 
827 		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
828 			int32 totalItems = teamMenu->CountItems();
829 
830 			// Set all WindowMenuItems to require an update.
831 			TWindowMenuItem* item = NULL;
832 			for (int32 i = 0; i < totalItems; i++) {
833 				if (!teamMenu->SubmenuAt(i)) {
834 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
835 					item->SetRequireUpdate();
836 				}
837 			}
838 
839 			// Perform SetTo() on all the items that still exist as well as add
840 			// new items.
841 			bool itemModified = false, resize = false;
842 			TTeamMenuItem* teamItem = NULL;
843 
844 			for (int32 i = 0; i < totalItems; i++) {
845 				if (teamMenu->SubmenuAt(i) == NULL)
846 					continue;
847 
848 				teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i));
849 				if (teamItem->IsExpanded()) {
850 					int32 teamCount = teamItem->Teams()->CountItems();
851 					for (int32 j = 0; j < teamCount; j++) {
852 						// The following code is almost a copy/paste from
853 						// WindowMenu.cpp
854 						team_id	theTeam = (team_id)teamItem->Teams()->ItemAt(j);
855 						int32 count = 0;
856 						int32* tokens = get_token_list(theTeam, &count);
857 
858 						for (int32 k = 0; k < count; k++) {
859 							client_window_info* wInfo
860 								= get_window_info(tokens[k]);
861 							if (wInfo == NULL)
862 								continue;
863 
864 							if (TWindowMenu::WindowShouldBeListed(wInfo->feel)
865 								&& (wInfo->show_hide_level <= 0
866 									|| wInfo->is_mini)) {
867 								// Check if we have a matching window item...
868 								item = teamItem->ExpandedWindowItem(
869 									wInfo->server_token);
870 								if (item) {
871 									item->SetTo(wInfo->name,
872 										wInfo->server_token, wInfo->is_mini,
873 										((1 << current_workspace())
874 											& wInfo->workspaces) != 0);
875 
876 									if (strcmp(wInfo->name,
877 										item->Label()) != 0)
878 										item->SetLabel(wInfo->name);
879 
880 									if (item->ChangedState())
881 										itemModified = true;
882 								} else if (teamItem->IsExpanded()) {
883 									// Add the item
884 									item = new TWindowMenuItem(wInfo->name,
885 										wInfo->server_token, wInfo->is_mini,
886 										((1 << current_workspace())
887 											& wInfo->workspaces) != 0, false);
888 									item->ExpandedItem(true);
889 									teamMenu->AddItem(item,
890 										TWindowMenuItem::InsertIndexFor(
891 											teamMenu, i + 1, item));
892 									resize = true;
893 								}
894 							}
895 							free(wInfo);
896 						}
897 						free(tokens);
898 					}
899 				}
900 			}
901 
902 			// Remove any remaining items which require an update.
903 			for (int32 i = 0; i < totalItems; i++) {
904 				if (!teamMenu->SubmenuAt(i)) {
905 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
906 					if (item && item->RequiresUpdate()) {
907 						item = static_cast<TWindowMenuItem*>
908 							(teamMenu->RemoveItem(i));
909 						delete item;
910 						totalItems--;
911 
912 						resize = true;
913 					}
914 				}
915 			}
916 
917 			// If any of the WindowMenuItems changed state, we need to force a
918 			// repaint.
919 			if (itemModified || resize) {
920 				teamMenu->Invalidate();
921 				if (resize)
922 					teamMenu->SizeWindow();
923 			}
924 
925 			teamMenu->Window()->Unlock();
926 		}
927 
928 		sMonLocker.Unlock();
929 
930 		// sleep for a bit...
931 		snooze(150000);
932 	}
933 	return B_OK;
934 }
935 
936 
937 void
938 TExpandoMenuBar::_FinishedDrag(bool invoke)
939 {
940 	if (fPreviousDragTargetItem != NULL) {
941 		if (invoke)
942 			fPreviousDragTargetItem->Invoke();
943 		fPreviousDragTargetItem->SetOverrideSelected(false);
944 		fPreviousDragTargetItem = NULL;
945 	}
946 	if (!invoke && fBarView->Dragging())
947 		fBarView->DragStop(true);
948 }
949