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 "SATGroup.h"
12
13#include <vector>
14
15#include <Debug.h>
16#include <Message.h>
17
18#include "Desktop.h"
19
20#include "SATWindow.h"
21#include "StackAndTile.h"
22#include "Window.h"
23
24
25using namespace std;
26using namespace LinearProgramming;
27
28
29const float kExtentPenalty = 1;
30const float kHighPenalty = 100;
31const float kInequalityPenalty = 10000;
32
33
34WindowArea::WindowArea(Crossing* leftTop, Crossing* rightTop,
35	Crossing* leftBottom, Crossing* rightBottom)
36	:
37	fGroup(NULL),
38
39	fLeftTopCrossing(leftTop),
40	fRightTopCrossing(rightTop),
41	fLeftBottomCrossing(leftBottom),
42	fRightBottomCrossing(rightBottom),
43
44	fMinWidthConstraint(NULL),
45	fMinHeightConstraint(NULL),
46	fMaxWidthConstraint(NULL),
47	fMaxHeightConstraint(NULL),
48	fWidthConstraint(NULL),
49	fHeightConstraint(NULL)
50{
51}
52
53
54WindowArea::~WindowArea()
55{
56	if (fGroup)
57		fGroup->WindowAreaRemoved(this);
58
59	_CleanupCorners();
60	fGroup->fWindowAreaList.RemoveItem(this);
61
62	_UninitConstraints();
63}
64
65
66bool
67WindowArea::Init(SATGroup* group)
68{
69	_UninitConstraints();
70
71	if (group == NULL || group->fWindowAreaList.AddItem(this) == false)
72		return false;
73
74	fGroup = group;
75
76	LinearSpec* linearSpec = fGroup->GetLinearSpec();
77
78	fMinWidthConstraint = linearSpec->AddConstraint(1.0, RightVar(), -1.0,
79		LeftVar(), kGE, 0);
80	fMinHeightConstraint = linearSpec->AddConstraint(1.0, BottomVar(), -1.0,
81		TopVar(), kGE, 0);
82
83	fMaxWidthConstraint = linearSpec->AddConstraint(1.0, RightVar(), -1.0,
84		LeftVar(), kLE, 0, kInequalityPenalty, kInequalityPenalty);
85	fMaxHeightConstraint = linearSpec->AddConstraint(1.0, BottomVar(), -1.0,
86		TopVar(), kLE, 0, kInequalityPenalty, kInequalityPenalty);
87
88	// Width and height have soft constraints
89	fWidthConstraint = linearSpec->AddConstraint(1.0, RightVar(), -1.0,
90		LeftVar(), kEQ, 0, kExtentPenalty,
91		kExtentPenalty);
92	fHeightConstraint = linearSpec->AddConstraint(-1.0, TopVar(), 1.0,
93		BottomVar(), kEQ, 0, kExtentPenalty,
94		kExtentPenalty);
95
96	if (!fMinWidthConstraint || !fMinHeightConstraint || !fWidthConstraint
97		|| !fHeightConstraint || !fMaxWidthConstraint
98		|| !fMaxHeightConstraint)
99		return false;
100
101	return true;
102}
103
104
105void
106WindowArea::DoGroupLayout()
107{
108	SATWindow* parentWindow = fWindowLayerOrder.ItemAt(0);
109	if (parentWindow == NULL)
110		return;
111
112	BRect frame = parentWindow->CompleteWindowFrame();
113	// Make it also work for solver which don't support negative variables
114	frame.OffsetBy(kMakePositiveOffset, kMakePositiveOffset);
115
116	// adjust window size soft constraints
117	fWidthConstraint->SetRightSide(frame.Width());
118	fHeightConstraint->SetRightSide(frame.Height());
119
120	LinearSpec* linearSpec = fGroup->GetLinearSpec();
121	Constraint* leftConstraint = linearSpec->AddConstraint(1.0, LeftVar(),
122		kEQ, frame.left);
123	Constraint* topConstraint = linearSpec->AddConstraint(1.0, TopVar(), kEQ,
124		frame.top);
125
126	// give soft constraints a high penalty
127	fWidthConstraint->SetPenaltyNeg(kHighPenalty);
128	fWidthConstraint->SetPenaltyPos(kHighPenalty);
129	fHeightConstraint->SetPenaltyNeg(kHighPenalty);
130	fHeightConstraint->SetPenaltyPos(kHighPenalty);
131
132	// After we set the new parameter solve and apply the new layout.
133	ResultType result;
134	for (int32 tries = 0; tries < 15; tries++) {
135		result = fGroup->GetLinearSpec()->Solve();
136		if (result == kInfeasible) {
137			debug_printf("can't solve constraints!\n");
138			break;
139		}
140		if (result == kOptimal) {
141			const WindowAreaList& areas = fGroup->GetAreaList();
142			for (int32 i = 0; i < areas.CountItems(); i++) {
143				WindowArea* area = areas.ItemAt(i);
144				area->_MoveToSAT(parentWindow);
145			}
146			break;
147		}
148	}
149
150	// set penalties back to normal
151	fWidthConstraint->SetPenaltyNeg(kExtentPenalty);
152	fWidthConstraint->SetPenaltyPos(kExtentPenalty);
153	fHeightConstraint->SetPenaltyNeg(kExtentPenalty);
154	fHeightConstraint->SetPenaltyPos(kExtentPenalty);
155
156	linearSpec->RemoveConstraint(leftConstraint);
157	linearSpec->RemoveConstraint(topConstraint);
158}
159
160
161void
162WindowArea::UpdateSizeLimits()
163{
164	_UpdateConstraintValues();
165}
166
167
168void
169WindowArea::UpdateSizeConstaints(const BRect& frame)
170{
171	// adjust window size soft constraints
172	fWidthConstraint->SetRightSide(frame.Width());
173	fHeightConstraint->SetRightSide(frame.Height());
174}
175
176
177bool
178WindowArea::MoveWindowToPosition(SATWindow* window, int32 index)
179{
180	int32 oldIndex = fWindowList.IndexOf(window);
181	ASSERT(oldIndex != index);
182	return fWindowList.MoveItem(oldIndex, index);
183}
184
185
186SATWindow*
187WindowArea::TopWindow()
188{
189	return fWindowLayerOrder.ItemAt(fWindowLayerOrder.CountItems() - 1);
190}
191
192
193void
194WindowArea::_UpdateConstraintValues()
195{
196	SATWindow* topWindow = TopWindow();
197	if (topWindow == NULL)
198		return;
199
200	int32 minWidth, maxWidth;
201	int32 minHeight, maxHeight;
202	SATWindow* window = fWindowList.ItemAt(0);
203	window->GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
204	for (int32 i = 1; i < fWindowList.CountItems(); i++) {
205		window = fWindowList.ItemAt(i);
206		// size limit constraints
207		int32 minW, maxW;
208		int32 minH, maxH;
209		window->GetSizeLimits(&minW, &maxW, &minH, &maxH);
210		if (minWidth < minW)
211			minWidth = minW;
212		if (minHeight < minH)
213			minHeight = minH;
214		if (maxWidth < maxW)
215			maxWidth = maxW;
216		if (maxHeight < maxH)
217			maxHeight = maxH;
218	}
219	// the current solver don't like big values
220	const int32 kMaxSolverValue = 5000;
221	if (minWidth > kMaxSolverValue)
222		minWidth = kMaxSolverValue;
223	if (minHeight > kMaxSolverValue)
224		minHeight = kMaxSolverValue;
225	if (maxWidth > kMaxSolverValue)
226		maxWidth = kMaxSolverValue;
227	if (maxHeight > kMaxSolverValue)
228		maxHeight = kMaxSolverValue;
229
230	topWindow->AddDecorator(&minWidth, &maxWidth, &minHeight, &maxHeight);
231	fMinWidthConstraint->SetRightSide(minWidth);
232	fMinHeightConstraint->SetRightSide(minHeight);
233
234	fMaxWidthConstraint->SetRightSide(maxWidth);
235	fMaxHeightConstraint->SetRightSide(maxHeight);
236
237	BRect frame = topWindow->CompleteWindowFrame();
238	fWidthConstraint->SetRightSide(frame.Width());
239	fHeightConstraint->SetRightSide(frame.Height());
240}
241
242
243bool
244WindowArea::_AddWindow(SATWindow* window, SATWindow* after)
245{
246	if (after) {
247		int32 indexAfter = fWindowList.IndexOf(after);
248		if (!fWindowList.AddItem(window, indexAfter + 1))
249			return false;
250	} else if (fWindowList.AddItem(window) == false)
251		return false;
252
253	AcquireReference();
254
255	if (fWindowList.CountItems() <= 1)
256		_InitCorners();
257
258	fWindowLayerOrder.AddItem(window);
259
260	_UpdateConstraintValues();
261	return true;
262}
263
264
265bool
266WindowArea::_RemoveWindow(SATWindow* window)
267{
268	if (!fWindowList.RemoveItem(window))
269		return false;
270
271	fWindowLayerOrder.RemoveItem(window);
272	_UpdateConstraintValues();
273
274	window->RemovedFromArea(this);
275	ReleaseReference();
276	return true;
277}
278
279
280Tab*
281WindowArea::LeftTab()
282{
283	return fLeftTopCrossing->VerticalTab();
284}
285
286
287Tab*
288WindowArea::RightTab()
289{
290	return fRightBottomCrossing->VerticalTab();
291}
292
293
294Tab*
295WindowArea::TopTab()
296{
297	return fLeftTopCrossing->HorizontalTab();
298}
299
300
301Tab*
302WindowArea::BottomTab()
303{
304	return fRightBottomCrossing->HorizontalTab();
305}
306
307
308BRect
309WindowArea::Frame()
310{
311	return BRect(fLeftTopCrossing->VerticalTab()->Position(),
312		fLeftTopCrossing->HorizontalTab()->Position(),
313		fRightBottomCrossing->VerticalTab()->Position(),
314		fRightBottomCrossing->HorizontalTab()->Position());
315}
316
317
318bool
319WindowArea::PropagateToGroup(SATGroup* group)
320{
321	BReference<Crossing> newLeftTop = _CrossingByPosition(fLeftTopCrossing,
322		group);
323	BReference<Crossing> newRightTop = _CrossingByPosition(fRightTopCrossing,
324		group);
325	BReference<Crossing> newLeftBottom = _CrossingByPosition(
326		fLeftBottomCrossing, group);
327	BReference<Crossing> newRightBottom = _CrossingByPosition(
328		fRightBottomCrossing, group);
329
330	if (!newLeftTop || !newRightTop || !newLeftBottom || !newRightBottom)
331		return false;
332
333	// hold a ref to the crossings till we cleaned up everything
334	BReference<Crossing> oldLeftTop = fLeftTopCrossing;
335	BReference<Crossing> oldRightTop = fRightTopCrossing;
336	BReference<Crossing> oldLeftBottom = fLeftBottomCrossing;
337	BReference<Crossing> oldRightBottom = fRightBottomCrossing;
338
339	fLeftTopCrossing = newLeftTop;
340	fRightTopCrossing = newRightTop;
341	fLeftBottomCrossing = newLeftBottom;
342	fRightBottomCrossing = newRightBottom;
343
344	_InitCorners();
345
346	BReference<SATGroup> oldGroup = fGroup;
347	// manage constraints
348	if (Init(group) == false)
349		return false;
350
351	oldGroup->fWindowAreaList.RemoveItem(this);
352	for (int32 i = 0; i < fWindowList.CountItems(); i++) {
353		SATWindow* window = fWindowList.ItemAt(i);
354		if (oldGroup->fSATWindowList.RemoveItem(window) == false)
355			return false;
356		if (group->fSATWindowList.AddItem(window) == false) {
357			_UninitConstraints();
358			return false;
359		}
360	}
361
362	_UpdateConstraintValues();
363
364	return true;
365}
366
367
368bool
369WindowArea::MoveToTopLayer(SATWindow* window)
370{
371	if (!fWindowLayerOrder.RemoveItem(window))
372		return false;
373	return fWindowLayerOrder.AddItem(window);
374}
375
376
377void
378WindowArea::_UninitConstraints()
379{
380	if (fGroup != NULL) {
381		LinearSpec* linearSpec = fGroup->GetLinearSpec();
382
383		if (linearSpec != NULL) {
384			linearSpec->RemoveConstraint(fMinWidthConstraint, true);
385			linearSpec->RemoveConstraint(fMinHeightConstraint, true);
386			linearSpec->RemoveConstraint(fMaxWidthConstraint, true);
387			linearSpec->RemoveConstraint(fMaxHeightConstraint, true);
388			linearSpec->RemoveConstraint(fWidthConstraint, true);
389			linearSpec->RemoveConstraint(fHeightConstraint, true);
390		}
391	}
392
393	fMinWidthConstraint = NULL;
394	fMinHeightConstraint = NULL;
395	fMaxWidthConstraint = NULL;
396	fMaxHeightConstraint = NULL;
397	fWidthConstraint = NULL;
398	fHeightConstraint = NULL;
399}
400
401
402BReference<Crossing>
403WindowArea::_CrossingByPosition(Crossing* crossing, SATGroup* group)
404{
405	BReference<Crossing> crossRef = NULL;
406
407	Tab* oldHTab = crossing->HorizontalTab();
408	BReference<Tab> hTab = group->FindHorizontalTab(oldHTab->Position());
409	if (!hTab)
410		hTab = group->_AddHorizontalTab(oldHTab->Position());
411	if (!hTab)
412		return crossRef;
413
414	Tab* oldVTab = crossing->VerticalTab();
415	crossRef = hTab->FindCrossing(oldVTab->Position());
416	if (crossRef)
417		return crossRef;
418
419	BReference<Tab> vTab = group->FindVerticalTab(oldVTab->Position());
420	if (!vTab)
421		vTab = group->_AddVerticalTab(oldVTab->Position());
422	if (!vTab)
423		return crossRef;
424
425	return hTab->AddCrossing(vTab);
426}
427
428
429void
430WindowArea::_InitCorners()
431{
432	_SetToWindowCorner(fLeftTopCrossing->RightBottomCorner());
433	_SetToNeighbourCorner(fLeftTopCrossing->LeftBottomCorner());
434	_SetToNeighbourCorner(fLeftTopCrossing->RightTopCorner());
435
436	_SetToWindowCorner(fRightTopCrossing->LeftBottomCorner());
437	_SetToNeighbourCorner(fRightTopCrossing->LeftTopCorner());
438	_SetToNeighbourCorner(fRightTopCrossing->RightBottomCorner());
439
440	_SetToWindowCorner(fLeftBottomCrossing->RightTopCorner());
441	_SetToNeighbourCorner(fLeftBottomCrossing->LeftTopCorner());
442	_SetToNeighbourCorner(fLeftBottomCrossing->RightBottomCorner());
443
444	_SetToWindowCorner(fRightBottomCrossing->LeftTopCorner());
445	_SetToNeighbourCorner(fRightBottomCrossing->LeftBottomCorner());
446	_SetToNeighbourCorner(fRightBottomCrossing->RightTopCorner());
447}
448
449
450void
451WindowArea::_CleanupCorners()
452{
453	_UnsetWindowCorner(fLeftTopCrossing->RightBottomCorner());
454	_UnsetNeighbourCorner(fLeftTopCrossing->LeftBottomCorner(),
455		fLeftBottomCrossing->LeftTopCorner());
456	_UnsetNeighbourCorner(fLeftTopCrossing->RightTopCorner(),
457		fLeftBottomCrossing->LeftTopCorner());
458
459	_UnsetWindowCorner(fRightTopCrossing->LeftBottomCorner());
460	_UnsetNeighbourCorner(fRightTopCrossing->LeftTopCorner(),
461		fLeftBottomCrossing->RightTopCorner());
462	_UnsetNeighbourCorner(fRightTopCrossing->RightBottomCorner(),
463		fLeftBottomCrossing->RightTopCorner());
464
465	_UnsetWindowCorner(fLeftBottomCrossing->RightTopCorner());
466	_UnsetNeighbourCorner(fLeftBottomCrossing->LeftTopCorner(),
467		fLeftBottomCrossing->LeftBottomCorner());
468	_UnsetNeighbourCorner(fLeftBottomCrossing->RightBottomCorner(),
469		fLeftBottomCrossing->LeftBottomCorner());
470
471	_UnsetWindowCorner(fRightBottomCrossing->LeftTopCorner());
472	_UnsetNeighbourCorner(fRightBottomCrossing->LeftBottomCorner(),
473		fRightBottomCrossing->RightBottomCorner());
474	_UnsetNeighbourCorner(fRightBottomCrossing->RightTopCorner(),
475		fRightBottomCrossing->RightBottomCorner());
476}
477
478
479void
480WindowArea::_SetToWindowCorner(Corner* corner)
481{
482	corner->status = Corner::kUsed;
483	corner->windowArea = this;
484}
485
486
487void
488WindowArea::_SetToNeighbourCorner(Corner* neighbour)
489{
490	if (neighbour->status == Corner::kNotDockable)
491		neighbour->status = Corner::kFree;
492}
493
494
495void
496WindowArea::_UnsetWindowCorner(Corner* corner)
497{
498	corner->status = Corner::kFree;
499	corner->windowArea = NULL;
500}
501
502
503void
504WindowArea::_UnsetNeighbourCorner(Corner* neighbour, Corner* opponent)
505{
506	if (neighbour->status == Corner::kFree && opponent->status != Corner::kUsed)
507		neighbour->status = Corner::kNotDockable;
508}
509
510
511void
512WindowArea::_MoveToSAT(SATWindow* triggerWindow)
513{
514	SATWindow* topWindow = TopWindow();
515	// if there is no window in the group we are done
516	if (topWindow == NULL)
517		return;
518
519	BRect frameSAT(LeftVar()->Value() - kMakePositiveOffset,
520		TopVar()->Value() - kMakePositiveOffset,
521		RightVar()->Value() - kMakePositiveOffset,
522		BottomVar()->Value() - kMakePositiveOffset);
523	topWindow->AdjustSizeLimits(frameSAT);
524
525	BRect frame = topWindow->CompleteWindowFrame();
526	float deltaToX = round(frameSAT.left - frame.left);
527	float deltaToY = round(frameSAT.top - frame.top);
528	frame.OffsetBy(deltaToX, deltaToY);
529	float deltaByX = round(frameSAT.right - frame.right);
530	float deltaByY = round(frameSAT.bottom - frame.bottom);
531
532	int32 workspace = triggerWindow->GetWindow()->CurrentWorkspace();
533	Desktop* desktop = triggerWindow->GetWindow()->Desktop();
534	desktop->MoveWindowBy(topWindow->GetWindow(), deltaToX, deltaToY,
535		workspace);
536	// Update frame to the new position
537	desktop->ResizeWindowBy(topWindow->GetWindow(), deltaByX, deltaByY);
538
539	UpdateSizeConstaints(frameSAT);
540}
541
542
543Corner::Corner()
544	:
545	status(kNotDockable),
546	windowArea(NULL)
547{
548
549}
550
551
552void
553Corner::Trace() const
554{
555	switch (status) {
556		case kFree:
557			debug_printf("free corner\n");
558			break;
559
560		case kUsed:
561		{
562			debug_printf("attached windows:\n");
563			const SATWindowList& list = windowArea->WindowList();
564			for (int i = 0; i < list.CountItems(); i++) {
565				debug_printf("- %s\n", list.ItemAt(i)->GetWindow()->Title());
566			}
567			break;
568		}
569
570		case kNotDockable:
571			debug_printf("not dockable\n");
572			break;
573	};
574}
575
576
577Crossing::Crossing(Tab* vertical, Tab* horizontal)
578	:
579	fVerticalTab(vertical),
580	fHorizontalTab(horizontal)
581{
582}
583
584
585Crossing::~Crossing()
586{
587	fVerticalTab->RemoveCrossing(this);
588	fHorizontalTab->RemoveCrossing(this);
589}
590
591
592Corner*
593Crossing::GetCorner(Corner::position_t corner) const
594{
595	return &const_cast<Corner*>(fCorners)[corner];
596}
597
598
599Corner*
600Crossing::GetOppositeCorner(Corner::position_t corner) const
601{
602	return &const_cast<Corner*>(fCorners)[3 - corner];
603}
604
605
606Tab*
607Crossing::VerticalTab() const
608{
609	return fVerticalTab;
610}
611
612
613Tab*
614Crossing::HorizontalTab() const
615{
616	return fHorizontalTab;
617}
618
619
620void
621Crossing::Trace() const
622{
623	debug_printf("left-top corner: ");
624	fCorners[Corner::kLeftTop].Trace();
625	debug_printf("right-top corner: ");
626	fCorners[Corner::kRightTop].Trace();
627	debug_printf("left-bottom corner: ");
628	fCorners[Corner::kLeftBottom].Trace();
629	debug_printf("right-bottom corner: ");
630	fCorners[Corner::kRightBottom].Trace();
631}
632
633
634Tab::Tab(SATGroup* group, Variable* variable, orientation_t orientation)
635	:
636	fGroup(group),
637	fVariable(variable),
638	fOrientation(orientation)
639{
640
641}
642
643
644Tab::~Tab()
645{
646	if (fOrientation == kVertical)
647		fGroup->_RemoveVerticalTab(this);
648	else
649		fGroup->_RemoveHorizontalTab(this);
650}
651
652
653float
654Tab::Position() const
655{
656	return (float)fVariable->Value() - kMakePositiveOffset;
657}
658
659
660void
661Tab::SetPosition(float position)
662{
663	fVariable->SetValue(position + kMakePositiveOffset);
664}
665
666
667Tab::orientation_t
668Tab::Orientation() const
669{
670	return fOrientation;
671}
672
673
674Constraint*
675Tab::Connect(Variable* variable)
676{
677	return fVariable->IsEqual(variable);
678}
679
680
681BReference<Crossing>
682Tab::AddCrossing(Tab* tab)
683{
684	if (tab->Orientation() == fOrientation)
685		return NULL;
686
687	Tab* vTab = (fOrientation == kVertical) ? this : tab;
688	Tab* hTab = (fOrientation == kHorizontal) ? this : tab;
689
690	Crossing* crossing = new (std::nothrow)Crossing(vTab, hTab);
691	if (!crossing)
692		return NULL;
693
694	if (!fCrossingList.AddItem(crossing)) {
695		return NULL;
696	}
697	if (!tab->fCrossingList.AddItem(crossing)) {
698		fCrossingList.RemoveItem(crossing);
699		return NULL;
700	}
701
702	BReference<Crossing> crossingRef(crossing, true);
703	return crossingRef;
704}
705
706
707bool
708Tab::RemoveCrossing(Crossing* crossing)
709{
710	Tab* vTab = crossing->VerticalTab();
711	Tab* hTab = crossing->HorizontalTab();
712
713	if (vTab != this && hTab != this)
714		return false;
715	fCrossingList.RemoveItem(crossing);
716
717	return true;
718}
719
720
721int32
722Tab::FindCrossingIndex(Tab* tab)
723{
724	if (fOrientation == kVertical) {
725		for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
726			if (fCrossingList.ItemAt(i)->HorizontalTab() == tab)
727				return i;
728		}
729	} else {
730		for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
731			if (fCrossingList.ItemAt(i)->VerticalTab() == tab)
732				return i;
733		}
734	}
735	return -1;
736}
737
738
739int32
740Tab::FindCrossingIndex(float pos)
741{
742	if (fOrientation == kVertical) {
743		for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
744			if (fabs(fCrossingList.ItemAt(i)->HorizontalTab()->Position() - pos)
745				< 0.0001)
746				return i;
747		}
748	} else {
749		for (int32 i = 0; i < fCrossingList.CountItems(); i++) {
750			if (fabs(fCrossingList.ItemAt(i)->VerticalTab()->Position() - pos)
751				< 0.0001)
752				return i;
753		}
754	}
755	return -1;
756}
757
758
759Crossing*
760Tab::FindCrossing(Tab* tab)
761{
762	return fCrossingList.ItemAt(FindCrossingIndex(tab));
763}
764
765
766Crossing*
767Tab::FindCrossing(float tabPosition)
768{
769	return fCrossingList.ItemAt(FindCrossingIndex(tabPosition));
770}
771
772
773const CrossingList*
774Tab::GetCrossingList() const
775{
776	return &fCrossingList;
777}
778
779
780int
781Tab::CompareFunction(const Tab* tab1, const Tab* tab2)
782{
783	if (tab1->Position() < tab2->Position())
784		return -1;
785
786	return 1;
787}
788
789
790SATGroup::SATGroup()
791	:
792	fLinearSpec(new(std::nothrow) LinearSpec()),
793	fHorizontalTabsSorted(false),
794	fVerticalTabsSorted(false),
795	fActiveWindow(NULL)
796{
797}
798
799
800SATGroup::~SATGroup()
801{
802	// Should be empty
803	if (fSATWindowList.CountItems() > 0)
804		debugger("Deleting a SATGroup which is not empty");
805	//while (fSATWindowList.CountItems() > 0)
806	//	RemoveWindow(fSATWindowList.ItemAt(0));
807
808	fLinearSpec->ReleaseReference();
809}
810
811
812bool
813SATGroup::AddWindow(SATWindow* window, Tab* left, Tab* top, Tab* right,
814	Tab* bottom)
815{
816	STRACE_SAT("SATGroup::AddWindow\n");
817
818	// first check if we have to create tabs and missing corners.
819	BReference<Tab> leftRef, rightRef, topRef, bottomRef;
820	BReference<Crossing> leftTopRef, rightTopRef, leftBottomRef, rightBottomRef;
821
822	if (left != NULL && top != NULL)
823		leftTopRef = left->FindCrossing(top);
824	if (right != NULL && top != NULL)
825		rightTopRef = right->FindCrossing(top);
826	if (left != NULL && bottom != NULL)
827		leftBottomRef = left->FindCrossing(bottom);
828	if (right != NULL && bottom != NULL)
829		rightBottomRef = right->FindCrossing(bottom);
830
831	if (left == NULL) {
832		leftRef = _AddVerticalTab();
833		left = leftRef.Get();
834	}
835	if (top == NULL) {
836		topRef = _AddHorizontalTab();
837		top = topRef.Get();
838	}
839	if (right == NULL) {
840		rightRef = _AddVerticalTab();
841		right = rightRef.Get();
842	}
843	if (bottom == NULL) {
844		bottomRef = _AddHorizontalTab();
845		bottom = bottomRef.Get();
846	}
847	if (left == NULL || top == NULL || right == NULL || bottom == NULL)
848		return false;
849
850	if (leftTopRef == NULL) {
851		leftTopRef = left->AddCrossing(top);
852		if (leftTopRef == NULL)
853			return false;
854	}
855	if (!rightTopRef) {
856		rightTopRef = right->AddCrossing(top);
857		if (!rightTopRef)
858			return false;
859	}
860	if (!leftBottomRef) {
861		leftBottomRef = left->AddCrossing(bottom);
862		if (!leftBottomRef)
863			return false;
864	}
865	if (!rightBottomRef) {
866		rightBottomRef = right->AddCrossing(bottom);
867		if (!rightBottomRef)
868			return false;
869	}
870
871	WindowArea* area = new(std::nothrow) WindowArea(leftTopRef, rightTopRef,
872		leftBottomRef, rightBottomRef);
873	if (area == NULL)
874		return false;
875	// the area register itself in our area list
876	if (area->Init(this) == false) {
877		delete area;
878		return false;
879	}
880	// delete the area if AddWindow failed / release our reference on it
881	BReference<WindowArea> areaRef(area, true);
882
883	return AddWindow(window, area);
884}
885
886
887bool
888SATGroup::AddWindow(SATWindow* window, WindowArea* area, SATWindow* after)
889{
890	if (!area->_AddWindow(window, after))
891		return false;
892
893	if (!fSATWindowList.AddItem(window)) {
894		area->_RemoveWindow(window);
895		return false;
896	}
897
898	if (!window->AddedToGroup(this, area)) {
899		area->_RemoveWindow(window);
900		fSATWindowList.RemoveItem(window);
901		return false;
902	}
903
904	return true;
905}
906
907
908bool
909SATGroup::RemoveWindow(SATWindow* window, bool stayBelowMouse)
910{
911	if (!fSATWindowList.RemoveItem(window))
912		return false;
913
914	// We need the area a little bit longer because the area could hold the
915	// last reference to the group.
916	BReference<WindowArea> area = window->GetWindowArea();
917	if (area.IsSet())
918		area->_RemoveWindow(window);
919
920	window->RemovedFromGroup(this, stayBelowMouse);
921
922	if (CountItems() >= 2)
923		WindowAt(0)->DoGroupLayout();
924
925	return true;
926}
927
928
929int32
930SATGroup::CountItems()
931{
932	return fSATWindowList.CountItems();
933}
934
935
936SATWindow*
937SATGroup::WindowAt(int32 index)
938{
939	return fSATWindowList.ItemAt(index);
940}
941
942
943SATWindow*
944SATGroup::ActiveWindow() const
945{
946	return fActiveWindow;
947}
948
949
950void
951SATGroup::SetActiveWindow(SATWindow* window)
952{
953	fActiveWindow = window;
954}
955
956
957const TabList*
958SATGroup::HorizontalTabs()
959{
960	if (!fHorizontalTabsSorted) {
961		fHorizontalTabs.SortItems(Tab::CompareFunction);
962		fHorizontalTabsSorted = true;
963	}
964	return &fHorizontalTabs;
965}
966
967
968const TabList*
969SATGroup::VerticalTabs()
970{
971	if (!fVerticalTabsSorted) {
972		fVerticalTabs.SortItems(Tab::CompareFunction);
973		fVerticalTabsSorted = true;
974	}
975	return &fVerticalTabs;
976}
977
978
979Tab*
980SATGroup::FindHorizontalTab(float position)
981{
982	return _FindTab(fHorizontalTabs, position);
983}
984
985
986Tab*
987SATGroup::FindVerticalTab(float position)
988{
989	return _FindTab(fVerticalTabs, position);
990}
991
992
993void
994SATGroup::WindowAreaRemoved(WindowArea* area)
995{
996	_SplitGroupIfNecessary(area);
997}
998
999
1000status_t
1001SATGroup::RestoreGroup(const BMessage& archive, StackAndTile* sat)
1002{
1003	// create new group
1004	SATGroup* group = new (std::nothrow)SATGroup;
1005	if (group == NULL)
1006		return B_NO_MEMORY;
1007	BReference<SATGroup> groupRef;
1008	groupRef.SetTo(group, true);
1009
1010	int32 nHTabs, nVTabs;
1011	status_t status;
1012	status = archive.FindInt32("htab_count", &nHTabs);
1013	if (status != B_OK)
1014		return status;
1015	status = archive.FindInt32("vtab_count", &nVTabs);
1016	if (status != B_OK)
1017		return status;
1018
1019	vector<BReference<Tab> > tempHTabs;
1020	for (int i = 0; i < nHTabs; i++) {
1021		BReference<Tab> tab = group->_AddHorizontalTab();
1022		if (!tab)
1023			return B_NO_MEMORY;
1024		tempHTabs.push_back(tab);
1025	}
1026	vector<BReference<Tab> > tempVTabs;
1027	for (int i = 0; i < nVTabs; i++) {
1028		BReference<Tab> tab = group->_AddVerticalTab();
1029		if (!tab)
1030			return B_NO_MEMORY;
1031		tempVTabs.push_back(tab);
1032	}
1033
1034	BMessage areaArchive;
1035	for (int32 i = 0; archive.FindMessage("area", i, &areaArchive) == B_OK;
1036		i++) {
1037		uint32 leftTab, rightTab, topTab, bottomTab;
1038		if (areaArchive.FindInt32("left_tab", (int32*)&leftTab) != B_OK
1039			|| areaArchive.FindInt32("right_tab", (int32*)&rightTab) != B_OK
1040			|| areaArchive.FindInt32("top_tab", (int32*)&topTab) != B_OK
1041			|| areaArchive.FindInt32("bottom_tab", (int32*)&bottomTab) != B_OK)
1042			return B_ERROR;
1043
1044		if (leftTab >= tempVTabs.size() || rightTab >= tempVTabs.size())
1045			return B_BAD_VALUE;
1046		if (topTab >= tempHTabs.size() || bottomTab >= tempHTabs.size())
1047			return B_BAD_VALUE;
1048
1049		Tab* left = tempVTabs[leftTab];
1050		Tab* right = tempVTabs[rightTab];
1051		Tab* top = tempHTabs[topTab];
1052		Tab* bottom = tempHTabs[bottomTab];
1053
1054		// adding windows to area
1055		uint64 windowId;
1056		SATWindow* prevWindow = NULL;
1057		for (int32 i = 0; areaArchive.FindInt64("window", i,
1058			(int64*)&windowId) == B_OK; i++) {
1059			SATWindow* window = sat->FindSATWindow(windowId);
1060			if (!window)
1061				continue;
1062
1063			if (prevWindow == NULL) {
1064				if (!group->AddWindow(window, left, top, right, bottom))
1065					continue;
1066				prevWindow = window;
1067			} else {
1068				if (!prevWindow->StackWindow(window))
1069					continue;
1070				prevWindow = window;
1071			}
1072		}
1073	}
1074	return B_OK;
1075}
1076
1077
1078status_t
1079SATGroup::ArchiveGroup(BMessage& archive)
1080{
1081	archive.AddInt32("htab_count", fHorizontalTabs.CountItems());
1082	archive.AddInt32("vtab_count", fVerticalTabs.CountItems());
1083
1084	for (int i = 0; i < fWindowAreaList.CountItems(); i++) {
1085		WindowArea* area = fWindowAreaList.ItemAt(i);
1086		int32 leftTab = fVerticalTabs.IndexOf(area->LeftTab());
1087		int32 rightTab = fVerticalTabs.IndexOf(area->RightTab());
1088		int32 topTab = fHorizontalTabs.IndexOf(area->TopTab());
1089		int32 bottomTab = fHorizontalTabs.IndexOf(area->BottomTab());
1090
1091		BMessage areaMessage;
1092		areaMessage.AddInt32("left_tab", leftTab);
1093		areaMessage.AddInt32("right_tab", rightTab);
1094		areaMessage.AddInt32("top_tab", topTab);
1095		areaMessage.AddInt32("bottom_tab", bottomTab);
1096
1097		const SATWindowList& windowList = area->WindowList();
1098		for (int a = 0; a < windowList.CountItems(); a++)
1099			areaMessage.AddInt64("window", windowList.ItemAt(a)->Id());
1100
1101		archive.AddMessage("area", &areaMessage);
1102	}
1103	return B_OK;
1104}
1105
1106
1107BReference<Tab>
1108SATGroup::_AddHorizontalTab(float position)
1109{
1110	if (fLinearSpec == NULL)
1111		return NULL;
1112	Variable* variable = fLinearSpec->AddVariable();
1113	if (variable == NULL)
1114		return NULL;
1115
1116	Tab* tab = new (std::nothrow)Tab(this, variable, Tab::kHorizontal);
1117	if (tab == NULL)
1118		return NULL;
1119	BReference<Tab> tabRef(tab, true);
1120
1121	if (!fHorizontalTabs.AddItem(tab))
1122		return NULL;
1123
1124	fHorizontalTabsSorted = false;
1125	tabRef->SetPosition(position);
1126	return tabRef;
1127}
1128
1129
1130BReference<Tab>
1131SATGroup::_AddVerticalTab(float position)
1132{
1133	if (fLinearSpec == NULL)
1134		return NULL;
1135	Variable* variable = fLinearSpec->AddVariable();
1136	if (variable == NULL)
1137		return NULL;
1138
1139	Tab* tab = new (std::nothrow)Tab(this, variable, Tab::kVertical);
1140	if (tab == NULL)
1141		return NULL;
1142	BReference<Tab> tabRef(tab, true);
1143
1144	if (!fVerticalTabs.AddItem(tab))
1145		return NULL;
1146
1147	fVerticalTabsSorted = false;
1148	tabRef->SetPosition(position);
1149	return tabRef;
1150}
1151
1152
1153bool
1154SATGroup::_RemoveHorizontalTab(Tab* tab)
1155{
1156	if (!fHorizontalTabs.RemoveItem(tab))
1157		return false;
1158	fHorizontalTabsSorted = false;
1159	// don't delete the tab it is reference counted
1160	return true;
1161}
1162
1163
1164bool
1165SATGroup::_RemoveVerticalTab(Tab* tab)
1166{
1167	if (!fVerticalTabs.RemoveItem(tab))
1168		return false;
1169	fVerticalTabsSorted = false;
1170	// don't delete the tab it is reference counted
1171	return true;
1172}
1173
1174
1175Tab*
1176SATGroup::_FindTab(const TabList& list, float position)
1177{
1178	for (int i = 0; i < list.CountItems(); i++)
1179		if (fabs(list.ItemAt(i)->Position() - position) < 0.00001)
1180			return list.ItemAt(i);
1181
1182	return NULL;
1183}
1184
1185
1186void
1187SATGroup::_SplitGroupIfNecessary(WindowArea* removedArea)
1188{
1189	// if there are windows stacked in the area we don't need to split
1190	if (removedArea == NULL || removedArea->WindowList().CountItems() > 1)
1191		return;
1192
1193	WindowAreaList neighbourWindows;
1194
1195	_FillNeighbourList(neighbourWindows, removedArea);
1196
1197	bool ownGroupProcessed = false;
1198	WindowAreaList newGroup;
1199	while (_FindConnectedGroup(neighbourWindows, removedArea, newGroup)) {
1200		STRACE_SAT("Connected group found; %i window(s)\n",
1201			(int)newGroup.CountItems());
1202		if (newGroup.CountItems() == 1
1203			&& newGroup.ItemAt(0)->WindowList().CountItems() == 1) {
1204			SATWindow* window = newGroup.ItemAt(0)->WindowList().ItemAt(0);
1205			RemoveWindow(window);
1206			_EnsureGroupIsOnScreen(window->GetGroup());
1207		} else if (ownGroupProcessed)
1208			_SpawnNewGroup(newGroup);
1209		else {
1210			_EnsureGroupIsOnScreen(this);
1211			ownGroupProcessed = true;
1212		}
1213
1214		newGroup.MakeEmpty();
1215	}
1216}
1217
1218
1219void
1220SATGroup::_FillNeighbourList(WindowAreaList& neighbourWindows,
1221	WindowArea* area)
1222{
1223	_LeftNeighbours(neighbourWindows, area);
1224	_RightNeighbours(neighbourWindows, area);
1225	_TopNeighbours(neighbourWindows, area);
1226	_BottomNeighbours(neighbourWindows, area);
1227}
1228
1229
1230void
1231SATGroup::_LeftNeighbours(WindowAreaList& neighbourWindows, WindowArea* parent)
1232{
1233	float startPos = parent->LeftTopCrossing()->HorizontalTab()->Position();
1234	float endPos = parent->LeftBottomCrossing()->HorizontalTab()->Position();
1235
1236	Tab* tab = parent->LeftTopCrossing()->VerticalTab();
1237	const CrossingList* crossingList = tab->GetCrossingList();
1238	for (int i = 0; i < crossingList->CountItems(); i++) {
1239		Corner* corner = crossingList->ItemAt(i)->LeftTopCorner();
1240		if (corner->status != Corner::kUsed)
1241			continue;
1242
1243		WindowArea* area = corner->windowArea;
1244		float pos1 = area->LeftTopCrossing()->HorizontalTab()->Position();
1245		float pos2 = area->LeftBottomCrossing()->HorizontalTab()->Position();
1246
1247		if (pos1 < endPos && pos2 > startPos)
1248			neighbourWindows.AddItem(area);
1249
1250		if (pos2 > endPos)
1251			break;
1252	}
1253}
1254
1255
1256void
1257SATGroup::_TopNeighbours(WindowAreaList& neighbourWindows, WindowArea* parent)
1258{
1259	float startPos = parent->LeftTopCrossing()->VerticalTab()->Position();
1260	float endPos = parent->RightTopCrossing()->VerticalTab()->Position();
1261
1262	Tab* tab = parent->LeftTopCrossing()->HorizontalTab();
1263	const CrossingList* crossingList = tab->GetCrossingList();
1264	for (int i = 0; i < crossingList->CountItems(); i++) {
1265		Corner* corner = crossingList->ItemAt(i)->LeftTopCorner();
1266		if (corner->status != Corner::kUsed)
1267			continue;
1268
1269		WindowArea* area = corner->windowArea;
1270		float pos1 = area->LeftTopCrossing()->VerticalTab()->Position();
1271		float pos2 = area->RightTopCrossing()->VerticalTab()->Position();
1272
1273		if (pos1 < endPos && pos2 > startPos)
1274			neighbourWindows.AddItem(area);
1275
1276		if (pos2 > endPos)
1277			break;
1278	}
1279}
1280
1281
1282void
1283SATGroup::_RightNeighbours(WindowAreaList& neighbourWindows, WindowArea* parent)
1284{
1285	float startPos = parent->RightTopCrossing()->HorizontalTab()->Position();
1286	float endPos = parent->RightBottomCrossing()->HorizontalTab()->Position();
1287
1288	Tab* tab = parent->RightTopCrossing()->VerticalTab();
1289	const CrossingList* crossingList = tab->GetCrossingList();
1290	for (int i = 0; i < crossingList->CountItems(); i++) {
1291		Corner* corner = crossingList->ItemAt(i)->RightTopCorner();
1292		if (corner->status != Corner::kUsed)
1293			continue;
1294
1295		WindowArea* area = corner->windowArea;
1296		float pos1 = area->RightTopCrossing()->HorizontalTab()->Position();
1297		float pos2 = area->RightBottomCrossing()->HorizontalTab()->Position();
1298
1299		if (pos1 < endPos && pos2 > startPos)
1300			neighbourWindows.AddItem(area);
1301
1302		if (pos2 > endPos)
1303			break;
1304	}
1305}
1306
1307
1308void
1309SATGroup::_BottomNeighbours(WindowAreaList& neighbourWindows,
1310	WindowArea* parent)
1311{
1312	float startPos = parent->LeftBottomCrossing()->VerticalTab()->Position();
1313	float endPos = parent->RightBottomCrossing()->VerticalTab()->Position();
1314
1315	Tab* tab = parent->LeftBottomCrossing()->HorizontalTab();
1316	const CrossingList* crossingList = tab->GetCrossingList();
1317	for (int i = 0; i < crossingList->CountItems(); i++) {
1318		Corner* corner = crossingList->ItemAt(i)->LeftBottomCorner();
1319		if (corner->status != Corner::kUsed)
1320			continue;
1321
1322		WindowArea* area = corner->windowArea;
1323		float pos1 = area->LeftBottomCrossing()->VerticalTab()->Position();
1324		float pos2 = area->RightBottomCrossing()->VerticalTab()->Position();
1325
1326		if (pos1 < endPos && pos2 > startPos)
1327			neighbourWindows.AddItem(area);
1328
1329		if (pos2 > endPos)
1330			break;
1331	}
1332}
1333
1334
1335bool
1336SATGroup::_FindConnectedGroup(WindowAreaList& seedList, WindowArea* removedArea,
1337	WindowAreaList& newGroup)
1338{
1339	if (seedList.CountItems() == 0)
1340		return false;
1341
1342	WindowArea* area = seedList.RemoveItemAt(0);
1343	newGroup.AddItem(area);
1344
1345	_FollowSeed(area, removedArea, seedList, newGroup);
1346	return true;
1347}
1348
1349
1350void
1351SATGroup::_FollowSeed(WindowArea* area, WindowArea* veto,
1352	WindowAreaList& seedList, WindowAreaList& newGroup)
1353{
1354	WindowAreaList neighbours;
1355	_FillNeighbourList(neighbours, area);
1356	for (int i = 0; i < neighbours.CountItems(); i++) {
1357		WindowArea* currentArea = neighbours.ItemAt(i);
1358		if (currentArea != veto && !newGroup.HasItem(currentArea)) {
1359			newGroup.AddItem(currentArea);
1360			// if we get a area from the seed list it is not a seed any more
1361			seedList.RemoveItem(currentArea);
1362		} else {
1363			// don't _FollowSeed of invalid areas
1364			neighbours.RemoveItemAt(i);
1365			i--;
1366		}
1367	}
1368
1369	for (int i = 0; i < neighbours.CountItems(); i++)
1370		_FollowSeed(neighbours.ItemAt(i), veto, seedList, newGroup);
1371}
1372
1373
1374void
1375SATGroup::_SpawnNewGroup(const WindowAreaList& newGroup)
1376{
1377	STRACE_SAT("SATGroup::_SpawnNewGroup\n");
1378	SATGroup* group = new (std::nothrow)SATGroup;
1379	if (group == NULL)
1380		return;
1381	BReference<SATGroup> groupRef;
1382	groupRef.SetTo(group, true);
1383
1384	for (int i = 0; i < newGroup.CountItems(); i++)
1385		newGroup.ItemAt(i)->PropagateToGroup(group);
1386
1387	_EnsureGroupIsOnScreen(group);
1388}
1389
1390
1391const float kMinOverlap = 50;
1392const float kMoveToScreen = 75;
1393
1394
1395void
1396SATGroup::_EnsureGroupIsOnScreen(SATGroup* group)
1397{
1398	STRACE_SAT("SATGroup::_EnsureGroupIsOnScreen\n");
1399	if (group == NULL || group->CountItems() < 1)
1400		return;
1401
1402	SATWindow* window = group->WindowAt(0);
1403	Desktop* desktop = window->GetWindow()->Desktop();
1404	if (desktop == NULL)
1405		return;
1406
1407	const float kBigDistance = 1E+10;
1408
1409	float minLeftDistance = kBigDistance;
1410	BRect leftRect;
1411	float minTopDistance = kBigDistance;
1412	BRect topRect;
1413	float minRightDistance = kBigDistance;
1414	BRect rightRect;
1415	float minBottomDistance = kBigDistance;
1416	BRect bottomRect;
1417
1418	BRect screen = window->GetWindow()->Screen()->Frame();
1419	BRect reducedScreen = screen;
1420	reducedScreen.InsetBy(kMinOverlap, kMinOverlap);
1421
1422	for (int i = 0; i < group->CountItems(); i++) {
1423		SATWindow* window = group->WindowAt(i);
1424		BRect frame = window->CompleteWindowFrame();
1425		if (reducedScreen.Intersects(frame))
1426			return;
1427
1428		if (frame.right < screen.left + kMinOverlap) {
1429			float dist = fabs(screen.left - frame.right);
1430			if (dist < minLeftDistance) {
1431				minLeftDistance = dist;
1432				leftRect = frame;
1433			} else if (dist == minLeftDistance)
1434				leftRect = leftRect | frame;
1435		}
1436		if (frame.top > screen.bottom - kMinOverlap) {
1437			float dist = fabs(frame.top - screen.bottom);
1438			if (dist < minBottomDistance) {
1439				minBottomDistance = dist;
1440				bottomRect = frame;
1441			} else if (dist == minBottomDistance)
1442				bottomRect = bottomRect | frame;
1443		}
1444		if (frame.left > screen.right - kMinOverlap) {
1445			float dist = fabs(frame.left - screen.right);
1446			if (dist < minRightDistance) {
1447				minRightDistance = dist;
1448				rightRect = frame;
1449			} else if (dist == minRightDistance)
1450				rightRect = rightRect | frame;
1451		}
1452		if (frame.bottom < screen.top + kMinOverlap) {
1453			float dist = fabs(frame.bottom - screen.top);
1454			if (dist < minTopDistance) {
1455				minTopDistance = dist;
1456				topRect = frame;
1457			} else if (dist == minTopDistance)
1458				topRect = topRect | frame;
1459		}
1460	}
1461
1462	BPoint offset;
1463	if (minLeftDistance < kBigDistance) {
1464		offset.x = screen.left - leftRect.right + kMoveToScreen;
1465		_CallculateYOffset(offset, leftRect, screen);
1466	} else if (minTopDistance < kBigDistance) {
1467		offset.y = screen.top - topRect.bottom + kMoveToScreen;
1468		_CallculateXOffset(offset, topRect, screen);
1469	} else if (minRightDistance < kBigDistance) {
1470		offset.x = screen.right - rightRect.left - kMoveToScreen;
1471		_CallculateYOffset(offset, rightRect, screen);
1472	} else if (minBottomDistance < kBigDistance) {
1473		offset.y = screen.bottom - bottomRect.top - kMoveToScreen;
1474		_CallculateXOffset(offset, bottomRect, screen);
1475	}
1476
1477	if (offset.x == 0. && offset.y == 0.)
1478		return;
1479	STRACE_SAT("move group back to screen: offset x: %f offset y: %f\n",
1480		offset.x, offset.y);
1481
1482	desktop->MoveWindowBy(window->GetWindow(), offset.x, offset.y);
1483	window->DoGroupLayout();
1484}
1485
1486
1487void
1488SATGroup::_CallculateXOffset(BPoint& offset, BRect& frame, BRect& screen)
1489{
1490	if (frame.right < screen.left + kMinOverlap)
1491		offset.x = screen.left - frame.right + kMoveToScreen;
1492	else if (frame.left > screen.right - kMinOverlap)
1493		offset.x = screen.right - frame.left - kMoveToScreen;
1494}
1495
1496
1497void
1498SATGroup::_CallculateYOffset(BPoint& offset, BRect& frame, BRect& screen)
1499{
1500	if (frame.top > screen.bottom - kMinOverlap)
1501		offset.y = screen.bottom - frame.top - kMoveToScreen;
1502	else if (frame.bottom < screen.top + kMinOverlap)
1503		offset.y = screen.top - frame.bottom + kMoveToScreen;
1504}
1505