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