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