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