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