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