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