1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered
30trademarks of Be Incorporated in the United States and other countries. Other
31brand product names are registered trademarks or trademarks of their respective
32holders.
33All 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
67class TTeamGroup {
68public:
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
76			BList*			TeamList() const
77								{ return fTeams; }
78			const char*		Name() const
79								{ return fName; }
80			const char*		Signature() const
81								{ return fSignature; }
82			uint32			Flags() const
83								{ return fFlags; }
84
85			const BBitmap*	SmallIcon() const { return fSmallIcon; }
86			const BBitmap*	LargeIcon() const { return fLargeIcon; }
87
88			void			CacheTeamIcons(int32 small, int32 large);
89
90private:
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
100class TSwitcherWindow : public BWindow {
101public:
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
125protected:
126			void			CenterWindow(BRect screenFrame, BSize);
127
128private:
129			TSwitchManager*	fManager;
130			TIconView*		fIconView;
131			TBox*			fTopView;
132			TWindowView*	fWindowView;
133			bool			fHairTrigger;
134			bool			fSkipKeyRepeats;
135};
136
137class TWindowView : public BView {
138public:
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);
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
157private:
158			int32			fCurrentToken;
159			float			fItemHeight;
160			TSwitcherWindow* fSwitcher;
161			TSwitchManager*	fManager;
162};
163
164class TIconView : public BView {
165public:
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
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
193private:
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
203class TBox : public BBox {
204public:
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
214private:
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
226static const int32 kHorizontalMargin = 11;
227static const int32 kVerticalMargin = 10;
228
229static const int32 kTeamIconSize = 48;
230
231static const int32 kWindowScrollSteps = 3;
232
233
234
235//	#pragma mark -
236
237
238static int32
239LowBitIndex(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
255inline bool
256IsVisibleInCurrentWorkspace(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
268bool
269IsKeyDown(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
278bool
279IsWindowOK(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
298int
299SmartStrcmp(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
336TTeamGroup::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
348TTeamGroup::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
361TTeamGroup::~TTeamGroup()
362{
363	delete fTeams;
364	free(fName);
365}
366
367
368void
369TTeamGroup::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
389void
390TTeamGroup::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
405TSwitchManager::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
469TSwitchManager::~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
478void
479TSwitchManager::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
608void
609TSwitchManager::_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
645void
646TSwitchManager::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
694void
695TSwitchManager::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
705TTeamGroup*
706TSwitchManager::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
722void
723TSwitchManager::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
766void
767TSwitchManager::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
800void
801TSwitchManager::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
829void
830TSwitchManager::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
846bool
847TSwitchManager::_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
867void
868TSwitchManager::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
881bool
882TSwitchManager::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*/
1006void
1007TSwitchManager::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*/
1042void
1043TSwitchManager::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
1060client_window_info*
1061TSwitchManager::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
1105int32
1106TSwitchManager::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
1139void
1140TSwitchManager::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
1149void
1150TSwitchManager::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
1183void
1184TSwitchManager::Unblock()
1185{
1186	fBlock = false;
1187	fSkipUntil = system_time();
1188}
1189
1190
1191int32
1192TSwitchManager::CurrentIndex()
1193{
1194	return fCurrentIndex;
1195}
1196
1197
1198int32
1199TSwitchManager::CurrentWindow()
1200{
1201	return fCurrentWindow;
1202}
1203
1204
1205int32
1206TSwitchManager::CurrentSlot()
1207{
1208	return fCurrentSlot;
1209}
1210
1211
1212BList*
1213TSwitchManager::GroupList()
1214{
1215	return &fGroupList;
1216}
1217
1218
1219BRect
1220TSwitchManager::CenterRect()
1221{
1222	return BRect(fCenterSlot * fSlotSize, 0,
1223		(fCenterSlot + 1) * fSlotSize - 1, fSlotSize - 1);
1224}
1225
1226
1227//	#pragma mark - TBox
1228
1229
1230TBox::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
1245void
1246TBox::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
1261void
1262TBox::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
1327void
1328TBox::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
1431void
1432TBox::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
1513void
1514TBox::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
1615TSwitcherWindow::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
1655TSwitcherWindow::~TSwitcherWindow()
1656{
1657}
1658
1659
1660void
1661TSwitcherWindow::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
1689void
1690TSwitcherWindow::ScreenChanged(BRect screenFrame, color_space)
1691{
1692	CenterWindow(screenFrame | BScreen(B_MAIN_SCREEN_ID).Frame(),
1693		Frame().Size());
1694}
1695
1696
1697void
1698TSwitcherWindow::WorkspaceActivated(int32, bool active)
1699{
1700	if (active)
1701		CenterWindow(BScreen(B_MAIN_SCREEN_ID).Frame(), Frame().Size());
1702}
1703
1704
1705void
1706TSwitcherWindow::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
1717void
1718TSwitcherWindow::Redraw(int32 index)
1719{
1720	BRect frame = fIconView->FrameOf(index);
1721	frame.right = fIconView->Bounds().right;
1722	fIconView->Invalidate(frame);
1723}
1724
1725
1726void
1727TSwitcherWindow::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
1788bool
1789TSwitcherWindow::QuitRequested()
1790{
1791	fManager->Stop(false, 0);
1792	return false;
1793}
1794
1795
1796void
1797TSwitcherWindow::WindowActivated(bool state)
1798{
1799	if (state)
1800		fManager->Unblock();
1801}
1802
1803
1804void
1805TSwitcherWindow::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
1818void
1819TSwitcherWindow::Hide()
1820{
1821	fIconView->Hiding();
1822	SetPulseRate(0);
1823	BWindow::Hide();
1824}
1825
1826
1827void
1828TSwitcherWindow::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
1839TBox*
1840TSwitcherWindow::TopView()
1841{
1842	return fTopView;
1843}
1844
1845
1846bool
1847TSwitcherWindow::HairTrigger()
1848{
1849	return fHairTrigger;
1850}
1851
1852
1853inline int32
1854TSwitcherWindow::SlotOf(int32 i)
1855{
1856	return fIconView->SlotOf(i);
1857}
1858
1859
1860inline TIconView*
1861TSwitcherWindow::IconView()
1862{
1863	return fIconView;
1864}
1865
1866
1867inline TWindowView*
1868TSwitcherWindow::WindowView()
1869{
1870	return fWindowView;
1871}
1872
1873
1874//	#pragma mark - TIconView
1875
1876
1877TIconView::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
1894TIconView::~TIconView()
1895{
1896}
1897
1898
1899void
1900TIconView::KeyDown(const char* /*bytes*/, int32 /*numBytes*/)
1901{
1902}
1903
1904
1905void
1906TIconView::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
1989void
1990TIconView::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
2036void
2037TIconView::CenterOn(int32 index)
2038{
2039	BRect rect = FrameOf(index);
2040	ScrollTo(rect.left - (fManager->CenterSlot() * fManager->SlotSize()), 0);
2041}
2042
2043
2044int32
2045TIconView::ItemAtPoint(BPoint point) const
2046{
2047	return IndexAt((int32)(point.x / fManager->SlotSize()) - fManager->CenterSlot());
2048}
2049
2050
2051void
2052TIconView::ScrollTo(BPoint where)
2053{
2054	BView::ScrollTo(where);
2055	fSwitcher->TopView()->DrawIconScrollers(true);
2056}
2057
2058
2059int32
2060TIconView::IndexAt(int32 slot) const
2061{
2062	if (slot < 0 || slot >= fManager->GroupList()->CountItems())
2063		return -1;
2064
2065	return slot;
2066}
2067
2068
2069int32
2070TIconView::SlotOf(int32 index) const
2071{
2072	BRect rect = FrameOf(index);
2073
2074	return (int32)(rect.left / fManager->SlotSize()) - fManager->CenterSlot();
2075}
2076
2077
2078BRect
2079TIconView::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
2089void
2090TIconView::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
2127void
2128TIconView::Draw(BRect update)
2129{
2130	DrawTeams(update);
2131}
2132
2133
2134void
2135TIconView::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
2148void
2149TIconView::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
2170void
2171TIconView::Showing()
2172{
2173}
2174
2175
2176void
2177TIconView::Hiding()
2178{
2179	ScrollTo(B_ORIGIN);
2180}
2181
2182
2183//	#pragma mark - TWindowView
2184
2185
2186TWindowView::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
2198void
2199TWindowView::AttachedToWindow()
2200{
2201	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
2202}
2203
2204
2205void
2206TWindowView::ScrollTo(BPoint where)
2207{
2208	BView::ScrollTo(where);
2209	fSwitcher->TopView()->DrawWindowScrollers(true);
2210}
2211
2212
2213BRect
2214TWindowView::FrameOf(int32 index) const
2215{
2216	return BRect(0, index * fItemHeight, 100, ((index + 1) * fItemHeight) - 1);
2217}
2218
2219
2220void
2221TWindowView::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
2242void
2243TWindowView::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
2281void
2282TWindowView::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
2390void
2391TWindowView::UpdateGroup(int32, int32 windowIndex)
2392{
2393	ScrollTo(0, windowIndex * fItemHeight);
2394	Invalidate(Bounds());
2395}
2396
2397
2398void
2399TWindowView::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