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