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