1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30trademarks of Be Incorporated in the United States and other countries. Other
31brand product names are registered trademarks or trademarks of their respective
32holders.
33All rights reserved.
34*/
35
36
37#include "ExpandoMenuBar.h"
38
39#include <strings.h>
40
41#include <map>
42
43#include <Autolock.h>
44#include <Bitmap.h>
45#include <Collator.h>
46#include <ControlLook.h>
47#include <Debug.h>
48#include <MenuPrivate.h>
49#include <MessengerPrivate.h>
50#include <NodeInfo.h>
51#include <Roster.h>
52#include <Screen.h>
53#include <Thread.h>
54#include <Window.h>
55
56#include "icons.h"
57
58#include "BarApp.h"
59#include "BarMenuBar.h"
60#include "BarView.h"
61#include "BarWindow.h"
62#include "DeskbarMenu.h"
63#include "DeskbarUtils.h"
64#include "InlineScrollView.h"
65#include "ResourceSet.h"
66#include "ShowHideMenuItem.h"
67#include "StatusView.h"
68#include "TeamMenu.h"
69#include "TeamMenuItem.h"
70#include "WindowMenu.h"
71#include "WindowMenuItem.h"
72
73
74bool TExpandoMenuBar::sDoMonitor = false;
75thread_id TExpandoMenuBar::sMonThread = B_ERROR;
76BLocker TExpandoMenuBar::sMonLocker("expando monitor");
77
78typedef std::map<BString, TTeamMenuItem*> TeamMenuItemMap;
79
80
81//	#pragma mark - TExpandoMenuBar
82
83
84TExpandoMenuBar::TExpandoMenuBar(menu_layout layout, TBarView* barView)
85	:
86	BMenuBar(BRect(0, 0, 0, 0), "ExpandoMenuBar", B_FOLLOW_NONE, layout),
87	fBarView(barView),
88	fOverflow(false),
89	fUnderflow(false),
90	fFirstBuild(true),
91	fPreviousDragTargetItem(NULL),
92	fLastMousedOverItem(NULL),
93	fLastClickedItem(NULL),
94	fLastClickTime(0)
95{
96	SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f);
97	SetFont(be_plain_font);
98}
99
100
101void
102TExpandoMenuBar::AllAttached()
103{
104	BMenuBar::AllAttached();
105
106	SizeWindow(0);
107}
108
109
110void
111TExpandoMenuBar::AttachedToWindow()
112{
113	BMenuBar::AttachedToWindow();
114
115	fTeamList.MakeEmpty();
116
117	if (Vertical())
118		StartMonitoringWindows();
119}
120
121
122void
123TExpandoMenuBar::DetachedFromWindow()
124{
125	BMenuBar::DetachedFromWindow();
126
127	StopMonitoringWindows();
128
129	BMessenger self(this);
130	BMessage message(kUnsubscribe);
131	message.AddMessenger("messenger", self);
132	be_app->PostMessage(&message);
133
134	RemoveItems(0, CountItems(), true);
135}
136
137
138void
139TExpandoMenuBar::MessageReceived(BMessage* message)
140{
141	int32 index;
142	TTeamMenuItem* item;
143
144	switch (message->what) {
145		case B_SOME_APP_LAUNCHED:
146		{
147			BList* teams = NULL;
148			message->FindPointer("teams", (void**)&teams);
149
150			BBitmap* icon = NULL;
151			message->FindPointer("icon", (void**)&icon);
152
153			const char* signature = NULL;
154			message->FindString("sig", &signature);
155
156			uint32 flags = 0;
157			message->FindInt32("flags", ((int32*) &flags));
158
159			const char* name = NULL;
160			message->FindString("name", &name);
161
162			AddTeam(teams, icon, strdup(name), strdup(signature));
163			break;
164		}
165
166		case B_MOUSE_WHEEL_CHANGED:
167		{
168			float deltaY = 0;
169			message->FindFloat("be:wheel_delta_y", &deltaY);
170			if (deltaY == 0)
171				return;
172
173			TInlineScrollView* scrollView
174				= dynamic_cast<TInlineScrollView*>(Parent());
175			if (scrollView == NULL || !scrollView->HasScrollers())
176				return;
177
178			float largeStep;
179			float smallStep;
180			scrollView->GetSteps(&smallStep, &largeStep);
181
182			// pressing the option/command/control key scrolls faster
183			if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY))
184				deltaY *= largeStep;
185			else
186				deltaY *= smallStep;
187
188			scrollView->ScrollBy(deltaY);
189			break;
190		}
191
192		case kAddTeam:
193			AddTeam(message->FindInt32("team"), message->FindString("sig"));
194			break;
195
196		case kRemoveTeam:
197		{
198			team_id team = -1;
199			message->FindInt32("team", &team);
200
201			RemoveTeam(team, true);
202			break;
203		}
204
205		case B_SOME_APP_QUIT:
206		{
207			team_id team = -1;
208			message->FindInt32("team", &team);
209
210			RemoveTeam(team, false);
211			break;
212		}
213
214		case kMinimizeTeam:
215		{
216			index = message->FindInt32("itemIndex");
217			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
218			if (item == NULL)
219				break;
220
221			TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW,
222				item->Teams(), item->Menu()->ConvertToScreen(item->Frame()),
223				true);
224			break;
225		}
226
227		case kBringTeamToFront:
228		{
229			index = message->FindInt32("itemIndex");
230			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
231			if (item == NULL)
232				break;
233
234			TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT,
235				item->Teams(), item->Menu()->ConvertToScreen(item->Frame()),
236				true);
237			break;
238		}
239
240		default:
241			BMenuBar::MessageReceived(message);
242			break;
243	}
244}
245
246
247void
248TExpandoMenuBar::MouseDown(BPoint where)
249{
250	if (fBarView == NULL || fBarView->Dragging())
251		return BMenuBar::MouseDown(where);
252
253	BMessage* message = Window()->CurrentMessage();
254	BMenuItem* item = ItemAtPoint(where);
255	fLastClickedItem = item;
256	if (message == NULL || item == NULL)
257		return BMenuBar::MouseDown(where);
258
259	int32 modifiers = 0;
260	int32 buttons = 0;
261	message->FindInt32("modifiers", &modifiers);
262	message->FindInt32("buttons", &buttons);
263
264	// close window item
265	if ((modifiers & B_SHIFT_KEY) != 0
266		&& (buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
267		TWindowMenuItem* wItem = dynamic_cast<TWindowMenuItem*>(item);
268		if (wItem != NULL) {
269			// we have a window item
270			BMessenger messenger;
271			client_window_info* info = get_window_info(wItem->ID());
272			if (info != NULL) {
273				BMessenger::Private(messenger).SetTo(info->team,
274					info->client_port, info->client_token);
275				messenger.SendMessage(B_QUIT_REQUESTED);
276				free(info);
277				return;
278					// absorb the message
279			}
280		}
281	}
282
283	// below depends on item being a team item
284
285	TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(item);
286	if (teamItem == NULL)
287		return BMenuBar::MouseDown(where);
288
289	// check for three finger salute, a.k.a. Vulcan Death Grip
290	if (teamItem->HandleMouseDown(where))
291		return;
292			// absorb the message
293
294	// check if within expander bounds to expand window items
295	if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando
296		&& teamItem->ExpanderBounds().Contains(where)) {
297		// start the animation here, finish on mouse up
298		teamItem->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW);
299		SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
300		Invalidate(teamItem->ExpanderBounds());
301		return;
302			// absorb the message
303	}
304
305	// double-click on an item brings the team to front
306	int32 clicks;
307	bigtime_t clickSpeed = 0;
308	get_click_speed(&clickSpeed);
309	bigtime_t delta = system_time() - fLastClickTime;
310	if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1
311		&& item == fLastClickedItem && delta <= clickSpeed) {
312		be_roster->ActivateApp((addr_t)teamItem->Teams()->ItemAt(0));
313			// activate this team
314		return;
315			// absorb the message
316	}
317
318	// Update fLastClickTime only if we are not already triggering the
319	// double-click action. Otherwise the delay is renewed at every subsequent
320	// click and they keep triggering the double click action
321	fLastClickTime = system_time();
322
323	BMenuBar::MouseDown(where);
324}
325
326
327void
328TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message)
329{
330	int32 buttons;
331	BMessage* currentMessage = Window()->CurrentMessage();
332	if (currentMessage == NULL
333		|| currentMessage->FindInt32("buttons", &buttons) != B_OK) {
334		buttons = 0;
335	}
336
337	if (message == NULL) {
338		// force a cleanup
339		_FinishedDrag();
340
341		if (Vertical() && buttons != 0
342				&& static_cast<TBarApp*>(be_app)->Settings()->superExpando) {
343			TTeamMenuItem* lastItem
344				= dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
345			if (lastItem != NULL) {
346				if (lastItem->ExpanderBounds().Contains(where))
347					lastItem->SetArrowDirection(
348						BControlLook::B_RIGHT_DOWN_ARROW);
349				else {
350					lastItem->SetArrowDirection(lastItem->IsExpanded()
351						? BControlLook::B_DOWN_ARROW
352						: BControlLook::B_RIGHT_ARROW);
353				}
354
355				Invalidate(lastItem->ExpanderBounds());
356			}
357		}
358
359		if (code == B_INSIDE_VIEW) {
360			TTruncatableMenuItem* item;
361			TTeamMenuItem* teamItem = TeamItemAtPoint(where, (BMenuItem**)&item);
362
363			if (item == NULL) {
364				fLastMousedOverItem = NULL;
365				SetToolTip((const char*)NULL);
366			} else if (item != fLastMousedOverItem) {
367				if ((static_cast<TBarApp*>(be_app)->Settings()->hideLabels
368						&& teamItem != NULL)
369					|| strcasecmp(item->TruncatedLabel(), item->Label()) > 0) {
370					SetToolTip(item->Label());
371				} else
372					SetToolTip((const char*)NULL);
373
374				fLastMousedOverItem = item;
375			}
376		}
377
378		BMenuBar::MouseMoved(where, code, message);
379		return;
380	}
381
382	if (buttons == 0)
383		return;
384
385	switch (code) {
386		case B_ENTERED_VIEW:
387			// fPreviousDragTargetItem should always be NULL here anyways.
388			if (fPreviousDragTargetItem != NULL)
389				_FinishedDrag();
390
391			fBarView->CacheDragData(message);
392			fPreviousDragTargetItem = NULL;
393			break;
394
395		case B_OUTSIDE_VIEW:
396			// NOTE: Should not be here, but for the sake of defensive
397			// programming... fall-through
398		case B_EXITED_VIEW:
399			_FinishedDrag();
400			break;
401
402		case B_INSIDE_VIEW:
403			if (fBarView != NULL && fBarView->Dragging()) {
404				TTeamMenuItem* item = NULL;
405				int32 itemCount = CountItems();
406				for (int32 i = 0; i < itemCount; i++) {
407					BMenuItem* _item = ItemAt(i);
408					if (_item->Frame().Contains(where)) {
409						item = dynamic_cast<TTeamMenuItem*>(_item);
410						break;
411					}
412				}
413				if (item == fPreviousDragTargetItem)
414					break;
415				if (fPreviousDragTargetItem != NULL)
416					fPreviousDragTargetItem->SetOverrideSelected(false);
417				if (item != NULL)
418					item->SetOverrideSelected(true);
419				fPreviousDragTargetItem = item;
420			}
421			break;
422	}
423}
424
425
426void
427TExpandoMenuBar::MouseUp(BPoint where)
428{
429	if (fBarView != NULL && fBarView->Dragging()) {
430		_FinishedDrag(true);
431		return;
432			// absorb the message
433	}
434
435	if (fLastClickedItem == NULL)
436		return BMenuBar::MouseUp(where);
437
438	TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
439	fLastClickedItem = NULL;
440
441	if (Vertical() && static_cast<TBarApp*>(be_app)->Settings()->superExpando
442		&& lastItem != NULL && lastItem->ExpanderBounds().Contains(where)) {
443		lastItem->ToggleExpandState(true);
444		lastItem->SetArrowDirection(lastItem->IsExpanded()
445			? BControlLook::B_DOWN_ARROW
446			: BControlLook::B_RIGHT_ARROW);
447
448		Invalidate(lastItem->ExpanderBounds());
449	}
450
451	BMenuBar::MouseUp(where);
452}
453
454
455void
456TExpandoMenuBar::BuildItems()
457{
458	BMessenger self(this);
459	TBarApp::Subscribe(self, &fTeamList);
460
461	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
462
463	float itemWidth = -1.0f;
464	if (Vertical() && (fBarView->ExpandoState() || fBarView->FullState())) {
465		itemWidth = settings->width;
466	}
467	SetMaxContentWidth(itemWidth);
468
469	TeamMenuItemMap items;
470	int32 itemCount = CountItems();
471	BList itemList(itemCount);
472	for (int32 i = 0; i < itemCount; i++) {
473		BMenuItem* menuItem = RemoveItem((int32)0);
474		itemList.AddItem(menuItem);
475		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(menuItem);
476		if (item != NULL)
477			items[BString(item->Signature()).ToLower()] = item;
478	}
479
480	if (settings->sortRunningApps)
481		fTeamList.SortItems(TTeamMenu::CompareByName);
482
483	int32 teamCount = fTeamList.CountItems();
484	for (int32 i = 0; i < teamCount; i++) {
485		BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i);
486		TeamMenuItemMap::const_iterator iter
487			= items.find(BString(barInfo->sig).ToLower());
488		if (iter == items.end()) {
489			// new team
490			TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams,
491				barInfo->icon, barInfo->name, barInfo->sig, itemWidth);
492
493			if (settings->trackerAlwaysFirst
494				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
495				AddItem(item, 0);
496			} else
497				AddItem(item);
498
499			if (fFirstBuild && Vertical() && settings->expandNewTeams)
500				item->ToggleExpandState(true);
501		} else {
502			// existing team, update info and add it
503			TTeamMenuItem* item = iter->second;
504			item->SetIcon(barInfo->icon);
505			item->SetOverrideWidth(itemWidth);
506
507			if (settings->trackerAlwaysFirst
508				&& strcasecmp(barInfo->sig, kTrackerSignature) == 0) {
509				AddItem(item, 0);
510			} else
511				AddItem(item);
512
513			// add window items back
514			int32 index = itemList.IndexOf(item);
515			TWindowMenuItem* windowItem;
516			TWindowMenu* submenu = dynamic_cast<TWindowMenu*>(item->Submenu());
517			bool hasWindowItems = false;
518			while ((windowItem = dynamic_cast<TWindowMenuItem*>(
519					(BMenuItem*)(itemList.ItemAt(++index)))) != NULL) {
520				if (Vertical())
521					AddItem(windowItem);
522				else {
523					delete windowItem;
524					hasWindowItems = submenu != NULL;
525				}
526			}
527
528			// unexpand if turn off show team expander
529			if (Vertical() && !settings->superExpando && item->IsExpanded())
530				item->ToggleExpandState(false);
531
532			if (hasWindowItems) {
533				// add (new) window items in submenu
534				submenu->SetExpanded(false, 0);
535				submenu->AttachedToWindow();
536			}
537		}
538	}
539
540	if (CountItems() == 0) {
541		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
542		// weird value - we just override it again
543		ResizeTo(gMinimumWindowWidth, 0);
544	} else {
545		// first build isn't complete until we've gotten here with an item
546		fFirstBuild = false;
547	}
548}
549
550
551bool
552TExpandoMenuBar::InDeskbarMenu(BPoint loc) const
553{
554	TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
555	if (window != NULL) {
556		if (TDeskbarMenu* bemenu = window->DeskbarMenu()) {
557			bool inDeskbarMenu = false;
558			if (bemenu->LockLooper()) {
559				inDeskbarMenu = bemenu->Frame().Contains(loc);
560				bemenu->UnlockLooper();
561			}
562			return inDeskbarMenu;
563		}
564	}
565
566	return false;
567}
568
569
570BMenuItem*
571TExpandoMenuBar::ItemAtPoint(BPoint point)
572{
573	int32 itemCount = CountItems();
574	for (int32 index = 0; index < itemCount; index++) {
575		BMenuItem* item = ItemAt(index);
576		if (item != NULL && item->Frame().Contains(point))
577			return item;
578	}
579
580	// no item found
581	return NULL;
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*/
590TTeamMenuItem*
591TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
592{
593	TTeamMenuItem* lastApp = NULL;
594	int32 itemCount = CountItems();
595
596	for (int32 index = 0; index < itemCount; index++) {
597		BMenuItem* item = ItemAt(index);
598		if (item != NULL && item->Frame().Contains(point)) {
599			if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
600				lastApp = (TTeamMenuItem*)item;
601
602			if (_item != NULL)
603				*_item = item;
604
605			return lastApp;
606		}
607	}
608
609	// no item found
610
611	if (_item != NULL)
612		*_item = NULL;
613
614	return NULL;
615}
616
617
618void
619TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name,
620	char* signature)
621{
622	TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature);
623
624	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
625	if (settings != NULL && settings->trackerAlwaysFirst
626		&& strcasecmp(signature, kTrackerSignature) == 0) {
627		AddItem(item, 0);
628	} else if (settings->sortRunningApps) {
629		TTeamMenuItem* teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(0));
630		int32 firstApp = 0;
631
632		// if Tracker should always be the first item, we need to skip it
633		// when sorting in the current item
634		if (settings->trackerAlwaysFirst && teamItem != NULL
635			&& strcasecmp(teamItem->Signature(), kTrackerSignature) == 0) {
636			firstApp++;
637		}
638
639		BCollator collator;
640		BLocale::Default()->GetCollator(&collator);
641
642		int32 i = firstApp;
643		int32 itemCount = CountItems();
644		while (i < itemCount) {
645			teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
646			if (teamItem != NULL && collator.Compare(teamItem->Label(), name)
647					> 0) {
648				AddItem(item, i);
649				break;
650			}
651			i++;
652		}
653		// was the item added to the list yet?
654		if (i == itemCount)
655			AddItem(item);
656	} else
657		AddItem(item);
658
659	if (Vertical() && settings != NULL && settings->superExpando
660		&& settings->expandNewTeams) {
661		item->ToggleExpandState(false);
662	}
663
664	SizeWindow(1);
665	Window()->UpdateIfNeeded();
666}
667
668
669void
670TExpandoMenuBar::AddTeam(team_id team, const char* signature)
671{
672	int32 itemCount = CountItems();
673	for (int32 i = 0; i < itemCount; i++) {
674		// Only add to team menu items
675		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
676		if (item != NULL && strcasecmp(item->Signature(), signature) == 0
677			&& !(item->Teams()->HasItem((void*)(addr_t)team))) {
678			item->Teams()->AddItem((void*)(addr_t)team);
679			break;
680		}
681	}
682}
683
684
685void
686TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
687{
688	TWindowMenuItem* windowItem = NULL;
689
690	for (int32 i = CountItems() - 1; i >= 0; i--) {
691		TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
692		if (item != NULL && item->Teams()->HasItem((void*)(addr_t)team)) {
693			item->Teams()->RemoveItem(team);
694			if (partial)
695				return;
696
697			BAutolock locker(sMonLocker);
698				// make the update thread wait
699			RemoveItem(i);
700			if (item == fPreviousDragTargetItem)
701				fPreviousDragTargetItem = NULL;
702
703			if (item == fLastMousedOverItem)
704				fLastMousedOverItem = NULL;
705
706			if (item == fLastClickedItem)
707				fLastClickedItem = NULL;
708
709			delete item;
710			while ((windowItem = dynamic_cast<TWindowMenuItem*>(
711					ItemAt(i))) != NULL) {
712				// Also remove window items (if there are any)
713				RemoveItem(i);
714				if (windowItem == fLastMousedOverItem)
715					fLastMousedOverItem = NULL;
716
717				if (windowItem == fLastClickedItem)
718					fLastClickedItem = NULL;
719
720				delete windowItem;
721			}
722			SizeWindow(-1);
723			Window()->UpdateIfNeeded();
724			return;
725		}
726	}
727}
728
729
730void
731TExpandoMenuBar::CheckItemSizes(int32 delta, bool reset)
732{
733	// horizontal only
734	if (fBarView == NULL || Vertical())
735		return;
736
737	// minimum two items before size overrun can occur
738	int32 itemCount = CountItems();
739	if (itemCount < 2)
740		return;
741
742	float minItemWidth = MinHorizontalItemWidth();
743	float maxItemWidth = MaxHorizontalItemWidth();
744	float maxMenuWidth = maxItemWidth * itemCount;
745	float maxWidth = MaxHorizontalWidth();
746	bool tooWide = maxMenuWidth > maxWidth;
747
748	// start at max width
749	float newItemWidth = maxItemWidth;
750
751	if (delta < 0 && fOverflow) {
752		// removing an item, check if menu is still too wide
753		if (tooWide)
754			newItemWidth = floorf(maxWidth / itemCount);
755		else
756			newItemWidth = maxItemWidth;
757	} else if (tooWide) {
758		fOverflow = true;
759		newItemWidth = std::min(floorf(maxWidth / itemCount), maxItemWidth);
760	}
761
762	// see if we should grow items
763	fUnderflow = delta < 0 && newItemWidth < maxItemWidth;
764
765	if (fOverflow || fUnderflow || fFirstBuild || reset) {
766		// clip within limits
767		if (newItemWidth > maxItemWidth)
768			newItemWidth = maxItemWidth;
769		else if (newItemWidth < minItemWidth)
770			newItemWidth = minItemWidth;
771
772		SetMaxContentWidth(newItemWidth);
773		if (newItemWidth == maxItemWidth)
774			fOverflow = false;
775
776		for (int32 index = 0; ; index++) {
777			TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index);
778			if (item == NULL)
779				break;
780
781			item->SetOverrideWidth(newItemWidth);
782		}
783
784		InvalidateLayout();
785
786		ResizeTo(newItemWidth * itemCount, Frame().Height());
787	}
788}
789
790
791float
792TExpandoMenuBar::MinHorizontalItemWidth()
793{
794	const int32 iconSize = static_cast<TBarApp*>(be_app)->TeamIconSize();
795	const float iconPadding = be_control_look->ComposeSpacing(kIconPadding);
796	float iconOnlyWidth = iconSize + iconPadding;
797	const int32 min = be_control_look->ComposeIconSize(kMinimumIconSize)
798		.IntegerWidth() + 1;
799
800	return static_cast<TBarApp*>(be_app)->Settings()->hideLabels
801		? iconOnlyWidth
802		: (iconSize - min) + gMinimumWindowWidth
803			+ (be_plain_font->Size() - 12) * 4;
804}
805
806
807float
808TExpandoMenuBar::MaxHorizontalItemWidth()
809{
810	const int32 iconSize = static_cast<TBarApp*>(be_app)->TeamIconSize();
811	const float iconPadding = be_control_look->ComposeSpacing(kIconPadding);
812	float iconOnlyWidth = iconSize + iconPadding;
813
814	// hide labels
815	if (static_cast<TBarApp*>(be_app)->Settings()->hideLabels)
816		return iconOnlyWidth + iconPadding; // add an extra icon padding
817
818	// set max item width to 1.25x min item width
819	return floorf(MinHorizontalItemWidth() * 1.25);
820}
821
822
823menu_layout
824TExpandoMenuBar::MenuLayout() const
825{
826	return Layout();
827}
828
829
830void
831TExpandoMenuBar::SetMenuLayout(menu_layout layout)
832{
833	BPrivate::MenuPrivate(this).SetLayout(layout);
834	InvalidateLayout();
835}
836
837
838void
839TExpandoMenuBar::Draw(BRect updateRect)
840{
841	BMenu::Draw(updateRect);
842}
843
844
845void
846TExpandoMenuBar::DrawBackground(BRect updateRect)
847{
848	if (Vertical())
849		return;
850
851	SetHighColor(tint_color(ui_color(B_MENU_BACKGROUND_COLOR), 1.22));
852	StrokeLine(Bounds().RightTop(), Bounds().RightBottom());
853}
854
855
856/*!	Some methods to help determine if we are showing too many apps
857	and need to add or remove in scroll arrows.
858*/
859bool
860TExpandoMenuBar::CheckForSizeOverrun()
861{
862	if (Vertical())
863		return CheckForSizeOverrunVertical();
864	else
865		return CheckForSizeOverrunHorizontal();
866}
867
868
869bool
870TExpandoMenuBar::CheckForSizeOverrunVertical()
871{
872	if (Window() == NULL || !Vertical())
873		return false;
874
875	return Window()->Frame().bottom > (BScreen(Window())).Frame().bottom;
876
877}
878
879
880bool
881TExpandoMenuBar::CheckForSizeOverrunHorizontal()
882{
883	if (fBarView == NULL || Vertical())
884		return false;
885
886	// minimum two items before size overrun can occur
887	int32 itemCount = CountItems();
888	if (itemCount < 2)
889		return false;
890
891	float minMenuWidth = MinHorizontalItemWidth() * itemCount;
892	float maxWidth = MaxHorizontalWidth();
893
894	return minMenuWidth > maxWidth;
895}
896
897
898float
899TExpandoMenuBar::MaxHorizontalWidth()
900{
901	return (BScreen(Window())).Frame().Width()
902		- fBarView->DragRegion()->Frame().Width() - 1
903		- fBarView->BarMenuBar()->Frame().Width();
904}
905
906
907void
908TExpandoMenuBar::SizeWindow(int32 delta)
909{
910	// instead of resizing the window here and there in the
911	// code the resize method will be centered in one place
912	// thus, the same behavior (good or bad) will be used
913	// wherever window sizing is done
914	if (fBarView == NULL || Window() == NULL)
915		return;
916
917	BRect screenFrame = (BScreen(Window())).Frame();
918	fBarView->SizeWindow(screenFrame);
919	fBarView->PositionWindow(screenFrame);
920
921	if (!Vertical())
922		CheckItemSizes(delta);
923
924	fBarView->CheckForScrolling();
925	Window()->UpdateIfNeeded();
926	Invalidate();
927}
928
929
930void
931TExpandoMenuBar::StartMonitoringWindows()
932{
933	if (sMonThread != B_ERROR)
934		return;
935
936	sDoMonitor = true;
937	sMonThread = spawn_thread(monitor_team_windows,
938		"Expando Window Watcher", B_LOW_PRIORITY, this);
939	resume_thread(sMonThread);
940}
941
942
943void
944TExpandoMenuBar::StopMonitoringWindows()
945{
946	if (sMonThread == B_ERROR)
947		return;
948
949	sDoMonitor = false;
950	status_t returnCode;
951	wait_for_thread(sMonThread, &returnCode);
952
953	sMonThread = B_ERROR;
954}
955
956
957int32
958TExpandoMenuBar::monitor_team_windows(void* arg)
959{
960	TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg;
961
962	while (teamMenu->sDoMonitor) {
963		sMonLocker.Lock();
964
965		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
966			int32 totalItems = teamMenu->CountItems();
967
968			// Set all WindowMenuItems to require an update.
969			TWindowMenuItem* item = NULL;
970			for (int32 i = 0; i < totalItems; i++) {
971				if (!teamMenu->SubmenuAt(i)) {
972					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
973					item->SetRequireUpdate(true);
974				}
975			}
976
977			// Perform SetTo() on all the items that still exist as well as add
978			// new items.
979			bool itemModified = false;
980			bool resize = false;
981			TTeamMenuItem* teamItem = NULL;
982
983			for (int32 i = 0; i < totalItems; i++) {
984				if (teamMenu->SubmenuAt(i) == NULL)
985					continue;
986
987				teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i));
988				if (teamItem->IsExpanded()) {
989					int32 teamCount = teamItem->Teams()->CountItems();
990					for (int32 j = 0; j < teamCount; j++) {
991						// The following code is almost a copy/paste from
992						// WindowMenu.cpp
993						team_id theTeam = (addr_t)teamItem->Teams()->ItemAt(j);
994						int32 count = 0;
995						int32* tokens = get_token_list(theTeam, &count);
996
997						for (int32 k = 0; k < count; k++) {
998							client_window_info* wInfo
999								= get_window_info(tokens[k]);
1000							if (wInfo == NULL)
1001								continue;
1002
1003							BString windowName(wInfo->name);
1004
1005							BString teamPrefix(teamItem->Label());
1006							teamPrefix.Append(": ");
1007
1008							BString teamSuffix(" - ");
1009							teamSuffix.Append(teamItem->Label());
1010
1011							if (windowName.StartsWith(teamPrefix))
1012								windowName.RemoveFirst(teamPrefix);
1013							if (windowName.EndsWith(teamSuffix))
1014								windowName.RemoveLast(teamSuffix);
1015
1016							if (TWindowMenu::WindowShouldBeListed(wInfo)) {
1017								// Check if we have a matching window item...
1018								item = teamItem->ExpandedWindowItem(
1019									wInfo->server_token);
1020								if (item != NULL) {
1021									item->SetTo(windowName,
1022										wInfo->server_token, wInfo->is_mini,
1023										((1 << current_workspace())
1024											& wInfo->workspaces) != 0);
1025
1026									if (strcasecmp(item->Label(), windowName)
1027											> 0) {
1028										item->SetLabel(windowName);
1029									}
1030									if (item->Modified())
1031										itemModified = true;
1032								} else if (teamItem->IsExpanded()) {
1033									// Add the item
1034									item = new TWindowMenuItem(windowName,
1035										wInfo->server_token, wInfo->is_mini,
1036										((1 << current_workspace())
1037											& wInfo->workspaces) != 0, false);
1038									item->SetExpanded(true);
1039									teamMenu->AddItem(item,
1040										TWindowMenuItem::InsertIndexFor(
1041											teamMenu, i + 1, item));
1042									resize = true;
1043								}
1044							}
1045							free(wInfo);
1046						}
1047						free(tokens);
1048					}
1049				}
1050			}
1051
1052			// Remove any remaining items which require an update.
1053			for (int32 i = 0; i < totalItems; i++) {
1054				if (!teamMenu->SubmenuAt(i)) {
1055					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
1056					if (item && item->RequiresUpdate()) {
1057						item = static_cast<TWindowMenuItem*>
1058							(teamMenu->RemoveItem(i));
1059						delete item;
1060						totalItems--;
1061
1062						resize = true;
1063					}
1064				}
1065			}
1066
1067			// If any of the WindowMenuItems changed state, we need to force a
1068			// repaint.
1069			if (itemModified || resize) {
1070				teamMenu->Invalidate();
1071				if (resize)
1072					teamMenu->SizeWindow(1);
1073			}
1074
1075			teamMenu->Window()->Unlock();
1076		}
1077
1078		sMonLocker.Unlock();
1079
1080		// sleep for a bit...
1081		snooze(150000);
1082	}
1083	return B_OK;
1084}
1085
1086
1087void
1088TExpandoMenuBar::_FinishedDrag(bool invoke)
1089{
1090	if (fPreviousDragTargetItem != NULL) {
1091		if (invoke)
1092			fPreviousDragTargetItem->Invoke();
1093
1094		fPreviousDragTargetItem->SetOverrideSelected(false);
1095		fPreviousDragTargetItem = NULL;
1096	}
1097
1098	if (!invoke && fBarView != NULL && fBarView->Dragging())
1099		fBarView->DragStop(true);
1100}
1101