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