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