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