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