1/*
2 * Copyright 2010-2014 Haiku, Inc. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		John Scipione, jscipione@gmail.com
7 *		Clemens Zeidler, haiku@clemens-zeidler.de
8 */
9
10
11#include "StackAndTile.h"
12
13#include <Debug.h>
14
15#include "StackAndTilePrivate.h"
16
17#include "Desktop.h"
18#include "SATWindow.h"
19#include "Tiling.h"
20#include "Window.h"
21
22
23static const int32 kRightOptionKey	= 0x67;
24static const int32 kTabKey			= 0x26;
25static const int32 kPageUpKey		= 0x21;
26static const int32 kPageDownKey		= 0x36;
27static const int32 kLeftArrowKey	= 0x61;
28static const int32 kUpArrowKey		= 0x57;
29static const int32 kRightArrowKey	= 0x63;
30static const int32 kDownArrowKey	= 0x62;
31
32static const int32 kModifiers = B_SHIFT_KEY | B_COMMAND_KEY
33	| B_CONTROL_KEY | B_OPTION_KEY | B_MENU_KEY;
34
35
36using namespace std;
37
38
39//	#pragma mark - StackAndTile
40
41
42StackAndTile::StackAndTile()
43	:
44	fDesktop(NULL),
45	fSATKeyPressed(false),
46	fCurrentSATWindow(NULL)
47{
48
49}
50
51
52StackAndTile::~StackAndTile()
53{
54
55}
56
57
58int32
59StackAndTile::Identifier()
60{
61	return BPrivate::kMagicSATIdentifier;
62}
63
64
65void
66StackAndTile::ListenerRegistered(Desktop* desktop)
67{
68	fDesktop = desktop;
69
70	WindowList& windows = desktop->AllWindows();
71	for (Window *window = windows.FirstWindow(); window != NULL;
72			window = window->NextWindow(kAllWindowList))
73		WindowAdded(window);
74}
75
76
77void
78StackAndTile::ListenerUnregistered()
79{
80	for (SATWindowMap::iterator it = fSATWindowMap.begin();
81		it != fSATWindowMap.end(); it++) {
82		SATWindow* satWindow = it->second;
83		delete satWindow;
84	}
85	fSATWindowMap.clear();
86}
87
88
89bool
90StackAndTile::HandleMessage(Window* sender, BPrivate::LinkReceiver& link,
91	BPrivate::LinkSender& reply)
92{
93	if (sender == NULL)
94		return _HandleMessage(link, reply);
95
96	SATWindow* satWindow = GetSATWindow(sender);
97	if (!satWindow)
98		return false;
99
100	return satWindow->HandleMessage(satWindow, link, reply);
101}
102
103
104void
105StackAndTile::WindowAdded(Window* window)
106{
107	SATWindow* satWindow = new (std::nothrow)SATWindow(this, window);
108	if (!satWindow)
109		return;
110
111	ASSERT(fSATWindowMap.find(window) == fSATWindowMap.end());
112	fSATWindowMap[window] = satWindow;
113}
114
115
116void
117StackAndTile::WindowRemoved(Window* window)
118{
119	STRACE_SAT("StackAndTile::WindowRemoved %s\n", window->Title());
120
121	SATWindowMap::iterator it = fSATWindowMap.find(window);
122	if (it == fSATWindowMap.end())
123		return;
124
125	SATWindow* satWindow = it->second;
126	// delete SATWindow
127	delete satWindow;
128	fSATWindowMap.erase(it);
129}
130
131
132bool
133StackAndTile::KeyPressed(uint32 what, int32 key, int32 modifiers)
134{
135	if (what == B_MODIFIERS_CHANGED
136		|| (what == B_UNMAPPED_KEY_DOWN && key == kRightOptionKey)
137		|| (what == B_UNMAPPED_KEY_UP && key == kRightOptionKey)) {
138		// switch to and from stacking and snapping mode
139		bool wasPressed = fSATKeyPressed;
140		fSATKeyPressed = (what == B_MODIFIERS_CHANGED
141				&& (modifiers & kModifiers) == B_OPTION_KEY)
142			|| (what == B_UNMAPPED_KEY_DOWN && key == kRightOptionKey);
143		if (wasPressed && !fSATKeyPressed)
144			_StopSAT();
145		if (!wasPressed && fSATKeyPressed)
146			_StartSAT();
147	}
148
149	if (!SATKeyPressed() || what != B_KEY_DOWN)
150		return false;
151
152	SATWindow* frontWindow = GetSATWindow(fDesktop->FocusWindow());
153	SATGroup* currentGroup = _GetSATGroup(frontWindow);
154
155	switch (key) {
156		case kLeftArrowKey:
157		case kRightArrowKey:
158		case kTabKey:
159		{
160			// go to previous or next window tab in current window group
161			if (currentGroup == NULL)
162				return false;
163
164			int32 groupSize = currentGroup->CountItems();
165			if (groupSize <= 1)
166				return false;
167
168			for (int32 i = 0; i < groupSize; i++) {
169				SATWindow* targetWindow = currentGroup->WindowAt(i);
170				if (targetWindow == frontWindow) {
171					if (key == kLeftArrowKey
172						|| (key == kTabKey && (modifiers & B_SHIFT_KEY) != 0)) {
173						// Go to previous window tab (wrap around)
174						int32 previousIndex = i > 0 ? i - 1 : groupSize - 1;
175						targetWindow = currentGroup->WindowAt(previousIndex);
176					} else {
177						// Go to next window tab (wrap around)
178						int32 nextIndex = i < groupSize - 1 ? i + 1 : 0;
179						targetWindow = currentGroup->WindowAt(nextIndex);
180					}
181
182					_ActivateWindow(targetWindow);
183					return true;
184				}
185			}
186			break;
187		}
188
189		case kUpArrowKey:
190		case kPageUpKey:
191		{
192			// go to previous window group
193			GroupIterator groups(this, fDesktop);
194			groups.SetCurrentGroup(currentGroup);
195			SATGroup* backmostGroup = NULL;
196
197			while (true) {
198				SATGroup* group = groups.NextGroup();
199				if (group == NULL || group == currentGroup)
200					break;
201				else if (group->CountItems() < 1)
202					continue;
203
204				if (currentGroup == NULL) {
205					SATWindow* activeWindow = group->ActiveWindow();
206					if (activeWindow != NULL)
207						_ActivateWindow(activeWindow);
208					else
209						_ActivateWindow(group->WindowAt(0));
210
211					return true;
212				}
213				backmostGroup = group;
214			}
215			if (backmostGroup != NULL && backmostGroup != currentGroup) {
216				SATWindow* activeWindow = backmostGroup->ActiveWindow();
217				if (activeWindow != NULL)
218					_ActivateWindow(activeWindow);
219				else
220					_ActivateWindow(backmostGroup->WindowAt(0));
221
222				return true;
223			}
224
225			break;
226		}
227
228		case kDownArrowKey:
229		case kPageDownKey:
230		{
231			// go to next window group
232			GroupIterator groups(this, fDesktop);
233			groups.SetCurrentGroup(currentGroup);
234
235			while (true) {
236				SATGroup* group = groups.NextGroup();
237				if (group == NULL || group == currentGroup)
238					break;
239				else if (group->CountItems() < 1)
240					continue;
241
242				SATWindow* activeWindow = group->ActiveWindow();
243				if (activeWindow != NULL)
244					_ActivateWindow(activeWindow);
245				else
246					_ActivateWindow(group->WindowAt(0));
247
248				if (currentGroup != NULL && frontWindow != NULL) {
249					Window* window = frontWindow->GetWindow();
250					fDesktop->SendWindowBehind(window);
251					WindowSentBehind(window, NULL);
252				}
253				return true;
254			}
255			break;
256		}
257	}
258
259	return false;
260}
261
262
263void
264StackAndTile::MouseDown(Window* window, BMessage* message, const BPoint& where)
265{
266	SATWindow* satWindow = GetSATWindow(window);
267	if (!satWindow || !satWindow->GetDecorator())
268		return;
269
270	// fCurrentSATWindow is not zero if e.g. the secondary and the primary
271	// mouse button are pressed at the same time
272	if ((message->FindInt32("buttons") & B_PRIMARY_MOUSE_BUTTON) == 0 ||
273		fCurrentSATWindow != NULL)
274		return;
275
276	// we are only interested in single clicks
277	if (message->FindInt32("clicks") == 2)
278		return;
279
280	int32 tab;
281	switch (satWindow->GetDecorator()->RegionAt(where, tab)) {
282		case Decorator::REGION_TAB:
283		case Decorator::REGION_LEFT_BORDER:
284		case Decorator::REGION_RIGHT_BORDER:
285		case Decorator::REGION_TOP_BORDER:
286		case Decorator::REGION_BOTTOM_BORDER:
287		case Decorator::REGION_LEFT_TOP_CORNER:
288		case Decorator::REGION_LEFT_BOTTOM_CORNER:
289		case Decorator::REGION_RIGHT_TOP_CORNER:
290		case Decorator::REGION_RIGHT_BOTTOM_CORNER:
291			break;
292
293		default:
294			return;
295	}
296
297	ASSERT(fCurrentSATWindow == NULL);
298	fCurrentSATWindow = satWindow;
299
300	if (!SATKeyPressed())
301		return;
302
303	_StartSAT();
304}
305
306
307void
308StackAndTile::MouseUp(Window* window, BMessage* message, const BPoint& where)
309{
310	if (fSATKeyPressed)
311		_StopSAT();
312
313	fCurrentSATWindow = NULL;
314}
315
316
317void
318StackAndTile::WindowMoved(Window* window)
319{
320	SATWindow* satWindow = GetSATWindow(window);
321	if (satWindow == NULL)
322		return;
323
324	if (SATKeyPressed() && fCurrentSATWindow)
325		satWindow->FindSnappingCandidates();
326	else
327		satWindow->DoGroupLayout();
328}
329
330
331void
332StackAndTile::WindowResized(Window* window)
333{
334	SATWindow* satWindow = GetSATWindow(window);
335	if (satWindow == NULL)
336		return;
337	satWindow->Resized();
338
339	if (SATKeyPressed() && fCurrentSATWindow)
340		satWindow->FindSnappingCandidates();
341	else
342		satWindow->DoGroupLayout();
343}
344
345
346void
347StackAndTile::WindowActivated(Window* window)
348{
349	SATWindow* satWindow = GetSATWindow(window);
350	if (satWindow == NULL)
351		return;
352
353	_ActivateWindow(satWindow);
354}
355
356
357void
358StackAndTile::WindowSentBehind(Window* window, Window* behindOf)
359{
360	SATWindow* satWindow = GetSATWindow(window);
361	if (satWindow == NULL)
362		return;
363
364	SATGroup* group = satWindow->GetGroup();
365	if (group == NULL)
366		return;
367
368	Desktop* desktop = satWindow->GetWindow()->Desktop();
369	if (desktop == NULL)
370		return;
371
372	const WindowAreaList& areaList = group->GetAreaList();
373	for (int32 i = 0; i < areaList.CountItems(); i++) {
374		WindowArea* area = areaList.ItemAt(i);
375		SATWindow* topWindow = area->TopWindow();
376		if (topWindow == NULL || topWindow == satWindow)
377			continue;
378		desktop->SendWindowBehind(topWindow->GetWindow(), behindOf);
379	}
380}
381
382
383void
384StackAndTile::WindowWorkspacesChanged(Window* window, uint32 workspaces)
385{
386	SATWindow* satWindow = GetSATWindow(window);
387	if (satWindow == NULL)
388		return;
389
390	SATGroup* group = satWindow->GetGroup();
391	if (group == NULL)
392		return;
393
394	Desktop* desktop = satWindow->GetWindow()->Desktop();
395	if (desktop == NULL)
396		return;
397
398	const WindowAreaList& areaList = group->GetAreaList();
399	for (int32 i = 0; i < areaList.CountItems(); i++) {
400		WindowArea* area = areaList.ItemAt(i);
401		if (area->WindowList().HasItem(satWindow))
402			continue;
403		SATWindow* topWindow = area->TopWindow();
404		desktop->SetWindowWorkspaces(topWindow->GetWindow(), workspaces);
405	}
406}
407
408
409void
410StackAndTile::WindowHidden(Window* window, bool fromMinimize)
411{
412	SATWindow* satWindow = GetSATWindow(window);
413	if (satWindow == NULL)
414		return;
415
416	SATGroup* group = satWindow->GetGroup();
417	if (group == NULL)
418		return;
419
420	if (fromMinimize == false && group->CountItems() > 1)
421		group->RemoveWindow(satWindow, false);
422}
423
424
425void
426StackAndTile::WindowMinimized(Window* window, bool minimize)
427{
428	SATWindow* satWindow = GetSATWindow(window);
429	if (satWindow == NULL)
430		return;
431
432	SATGroup* group = satWindow->GetGroup();
433	if (group == NULL)
434		return;
435
436	Desktop* desktop = satWindow->GetWindow()->Desktop();
437	if (desktop == NULL)
438		return;
439
440	for (int i = 0; i < group->CountItems(); i++) {
441		SATWindow* listWindow = group->WindowAt(i);
442		if (listWindow != satWindow)
443			listWindow->GetWindow()->ServerWindow()->NotifyMinimize(minimize);
444	}
445}
446
447
448void
449StackAndTile::WindowTabLocationChanged(Window* window, float location,
450	bool isShifting)
451{
452
453}
454
455
456void
457StackAndTile::SizeLimitsChanged(Window* window, int32 minWidth, int32 maxWidth,
458	int32 minHeight, int32 maxHeight)
459{
460	SATWindow* satWindow = GetSATWindow(window);
461	if (!satWindow)
462		return;
463	satWindow->SetOriginalSizeLimits(minWidth, maxWidth, minHeight, maxHeight);
464
465	// trigger a relayout
466	WindowMoved(window);
467}
468
469
470void
471StackAndTile::WindowLookChanged(Window* window, window_look look)
472{
473	SATWindow* satWindow = GetSATWindow(window);
474	if (!satWindow)
475		return;
476	satWindow->WindowLookChanged(look);
477}
478
479
480void
481StackAndTile::WindowFeelChanged(Window* window, window_feel feel)
482{
483	// check if it is still a compatible feel
484	if (feel == B_NORMAL_WINDOW_FEEL)
485		return;
486	SATWindow* satWindow = GetSATWindow(window);
487	if (satWindow == NULL)
488		return;
489
490	SATGroup* group = satWindow->GetGroup();
491	if (group == NULL)
492		return;
493
494	if (group->CountItems() > 1)
495		group->RemoveWindow(satWindow, false);
496}
497
498
499bool
500StackAndTile::SetDecoratorSettings(Window* window, const BMessage& settings)
501{
502	SATWindow* satWindow = GetSATWindow(window);
503	if (!satWindow)
504		return false;
505
506	return satWindow->SetSettings(settings);
507}
508
509
510void
511StackAndTile::GetDecoratorSettings(Window* window, BMessage& settings)
512{
513	SATWindow* satWindow = GetSATWindow(window);
514	if (!satWindow)
515		return;
516
517	satWindow->GetSettings(settings);
518}
519
520
521SATWindow*
522StackAndTile::GetSATWindow(Window* window)
523{
524	if (window == NULL)
525		return NULL;
526
527	SATWindowMap::const_iterator it = fSATWindowMap.find(
528		window);
529	if (it != fSATWindowMap.end())
530		return it->second;
531
532	// TODO fix race condition with WindowAdded this method is called before
533	// WindowAdded and a SATWindow is created twice!
534	return NULL;
535
536	// If we don't know this window, memory allocation might has been failed
537	// previously. Try to add the window now.
538	SATWindow* satWindow = new (std::nothrow)SATWindow(this, window);
539	if (satWindow)
540		fSATWindowMap[window] = satWindow;
541
542	return satWindow;
543}
544
545
546SATWindow*
547StackAndTile::FindSATWindow(uint64 id)
548{
549	for (SATWindowMap::const_iterator it = fSATWindowMap.begin();
550		it != fSATWindowMap.end(); it++) {
551		SATWindow* window = it->second;
552		if (window->Id() == id)
553			return window;
554	}
555
556	return NULL;
557}
558
559
560//	#pragma mark - StackAndTile private methods
561
562
563void
564StackAndTile::_StartSAT()
565{
566	STRACE_SAT("StackAndTile::_StartSAT()\n");
567	if (!fCurrentSATWindow)
568		return;
569
570	// Remove window from the group.
571	SATGroup* group = fCurrentSATWindow->GetGroup();
572	if (group == NULL)
573		return;
574
575	group->RemoveWindow(fCurrentSATWindow, false);
576	// Bring window to the front. (in focus follow mouse this is not
577	// automatically the case)
578	_ActivateWindow(fCurrentSATWindow);
579
580	fCurrentSATWindow->FindSnappingCandidates();
581}
582
583
584void
585StackAndTile::_StopSAT()
586{
587	STRACE_SAT("StackAndTile::_StopSAT()\n");
588	if (!fCurrentSATWindow)
589		return;
590	if (fCurrentSATWindow->JoinCandidates())
591		_ActivateWindow(fCurrentSATWindow);
592}
593
594
595void
596StackAndTile::_ActivateWindow(SATWindow* satWindow)
597{
598	if (satWindow == NULL)
599		return;
600
601	SATGroup* group = satWindow->GetGroup();
602	if (group == NULL)
603		return;
604
605	Desktop* desktop = satWindow->GetWindow()->Desktop();
606	if (desktop == NULL)
607		return;
608
609	WindowArea* area = satWindow->GetWindowArea();
610	if (area == NULL)
611		return;
612
613	area->MoveToTopLayer(satWindow);
614
615	// save the active window of the current group
616	SATWindow* frontWindow = GetSATWindow(fDesktop->FocusWindow());
617	SATGroup* currentGroup = _GetSATGroup(frontWindow);
618	if (currentGroup != NULL && currentGroup != group && frontWindow != NULL)
619		currentGroup->SetActiveWindow(frontWindow);
620	else
621		group->SetActiveWindow(satWindow);
622
623	const WindowAreaList& areas = group->GetAreaList();
624	int32 areasCount = areas.CountItems();
625	for (int32 i = 0; i < areasCount; i++) {
626		WindowArea* currentArea = areas.ItemAt(i);
627		if (currentArea == area)
628			continue;
629
630		desktop->ActivateWindow(currentArea->TopWindow()->GetWindow());
631	}
632
633	desktop->ActivateWindow(satWindow->GetWindow());
634}
635
636
637bool
638StackAndTile::_HandleMessage(BPrivate::LinkReceiver& link,
639	BPrivate::LinkSender& reply)
640{
641	int32 what;
642	link.Read<int32>(&what);
643
644	switch (what) {
645		case BPrivate::kSaveAllGroups:
646		{
647			BMessage allGroupsArchive;
648			GroupIterator groups(this, fDesktop);
649			while (true) {
650				SATGroup* group = groups.NextGroup();
651				if (group == NULL)
652					break;
653				if (group->CountItems() <= 1)
654					continue;
655				BMessage groupArchive;
656				if (group->ArchiveGroup(groupArchive) != B_OK)
657					continue;
658				allGroupsArchive.AddMessage("group", &groupArchive);
659			}
660			int32 size = allGroupsArchive.FlattenedSize();
661			char buffer[size];
662			if (allGroupsArchive.Flatten(buffer, size) == B_OK) {
663				reply.StartMessage(B_OK);
664				reply.Attach<int32>(size);
665				reply.Attach(buffer, size);
666			} else
667				reply.StartMessage(B_ERROR);
668			reply.Flush();
669			break;
670		}
671
672		case BPrivate::kRestoreGroup:
673		{
674			int32 size;
675			if (link.Read<int32>(&size) == B_OK) {
676				char buffer[size];
677				BMessage group;
678				if (link.Read(buffer, size) == B_OK
679					&& group.Unflatten(buffer) == B_OK) {
680					status_t status = SATGroup::RestoreGroup(group, this);
681					reply.StartMessage(status);
682					reply.Flush();
683				}
684			}
685			break;
686		}
687
688		default:
689			return false;
690	}
691
692	return true;
693}
694
695
696SATGroup*
697StackAndTile::_GetSATGroup(SATWindow* window)
698{
699	if (window == NULL)
700		return NULL;
701
702	SATGroup* group = window->GetGroup();
703	if (group == NULL)
704		return NULL;
705
706	if (group->CountItems() < 1)
707		return NULL;
708
709	return group;
710}
711
712
713//	#pragma mark - GroupIterator
714
715
716GroupIterator::GroupIterator(StackAndTile* sat, Desktop* desktop)
717	:
718	fStackAndTile(sat),
719	fDesktop(desktop),
720	fCurrentGroup(NULL)
721{
722	RewindToFront();
723}
724
725
726void
727GroupIterator::RewindToFront()
728{
729	fCurrentWindow = fDesktop->CurrentWindows().LastWindow();
730}
731
732
733SATGroup*
734GroupIterator::NextGroup()
735{
736	SATGroup* group = NULL;
737	do {
738		Window* window = fCurrentWindow;
739		if (window == NULL) {
740			group = NULL;
741			break;
742		}
743		fCurrentWindow = fCurrentWindow->PreviousWindow(
744			fCurrentWindow->CurrentWorkspace());
745		if (window->IsHidden()
746			|| strcmp(window->Title(), "Deskbar") == 0
747			|| strcmp(window->Title(), "Desktop") == 0) {
748			continue;
749		}
750
751		SATWindow* satWindow = fStackAndTile->GetSATWindow(window);
752		group = satWindow->GetGroup();
753	} while (group == NULL || fCurrentGroup == group);
754
755	fCurrentGroup = group;
756	return fCurrentGroup;
757}
758
759
760//	#pragma mark - WindowIterator
761
762
763WindowIterator::WindowIterator(SATGroup* group, bool reverseLayerOrder)
764	:
765	fGroup(group),
766	fReverseLayerOrder(reverseLayerOrder)
767{
768	if (fReverseLayerOrder)
769		_ReverseRewind();
770	else
771		Rewind();
772}
773
774
775void
776WindowIterator::Rewind()
777{
778	fAreaIndex = 0;
779	fWindowIndex = 0;
780	fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex);
781}
782
783
784SATWindow*
785WindowIterator::NextWindow()
786{
787	if (fReverseLayerOrder)
788		return _ReverseNextWindow();
789
790	if (fWindowIndex == fCurrentArea->LayerOrder().CountItems()) {
791		fAreaIndex++;
792		fWindowIndex = 0;
793		fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex);
794		if (!fCurrentArea)
795			return NULL;
796	}
797	SATWindow* window = fCurrentArea->LayerOrder().ItemAt(fWindowIndex);
798	fWindowIndex++;
799	return window;
800}
801
802
803//	#pragma mark - WindowIterator private methods
804
805
806SATWindow*
807WindowIterator::_ReverseNextWindow()
808{
809	if (fWindowIndex < 0) {
810		fAreaIndex++;
811		fCurrentArea = fGroup->GetAreaList().ItemAt(fAreaIndex);
812		if (!fCurrentArea)
813			return NULL;
814		fWindowIndex = fCurrentArea->LayerOrder().CountItems() - 1;
815	}
816	SATWindow* window = fCurrentArea->LayerOrder().ItemAt(fWindowIndex);
817	fWindowIndex--;
818	return window;
819}
820
821
822void
823WindowIterator::_ReverseRewind()
824{
825	Rewind();
826	if (fCurrentArea)
827		fWindowIndex = fCurrentArea->LayerOrder().CountItems() - 1;
828}
829