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