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