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