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