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