xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision 6c4a44e36ba846c54467103f884d65dfa13e7fcb)
1 /*
2 Open Tracker License
3 
4 Terms and Conditions
5 
6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7 
8 Permission is hereby granted, free of charge, to any person obtaining a copy of
9 this software and associated documentation files (the "Software"), to deal in
10 the Software without restriction, including without limitation the rights to
11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12 of the Software, and to permit persons to whom the Software is furnished to do
13 so, subject to the following conditions:
14 
15 The above copyright notice and this permission notice applies to all licensees
16 and shall be included in all copies or substantial portions of the Software.
17 
18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 
25 Except as contained in this notice, the name of Be Incorporated shall not be
26 used in advertising or otherwise to promote the sale, use or other dealings in
27 this Software without prior written authorization from Be Incorporated.
28 
29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30 trademarks of Be Incorporated in the United States and other countries. Other
31 brand product names are registered trademarks or trademarks of their respective
32 holders.
33 All rights reserved.
34 */
35 
36 
37 #include "ExpandoMenuBar.h"
38 
39 #include <string.h>
40 
41 #include <Autolock.h>
42 #include <Bitmap.h>
43 #include <ControlLook.h>
44 #include <Debug.h>
45 #include <NodeInfo.h>
46 #include <Roster.h>
47 #include <Screen.h>
48 
49 #include "icons.h"
50 
51 #include "BarApp.h"
52 #include "BarMenuTitle.h"
53 #include "BarView.h"
54 #include "BarWindow.h"
55 #include "DeskbarMenu.h"
56 #include "DeskbarUtils.h"
57 #include "ResourceSet.h"
58 #include "ShowHideMenuItem.h"
59 #include "StatusView.h"
60 #include "TeamMenuItem.h"
61 #include "WindowMenu.h"
62 #include "WindowMenuItem.h"
63 
64 
65 const float kDefaultDeskbarMenuWidth = 50.0f;
66 const float kSepItemWidth = 5.0f;
67 const float kIconPadding = 8.0f;
68 
69 const uint32 kMinimizeTeam = 'mntm';
70 const uint32 kBringTeamToFront = 'bftm';
71 
72 bool TExpandoMenuBar::sDoMonitor = false;
73 thread_id TExpandoMenuBar::sMonThread = B_ERROR;
74 BLocker TExpandoMenuBar::sMonLocker("expando monitor");
75 
76 
77 TExpandoMenuBar::TExpandoMenuBar(TBarView* bar, BRect frame, const char* name,
78 	bool vertical, bool drawLabel)
79 	:
80 	BMenuBar(frame, name, B_FOLLOW_NONE,
81 		vertical ? B_ITEMS_IN_COLUMN : B_ITEMS_IN_ROW, vertical),
82 	fVertical(vertical),
83 	fOverflow(false),
84 	fDrawLabel(drawLabel),
85 	fIsScrolling(false),
86 	fShowTeamExpander(static_cast<TBarApp*>(be_app)->Settings()->superExpando),
87 	fExpandNewTeams(static_cast<TBarApp*>(be_app)->Settings()->expandNewTeams),
88 	fDeskbarMenuWidth(kDefaultDeskbarMenuWidth),
89 	fBarView(bar),
90 	fFirstApp(0),
91 	fPreviousDragTargetItem(NULL),
92 	fLastClickItem(NULL)
93 {
94 	SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f);
95 	SetFont(be_plain_font);
96 	if (fVertical)
97 		SetMaxContentWidth(sMinimumWindowWidth);
98 	else {
99 		// Make more room for the icon in horizontal mode
100 		int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
101 		float maxContentWidth = sMinimumWindowWidth + iconSize
102 			- kMinimumIconSize;
103 		SetMaxContentWidth(maxContentWidth);
104 	}
105 }
106 
107 
108 int
109 TExpandoMenuBar::CompareByName(const void* first, const void* second)
110 {
111 	return strcasecmp((*(static_cast<BarTeamInfo* const*>(first)))->name,
112 		(*(static_cast<BarTeamInfo* const*>(second)))->name);
113 }
114 
115 
116 void
117 TExpandoMenuBar::AttachedToWindow()
118 {
119 	BMessenger self(this);
120 	BList teamList;
121 	TBarApp::Subscribe(self, &teamList);
122 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
123 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
124 
125 	float itemWidth = -0.1f;
126 	if (fVertical)
127 		itemWidth = Frame().Width();
128 	else {
129 		itemWidth = iconSize;
130 		if (fDrawLabel)
131 			itemWidth += sMinimumWindowWidth - kMinimumIconSize;
132 		else
133 			itemWidth += kIconPadding * 2;
134 	}
135 	float itemHeight = -1.0f;
136 
137 	// top or bottom mode, add deskbar menu and sep for menubar tracking
138 	// consistency
139 	if (!fVertical) {
140 		TDeskbarMenu* beMenu = new TDeskbarMenu(fBarView);
141 		TBarWindow::SetDeskbarMenu(beMenu);
142 		const BBitmap* logoBitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE,
143 			R_LeafLogoBitmap);
144 		if (logoBitmap != NULL)
145 			fDeskbarMenuWidth = logoBitmap->Bounds().Width() + 16;
146 
147 		fDeskbarMenuItem = new TBarMenuTitle(fDeskbarMenuWidth,
148 			Frame().Height(), logoBitmap, beMenu, true);
149 		AddItem(fDeskbarMenuItem);
150 
151 		fSeparatorItem = new TTeamMenuItem(kSepItemWidth, itemHeight, fVertical);
152 		AddItem(fSeparatorItem);
153 		fSeparatorItem->SetEnabled(false);
154 		fFirstApp = 2;
155 	} else {
156 		fDeskbarMenuItem = NULL;
157 		fSeparatorItem = NULL;
158 	}
159 
160 	if (settings->sortRunningApps)
161 		teamList.SortItems(CompareByName);
162 
163 	int32 count = teamList.CountItems();
164 	for (int32 i = 0; i < count; i++) {
165 		BarTeamInfo* barInfo = (BarTeamInfo*)teamList.ItemAt(i);
166 		if ((barInfo->flags & B_BACKGROUND_APP) == 0
167 			&& strcasecmp(barInfo->sig, kDeskbarSignature) != 0) {
168 			if (settings->trackerAlwaysFirst
169 				&& !strcmp(barInfo->sig, kTrackerSignature)) {
170 				AddItem(new TTeamMenuItem(barInfo->teams, barInfo->icon,
171 					barInfo->name, barInfo->sig, itemWidth, itemHeight,
172 					fDrawLabel, fVertical), fFirstApp);
173 			} else {
174 				AddItem(new TTeamMenuItem(barInfo->teams, barInfo->icon,
175 					barInfo->name, barInfo->sig, itemWidth, itemHeight,
176 					fDrawLabel, fVertical));
177 			}
178 
179 			barInfo->teams = NULL;
180 			barInfo->icon = NULL;
181 			barInfo->name = NULL;
182 			barInfo->sig = NULL;
183 		}
184 
185 		delete barInfo;
186 	}
187 
188 	BMenuBar::AttachedToWindow();
189 
190 	if (CountItems() == 0) {
191 		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
192 		// weird value - we just override it again
193 		ResizeTo(itemWidth, 0);
194 	}
195 
196 	if (fVertical) {
197 		sDoMonitor = true;
198 		sMonThread = spawn_thread(monitor_team_windows,
199 			"Expando Window Watcher", B_LOW_PRIORITY, this);
200 		resume_thread(sMonThread);
201 	}
202 }
203 
204 
205 void
206 TExpandoMenuBar::DetachedFromWindow()
207 {
208 	BMenuBar::DetachedFromWindow();
209 
210 	if (sMonThread != B_ERROR) {
211 		sDoMonitor = false;
212 
213 		status_t returnCode;
214 		wait_for_thread(sMonThread, &returnCode);
215 
216 		sMonThread = B_ERROR;
217 	}
218 
219 	BMessenger self(this);
220 	BMessage message(kUnsubscribe);
221 	message.AddMessenger("messenger", self);
222 	be_app->PostMessage(&message);
223 
224 	RemoveItems(0, CountItems(), true);
225 }
226 
227 
228 void
229 TExpandoMenuBar::MessageReceived(BMessage* message)
230 {
231 	int32 index;
232 	TTeamMenuItem* item;
233 
234 	switch (message->what) {
235 		case B_SOME_APP_LAUNCHED: {
236 			BList* teams = NULL;
237 			message->FindPointer("teams", (void**)&teams);
238 
239 			BBitmap* icon = NULL;
240 			message->FindPointer("icon", (void**)&icon);
241 
242 			const char* signature;
243 			if (message->FindString("sig", &signature) == B_OK
244 				&&strcasecmp(signature, kDeskbarSignature) == 0) {
245 				delete teams;
246 				delete icon;
247 				break;
248 			}
249 
250 			uint32 flags;
251 			if (message->FindInt32("flags", ((int32*) &flags)) == B_OK
252 				&& (flags & B_BACKGROUND_APP) != 0) {
253 				delete teams;
254 				delete icon;
255 				break;
256 			}
257 
258 			const char* name = NULL;
259 			message->FindString("name", &name);
260 
261 			AddTeam(teams, icon, strdup(name), strdup(signature));
262 			break;
263 		}
264 
265 		case kAddTeam:
266 			AddTeam(message->FindInt32("team"), message->FindString("sig"));
267 			break;
268 
269 		case kRemoveTeam:
270 		{
271 			team_id team = -1;
272 			message->FindInt32("team", &team);
273 
274 			RemoveTeam(team, true);
275 			break;
276 		}
277 
278 		case B_SOME_APP_QUIT:
279 		{
280 			team_id team = -1;
281 			message->FindInt32("team", &team);
282 
283 			RemoveTeam(team, false);
284 			break;
285 		}
286 
287 		case kMinimizeTeam:
288 		{
289 			index = message->FindInt32("itemIndex");
290 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
291 			if (item == NULL)
292 				break;
293 
294 			TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW,
295 				item->Teams(),
296 				item->Menu()->ConvertToScreen(item->Frame()),
297 				true);
298 			break;
299 		}
300 
301 		case kBringTeamToFront:
302 		{
303 			index = message->FindInt32("itemIndex");
304 			item = dynamic_cast<TTeamMenuItem*>(ItemAt(index));
305 			if (item == NULL)
306 				break;
307 
308 			TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT,
309 				item->Teams(), item->Menu()->ConvertToScreen(item->Frame()),
310 				true);
311 			break;
312 		}
313 
314 		default:
315 			BMenuBar::MessageReceived(message);
316 			break;
317 	}
318 }
319 
320 
321 void
322 TExpandoMenuBar::MouseDown(BPoint where)
323 {
324 	BMessage* message = Window()->CurrentMessage();
325 	BMenuItem* menuItem;
326 	TTeamMenuItem* item = TeamItemAtPoint(where, &menuItem);
327 
328 	// check for three finger salute, a.k.a. Vulcan Death Grip
329 	if (message != NULL && item != NULL && !fBarView->Dragging()) {
330 		int32 modifiers = 0;
331 		message->FindInt32("modifiers", &modifiers);
332 
333 		if ((modifiers & B_COMMAND_KEY) != 0
334 			&& (modifiers & B_CONTROL_KEY) != 0
335 			&& (modifiers & B_SHIFT_KEY) != 0) {
336 			const BList* teams = item->Teams();
337 			int32 teamCount = teams->CountItems();
338 
339 			team_id teamID;
340 			for (int32 team = 0; team < teamCount; team++) {
341 				teamID = (team_id)teams->ItemAt(team);
342 				kill_team(teamID);
343 				// remove the team immediately from display
344 				RemoveTeam(teamID, false);
345 			}
346 
347 			return;
348 		}
349 
350 		// control click - show all/hide all shortcut
351 		if ((modifiers & B_CONTROL_KEY) != 0) {
352 			// show/hide item's teams
353 			BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
354 				? kMinimizeTeam : kBringTeamToFront);
355 			showMessage.AddInt32("itemIndex", IndexOf(item));
356 			Window()->PostMessage(&showMessage, this);
357 			return;
358 		}
359 
360 		// Check the bounds of the expand Team icon
361 		if (fShowTeamExpander && fVertical) {
362 			BRect expanderRect = item->ExpanderBounds();
363 			if (expanderRect.Contains(where)) {
364 				// Let the update thread wait...
365 				BAutolock locker(sMonLocker);
366 
367 				// Toggle the item
368 				item->ToggleExpandState(true);
369 				item->Draw();
370 
371 				// Absorb the message.
372 				return;
373 			}
374 		}
375 
376 		// double-click on an item brings the team to front
377 		int32 clicks;
378 		if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1
379 			&& item == menuItem && item == fLastClickItem) {
380 			// activate this team
381 			be_roster->ActivateApp((team_id)item->Teams()->ItemAt(0));
382 			return;
383 		}
384 
385 		fLastClickItem = item;
386 	}
387 
388 	BMenuBar::MouseDown(where);
389 }
390 
391 
392 void
393 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage* message)
394 {
395 	if (message == NULL) {
396 		// force a cleanup
397 		_FinishedDrag();
398 
399 		switch (code) {
400 			case B_ENTERED_VIEW:
401 			case B_INSIDE_VIEW:
402 			{
403 				TTeamMenuItem* item = TeamItemAtPoint(where);
404 				if (item == fLastMousedOverItem) {
405 					// already set the tooltip for this item, break out
406 					break;
407 				}
408 
409 				if (item == NULL) {
410 					// item is NULL, remove the tooltip and break out
411 					fLastMousedOverItem = NULL;
412 					SetToolTip((const char*)NULL);
413 					break;
414 				}
415 
416 				if (item->HasLabel()) {
417 					// item has a visible label, remove the tooltip and break out
418 					fLastMousedOverItem = item;
419 					SetToolTip((const char*)NULL);
420 					break;
421 				}
422 
423 				// new item, set the tooltip to the item name
424 				SetToolTip(item->Name());
425 
426 				// save the current item for the next MouseMoved() call
427 				fLastMousedOverItem = item;
428 
429 				break;
430 			}
431 		}
432 
433 		BMenuBar::MouseMoved(where, code, message);
434 		return;
435 	}
436 
437 	uint32 buttons;
438 	if (Window()->CurrentMessage() == NULL
439 		|| Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons)
440 			< B_OK) {
441 		buttons = 0;
442 	}
443 
444 	if (buttons == 0)
445 		return;
446 
447 	switch (code) {
448 		case B_ENTERED_VIEW:
449 			// fPreviousDragTargetItem should always be NULL here anyways.
450 			if (fPreviousDragTargetItem)
451 				_FinishedDrag();
452 
453 			fBarView->CacheDragData(message);
454 			fPreviousDragTargetItem = NULL;
455 			break;
456 
457 		case B_OUTSIDE_VIEW:
458 			// NOTE: Should not be here, but for the sake of defensive
459 			// programming...
460 		case B_EXITED_VIEW:
461 			_FinishedDrag();
462 			break;
463 
464 		case B_INSIDE_VIEW:
465 			if (fBarView->Dragging()) {
466 				TTeamMenuItem* item = NULL;
467 				int32 itemCount = CountItems();
468 				for (int32 i = 0; i < itemCount; i++) {
469 					BMenuItem* _item = ItemAt(i);
470 					if (_item->Frame().Contains(where)) {
471 						item = dynamic_cast<TTeamMenuItem*>(_item);
472 						break;
473 					}
474 				}
475 				if (item == fPreviousDragTargetItem)
476 					break;
477 				if (fPreviousDragTargetItem != NULL)
478 					fPreviousDragTargetItem->SetOverrideSelected(false);
479 				if (item != NULL)
480 					item->SetOverrideSelected(true);
481 				fPreviousDragTargetItem = item;
482 			}
483 			break;
484 	}
485 }
486 
487 
488 void
489 TExpandoMenuBar::MouseUp(BPoint where)
490 {
491 	if (!fBarView->Dragging()) {
492 		BMenuBar::MouseUp(where);
493 		return;
494 	}
495 
496 	_FinishedDrag(true);
497 }
498 
499 
500 bool
501 TExpandoMenuBar::InDeskbarMenu(BPoint loc) const
502 {
503 	if (!fVertical) {
504 		if (fDeskbarMenuItem && fDeskbarMenuItem->Frame().Contains(loc))
505 			return true;
506 	} else {
507 		TBarWindow* window = dynamic_cast<TBarWindow*>(Window());
508 		if (window) {
509 			if (TDeskbarMenu* bemenu = window->DeskbarMenu()) {
510 				bool inDeskbarMenu = false;
511 				if (bemenu->LockLooper()) {
512 					inDeskbarMenu = bemenu->Frame().Contains(loc);
513 					bemenu->UnlockLooper();
514 				}
515 				return inDeskbarMenu;
516 			}
517 		}
518 	}
519 
520 	return false;
521 }
522 
523 
524 /*!	Returns the team menu item that belongs to the item under the
525 	specified \a point.
526 	If \a _item is given, it will return the exact menu item under
527 	that point (which might be a window item when the expander is on).
528 */
529 TTeamMenuItem*
530 TExpandoMenuBar::TeamItemAtPoint(BPoint point, BMenuItem** _item)
531 {
532 	TTeamMenuItem* lastApp = NULL;
533 	int32 count = CountItems();
534 
535 	for (int32 i = fFirstApp; i < count; i++) {
536 		BMenuItem* item = ItemAt(i);
537 
538 		if (dynamic_cast<TTeamMenuItem*>(item) != NULL)
539 			lastApp = (TTeamMenuItem*)item;
540 
541 		if (item && item->Frame().Contains(point)) {
542 			if (_item != NULL)
543 				*_item = item;
544 
545 			return lastApp;
546 		}
547 	}
548 
549 	// no item found
550 
551 	if (_item != NULL)
552 		*_item = NULL;
553 
554 	return NULL;
555 }
556 
557 
558 void
559 TExpandoMenuBar::AddTeam(BList* team, BBitmap* icon, char* name,
560 	char* signature)
561 {
562 	desk_settings* settings = static_cast<TBarApp*>(be_app)->Settings();
563 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
564 
565 	float itemWidth = -1.0f;
566 	if (fVertical)
567 		itemWidth = fBarView->Bounds().Width();
568 	else {
569 		itemWidth = iconSize;
570 		if (fDrawLabel)
571 			itemWidth += sMinimumWindowWidth - kMinimumIconSize;
572 		else
573 			itemWidth += kIconPadding * 2;
574 	}
575 	float itemHeight = -1.0f;
576 
577 	TTeamMenuItem* item = new TTeamMenuItem(team, icon, name, signature,
578 		itemWidth, itemHeight, fDrawLabel, fVertical);
579 
580 	if (settings->trackerAlwaysFirst && !strcmp(signature, kTrackerSignature))
581 		AddItem(item, fFirstApp);
582 	else if (settings->sortRunningApps) {
583 		TTeamMenuItem* teamItem
584 			= dynamic_cast<TTeamMenuItem*>(ItemAt(fFirstApp));
585 		int32 firstApp = fFirstApp;
586 
587 		// if Tracker should always be the first item, we need to skip it
588 		// when sorting in the current item
589 		if (settings->trackerAlwaysFirst && teamItem != NULL
590 			&& !strcmp(teamItem->Signature(), kTrackerSignature)) {
591 			firstApp++;
592 		}
593 
594 		int32 i = firstApp;
595 		int32 itemCount = CountItems();
596 		while (i < itemCount) {
597 			teamItem = dynamic_cast<TTeamMenuItem*>(ItemAt(i));
598 			if (teamItem != NULL && strcasecmp(teamItem->Name(), name) > 0) {
599 				AddItem(item, i);
600 				break;
601 			}
602 			i++;
603 		}
604 		// was the item added to the list yet?
605 		if (i == itemCount)
606 			AddItem(item);
607 	} else
608 		AddItem(item);
609 
610 	if (fVertical) {
611 		if (item && fShowTeamExpander && fExpandNewTeams)
612 			item->ToggleExpandState(false);
613 
614 		fBarView->SizeWindow(BScreen(Window()).Frame());
615 	} else
616 		CheckItemSizes(1);
617 
618 	Window()->UpdateIfNeeded();
619 }
620 
621 
622 void
623 TExpandoMenuBar::AddTeam(team_id team, const char* signature)
624 {
625 	int32 count = CountItems();
626 	for (int32 i = fFirstApp; i < count; i++) {
627 		// Only add to team menu items
628 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
629 			if (strcasecmp(item->Signature(), signature) == 0) {
630 				if (!(item->Teams()->HasItem((void*)team)))
631 					item->Teams()->AddItem((void*)team);
632 				break;
633 			}
634 		}
635 	}
636 }
637 
638 
639 void
640 TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
641 {
642 	int32 count = CountItems();
643 	for (int32 i = fFirstApp; i < count; i++) {
644 		if (TTeamMenuItem* item = dynamic_cast<TTeamMenuItem*>(ItemAt(i))) {
645 			if (item->Teams()->HasItem((void*)team)) {
646 				item->Teams()->RemoveItem(team);
647 
648 				if (partial)
649 					return;
650 
651 #ifdef DOUBLECLICKBRINGSTOFRONT
652 				if (fLastClickItem == i)
653 					fLastClickItem = -1;
654 #endif
655 
656 				RemoveItem(i);
657 
658 				if (fVertical) {
659 					// instead of resizing the window here and there in the
660 					// code the resize method will be centered in one place
661 					// thus, the same behavior (good or bad) will be used
662 					// whereever window sizing is done
663 					fBarView->SizeWindow(BScreen(Window()).Frame());
664 				} else
665 					CheckItemSizes(-1);
666 
667 				Window()->UpdateIfNeeded();
668 
669 				delete item;
670 				return;
671 			}
672 		}
673 	}
674 }
675 
676 
677 void
678 TExpandoMenuBar::CheckItemSizes(int32 delta)
679 {
680 	if (fBarView->Vertical())
681 		return;
682 
683 	int32 iconSize = static_cast<TBarApp*>(be_app)->IconSize();
684 	float maxContentWidth = sMinimumWindowWidth + iconSize - kMinimumIconSize;
685 
686 	// There are 2 extra items:
687 	//     The Be Menu
688 	//     The little separator item
689 	int32 count = CountItems() - 2;
690 	float maxWidth = Frame().Width() - fDeskbarMenuWidth - kSepItemWidth * 2;
691 	float fullWidth = maxContentWidth * count + fDeskbarMenuWidth
692 		+ kSepItemWidth;
693 	float iconOnlyWidth = kIconPadding + iconSize + kIconPadding;
694 
695 	bool reset = false;
696 	float newWidth = 0.0f;
697 
698 	if (delta >= 0 && fullWidth > maxWidth) {
699 		fOverflow = true;
700 		reset = true;
701 		if (fDrawLabel)
702 			newWidth = floorf(maxWidth / count);
703 		else
704 			newWidth = iconOnlyWidth;
705 	} else if (delta < 0 && fOverflow) {
706 		reset = true;
707 		if (fullWidth > maxWidth) {
708 			if (fDrawLabel)
709 				newWidth = floorf(maxWidth / count);
710 			else
711 				newWidth = iconOnlyWidth;
712 		} else
713 			newWidth = maxContentWidth;
714 	}
715 
716 	if (newWidth > maxContentWidth)
717 		newWidth = maxContentWidth;
718 
719 	if (reset) {
720 		SetMaxContentWidth(newWidth);
721 		if (newWidth == maxContentWidth)
722 			fOverflow = false;
723 		InvalidateLayout();
724 
725 		for (int32 index = fFirstApp; ; index++) {
726 			TTeamMenuItem* item = (TTeamMenuItem*)ItemAt(index);
727 			if (!item)
728 				break;
729 
730 			if (!fDrawLabel && newWidth > iconOnlyWidth) {
731 				item->SetOverrideWidth(iconOnlyWidth);
732 			} else {
733 				item->SetOverrideWidth(newWidth);
734 			}
735 		}
736 
737 		Invalidate();
738 		Window()->UpdateIfNeeded();
739 	}
740 }
741 
742 
743 menu_layout
744 TExpandoMenuBar::MenuLayout() const
745 {
746 	return Layout();
747 }
748 
749 
750 void
751 TExpandoMenuBar::Draw(BRect update)
752 {
753 	BMenu::Draw(update);
754 }
755 
756 
757 void
758 TExpandoMenuBar::DrawBackground(BRect)
759 {
760 	if (fVertical)
761 		return;
762 
763 	BRect bounds(Bounds());
764 	rgb_color menuColor = LowColor();
765 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
766 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
767 
768 	int32 last = CountItems() - 1;
769 	if (last >= 0)
770 		bounds.left = ItemAt(last)->Frame().right + 1;
771 	else
772 		bounds.left = 0;
773 
774 	if (be_control_look != NULL) {
775 		SetHighColor(tint_color(menuColor, 1.22));
776 		StrokeLine(bounds.LeftTop(), bounds.LeftBottom());
777 		bounds.left++;
778 		uint32 borders = BControlLook::B_TOP_BORDER
779 			| BControlLook::B_BOTTOM_BORDER | BControlLook::B_RIGHT_BORDER;
780 
781 		be_control_look->DrawButtonBackground(this, bounds, bounds, menuColor,
782 			0, borders);
783 	} else {
784 		SetHighColor(vlight);
785 		StrokeLine(bounds.LeftTop(), bounds.RightTop());
786 		StrokeLine(BPoint(bounds.left, bounds.top + 1), bounds.LeftBottom());
787 		SetHighColor(hilite);
788 		StrokeLine(BPoint(bounds.left + 1, bounds.bottom),
789 			bounds.RightBottom());
790 	}
791 }
792 
793 
794 /*!	Something to help determine if we are showing too many apps
795 	need to add in scrolling functionality.
796 */
797 void
798 TExpandoMenuBar::CheckForSizeOverrun()
799 {
800 	BRect screenFrame = (BScreen(Window())).Frame();
801 
802 	fIsScrolling = fVertical ? Window()->Frame().bottom > screenFrame.bottom
803 		: false;
804 }
805 
806 
807 void
808 TExpandoMenuBar::SizeWindow()
809 {
810 	if (fVertical)
811 		fBarView->SizeWindow(BScreen(Window()).Frame());
812 	else
813 		CheckItemSizes(1);
814 }
815 
816 
817 int32
818 TExpandoMenuBar::monitor_team_windows(void* arg)
819 {
820 	TExpandoMenuBar* teamMenu = (TExpandoMenuBar*)arg;
821 
822 	while (teamMenu->sDoMonitor) {
823 		sMonLocker.Lock();
824 
825 		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
826 			int32 totalItems = teamMenu->CountItems();
827 
828 			// Set all WindowMenuItems to require an update.
829 			TWindowMenuItem* item = NULL;
830 			for (int32 i = 0; i < totalItems; i++) {
831 				if (!teamMenu->SubmenuAt(i)) {
832 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
833 					item->SetRequireUpdate();
834 				}
835 			}
836 
837 			// Perform SetTo() on all the items that still exist as well as add
838 			// new items.
839 			bool itemModified = false, resize = false;
840 			TTeamMenuItem* teamItem = NULL;
841 
842 			for (int32 i = 0; i < totalItems; i++) {
843 				if (teamMenu->SubmenuAt(i) == NULL)
844 					continue;
845 
846 				teamItem = static_cast<TTeamMenuItem*>(teamMenu->ItemAt(i));
847 				if (teamItem->IsExpanded()) {
848 					int32 teamCount = teamItem->Teams()->CountItems();
849 					for (int32 j = 0; j < teamCount; j++) {
850 						// The following code is almost a copy/paste from
851 						// WindowMenu.cpp
852 						team_id	theTeam = (team_id)teamItem->Teams()->ItemAt(j);
853 						int32 count = 0;
854 						int32* tokens = get_token_list(theTeam, &count);
855 
856 						for (int32 k = 0; k < count; k++) {
857 							client_window_info* wInfo
858 								= get_window_info(tokens[k]);
859 							if (wInfo == NULL)
860 								continue;
861 
862 							if (TWindowMenu::WindowShouldBeListed(wInfo)) {
863 								// Check if we have a matching window item...
864 								item = teamItem->ExpandedWindowItem(
865 									wInfo->server_token);
866 								if (item) {
867 									item->SetTo(wInfo->name,
868 										wInfo->server_token, wInfo->is_mini,
869 										((1 << current_workspace())
870 											& wInfo->workspaces) != 0);
871 
872 									if (strcmp(wInfo->name,
873 										item->Label()) != 0)
874 										item->SetLabel(wInfo->name);
875 
876 									if (item->ChangedState())
877 										itemModified = true;
878 								} else if (teamItem->IsExpanded()) {
879 									// Add the item
880 									item = new TWindowMenuItem(wInfo->name,
881 										wInfo->server_token, wInfo->is_mini,
882 										((1 << current_workspace())
883 											& wInfo->workspaces) != 0, false);
884 									item->ExpandedItem(true);
885 									teamMenu->AddItem(item,
886 										TWindowMenuItem::InsertIndexFor(
887 											teamMenu, i + 1, item));
888 									resize = true;
889 								}
890 							}
891 							free(wInfo);
892 						}
893 						free(tokens);
894 					}
895 				}
896 			}
897 
898 			// Remove any remaining items which require an update.
899 			for (int32 i = 0; i < totalItems; i++) {
900 				if (!teamMenu->SubmenuAt(i)) {
901 					item = static_cast<TWindowMenuItem*>(teamMenu->ItemAt(i));
902 					if (item && item->RequiresUpdate()) {
903 						item = static_cast<TWindowMenuItem*>
904 							(teamMenu->RemoveItem(i));
905 						delete item;
906 						totalItems--;
907 
908 						resize = true;
909 					}
910 				}
911 			}
912 
913 			// If any of the WindowMenuItems changed state, we need to force a
914 			// repaint.
915 			if (itemModified || resize) {
916 				teamMenu->Invalidate();
917 				if (resize)
918 					teamMenu->SizeWindow();
919 			}
920 
921 			teamMenu->Window()->Unlock();
922 		}
923 
924 		sMonLocker.Unlock();
925 
926 		// sleep for a bit...
927 		snooze(150000);
928 	}
929 	return B_OK;
930 }
931 
932 
933 void
934 TExpandoMenuBar::_FinishedDrag(bool invoke)
935 {
936 	if (fPreviousDragTargetItem != NULL) {
937 		if (invoke)
938 			fPreviousDragTargetItem->Invoke();
939 		fPreviousDragTargetItem->SetOverrideSelected(false);
940 		fPreviousDragTargetItem = NULL;
941 	}
942 	if (!invoke && fBarView->Dragging())
943 		fBarView->DragStop(true);
944 }
945