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