xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision 6aff37d1c79e20748c683ae224bd629f88a5b0be)
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 	else {
475 		itemWidth = iconSize;
476 		if (!settings->hideLabels)
477 			itemWidth += gMinimumWindowWidth - kMinimumIconSize;
478 		else
479 			itemWidth += kIconPadding * 2;
480 	}
481 	float itemHeight = -1.0f;
482 
483 	TeamMenuItemMap items;
484 	int32 itemCount = CountItems();
485 	BList itemList(itemCount);
486 	for (int32 i = 0; i < itemCount; i++) {
487 		BMenuItem* menuItem = RemoveItem((int32)0);
488 		itemList.AddItem(menuItem);
489 		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem);
490 		if (item != NULL)
491 			items[BString(item->Signature()).ToLower()] = item;
492 	}
493 
494 	if (settings->sortRunningApps)
495 		fTeamList.SortItems(TTeamMenu::CompareByName);
496 
497 	int32 teamCount = fTeamList.CountItems();
498 	for (int32 i = 0; i < teamCount; i++) {
499 		BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i);
500 		TeamMenuItemMap::const_iterator iter
501 			= items.find(BString(barInfo->sig).ToLower());
502 		if (iter == items.end()) {
503 			// new team
504 			TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams,
505 				barInfo->icon, barInfo->name, barInfo->sig, itemWidth,
506 				itemHeight);
507 
508 			if (settings->trackerAlwaysFirst
509 				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
510 				AddItem(item, 0);
511 			} else
512 				AddItem(item);
513 
514 			if (fFirstBuild && fVertical && settings->expandNewTeams)
515 				item->ToggleExpandState(true);
516 		} else {
517 			// existing team, update info and add it
518 			TTeamMenuItem* item = iter->second;
519 			item->SetIcon(barInfo->icon);
520 			item->SetOverrideWidth(itemWidth);
521 			item->SetOverrideHeight(itemHeight);
522 
523 			if (settings->trackerAlwaysFirst
524 				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
525 				AddItem(item, 0);
526 			} else
527 				AddItem(item);
528 
529 			// add window items back
530 			int32 index = itemList.IndexOf(item);
531 			TWindowMenuItem* windowItem;
532 			TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu());
533 			bool hasWindowItems = false;
534 			while ((windowItem = dynamic_cast<TWindowMenuItem*>(
535 					(BMenuItem*)(itemList.ItemAt(++index)))) != NULL) {
536 				if (fVertical)
537 					AddItem(windowItem);
538 				else {
539 					delete windowItem;
540 					hasWindowItems = submenu != NULL;
541 				}
542 			}
543 
544 			// unexpand if turn off show team expander
545 			if (fVertical && !settings->superExpando && item->IsExpanded())
546 				item->ToggleExpandState(false);
547 
548 			if (hasWindowItems) {
549 				// add (new) window items in submenu
550 				submenu->SetExpanded(false, 0);
551 				submenu->AttachedToWindow();
552 			}
553 		}
554 	}
555 
556 	if (CountItems() == 0) {
557 		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
558 		// weird value - we just override it again
559 		ResizeTo(itemWidth, 0);
560 	}
561 
562 	fFirstBuild = false;
563 }
564 
565 
566 bool
567 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const
568 {
569 	TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
570 	if (window != NULL) {
571 		if (TDeskbarMenu* bemenu = window->DeskbarMenu()) {
572 			bool inDeskbarMenu = false;
573 			if (bemenu->LockLooper()) {
574 				inDeskbarMenu = bemenu->Frame().Contains(loc);
575 				bemenu->UnlockLooper();
576 			}
577 			return inDeskbarMenu;
578 		}
579 	}
580 
581 	return false;
582 }
583 
584 
585 /*!	Returns the team menu item that belongs to the item under the
586 	specified \a point.
587 	If \a _item is given, it will return the exact menu item under
588 	that point (which might be a window item when the expander is on).
589 */
590 TTeamMenuItem*
591 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
592 {
593 	TTeamMenuItem* lastApp = NULL;
594 	int32 count = CountItems();
595 
596 	for (int32 i = 0; i < count; i++) {
597 		BMenuItem* item = ItemAt(i);
598 
599 		if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
600 			lastApp = (TTeamMenuItem*)item;
601 
602 		if (item && item->Frame().Contains(point)) {
603 			if (_item != NULL)
604 				*_item = item;
605 
606 			return lastApp;
607 		}
608 	}
609 
610 	// no item found
611 
612 	if (_item != NULL)
613 		*_item = NULL;
614 
615 	return NULL;
616 }
617 
618 
619 void
620 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name,
621 	char* signature)
622 {
623 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
624 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
625 
626 	float itemWidth = -1.0f;
627 	if (fVertical)
628 		itemWidth = fBarView->Bounds().Width();
629 	else {
630 		itemWidth = iconSize;
631 		if (!settings->hideLabels)
632 			itemWidth += gMinimumWindowWidth - kMinimumIconSize;
633 		else
634 			itemWidth += kIconPadding * 2;
635 	}
636 	float itemHeight = -1.0f;
637 
638 	TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature,
639 		itemWidth, itemHeight);
640 
641 	if (settings->trackerAlwaysFirst
642 		&& strcasecmp(signature, kTrackerSignature) == 0) {
643 		AddItem(item, 0);
644 	} else if (settings->sortRunningApps) {
645 		TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(0));
646 		int32 firstApp = 0;
647 
648 		// if Tracker should always be the first item, we need to skip it
649 		// when sorting in the current item
650 		if (settings->trackerAlwaysFirst && teamItem != NULL
651 			&& strcasecmp(teamItem->Signature(), kTrackerSignature) == 0) {
652 			firstApp++;
653 		}
654 
655 		BCollator collator;
656 		BLocale::Default()->GetCollator(&collator);
657 
658 		int32 i = firstApp;
659 		int32 itemCount = CountItems();
660 		while (i < itemCount) {
661 			teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
662 			if (teamItem != NULL && collator.Compare(teamItem->Label(), name)
663 					> 0) {
664 				AddItem(item, i);
665 				break;
666 			}
667 			i++;
668 		}
669 		// was the item added to the list yet?
670 		if (i == itemCount)
671 			AddItem(item);
672 	} else
673 		AddItem(item);
674 
675 	if (fVertical && settings->superExpando && settings->expandNewTeams)
676 		item->ToggleExpandState(false);
677 
678 	SizeWindow(1);
679 	Window()->UpdateIfNeeded();
680 }
681 
682 
683 void
684 TExpandoMenuBar::AddTeam(team_id team, const char* signature)
685 {
686 	int32 itemCount = CountItems();
687 	for (int32 i = 0; i < itemCount; i++) {
688 		// Only add to team menu items
689 		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
690 		if (item != NULL && strcasecmp(item->Signature(), signature) == 0
691 			&& !(item->Teams()->HasItem((void*)(addr_t)team))) {
692 			item->Teams()->AddItem((void*)(addr_t)team);
693 			break;
694 		}
695 	}
696 }
697 
698 
699 void
700 TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
701 {
702 	TWindowMenuItem* windowItem = NULL;
703 
704 	for (int32 i = CountItems() - 1; i >= 0; i--) {
705 		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
706 		if (item != NULL && item->Teams()->HasItem((void*)(addr_t)team)) {
707 			item->Teams()->RemoveItem(team);
708 			if (partial)
709 				return;
710 
711 			BAutolock locker(sMonLocker);
712 				// make the update thread wait
713 			RemoveItem(i);
714 			if (item == fPreviousDragTargetItem)
715 				fPreviousDragTargetItem = NULL;
716 
717 			if (item == fLastMousedOverItem)
718 				fLastMousedOverItem = NULL;
719 
720 			if (item == fLastClickedItem)
721 				fLastClickedItem = NULL;
722 
723 			delete item;
724 			while ((windowItem = dynamic_cast<TWindowMenuItem*>(
725 					ItemAt(i))) != NULL) {
726 				// Also remove window items (if there are any)
727 				RemoveItem(i);
728 				if (windowItem == fLastMousedOverItem)
729 					fLastMousedOverItem = NULL;
730 
731 				if (windowItem == fLastClickedItem)
732 					fLastClickedItem = NULL;
733 
734 				delete windowItem;
735 			}
736 			SizeWindow(-1);
737 			Window()->UpdateIfNeeded();
738 			return;
739 		}
740 	}
741 }
742 
743 
744 void
745 TExpandoMenuBar::CheckItemSizes(int32 delta)
746 {
747 	if (fBarView->Vertical())
748 		return;
749 
750 	bool drawLabels = !static_cast<TBarApp*>(be_app)->Settings()->hideLabels;
751 
752 	float maxWidth = fBarView->DragRegion()->Frame().left
753 		- fDeskbarMenuWidth - kSepItemWidth;
754 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
755 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
756 	float minItemWidth = drawLabels
757 		? iconOnlyWidth + kMinMenuItemWidth
758 		: iconOnlyWidth - kIconPadding;
759 	float maxItemWidth = drawLabels
760 		? gMinimumWindowWidth + iconSize - kMinimumIconSize
761 		: iconOnlyWidth;
762 	float menuWidth = maxItemWidth * CountItems() + fDeskbarMenuWidth
763 		+ kSepItemWidth;
764 
765 	bool reset = false;
766 	float newWidth = -1.0f;
767 
768 	if (delta >= 0 && menuWidth > maxWidth) {
769 		fOverflow = true;
770 		reset = true;
771 		newWidth = floorf(maxWidth / CountItems());
772 	} else if (delta < 0 && fOverflow) {
773 		reset = true;
774 		if (menuWidth > maxWidth)
775 			newWidth = floorf(maxWidth / CountItems());
776 		else
777 			newWidth = maxItemWidth;
778 	}
779 
780 	if (reset) {
781 		if (newWidth > maxItemWidth)
782 			newWidth = maxItemWidth;
783 		else if (newWidth < minItemWidth)
784 			newWidth = minItemWidth;
785 
786 		SetMaxContentWidth(newWidth);
787 		if (newWidth == maxItemWidth)
788 			fOverflow = false;
789 
790 		InvalidateLayout();
791 
792 		for (int32 index = 0; ; index++) {
793 			TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index);
794 			if (item == NULL)
795 				break;
796 
797 			item->SetOverrideWidth(newWidth);
798 		}
799 
800 		Invalidate();
801 		Window()->UpdateIfNeeded();
802 		fBarView->CheckForScrolling();
803 	}
804 }
805 
806 
807 menu_layout
808 TExpandoMenuBar::MenuLayout() const
809 {
810 	return Layout();
811 }
812 
813 
814 void
815 TExpandoMenuBar::SetMenuLayout(menu_layout layout)
816 {
817 	fVertical = layout == B_ITEMS_IN_COLUMN;
818 	BPrivate::MenuPrivate(this).SetLayout(layout);
819 	SetMaxItemWidth();
820 		// when the menu layout changes, make sure to set the max width
821 }
822 
823 
824 void
825 TExpandoMenuBar::Draw(BRect updateRect)
826 {
827 	BMenu::Draw(updateRect);
828 }
829 
830 
831 void
832 TExpandoMenuBar::DrawBackground(BRect updateRect)
833 {
834 	if (fVertical)
835 		return;
836 
837 	BRect bounds(Bounds());
838 	rgb_color menuColor = ui_color(B_MENU_BACKGROUND_COLOR);
839 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
840 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
841 
842 	int32 count = CountItems() - 1;
843 	if (count >= 0)
844 		bounds.left = ItemAt(count)->Frame().right + 1;
845 	else
846 		bounds.left = 0;
847 
848 	if (be_control_look != NULL) {
849 		SetHighColor(tint_color(menuColor, 1.22));
850 		StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
851 		bounds.left++;
852 		uint32 borders = BControlLook::B_TOP_BORDER
853 			| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
854 
855 		be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
856 			0, borders);
857 	} else {
858 		SetHighColor(vlight);
859 		StrokeLine(bounds.LeftTop(), bounds.RightTop());
860 		StrokeLine(BPoint(bounds.left, bounds.top + 1), bounds.LeftBottom());
861 		SetHighColor(hilite);
862 		StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
863 			bounds.RightBottom());
864 	}
865 }
866 
867 
868 /*!	Something to help determine if we are showing too many apps
869 	need to add in scrolling functionality.
870 */
871 bool
872 TExpandoMenuBar::CheckForSizeOverrun()
873 {
874 	if (fVertical) {
875 		if (Window() == NULL)
876 			return false;
877 
878 		BRect screenFrame = (BScreen(Window())).Frame();
879 		return Window()->Frame().bottom > screenFrame.bottom;
880 	}
881 
882 	// horizontal
883 	int32 count = CountItems() - 1;
884 	if (count < 0)
885 		return false;
886 
887 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
888 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
889 	float minItemWidth = !static_cast<TBarApp*>(be_app)->Settings()->hideLabels
890 		? iconOnlyWidth + kMinMenuItemWidth
891 		: iconOnlyWidth - kIconPadding;
892 	float menuWidth = minItemWidth * CountItems() + fDeskbarMenuWidth
893 		+ kSepItemWidth;
894 	float maxWidth = fBarView->DragRegion()->Frame().left
895 		- fDeskbarMenuWidth - kSepItemWidth;
896 
897 	return menuWidth > maxWidth;
898 }
899 
900 
901 void
902 TExpandoMenuBar::SetMaxItemWidth()
903 {
904 	if (fVertical)
905 		SetMaxContentWidth(static_cast<TBarApp*>(be_app)->Settings()->width);
906 	else {
907 		// Make more room for the icon in horizontal mode
908 		int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
909 		SetMaxContentWidth(gMinimumWindowWidth + iconSize
910 			- kMinimumIconSize);
911 	}
912 }
913 
914 
915 void
916 TExpandoMenuBar::SizeWindow(int32 delta)
917 {
918 	// instead of resizing the window here and there in the
919 	// code the resize method will be centered in one place
920 	// thus, the same behavior (good or bad) will be used
921 	// wherever window sizing is done
922 	if (fVertical) {
923 		BRect screenFrame = (BScreen(Window())).Frame();
924 		fBarView->SizeWindow(screenFrame);
925 		fBarView->PositionWindow(screenFrame);
926 		fBarView->CheckForScrolling();
927 	} else
928 		CheckItemSizes(delta);
929 }
930 
931 
932 void
933 TExpandoMenuBar::StartMonitoringWindows()
934 {
935 	if (sMonThread != B_ERROR)
936 		return;
937 
938 	sDoMonitor = true;
939 	sMonThread = spawn_thread(monitor_team_windows,
940 		"Expando Window Watcher", B_LOW_PRIORITY, this);
941 	resume_thread(sMonThread);
942 }
943 
944 
945 void
946 TExpandoMenuBar::StopMonitoringWindows()
947 {
948 	if (sMonThread == B_ERROR)
949 		return;
950 
951 	sDoMonitor = false;
952 	status_t returnCode;
953 	wait_for_thread(sMonThread, &returnCode);
954 
955 	sMonThread = B_ERROR;
956 }
957 
958 
959 int32
960 TExpandoMenuBar::monitor_team_windows(void* arg)
961 {
962 	TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg;
963 
964 	while (teamMenu->sDoMonitor) {
965 		sMonLocker.Lock();
966 
967 		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
968 			int32 totalItems = teamMenu->CountItems();
969 
970 			// Set all WindowMenuItems to require an update.
971 			TWindowMenuItem* item = NULL;
972 			for (int32 i = 0; i < totalItems; i++) {
973 				if (!teamMenu->SubmenuAt(i)) {
974 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
975 					item->SetRequireUpdate(true);
976 				}
977 			}
978 
979 			// Perform SetTo() on all the items that still exist as well as add
980 			// new items.
981 			bool itemModified = false;
982 			bool resize = false;
983 			TTeamMenuItem* teamItem = NULL;
984 
985 			for (int32 i = 0; i < totalItems; i++) {
986 				if (teamMenu->SubmenuAt(i) == NULL)
987 					continue;
988 
989 				teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i));
990 				if (teamItem->IsExpanded()) {
991 					int32 teamCount = teamItem->Teams()->CountItems();
992 					for (int32 j = 0; j < teamCount; j++) {
993 						// The following code is almost a copy/paste from
994 						// WindowMenu.cpp
995 						team_id theTeam = (addr_t)teamItem->Teams()->ItemAt(j);
996 						int32 count = 0;
997 						int32* tokens = get_token_list(theTeam, &count);
998 
999 						for (int32 k = 0; k < count; k++) {
1000 							client_window_info* wInfo
1001 								= get_window_info(tokens[k]);
1002 							if (wInfo == NULL)
1003 								continue;
1004 
1005 							BString windowName(wInfo->name);
1006 
1007 							BString teamPrefix(teamItem->Label());
1008 							teamPrefix.Append(": ");
1009 
1010 							BString teamSuffix(" - ");
1011 							teamSuffix.Append(teamItem->Label());
1012 
1013 							if (windowName.StartsWith(teamPrefix))
1014 								windowName.RemoveFirst(teamPrefix);
1015 							if (windowName.EndsWith(teamSuffix))
1016 								windowName.RemoveLast(teamSuffix);
1017 
1018 							if (TWindowMenu::WindowShouldBeListed(wInfo)) {
1019 								// Check if we have a matching window item...
1020 								item = teamItem->ExpandedWindowItem(
1021 									wInfo->server_token);
1022 								if (item != NULL) {
1023 									item->SetTo(windowName,
1024 										wInfo->server_token, wInfo->is_mini,
1025 										((1 << current_workspace())
1026 											& wInfo->workspaces) != 0);
1027 
1028 									if (strcasecmp(item->Label(), windowName) > 0)
1029 										item->SetLabel(windowName);
1030 
1031 									if (item->Modified())
1032 										itemModified = true;
1033 								} else if (teamItem->IsExpanded()) {
1034 									// Add the item
1035 									item = new TWindowMenuItem(windowName,
1036 										wInfo->server_token, wInfo->is_mini,
1037 										((1 << current_workspace())
1038 											& wInfo->workspaces) != 0, false);
1039 									item->SetExpanded(true);
1040 									teamMenu->AddItem(item,
1041 										TWindowMenuItem::InsertIndexFor(
1042 											teamMenu, i + 1, item));
1043 									resize = true;
1044 								}
1045 							}
1046 							free(wInfo);
1047 						}
1048 						free(tokens);
1049 					}
1050 				}
1051 			}
1052 
1053 			// Remove any remaining items which require an update.
1054 			for (int32 i = 0; i < totalItems; i++) {
1055 				if (!teamMenu->SubmenuAt(i)) {
1056 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
1057 					if (item && item->RequiresUpdate()) {
1058 						item = static_cast<TWindowMenuItem*>
1059 							(teamMenu->RemoveItem(i));
1060 						delete item;
1061 						totalItems--;
1062 
1063 						resize = true;
1064 					}
1065 				}
1066 			}
1067 
1068 			// If any of the WindowMenuItems changed state, we need to force a
1069 			// repaint.
1070 			if (itemModified || resize) {
1071 				teamMenu->Invalidate();
1072 				if (resize)
1073 					teamMenu->SizeWindow(1);
1074 			}
1075 
1076 			teamMenu->Window()->Unlock();
1077 		}
1078 
1079 		sMonLocker.Unlock();
1080 
1081 		// sleep for a bit...
1082 		snooze(150000);
1083 	}
1084 	return B_OK;
1085 }
1086 
1087 
1088 void
1089 TExpandoMenuBar::_FinishedDrag(bool invoke)
1090 {
1091 	if (fPreviousDragTargetItem != NULL) {
1092 		if (invoke)
1093 			fPreviousDragTargetItem->Invoke();
1094 
1095 		fPreviousDragTargetItem->SetOverrideSelected(false);
1096 		fPreviousDragTargetItem = NULL;
1097 	}
1098 
1099 	if (!invoke && fBarView->Dragging())
1100 		fBarView->DragStop(true);
1101 }
1102 
1103 
1104 void
1105 TExpandoMenuBar::_DoneTracking(BPoint where)
1106 {
1107 	TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
1108 	if (lastItem == NULL)
1109 		return;
1110 
1111 	if (!lastItem->ExpanderBounds().Contains(where))
1112 		return;
1113 
1114 	lastItem->ToggleExpandState(true);
1115 	lastItem->SetArrowDirection(lastItem->IsExpanded()
1116 		? BControlLook::B_DOWN_ARROW
1117 		: BControlLook::B_RIGHT_ARROW);
1118 
1119 	Invalidate(lastItem->ExpanderBounds());
1120 }
1121 
1122 
1123 void
1124 TExpandoMenuBar::_Track(BPoint where, uint32)
1125 {
1126 	TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
1127 	if (lastItem == NULL)
1128 		return;
1129 
1130 	if (lastItem->ExpanderBounds().Contains(where))
1131 		lastItem->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW);
1132 	else {
1133 		lastItem->SetArrowDirection(lastItem->IsExpanded()
1134 			? BControlLook::B_DOWN_ARROW
1135 			: BControlLook::B_RIGHT_ARROW);
1136 	}
1137 
1138 	Invalidate(lastItem->ExpanderBounds());
1139 }
1140