xref: /haiku/src/apps/deskbar/Switcher.cpp (revision 020cbad9d40235a2c50a81a42d69912a5ff8fbc4)
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 
36 #include "tracker_private.h"
37 #include "BarApp.h"
38 #include "Switcher.h"
39 #include "ResourceSet.h"
40 #include "WindowMenuItem.h"
41 #include "icons.h"
42 
43 #include <Bitmap.h>
44 #include <Debug.h>
45 #include <Font.h>
46 #include <Mime.h>
47 #include <Node.h>
48 #include <NodeInfo.h>
49 #include <Roster.h>
50 #include <Screen.h>
51 #include <String.h>
52 
53 #include <string.h>
54 #include <stdlib.h>
55 #include <float.h>
56 
57 
58 #define _ALLOW_STICKY_ 0
59 	// allows you to press 's' to keep the switcher window on screen
60 
61 #if __HAIKU__
62 	static const color_space kIconFormat = B_RGBA32;
63 #else
64 	static const color_space kIconFormat = B_CMAP8;
65 #endif
66 
67 
68 class TTeamGroup {
69 	public:
70 		TTeamGroup();
71 		TTeamGroup(BList *teams, uint32 flags, char *name, const char *sig);
72 		virtual ~TTeamGroup();
73 
74 		void Draw(BView *, BRect bounds, bool main);
75 
76 		BList *TeamList() const
77 			{ return fTeams; }
78 		const char *Name() const
79 			{ return fName; }
80 		const char *Signature() const
81 			{ return fSignature; }
82 		uint32 Flags() const
83 			{ return fFlags; }
84 		const BBitmap *SmallIcon() const
85 			{ return fSmallIcon; }
86 		const BBitmap *LargeIcon() const
87 			{ return fLargeIcon; }
88 
89 	private:
90 		BList *fTeams;
91 		uint32 fFlags;
92 		char fSignature[B_MIME_TYPE_LENGTH];
93 		char *fName;
94 		BBitmap	*fSmallIcon;
95 		BBitmap	*fLargeIcon;
96 };
97 
98 class TSwitcherWindow : public BWindow {
99 	public:
100 		TSwitcherWindow(BRect frame, TSwitchManager *mgr);
101 		virtual	~TSwitcherWindow();
102 
103 		virtual	bool QuitRequested();
104 		virtual void MessageReceived(BMessage *message);
105 		virtual	void Show();
106 		virtual	void Hide();
107 		virtual void WindowActivated(bool state);
108 
109 		void DoKey(uint32 key, uint32 mods);
110 		TIconView *IconView();
111 		TWindowView *WindowView();
112 		TBox *TopView();
113 		bool HairTrigger();
114 		void Update(int32 previous, int32 current, int32 prevSlot,
115 			int32 currentSlot, bool forward);
116 		int32 SlotOf(int32);
117 		void Redraw(int32 index);
118 
119 	private:
120 		TSwitchManager *fManager;
121 		TIconView *fIconView;
122 		TBox *fTopView;
123 		TWindowView *fWindowView;
124 		bool fHairTrigger;
125 		bool fSkipKeyRepeats;
126 };
127 
128 class TWindowView : public BView {
129 	public:
130 		TWindowView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcher);
131 
132 		void UpdateGroup(int32 groupIndex, int32 windowIndex);
133 
134 		virtual void AttachedToWindow();
135 		virtual	void Draw(BRect update);
136 		virtual	void Pulse();
137 		virtual	void GetPreferredSize(float *w, float *h);
138 		void ScrollTo(float x, float y)
139 			{ ScrollTo(BPoint(x,y)); }
140 		virtual	void ScrollTo(BPoint where);
141 
142 		void ShowIndex(int32 windex);
143 		BRect FrameOf(int32 index) const;
144 
145 	private:
146 		int32 fCurrentToken;
147 		float fItemHeight;
148 		TSwitcherWindow	*fSwitcher;
149 		TSwitchManager *fManager;
150 		bool fLocal;
151 };
152 
153 class TIconView : public BView {
154 	public:
155 		TIconView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcher);
156 		~TIconView();
157 
158 		void Showing();
159 		void Hiding();
160 
161 		virtual void KeyDown(const char *bytes, int32 numBytes);
162 		virtual	void Pulse();
163 		virtual	void MouseDown(BPoint );
164 		virtual	void Draw(BRect );
165 
166 		void ScrollTo(float x, float y)
167 			{ ScrollTo(BPoint(x,y)); }
168 		virtual	void ScrollTo(BPoint where);
169 		void Update(int32 previous, int32 current, int32 previousSlot,
170 			int32 currentSlot, bool forward);
171 		void DrawTeams(BRect update);
172 		int32 SlotOf(int32) const;
173 		BRect FrameOf(int32) const;
174 		int32 ItemAtPoint(BPoint) const;
175 		int32 IndexAt(int32 slot) const;
176 		void CenterOn(int32 index);
177 
178 	private:
179 		void CacheIcons(TTeamGroup *);
180 		void AnimateIcon(BBitmap *startIcon, BBitmap *endIcon);
181 
182 		bool fAutoScrolling;
183 		bool fCapsState;
184 		TSwitcherWindow	*fSwitcher;
185 		TSwitchManager *fManager;
186 		BBitmap	*fOffBitmap;
187 		BView *fOffView;
188 		BBitmap	*fCurrentSmall;
189 		BBitmap	*fCurrentLarge;
190 };
191 
192 class TBox : public BBox {
193 	public:
194 		TBox(BRect bounds, TSwitchManager *manager, TSwitcherWindow *, TIconView *iconView);
195 
196 		virtual void Draw(BRect update);
197 		virtual void AllAttached();
198 		virtual	void DrawIconScrollers(bool force);
199 		virtual	void DrawWindowScrollers(bool force);
200 		virtual	void MouseDown(BPoint where);
201 
202 	private:
203 		TSwitchManager *fManager;
204 		TSwitcherWindow	*fWindow;
205 		TIconView *fIconView;
206 		BRect fCenter;
207 		bool fLeftScroller;
208 		bool fRightScroller;
209 		bool fUpScroller;
210 		bool fDownScroller;
211 };
212 
213 
214 const int32 kHorizontalMargin = 11;
215 const int32 kVerticalMargin = 10;
216 
217 // SLOT_SIZE must be divisible by 4. That's because of the scrolling
218 // animation. If this needs to change then look at TIconView::Update()
219 
220 const int32 kSlotSize = 36;
221 const int32 kScrollStep = kSlotSize / 2;
222 const int32 kNumSlots = 7;
223 const int32 kCenterSlot = 3;
224 
225 
226 //	#pragma mark -
227 
228 
229 static int32
230 LowBitIndex(uint32 value)
231 {
232 	int32 result = 0;
233 	int32 bitMask = 1;
234 
235 	if (value == 0)
236 		return -1;
237 
238 	while (result < 32 && (value & bitMask) == 0) {
239 		result++;
240 		bitMask = bitMask << 1;
241 	}
242 	return result;
243 }
244 
245 
246 inline bool
247 IsVisibleInCurrentWorkspace(const window_info *windowInfo)
248 {
249 	/*
250 	 The window list is always ordered from the top
251 	 front visible window (the first on the list), going down through all
252 	 the other visible windows, then all the hidden or non workspace
253 	 visible window at the end.
254 
255 	 layer > 2 : normal visible window.
256 	 layer == 2 : reserved for the desktop window (visible also).
257 	 layer < 2 : hidden (0) and non workspace visible window (1)
258 	*/
259 	return windowInfo->layer > 2;
260 }
261 
262 
263 bool
264 IsKeyDown(int32 key)
265 {
266 	key_info keyInfo;
267 
268 	get_key_info(&keyInfo);
269 	return (keyInfo.key_states[key >> 3] & (1 << ((7 - key) & 7))) != 0;
270 }
271 
272 
273 bool
274 IsWindowOK(const window_info *windowInfo)
275 {
276 	// is_mini (true means that the window is minimized).
277 	// if not, then
278 	// show_hide >= 1 means that the window is hidden.
279 	//
280 	// If the window is both minimized and hidden, then you get :
281 	//	 TWindow->is_mini = false;
282 	//	 TWindow->was_mini = true;
283 	//	 TWindow->show_hide >= 1;
284 
285 	if (windowInfo->w_type != _STD_W_TYPE_)
286 		return false;
287 
288 	if (windowInfo->is_mini)
289 		return true;
290 
291 	return windowInfo->show_hide_level <= 0;
292 }
293 
294 
295 bool
296 OKToUse(const TTeamGroup *teamGroup)
297 {
298 	if (!teamGroup)
299 		return false;
300 
301 	// skip background applications
302 	if ((teamGroup->Flags() & B_BACKGROUND_APP) != 0)
303 		return false;
304 
305 	// skip the Deakbar itself
306 	if (strcasecmp(teamGroup->Signature(), kDeskbarSignature) == 0)
307 		return false;
308 
309 	return true;
310 }
311 
312 
313 int
314 SmartStrcmp(const char *s1, const char *s2)
315 {
316 	if (strcasecmp(s1, s2) == 0)
317 		return 0;
318 
319 	// if the strings on differ in spaces or underscores they still match
320 	while (*s1 && *s2) {
321 		if ((*s1 == ' ') || (*s1 == '_')) {
322 			s1++;
323 			continue;
324 		}
325 		if ((*s2 == ' ') || (*s2 == '_')) {
326 			s2++;
327 			continue;
328 		}
329 		if (*s1 != *s2)
330 			return 1;		// they differ
331 		s1++;
332 		s2++;
333 	}
334 
335 	// if one of the strings ended before the other
336 	// ??? could process trailing spaces & underscores!
337 	if (*s1)
338 		return 1;
339 	if (*s2)
340 		return 1;
341 
342 	return 0;
343 }
344 
345 
346 //	#pragma mark -
347 
348 
349 TTeamGroup::TTeamGroup()
350 	:
351 	fTeams(NULL),
352 	fFlags(0),
353 	fName(NULL),
354 	fSmallIcon(NULL),
355 	fLargeIcon(NULL)
356 {
357 	fSignature[0] = '\0';
358 }
359 
360 
361 TTeamGroup::TTeamGroup(BList *teams, uint32 flags, char *name,
362 	const char *signature)
363 	:
364 	fTeams(teams),
365 	fFlags(flags),
366 	fName(name),
367 	fSmallIcon(NULL),
368 	fLargeIcon(NULL)
369 {
370 	strcpy(fSignature, signature);
371 
372 	fSmallIcon = new BBitmap(BRect(0, 0, 15, 15), kIconFormat);
373 	fLargeIcon = new BBitmap(BRect(0, 0, 31, 31), kIconFormat);
374 
375 	app_info appInfo;
376 	if (be_roster->GetAppInfo(signature, &appInfo) == B_OK) {
377 		BNode node(&(appInfo.ref));
378 		if (node.InitCheck() == B_OK) {
379 			BNodeInfo nodeInfo(&node);
380 			if (nodeInfo.InitCheck() == B_OK) {
381 				nodeInfo.GetTrackerIcon(fSmallIcon, B_MINI_ICON);
382 				nodeInfo.GetTrackerIcon(fLargeIcon, B_LARGE_ICON);
383 			}
384 		}
385 	}
386 }
387 
388 
389 TTeamGroup::~TTeamGroup()
390 {
391 	delete fTeams;
392 	free(fName);
393 	delete fSmallIcon;
394 	delete fLargeIcon;
395 }
396 
397 
398 void
399 TTeamGroup::Draw(BView *view, BRect bounds, bool main)
400 {
401 	BRect rect;
402 	if (main) {
403 		rect = fLargeIcon->Bounds();
404 		rect.OffsetTo(bounds.LeftTop());
405 		rect.OffsetBy(2, 2);
406 		view->DrawBitmap(fLargeIcon, rect);
407 	} else {
408 		rect = fSmallIcon->Bounds();
409 		rect.OffsetTo(bounds.LeftTop());
410 		rect.OffsetBy(10, 10);
411 		view->DrawBitmap(fSmallIcon, rect);
412 	}
413 }
414 
415 
416 //	#pragma mark -
417 
418 
419 TSwitchManager::TSwitchManager(BPoint point)
420 	: BHandler("SwitchManager"),
421 	fMainMonitor(create_sem(1, "main_monitor")),
422 	fBlock(false),
423 	fSkipUntil(0),
424 	fGroupList(10),
425 	fCurrentIndex(0),
426 	fCurrentSlot(0),
427 	fWindowID(-1)
428 {
429 	BRect rect(point.x, point.y,
430 		point.x + (kSlotSize * kNumSlots) - 1 + (2 * kHorizontalMargin),
431 		point.y + 82);
432 	fWindow = new TSwitcherWindow(rect, this);
433 	fWindow->AddHandler(this);
434 
435 	fWindow->Lock();
436 	fWindow->Run();
437 
438 	BList tmpList;
439 	TBarApp::Subscribe(BMessenger(this), &tmpList);
440 
441 	for (int32 i = 0; ; i++) {
442 		BarTeamInfo	*barTeamInfo = (BarTeamInfo	*)tmpList.ItemAt(i);
443 		if (!barTeamInfo)
444 			break;
445 
446 		TTeamGroup *tinfo = new TTeamGroup(barTeamInfo->teams, barTeamInfo->flags,
447 			barTeamInfo->name, barTeamInfo->sig);
448 		fGroupList.AddItem(tinfo);
449 
450 		barTeamInfo->teams = NULL;
451 		barTeamInfo->name = NULL;
452 
453 		delete barTeamInfo;
454 	}
455 	fWindow->Unlock();
456 }
457 
458 
459 TSwitchManager::~TSwitchManager()
460 {
461 	for (int32 i = fGroupList.CountItems(); i-- > 0;) {
462 		TTeamGroup *teamInfo = static_cast<TTeamGroup *>(fGroupList.ItemAt(i));
463 		delete teamInfo;
464 	}
465 }
466 
467 
468 void
469 TSwitchManager::MessageReceived(BMessage *message)
470 {
471 	switch (message->what) {
472 		case B_SOME_APP_QUIT:
473 		{
474 			// This is only sent when last team of a matching set quits
475 			team_id teamID;
476 			int i = 0;
477 			TTeamGroup *tinfo;
478 			message->FindInt32("team", &teamID);
479 			while ((tinfo = (TTeamGroup *) fGroupList.ItemAt(i)) != NULL) {
480 				if (tinfo->TeamList()->HasItem((void *)teamID)) {
481 					fGroupList.RemoveItem(i);
482 
483 					if (OKToUse(tinfo)) {
484 						fWindow->Redraw(i);
485 						if (i <= fCurrentIndex) {
486 							fCurrentIndex--;
487 							CycleApp(true);
488 						}
489 					}
490 					delete tinfo;
491 					break;
492 				}
493 				i++;
494 			}
495 			break;
496 		}
497 
498 		case B_SOME_APP_LAUNCHED:
499 		{
500 			BList *teams;
501 			const char *name;
502 			BBitmap	*smallIcon;
503 			uint32 flags;
504 			const char *signature;
505 
506 			if (message->FindPointer("teams", (void **)&teams) != B_OK)
507 				break;
508 
509 			if (message->FindPointer("icon", (void **)&smallIcon) != B_OK) {
510 				delete teams;
511 				break;
512 			}
513 			delete smallIcon;
514 			if (message->FindString("sig", &signature) != B_OK) {
515 				delete teams;
516 				break;
517 			}
518 			if (message->FindInt32("flags", (int32 *)&flags) != B_OK) {
519 				delete teams;
520 				break;
521 			}
522 			if (message->FindString("name", &name) != B_OK) {
523 				delete teams;
524 				break;
525 			}
526 
527 			TTeamGroup *tinfo = new TTeamGroup(teams, flags, strdup(name), signature);
528 
529 			fGroupList.AddItem(tinfo);
530 			if (OKToUse(tinfo))
531 				fWindow->Redraw(fGroupList.CountItems() - 1);
532 
533 			break;
534 		}
535 
536 		case msg_AddTeam:
537 		{
538 			const char *signature = message->FindString("sig");
539 			team_id team = message->FindInt32("team");
540 
541 			int32 numItems = fGroupList.CountItems();
542 			for (int32 i = 0; i < numItems; i++) {
543 				TTeamGroup *tinfo = (TTeamGroup *)fGroupList.ItemAt(i);
544 				if (strcasecmp(tinfo->Signature(), signature) == 0) {
545 					if (!(tinfo->TeamList()->HasItem((void *)team)))
546 						tinfo->TeamList()->AddItem((void *)team);
547 					break;
548 				}
549 			}
550 			break;
551 		}
552 
553 		case msg_RemoveTeam:
554 		{
555 			team_id team = message->FindInt32("team");
556 
557 			int32 numItems = fGroupList.CountItems();
558 			for (int32 i = 0; i < numItems; i++) {
559 				TTeamGroup *tinfo = (TTeamGroup *)fGroupList.ItemAt(i);
560 				if (tinfo->TeamList()->HasItem((void *)team)) {
561 					tinfo->TeamList()->RemoveItem((void *)team);
562 					break;
563 				}
564 			}
565 			break;
566 		}
567 
568 		case 'TASK':
569 		{
570 			// The first TASK message calls MainEntry. Subsequent ones
571 			// call Process().
572 			bigtime_t time;
573 			message->FindInt64("when", (int64 *)&time);
574 
575 			// The fSkipUntil stuff can be removed once the new input_server
576 			// starts differentiating initial key_downs from KeyDowns generated
577 			// by auto-repeat. Until then the fSkipUntil stuff helps, but it
578 			// isn't perfect.
579 
580 			if (time < fSkipUntil)
581 				break;
582 
583 			status_t status = acquire_sem_etc(fMainMonitor, 1, B_TIMEOUT, 0);
584 			if (status != B_OK) {
585 				if (!fWindow->IsHidden() && !fBlock) {
586 					// Want to skip TASK msgs posted before the window
587 					// was made visible. Better UI feel if we do this.
588 					if (time > fSkipUntil) {
589 						uint32 modifiers;
590 						message->FindInt32("modifiers", (int32 *)&modifiers);
591 						Process((modifiers & B_SHIFT_KEY) == 0, (modifiers & B_OPTION_KEY) != 0);
592 					}
593 				}
594 			} else
595 				MainEntry(message);
596 
597 			break;
598 		}
599 
600 		default:
601 			break;
602 	}
603 }
604 
605 
606 void
607 TSwitchManager::MainEntry(BMessage *message)
608 {
609 	bigtime_t keyRepeatRate;
610 	get_key_repeat_delay(&keyRepeatRate);
611 
612 	if (keyRepeatRate < 200000)
613 		keyRepeatRate = 200000;
614 
615 	bigtime_t timeout = system_time() + keyRepeatRate;
616 
617 	app_info appInfo;
618 	be_roster->GetActiveAppInfo(&appInfo);
619 
620 	int32 index;
621 	fCurrentIndex = (FindTeam(appInfo.team, &index) != NULL) ? index : 0;
622 	int32 key;
623 	message->FindInt32("key", (int32 *)&key);
624 
625 	uint32 modifierKeys = 0;
626 	while (system_time() < timeout) {
627 		modifierKeys = modifiers();
628 		if (!IsKeyDown(key)) {
629 			QuickSwitch(message);
630 			return;
631 		}
632 		if ((modifierKeys & B_CONTROL_KEY) == 0) {
633 			QuickSwitch(message);
634 			return;
635 		}
636 		snooze(50000);
637 	}
638 
639 	Process((modifierKeys & B_SHIFT_KEY) == 0, (modifierKeys & B_OPTION_KEY) != 0);
640 }
641 
642 
643 void
644 TSwitchManager::Stop(bool do_action, uint32)
645 {
646 	fWindow->Hide();
647 	if (do_action)
648 		ActivateApp(true, true);
649 
650 	release_sem(fMainMonitor);
651 }
652 
653 
654 TTeamGroup *
655 TSwitchManager::FindTeam(team_id teamID, int32 *index)
656 {
657 	int i = 0;
658 	TTeamGroup	*tinfo;
659 	while ((tinfo = (TTeamGroup *)fGroupList.ItemAt(i)) != NULL) {
660 		if (tinfo->TeamList()->HasItem((void*) teamID)) {
661 			*index = i;
662 			return tinfo;
663 		}
664 		i++;
665 	}
666 
667 	return NULL;
668 }
669 
670 
671 void
672 TSwitchManager::Process(bool forward, bool byWindow)
673 {
674 	bool hidden = false;
675 	if (fWindow->Lock()) {
676 		hidden = fWindow->IsHidden();
677 		fWindow->Unlock();
678 	}
679 	if (byWindow) {
680 		// If hidden we need to get things started by switching to correct app
681 		if (hidden)
682 			SwitchToApp(fCurrentIndex, fCurrentIndex, forward);
683 		CycleWindow(forward, false);
684 	} else
685 		CycleApp(forward, false);
686 
687 	if (hidden) {
688 		// more auto keyrepeat code
689 		// Because of key repeats we don't want to respond to any extraneous
690 		// 'TASK' messages until the window is completely shown. So block here.
691 		// the WindowActivated hook function will unblock.
692 		fBlock = true;
693 
694 		if (fWindow->Lock()) {
695 			BRect screenFrame = BScreen().Frame();
696 			BRect windowFrame = fWindow->Frame();
697 
698 			if (!screenFrame.Contains(windowFrame)) {
699 				// center the window
700 				BPoint point((screenFrame.left + screenFrame.right) / 2,
701 					(screenFrame.top + screenFrame.bottom) / 2);
702 
703 				point.x -= (windowFrame.Width() / 2);
704 				point.y -= (windowFrame.Height() / 2);
705 				fWindow->MoveTo(point);
706 			}
707 
708 			fWindow->Show();
709 			fWindow->Unlock();
710 		}
711 	}
712 }
713 
714 
715 void
716 TSwitchManager::QuickSwitch(BMessage *message)
717 {
718 	uint32 modifiers = 0;
719 	message->FindInt32("modifiers", (int32 *) &modifiers);
720 
721 	team_id team;
722 	if (message->FindInt32("team", &team) == B_OK) {
723 		bool forward = ((modifiers & B_SHIFT_KEY) == 0);
724 
725 		if ((modifiers & B_OPTION_KEY) != 0)
726 			SwitchWindow(team, forward, true);
727 		else
728 			CycleApp(forward, true);
729 	}
730 
731 	release_sem(fMainMonitor);
732 }
733 
734 
735 int32
736 TSwitchManager::CountVisibleGroups()
737 {
738 	int32 result = 0;
739 
740 	int32 count = fGroupList.CountItems();
741 	for (int32 i = 0; i < count; i++) {
742 		if (!OKToUse((TTeamGroup *) fGroupList.ItemAt(i)))
743 			continue;
744 
745 		result++;
746 	}
747 	return result;
748 }
749 
750 
751 void
752 TSwitchManager::CycleWindow(bool forward, bool wrap)
753 {
754 	int32 max = CountWindows(fCurrentIndex);
755 	int32 prev = fCurrentWindow;
756 	int32 next = fCurrentWindow;
757 
758 	if (forward) {
759 		next++;
760 		if (next >= max) {
761 			if (!wrap)
762 				return;
763 			next = 0;
764 		}
765 	} else {
766 		next--;
767 		if (next < 0) {
768 			if (!wrap)
769 				return;
770 			next = max - 1;
771 		}
772 	}
773 	fCurrentWindow = next;
774 
775 	if (fCurrentWindow != prev)
776 		fWindow->WindowView()->ShowIndex(fCurrentWindow);
777 }
778 
779 
780 void
781 TSwitchManager::CycleApp(bool forward, bool activateNow)
782 {
783 	int32 startIndex = fCurrentIndex;
784 	int32 max = fGroupList.CountItems();
785 
786 	for (;;) {
787 		if (forward) {
788 			fCurrentIndex++;
789 			if (fCurrentIndex >= max)
790 				fCurrentIndex = 0;
791 		} else {
792 			fCurrentIndex--;
793 			if (fCurrentIndex < 0)
794 				fCurrentIndex = max - 1;
795 		}
796 		if ((fCurrentIndex == startIndex))
797 			// we've gone completely through the list without finding
798 			// a good app. Oh well.
799 			return;
800 
801 		if (!OKToUse((TTeamGroup *)fGroupList.ItemAt(fCurrentIndex)))
802 			continue;
803 
804 		// if we're here then we found a good one
805 		SwitchToApp(startIndex, fCurrentIndex, forward);
806 
807 		if (!activateNow)
808 			break;
809 
810 		if (ActivateApp(false, false))
811 			break;
812 	}
813 }
814 
815 
816 void
817 TSwitchManager::SwitchToApp(int32 previousIndex, int32 newIndex, bool forward)
818 {
819 	int32 previousSlot = fCurrentSlot;
820 
821 	fCurrentIndex = newIndex;
822 	fCurrentSlot = fWindow->SlotOf(fCurrentIndex);
823 	fCurrentWindow = 0;
824 
825 	fWindow->Update(previousIndex, fCurrentIndex, previousSlot, fCurrentSlot, forward);
826 }
827 
828 
829 bool
830 TSwitchManager::ActivateApp(bool forceShow, bool allowWorkspaceSwitch)
831 {
832 	// Let's get the info about the selected window. If it doesn't exist
833 	// anymore then get info about first window. If that doesn't exist then
834 	// do nothing.
835 	window_info	*windowInfo = WindowInfo(fCurrentIndex, fCurrentWindow);
836 	if (windowInfo == NULL) {
837 		windowInfo = WindowInfo(fCurrentIndex, 0);
838 		if (windowInfo == NULL)
839 			return false;
840 	}
841 
842 	int32 currentWorkspace = current_workspace();
843 	TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex);
844 	// Let's handle the easy case first: There's only 1 team in the group
845 	if (teamGroup->TeamList()->CountItems() == 1) {
846 		bool result;
847 		if (forceShow && (fCurrentWindow != 0 || windowInfo->is_mini))
848 			do_window_action(windowInfo->id, B_BRING_TO_FRONT,
849 				BRect(0, 0, 0, 0), false);
850 
851 		if (!forceShow && windowInfo->is_mini) {
852 			// we aren't unhiding minimized windows, so we can't do
853 			// anything here
854 			result = false;
855 		} else if (!allowWorkspaceSwitch
856 			&& (windowInfo->workspaces & (1 << currentWorkspace)) == 0) {
857 			// we're not supposed to switch workspaces so abort.
858 			result = false;
859 		} else {
860 			result = true;
861 			be_roster->ActivateApp((team_id)teamGroup->TeamList()->ItemAt(0));
862 		}
863 
864 		ASSERT(windowInfo);
865 		free(windowInfo);
866 		return result;
867 	}
868 
869 	// Now the trickier case. We're trying to Bring to the Front a group
870 	// of teams. The current window (defined by fCurrentWindow) will define
871 	// which workspace we're going to. Then, once that is determined we
872 	// want to bring to the front every window of the group of teams that
873 	// lives in that workspace.
874 
875 	if ((windowInfo->workspaces & (1 << currentWorkspace)) == 0) {
876 		if (!allowWorkspaceSwitch) {
877 			// If the first window in the list isn't in current workspace,
878 			// then none are. So we can't switch to this app.
879 			ASSERT(windowInfo);
880 			free(windowInfo);
881 			return false;
882 		}
883 		int32 destWorkspace = LowBitIndex(windowInfo->workspaces);
884 		// now switch to that workspace
885 		activate_workspace(destWorkspace);
886 	}
887 
888 	if (!forceShow && windowInfo->is_mini) {
889 		// If the first window in the list is hidden then no windows in
890 		// this group are visible. So we can't switch to this app.
891 		ASSERT(windowInfo);
892 		free(windowInfo);
893 		return false;
894 	}
895 
896 	int32 tokenCount;
897 	int32 *tokens = get_token_list(-1, &tokenCount);
898 	if (!tokens) {
899 		ASSERT(windowInfo);
900 		free(windowInfo);
901 		return true;	// weird error, so don't try to recover
902 	}
903 
904 	BList windowsToActivate;
905 
906 	// Now we go through all the windows in the current workspace list in order.
907 	// As we hit member teams we build the "activate" list.
908 	for (int32 i = 0; i < tokenCount; i++) {
909 		window_info	*matchWindowInfo = get_window_info(tokens[i]);
910 		if (!matchWindowInfo) {
911 			// That window probably closed. Just go to the next one.
912 			continue;
913 		}
914 		if (!IsVisibleInCurrentWorkspace(matchWindowInfo)) {
915 			// first non-visible in workspace window means we're done.
916 			free(matchWindowInfo);
917 			break;
918 		}
919 		if (matchWindowInfo->id != windowInfo->id
920 			&& teamGroup->TeamList()->HasItem((void *)matchWindowInfo->team))
921 			windowsToActivate.AddItem((void *)matchWindowInfo->id);
922 
923 		free(matchWindowInfo);
924 	}
925 
926 	free(tokens);
927 
928 	// Want to go through the list backwards to keep windows in same relative
929 	// order.
930 	int32 i = windowsToActivate.CountItems() - 1;
931 	for (; i >= 0; i--) {
932 		int32 wid = (int32) windowsToActivate.ItemAt(i);
933 		do_window_action(wid, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false);
934 	}
935 
936 	// now bring the select window on top of everything.
937 
938 	do_window_action(windowInfo->id, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false);
939 	free(windowInfo);
940 	return true;
941 }
942 
943 
944 void
945 TSwitchManager::QuitApp()
946 {
947 	// check if we're in the last slot already (the last usable team group)
948 
949 	TTeamGroup *teamGroup;
950 	int32 count = 0;
951 	for (int32 i = fCurrentIndex + 1; i < fGroupList.CountItems(); i++) {
952 		teamGroup = (TTeamGroup *)fGroupList.ItemAt(i);
953 
954 		if (!OKToUse(teamGroup))
955 			continue;
956 
957 		count++;
958 	}
959 
960 	teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex);
961 
962 	if (count == 0) {
963 		// switch to previous app in the list so that we don't jump to
964 		// the start of the list (try to keep the same position when
965 		// the apps at the current index go away)
966 		CycleApp(false, false);
967 	}
968 
969 	// send the quit request to all teams in this group
970 
971 	for (int32 i = teamGroup->TeamList()->CountItems(); i-- > 0;) {
972 		team_id team = (team_id)teamGroup->TeamList()->ItemAt(i);
973 		app_info info;
974 		if (be_roster->GetRunningAppInfo(team, &info) == B_OK) {
975 			if (!strcasecmp(info.signature, kTrackerSignature)) {
976 				// Tracker can't be quit this way
977 				continue;
978 			}
979 
980 			BMessenger messenger(NULL, team);
981 			messenger.SendMessage(B_QUIT_REQUESTED);
982 		}
983 	}
984 }
985 
986 
987 void
988 TSwitchManager::HideApp()
989 {
990 	// hide all teams in this group
991 
992 	TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(fCurrentIndex);
993 
994 	for (int32 i = teamGroup->TeamList()->CountItems(); i-- > 0;) {
995 		team_id team = (team_id)teamGroup->TeamList()->ItemAt(i);
996 		app_info info;
997 		if (be_roster->GetRunningAppInfo(team, &info) == B_OK)
998 			do_minimize_team(BRect(), team, false);
999 	}
1000 }
1001 
1002 
1003 window_info *
1004 TSwitchManager::WindowInfo(int32 groupIndex, int32 windowIndex)
1005 {
1006 	TTeamGroup *teamGroup = (TTeamGroup *) fGroupList.ItemAt(groupIndex);
1007 	if (!teamGroup)
1008 		return NULL;
1009 
1010 	int32 tokenCount;
1011 	int32 *tokens = get_token_list(-1, &tokenCount);
1012 	if (!tokens)
1013 		return NULL;
1014 
1015 	int32 matches = 0;
1016 
1017 	// Want to find the "windowIndex'th" window in window order that belongs
1018 	// the the specified group (groupIndex). Since multiple teams can belong to
1019 	// the same group (multiple-launch apps) we get the list of _every_
1020 	// window and go from there.
1021 
1022 	window_info	*result = NULL;
1023 	for (int32 i = 0; i < tokenCount; i++) {
1024 		window_info	*windowInfo = get_window_info(tokens[i]);
1025 		if (windowInfo) {
1026 			// skip hidden/special windows
1027 			if (IsWindowOK(windowInfo)
1028 				&& (teamGroup->TeamList()->HasItem((void *)windowInfo->team))) {
1029 				// this window belongs to the team!
1030 				if (matches == windowIndex) {
1031 					// we found it!
1032 					result = windowInfo;
1033 					break;
1034 				}
1035 				matches++;
1036 			}
1037 			free(windowInfo);
1038 		}
1039 		// else - that window probably closed. Just go to the next one.
1040 	}
1041 
1042 	free(tokens);
1043 
1044 	return result;
1045 }
1046 
1047 
1048 int32
1049 TSwitchManager::CountWindows(int32 groupIndex, bool )
1050 {
1051 	TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(groupIndex);
1052 	if (!teamGroup)
1053 		return 0;
1054 
1055 	int32 result = 0;
1056 
1057 	for (int32 i = 0; ; i++) {
1058 		team_id	teamID = (team_id)teamGroup->TeamList()->ItemAt(i);
1059 		if (teamID == 0)
1060 			break;
1061 
1062 		int32 count;
1063 		int32 *tokens = get_token_list(teamID, &count);
1064 		if (!tokens)
1065 			continue;
1066 
1067 		for (int32 i = 0; i < count; i++) {
1068 			window_info	*windowInfo = get_window_info(tokens[i]);
1069 			if (windowInfo) {
1070 				if (IsWindowOK(windowInfo))
1071 					result++;
1072 				free(windowInfo);
1073 			}
1074 		}
1075 		free(tokens);
1076 	}
1077 
1078 	return result;
1079 }
1080 
1081 
1082 void
1083 TSwitchManager::ActivateWindow(int32 windowID)
1084 {
1085 	if (windowID == -1)
1086 		windowID = fWindowID;
1087 
1088 	do_window_action(windowID, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false);
1089 }
1090 
1091 
1092 void
1093 TSwitchManager::SwitchWindow(team_id team, bool, bool activate)
1094 {
1095 	// Find the _last_ window in the current workspace that belongs
1096 	// to the group. This is the window to activate.
1097 
1098 	int32 index;
1099 	TTeamGroup*teamGroup = FindTeam(team, &index);
1100 
1101 	// cycle through the windows in the active application
1102 	int32 count;
1103 	int32 *tokens = get_token_list(-1, &count);
1104 	for (int32 i = count-1; i >= 0; i--) {
1105 		window_info	*windowInfo;
1106 		windowInfo = get_window_info(tokens[i]);
1107 		if (windowInfo && IsVisibleInCurrentWorkspace(windowInfo)
1108 			&& teamGroup->TeamList()->HasItem((void *)windowInfo->team)) {
1109 				fWindowID = windowInfo->id;
1110 				if (activate)
1111 					ActivateWindow(windowInfo->id);
1112 
1113 				free(windowInfo);
1114 				break;
1115 		}
1116 		free(windowInfo);
1117 	}
1118 	free(tokens);
1119 }
1120 
1121 
1122 void
1123 TSwitchManager::Unblock()
1124 {
1125 	fBlock = false;
1126 	fSkipUntil = system_time();
1127 }
1128 
1129 
1130 int32
1131 TSwitchManager::CurrentIndex()
1132 {
1133 	return fCurrentIndex;
1134 }
1135 
1136 
1137 int32
1138 TSwitchManager::CurrentWindow()
1139 {
1140 	return fCurrentWindow;
1141 }
1142 
1143 
1144 int32
1145 TSwitchManager::CurrentSlot()
1146 {
1147 	return fCurrentSlot;
1148 }
1149 
1150 
1151 BList *
1152 TSwitchManager::GroupList()
1153 {
1154 	return &fGroupList;
1155 }
1156 
1157 
1158 //	#pragma mark -
1159 
1160 
1161 TBox::TBox(BRect bounds, TSwitchManager *manager, TSwitcherWindow *window,
1162 	TIconView *iview)
1163 	: BBox(bounds, "top", B_FOLLOW_NONE, B_WILL_DRAW, B_NO_BORDER),
1164 	fManager(manager),
1165 	fWindow(window),
1166 	fIconView(iview),
1167 	fLeftScroller(false),
1168 	fRightScroller(false)
1169 {
1170 }
1171 
1172 
1173 void
1174 TBox::AllAttached()
1175 {
1176 	BRect centerRect(kCenterSlot * kSlotSize, 0,
1177 		(kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1);
1178 	BRect frame = fIconView->Frame();
1179 
1180 	// scroll the centerRect to correct location
1181 	centerRect.OffsetBy(frame.left, frame.top);
1182 
1183 	// switch to local coords
1184 	fIconView->ConvertToParent(&centerRect);
1185 
1186 	fCenter = centerRect;
1187 }
1188 
1189 
1190 void
1191 TBox::MouseDown(BPoint where)
1192 {
1193 	if (!fLeftScroller && !fRightScroller && !fUpScroller && !fDownScroller)
1194 		return;
1195 
1196 	BRect frame = fIconView->Frame();
1197 	BRect bounds = Bounds();
1198 
1199 	if (fLeftScroller) {
1200 		BRect lhit(0, frame.top, frame.left, frame.bottom);
1201 		if (lhit.Contains(where)) {
1202 			// Want to scroll by NUMSLOTS-1 slots
1203 			int32 previousIndex = fManager->CurrentIndex();
1204 			int32 previousSlot = fManager->CurrentSlot();
1205 			int32 newSlot = previousSlot - (kNumSlots - 1);
1206 			if (newSlot < 0)
1207 				newSlot = 0;
1208 			int32 newIndex = fIconView->IndexAt(newSlot);
1209 
1210 			fManager->SwitchToApp(previousIndex, newIndex, false);
1211 		}
1212 	}
1213 
1214 	if (fRightScroller) {
1215 		BRect rhit(frame.right, frame.top, bounds.right, frame.bottom);
1216 		if (rhit.Contains(where)) {
1217 			// Want to scroll by NUMSLOTS-1 slots
1218 			int32 previousIndex = fManager->CurrentIndex();
1219 			int32 previousSlot = fManager->CurrentSlot();
1220 			int32 newSlot = previousSlot + (kNumSlots-1);
1221 			int32 newIndex = fIconView->IndexAt(newSlot);
1222 
1223 			if (newIndex < 0) {
1224 				// don't have a page full to scroll
1225 				int32 valid = fManager->CountVisibleGroups();
1226 				newIndex = fIconView->IndexAt(valid-1);
1227 			}
1228 			fManager->SwitchToApp(previousIndex, newIndex, true);
1229 		}
1230 	}
1231 
1232 	frame = fWindow->WindowView()->Frame();
1233 	if (fUpScroller) {
1234 		BRect hit1(frame.left - 10, frame.top, frame.left, (frame.top+frame.bottom)/2);
1235 		BRect hit2(frame.right, frame.top, frame.right + 10, (frame.top+frame.bottom)/2);
1236 		if (hit1.Contains(where) || hit2.Contains(where)) {
1237 			// Want to scroll up 1 window
1238 			fManager->CycleWindow(false, false);
1239 		}
1240 	}
1241 
1242 	if (fDownScroller) {
1243 		BRect hit1(frame.left - 10, (frame.top+frame.bottom) / 2, frame.left, frame.bottom);
1244 		BRect hit2(frame.right, (frame.top+frame.bottom) / 2, frame.right + 10, frame.bottom);
1245 		if (hit1.Contains(where) || hit2.Contains(where)) {
1246 			// Want to scroll down 1 window
1247 			fManager->CycleWindow(true, false);
1248 		}
1249 	}
1250 }
1251 
1252 
1253 void
1254 TBox::Draw(BRect update)
1255 {
1256 	static const int32 kChildInset = 7;
1257 	static const int32 kWedge = 6;
1258 
1259 	BBox::Draw(update);
1260 
1261 	// The fancy border around the icon view
1262 
1263 	BRect bounds = Bounds();
1264 	float height = fIconView->Bounds().Height();
1265 	float center = (bounds.right + bounds.left) / 2;
1266 
1267 	BRect box(3, 3, bounds.right - 3, 3 + height + kChildInset * 2);
1268 	rgb_color white = {255,255,255,255};
1269 	rgb_color standardGray = ui_color(B_PANEL_BACKGROUND_COLOR);
1270 	rgb_color veryDarkGray = {128, 128, 128, 255};
1271 	rgb_color darkGray = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT);
1272 
1273 	// Fill the area with dark gray
1274 	SetHighColor(darkGray);
1275 	box.InsetBy(1,1);
1276 	FillRect(box);
1277 
1278 	box.InsetBy(-1,-1);
1279 
1280 	BeginLineArray(50);
1281 
1282 	// The main frame around the icon view
1283 	AddLine(box.LeftTop(), BPoint(center-kWedge, box.top), veryDarkGray);
1284 	AddLine(BPoint(center+kWedge, box.top), box.RightTop(), veryDarkGray);
1285 
1286 	AddLine(box.LeftBottom(), BPoint(center-kWedge, box.bottom), veryDarkGray);
1287 	AddLine(BPoint(center+kWedge, box.bottom), box.RightBottom(), veryDarkGray);
1288 	AddLine(box.LeftBottom() + BPoint(1, 1),
1289 		BPoint(center-kWedge, box.bottom + 1), white);
1290 	AddLine(BPoint(center+kWedge, box.bottom) + BPoint(0, 1),
1291 		box.RightBottom() + BPoint(1, 1), white);
1292 
1293 	AddLine(box.LeftTop(), box.LeftBottom(), veryDarkGray);
1294 	AddLine(box.RightTop(), box.RightBottom(), veryDarkGray);
1295 	AddLine(box.RightTop() + BPoint(1, 1),
1296 		box.RightBottom() + BPoint(1, 1), white);
1297 
1298 	// downward pointing area at top of frame
1299 	BPoint point(center - kWedge, box.top);
1300 	AddLine(point, point + BPoint(kWedge, kWedge), veryDarkGray);
1301 	AddLine(point + BPoint(kWedge, kWedge),
1302 		BPoint(center+kWedge, point.y), veryDarkGray);
1303 
1304 	AddLine(point + BPoint(1, 0),
1305 		point + BPoint(1, 0) + BPoint(kWedge - 1, kWedge - 1), white);
1306 
1307 	AddLine(point + BPoint(2, -1) + BPoint(kWedge - 1, kWedge - 1),
1308 		BPoint(center+kWedge-1, point.y), darkGray);
1309 
1310 	BPoint topPoint = point;
1311 
1312 	// upward pointing area at bottom of frame
1313 	point.y = box.bottom;
1314 	point.x = center - kWedge;
1315 	AddLine(point, point + BPoint(kWedge, -kWedge), veryDarkGray);
1316 	AddLine(point + BPoint(kWedge, -kWedge),
1317 		BPoint(center+kWedge, point.y), veryDarkGray);
1318 
1319 	AddLine(point + BPoint(1, 0),
1320 		point + BPoint(1, 0) + BPoint(kWedge - 1, -(kWedge - 1)), white);
1321 
1322 	AddLine(point + BPoint(2 , 1) + BPoint(kWedge - 1, -(kWedge - 1)),
1323 		BPoint(center + kWedge - 1, point.y), darkGray);
1324 
1325 	BPoint bottomPoint = point;
1326 
1327 	EndLineArray();
1328 
1329 	// fill the downward pointing arrow area
1330 	SetHighColor(standardGray);
1331 	FillTriangle(topPoint + BPoint(2, 0),
1332 		topPoint + BPoint(2, 0) + BPoint(kWedge - 2, kWedge - 2),
1333 		BPoint(center + kWedge - 2, topPoint.y));
1334 
1335 	// fill the upward pointing arrow area
1336 	SetHighColor(standardGray);
1337 	FillTriangle(bottomPoint + BPoint(2,0),
1338 		bottomPoint + BPoint(2, 0) + BPoint(kWedge - 2, -(kWedge - 2)),
1339 		BPoint(center + kWedge - 2, bottomPoint.y));
1340 
1341 	DrawIconScrollers(false);
1342 	DrawWindowScrollers(false);
1343 
1344 }
1345 
1346 
1347 void
1348 TBox::DrawIconScrollers(bool force)
1349 {
1350 	bool updateLeft = false;
1351 	bool updateRight = false;
1352 	rgb_color leftc;
1353 	rgb_color rightc;
1354 	rgb_color bkg = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT);
1355 	rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT);
1356 
1357 	BRect rect = fIconView->Bounds();
1358 	if (rect.left > (kSlotSize * kCenterSlot)) {
1359 		updateLeft = true;
1360 		fLeftScroller = true;
1361 		leftc = dark;
1362 	} else {
1363 		fLeftScroller = false;
1364 		if (force) {
1365 			updateLeft = true;
1366 			leftc = bkg;
1367 		}
1368 	}
1369 
1370 	int32 maxIndex = fManager->GroupList()->CountItems() - 1;
1371 			// last_frame is in fIconView coordinate space
1372 	BRect lastFrame = fIconView->FrameOf(maxIndex);
1373 
1374 	if (lastFrame.right > rect.right) {
1375 		updateRight = true;
1376 		fRightScroller = true;
1377 		rightc = dark;
1378 	} else {
1379 		fRightScroller = false;
1380 		if (force) {
1381 			updateRight = true;
1382 			rightc = bkg;
1383 		}
1384 	}
1385 
1386 	rect = fIconView->Frame();
1387 	if (updateLeft) {
1388 		SetHighColor(leftc);
1389 		BPoint	pt1, pt2, pt3;
1390 		pt1.x = rect.left - 5;
1391 		pt1.y = floorf((rect.bottom + rect.top) / 2);
1392 		pt2.x = pt3.x = pt1.x + 3;
1393 		pt2.y = pt1.y - 3;
1394 		pt3.y = pt1.y + 3;
1395 		FillTriangle(pt1, pt2, pt3);
1396 	}
1397 	if (updateRight) {
1398 		SetHighColor(rightc);
1399 		BPoint	pt1, pt2, pt3;
1400 		pt1.x = rect.right + 4;
1401 		pt1.y = rintf((rect.bottom + rect.top) / 2);
1402 		pt2.x = pt3.x = pt1.x - 4;
1403 		pt2.y = pt1.y - 4;
1404 		pt3.y = pt1.y + 4;
1405 		FillTriangle(pt1, pt2, pt3);
1406 	}
1407 }
1408 
1409 
1410 void
1411 TBox::DrawWindowScrollers(bool force)
1412 {
1413 	bool updateUp = false;
1414 	bool updateDown = false;
1415 	rgb_color upColor;
1416 	rgb_color downColor;
1417 	rgb_color bkg = ui_color(B_PANEL_BACKGROUND_COLOR);
1418 	rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT);
1419 
1420 	BRect rect = fWindow->WindowView()->Bounds();
1421 	if (rect.top != 0) {
1422 		updateUp = true;
1423 		fUpScroller = true;
1424 		upColor = dark;
1425 	} else {
1426 		fUpScroller = false;
1427 		if (force) {
1428 			updateUp = true;
1429 			upColor = bkg;
1430 		}
1431 	}
1432 
1433 	int32 groupIndex = fManager->CurrentIndex();
1434 	int32 maxIndex = fManager->CountWindows(groupIndex) - 1;
1435 
1436 	BRect lastFrame(0, 0, 0, 0);
1437 	if (maxIndex >= 0) {
1438 		lastFrame = fWindow->WindowView()->FrameOf(maxIndex);
1439 	}
1440 	if (maxIndex >= 0 && lastFrame.bottom > rect.bottom) {
1441 		updateDown = true;
1442 		fDownScroller = true;
1443 		downColor = dark;
1444 	} else {
1445 		fDownScroller = false;
1446 		if (force) {
1447 			updateDown = true;
1448 			downColor = bkg;
1449 		}
1450 	}
1451 
1452 	rect = fWindow->WindowView()->Frame();
1453 	rect.InsetBy(-3, 0);
1454 	if (updateUp) {
1455 		SetHighColor(upColor);
1456 		BPoint	pt1, pt2, pt3;
1457 		pt1.x = rect.left - 6;
1458 		pt1.y = rect.top + 3;
1459 		pt2.y = pt3.y = pt1.y + 4;
1460 		pt2.x = pt1.x - 4;
1461 		pt3.x = pt1.x + 4;
1462 		FillTriangle(pt1, pt2, pt3);
1463 
1464 		pt1.x += rect.Width() + 12;
1465 		pt2.x += rect.Width() + 12;
1466 		pt3.x += rect.Width() + 12;
1467 		FillTriangle(pt1, pt2, pt3);
1468 	}
1469 	if (updateDown) {
1470 		SetHighColor(downColor);
1471 		BPoint	pt1, pt2, pt3;
1472 		pt1.x = rect.left - 6;
1473 		pt1.y = rect.bottom - 3;
1474 		pt2.y = pt3.y = pt1.y - 4;
1475 		pt2.x = pt1.x - 4;
1476 		pt3.x = pt1.x + 4;
1477 		FillTriangle(pt1, pt2, pt3);
1478 
1479 		pt1.x += rect.Width() + 12;
1480 		pt2.x += rect.Width() + 12;
1481 		pt3.x += rect.Width() + 12;
1482 		FillTriangle(pt1, pt2, pt3);
1483 
1484 	}
1485 	Sync();
1486 }
1487 
1488 
1489 //	#pragma mark -
1490 
1491 
1492 TSwitcherWindow::TSwitcherWindow(BRect frame, TSwitchManager *manager)
1493 	: BWindow(frame, "Twitcher", B_MODAL_WINDOW_LOOK,
1494 			B_MODAL_ALL_WINDOW_FEEL,
1495 			B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE, B_ALL_WORKSPACES),
1496 	fManager(manager),
1497 	fHairTrigger(true)
1498 {
1499 	BRect rect = frame;
1500 	rect.OffsetTo(B_ORIGIN);
1501 	rect.InsetBy(kHorizontalMargin, 0);
1502 	rect.top = kVerticalMargin;
1503 	rect.bottom = rect.top + kSlotSize - 1;
1504 
1505 	fIconView = new TIconView(rect, manager, this);
1506 
1507 	rect.top = rect.bottom + (kVerticalMargin * 1 + 4);
1508 	rect.InsetBy(9, 0);
1509 
1510 	fWindowView = new TWindowView(rect, manager, this);
1511 	fWindowView->ResizeToPreferred();
1512 
1513 	fTopView = new TBox(Bounds(), fManager, this, fIconView);
1514 	AddChild(fTopView);
1515 
1516 	SetPulseRate(0);
1517 	fTopView->AddChild(fIconView);
1518 	fTopView->AddChild(fWindowView);
1519 }
1520 
1521 
1522 TSwitcherWindow::~TSwitcherWindow()
1523 {
1524 }
1525 
1526 
1527 void
1528 TSwitcherWindow::MessageReceived(BMessage *message)
1529 {
1530 	switch (message->what) {
1531 		case B_KEY_DOWN:
1532 		{
1533 			int32 repeats = 0;
1534 			if (message->FindInt32("be:key_repeat", &repeats) == B_OK
1535 				&& (fSkipKeyRepeats || (repeats % 6) != 0))
1536 				break;
1537 
1538 			// The first actual key press let's us listening to repeated keys
1539 			fSkipKeyRepeats = false;
1540 
1541 			uint32 rawChar;
1542 			uint32 modifiers;
1543 			message->FindInt32("raw_char", 0, (int32 *)&rawChar);
1544 			message->FindInt32("modifiers", 0, (int32 *)&modifiers);
1545 			DoKey(rawChar, modifiers);
1546 			break;
1547 		}
1548 
1549 		default:
1550 			BWindow::MessageReceived(message);
1551 	}
1552 }
1553 
1554 
1555 void
1556 TSwitcherWindow::Redraw(int32 index)
1557 {
1558 	BRect frame = fIconView->FrameOf(index);
1559 	frame.right = fIconView->Bounds().right;
1560 	fIconView->Invalidate(frame);
1561 }
1562 
1563 
1564 void
1565 TSwitcherWindow::DoKey(uint32 key, uint32 modifiers)
1566 {
1567 	bool forward = ((modifiers & B_SHIFT_KEY) == 0);
1568 
1569 	switch (key) {
1570 		case B_RIGHT_ARROW:
1571 		case '`':
1572 			fManager->CycleApp(true, false);
1573 			break;
1574 
1575 		case B_LEFT_ARROW:
1576 		case '~':
1577 			fManager->CycleApp(false, false);
1578 			break;
1579 
1580 		case B_UP_ARROW:
1581 			fManager->CycleWindow(false, false);
1582 			break;
1583 
1584 		case B_DOWN_ARROW:
1585 			fManager->CycleWindow(true, false);
1586 			break;
1587 
1588 		case B_TAB:
1589 			fManager->CycleApp(forward, false);
1590 			break;
1591 
1592 		case B_ESCAPE:
1593 			fManager->Stop(false, 0);
1594 			break;
1595 
1596 		case B_SPACE:
1597 		case B_ENTER:
1598 			fManager->Stop(true, modifiers);
1599 			break;
1600 
1601 		case 'q':
1602 		case 'Q':
1603 			fManager->QuitApp();
1604 			break;
1605 
1606 		case 'h':
1607 		case 'H':
1608 			fManager->HideApp();
1609 			break;
1610 
1611 #if _ALLOW_STICKY_
1612 		case 's':
1613 		case 'S':
1614 			if (fHairTrigger) {
1615 				SetLook(B_TITLED_WINDOW_LOOK);
1616 				fHairTrigger = false;
1617 			} else {
1618 				SetLook(B_MODAL_WINDOW_LOOK);
1619 				fHairTrigger = true;
1620 			}
1621 			break;
1622 #endif
1623 	}
1624 }
1625 
1626 
1627 bool
1628 TSwitcherWindow::QuitRequested()
1629 {
1630 	((TBarApp *) be_app)->Settings()->switcherLoc = Frame().LeftTop();
1631 	fManager->Stop(false, 0);
1632 	return false;
1633 }
1634 
1635 
1636 void
1637 TSwitcherWindow::WindowActivated(bool state)
1638 {
1639 	if (state)
1640 		fManager->Unblock();
1641 }
1642 
1643 
1644 void
1645 TSwitcherWindow::Update(int32 prev, int32 current, int32 previousSlot,
1646 	int32 currentSlot, bool forward)
1647 {
1648 	if (!IsHidden())
1649 		fIconView->Update(prev, current, previousSlot, currentSlot, forward);
1650 	else
1651 		fIconView->CenterOn(current);
1652 
1653 	fWindowView->UpdateGroup(current, 0);
1654 }
1655 
1656 
1657 void
1658 TSwitcherWindow::Hide()
1659 {
1660 	fIconView->Hiding();
1661 	SetPulseRate(0);
1662 	BWindow::Hide();
1663 }
1664 
1665 
1666 void
1667 TSwitcherWindow::Show()
1668 {
1669 	fHairTrigger = true;
1670 	fSkipKeyRepeats = true;
1671 	fIconView->Showing();
1672 	SetPulseRate(100000);
1673 	SetLook(B_MODAL_WINDOW_LOOK);
1674 	BWindow::Show();
1675 }
1676 
1677 
1678 TBox *
1679 TSwitcherWindow::TopView()
1680 {
1681 	return fTopView;
1682 }
1683 
1684 
1685 bool
1686 TSwitcherWindow::HairTrigger()
1687 {
1688 	return fHairTrigger;
1689 }
1690 
1691 
1692 inline int32
1693 TSwitcherWindow::SlotOf(int32 i)
1694 {
1695 	return fIconView->SlotOf(i);
1696 }
1697 
1698 
1699 inline TIconView *
1700 TSwitcherWindow::IconView()
1701 {
1702 	return fIconView;
1703 }
1704 
1705 
1706 inline TWindowView *
1707 TSwitcherWindow::WindowView()
1708 {
1709 	return fWindowView;
1710 }
1711 
1712 
1713 //	#pragma mark -
1714 
1715 
1716 TIconView::TIconView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcherWindow)
1717 	: BView(frame, "main_view", B_FOLLOW_NONE,
1718 			B_WILL_DRAW | B_PULSE_NEEDED),
1719 	fAutoScrolling(false),
1720 	fSwitcher(switcherWindow),
1721 	fManager(manager)
1722 {
1723 	BRect rect(0, 0, kSlotSize - 1, kSlotSize - 1);
1724 	rgb_color color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT);
1725 
1726 	fOffView = new BView(rect, "off_view", B_FOLLOW_NONE, B_WILL_DRAW);
1727 	fOffView->SetHighColor(color);
1728 	fOffBitmap = new BBitmap(rect, B_RGB32, true);
1729 	fOffBitmap->AddChild(fOffView);
1730 
1731 	fCurrentSmall = new BBitmap(BRect(0, 0, 15, 15), kIconFormat);
1732 	fCurrentLarge = new BBitmap(BRect(0, 0, 31, 31), kIconFormat);
1733 
1734 	SetViewColor(color);
1735 	SetLowColor(color);
1736 }
1737 
1738 
1739 TIconView::~TIconView()
1740 {
1741 	delete fCurrentSmall;
1742 	delete fCurrentLarge;
1743 	delete fOffBitmap;
1744 }
1745 
1746 
1747 void
1748 TIconView::KeyDown(const char *, int32)
1749 {
1750 }
1751 
1752 
1753 void
1754 TIconView::CacheIcons(TTeamGroup *teamGroup)
1755 {
1756 	const BBitmap *bitmap = teamGroup->SmallIcon();
1757 	ASSERT(bitmap);
1758 	fCurrentSmall->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0,
1759 		bitmap->ColorSpace());
1760 
1761 	bitmap = teamGroup->LargeIcon();
1762 	ASSERT(bitmap);
1763 	fCurrentLarge->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0,
1764 		bitmap->ColorSpace());
1765 }
1766 
1767 
1768 void
1769 TIconView::AnimateIcon(BBitmap *startIcon, BBitmap *endIcon)
1770 {
1771 	BRect centerRect(kCenterSlot*kSlotSize, 0,
1772 		(kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1);
1773 	BRect startIconBounds = startIcon->Bounds();
1774 	BRect bounds = Bounds();
1775 	float width = startIconBounds.Width();
1776 	int32 amount = (width < 20) ? -2 : 2;
1777 
1778 	// center the starting icon inside of centerRect
1779 	float off = (centerRect.Width() - width) / 2;
1780 	startIconBounds.OffsetTo(BPoint(off, off));
1781 
1782 	// scroll the centerRect to correct location
1783 	centerRect.OffsetBy(bounds.left, 0);
1784 
1785 	BRect destRect = fOffBitmap->Bounds();
1786 	// scroll to the centerRect location
1787 	destRect.OffsetTo(centerRect.left, 0);
1788 	// center the destRect inside of centerRect.
1789 	off = (centerRect.Width() - destRect.Width()) / 2;
1790 	destRect.OffsetBy(BPoint(off, off));
1791 
1792 	fOffBitmap->Lock();
1793 	fOffView->SetDrawingMode(B_OP_ALPHA);
1794 	for (int i = 0; i < 2; i++) {
1795 		startIconBounds.InsetBy(amount,amount);
1796 		snooze(20000);
1797 		fOffView->FillRect(fOffView->Bounds());
1798 		fOffView->DrawBitmap(startIcon, startIconBounds);
1799 		fOffView->Sync();
1800 		DrawBitmap(fOffBitmap, destRect);
1801 	}
1802 	for (int i = 0; i < 2; i++) {
1803 		startIconBounds.InsetBy(amount,amount);
1804 		snooze(20000);
1805 		fOffView->FillRect(fOffView->Bounds());
1806 		fOffView->DrawBitmap(endIcon, startIconBounds);
1807 		fOffView->Sync();
1808 		DrawBitmap(fOffBitmap, destRect);
1809 	}
1810 
1811 	fOffView->SetDrawingMode(B_OP_COPY);
1812 	fOffBitmap->Unlock();
1813 }
1814 
1815 
1816 void
1817 TIconView::Update(int32, int32 current, int32 previousSlot, int32 currentSlot,
1818 	bool forward)
1819 {
1820 	// Animate the shrinking of the currently centered icon.
1821 	AnimateIcon(fCurrentLarge, fCurrentSmall);
1822 
1823 	int32 nslots = abs(previousSlot - currentSlot);
1824 	int32 stepSize = kScrollStep;
1825 
1826 	if (forward && (currentSlot < previousSlot)) {
1827 		// we were at the end of the list and we just moved to the start
1828 		forward = false;
1829 		if (previousSlot - currentSlot > 4)
1830 			stepSize *= 2;
1831 	} else if (!forward && (currentSlot > previousSlot)) {
1832 		// we're are moving backwards and we just hit start of list and
1833 		// we wrapped to the end.
1834 		forward = true;
1835 		if (currentSlot - previousSlot > 4)
1836 			stepSize *= 2;
1837 	}
1838 
1839 	int32 scrollValue = forward ? stepSize : -stepSize;
1840 	int32 total = 0;
1841 
1842 	fAutoScrolling = true;
1843 	while (total < (nslots * kSlotSize)) {
1844 		ScrollBy(scrollValue, 0);
1845 		snooze(1000);
1846 		total += stepSize;
1847 		Window()->UpdateIfNeeded();
1848 	}
1849 	fAutoScrolling = false;
1850 
1851 	TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(current);
1852 	ASSERT(teamGroup);
1853 	CacheIcons(teamGroup);
1854 
1855 	// Animate the expansion of the currently centered icon
1856 	AnimateIcon(fCurrentSmall, fCurrentLarge);
1857 }
1858 
1859 
1860 void
1861 TIconView::CenterOn(int32 index)
1862 {
1863 	BRect rect = FrameOf(index);
1864 	ScrollTo(rect.left - (kCenterSlot * kSlotSize), 0);
1865 }
1866 
1867 
1868 int32
1869 TIconView::ItemAtPoint(BPoint point) const
1870 {
1871 	float tmpPointVerticalIndex = (point.x / kSlotSize) - kCenterSlot;
1872 	if (tmpPointVerticalIndex < 0)
1873 		return -1;
1874 
1875 	int32 pointVerticalIndex = (int32)tmpPointVerticalIndex;
1876 
1877 	for (int32 i = 0, verticalIndex = 0; ; i++) {
1878 
1879 		TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(i);
1880 		if (teamGroup == NULL)
1881 			break;
1882 
1883 		if (!OKToUse(teamGroup))
1884 			continue;
1885 
1886 		if (verticalIndex == pointVerticalIndex)
1887 			return i;
1888 
1889 		verticalIndex++;
1890 	}
1891 	return -1;
1892 }
1893 
1894 
1895 void
1896 TIconView::ScrollTo(BPoint where)
1897 {
1898 	BView::ScrollTo(where);
1899 	fSwitcher->TopView()->DrawIconScrollers(true);
1900 }
1901 
1902 
1903 int32
1904 TIconView::IndexAt(int32 slot) const
1905 {
1906 	BList *list = fManager->GroupList();
1907 	int32 count = list->CountItems();
1908 	int32 slotIndex = 0;
1909 
1910 	for (int32 i = 0; i < count; i++) {
1911 		TTeamGroup *teamGroup = (TTeamGroup *)list->ItemAt(i);
1912 
1913 		if (!OKToUse(teamGroup))
1914 			continue;
1915 
1916 		if (slotIndex == slot) {
1917 			return i;
1918 		}
1919 		slotIndex++;
1920 	}
1921 	return -1;
1922 }
1923 
1924 
1925 int32
1926 TIconView::SlotOf(int32 index) const
1927 {
1928 	BRect rect = FrameOf(index);
1929 	return (int32)(rect.left / kSlotSize) - kCenterSlot;
1930 }
1931 
1932 
1933 BRect
1934 TIconView::FrameOf(int32 index) const
1935 {
1936 	BList *list = fManager->GroupList();
1937 	int32 visible = kCenterSlot - 1;
1938 		// first few slots in view are empty
1939 
1940 	TTeamGroup *teamGroup;
1941 	for (int32 i = 0; i <= index; i++) {
1942 		teamGroup = (TTeamGroup *)list->ItemAt(i);
1943 
1944 		if (!OKToUse(teamGroup))
1945 			continue;
1946 
1947 		visible++;
1948 	}
1949 
1950 	return BRect(visible * kSlotSize, 0, (visible + 1) * kSlotSize - 1, kSlotSize - 1);
1951 }
1952 
1953 
1954 void
1955 TIconView::DrawTeams(BRect update)
1956 {
1957 	int32 mainIndex = fManager->CurrentIndex();
1958 	BList *list = fManager->GroupList();
1959 	int32 count = list->CountItems();
1960 
1961 	BRect rect(kCenterSlot * kSlotSize, 0,
1962 		(kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1);
1963 
1964 	for (int32 i = 0; i < count; i++) {
1965 		TTeamGroup *teamGroup = (TTeamGroup *) list->ItemAt(i);
1966 
1967 		if (!OKToUse(teamGroup))
1968 			continue;
1969 
1970 		if (rect.Intersects(update) && teamGroup) {
1971 #ifdef __HAIKU__
1972 			SetDrawingMode(B_OP_ALPHA);
1973 			SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY);
1974 #else
1975 			SetDrawingMode(B_OP_OVER);
1976 #endif
1977 			teamGroup->Draw(this, rect, !fAutoScrolling && (i == mainIndex));
1978 
1979 			if (i == mainIndex)
1980 				CacheIcons(teamGroup);
1981 
1982 			SetDrawingMode(B_OP_COPY);
1983 		}
1984 		rect.OffsetBy(kSlotSize,0);
1985 	}
1986 }
1987 
1988 
1989 void
1990 TIconView::Draw(BRect update)
1991 {
1992 	DrawTeams(update);
1993 }
1994 
1995 
1996 void
1997 TIconView::MouseDown(BPoint where)
1998 {
1999 	int32 index = ItemAtPoint(where);
2000 	if (index >= 0) {
2001 		int32 previousIndex = fManager->CurrentIndex();
2002 		int32 previousSlot = fManager->CurrentSlot();
2003 		int32 currentSlot = SlotOf(index);
2004 		fManager->SwitchToApp(previousIndex, index, (currentSlot > previousSlot));
2005 	}
2006 }
2007 
2008 
2009 void
2010 TIconView::Pulse()
2011 {
2012 	uint32 modifiersKeys = modifiers();
2013 	if (fSwitcher->HairTrigger() && (modifiersKeys & B_CONTROL_KEY) == 0) {
2014 		fManager->Stop(true, modifiersKeys);
2015 		return;
2016 	}
2017 
2018 	if (!fSwitcher->HairTrigger()) {
2019 		uint32 buttons;
2020 		BPoint point;
2021 		GetMouse(&point, &buttons);
2022 		if (buttons != 0) {
2023 			point = ConvertToScreen(point);
2024 			if (!Window()->Frame().Contains(point))
2025 				fManager->Stop(false, 0);
2026 		}
2027 	}
2028 }
2029 
2030 
2031 void
2032 TIconView::Showing()
2033 {
2034 }
2035 
2036 
2037 void
2038 TIconView::Hiding()
2039 {
2040 	ScrollTo(B_ORIGIN);
2041 }
2042 
2043 
2044 //	#pragma mark -
2045 
2046 
2047 TWindowView::TWindowView(BRect rect, TSwitchManager *manager, TSwitcherWindow *window)
2048 	: BView(rect, "wlist_view", B_FOLLOW_NONE, B_WILL_DRAW | B_PULSE_NEEDED),
2049 	fCurrentToken(-1),
2050 	fSwitcher(window),
2051 	fManager(manager)
2052 {
2053 	SetFont(be_plain_font);
2054 }
2055 
2056 
2057 void
2058 TWindowView::AttachedToWindow()
2059 {
2060 	if (Parent())
2061 		SetViewColor(Parent()->ViewColor());
2062 	else
2063 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
2064 }
2065 
2066 
2067 void
2068 TWindowView::ScrollTo(BPoint where)
2069 {
2070 	BView::ScrollTo(where);
2071 	fSwitcher->TopView()->DrawWindowScrollers(true);
2072 }
2073 
2074 
2075 
2076 BRect
2077 TWindowView::FrameOf(int32 index) const
2078 {
2079 	return BRect(0, index * fItemHeight, 100, ((index + 1) * fItemHeight) - 1);
2080 }
2081 
2082 
2083 const int32 kWindowScrollSteps = 3;
2084 
2085 void
2086 TWindowView::GetPreferredSize(float *_width, float *_height)
2087 {
2088 	font_height	fh;
2089 	be_plain_font->GetHeight(&fh);
2090 	fItemHeight = (int32) fh.ascent + fh.descent;
2091 
2092 	// top & bottom margin
2093 	fItemHeight = fItemHeight + 3 + 3;
2094 
2095 	// want fItemHeight to be divisible by kWindowScrollSteps.
2096 	fItemHeight = ((((int) fItemHeight) + kWindowScrollSteps) / kWindowScrollSteps)
2097 		* kWindowScrollSteps;
2098 
2099 	*_height = fItemHeight;
2100 
2101 	// leave width alone
2102 	*_width = Bounds().Width();
2103 }
2104 
2105 
2106 void
2107 TWindowView::ShowIndex(int32 newIndex)
2108 {
2109 	// convert index to scroll location
2110 	BPoint point(0, newIndex * fItemHeight);
2111 	BRect bounds = Bounds();
2112 
2113 	int32 groupIndex = fManager->CurrentIndex();
2114 	TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(groupIndex);
2115 	if (!teamGroup)
2116 		return;
2117 
2118 	window_info *windowInfo = fManager->WindowInfo(groupIndex, newIndex);
2119 	if (windowInfo == NULL)
2120 		return;
2121 
2122 	fCurrentToken = windowInfo->id;
2123 	free(windowInfo);
2124 
2125 	if (bounds.top == point.y)
2126 		return;
2127 
2128 	int32 oldIndex = (int32) (bounds.top / fItemHeight);
2129 
2130 	int32 stepSize = (int32) (fItemHeight / kWindowScrollSteps);
2131 	int32 scrollValue = (newIndex > oldIndex) ? stepSize : -stepSize;
2132 	int32 total = 0;
2133 	int32 nslots = abs(newIndex - oldIndex);
2134 
2135 	while (total < (nslots * (int32)fItemHeight)) {
2136 		ScrollBy(0, scrollValue);
2137 		snooze(10000);
2138 		total += stepSize;
2139 		Window()->UpdateIfNeeded();
2140 	}
2141 }
2142 
2143 
2144 void
2145 TWindowView::Draw(BRect update)
2146 {
2147 	int32 groupIndex = fManager->CurrentIndex();
2148 	TTeamGroup *teamGroup = (TTeamGroup *) fManager->GroupList()->ItemAt(groupIndex);
2149 	if (!teamGroup)
2150 		return;
2151 
2152 	BRect bounds = Bounds();
2153 	int32 windowIndex = (int32) (bounds.top / fItemHeight);
2154 	BRect windowRect = bounds;
2155 
2156 	windowRect.top = windowIndex * fItemHeight;
2157 	windowRect.bottom = ((windowIndex+1) * fItemHeight) - 1;
2158 
2159 	for (int32 i = 0; i < 3; i++) {
2160 		if (!update.Intersects(windowRect)) {
2161 			windowIndex++;
2162 			windowRect.OffsetBy(0, fItemHeight);
2163 			continue;
2164 		}
2165 
2166 		// is window in current workspace?
2167 
2168 		bool local = true;
2169 		bool minimized = false;
2170 		BString title;
2171 
2172 		window_info	*windowInfo = fManager->WindowInfo(groupIndex, windowIndex);
2173 		if (windowInfo != NULL) {
2174 			if (SmartStrcmp(windowInfo->name, teamGroup->Name()) != 0)
2175 				title << teamGroup->Name() << ": " << windowInfo->name;
2176 			else
2177 				title = teamGroup->Name();
2178 
2179 			int32 currentWorkspace = current_workspace();
2180 			if ((windowInfo->workspaces & (1 << currentWorkspace)) == 0)
2181 				local = false;
2182 
2183 			minimized = windowInfo->is_mini;
2184 			free(windowInfo);
2185 		} else
2186 			title = teamGroup->Name();
2187 
2188 		if (!title.Length())
2189 			return;
2190 
2191 		float stringWidth = StringWidth(title.String());
2192 		float maxWidth = bounds.Width() - (14 + 5);
2193 
2194 		if (stringWidth > maxWidth) {
2195 			// window title is too long, need to truncate
2196 			TruncateString(&title, B_TRUNCATE_END, maxWidth);
2197 			stringWidth = maxWidth;
2198 		}
2199 
2200 		BPoint point((bounds.Width() - (stringWidth + 14 + 5)) / 2, windowRect.bottom - 4);
2201 		BPoint p(point.x, (windowRect.top + windowRect.bottom) / 2);
2202 		SetDrawingMode(B_OP_OVER);
2203 		const BBitmap *bitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE,
2204 			minimized ? R_WindowHiddenIcon : R_WindowShownIcon);
2205 		p.y -= (bitmap->Bounds().bottom - bitmap->Bounds().top) / 2;
2206 		DrawBitmap(bitmap, p);
2207 
2208 		if (!local) {
2209 			SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT));
2210 			p.x -= 8;
2211 			p.y += 4;
2212 			StrokeLine(p + BPoint(2, 2), p + BPoint(2, 2));
2213 			StrokeLine(p + BPoint(4, 2), p + BPoint(6, 2));
2214 
2215 			StrokeLine(p + BPoint(0, 5), p + BPoint(0, 5));
2216 			StrokeLine(p + BPoint(2, 5), p + BPoint(6, 5));
2217 
2218 			StrokeLine(p + BPoint(1, 8), p + BPoint(1, 8));
2219 			StrokeLine(p + BPoint(3, 8), p + BPoint(6, 8));
2220 
2221 			SetHighColor(0, 0, 0);
2222 		}
2223 
2224 		point.x += 21;
2225 		MovePenTo(point);
2226 
2227 		DrawString(title.String());
2228 		SetDrawingMode(B_OP_COPY);
2229 
2230 		windowIndex++;
2231 		windowRect.OffsetBy(0, fItemHeight);
2232 	}
2233 }
2234 
2235 
2236 void
2237 TWindowView::UpdateGroup(int32 , int32 windowIndex)
2238 {
2239 	ScrollTo(0, windowIndex * fItemHeight);
2240 	Invalidate(Bounds());
2241 }
2242 
2243 
2244 void
2245 TWindowView::Pulse()
2246 {
2247 	// If selected window went away then reset to first window
2248 	window_info	*windowInfo = get_window_info(fCurrentToken);
2249 	if (windowInfo == NULL) {
2250 		Invalidate();
2251 		ShowIndex(0);
2252 	} else
2253 		free(windowInfo);
2254 }
2255 
2256