xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision a09c983cc63b6d50c46a0f2a872fb1adbffcc363)
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 
37 #include "ExpandoMenuBar.h"
38 
39 #include <string.h>
40 
41 #include <Autolock.h>
42 #include <Bitmap.h>
43 #include <ControlLook.h>
44 #include <Debug.h>
45 #include <NodeInfo.h>
46 #include <Roster.h>
47 #include <Screen.h>
48 #include <Window.h>
49 
50 #include "icons.h"
51 
52 #include "BarApp.h"
53 #include "BarMenuTitle.h"
54 #include "BarView.h"
55 #include "BarWindow.h"
56 #include "DeskbarMenu.h"
57 #include "DeskbarUtils.h"
58 #include "InlineScrollView.h"
59 #include "ResourceSet.h"
60 #include "ShowHideMenuItem.h"
61 #include "StatusView.h"
62 #include "TeamMenuItem.h"
63 #include "WindowMenu.h"
64 #include "WindowMenuItem.h"
65 
66 
67 const float kMinMenuItemWidth = 50.0f;
68 const float kSepItemWidth = 5.0f;
69 const float kIconPadding = 8.0f;
70 
71 const uint32 kMinimizeTeam = 'mntm';
72 const uint32 kBringTeamToFront = 'bftm';
73 
74 bool TExpandoMenuBar::sDoMonitor = false;
75 thread_id TExpandoMenuBar::sMonThread = B_ERROR;
76 BLocker TExpandoMenuBar::sMonLocker("expando monitor");
77 
78 
79 TExpandoMenuBar::TExpandoMenuBar(BRect frame, const char* name,
80 	TBarView* barView, bool vertical)
81 	:
82 	BMenuBar(frame, name, B_FOLLOW_NONE,
83 		vertical ? B_ITEMS_IN_COLUMN : B_ITEMS_IN_ROW),
84 	fBarView(barView),
85 	fVertical(vertical),
86 	fOverflow(false),
87 	fDrawLabel(!static_cast<TBarApp*>(be_app)->Settings()->hideLabels),
88 	fShowTeamExpander(static_cast<TBarApp*>(be_app)->Settings()->superExpando),
89 	fExpandNewTeams(static_cast<TBarApp*>(be_app)->Settings()->expandNewTeams),
90 	fDeskbarMenuWidth(kMinMenuItemWidth),
91 	fPreviousDragTargetItem(NULL),
92 	fLastMousedOverItem(NULL),
93 	fLastClickedItem(NULL),
94 	fClickedExpander(false)
95 {
96 	SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f);
97 	SetFont(be_plain_font);
98 	SetMaxItemWidth();
99 
100 	// top or bottom mode, add deskbar menu and sep for menubar tracking
101 	// consistency
102 	const BBitmap* logoBitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE,
103 		R_LeafLogoBitmap);
104 	if (logoBitmap != NULL)
105 		fDeskbarMenuWidth = logoBitmap->Bounds().Width() + 16;
106 }
107 
108 
109 int
110 TExpandoMenuBar::CompareByName(const void* first, const void* second)
111 {
112 	return strcasecmp((*(static_cast<BarTeamInfo* const*>(first)))->name,
113 		(*(static_cast<BarTeamInfo* const*>(second)))->name);
114 }
115 
116 
117 void
118 TExpandoMenuBar::AttachedToWindow()
119 {
120 	BMenuBar::AttachedToWindow();
121 
122 	fTeamList.MakeEmpty();
123 
124 	if (fVertical) {
125 		sDoMonitor = true;
126 		sMonThread = spawn_thread(monitor_team_windows,
127 			"Expando Window Watcher", B_LOW_PRIORITY, this);
128 		resume_thread(sMonThread);
129 	}
130 }
131 
132 
133 void
134 TExpandoMenuBar::DetachedFromWindow()
135 {
136 	BMenuBar::DetachedFromWindow();
137 
138 	if (sMonThread != B_ERROR) {
139 		sDoMonitor = false;
140 
141 		status_t returnCode;
142 		wait_for_thread(sMonThread, &returnCode);
143 
144 		sMonThread = B_ERROR;
145 	}
146 
147 	BMessenger self(this);
148 	BMessage message(kUnsubscribe);
149 	message.AddMessenger("messenger", self);
150 	be_app->PostMessage(&message);
151 
152 	RemoveItems(0, CountItems(), true);
153 }
154 
155 
156 void
157 TExpandoMenuBar::MessageReceived(BMessage* message)
158 {
159 	int32 index;
160 	TTeamMenuItem* item;
161 
162 	switch (message->what) {
163 		case B_SOME_APP_LAUNCHED:
164 		{
165 			BList* teams = NULL;
166 			message->FindPointer("teams", (void**)&teams);
167 
168 			BBitmap* icon = NULL;
169 			message->FindPointer("icon", (void**)&icon);
170 
171 			const char* signature = NULL;
172 			message->FindString("sig", &signature);
173 
174 			uint32 flags = 0;
175 			message->FindInt32("flags", ((int32*) &flags));
176 
177 			const char* name = NULL;
178 			message->FindString("name", &name);
179 
180 			AddTeam(teams, icon, strdup(name), strdup(signature));
181 			break;
182 		}
183 
184 		case B_MOUSE_WHEEL_CHANGED:
185 		{
186 			float deltaY = 0;
187 			message->FindFloat("be:wheel_delta_y", &deltaY);
188 			if (deltaY == 0)
189 				return;
190 
191 			TInlineScrollView* scrollView
192 				= dynamic_cast<TInlineScrollView*>(Parent());
193 			if (scrollView == NULL)
194 				return;
195 
196 			float largeStep;
197 			float smallStep;
198 			scrollView->GetSteps(&smallStep, &largeStep);
199 
200 			// pressing the option/command/control key scrolls faster
201 			if (modifiers() & (B_OPTION_KEY | B_COMMAND_KEY | B_CONTROL_KEY))
202 				deltaY *= largeStep;
203 			else
204 				deltaY *= smallStep;
205 
206 			scrollView->ScrollBy(deltaY);
207 			break;
208 		}
209 
210 		case kAddTeam:
211 			AddTeam(message->FindInt32("team"), message->FindString("sig"));
212 			break;
213 
214 		case kRemoveTeam:
215 		{
216 			team_id team = -1;
217 			message->FindInt32("team", &team);
218 
219 			RemoveTeam(team, true);
220 			break;
221 		}
222 
223 		case B_SOME_APP_QUIT:
224 		{
225 			team_id team = -1;
226 			message->FindInt32("team", &team);
227 
228 			RemoveTeam(team, false);
229 			break;
230 		}
231 
232 		case kMinimizeTeam:
233 		{
234 			index = message->FindInt32("itemIndex");
235 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
236 			if (item == NULL)
237 				break;
238 
239 			TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW,
240 				item->Teams(),
241 				item->Menu()->ConvertToScreen(item->Frame()),
242 				true);
243 			break;
244 		}
245 
246 		case kBringTeamToFront:
247 		{
248 			index = message->FindInt32("itemIndex");
249 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
250 			if (item == NULL)
251 				break;
252 
253 			TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT,
254 				item->Teams(), item->Menu()->ConvertToScreen(item->Frame()),
255 				true);
256 			break;
257 		}
258 
259 		default:
260 			BMenuBar::MessageReceived(message);
261 			break;
262 	}
263 }
264 
265 
266 void
267 TExpandoMenuBar::MouseDown(BPoint where)
268 {
269 	fClickedExpander = false;
270 		// in case MouseUp() wasn't called
271 
272 	BMessage* message = Window()->CurrentMessage();
273 	BMenuItem* menuItem;
274 	TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem);
275 
276 	if (message == NULL || item == NULL || fBarView->Dragging()) {
277 		BMenuBar::MouseDown(where);
278 		return;
279 	}
280 
281 	int32 modifiers = 0;
282 	message->FindInt32("modifiers", &modifiers);
283 
284 	// check for three finger salute, a.k.a. Vulcan Death Grip
285 	if ((modifiers & B_COMMAND_KEY) != 0
286 		&& (modifiers & B_CONTROL_KEY) != 0
287 		&& (modifiers & B_SHIFT_KEY) != 0) {
288 		const BList* teams = item->Teams();
289 		int32 teamCount = teams->CountItems();
290 		team_id teamID;
291 		for (int32 team = 0; team < teamCount; team++) {
292 			teamID = (addr_t)teams->ItemAt(team);
293 			kill_team(teamID);
294 			RemoveTeam(teamID, false);
295 				// remove the team from display immediately
296 		}
297 		return;
298 			// absorb the message
299 	}
300 
301 	// control click - show all/hide all shortcut
302 	if ((modifiers & B_CONTROL_KEY) != 0) {
303 		// show/hide item's teams
304 		BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
305 			? kMinimizeTeam : kBringTeamToFront);
306 		showMessage.AddInt32("itemIndex", IndexOf(item));
307 		Window()->PostMessage(&showMessage, this);
308 		return;
309 			// absorb the message
310 	}
311 
312 	int32 buttons = 0;
313 	// check if within expander bounds to expand window items
314 	if (fVertical && fShowTeamExpander
315 		&& item->ExpanderBounds().Contains(where)
316 		&& message->FindInt32("buttons", &buttons) == B_OK
317 		&& buttons == B_PRIMARY_MOUSE_BUTTON) {
318 		// start the animation here, finish on mouse up
319 		fLastClickedItem = item;
320 		fClickedExpander = true;
321 		item->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW);
322 		Invalidate(item->ExpanderBounds());
323 		return;
324 			// absorb the message
325 	}
326 
327 	// double-click on an item brings the team to front
328 	int32 clicks;
329 	if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1
330 		&& item == menuItem && item == fLastClickedItem) {
331 		be_roster->ActivateApp((addr_t)item->Teams()->ItemAt(0));
332 			// activate this team
333 		return;
334 			// absorb the message
335 	}
336 
337 	fLastClickedItem = item;
338 	BMenuBar::MouseDown(where);
339 }
340 
341 
342 void
343 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message)
344 {
345 	int32 buttons;
346 	BMessage* currentMessage = Window()->CurrentMessage();
347 	if (currentMessage == NULL
348 		|| currentMessage->FindInt32("buttons", &buttons) != B_OK) {
349 		buttons = 0;
350 	}
351 
352 	if (message == NULL) {
353 		// force a cleanup
354 		_FinishedDrag();
355 
356 		switch (code) {
357 			case B_ENTERED_VIEW:
358 			{
359 				TTeamMenuItem* lastItem
360 					= dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
361 				if (fVertical && fShowTeamExpander && fClickedExpander
362 					&& lastItem != NULL && buttons == B_PRIMARY_MOUSE_BUTTON) {
363 					// Started expander animation, exited view then entered
364 					// again, redraw the expanded arrow
365 					lastItem->SetArrowDirection(BControlLook::B_RIGHT_DOWN_ARROW);
366 					Invalidate(lastItem->ExpanderBounds());
367 				}
368 				break;
369 			}
370 
371 			case B_INSIDE_VIEW:
372 			{
373 				BMenuItem* menuItem;
374 				TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem);
375 				TWindowMenuItem* windowMenuItem
376 					= dynamic_cast<TWindowMenuItem*>(menuItem);
377 
378 				if (item == NULL || menuItem == NULL) {
379 					// item is NULL, remove the tooltip and break out
380 					fLastMousedOverItem = NULL;
381 					SetToolTip((const char*)NULL);
382 					break;
383 				}
384 
385 				if (menuItem == fLastMousedOverItem) {
386 					// already set the tooltip for this item, break out
387 					break;
388 				}
389 
390 				if (windowMenuItem != NULL && fBarView->Vertical()
391 					&& fBarView->ExpandoState() && item->IsExpanded()) {
392 					// expando mode window menu item
393 					fLastMousedOverItem = menuItem;
394 					if (strcmp(windowMenuItem->Label(),
395 							windowMenuItem->FullTitle()) != 0) {
396 						// label is truncated, set tooltip
397 						SetToolTip(windowMenuItem->FullTitle());
398 					} else
399 						SetToolTip((const char*)NULL);
400 
401 					break;
402 				}
403 
404 				if (item->HasLabel()) {
405 					// item has a visible label, remove the tooltip and break out
406 					fLastMousedOverItem = menuItem;
407 					SetToolTip((const char*)NULL);
408 					break;
409 				}
410 
411 				SetToolTip(item->Name());
412 					// new item, set the tooltip to the item name
413 				fLastMousedOverItem = menuItem;
414 					// save the current menuitem for the next MouseMoved() call
415 				break;
416 			}
417 
418 			case B_OUTSIDE_VIEW:
419 				// NOTE: Should not be here, but for the sake of defensive
420 				// programming... fall-through
421 			case B_EXITED_VIEW:
422 			{
423 				TTeamMenuItem* lastItem
424 					= dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
425 				if (lastItem != NULL && fVertical && fShowTeamExpander
426 					&& fClickedExpander) {
427 					// Started expander animation, then exited view,
428 					// since we can't track outside mouse movements
429 					// redraw the original expander arrow
430 					lastItem->SetArrowDirection(lastItem->IsExpanded()
431 						? BControlLook::B_DOWN_ARROW
432 						: BControlLook::B_RIGHT_ARROW);
433 					Invalidate(lastItem->ExpanderBounds());
434 				}
435 				break;
436 			}
437 		}
438 
439 		BMenuBar::MouseMoved(where, code, message);
440 		return;
441 	}
442 
443 	if (buttons == 0)
444 		return;
445 
446 	switch (code) {
447 		case B_ENTERED_VIEW:
448 			// fPreviousDragTargetItem should always be NULL here anyways.
449 			if (fPreviousDragTargetItem != NULL)
450 				_FinishedDrag();
451 
452 			fBarView->CacheDragData(message);
453 			fPreviousDragTargetItem = NULL;
454 			break;
455 
456 		case B_OUTSIDE_VIEW:
457 			// NOTE: Should not be here, but for the sake of defensive
458 			// programming... fall-through
459 		case B_EXITED_VIEW:
460 			_FinishedDrag();
461 			break;
462 
463 		case B_INSIDE_VIEW:
464 			if (fBarView->Dragging()) {
465 				TTeamMenuItem* item = NULL;
466 				int32 itemCount = CountItems();
467 				for (int32 i = 0; i < itemCount; i++) {
468 					BMenuItem* _item = ItemAt(i);
469 					if (_item->Frame().Contains(where)) {
470 						item = dynamic_cast<TTeamMenuItem*>(_item);
471 						break;
472 					}
473 				}
474 				if (item == fPreviousDragTargetItem)
475 					break;
476 				if (fPreviousDragTargetItem != NULL)
477 					fPreviousDragTargetItem->SetOverrideSelected(false);
478 				if (item != NULL)
479 					item->SetOverrideSelected(true);
480 				fPreviousDragTargetItem = item;
481 			}
482 			break;
483 	}
484 }
485 
486 
487 void
488 TExpandoMenuBar::MouseUp(BPoint where)
489 {
490 	bool clickedExpander = fClickedExpander;
491 	fClickedExpander = false;
492 
493 	if (fBarView->Dragging()) {
494 		_FinishedDrag(true);
495 		return;
496 			// absorb the message
497 	}
498 
499 	TTeamMenuItem* item = TeamItemAtPoint(where, NULL);
500 	TTeamMenuItem* lastItem = dynamic_cast<TTeamMenuItem*>(fLastClickedItem);
501 	if (fVertical && fShowTeamExpander && clickedExpander) {
502 		if (item != NULL && lastItem != NULL && item == lastItem
503 			&& item->ExpanderBounds().Contains(where)) {
504 			// Toggle the expanded state
505 			BAutolock locker(sMonLocker);
506 				// let the update thread wait...
507 			item->ToggleExpandState(true);
508 			item->Draw();
509 			return;
510 				// absorb the message
511 		} else if (lastItem != NULL) {
512 			// User changed their mind, redraw the original expander arrow
513 			lastItem->SetArrowDirection(lastItem->IsExpanded()
514 				? BControlLook::B_DOWN_ARROW : BControlLook::B_RIGHT_ARROW);
515 			Invalidate(lastItem->ExpanderBounds());
516 		}
517 	}
518 
519 	BMenuBar::MouseUp(where);
520 }
521 
522 
523 void
524 TExpandoMenuBar::BuildItems()
525 {
526 	BMessenger self(this);
527 	TBarApp::Subscribe(self, &fTeamList);
528 
529 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
530 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
531 	fDrawLabel = !settings->hideLabels;
532 	fShowTeamExpander = settings->superExpando;
533 	fExpandNewTeams = settings->expandNewTeams;
534 
535 	float itemWidth = -0.1f;
536 	if (fVertical)
537 		itemWidth = Frame().Width();
538 	else {
539 		itemWidth = iconSize;
540 		if (fDrawLabel)
541 			itemWidth += sMinimumWindowWidth - kMinimumIconSize;
542 		else
543 			itemWidth += kIconPadding * 2;
544 	}
545 	float itemHeight = -1.0f;
546 
547 	RemoveItems(0, CountItems(), true);
548 		// remove all items
549 
550 	if (settings->sortRunningApps)
551 		fTeamList.SortItems(CompareByName);
552 
553 	int32 count = fTeamList.CountItems();
554 	for (int32 i = 0; i < count; i++) {
555 		// add items back
556 		BarTeamInfo* barInfo = (BarTeamInfo*)fTeamList.ItemAt(i);
557 		TTeamMenuItem* item = new TTeamMenuItem(barInfo->teams,
558 			barInfo->icon, barInfo->name, barInfo->sig, itemWidth,
559 			itemHeight, fDrawLabel, fVertical);
560 
561 		if (settings->trackerAlwaysFirst
562 			&& strcmp(barInfo->sig, kTrackerSignature) == 0) {
563 			AddItem(item, 0);
564 		} else
565 			AddItem(item);
566 	}
567 
568 	if (CountItems() == 0) {
569 		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
570 		// weird value - we just override it again
571 		ResizeTo(itemWidth, 0);
572 	}
573 }
574 
575 
576 bool
577 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const
578 {
579 	TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
580 	if (window) {
581 		if (TDeskbarMenu* bemenu = window->DeskbarMenu()) {
582 			bool inDeskbarMenu = false;
583 			if (bemenu->LockLooper()) {
584 				inDeskbarMenu = bemenu->Frame().Contains(loc);
585 				bemenu->UnlockLooper();
586 			}
587 			return inDeskbarMenu;
588 		}
589 	}
590 
591 	return false;
592 }
593 
594 
595 /*!	Returns the team menu item that belongs to the item under the
596 	specified \a point.
597 	If \a _item is given, it will return the exact menu item under
598 	that point (which might be a window item when the expander is on).
599 */
600 TTeamMenuItem*
601 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
602 {
603 	TTeamMenuItem* lastApp = NULL;
604 	int32 count = CountItems();
605 
606 	for (int32 i = 0; i < count; i++) {
607 		BMenuItem* item = ItemAt(i);
608 
609 		if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
610 			lastApp = (TTeamMenuItem*)item;
611 
612 		if (item && item->Frame().Contains(point)) {
613 			if (_item != NULL)
614 				*_item = item;
615 
616 			return lastApp;
617 		}
618 	}
619 
620 	// no item found
621 
622 	if (_item != NULL)
623 		*_item = NULL;
624 
625 	return NULL;
626 }
627 
628 
629 void
630 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name,
631 	char* signature)
632 {
633 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
634 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
635 
636 	float itemWidth = -1.0f;
637 	if (fVertical)
638 		itemWidth = fBarView->Bounds().Width();
639 	else {
640 		itemWidth = iconSize;
641 		if (fDrawLabel)
642 			itemWidth += sMinimumWindowWidth - kMinimumIconSize;
643 		else
644 			itemWidth += kIconPadding * 2;
645 	}
646 	float itemHeight = -1.0f;
647 
648 	TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature,
649 		itemWidth, itemHeight, fDrawLabel, fVertical);
650 
651 	if (settings->trackerAlwaysFirst && !strcmp(signature, kTrackerSignature))
652 		AddItem(item, 0);
653 	else if (settings->sortRunningApps) {
654 		TTeamMenuItem* teamItem
655 			= dynamic_cast<TTeamMenuItem*>(ItemAt(0));
656 		int32 firstApp = 0;
657 
658 		// if Tracker should always be the first item, we need to skip it
659 		// when sorting in the current item
660 		if (settings->trackerAlwaysFirst && teamItem != NULL
661 			&& !strcmp(teamItem->Signature(), kTrackerSignature)) {
662 			firstApp++;
663 		}
664 
665 		int32 i = firstApp;
666 		int32 itemCount = CountItems();
667 		while (i < itemCount) {
668 			teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
669 			if (teamItem != NULL && strcasecmp(teamItem->Name(), name) > 0) {
670 				AddItem(item, i);
671 				break;
672 			}
673 			i++;
674 		}
675 		// was the item added to the list yet?
676 		if (i == itemCount)
677 			AddItem(item);
678 	} else
679 		AddItem(item);
680 
681 	if (fVertical) {
682 		if (item && fShowTeamExpander && fExpandNewTeams)
683 			item->ToggleExpandState(false);
684 	}
685 
686 	SizeWindow(1);
687 	Window()->UpdateIfNeeded();
688 }
689 
690 
691 void
692 TExpandoMenuBar::AddTeam(team_id team, const char* signature)
693 {
694 	int32 count = CountItems();
695 	for (int32 i = 0; i < count; i++) {
696 		// Only add to team menu items
697 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
698 			if (strcasecmp(item->Signature(), signature) == 0) {
699 				if (!(item->Teams()->HasItem((void*)(addr_t)team)))
700 					item->Teams()->AddItem((void*)(addr_t)team);
701 				break;
702 			}
703 		}
704 	}
705 }
706 
707 
708 void
709 TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
710 {
711 	TWindowMenuItem* windowItem = NULL;
712 
713 	for (int32 i = CountItems() - 1; i >= 0; i--) {
714 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
715 			if (item->Teams()->HasItem((void*)(addr_t)team)) {
716 				item->Teams()->RemoveItem(team);
717 				if (partial)
718 					return;
719 
720 				BAutolock locker(sMonLocker);
721 					// make the update thread wait
722 				RemoveItem(i);
723 				if (item == fPreviousDragTargetItem)
724 					fPreviousDragTargetItem = NULL;
725 				if (item == fLastMousedOverItem)
726 					fLastMousedOverItem = NULL;
727 				if (item == fLastClickedItem)
728 					fLastClickedItem = NULL;
729 				delete item;
730 				while ((windowItem = dynamic_cast<TWindowMenuItem*>(
731 						ItemAt(i))) != NULL) {
732 					// Also remove window items (if there are any)
733 					RemoveItem(i);
734 					if (windowItem == fLastMousedOverItem)
735 						fLastMousedOverItem = NULL;
736 					if (windowItem == fLastClickedItem)
737 						fLastClickedItem = NULL;
738 					delete windowItem;
739 				}
740 				SizeWindow(-1);
741 				Window()->UpdateIfNeeded();
742 				return;
743 			}
744 		}
745 	}
746 }
747 
748 
749 void
750 TExpandoMenuBar::CheckItemSizes(int32 delta)
751 {
752 	if (fBarView->Vertical())
753 		return;
754 
755 	float maxWidth = fBarView->DragRegion()->Frame().left
756 		- fDeskbarMenuWidth - kSepItemWidth;
757 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
758 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
759 	float minItemWidth = fDrawLabel
760 		? iconOnlyWidth + kMinMenuItemWidth
761 		: iconOnlyWidth - kIconPadding;
762 	float maxItemWidth = fDrawLabel
763 		? sMinimumWindowWidth + iconSize - kMinimumIconSize
764 		: iconOnlyWidth;
765 	float menuWidth = maxItemWidth * CountItems() + fDeskbarMenuWidth
766 		+ kSepItemWidth;
767 
768 	bool reset = false;
769 	float newWidth = -1.0f;
770 
771 	if (delta >= 0 && menuWidth > maxWidth) {
772 		fOverflow = true;
773 		reset = true;
774 		newWidth = floorf(maxWidth / CountItems());
775 	} else if (delta < 0 && fOverflow) {
776 		reset = true;
777 		if (menuWidth > maxWidth)
778 			newWidth = floorf(maxWidth / CountItems());
779 		else
780 			newWidth = maxItemWidth;
781 	}
782 
783 	if (reset) {
784 		if (newWidth > maxItemWidth)
785 			newWidth = maxItemWidth;
786 		else if (newWidth < minItemWidth)
787 			newWidth = minItemWidth;
788 
789 		SetMaxContentWidth(newWidth);
790 		if (newWidth == maxItemWidth)
791 			fOverflow = false;
792 
793 		InvalidateLayout();
794 
795 		for (int32 index = 0; ; index++) {
796 			TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index);
797 			if (item == NULL)
798 				break;
799 
800 			item->SetOverrideWidth(newWidth);
801 		}
802 
803 		Invalidate();
804 		Window()->UpdateIfNeeded();
805 		fBarView->CheckForScrolling();
806 	}
807 }
808 
809 
810 menu_layout
811 TExpandoMenuBar::MenuLayout() const
812 {
813 	return Layout();
814 }
815 
816 
817 void
818 TExpandoMenuBar::Draw(BRect updateRect)
819 {
820 	BMenu::Draw(updateRect);
821 }
822 
823 
824 void
825 TExpandoMenuBar::DrawBackground(BRect updateRect)
826 {
827 	if (fVertical)
828 		return;
829 
830 	BRect bounds(Bounds());
831 	rgb_color menuColor = LowColor();
832 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
833 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
834 
835 	int32 count = CountItems() - 1;
836 	if (count >= 0)
837 		bounds.left = ItemAt(count)->Frame().right + 1;
838 	else
839 		bounds.left = 0;
840 
841 	if (be_control_look != NULL) {
842 		SetHighColor(tint_color(menuColor, 1.22));
843 		StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
844 		bounds.left++;
845 		uint32 borders = BControlLook::B_TOP_BORDER
846 			| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
847 
848 		be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
849 			0, borders);
850 	} else {
851 		SetHighColor(vlight);
852 		StrokeLine(bounds.LeftTop(), bounds.RightTop());
853 		StrokeLine(BPoint(bounds.left, bounds.top + 1), bounds.LeftBottom());
854 		SetHighColor(hilite);
855 		StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
856 			bounds.RightBottom());
857 	}
858 }
859 
860 
861 /*!	Something to help determine if we are showing too many apps
862 	need to add in scrolling functionality.
863 */
864 bool
865 TExpandoMenuBar::CheckForSizeOverrun()
866 {
867 	if (fVertical) {
868 		BRect screenFrame = (BScreen(Window())).Frame();
869 		return Window()->Frame().bottom > screenFrame.bottom;
870 	}
871 
872 	// horizontal
873 	int32 count = CountItems() - 1;
874 	if (count < 0)
875 		return false;
876 
877 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
878 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
879 	float minItemWidth = fDrawLabel
880 		? iconOnlyWidth + kMinMenuItemWidth
881 		: iconOnlyWidth - kIconPadding;
882 	float menuWidth = minItemWidth * CountItems() + fDeskbarMenuWidth
883 		+ kSepItemWidth;
884 	float maxWidth = fBarView->DragRegion()->Frame().left
885 		- fDeskbarMenuWidth - kSepItemWidth;
886 
887 	return menuWidth > maxWidth;
888 }
889 
890 
891 void
892 TExpandoMenuBar::SetMaxItemWidth()
893 {
894 	if (fVertical)
895 		SetMaxContentWidth(sMinimumWindowWidth);
896 	else {
897 		// Make more room for the icon in horizontal mode
898 		int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
899 		SetMaxContentWidth(sMinimumWindowWidth + iconSize
900 			- kMinimumIconSize);
901 	}
902 }
903 
904 
905 void
906 TExpandoMenuBar::SizeWindow(int32 delta)
907 {
908 	// instead of resizing the window here and there in the
909 	// code the resize method will be centered in one place
910 	// thus, the same behavior (good or bad) will be used
911 	// wherever window sizing is done
912 	if (fVertical) {
913 		BRect screenFrame = (BScreen(Window())).Frame();
914 		fBarView->SizeWindow(screenFrame);
915 		fBarView->PositionWindow(screenFrame);
916 		fBarView->CheckForScrolling();
917 	} else
918 		CheckItemSizes(delta);
919 }
920 
921 
922 int32
923 TExpandoMenuBar::monitor_team_windows(void* arg)
924 {
925 	TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg;
926 
927 	while (teamMenu->sDoMonitor) {
928 		sMonLocker.Lock();
929 
930 		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
931 			int32 totalItems = teamMenu->CountItems();
932 
933 			// Set all WindowMenuItems to require an update.
934 			TWindowMenuItem* item = NULL;
935 			for (int32 i = 0; i < totalItems; i++) {
936 				if (!teamMenu->SubmenuAt(i)) {
937 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
938 					item->SetRequireUpdate();
939 				}
940 			}
941 
942 			// Perform SetTo() on all the items that still exist as well as add
943 			// new items.
944 			bool itemModified = false, resize = false;
945 			TTeamMenuItem* teamItem = NULL;
946 
947 			for (int32 i = 0; i < totalItems; i++) {
948 				if (teamMenu->SubmenuAt(i) == NULL)
949 					continue;
950 
951 				teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i));
952 				if (teamItem->IsExpanded()) {
953 					int32 teamCount = teamItem->Teams()->CountItems();
954 					for (int32 j = 0; j < teamCount; j++) {
955 						// The following code is almost a copy/paste from
956 						// WindowMenu.cpp
957 						team_id	theTeam = (addr_t)teamItem->Teams()->ItemAt(j);
958 						int32 count = 0;
959 						int32* tokens = get_token_list(theTeam, &count);
960 
961 						for (int32 k = 0; k < count; k++) {
962 							client_window_info* wInfo
963 								= get_window_info(tokens[k]);
964 							if (wInfo == NULL)
965 								continue;
966 
967 							if (TWindowMenu::WindowShouldBeListed(wInfo)) {
968 								// Check if we have a matching window item...
969 								item = teamItem->ExpandedWindowItem(
970 									wInfo->server_token);
971 								if (item) {
972 									item->SetTo(wInfo->name,
973 										wInfo->server_token, wInfo->is_mini,
974 										((1 << current_workspace())
975 											& wInfo->workspaces) != 0);
976 
977 									if (strcmp(wInfo->name,
978 										item->Label()) != 0)
979 										item->SetLabel(wInfo->name);
980 
981 									if (item->ChangedState())
982 										itemModified = true;
983 								} else if (teamItem->IsExpanded()) {
984 									// Add the item
985 									item = new TWindowMenuItem(wInfo->name,
986 										wInfo->server_token, wInfo->is_mini,
987 										((1 << current_workspace())
988 											& wInfo->workspaces) != 0, false);
989 									item->ExpandedItem(true);
990 									teamMenu->AddItem(item,
991 										TWindowMenuItem::InsertIndexFor(
992 											teamMenu, i + 1, item));
993 									resize = true;
994 								}
995 							}
996 							free(wInfo);
997 						}
998 						free(tokens);
999 					}
1000 				}
1001 			}
1002 
1003 			// Remove any remaining items which require an update.
1004 			for (int32 i = 0; i < totalItems; i++) {
1005 				if (!teamMenu->SubmenuAt(i)) {
1006 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
1007 					if (item && item->RequiresUpdate()) {
1008 						item = static_cast<TWindowMenuItem*>
1009 							(teamMenu->RemoveItem(i));
1010 						delete item;
1011 						totalItems--;
1012 
1013 						resize = true;
1014 					}
1015 				}
1016 			}
1017 
1018 			// If any of the WindowMenuItems changed state, we need to force a
1019 			// repaint.
1020 			if (itemModified || resize) {
1021 				teamMenu->Invalidate();
1022 				if (resize)
1023 					teamMenu->SizeWindow(1);
1024 			}
1025 
1026 			teamMenu->Window()->Unlock();
1027 		}
1028 
1029 		sMonLocker.Unlock();
1030 
1031 		// sleep for a bit...
1032 		snooze(150000);
1033 	}
1034 	return B_OK;
1035 }
1036 
1037 
1038 void
1039 TExpandoMenuBar::_FinishedDrag(bool invoke)
1040 {
1041 	if (fPreviousDragTargetItem != NULL) {
1042 		if (invoke)
1043 			fPreviousDragTargetItem->Invoke();
1044 		fPreviousDragTargetItem->SetOverrideSelected(false);
1045 		fPreviousDragTargetItem = NULL;
1046 	}
1047 	if (!invoke && fBarView->Dragging())
1048 		fBarView->DragStop(true);
1049 }
1050