xref: /haiku/src/apps/deskbar/Switcher.cpp (revision 1acbe440b8dd798953bec31d18ee589aa3f71b73)
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 window_info *
988 TSwitchManager::WindowInfo(int32 groupIndex, int32 windowIndex)
989 {
990 	TTeamGroup *teamGroup = (TTeamGroup *) fGroupList.ItemAt(groupIndex);
991 	if (!teamGroup)
992 		return NULL;
993 
994 	int32 tokenCount;
995 	int32 *tokens = get_token_list(-1, &tokenCount);
996 	if (!tokens)
997 		return NULL;
998 
999 	int32 matches = 0;
1000 
1001 	// Want to find the "windowIndex'th" window in window order that belongs
1002 	// the the specified group (groupIndex). Since multiple teams can belong to
1003 	// the same group (multiple-launch apps) we get the list of _every_
1004 	// window and go from there.
1005 
1006 	window_info	*result = NULL;
1007 	for (int32 i = 0; i < tokenCount; i++) {
1008 		window_info	*windowInfo = get_window_info(tokens[i]);
1009 		if (windowInfo) {
1010 			// skip hidden/special windows
1011 			if (IsWindowOK(windowInfo)
1012 				&& (teamGroup->TeamList()->HasItem((void *)windowInfo->team))) {
1013 				// this window belongs to the team!
1014 				if (matches == windowIndex) {
1015 					// we found it!
1016 					result = windowInfo;
1017 					break;
1018 				}
1019 				matches++;
1020 			}
1021 			free(windowInfo);
1022 		}
1023 		// else - that window probably closed. Just go to the next one.
1024 	}
1025 
1026 	free(tokens);
1027 
1028 	return result;
1029 }
1030 
1031 
1032 int32
1033 TSwitchManager::CountWindows(int32 groupIndex, bool )
1034 {
1035 	TTeamGroup *teamGroup = (TTeamGroup *)fGroupList.ItemAt(groupIndex);
1036 	if (!teamGroup)
1037 		return 0;
1038 
1039 	int32 result = 0;
1040 
1041 	for (int32 i = 0; ; i++) {
1042 		team_id	teamID = (team_id)teamGroup->TeamList()->ItemAt(i);
1043 		if (teamID == 0)
1044 			break;
1045 
1046 		int32 count;
1047 		int32 *tokens = get_token_list(teamID, &count);
1048 		if (!tokens)
1049 			continue;
1050 
1051 		for (int32 i = 0; i < count; i++) {
1052 			window_info	*windowInfo = get_window_info(tokens[i]);
1053 			if (windowInfo) {
1054 				if (IsWindowOK(windowInfo))
1055 					result++;
1056 				free(windowInfo);
1057 			}
1058 		}
1059 		free(tokens);
1060 	}
1061 
1062 	return result;
1063 }
1064 
1065 
1066 void
1067 TSwitchManager::ActivateWindow(int32 windowID)
1068 {
1069 	if (windowID == -1)
1070 		windowID = fWindowID;
1071 
1072 	do_window_action(windowID, B_BRING_TO_FRONT, BRect(0, 0, 0, 0), false);
1073 }
1074 
1075 
1076 void
1077 TSwitchManager::SwitchWindow(team_id team, bool, bool activate)
1078 {
1079 	// Find the _last_ window in the current workspace that belongs
1080 	// to the group. This is the window to activate.
1081 
1082 	int32 index;
1083 	TTeamGroup*teamGroup = FindTeam(team, &index);
1084 
1085 	// cycle through the windows in the active application
1086 	int32 count;
1087 	int32 *tokens = get_token_list(-1, &count);
1088 	for (int32 i = count-1; i >= 0; i--) {
1089 		window_info	*windowInfo;
1090 		windowInfo = get_window_info(tokens[i]);
1091 		if (windowInfo && IsVisibleInCurrentWorkspace(windowInfo)
1092 			&& teamGroup->TeamList()->HasItem((void *)windowInfo->team)) {
1093 				fWindowID = windowInfo->id;
1094 				if (activate)
1095 					ActivateWindow(windowInfo->id);
1096 
1097 				free(windowInfo);
1098 				break;
1099 		}
1100 		free(windowInfo);
1101 	}
1102 	free(tokens);
1103 }
1104 
1105 
1106 void
1107 TSwitchManager::Unblock()
1108 {
1109 	fBlock = false;
1110 	fSkipUntil = system_time();
1111 }
1112 
1113 
1114 int32
1115 TSwitchManager::CurrentIndex()
1116 {
1117 	return fCurrentIndex;
1118 }
1119 
1120 
1121 int32
1122 TSwitchManager::CurrentWindow()
1123 {
1124 	return fCurrentWindow;
1125 }
1126 
1127 
1128 int32
1129 TSwitchManager::CurrentSlot()
1130 {
1131 	return fCurrentSlot;
1132 }
1133 
1134 
1135 BList *
1136 TSwitchManager::GroupList()
1137 {
1138 	return &fGroupList;
1139 }
1140 
1141 
1142 //	#pragma mark -
1143 
1144 
1145 TBox::TBox(BRect bounds, TSwitchManager *manager, TSwitcherWindow *window,
1146 	TIconView *iview)
1147 	: BBox(bounds, "top", B_FOLLOW_NONE, B_WILL_DRAW, B_NO_BORDER),
1148 	fManager(manager),
1149 	fWindow(window),
1150 	fIconView(iview),
1151 	fLeftScroller(false),
1152 	fRightScroller(false)
1153 {
1154 }
1155 
1156 
1157 void
1158 TBox::AllAttached()
1159 {
1160 	BRect centerRect(kCenterSlot * kSlotSize, 0,
1161 		(kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1);
1162 	BRect frame = fIconView->Frame();
1163 
1164 	// scroll the centerRect to correct location
1165 	centerRect.OffsetBy(frame.left, frame.top);
1166 
1167 	// switch to local coords
1168 	fIconView->ConvertToParent(&centerRect);
1169 
1170 	fCenter = centerRect;
1171 }
1172 
1173 
1174 void
1175 TBox::MouseDown(BPoint where)
1176 {
1177 	if (!fLeftScroller && !fRightScroller && !fUpScroller && !fDownScroller)
1178 		return;
1179 
1180 	BRect frame = fIconView->Frame();
1181 	BRect bounds = Bounds();
1182 
1183 	if (fLeftScroller) {
1184 		BRect lhit(0, frame.top, frame.left, frame.bottom);
1185 		if (lhit.Contains(where)) {
1186 			// Want to scroll by NUMSLOTS-1 slots
1187 			int32 previousIndex = fManager->CurrentIndex();
1188 			int32 previousSlot = fManager->CurrentSlot();
1189 			int32 newSlot = previousSlot - (kNumSlots - 1);
1190 			if (newSlot < 0)
1191 				newSlot = 0;
1192 			int32 newIndex = fIconView->IndexAt(newSlot);
1193 
1194 			fManager->SwitchToApp(previousIndex, newIndex, false);
1195 		}
1196 	}
1197 
1198 	if (fRightScroller) {
1199 		BRect rhit(frame.right, frame.top, bounds.right, frame.bottom);
1200 		if (rhit.Contains(where)) {
1201 			// Want to scroll by NUMSLOTS-1 slots
1202 			int32 previousIndex = fManager->CurrentIndex();
1203 			int32 previousSlot = fManager->CurrentSlot();
1204 			int32 newSlot = previousSlot + (kNumSlots-1);
1205 			int32 newIndex = fIconView->IndexAt(newSlot);
1206 
1207 			if (newIndex < 0) {
1208 				// don't have a page full to scroll
1209 				int32 valid = fManager->CountVisibleGroups();
1210 				newIndex = fIconView->IndexAt(valid-1);
1211 			}
1212 			fManager->SwitchToApp(previousIndex, newIndex, true);
1213 		}
1214 	}
1215 
1216 	frame = fWindow->WindowView()->Frame();
1217 	if (fUpScroller) {
1218 		BRect hit1(frame.left - 10, frame.top, frame.left, (frame.top+frame.bottom)/2);
1219 		BRect hit2(frame.right, frame.top, frame.right + 10, (frame.top+frame.bottom)/2);
1220 		if (hit1.Contains(where) || hit2.Contains(where)) {
1221 			// Want to scroll up 1 window
1222 			fManager->CycleWindow(false, false);
1223 		}
1224 	}
1225 
1226 	if (fDownScroller) {
1227 		BRect hit1(frame.left - 10, (frame.top+frame.bottom) / 2, frame.left, frame.bottom);
1228 		BRect hit2(frame.right, (frame.top+frame.bottom) / 2, frame.right + 10, frame.bottom);
1229 		if (hit1.Contains(where) || hit2.Contains(where)) {
1230 			// Want to scroll down 1 window
1231 			fManager->CycleWindow(true, false);
1232 		}
1233 	}
1234 }
1235 
1236 
1237 void
1238 TBox::Draw(BRect update)
1239 {
1240 	static const int32 kChildInset = 7;
1241 	static const int32 kWedge = 6;
1242 
1243 	BBox::Draw(update);
1244 
1245 	// The fancy border around the icon view
1246 
1247 	BRect bounds = Bounds();
1248 	float height = fIconView->Bounds().Height();
1249 	float center = (bounds.right + bounds.left) / 2;
1250 
1251 	BRect box(3, 3, bounds.right - 3, 3 + height + kChildInset * 2);
1252 	rgb_color white = {255,255,255,255};
1253 	rgb_color standardGray = ui_color(B_PANEL_BACKGROUND_COLOR);
1254 	rgb_color veryDarkGray = {128, 128, 128, 255};
1255 	rgb_color darkGray = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT);
1256 
1257 	// Fill the area with dark gray
1258 	SetHighColor(darkGray);
1259 	box.InsetBy(1,1);
1260 	FillRect(box);
1261 
1262 	box.InsetBy(-1,-1);
1263 
1264 	BeginLineArray(50);
1265 
1266 	// The main frame around the icon view
1267 	AddLine(box.LeftTop(), BPoint(center-kWedge, box.top), veryDarkGray);
1268 	AddLine(BPoint(center+kWedge, box.top), box.RightTop(), veryDarkGray);
1269 
1270 	AddLine(box.LeftBottom(), BPoint(center-kWedge, box.bottom), veryDarkGray);
1271 	AddLine(BPoint(center+kWedge, box.bottom), box.RightBottom(), veryDarkGray);
1272 	AddLine(box.LeftBottom() + BPoint(1, 1),
1273 		BPoint(center-kWedge, box.bottom + 1), white);
1274 	AddLine(BPoint(center+kWedge, box.bottom) + BPoint(0, 1),
1275 		box.RightBottom() + BPoint(1, 1), white);
1276 
1277 	AddLine(box.LeftTop(), box.LeftBottom(), veryDarkGray);
1278 	AddLine(box.RightTop(), box.RightBottom(), veryDarkGray);
1279 	AddLine(box.RightTop() + BPoint(1, 1),
1280 		box.RightBottom() + BPoint(1, 1), white);
1281 
1282 	// downward pointing area at top of frame
1283 	BPoint point(center - kWedge, box.top);
1284 	AddLine(point, point + BPoint(kWedge, kWedge), veryDarkGray);
1285 	AddLine(point + BPoint(kWedge, kWedge),
1286 		BPoint(center+kWedge, point.y), veryDarkGray);
1287 
1288 	AddLine(point + BPoint(1, 0),
1289 		point + BPoint(1, 0) + BPoint(kWedge - 1, kWedge - 1), white);
1290 
1291 	AddLine(point + BPoint(2, -1) + BPoint(kWedge - 1, kWedge - 1),
1292 		BPoint(center+kWedge-1, point.y), darkGray);
1293 
1294 	BPoint topPoint = point;
1295 
1296 	// upward pointing area at bottom of frame
1297 	point.y = box.bottom;
1298 	point.x = center - kWedge;
1299 	AddLine(point, point + BPoint(kWedge, -kWedge), veryDarkGray);
1300 	AddLine(point + BPoint(kWedge, -kWedge),
1301 		BPoint(center+kWedge, point.y), veryDarkGray);
1302 
1303 	AddLine(point + BPoint(1, 0),
1304 		point + BPoint(1, 0) + BPoint(kWedge - 1, -(kWedge - 1)), white);
1305 
1306 	AddLine(point + BPoint(2 , 1) + BPoint(kWedge - 1, -(kWedge - 1)),
1307 		BPoint(center + kWedge - 1, point.y), darkGray);
1308 
1309 	BPoint bottomPoint = point;
1310 
1311 	EndLineArray();
1312 
1313 	// fill the downward pointing arrow area
1314 	SetHighColor(standardGray);
1315 	FillTriangle(topPoint + BPoint(2, 0),
1316 		topPoint + BPoint(2, 0) + BPoint(kWedge - 2, kWedge - 2),
1317 		BPoint(center + kWedge - 2, topPoint.y));
1318 
1319 	// fill the upward pointing arrow area
1320 	SetHighColor(standardGray);
1321 	FillTriangle(bottomPoint + BPoint(2,0),
1322 		bottomPoint + BPoint(2, 0) + BPoint(kWedge - 2, -(kWedge - 2)),
1323 		BPoint(center + kWedge - 2, bottomPoint.y));
1324 
1325 	DrawIconScrollers(false);
1326 	DrawWindowScrollers(false);
1327 
1328 }
1329 
1330 
1331 void
1332 TBox::DrawIconScrollers(bool force)
1333 {
1334 	bool updateLeft = false;
1335 	bool updateRight = false;
1336 	rgb_color leftc;
1337 	rgb_color rightc;
1338 	rgb_color bkg = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT);
1339 	rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT);
1340 
1341 	BRect rect = fIconView->Bounds();
1342 	if (rect.left > (kSlotSize * kCenterSlot)) {
1343 		updateLeft = true;
1344 		fLeftScroller = true;
1345 		leftc = dark;
1346 	} else {
1347 		fLeftScroller = false;
1348 		if (force) {
1349 			updateLeft = true;
1350 			leftc = bkg;
1351 		}
1352 	}
1353 
1354 	int32 maxIndex = fManager->GroupList()->CountItems() - 1;
1355 			// last_frame is in fIconView coordinate space
1356 	BRect lastFrame = fIconView->FrameOf(maxIndex);
1357 
1358 	if (lastFrame.right > rect.right) {
1359 		updateRight = true;
1360 		fRightScroller = true;
1361 		rightc = dark;
1362 	} else {
1363 		fRightScroller = false;
1364 		if (force) {
1365 			updateRight = true;
1366 			rightc = bkg;
1367 		}
1368 	}
1369 
1370 	rect = fIconView->Frame();
1371 	if (updateLeft) {
1372 		SetHighColor(leftc);
1373 		BPoint	pt1, pt2, pt3;
1374 		pt1.x = rect.left - 5;
1375 		pt1.y = floorf((rect.bottom + rect.top) / 2);
1376 		pt2.x = pt3.x = pt1.x + 3;
1377 		pt2.y = pt1.y - 3;
1378 		pt3.y = pt1.y + 3;
1379 		FillTriangle(pt1, pt2, pt3);
1380 	}
1381 	if (updateRight) {
1382 		SetHighColor(rightc);
1383 		BPoint	pt1, pt2, pt3;
1384 		pt1.x = rect.right + 4;
1385 		pt1.y = rintf((rect.bottom + rect.top) / 2);
1386 		pt2.x = pt3.x = pt1.x - 4;
1387 		pt2.y = pt1.y - 4;
1388 		pt3.y = pt1.y + 4;
1389 		FillTriangle(pt1, pt2, pt3);
1390 	}
1391 }
1392 
1393 
1394 void
1395 TBox::DrawWindowScrollers(bool force)
1396 {
1397 	bool updateUp = false;
1398 	bool updateDown = false;
1399 	rgb_color upColor;
1400 	rgb_color downColor;
1401 	rgb_color bkg = ui_color(B_PANEL_BACKGROUND_COLOR);
1402 	rgb_color dark = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT);
1403 
1404 	BRect rect = fWindow->WindowView()->Bounds();
1405 	if (rect.top != 0) {
1406 		updateUp = true;
1407 		fUpScroller = true;
1408 		upColor = dark;
1409 	} else {
1410 		fUpScroller = false;
1411 		if (force) {
1412 			updateUp = true;
1413 			upColor = bkg;
1414 		}
1415 	}
1416 
1417 	int32 groupIndex = fManager->CurrentIndex();
1418 	int32 maxIndex = fManager->CountWindows(groupIndex) - 1;
1419 
1420 	BRect lastFrame(0, 0, 0, 0);
1421 	if (maxIndex >= 0) {
1422 		lastFrame = fWindow->WindowView()->FrameOf(maxIndex);
1423 	}
1424 	if (maxIndex >= 0 && lastFrame.bottom > rect.bottom) {
1425 		updateDown = true;
1426 		fDownScroller = true;
1427 		downColor = dark;
1428 	} else {
1429 		fDownScroller = false;
1430 		if (force) {
1431 			updateDown = true;
1432 			downColor = bkg;
1433 		}
1434 	}
1435 
1436 	rect = fWindow->WindowView()->Frame();
1437 	rect.InsetBy(-3, 0);
1438 	if (updateUp) {
1439 		SetHighColor(upColor);
1440 		BPoint	pt1, pt2, pt3;
1441 		pt1.x = rect.left - 6;
1442 		pt1.y = rect.top + 3;
1443 		pt2.y = pt3.y = pt1.y + 4;
1444 		pt2.x = pt1.x - 4;
1445 		pt3.x = pt1.x + 4;
1446 		FillTriangle(pt1, pt2, pt3);
1447 
1448 		pt1.x += rect.Width() + 12;
1449 		pt2.x += rect.Width() + 12;
1450 		pt3.x += rect.Width() + 12;
1451 		FillTriangle(pt1, pt2, pt3);
1452 	}
1453 	if (updateDown) {
1454 		SetHighColor(downColor);
1455 		BPoint	pt1, pt2, pt3;
1456 		pt1.x = rect.left - 6;
1457 		pt1.y = rect.bottom - 3;
1458 		pt2.y = pt3.y = pt1.y - 4;
1459 		pt2.x = pt1.x - 4;
1460 		pt3.x = pt1.x + 4;
1461 		FillTriangle(pt1, pt2, pt3);
1462 
1463 		pt1.x += rect.Width() + 12;
1464 		pt2.x += rect.Width() + 12;
1465 		pt3.x += rect.Width() + 12;
1466 		FillTriangle(pt1, pt2, pt3);
1467 
1468 	}
1469 	Sync();
1470 }
1471 
1472 
1473 //	#pragma mark -
1474 
1475 
1476 TSwitcherWindow::TSwitcherWindow(BRect frame, TSwitchManager *manager)
1477 	: BWindow(frame, "Twitcher", B_MODAL_WINDOW_LOOK,
1478 			B_MODAL_ALL_WINDOW_FEEL,
1479 			B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_NOT_RESIZABLE, B_ALL_WORKSPACES),
1480 	fManager(manager),
1481 	fHairTrigger(true)
1482 {
1483 	BRect rect = frame;
1484 	rect.OffsetTo(B_ORIGIN);
1485 	rect.InsetBy(kHorizontalMargin, 0);
1486 	rect.top = kVerticalMargin;
1487 	rect.bottom = rect.top + kSlotSize - 1;
1488 
1489 	fIconView = new TIconView(rect, manager, this);
1490 
1491 	rect.top = rect.bottom + (kVerticalMargin * 1 + 4);
1492 	rect.InsetBy(9, 0);
1493 
1494 	fWindowView = new TWindowView(rect, manager, this);
1495 	fWindowView->ResizeToPreferred();
1496 
1497 	fTopView = new TBox(Bounds(), fManager, this, fIconView);
1498 	AddChild(fTopView);
1499 
1500 	SetPulseRate(0);
1501 	fTopView->AddChild(fIconView);
1502 	fTopView->AddChild(fWindowView);
1503 }
1504 
1505 
1506 TSwitcherWindow::~TSwitcherWindow()
1507 {
1508 }
1509 
1510 
1511 void
1512 TSwitcherWindow::MessageReceived(BMessage *message)
1513 {
1514 	switch (message->what) {
1515 		case B_KEY_DOWN:
1516 		{
1517 			int32 repeats = 0;
1518 			if (message->FindInt32("be:key_repeat", &repeats) == B_OK
1519 				&& (fSkipKeyRepeats || (repeats % 6) != 0))
1520 				break;
1521 
1522 			// The first actual key press let's us listening to repeated keys
1523 			fSkipKeyRepeats = false;
1524 
1525 			uint32 rawChar;
1526 			uint32 modifiers;
1527 			message->FindInt32("raw_char", 0, (int32 *)&rawChar);
1528 			message->FindInt32("modifiers", 0, (int32 *)&modifiers);
1529 			DoKey(rawChar, modifiers);
1530 			break;
1531 		}
1532 
1533 		default:
1534 			BWindow::MessageReceived(message);
1535 	}
1536 }
1537 
1538 
1539 void
1540 TSwitcherWindow::Redraw(int32 index)
1541 {
1542 	BRect frame = fIconView->FrameOf(index);
1543 	frame.right = fIconView->Bounds().right;
1544 	fIconView->Invalidate(frame);
1545 }
1546 
1547 
1548 void
1549 TSwitcherWindow::DoKey(uint32 key, uint32 modifiers)
1550 {
1551 	bool forward = ((modifiers & B_SHIFT_KEY) == 0);
1552 
1553 	switch (key) {
1554 		case B_RIGHT_ARROW:
1555 		case '`':
1556 			fManager->CycleApp(true, false);
1557 			break;
1558 
1559 		case B_LEFT_ARROW:
1560 		case '~':
1561 			fManager->CycleApp(false, false);
1562 			break;
1563 
1564 		case B_UP_ARROW:
1565 			fManager->CycleWindow(false, false);
1566 			break;
1567 
1568 		case B_DOWN_ARROW:
1569 			fManager->CycleWindow(true, false);
1570 			break;
1571 
1572 		case B_TAB:
1573 			fManager->CycleApp(forward, false);
1574 			break;
1575 
1576 		case B_ESCAPE:
1577 			fManager->Stop(false, 0);
1578 			break;
1579 
1580 		case B_SPACE:
1581 		case B_ENTER:
1582 			fManager->Stop(true, modifiers);
1583 			break;
1584 
1585 		case 'q':
1586 		case 'Q':
1587 			fManager->QuitApp();
1588 			break;
1589 
1590 #if _ALLOW_STICKY_
1591 		case 's':
1592 		case 'S':
1593 			if (fHairTrigger) {
1594 				SetLook(B_TITLED_WINDOW_LOOK);
1595 				fHairTrigger = false;
1596 			} else {
1597 				SetLook(B_MODAL_WINDOW_LOOK);
1598 				fHairTrigger = true;
1599 			}
1600 			break;
1601 #endif
1602 	}
1603 }
1604 
1605 
1606 bool
1607 TSwitcherWindow::QuitRequested()
1608 {
1609 	((TBarApp *) be_app)->Settings()->switcherLoc = Frame().LeftTop();
1610 	fManager->Stop(false, 0);
1611 	return false;
1612 }
1613 
1614 
1615 void
1616 TSwitcherWindow::WindowActivated(bool state)
1617 {
1618 	if (state)
1619 		fManager->Unblock();
1620 }
1621 
1622 
1623 void
1624 TSwitcherWindow::Update(int32 prev, int32 current, int32 previousSlot,
1625 	int32 currentSlot, bool forward)
1626 {
1627 	if (!IsHidden())
1628 		fIconView->Update(prev, current, previousSlot, currentSlot, forward);
1629 	else
1630 		fIconView->CenterOn(current);
1631 
1632 	fWindowView->UpdateGroup(current, 0);
1633 }
1634 
1635 
1636 void
1637 TSwitcherWindow::Hide()
1638 {
1639 	fIconView->Hiding();
1640 	SetPulseRate(0);
1641 	BWindow::Hide();
1642 }
1643 
1644 
1645 void
1646 TSwitcherWindow::Show()
1647 {
1648 	fHairTrigger = true;
1649 	fSkipKeyRepeats = true;
1650 	fIconView->Showing();
1651 	SetPulseRate(100000);
1652 	SetLook(B_MODAL_WINDOW_LOOK);
1653 	BWindow::Show();
1654 }
1655 
1656 
1657 TBox *
1658 TSwitcherWindow::TopView()
1659 {
1660 	return fTopView;
1661 }
1662 
1663 
1664 bool
1665 TSwitcherWindow::HairTrigger()
1666 {
1667 	return fHairTrigger;
1668 }
1669 
1670 
1671 inline int32
1672 TSwitcherWindow::SlotOf(int32 i)
1673 {
1674 	return fIconView->SlotOf(i);
1675 }
1676 
1677 
1678 inline TIconView *
1679 TSwitcherWindow::IconView()
1680 {
1681 	return fIconView;
1682 }
1683 
1684 
1685 inline TWindowView *
1686 TSwitcherWindow::WindowView()
1687 {
1688 	return fWindowView;
1689 }
1690 
1691 
1692 //	#pragma mark -
1693 
1694 
1695 TIconView::TIconView(BRect frame, TSwitchManager *manager, TSwitcherWindow *switcherWindow)
1696 	: BView(frame, "main_view", B_FOLLOW_NONE,
1697 			B_WILL_DRAW | B_PULSE_NEEDED),
1698 	fAutoScrolling(false),
1699 	fSwitcher(switcherWindow),
1700 	fManager(manager)
1701 {
1702 	BRect rect(0, 0, kSlotSize - 1, kSlotSize - 1);
1703 	rgb_color color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_1_TINT);
1704 
1705 	fOffView = new BView(rect, "off_view", B_FOLLOW_NONE, B_WILL_DRAW);
1706 	fOffView->SetHighColor(color);
1707 	fOffBitmap = new BBitmap(rect, B_RGB32, true);
1708 	fOffBitmap->AddChild(fOffView);
1709 
1710 	fCurrentSmall = new BBitmap(BRect(0, 0, 15, 15), kIconFormat);
1711 	fCurrentLarge = new BBitmap(BRect(0, 0, 31, 31), kIconFormat);
1712 
1713 	SetViewColor(color);
1714 	SetLowColor(color);
1715 }
1716 
1717 
1718 TIconView::~TIconView()
1719 {
1720 	delete fCurrentSmall;
1721 	delete fCurrentLarge;
1722 	delete fOffBitmap;
1723 }
1724 
1725 
1726 void
1727 TIconView::KeyDown(const char *, int32)
1728 {
1729 }
1730 
1731 
1732 void
1733 TIconView::CacheIcons(TTeamGroup *teamGroup)
1734 {
1735 	const BBitmap *bitmap = teamGroup->SmallIcon();
1736 	ASSERT(bitmap);
1737 	fCurrentSmall->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0,
1738 		bitmap->ColorSpace());
1739 
1740 	bitmap = teamGroup->LargeIcon();
1741 	ASSERT(bitmap);
1742 	fCurrentLarge->SetBits(bitmap->Bits(), bitmap->BitsLength(), 0,
1743 		bitmap->ColorSpace());
1744 }
1745 
1746 
1747 void
1748 TIconView::AnimateIcon(BBitmap *startIcon, BBitmap *endIcon)
1749 {
1750 	BRect centerRect(kCenterSlot*kSlotSize, 0,
1751 		(kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1);
1752 	BRect startIconBounds = startIcon->Bounds();
1753 	BRect bounds = Bounds();
1754 	float width = startIconBounds.Width();
1755 	int32 amount = (width < 20) ? -2 : 2;
1756 
1757 	// center the starting icon inside of centerRect
1758 	float off = (centerRect.Width() - width) / 2;
1759 	startIconBounds.OffsetTo(BPoint(off, off));
1760 
1761 	// scroll the centerRect to correct location
1762 	centerRect.OffsetBy(bounds.left, 0);
1763 
1764 	BRect destRect = fOffBitmap->Bounds();
1765 	// scroll to the centerRect location
1766 	destRect.OffsetTo(centerRect.left, 0);
1767 	// center the destRect inside of centerRect.
1768 	off = (centerRect.Width() - destRect.Width()) / 2;
1769 	destRect.OffsetBy(BPoint(off, off));
1770 
1771 	fOffBitmap->Lock();
1772 	fOffView->SetDrawingMode(B_OP_ALPHA);
1773 	for (int i = 0; i < 2; i++) {
1774 		startIconBounds.InsetBy(amount,amount);
1775 		snooze(20000);
1776 		fOffView->FillRect(fOffView->Bounds());
1777 		fOffView->DrawBitmap(startIcon, startIconBounds);
1778 		fOffView->Sync();
1779 		DrawBitmap(fOffBitmap, destRect);
1780 	}
1781 	for (int i = 0; i < 2; i++) {
1782 		startIconBounds.InsetBy(amount,amount);
1783 		snooze(20000);
1784 		fOffView->FillRect(fOffView->Bounds());
1785 		fOffView->DrawBitmap(endIcon, startIconBounds);
1786 		fOffView->Sync();
1787 		DrawBitmap(fOffBitmap, destRect);
1788 	}
1789 
1790 	fOffView->SetDrawingMode(B_OP_COPY);
1791 	fOffBitmap->Unlock();
1792 }
1793 
1794 
1795 void
1796 TIconView::Update(int32, int32 current, int32 previousSlot, int32 currentSlot,
1797 	bool forward)
1798 {
1799 	// Animate the shrinking of the currently centered icon.
1800 	AnimateIcon(fCurrentLarge, fCurrentSmall);
1801 
1802 	int32 nslots = abs(previousSlot - currentSlot);
1803 	int32 stepSize = kScrollStep;
1804 
1805 	if (forward && (currentSlot < previousSlot)) {
1806 		// we were at the end of the list and we just moved to the start
1807 		forward = false;
1808 		if (previousSlot - currentSlot > 4)
1809 			stepSize *= 2;
1810 	} else if (!forward && (currentSlot > previousSlot)) {
1811 		// we're are moving backwards and we just hit start of list and
1812 		// we wrapped to the end.
1813 		forward = true;
1814 		if (currentSlot - previousSlot > 4)
1815 			stepSize *= 2;
1816 	}
1817 
1818 	int32 scrollValue = forward ? stepSize : -stepSize;
1819 	int32 total = 0;
1820 
1821 	fAutoScrolling = true;
1822 	while (total < (nslots * kSlotSize)) {
1823 		ScrollBy(scrollValue, 0);
1824 		snooze(1000);
1825 		total += stepSize;
1826 		Window()->UpdateIfNeeded();
1827 	}
1828 	fAutoScrolling = false;
1829 
1830 	TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(current);
1831 	ASSERT(teamGroup);
1832 	CacheIcons(teamGroup);
1833 
1834 	// Animate the expansion of the currently centered icon
1835 	AnimateIcon(fCurrentSmall, fCurrentLarge);
1836 }
1837 
1838 
1839 void
1840 TIconView::CenterOn(int32 index)
1841 {
1842 	BRect rect = FrameOf(index);
1843 	ScrollTo(rect.left - (kCenterSlot * kSlotSize), 0);
1844 }
1845 
1846 
1847 int32
1848 TIconView::ItemAtPoint(BPoint point) const
1849 {
1850 	float tmpPointVerticalIndex = (point.x / kSlotSize) - kCenterSlot;
1851 	if (tmpPointVerticalIndex < 0)
1852 		return -1;
1853 
1854 	int32 pointVerticalIndex = (int32)tmpPointVerticalIndex;
1855 
1856 	for (int32 i = 0, verticalIndex = 0; ; i++) {
1857 
1858 		TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(i);
1859 		if (teamGroup == NULL)
1860 			break;
1861 
1862 		if (!OKToUse(teamGroup))
1863 			continue;
1864 
1865 		if (verticalIndex == pointVerticalIndex)
1866 			return i;
1867 
1868 		verticalIndex++;
1869 	}
1870 	return -1;
1871 }
1872 
1873 
1874 void
1875 TIconView::ScrollTo(BPoint where)
1876 {
1877 	BView::ScrollTo(where);
1878 	fSwitcher->TopView()->DrawIconScrollers(true);
1879 }
1880 
1881 
1882 int32
1883 TIconView::IndexAt(int32 slot) const
1884 {
1885 	BList *list = fManager->GroupList();
1886 	int32 count = list->CountItems();
1887 	int32 slotIndex = 0;
1888 
1889 	for (int32 i = 0; i < count; i++) {
1890 		TTeamGroup *teamGroup = (TTeamGroup *)list->ItemAt(i);
1891 
1892 		if (!OKToUse(teamGroup))
1893 			continue;
1894 
1895 		if (slotIndex == slot) {
1896 			return i;
1897 		}
1898 		slotIndex++;
1899 	}
1900 	return -1;
1901 }
1902 
1903 
1904 int32
1905 TIconView::SlotOf(int32 index) const
1906 {
1907 	BRect rect = FrameOf(index);
1908 	return (int32)(rect.left / kSlotSize) - kCenterSlot;
1909 }
1910 
1911 
1912 BRect
1913 TIconView::FrameOf(int32 index) const
1914 {
1915 	BList *list = fManager->GroupList();
1916 	int32 visible = kCenterSlot - 1;
1917 		// first few slots in view are empty
1918 
1919 	TTeamGroup *teamGroup;
1920 	for (int32 i = 0; i <= index; i++) {
1921 		teamGroup = (TTeamGroup *)list->ItemAt(i);
1922 
1923 		if (!OKToUse(teamGroup))
1924 			continue;
1925 
1926 		visible++;
1927 	}
1928 
1929 	return BRect(visible * kSlotSize, 0, (visible + 1) * kSlotSize - 1, kSlotSize - 1);
1930 }
1931 
1932 
1933 void
1934 TIconView::DrawTeams(BRect update)
1935 {
1936 	int32 mainIndex = fManager->CurrentIndex();
1937 	BList *list = fManager->GroupList();
1938 	int32 count = list->CountItems();
1939 
1940 	BRect rect(kCenterSlot * kSlotSize, 0,
1941 		(kCenterSlot + 1) * kSlotSize - 1, kSlotSize - 1);
1942 
1943 	for (int32 i = 0; i < count; i++) {
1944 		TTeamGroup *teamGroup = (TTeamGroup *) list->ItemAt(i);
1945 
1946 		if (!OKToUse(teamGroup))
1947 			continue;
1948 
1949 		if (rect.Intersects(update) && teamGroup) {
1950 			SetDrawingMode(B_OP_OVER);
1951 
1952 			teamGroup->Draw(this, rect, !fAutoScrolling && (i == mainIndex));
1953 
1954 			if (i == mainIndex)
1955 				CacheIcons(teamGroup);
1956 
1957 			SetDrawingMode(B_OP_COPY);
1958 		}
1959 		rect.OffsetBy(kSlotSize,0);
1960 	}
1961 }
1962 
1963 
1964 void
1965 TIconView::Draw(BRect update)
1966 {
1967 	DrawTeams(update);
1968 }
1969 
1970 
1971 void
1972 TIconView::MouseDown(BPoint where)
1973 {
1974 	int32 index = ItemAtPoint(where);
1975 	if (index >= 0) {
1976 		int32 previousIndex = fManager->CurrentIndex();
1977 		int32 previousSlot = fManager->CurrentSlot();
1978 		int32 currentSlot = SlotOf(index);
1979 		fManager->SwitchToApp(previousIndex, index, (currentSlot > previousSlot));
1980 	}
1981 }
1982 
1983 
1984 void
1985 TIconView::Pulse()
1986 {
1987 	uint32 modifiersKeys = modifiers();
1988 	if (fSwitcher->HairTrigger() && (modifiersKeys & B_CONTROL_KEY) == 0) {
1989 		fManager->Stop(true, modifiersKeys);
1990 		return;
1991 	}
1992 
1993 	if (!fSwitcher->HairTrigger()) {
1994 		uint32 buttons;
1995 		BPoint point;
1996 		GetMouse(&point, &buttons);
1997 		if (buttons != 0) {
1998 			point = ConvertToScreen(point);
1999 			if (!Window()->Frame().Contains(point))
2000 				fManager->Stop(false, 0);
2001 		}
2002 	}
2003 }
2004 
2005 
2006 void
2007 TIconView::Showing()
2008 {
2009 }
2010 
2011 
2012 void
2013 TIconView::Hiding()
2014 {
2015 	ScrollTo(B_ORIGIN);
2016 }
2017 
2018 
2019 //	#pragma mark -
2020 
2021 
2022 TWindowView::TWindowView(BRect rect, TSwitchManager *manager, TSwitcherWindow *window)
2023 	: BView(rect, "wlist_view", B_FOLLOW_NONE, B_WILL_DRAW | B_PULSE_NEEDED),
2024 	fCurrentToken(-1),
2025 	fSwitcher(window),
2026 	fManager(manager)
2027 {
2028 	SetFont(be_plain_font);
2029 }
2030 
2031 
2032 void
2033 TWindowView::AttachedToWindow()
2034 {
2035 	if (Parent())
2036 		SetViewColor(Parent()->ViewColor());
2037 	else
2038 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
2039 }
2040 
2041 
2042 void
2043 TWindowView::ScrollTo(BPoint where)
2044 {
2045 	BView::ScrollTo(where);
2046 	fSwitcher->TopView()->DrawWindowScrollers(true);
2047 }
2048 
2049 
2050 
2051 BRect
2052 TWindowView::FrameOf(int32 index) const
2053 {
2054 	return BRect(0, index * fItemHeight, 100, ((index + 1) * fItemHeight) - 1);
2055 }
2056 
2057 
2058 const int32 kWindowScrollSteps = 3;
2059 
2060 void
2061 TWindowView::GetPreferredSize(float *_width, float *_height)
2062 {
2063 	font_height	fh;
2064 	be_plain_font->GetHeight(&fh);
2065 	fItemHeight = (int32) fh.ascent + fh.descent;
2066 
2067 	// top & bottom margin
2068 	fItemHeight = fItemHeight + 3 + 3;
2069 
2070 	// want fItemHeight to be divisible by kWindowScrollSteps.
2071 	fItemHeight = ((((int) fItemHeight) + kWindowScrollSteps) / kWindowScrollSteps)
2072 		* kWindowScrollSteps;
2073 
2074 	*_height = fItemHeight;
2075 
2076 	// leave width alone
2077 	*_width = Bounds().Width();
2078 }
2079 
2080 
2081 void
2082 TWindowView::ShowIndex(int32 newIndex)
2083 {
2084 	// convert index to scroll location
2085 	BPoint point(0, newIndex * fItemHeight);
2086 	BRect bounds = Bounds();
2087 
2088 	int32 groupIndex = fManager->CurrentIndex();
2089 	TTeamGroup *teamGroup = (TTeamGroup *)fManager->GroupList()->ItemAt(groupIndex);
2090 	if (!teamGroup)
2091 		return;
2092 
2093 	window_info *windowInfo = fManager->WindowInfo(groupIndex, newIndex);
2094 	if (windowInfo == NULL)
2095 		return;
2096 
2097 	fCurrentToken = windowInfo->id;
2098 	free(windowInfo);
2099 
2100 	if (bounds.top == point.y)
2101 		return;
2102 
2103 	int32 oldIndex = (int32) (bounds.top / fItemHeight);
2104 
2105 	int32 stepSize = (int32) (fItemHeight / kWindowScrollSteps);
2106 	int32 scrollValue = (newIndex > oldIndex) ? stepSize : -stepSize;
2107 	int32 total = 0;
2108 	int32 nslots = abs(newIndex - oldIndex);
2109 
2110 	while (total < (nslots * (int32)fItemHeight)) {
2111 		ScrollBy(0, scrollValue);
2112 		snooze(10000);
2113 		total += stepSize;
2114 		Window()->UpdateIfNeeded();
2115 	}
2116 }
2117 
2118 
2119 void
2120 TWindowView::Draw(BRect update)
2121 {
2122 	int32 groupIndex = fManager->CurrentIndex();
2123 	TTeamGroup *teamGroup = (TTeamGroup *) fManager->GroupList()->ItemAt(groupIndex);
2124 	if (!teamGroup)
2125 		return;
2126 
2127 	BRect bounds = Bounds();
2128 	int32 windowIndex = (int32) (bounds.top / fItemHeight);
2129 	BRect windowRect = bounds;
2130 
2131 	windowRect.top = windowIndex * fItemHeight;
2132 	windowRect.bottom = ((windowIndex+1) * fItemHeight) - 1;
2133 
2134 	for (int32 i = 0; i < 3; i++) {
2135 		if (!update.Intersects(windowRect)) {
2136 			windowIndex++;
2137 			windowRect.OffsetBy(0, fItemHeight);
2138 			continue;
2139 		}
2140 
2141 		// is window in current workspace?
2142 
2143 		bool local = true;
2144 		bool minimized = false;
2145 		BString title;
2146 
2147 		window_info	*windowInfo = fManager->WindowInfo(groupIndex, windowIndex);
2148 		if (windowInfo != NULL) {
2149 			if (SmartStrcmp(windowInfo->name, teamGroup->Name()) != 0)
2150 				title << teamGroup->Name() << ": " << windowInfo->name;
2151 			else
2152 				title = teamGroup->Name();
2153 
2154 			int32 currentWorkspace = current_workspace();
2155 			if ((windowInfo->workspaces & (1 << currentWorkspace)) == 0)
2156 				local = false;
2157 
2158 			minimized = windowInfo->is_mini;
2159 			free(windowInfo);
2160 		} else
2161 			title = teamGroup->Name();
2162 
2163 		if (!title.Length())
2164 			return;
2165 
2166 		float stringWidth = StringWidth(title.String());
2167 		float maxWidth = bounds.Width() - (14 + 5);
2168 
2169 		if (stringWidth > maxWidth) {
2170 			// window title is too long, need to truncate
2171 			TruncateString(&title, B_TRUNCATE_END, maxWidth);
2172 			stringWidth = maxWidth;
2173 		}
2174 
2175 		BPoint point((bounds.Width() - (stringWidth + 14 + 5)) / 2, windowRect.bottom - 4);
2176 		BPoint p(point.x, (windowRect.top + windowRect.bottom) / 2);
2177 		SetDrawingMode(B_OP_OVER);
2178 		const BBitmap *bitmap = AppResSet()->FindBitmap(B_MESSAGE_TYPE,
2179 			minimized ? R_WindowHiddenIcon : R_WindowShownIcon);
2180 		p.y -= (bitmap->Bounds().bottom - bitmap->Bounds().top) / 2;
2181 		DrawBitmap(bitmap, p);
2182 
2183 		if (!local) {
2184 			SetHighColor(tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), B_DARKEN_4_TINT));
2185 			p.x -= 8;
2186 			p.y += 4;
2187 			StrokeLine(p + BPoint(2, 2), p + BPoint(2, 2));
2188 			StrokeLine(p + BPoint(4, 2), p + BPoint(6, 2));
2189 
2190 			StrokeLine(p + BPoint(0, 5), p + BPoint(0, 5));
2191 			StrokeLine(p + BPoint(2, 5), p + BPoint(6, 5));
2192 
2193 			StrokeLine(p + BPoint(1, 8), p + BPoint(1, 8));
2194 			StrokeLine(p + BPoint(3, 8), p + BPoint(6, 8));
2195 
2196 			SetHighColor(0, 0, 0);
2197 		}
2198 
2199 		point.x += 21;
2200 		MovePenTo(point);
2201 
2202 		DrawString(title.String());
2203 		SetDrawingMode(B_OP_COPY);
2204 
2205 		windowIndex++;
2206 		windowRect.OffsetBy(0, fItemHeight);
2207 	}
2208 }
2209 
2210 
2211 void
2212 TWindowView::UpdateGroup(int32 , int32 windowIndex)
2213 {
2214 	ScrollTo(0, windowIndex * fItemHeight);
2215 	Invalidate(Bounds());
2216 }
2217 
2218 
2219 void
2220 TWindowView::Pulse()
2221 {
2222 	// If selected window went away then reset to first window
2223 	window_info	*windowInfo = get_window_info(fCurrentToken);
2224 	if (windowInfo == NULL) {
2225 		Invalidate();
2226 		ShowIndex(0);
2227 	} else
2228 		free(windowInfo);
2229 }
2230 
2231