⛏️ index : haiku.git

/*
 * Copyright 2010, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Clemens Zeidler <haiku@clemens-zeidler.de>
 */

#include "Tiling.h"


#include "SATWindow.h"
#include "StackAndTile.h"
#include "Window.h"


using namespace std;


//#define DEBUG_TILING

#ifdef DEBUG_TILING
#	define STRACE_TILING(x...) debug_printf("SAT Tiling: "x)
#else
#	define STRACE_TILING(x...) ;
#endif


SATTiling::SATTiling(SATWindow* window)
	:
	fSATWindow(window),
	fFreeAreaGroup(NULL)
{
	_ResetSearchResults();
}


SATTiling::~SATTiling()
{

}


bool
SATTiling::FindSnappingCandidates(SATGroup* group)
{
	_ResetSearchResults();

	if (_IsTileableWindow(fSATWindow->GetWindow()) == false
		|| (group->CountItems() == 1
			&& _IsTileableWindow(group->WindowAt(0)->GetWindow()) == false))
		return false;
	if (fSATWindow->GetGroup() == group)
		return false;

	if (_FindFreeAreaInGroup(group)) {
		fFreeAreaGroup = group;
		_HighlightWindows(fFreeAreaGroup, true);
		return true;
	}

	return false;
}


bool
SATTiling::JoinCandidates()
{
	if (!fFreeAreaGroup)
		return false;

	if (!fFreeAreaGroup->AddWindow(fSATWindow, fFreeAreaLeft, fFreeAreaTop,
		fFreeAreaRight, fFreeAreaBottom)) {
		_ResetSearchResults();
		return false;
	}

	fFreeAreaGroup->WindowAt(0)->DoGroupLayout();

	_ResetSearchResults();
	return true;
}


void
SATTiling::WindowLookChanged(window_look look)
{
	SATGroup* group = fSATWindow->GetGroup();
	if (group == NULL)
		return;
	if (_IsTileableWindow(fSATWindow->GetWindow()) == false)
		group->RemoveWindow(fSATWindow);
}


bool
SATTiling::_IsTileableWindow(Window* window)
{
	if (window->Look() == B_DOCUMENT_WINDOW_LOOK)
		return true;
	if (window->Look() == B_TITLED_WINDOW_LOOK)
		return true;
	if (window->Look() == B_FLOATING_WINDOW_LOOK)
		return true;
	if (window->Look() == B_MODAL_WINDOW_LOOK)
		return true;
	if (window->Look() == B_BORDERED_WINDOW_LOOK)
		return true;
	return false;	
}


bool
SATTiling::_FindFreeAreaInGroup(SATGroup* group)
{
	if (_FindFreeAreaInGroup(group, Corner::kLeftTop))
		return true;
	if (_FindFreeAreaInGroup(group, Corner::kRightTop))
		return true;
	if (_FindFreeAreaInGroup(group, Corner::kLeftBottom))
		return true;
	if (_FindFreeAreaInGroup(group, Corner::kRightBottom))
		return true;

	return false;
}


bool
SATTiling::_FindFreeAreaInGroup(SATGroup* group, Corner::position_t cor)
{
	BRect windowFrame = fSATWindow->CompleteWindowFrame();

	const TabList* verticalTabs = group->VerticalTabs();
	for (int i = 0; i < verticalTabs->CountItems(); i++) {
		Tab* tab = verticalTabs->ItemAt(i);
		const CrossingList* crossingList = tab->GetCrossingList();
		for (int c = 0; c < crossingList->CountItems(); c++) {
			Crossing* crossing = crossingList->ItemAt(c);
			if (_InteresstingCrossing(crossing, cor, windowFrame)) {
				if (_FindFreeArea(group, crossing, cor, windowFrame)) {
					STRACE_TILING("SATTiling: free area found; corner %i\n",
						cor);
					return true;
				}
			}
		}
	}

	return false;
}


const float kNoMatch = 999.f;
const float kMaxMatchingDistance = 12.f;


bool
SATTiling::_InteresstingCrossing(Crossing* crossing,
	Corner::position_t cor, BRect& windowFrame)
{
	const Corner* corner = crossing->GetOppositeCorner(cor);
	if (corner->status != Corner::kFree)
		return false;

	float hTabPosition = crossing->HorizontalTab()->Position();
	float vTabPosition = crossing->VerticalTab()->Position();
	float hBorder = 0., vBorder = 0.;
	float vDistance = -1., hDistance = -1.;
	bool windowAtH = false, windowAtV = false;
	switch (cor) {
		case Corner::kLeftTop:
			if (crossing->RightBottomCorner()->status == Corner::kUsed)
				return false;
			vBorder = windowFrame.left;
			hBorder = windowFrame.top;
			if (crossing->LeftBottomCorner()->status == Corner::kUsed)
				windowAtV = true;
			if (crossing->RightTopCorner()->status == Corner::kUsed)
				windowAtH = true;
			vDistance = vTabPosition - vBorder;
			hDistance = hTabPosition - hBorder;
			break;
		case Corner::kRightTop:
			if (crossing->LeftBottomCorner()->status == Corner::kUsed)
				return false;
			vBorder = windowFrame.right;
			hBorder = windowFrame.top;
			if (crossing->RightBottomCorner()->status == Corner::kUsed)
				windowAtV = true;
			if (crossing->LeftTopCorner()->status == Corner::kUsed)
				windowAtH = true;
			vDistance = vBorder - vTabPosition;
			hDistance = hTabPosition - hBorder;
			break;
		case Corner::kLeftBottom:
			if (crossing->RightTopCorner()->status == Corner::kUsed)
				return false;
			vBorder = windowFrame.left;
			hBorder = windowFrame.bottom;
			if (crossing->LeftTopCorner()->status == Corner::kUsed)
				windowAtV = true;
			if (crossing->RightBottomCorner()->status == Corner::kUsed)
				windowAtH = true;
			vDistance = vTabPosition - vBorder;
			hDistance = hBorder - hTabPosition;
			break;
		case Corner::kRightBottom:
			if (crossing->LeftTopCorner()->status == Corner::kUsed)
				return false;
			vBorder = windowFrame.right;
			hBorder = windowFrame.bottom;
			if (crossing->RightTopCorner()->status == Corner::kUsed)
				windowAtV = true;
			if (crossing->LeftBottomCorner()->status == Corner::kUsed)
				windowAtH = true;
			vDistance = vBorder - vTabPosition;
			hDistance = hBorder - hTabPosition;
			break;
	};

	bool hValid = false;
	if (windowAtH && fabs(hDistance) < kMaxMatchingDistance
		&& vDistance  < kMaxMatchingDistance)
		hValid = true;
	bool vValid = false;
	if (windowAtV && fabs(vDistance) < kMaxMatchingDistance
		&& hDistance  < kMaxMatchingDistance)
		vValid = true;
	if (!hValid && !vValid)
		return false;

	return true;
};


const float kBigAreaError = 1E+17;


bool
SATTiling::_FindFreeArea(SATGroup* group, const Crossing* crossing,
	Corner::position_t corner, BRect& windowFrame)
{
	fFreeAreaLeft = fFreeAreaRight = fFreeAreaTop = fFreeAreaBottom = NULL;

	const TabList* hTabs = group->HorizontalTabs();
	const TabList* vTabs = group->VerticalTabs();
	int32 hIndex = hTabs->IndexOf(crossing->HorizontalTab());
	if (hIndex < 0)
		return false;
	int32 vIndex = vTabs->IndexOf(crossing->VerticalTab());
	if (vIndex < 0)
		return false;

	Tab** endHTab = NULL, **endVTab = NULL;
	int8 vSearchDirection = 1, hSearchDirection = 1;
	switch (corner) {
		case Corner::kLeftTop:
			fFreeAreaLeft = crossing->VerticalTab();
			fFreeAreaTop = crossing->HorizontalTab();
			endHTab = &fFreeAreaBottom;
			endVTab = &fFreeAreaRight;
			vSearchDirection = 1;
			hSearchDirection = 1;
			break;
		case Corner::kRightTop:
			fFreeAreaRight = crossing->VerticalTab();
			fFreeAreaTop = crossing->HorizontalTab();
			endHTab = &fFreeAreaBottom;
			endVTab = &fFreeAreaLeft;
			vSearchDirection = -1;
			hSearchDirection = 1;
			break;
		case Corner::kLeftBottom:
			fFreeAreaLeft = crossing->VerticalTab();
			fFreeAreaBottom = crossing->HorizontalTab();
			endHTab = &fFreeAreaTop;
			endVTab = &fFreeAreaRight;
			vSearchDirection = 1;
			hSearchDirection = -1;
			break;
		case Corner::kRightBottom:
			fFreeAreaRight = crossing->VerticalTab();
			fFreeAreaBottom = crossing->HorizontalTab();
			endHTab = &fFreeAreaTop;
			endVTab = &fFreeAreaLeft;
			vSearchDirection = -1;
			hSearchDirection = -1;
			break;
	};

	Tab* bestLeftTab = NULL, *bestRightTab = NULL, *bestTopTab = NULL,
		*bestBottomTab = NULL;
	float bestError = kBigAreaError;
	float error;
	bool stop = false;
	bool found = false;
	int32 v = vIndex;
	do {
		v += vSearchDirection;
		*endVTab = vTabs->ItemAt(v);
		int32 h = hIndex;
		do {
			h += hSearchDirection;
			*endHTab = hTabs->ItemAt(h);
			if (!_CheckArea(group, corner, windowFrame, error)) {
				if (h == hIndex + hSearchDirection)
					stop = true;
				break;
			}
			found = true;
			if (error < bestError) {
				bestError = error;
				bestLeftTab = fFreeAreaLeft;
				bestRightTab = fFreeAreaRight;
				bestTopTab = fFreeAreaTop;
				bestBottomTab = fFreeAreaBottom;
			}
		} while (*endHTab);
		if (stop)
			break;
	} while (*endVTab);
	if (!found)
		return false;

	fFreeAreaLeft = bestLeftTab;
	fFreeAreaRight = bestRightTab;
	fFreeAreaTop = bestTopTab;
	fFreeAreaBottom = bestBottomTab;

	return true;
}


bool
SATTiling::_HasOverlapp(SATGroup* group)
{
	BRect areaRect = _FreeAreaSize();
	areaRect.InsetBy(1., 1.);

	const TabList* hTabs = group->HorizontalTabs();
	for (int32 h = 0; h < hTabs->CountItems(); h++) {
		Tab* hTab = hTabs->ItemAt(h);
		if (hTab->Position() >= areaRect.bottom)
			return false;
		const CrossingList* crossings = hTab->GetCrossingList();
		for (int32 i = 0; i <  crossings->CountItems(); i++) {
			Crossing* leftTopCrossing = crossings->ItemAt(i);
			Tab* vTab = leftTopCrossing->VerticalTab();
			if (vTab->Position() > areaRect.right)
				continue;
			Corner* corner = leftTopCrossing->RightBottomCorner();
			if (corner->status != Corner::kUsed)
				continue;
			BRect rect = corner->windowArea->Frame();
			if (areaRect.Intersects(rect))
				return true;
		}
	}
	return false;
}


bool
SATTiling::_CheckArea(SATGroup* group, Corner::position_t corner,
	BRect& windowFrame, float& error)
{
	error = kBigAreaError;
	if (!_CheckMinFreeAreaSize())
		return false;
	// check if corner is in the free area
	if (!_IsCornerInFreeArea(corner, windowFrame))
		return false;
	
	error = _FreeAreaError(windowFrame);
	if (!_HasOverlapp(group))
		return true;
	return false;
}


bool
SATTiling::_CheckMinFreeAreaSize()
{
	// check if the area has a minimum size
	if (fFreeAreaLeft && fFreeAreaRight
		&& fFreeAreaRight->Position() - fFreeAreaLeft->Position()
			< 2 * kMaxMatchingDistance)
		return false;
	if (fFreeAreaBottom && fFreeAreaTop
		&& fFreeAreaBottom->Position() - fFreeAreaTop->Position()
			< 2 * kMaxMatchingDistance)
		return false;
	return true;
}


float
SATTiling::_FreeAreaError(BRect& windowFrame)
{
	const float kEndTabError = 9999999;
	float error = 0.;
	if (fFreeAreaLeft && fFreeAreaRight)
		error += pow(fFreeAreaRight->Position() - fFreeAreaLeft->Position()
			- windowFrame.Width(), 2);
	else
		error += kEndTabError;
	if (fFreeAreaBottom && fFreeAreaTop)
		error += pow(fFreeAreaBottom->Position() - fFreeAreaTop->Position()
			- windowFrame.Height(), 2);
	else
		error += kEndTabError;
	return error;
}


bool
SATTiling::_IsCornerInFreeArea(Corner::position_t corner, BRect& frame)
{
	BRect freeArea = _FreeAreaSize();

	switch (corner) {
		case Corner::kLeftTop:
			if (freeArea.bottom - kMaxMatchingDistance > frame.top
				&& freeArea.right - kMaxMatchingDistance > frame.left)
				return true;
			break;
		case Corner::kRightTop:
			if (freeArea.bottom - kMaxMatchingDistance > frame.top
				&& freeArea.left + kMaxMatchingDistance < frame.right)
				return true;
			break;
		case Corner::kLeftBottom:
			if (freeArea.top + kMaxMatchingDistance < frame.bottom
				&& freeArea.right - kMaxMatchingDistance > frame.left)
				return true;
			break;
		case Corner::kRightBottom:
			if (freeArea.top + kMaxMatchingDistance < frame.bottom
				&& freeArea.left + kMaxMatchingDistance < frame.right)
				return true;
			break;
	}

	return false;
}


BRect
SATTiling::_FreeAreaSize()
{
	// not to big to be be able to add/sub small float values
	const float kBigValue = 9999999.;
	float left = fFreeAreaLeft ? fFreeAreaLeft->Position() : -kBigValue;
	float right = fFreeAreaRight ? fFreeAreaRight->Position() : kBigValue;
	float top = fFreeAreaTop ? fFreeAreaTop->Position() : -kBigValue;
	float bottom = fFreeAreaBottom ? fFreeAreaBottom->Position() : kBigValue;
	return BRect(left, top, right, bottom);
}


void
SATTiling::_HighlightWindows(SATGroup* group, bool highlight)
{
	const TabList* hTabs = group->HorizontalTabs();
	const TabList* vTabs = group->VerticalTabs();
	// height light windows at all four sites
	bool leftWindowsFound = _SearchHighlightWindow(fFreeAreaLeft, fFreeAreaTop, fFreeAreaBottom, hTabs,
		fFreeAreaTop ? Corner::kLeftBottom : Corner::kLeftTop,
		Decorator::REGION_RIGHT_BORDER, highlight);

	bool topWindowsFound = _SearchHighlightWindow(fFreeAreaTop, fFreeAreaLeft, fFreeAreaRight, vTabs,
		fFreeAreaLeft ? Corner::kRightTop : Corner::kLeftTop,
		Decorator::REGION_BOTTOM_BORDER, highlight);

	bool rightWindowsFound = _SearchHighlightWindow(fFreeAreaRight, fFreeAreaTop, fFreeAreaBottom, hTabs,
		fFreeAreaTop ? Corner::kRightBottom : Corner::kRightTop,
		Decorator::REGION_LEFT_BORDER, highlight);

	bool bottomWindowsFound = _SearchHighlightWindow(fFreeAreaBottom, fFreeAreaLeft, fFreeAreaRight,
		vTabs, fFreeAreaLeft ? Corner::kRightBottom : Corner::kLeftBottom,
		Decorator::REGION_TOP_BORDER, highlight);

	if (leftWindowsFound)
		fSATWindow->HighlightBorders(Decorator::REGION_LEFT_BORDER, highlight);
	if (topWindowsFound)
		fSATWindow->HighlightBorders(Decorator::REGION_TOP_BORDER, highlight);
	if (rightWindowsFound)
		fSATWindow->HighlightBorders(Decorator::REGION_RIGHT_BORDER, highlight);
	if (bottomWindowsFound) {
		fSATWindow->HighlightBorders(Decorator::REGION_BOTTOM_BORDER,
			highlight);
	}
}


bool
SATTiling::_SearchHighlightWindow(Tab* tab, Tab* firstOrthTab,
	Tab* secondOrthTab, const TabList* orthTabs, Corner::position_t areaCorner,
	Decorator::Region region, bool highlight)
{
	bool windowsFound = false;

	if (!tab)
		return false;

	int8 searchDir = 1;
	Tab* startOrthTab = NULL;
	Tab* endOrthTab = NULL;
	if (firstOrthTab) {
		searchDir = 1;
		startOrthTab = firstOrthTab;
		endOrthTab = secondOrthTab;
	}
	else if (secondOrthTab) {
		searchDir = -1;
		startOrthTab = secondOrthTab;
		endOrthTab = firstOrthTab;
	}
	else
		return false;

	int32 index = orthTabs->IndexOf(startOrthTab);
	if (index < 0)
		return false;

	for (; index < orthTabs->CountItems() && index >= 0; index += searchDir) {
		Tab* orthTab = orthTabs->ItemAt(index);
		if (orthTab == endOrthTab)
 			break;
		Crossing* crossing = tab->FindCrossing(orthTab);
		if (!crossing)
			continue;
		Corner* corner = crossing->GetCorner(areaCorner);
		if (corner->windowArea) {
			_HighlightWindows(corner->windowArea, region,  highlight);
			windowsFound = true;
		}
	}
	return windowsFound;
}


void
SATTiling::_HighlightWindows(WindowArea* area, Decorator::Region region,
	bool highlight)
{
	const SATWindowList& list = area->LayerOrder();
	SATWindow* topWindow = list.ItemAt(list.CountItems() - 1);
	if (topWindow == NULL)
		return;
	topWindow->HighlightBorders(region, highlight);
}


void
SATTiling::_ResetSearchResults()
{
	if (!fFreeAreaGroup)
		return;

	_HighlightWindows(fFreeAreaGroup, false);
	fFreeAreaGroup = NULL;
}