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