xref: /haiku/src/apps/deskbar/ExpandoMenuBar.cpp (revision 24159a0c7d6d6dcba9f2a0c1a7c08d2c8167f21b)
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 trademarks
30 of Be Incorporated in the United States and other countries. Other brand product
31 names are registered trademarks or trademarks of their respective holders.
32 All rights reserved.
33 */
34 
35 #include <Debug.h>
36 #include <string.h>
37 #include <NodeInfo.h>
38 #include <Roster.h>
39 #include <Screen.h>
40 #include <Bitmap.h>
41 #include <Autolock.h>
42 
43 #include "icons.h"
44 #include "icons_logo.h"
45 #include "BarApp.h"
46 #include "BarMenuTitle.h"
47 #include "BarView.h"
48 #include "BeMenu.h"
49 #include "DeskBarUtils.h"
50 #include "ExpandoMenuBar.h"
51 #include "ResourceSet.h"
52 #include "ShowHideMenuItem.h"
53 #include "StatusView.h"
54 #include "TeamMenuItem.h"
55 #include "WindowMenu.h"
56 #include "WindowMenuItem.h"
57 
58 const float kBeMenuWidth = 50.0f;
59 const float kSepItemWidth = 5.0f;
60 
61 const uint32 M_MINIMIZE_TEAM = 'mntm';
62 const uint32 M_BRING_TEAM_TO_FRONT = 'bftm';
63 
64 
65 bool TExpandoMenuBar::sDoMonitor = false;
66 thread_id TExpandoMenuBar::sMonThread = B_ERROR;
67 BLocker TExpandoMenuBar::sMonLocker("expando monitor");
68 
69 
70 TExpandoMenuBar::TExpandoMenuBar(TBarView *bar, BRect frame, const char *name,
71 	bool vertical, bool drawLabel)
72 	: BMenuBar(frame, name, B_FOLLOW_NONE,
73 			vertical ? B_ITEMS_IN_COLUMN : B_ITEMS_IN_ROW, vertical),
74 	fVertical(vertical),
75 	fOverflow(false),
76 	fDrawLabel(drawLabel),
77 	fIsScrolling(false),
78 	fShowTeamExpander(static_cast<TBarApp *>(be_app)->Settings()->superExpando),
79 	fExpandNewTeams(static_cast<TBarApp *>(be_app)->Settings()->expandNewTeams),
80 	fBarView(bar),
81 	fFirstApp(0)
82 {
83 #ifdef DOUBLECLICKBRINGSTOFRONT
84 	fLastClickItem = -1;
85 	fLastClickTime = 0;
86 #endif
87 
88 	SetItemMargins(0.0f, 0.0f, 0.0f, 0.0f);
89 	SetFont(be_plain_font);
90 	SetMaxContentWidth(kMinimumWindowWidth);
91 }
92 
93 
94 int
95 TExpandoMenuBar::CompareByName(const void *first, const void *second)
96 {
97 	return strcasecmp((*(static_cast<BarTeamInfo * const*>(first)))->name,
98 		(*(static_cast<BarTeamInfo * const*>(second)))->name);
99 }
100 
101 
102 void
103 TExpandoMenuBar::AttachedToWindow()
104 {
105 	BMessenger self(this);
106 	BList teamList;
107 	TBarApp::Subscribe(self, &teamList);
108 	float width = fVertical ? Frame().Width() : kMinimumWindowWidth;
109 	float height = -1.0f;
110 
111 	// top or bottom mode, add be menu and sep for menubar tracking consistency
112 	if (!fVertical) {
113 		TBeMenu *beMenu = new TBeMenu(fBarView);
114 		TBarWindow::SetBeMenu(beMenu);
115  		fBeMenuItem = new TBarMenuTitle(kBeMenuWidth, Frame().Height(),
116  			AppResSet()->FindBitmap(B_MESSAGE_TYPE, R_BeLogoIcon), beMenu, true);
117 		AddItem(fBeMenuItem);
118 
119 		fSeparatorItem = new TTeamMenuItem(kSepItemWidth, height, fVertical);
120 		AddItem(fSeparatorItem);
121 		fSeparatorItem->SetEnabled(false);
122 		fFirstApp = 2;
123 	} else {
124 		fBeMenuItem = NULL;
125 		fSeparatorItem = NULL;
126 	}
127 
128 	desk_settings *settings = ((TBarApp *)be_app)->Settings();
129 
130 	if (settings->sortRunningApps)
131 		teamList.SortItems(CompareByName);
132 
133 	int32 count = teamList.CountItems();
134 	for (int32 i = 0; i < count; i++) {
135 		BarTeamInfo *barInfo = (BarTeamInfo *)teamList.ItemAt(i);
136 		if ((barInfo->flags & B_BACKGROUND_APP) == 0
137 			&& strcasecmp(barInfo->sig, kDeskbarSignature) != 0) {
138 			if (settings->trackerAlwaysFirst
139 				&& !strcmp(barInfo->sig, kTrackerSignature)) {
140 				AddItem(new TTeamMenuItem(barInfo->teams, barInfo->icon,
141 					barInfo->name, barInfo->sig, width, height,
142 					fDrawLabel, fVertical), fFirstApp);
143 			} else {
144 				AddItem(new TTeamMenuItem(barInfo->teams, barInfo->icon,
145 					barInfo->name, barInfo->sig, width, height,
146 					fDrawLabel, fVertical));
147 			}
148 
149 			barInfo->teams = NULL;
150 			barInfo->icon = NULL;
151 			barInfo->name = NULL;
152 			barInfo->sig = NULL;
153 		}
154 
155 		delete barInfo;
156 	}
157 
158 	BMenuBar::AttachedToWindow();
159 
160 	if (CountItems() == 0) {
161 		// If we're empty, BMenuBar::AttachedToWindow() resizes us to some
162 		// weird value - we just override it again
163 		ResizeTo(width, 0);
164 	}
165 
166 	if (fVertical) {
167 		sDoMonitor = true;
168 		sMonThread = spawn_thread(monitor_team_windows,
169 			"Expando Window Watcher", B_LOW_PRIORITY, this);
170 		resume_thread(sMonThread);
171 	}
172 }
173 
174 
175 void
176 TExpandoMenuBar::DetachedFromWindow()
177 {
178 	BMenuBar::DetachedFromWindow();
179 
180 	if (sMonThread != B_ERROR) {
181 		sDoMonitor = false;
182 
183 		status_t returnCode;
184 		wait_for_thread(sMonThread, &returnCode);
185 
186 		sMonThread = B_ERROR;
187 	}
188 
189 	BMessenger self(this);
190 	BMessage message(msg_Unsubscribe);
191 	message.AddMessenger("messenger", self);
192 	be_app->PostMessage(&message);
193 
194 	BMenuItem *item = NULL;
195 	while ((item = RemoveItem(0L)) != NULL)
196 		delete item;
197 }
198 
199 
200 void
201 TExpandoMenuBar::MessageReceived(BMessage *message)
202 {
203 	int32 index;
204 	TTeamMenuItem *item;
205 
206 	switch (message->what) {
207 		case B_SOME_APP_LAUNCHED: {
208 			BList *teams = NULL;
209 			message->FindPointer("teams", (void **)&teams);
210 
211 			BBitmap *icon = NULL;
212 			message->FindPointer("icon", (void **)&icon);
213 
214 			const char *signature;
215 			if (message->FindString("sig", &signature) == B_OK
216 				&&strcasecmp(signature, kDeskbarSignature) == 0) {
217 				delete teams;
218 				delete icon;
219 				break;
220 			}
221 
222 			uint32 flags;
223 			if (message->FindInt32("flags", ((int32*) &flags)) == B_OK
224 				&& (flags & B_BACKGROUND_APP) != 0) {
225 				delete teams;
226 				delete icon;
227 				break;
228 			}
229 
230 			const char *name = NULL;
231 			message->FindString("name", &name);
232 
233 			AddTeam(teams, icon, strdup(name), strdup(signature));
234 			break;
235 		}
236 
237 		case msg_AddTeam:
238 			AddTeam(message->FindInt32("team"), message->FindString("sig"));
239 			break;
240 
241 		case msg_RemoveTeam:
242 		{
243 			team_id team = -1;
244 			message->FindInt32("team", &team);
245 
246 			RemoveTeam(team, true);
247 			break;
248 		}
249 
250 		case B_SOME_APP_QUIT:
251 		{
252 			team_id team = -1;
253 			message->FindInt32("team", &team);
254 
255 			RemoveTeam(team, false);
256 			break;
257 		}
258 
259 		case M_MINIMIZE_TEAM:
260 		{
261 			index = message->FindInt32("itemIndex");
262 			item = dynamic_cast<TTeamMenuItem *>(ItemAt(index));
263 			if (item == NULL)
264 				break;
265 
266 			TShowHideMenuItem::TeamShowHideCommon(B_MINIMIZE_WINDOW,
267 				item->Teams(),
268 				item->Menu()->ConvertToScreen(item->Frame()),
269 				true);
270 			break;
271 		}
272 
273 		case M_BRING_TEAM_TO_FRONT:
274 		{
275 			index = message->FindInt32("itemIndex");
276 			item = dynamic_cast<TTeamMenuItem *>(ItemAt(index));
277 			if (item == NULL)
278 				break;
279 
280 			TShowHideMenuItem::TeamShowHideCommon(B_BRING_TO_FRONT,
281 				item->Teams(),
282 				item->Menu()->ConvertToScreen(item->Frame()),
283 				true);
284 			break;
285 		}
286 
287 		default:
288 			BMenuBar::MessageReceived(message);
289 			break;
290 	}
291 }
292 
293 
294 void
295 TExpandoMenuBar::MouseDown(BPoint where)
296 {
297 	BMessage *message = Window()->CurrentMessage();
298 
299 	// check for three finger salute, a.k.a. Vulcan Death Grip
300 	if (message != NULL) {
301 		int32 modifiers = 0;
302 		message->FindInt32("modifiers", &modifiers);
303 
304 		if ((modifiers & B_COMMAND_KEY) != 0
305 			&& (modifiers & B_OPTION_KEY) != 0
306 			&& (modifiers & B_SHIFT_KEY) != 0
307 			&& !fBarView->Dragging()) {
308 
309 			TTeamMenuItem *item = ItemAtPoint(where);
310 			if (item) {
311 				const BList	*teams = item->Teams();
312 				int32 teamCount = teams->CountItems();
313 
314 				team_id teamID;
315 				for (int32 team = 0; team < teamCount; team++) {
316 					teamID = (team_id)teams->ItemAt(team);
317 					kill_team(teamID);
318 					//	remove the team immediately
319 					//	from display
320 					RemoveTeam(teamID, false);
321 				}
322 
323 				return;
324 			}
325 		}
326 	}
327 
328 	const int32 count = CountItems();
329 
330 // This feature is broken because the menu bar never receives
331 // the second click
332 #ifdef DOUBLECLICKBRINGSTOFRONT
333 	// doubleclick on an item brings all to front
334 	for (int32 i = fFirstApp; i < count; i++) {
335 		TTeamMenuItem *item = (TTeamMenuItem *)ItemAt(i);
336 		if (item->Frame().Contains(where)) {
337 			bigtime_t clickSpeed = 0;
338 			get_click_speed(&clickSpeed);
339 			if ( (fLastClickItem == i) &&
340 				 (clickSpeed > (system_time() - fLastClickTime)) ) {
341 				// bring this team's window to the front
342 				BMessage showMessage(M_BRING_TEAM_TO_FRONT);
343 				showMessage.AddInt32("itemIndex", i);
344 				Window()->PostMessage(&showMessage, this);
345 				return;
346 			}
347 
348 			fLastClickItem = i;
349 			fLastClickTime = system_time();
350 			break;
351 		}
352 	}
353 #endif
354 
355 	// control click - show all/hide all shortcut
356 	if (message != NULL) {
357 		int32 modifiers = 0;
358 		message->FindInt32("modifiers", &modifiers);
359 		if ((modifiers & B_CONTROL_KEY) != 0
360 			&& ! fBarView->Dragging()) {
361 			int32 lastApp = -1;
362 
363 			// find the clicked item
364 			for (int32 i = fFirstApp; i < count; i++) {
365 				const TTeamMenuItem *item = (TTeamMenuItem *)ItemAt(i);
366 
367 				// check if this item is really a team item	(what a cruel way...)
368 				// "lastApp" will always point to the last application in
369 				// the list - the other entries might be windows (due to the team expander)
370 				if (item->Submenu())
371 					 lastApp = i;
372 
373 				if (item->Frame().Contains(where)) {
374 					// show/hide item's teams
375 					BMessage showMessage((modifiers & B_SHIFT_KEY) != 0
376 						? M_MINIMIZE_TEAM : M_BRING_TEAM_TO_FRONT);
377 					showMessage.AddInt32("itemIndex", lastApp);
378 					Window()->PostMessage(&showMessage, this);
379 					return;
380 				}
381 			}
382 		}
383 	}
384 
385 	// Check the bounds of the expand Team icon
386 	if (fShowTeamExpander && fVertical && !fBarView->Dragging()) {
387 		TTeamMenuItem *item = ItemAtPoint(where);
388 		if (item->Submenu()) {
389 			BRect expanderRect = item->ExpanderBounds();
390 			if (expanderRect.Contains(where)) {
391 				// Let the update thread wait...
392 				BAutolock locker(sMonLocker);
393 
394 				// Toggle the item
395 				item->ToggleExpandState(true);
396 				item->Draw();
397 
398 				// Absorb the message.
399 				return;
400 			}
401 		}
402 	}
403 
404 	BMenuBar::MouseDown(where);
405 }
406 
407 
408 void
409 TExpandoMenuBar::MouseMoved(BPoint where, uint32 code, const BMessage *message)
410 {
411 	if (!message) {
412 		//	force a cleanup
413 		fBarView->DragStop(true);
414 		BMenuBar::MouseMoved(where, code, message);
415 		return;
416 	}
417 
418 	BPoint loc;
419 	uint32 buttons;
420 	GetMouse(&loc, &buttons);
421 
422 	switch (code) {
423 		case B_ENTERED_VIEW:
424 			if (message && buttons != 0) {
425 				fBarView->CacheDragData((BMessage *)message);
426 				MouseDown(loc);
427 			}
428 			break;
429 
430 		case B_EXITED_VIEW:
431 			if (fBarView->Dragging() && buttons != 0) {
432 				if (!ItemAtPoint(where)
433 					&& !InBeMenu(where)
434 					&& (fSeparatorItem && !fSeparatorItem->Frame().Contains(where))
435 					&& !Frame().Contains(where))
436 					fBarView->DragStop();
437 
438 			}
439 			break;
440 	}
441 	BMenuBar::MouseMoved(where, code, message);
442 }
443 
444 
445 bool
446 TExpandoMenuBar::InBeMenu(BPoint loc) const
447 {
448 	if (!fVertical) {
449 		if (fBeMenuItem && fBeMenuItem->Frame().Contains(loc))
450 			return true;
451 	} else {
452 		TBarWindow *window = dynamic_cast<TBarWindow*>(Window());
453 		if (window) {
454 			TBeMenu *bemenu = window->BeMenu();
455 			if (bemenu && bemenu->Frame().Contains(loc))
456 				return true;
457 		}
458 	}
459 
460 	return false;
461 }
462 
463 
464 TTeamMenuItem *
465 TExpandoMenuBar::ItemAtPoint(BPoint point)
466 {
467 	TTeamMenuItem *item = NULL;
468 	int32 count = CountItems();
469 
470 	for (int32 i = fFirstApp; i < count; i++) {
471 		item = (TTeamMenuItem *)ItemAt(i);
472 		if (item && item->Frame().Contains(point))
473 			return item;
474 	}
475 	return NULL;
476 }
477 
478 
479 void
480 TExpandoMenuBar::AddTeam(BList *team, BBitmap *icon, char *name, char *signature)
481 {
482 	float itemWidth = fVertical ? Frame().Width() : kMinimumWindowWidth;
483 	float itemHeight = -1.0f;
484 
485 	desk_settings *settings = ((TBarApp *)be_app)->Settings();
486 	TTeamMenuItem *item = new TTeamMenuItem(team, icon, name, signature, itemWidth,
487 		itemHeight, fDrawLabel, fVertical);
488 
489 	if (settings->trackerAlwaysFirst && !strcmp(signature, kTrackerSignature)) {
490 		AddItem(item, fFirstApp);
491 	} else if (settings->sortRunningApps) {
492 		TTeamMenuItem *teamItem = dynamic_cast<TTeamMenuItem *>(ItemAt(fFirstApp));
493 		int32 firstApp = fFirstApp;
494 
495 		// if Tracker should always be the first item, we need to skip it
496 		// when sorting in the current item
497 		if (settings->trackerAlwaysFirst && teamItem != NULL
498 			&& !strcmp(teamItem->Signature(), kTrackerSignature)) {
499 			firstApp++;
500 		}
501 
502 		int32 count = CountItems(), i;
503 		for (i = firstApp; i < count; i++) {
504 			teamItem = dynamic_cast<TTeamMenuItem *>(ItemAt(i));
505 			if (teamItem != NULL && strcasecmp(teamItem->Name(), name) > 0) {
506 				AddItem(item, i);
507 				break;
508 			}
509 		}
510 		// was the item added to the list yet?
511 		if (i == count)
512 			AddItem(item);
513 	} else
514 		AddItem(item);
515 
516 	if (fVertical) {
517 		if (item && fShowTeamExpander && fExpandNewTeams)
518 			item->ToggleExpandState(false);
519 
520 		fBarView->SizeWindow(BScreen(Window()).Frame());
521 	} else
522 		CheckItemSizes(1);
523 
524 	Window()->UpdateIfNeeded();
525 }
526 
527 
528 void
529 TExpandoMenuBar::AddTeam(team_id team, const char *signature)
530 {
531 	int32 count = CountItems();
532 	for (int32 i = fFirstApp; i < count; i++) {
533 		// Only add to team menu items
534 		if (TTeamMenuItem *item = dynamic_cast<TTeamMenuItem *>(ItemAt(i))) {
535 			if (strcasecmp(item->Signature(), signature) == 0) {
536 				if (!(item->Teams()->HasItem((void *)team)))
537 					item->Teams()->AddItem((void *)team);
538 
539 				break;
540 			}
541 		}
542 	}
543 }
544 
545 
546 void
547 TExpandoMenuBar::RemoveTeam(team_id team, bool partial)
548 {
549 	int32 count = CountItems();
550 	for (int32 i = fFirstApp; i < count; i++) {
551 		if (TTeamMenuItem *item = dynamic_cast<TTeamMenuItem *>(ItemAt(i))) {
552 			if (item->Teams()->HasItem((void *)team)) {
553 				item->Teams()->RemoveItem(team);
554 
555 				if (partial)
556 					return;
557 
558 #ifdef DOUBLECLICKBRINGSTOFRONT
559 				if (fLastClickItem == i)
560 					fLastClickItem = -1;
561 #endif
562 
563 				RemoveItem(i);
564 
565 				if (fVertical) {
566 					//	instead of resizing the window here and there in the code
567 					//	the resize method will be centered in one place
568 					//	thus, the same behavior (good or bad) will be used whereever
569 					//	window sizing is done
570 					fBarView->SizeWindow(BScreen(Window()).Frame());
571 				} else
572 					CheckItemSizes(-1);
573 
574 				Window()->UpdateIfNeeded();
575 
576 				delete item;
577 				return;
578 			}
579 		}
580 	}
581 }
582 
583 
584 void
585 TExpandoMenuBar::CheckItemSizes(int32 delta)
586 {
587 	float width = Frame().Width();
588 	int32 count = CountItems();
589 	bool reset = false;
590 	float newWidth = 0;
591 	float fullWidth = (kMinimumWindowWidth * count);
592 
593 	if (!fBarView->Vertical()) {
594 		// in this case there are 2 extra items:
595 		//		The Be Menu
596 		//		The little separator item
597 		fullWidth = fullWidth - (kMinimumWindowWidth * 2) + (kBeMenuWidth + kSepItemWidth);
598 		width -= (kBeMenuWidth + kSepItemWidth);
599 		count -= 2;
600 	}
601 
602 	if (delta >= 0 && fullWidth > width) {
603 		fOverflow = true;
604 		reset = true;
605 		newWidth = floorf(width/count);
606 	} else if (delta < 0 && fOverflow) {
607 		reset = true;
608 		if (fullWidth > width)
609 			newWidth = floorf(width/count);
610 		else
611 			newWidth = kMinimumWindowWidth;
612 	}
613 	if (newWidth > kMinimumWindowWidth)
614 		newWidth = kMinimumWindowWidth;
615 
616 	if (reset) {
617 		SetMaxContentWidth(newWidth);
618 		if (newWidth == kMinimumWindowWidth)
619 			fOverflow = false;
620 		InvalidateLayout();
621 
622 		for (int32 index = fFirstApp; ; index++) {
623 			TTeamMenuItem *item = (TTeamMenuItem *)ItemAt(index);
624 			if (!item)
625 				break;
626 			item->SetOverrideWidth(newWidth);
627 		}
628 
629 		Invalidate();
630 		Window()->UpdateIfNeeded();
631 	}
632 }
633 
634 
635 menu_layout
636 TExpandoMenuBar::MenuLayout() const
637 {
638 	return Layout();
639 }
640 
641 
642 void
643 TExpandoMenuBar::Draw(BRect update)
644 {
645 	BMenu::Draw(update);
646 }
647 
648 
649 void
650 TExpandoMenuBar::DrawBackground(BRect)
651 {
652 	BRect bounds(Bounds());
653 	rgb_color menuColor = ui_color(B_MENU_BACKGROUND_COLOR);
654 	rgb_color hilite = tint_color(menuColor, B_DARKEN_1_TINT);
655 	rgb_color dark = tint_color(menuColor, B_DARKEN_2_TINT);
656 	rgb_color vlight = tint_color(menuColor, B_LIGHTEN_2_TINT);
657 	int32 last = CountItems() - 1;
658 	float start;
659 
660 	if (last >= 0)
661 		start = ItemAt(last)->Frame().right + 1;
662 	else
663 		start = 0;
664 
665 	if (!fVertical) {
666 		SetHighColor(vlight);
667 		StrokeLine(BPoint(start, bounds.top+1), bounds.RightTop() + BPoint(0,1));
668 		StrokeLine(BPoint(start, bounds.top+1), BPoint(start, bounds.bottom));
669 		SetHighColor(hilite);
670 		StrokeLine(BPoint(start+1, bounds.bottom), bounds.RightBottom());
671 	}
672 }
673 
674 
675 /**	Something to help determine if we are showing too many apps
676  *	need to add in scrolling functionality.
677  */
678 
679 void
680 TExpandoMenuBar::CheckForSizeOverrun()
681 {
682 	BRect screenFrame = (BScreen(Window())).Frame();
683 	if (fVertical)
684 		fIsScrolling = Window()->Frame().bottom > screenFrame.bottom;
685 	else
686 		fIsScrolling = false;
687 }
688 
689 
690 void
691 TExpandoMenuBar::SizeWindow()
692 {
693 	if (fVertical)
694 		fBarView->SizeWindow(BScreen(Window()).Frame());
695 	else
696 		CheckItemSizes(1);
697 }
698 
699 
700 int32
701 TExpandoMenuBar::monitor_team_windows(void *arg)
702 {
703 	TExpandoMenuBar *teamMenu = (TExpandoMenuBar *)arg;
704 
705 	while (teamMenu->sDoMonitor) {
706 		sMonLocker.Lock();
707 
708 		if (teamMenu->Window()->LockWithTimeout(50000) == B_OK) {
709 			int32 totalItems = teamMenu->CountItems();
710 
711 			// Set all WindowMenuItems to require an update.
712 			TWindowMenuItem *item = NULL;
713 			for (int32 i = 0; i < totalItems; i++) {
714 				if (!teamMenu->SubmenuAt(i)){
715 					item = static_cast<TWindowMenuItem *>(teamMenu->ItemAt(i));
716 					item->SetRequireUpdate();
717 				}
718 			}
719 
720 			// Perform SetTo() on all the items that still exist as well as add new items.
721 			bool itemModified = false, resize = false;
722 			TTeamMenuItem *teamItem = NULL;
723 
724 			for (int32 i = 0; i < totalItems; i++) {
725 				if (teamMenu->SubmenuAt(i) == NULL)
726 					continue;
727 
728 				teamItem = static_cast<TTeamMenuItem *>(teamMenu->ItemAt(i));
729 				if (teamItem->IsExpanded()) {
730 					int32 teamCount = teamItem->Teams()->CountItems();
731 					for (int32 j = 0; j < teamCount; j++) {
732 						// The following code is almost a copy/paste from
733 						// WindowMenu.cpp
734 						team_id	theTeam = (team_id)teamItem->Teams()->ItemAt(j);
735 						int32 count = 0;
736 						int32 *tokens = get_token_list(theTeam, &count);
737 
738 						for (int32 k = 0; k < count; k++) {
739 							window_info *wInfo = get_window_info(tokens[k]);
740 							if (wInfo == NULL)
741 								continue;
742 
743 							if (TWindowMenu::WindowShouldBeListed(wInfo->w_type)
744 								&& (wInfo->show_hide_level <= 0 || wInfo->is_mini)) {
745 								// Check if we have a matching window item...
746 								item = teamItem->ExpandedWindowItem(wInfo->id);
747 								if (item) {
748 									item->SetTo(wInfo->name, wInfo->id, wInfo->is_mini,
749 										((1 << current_workspace()) & wInfo->workspaces) != 0);
750 
751 									if (strcmp(wInfo->name, item->Label()) != 0)
752 										item->SetLabel(wInfo->name);
753 
754 									if (item->ChangedState())
755 										itemModified = true;
756 								} else if (teamItem->IsExpanded()) {
757 									// Add the item
758 									item = new TWindowMenuItem(wInfo->name, wInfo->id,
759 										wInfo->is_mini,
760 										((1 << current_workspace()) & wInfo->workspaces) != 0,
761 										false);
762 									item->ExpandedItem(true);
763 									teamMenu->AddItem(item, i + 1);
764 									resize = true;
765 								}
766 							}
767 							free(wInfo);
768 						}
769 						free(tokens);
770 					}
771 				}
772 			}
773 
774 			// Remove any remaining items which require an update.
775 			for (int32 i = 0; i < totalItems; i++) {
776 				if (!teamMenu->SubmenuAt(i)){
777 					item = static_cast<TWindowMenuItem *>(teamMenu->ItemAt(i));
778 					if (item && item->RequiresUpdate()) {
779 						item = static_cast<TWindowMenuItem *>(teamMenu->RemoveItem(i));
780 						delete item;
781 						totalItems--;
782 
783 						resize = true;
784 					}
785 				}
786 			}
787 
788 			// If any of the WindowMenuItems changed state, we need to force a repaint.
789 			if (itemModified || resize) {
790 				teamMenu->Invalidate();
791 				if (resize)
792 					teamMenu->SizeWindow();
793 			}
794 
795 			teamMenu->Window()->Unlock();
796 		}
797 
798 		sMonLocker.Unlock();
799 
800 		// sleep for a bit...
801 		snooze(150000);
802 	}
803 	return B_OK;
804 }
805 
806