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