xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision 1345706a9ff6ad0dc041339a02d4259998b0765d)
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 && item != NULL && !fBarView->Dragging()) {
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 			const BList* teams = item->Teams();
313 			int32 teamCount = teams->CountItems();
314 
315 			team_id teamID;
316 			for (int32 team = 0; team < teamCount; team++) {
317 				teamID = (team_id)teams->ItemAt(team);
318 				kill_team(teamID);
319 				// remove the team immediately from display
320 				RemoveTeam(teamID, false);
321 			}
322 
323 			return;
324 		}
325 
326 		// control click - show all/hide all shortcut
327 		if ((modifiers & B_CONTROL_KEY) != 0) {
328 			// show/hide item's teams
329 			BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
330 				? kMinimizeTeam : kBringTeamToFront);
331 			showMessage.AddInt32("itemIndex", IndexOf(item));
332 			Window()->PostMessage(&showMessage, this);
333 			return;
334 		}
335 
336 		// Check the bounds of the expand Team icon
337 		if (fShowTeamExpander && fVertical) {
338 			BRect expanderRect = item->ExpanderBounds();
339 			if (expanderRect.Contains(where)) {
340 				// Let the update thread wait...
341 				BAutolock locker(sMonLocker);
342 
343 				// Toggle the item
344 				item->ToggleExpandState(true);
345 				item->Draw();
346 
347 				// Absorb the message.
348 				return;
349 			}
350 		}
351 
352 		// double-click on an item brings the team to front
353 		int32 clicks;
354 		if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1
355 			&& item == menuItem && item == fLastClickItem) {
356 			// activate this team
357 			be_roster->ActivateApp((team_id)item->Teams()->ItemAt(0));
358 			return;
359 		}
360 
361 		fLastClickItem = item;
362 	}
363 
364 	BMenuBar::MouseDown(where);
365 }
366 
367 
368 void
369 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message)
370 {
371 	if (!message) {
372 		// force a cleanup
373 		_FinishedDrag();
374 		BMenuBar::MouseMoved(where, code, message);
375 		return;
376 	}
377 
378 	uint32 buttons;
379 	if (!(Window()->CurrentMessage())
380 		|| Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons)
381 		< B_OK)
382 		buttons = 0;
383 
384 	if (buttons == 0)
385 		return;
386 
387 	switch (code) {
388 		case B_ENTERED_VIEW:
389 			// fPreviousDragTargetItem should always be NULL here anyways.
390 			if (fPreviousDragTargetItem)
391 				_FinishedDrag();
392 
393 			fBarView->CacheDragData(message);
394 			fPreviousDragTargetItem = NULL;
395 			break;
396 
397 		case B_OUTSIDE_VIEW:
398 			// NOTE: Should not be here, but for the sake of defensive
399 			// programming...
400 		case B_EXITED_VIEW:
401 			_FinishedDrag();
402 			break;
403 
404 		case B_INSIDE_VIEW:
405 			if (fBarView->Dragging()) {
406 				TTeamMenuItem* item = NULL;
407 				for (int32 i = 0; i < CountItems(); i++) {
408 					BMenuItem* _item = ItemAt(i);
409 					if (_item->Frame().Contains(where)) {
410 						item = dynamic_cast<TTeamMenuItem*>(_item);
411 						break;
412 					}
413 				}
414 				if (item == fPreviousDragTargetItem)
415 					break;
416 				if (fPreviousDragTargetItem != NULL)
417 					fPreviousDragTargetItem->SetOverrideSelected(false);
418 				if (item != NULL)
419 					item->SetOverrideSelected(true);
420 				fPreviousDragTargetItem = item;
421 			}
422 			break;
423 	}
424 }
425 
426 
427 void
428 TExpandoMenuBar::MouseUp(BPoint where)
429 {
430 	if (!fBarView->Dragging()) {
431 		BMenuBar::MouseUp(where);
432 		return;
433 	}
434 
435 	_FinishedDrag(true);
436 }
437 
438 
439 bool
440 TExpandoMenuBar::InBeMenu(BPoint loc) const
441 {
442 	if (!fVertical) {
443 		if (fBeMenuItem && fBeMenuItem->Frame().Contains(loc))
444 			return true;
445 	} else {
446 		TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
447 		if (window) {
448 			if (TBeMenu* bemenu = window->BeMenu()) {
449 				bool inBeMenu = false;
450 				if (bemenu->LockLooper()) {
451 					inBeMenu = bemenu->Frame().Contains(loc);
452 					bemenu->UnlockLooper();
453 				}
454 				return inBeMenu;
455 			}
456 		}
457 	}
458 
459 	return false;
460 }
461 
462 
463 /*!	Returns the team menu item that belongs to the item under the
464 	specified \a point.
465 	If \a _item is given, it will return the exact menu item under
466 	that point (which might be a window item when the expander is on).
467 */
468 TTeamMenuItem*
469 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
470 {
471 	TTeamMenuItem* lastApp = NULL;
472 	int32 count = CountItems();
473 
474 	for (int32 i = fFirstApp; i < count; i++) {
475 		BMenuItem* item = ItemAt(i);
476 
477 		if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
478 			lastApp = (TTeamMenuItem*)item;
479 
480 		if (item && item->Frame().Contains(point)) {
481 			if (_item != NULL)
482 				*_item = item;
483 
484 			return lastApp;
485 		}
486 	}
487 
488 	// no item found
489 
490 	if (_item != NULL)
491 		*_item = NULL;
492 
493 	return NULL;
494 }
495 
496 
497 void
498 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name,
499 	char* signature)
500 {
501 	float itemWidth = fVertical ? fBarView->Bounds().Width()
502 		: sMinimumWindowWidth;
503 	float itemHeight = -1.0f;
504 
505 	desk_settings* settings = ((TBarApp*)be_app)->Settings();
506 	TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature,
507 		itemWidth, itemHeight, fDrawLabel, fVertical);
508 
509 	if (settings->trackerAlwaysFirst && !strcmp(signature, kTrackerSignature)) {
510 		AddItem(item, fFirstApp);
511 	} else if (settings->sortRunningApps) {
512 		TTeamMenuItem* teamItem
513 			= dynamic_cast<TTeamMenuItem*>(ItemAt(fFirstApp));
514 		int32 firstApp = fFirstApp;
515 
516 		// if Tracker should always be the first item, we need to skip it
517 		// when sorting in the current item
518 		if (settings->trackerAlwaysFirst && teamItem != NULL
519 			&& !strcmp(teamItem->Signature(), kTrackerSignature)) {
520 			firstApp++;
521 		}
522 
523 		int32 count = CountItems(), i;
524 		for (i = firstApp; i < count; i++) {
525 			teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
526 			if (teamItem != NULL && strcasecmp(teamItem->Name(), name) > 0) {
527 				AddItem(item, i);
528 				break;
529 			}
530 		}
531 		// was the item added to the list yet?
532 		if (i == count)
533 			AddItem(item);
534 	} else
535 		AddItem(item);
536 
537 	if (fVertical) {
538 		if (item && fShowTeamExpander && fExpandNewTeams)
539 			item->ToggleExpandState(false);
540 
541 		fBarView->SizeWindow(BScreen(Window()).Frame());
542 	} else
543 		CheckItemSizes(1);
544 
545 	Window()->UpdateIfNeeded();
546 }
547 
548 
549 void
550 TExpandoMenuBar::AddTeam(team_id team, const char* signature)
551 {
552 	int32 count = CountItems();
553 	for (int32 i = fFirstApp; i < count; i++) {
554 		// Only add to team menu items
555 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
556 			if (strcasecmp(item->Signature(), signature) == 0) {
557 				if (!(item->Teams()->HasItem((void*)team)))
558 					item->Teams()->AddItem((void*)team);
559 
560 				break;
561 			}
562 		}
563 	}
564 }
565 
566 
567 void
568 TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
569 {
570 	int32 count = CountItems();
571 	for (int32 i = fFirstApp; i < count; i++) {
572 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
573 			if (item->Teams()->HasItem((void*)team)) {
574 				item->Teams()->RemoveItem(team);
575 
576 				if (partial)
577 					return;
578 
579 #ifdef DOUBLECLICKBRINGSTOFRONT
580 				if (fLastClickItem == i)
581 					fLastClickItem = -1;
582 #endif
583 
584 				RemoveItem(i);
585 
586 				if (fVertical) {
587 					//	instead of resizing the window here and there in the
588 					//	code the resize method will be centered in one place
589 					//	thus, the same behavior (good or bad) will be used
590 					//	whereever window sizing is done
591 					fBarView->SizeWindow(BScreen(Window()).Frame());
592 				} else
593 					CheckItemSizes(-1);
594 
595 				Window()->UpdateIfNeeded();
596 
597 				delete item;
598 				return;
599 			}
600 		}
601 	}
602 }
603 
604 
605 void
606 TExpandoMenuBar::CheckItemSizes(int32 delta)
607 {
608 	float width = Frame().Width();
609 	int32 count = CountItems();
610 	bool reset = false;
611 	float newWidth = 0;
612 	float fullWidth = (sMinimumWindowWidth * count);
613 
614 	if (!fBarView->Vertical()) {
615 		// in this case there are 2 extra items:
616 		//		The Be Menu
617 		//		The little separator item
618 		fullWidth = fullWidth - (sMinimumWindowWidth * 2)
619 			+ (fBeMenuWidth + kSepItemWidth);
620 		width -= (fBeMenuWidth + kSepItemWidth);
621 		count -= 2;
622 	}
623 
624 	if (delta >= 0 && fullWidth > width) {
625 		fOverflow = true;
626 		reset = true;
627 		newWidth = floorf(width/count);
628 	} else if (delta < 0 && fOverflow) {
629 		reset = true;
630 		if (fullWidth > width)
631 			newWidth = floorf(width/count);
632 		else
633 			newWidth = sMinimumWindowWidth;
634 	}
635 	if (newWidth > sMinimumWindowWidth)
636 		newWidth = sMinimumWindowWidth;
637 
638 	if (reset) {
639 		SetMaxContentWidth(newWidth);
640 		if (newWidth == sMinimumWindowWidth)
641 			fOverflow = false;
642 		InvalidateLayout();
643 
644 		for (int32 index = fFirstApp; ; index++) {
645 			TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index);
646 			if (!item)
647 				break;
648 			item->SetOverrideWidth(newWidth);
649 		}
650 
651 		Invalidate();
652 		Window()->UpdateIfNeeded();
653 	}
654 }
655 
656 
657 menu_layout
658 TExpandoMenuBar::MenuLayout() const
659 {
660 	return Layout();
661 }
662 
663 
664 void
665 TExpandoMenuBar::Draw(BRect update)
666 {
667 	BMenu::Draw(update);
668 }
669 
670 
671 void
672 TExpandoMenuBar::DrawBackground(BRect)
673 {
674 	if (fVertical)
675 		return;
676 
677 	BRect bounds(Bounds());
678 	rgb_color menuColor = ViewColor();
679 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
680 	rgb_color dark = tint_color(menuColor, B_DARKEN_2_TINT);
681 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
682 
683 	int32 last = CountItems() - 1;
684 	if (last >= 0)
685 		bounds.left = ItemAt(last)->Frame().right + 1;
686 	else
687 		bounds.left = 0;
688 
689 	if (be_control_look != NULL) {
690 		SetHighColor(tint_color(menuColor, 1.22));
691 		StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
692 		bounds.left++;
693 		uint32 borders = BControlLook::B_TOP_BORDER
694 			| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
695 
696 		be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
697 			0, borders);
698 	} else {
699 		SetHighColor(vlight);
700 		StrokeLine(bounds.LeftTop(), bounds.RightTop());
701 		StrokeLine(BPoint(bounds.left, bounds.top + 1), bounds.LeftBottom());
702 		SetHighColor(hilite);
703 		StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
704 			bounds.RightBottom());
705 	}
706 }
707 
708 
709 /*!	Something to help determine if we are showing too many apps
710 	need to add in scrolling functionality.
711 */
712 void
713 TExpandoMenuBar::CheckForSizeOverrun()
714 {
715 	BRect screenFrame = (BScreen(Window())).Frame();
716 	if (fVertical)
717 		fIsScrolling = Window()->Frame().bottom > screenFrame.bottom;
718 	else
719 		fIsScrolling = 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