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