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