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