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