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