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