/* * Copyright 2010, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Clemens Zeidler */ #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; }