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