/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich SPDX-FileCopyrightText: 1997-2002 Cristian Tibirna SPDX-FileCopyrightText: 2003 Lubos Lunak SPDX-FileCopyrightText: 2022 Natalie Clarius SPDX-License-Identifier: GPL-2.0-or-later */ #include "placement.h" #ifndef KCMRULES #include "cursor.h" #include "options.h" #include "rules.h" #include "virtualdesktops.h" #include "workspace.h" #if KWIN_BUILD_X11 #include "x11window.h" #endif #endif #include "window.h" #include #include namespace KWin { #ifndef KCMRULES Placement::Placement() { reinitCascading(); } /** * Places the client \a c according to the workspace's layout policy */ void Placement::place(Window *c, const QRectF &area) { PlacementPolicy policy = c->rules()->checkPlacement(PlacementDefault); if (policy != PlacementDefault) { place(c, area, policy); return; } if (c->isUtility()) { placeUtility(c, area.toRect(), options->placement()); } else if (c->isDialog()) { placeDialog(c, area.toRect(), options->placement()); } else if (c->isSplash()) { placeOnMainWindow(c, area.toRect()); // on mainwindow, if any, otherwise centered } else if (c->isOnScreenDisplay() || c->isNotification() || c->isCriticalNotification()) { placeOnScreenDisplay(c, area.toRect()); } else if (c->isTransient() && c->hasTransientPlacementHint()) { placeTransient(c); } else if (c->isTransient() && c->surface()) { placeDialog(c, area.toRect(), options->placement()); } else { place(c, area, options->placement()); } } void Placement::place(Window *c, const QRectF &area, PlacementPolicy policy, PlacementPolicy nextPlacement) { if (policy == PlacementUnknown || policy == PlacementDefault) { policy = options->placement(); } switch (policy) { case PlacementNone: return; case PlacementRandom: placeAtRandom(c, area.toRect(), nextPlacement); break; case PlacementCentered: placeCentered(c, area, nextPlacement); break; case PlacementZeroCornered: placeZeroCornered(c, area.toRect(), nextPlacement); break; case PlacementUnderMouse: placeUnderMouse(c, area.toRect(), nextPlacement); break; case PlacementOnMainWindow: placeOnMainWindow(c, area.toRect(), nextPlacement); break; case PlacementMaximizing: placeMaximizing(c, area.toRect(), nextPlacement); break; default: placeSmart(c, area, nextPlacement); } } /** * Place the client \a c according to a simply "random" placement algorithm. */ void Placement::placeAtRandom(Window *c, const QRect &area, PlacementPolicy /*next*/) { Q_ASSERT(area.isValid()); const QSizeF size = c->size(); if (size.isEmpty()) { return; } const int step = 24; static int px = step; static int py = 2 * step; int tx, ty; if (px < area.x()) { px = area.x(); } if (py < area.y()) { py = area.y(); } px += step; py += 2 * step; if (px > area.width() / 2) { px = area.x() + step; } if (py > area.height() / 2) { py = area.y() + step; } tx = px; ty = py; if (tx + size.width() > area.right()) { tx = area.right() - size.width(); if (tx < 0) { tx = 0; } px = area.x(); } if (ty + size.height() > area.bottom()) { ty = area.bottom() - size.height(); if (ty < 0) { ty = 0; } py = area.y(); } const QRectF placed = cascadeIfCovering(c, QRectF(QPointF(tx, ty), size), area); c->move(placed.topLeft()); } static inline bool isIrrelevant(const Window *window, const Window *regarding, VirtualDesktop *desktop) { return window == regarding || !window->isClient() || !window->isShown() || window->isShade() || !window->isOnDesktop(desktop) || !window->isOnCurrentActivity() || window->isDesktop(); }; /** * Place the client \a c according to a really smart placement algorithm :-) */ void Placement::placeSmart(Window *window, const QRectF &area, PlacementPolicy /*next*/) { Q_ASSERT(area.isValid()); /* * SmartPlacement by Cristian Tibirna (tibirna@kde.org) * adapted for kwm (16-19jan98) and for kwin (16Nov1999) using (with * permission) ideas from fvwm, authored by * Anthony Martin (amartin@engr.csulb.edu). * Xinerama supported added by Balaji Ramani (balaji@yablibli.com) * with ideas from xfce. */ const QSizeF size = window->size(); if (size.isEmpty()) { return; } const int none = 0, h_wrong = -1, w_wrong = -2; // overlap types long int overlap, min_overlap = 0; int x_optimal, y_optimal; int possible; VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front(); int cxl, cxr, cyt, cyb; // temp coords int xl, xr, yt, yb; // temp coords int basket; // temp holder // get the maximum allowed windows space int x = area.left(); int y = area.top(); x_optimal = x; y_optimal = y; // client gabarit int ch = std::ceil(size.height()); int cw = std::ceil(size.width()); // Explicitly converts those to int to avoid accidentally // mixing ints and qreal in the calculations below. int area_xr = std::floor(area.x() + area.width()); int area_yb = std::floor(area.y() + area.height()); bool first_pass = true; // CT lame flag. Don't like it. What else would do? // loop over possible positions do { // test if enough room in x and y directions if (y + ch > area_yb && ch < area.height()) { overlap = h_wrong; // this throws the algorithm to an exit } else if (x + cw > area_xr) { overlap = w_wrong; } else { overlap = none; // initialize cxl = x; cxr = x + cw; cyt = y; cyb = y + ch; for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) { auto client = *l; if (isIrrelevant(client, window, desktop)) { continue; } xl = client->x(); yt = client->y(); xr = xl + client->width(); yb = yt + client->height(); // if windows overlap, calc the overall overlapping if ((cxl < xr) && (cxr > xl) && (cyt < yb) && (cyb > yt)) { xl = std::max(cxl, xl); xr = std::min(cxr, xr); yt = std::max(cyt, yt); yb = std::min(cyb, yb); if (client->keepAbove()) { overlap += 16 * (xr - xl) * (yb - yt); } else if (client->keepBelow() && !client->isDock()) { // ignore KeepBelow windows overlap += 0; // for placement (see X11Window::belongsToLayer() for Dock) } else { overlap += (xr - xl) * (yb - yt); } } } } // CT first time we get no overlap we stop. if (overlap == none) { x_optimal = x; y_optimal = y; break; } if (first_pass) { first_pass = false; min_overlap = overlap; } // CT save the best position and the minimum overlap up to now else if (overlap >= none && overlap < min_overlap) { min_overlap = overlap; x_optimal = x; y_optimal = y; } // really need to loop? test if there's any overlap if (overlap > none) { possible = area_xr; if (possible - cw > x) { possible -= cw; } // compare to the position of each client on the same desk for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) { auto client = *l; if (isIrrelevant(client, window, desktop)) { continue; } xl = client->x(); yt = client->y(); xr = xl + client->width(); yb = yt + client->height(); // if not enough room above or under the current tested client // determine the first non-overlapped x position if ((y < yb) && (yt < ch + y)) { if ((xr > x) && (possible > xr)) { possible = xr; } basket = xl - cw; if ((basket > x) && (possible > basket)) { possible = basket; } } } x = possible; } // ... else ==> not enough x dimension (overlap was wrong on horizontal) else if (overlap == w_wrong) { x = area.left(); possible = area_yb; if (possible - ch > y) { possible -= ch; } // test the position of each window on the desk for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) { auto client = *l; if (isIrrelevant(client, window, desktop)) { continue; } xl = client->x(); yt = client->y(); xr = xl + client->width(); yb = yt + client->height(); // if not enough room to the left or right of the current tested client // determine the first non-overlapped y position if ((yb > y) && (possible > yb)) { possible = yb; } basket = yt - ch; if ((basket > y) && (possible > basket)) { possible = basket; } } y = possible; } } while ((overlap != none) && (overlap != h_wrong) && (y < area_yb)); if (ch >= area.height()) { y_optimal = area.top(); } // place the window window->move(QPoint(x_optimal, y_optimal)); } void Placement::reinitCascading() { cci.clear(); const auto desktops = VirtualDesktopManager::self()->desktops(); for (VirtualDesktop *desktop : desktops) { reinitCascading(desktop); } } void Placement::reinitCascading(VirtualDesktop *desktop) { cci[desktop] = DesktopCascadingInfo{ .pos = QPoint(-1, -1), .col = 0, .row = 0, }; } QPoint Workspace::cascadeOffset(const QRectF &area) const { return QPoint(area.width() / 48, area.height() / 48); } /** * Place windows in a cascading order, remembering positions for each desktop */ void Placement::placeCascaded(Window *c, const QRect &area, PlacementPolicy nextPlacement) { Q_ASSERT(area.isValid()); const QSizeF size = c->size(); if (size.isEmpty()) { return; } // CT how do I get from the 'Client' class the size that NW squarish "handle" const QPoint delta = workspace()->cascadeOffset(area); VirtualDesktop *dn = c->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : c->desktops().constLast(); if (nextPlacement == PlacementUnknown) { nextPlacement = PlacementSmart; } // initialize if needed if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < area.left() || cci[dn].pos.y() < area.top()) { cci[dn].pos = QPoint(area.left(), area.top()); cci[dn].col = cci[dn].row = 0; } int xp = cci[dn].pos.x(); int yp = cci[dn].pos.y(); // here to touch in case people vote for resize on placement if ((yp + size.height()) > area.height()) { yp = area.top(); } if ((xp + size.width()) > area.width()) { if (!yp) { place(c, area, nextPlacement); return; } else { xp = area.left(); } } // if this isn't the first window if (cci[dn].pos.x() != area.left() && cci[dn].pos.y() != area.top()) { if (xp != area.left() && yp == area.top()) { cci[dn].col++; xp = delta.x() * cci[dn].col; } if (yp != area.top() && xp == area.left()) { cci[dn].row++; yp = delta.y() * cci[dn].row; } // last resort: if still doesn't fit, smart place it if (((xp + size.width()) > area.width() - area.left()) || ((yp + size.height()) > area.height() - area.top())) { place(c, area, nextPlacement); return; } } // place the window c->move(QPoint(xp, yp)); // new position cci[dn].pos = QPoint(xp + delta.x(), yp + delta.y()); } /** * Place windows centered, on top of all others */ void Placement::placeCentered(Window *c, const QRectF &area, PlacementPolicy /*next*/) { Q_ASSERT(area.isValid()); const QSizeF size = c->size(); if (size.isEmpty()) { return; } const QPoint position(std::max(area.left() + (area.width() - size.width()) / 2, area.left()), std::max(area.top() + (area.height() - size.height()) / 2, area.top())); const QRectF placed = cascadeIfCovering(c, QRectF(position, size), area); c->move(placed.topLeft()); } /** * Place windows in the (0,0) corner, on top of all others */ void Placement::placeZeroCornered(Window *c, const QRect &area, PlacementPolicy /*next*/) { Q_ASSERT(area.isValid()); const QSizeF size = c->size(); if (size.isEmpty()) { return; } // get the maximum allowed windows space and desk's origin const QRectF placed = cascadeIfCovering(c, QRectF(area.topLeft(), size), area); c->move(placed.topLeft()); } void Placement::placeUtility(Window *c, const QRect &area, PlacementPolicy /*next*/) { // TODO kwin should try to place utility windows next to their mainwindow, // preferably at the right edge, and going down if there are more of them // if there's not enough place outside the mainwindow, it should prefer // top-right corner // use the default placement for now place(c, area, PlacementDefault); } void Placement::placeOnScreenDisplay(Window *c, const QRect &area) { Q_ASSERT(area.isValid()); const QSizeF size = c->size(); if (size.isEmpty()) { return; } // place at lower area of the screen const int x = area.left() + (area.width() - size.width()) / 2; const int y = area.top() + 2 * area.height() / 3 - size.height() / 2; c->move(QPoint(x, y)); } void Placement::placeTransient(Window *c) { c->moveResize(c->transientPlacement()); } void Placement::placeDialog(Window *c, const QRect &area, PlacementPolicy nextPlacement) { placeOnMainWindow(c, area, nextPlacement); } void Placement::placeUnderMouse(Window *c, const QRect &area, PlacementPolicy /*next*/) { const QSizeF size = c->size(); if (size.isEmpty()) { return; } const QPointF cursorPos = Cursors::self()->mouse()->pos(); const QRectF centered(cursorPos.x() - size.width() / 2, cursorPos.y() - size.height() / 2, size.width(), size.height()); const QRectF screenArea = workspace()->clientArea(PlacementArea, c, cursorPos); const QRectF placed = cascadeIfCovering(c, c->keepInArea(centered, screenArea), screenArea); c->move(placed.topLeft()); } void Placement::placeOnMainWindow(Window *c, const QRect &area, PlacementPolicy nextPlacement) { Q_ASSERT(area.isValid()); if (nextPlacement == PlacementUnknown) { nextPlacement = PlacementCentered; } if (nextPlacement == PlacementMaximizing) { // maximize if needed placeMaximizing(c, area, PlacementNone); } const QSizeF size = c->size(); if (size.isEmpty()) { return; } auto mainwindows = c->mainWindows(); Window *place_on = nullptr; Window *place_on2 = nullptr; int mains_count = 0; for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) { if (mainwindows.count() > 1 && (*it)->isSpecialWindow()) { continue; // don't consider toolbars etc when placing } ++mains_count; place_on2 = *it; if ((*it)->isOnCurrentDesktop()) { if (place_on == nullptr) { place_on = *it; } else { // two or more on current desktop -> center // That's the default at least. However, with maximizing placement // policy as the default, the dialog should be either maximized or // made as large as its maximum size and then placed centered. // So the nextPlacement argument allows chaining. In this case, nextPlacement // is Maximizing and it will call placeCentered(). place(c, area, PlacementCentered); return; } } } if (place_on == nullptr) { // 'mains_count' is used because it doesn't include ignored mainwindows if (mains_count != 1) { place(c, area, PlacementCentered); return; } place_on = place_on2; // use the only window filtered together with 'mains_count' } if (place_on->isDesktop()) { place(c, area, PlacementCentered); return; } QRectF geom(QPointF(0, 0), size); geom.moveCenter(place_on->frameGeometry().center().toPoint()); // get area again, because the mainwindow may be on different xinerama screen const QRect placementArea = workspace()->clientArea(PlacementArea, c, geom.center()).toRect(); c->move(c->keepInArea(geom, placementArea).topLeft()); // make sure it's kept inside workarea } void Placement::placeMaximizing(Window *c, const QRect &area, PlacementPolicy nextPlacement) { Q_ASSERT(area.isValid()); if (nextPlacement == PlacementUnknown) { nextPlacement = PlacementCentered; } if (c->isMaximizable()) { c->maximize(MaximizeFull); } else { place(c, area, nextPlacement); } } /** * Cascade the window until it no longer fully overlaps any other window */ QRectF Placement::cascadeIfCovering(Window *window, const QRectF &geometry, const QRectF &area) const { const QPoint offset = workspace()->cascadeOffset(area); VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front(); QRectF possibleGeo = geometry; bool noOverlap = false; // cascade until confirmed no total overlap or not enough space to cascade while (!noOverlap) { noOverlap = true; QRectF coveredArea; // check current position candidate for overlaps with other windows for (auto l = workspace()->stackingOrder().crbegin(); l != workspace()->stackingOrder().crend(); ++l) { auto other = *l; if (isIrrelevant(other, window, desktop) || !other->frameGeometry().intersects(possibleGeo)) { continue; } if (possibleGeo.contains(other->frameGeometry()) && !coveredArea.contains(other->frameGeometry())) { // placed window would completely overlap another window which is not already // covered by other windows: try to cascade it from the topleft of that other // window noOverlap = false; possibleGeo.moveTopLeft(other->pos() + offset); if (possibleGeo.right() > area.right() || possibleGeo.bottom() > area.bottom()) { // new cascaded geometry would be out of the bounds of the placement area: // abort the cascading and keep the window in the original position return geometry; } break; } // keep track of the area occupied by other windows as we go from top to bottom // in the stacking order, so we don't need to bother trying to avoid overlap with // windows which are already covered up by other windows anyway coveredArea |= other->frameGeometry(); if (coveredArea.contains(area)) { break; } } } return possibleGeo; } void Placement::cascadeDesktop() { Workspace *ws = Workspace::self(); reinitCascading(VirtualDesktopManager::self()->currentDesktop()); const auto stackingOrder = ws->stackingOrder(); for (Window *window : stackingOrder) { if (!window->isClient() || (!window->isOnCurrentDesktop()) || (window->isMinimized()) || (window->isOnAllDesktops()) || (!window->isMovable())) { continue; } const QRect placementArea = workspace()->clientArea(PlacementArea, window).toRect(); placeCascaded(window, placementArea); } } void Placement::unclutterDesktop() { const auto &windows = Workspace::self()->windows(); for (int i = windows.size() - 1; i >= 0; i--) { auto window = windows.at(i); if (!window->isClient()) { continue; } if ((!window->isOnCurrentDesktop()) || (window->isMinimized()) || (window->isOnAllDesktops()) || (!window->isMovable())) { continue; } const QRect placementArea = workspace()->clientArea(PlacementArea, window).toRect(); placeSmart(window, placementArea); } } #endif const char *Placement::policyToString(PlacementPolicy policy) { const char *const policies[] = { "NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Centered", "ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing"}; Q_ASSERT(policy < int(sizeof(policies) / sizeof(policies[0]))); return policies[policy]; } #ifndef KCMRULES // ******************** // Workspace // ******************** void Window::packTo(qreal left, qreal top) { workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event; const Output *oldOutput = moveResizeOutput(); move(QPoint(left, top)); if (moveResizeOutput() != oldOutput) { sendToOutput(moveResizeOutput()); // checks rule validity if (requestedMaximizeMode() != MaximizeRestore) { checkWorkspacePosition(); } } } /** * Moves active window left until in bumps into another window or workarea edge. */ void Workspace::slotWindowMoveLeft() { if (m_activeWindow && m_activeWindow->isMovable()) { const QRectF geometry = m_activeWindow->moveResizeGeometry(); m_activeWindow->packTo(packPositionLeft(m_activeWindow, geometry.left(), true), geometry.y()); } } void Workspace::slotWindowMoveRight() { if (m_activeWindow && m_activeWindow->isMovable()) { const QRectF geometry = m_activeWindow->moveResizeGeometry(); m_activeWindow->packTo(packPositionRight(m_activeWindow, geometry.right(), true) - geometry.width(), geometry.y()); } } void Workspace::slotWindowMoveUp() { if (m_activeWindow && m_activeWindow->isMovable()) { const QRectF geometry = m_activeWindow->moveResizeGeometry(); m_activeWindow->packTo(geometry.x(), packPositionUp(m_activeWindow, geometry.top(), true)); } } void Workspace::slotWindowMoveDown() { if (m_activeWindow && m_activeWindow->isMovable()) { const QRectF geometry = m_activeWindow->moveResizeGeometry(); m_activeWindow->packTo(geometry.x(), packPositionDown(m_activeWindow, geometry.bottom(), true) - geometry.height()); } } /** Moves the active window to the center of the screen. */ void Workspace::slotWindowCenter() { if (m_activeWindow && m_activeWindow->isMovable()) { const QRectF geometry = m_activeWindow->moveResizeGeometry(); QPointF center = clientArea(MaximizeArea, m_activeWindow).center(); m_activeWindow->packTo(center.x() - (geometry.width() / 2), center.y() - (geometry.height() / 2)); } } void Workspace::slotWindowExpandHorizontal() { if (m_activeWindow) { m_activeWindow->growHorizontal(); } } void Window::growHorizontal() { if (!isResizable() || isShade()) { return; } QRectF geom = moveResizeGeometry(); geom.setRight(workspace()->packPositionRight(this, geom.right(), true)); QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedW); if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().width() > 1) { // take care of size increments qreal newright = workspace()->packPositionRight(this, geom.right() + resizeIncrements().width() - 1, true); // check that it hasn't grown outside of the area, due to size increments // TODO this may be wrong? if (workspace()->clientArea(MovementArea, this, QPoint((x() + newright) / 2, moveResizeGeometry().center().y())) .right() >= newright) { geom.setRight(newright); } } geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW)); geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH)); workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event; moveResize(geom); } void Workspace::slotWindowShrinkHorizontal() { if (m_activeWindow) { m_activeWindow->shrinkHorizontal(); } } void Window::shrinkHorizontal() { if (!isResizable() || isShade()) { return; } QRectF geom = moveResizeGeometry(); geom.setRight(workspace()->packPositionLeft(this, geom.right(), false) + 1); if (geom.width() <= 1) { return; } geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW)); if (geom.width() > 20) { workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event; moveResize(geom); } } void Workspace::slotWindowExpandVertical() { if (m_activeWindow) { m_activeWindow->growVertical(); } } void Window::growVertical() { if (!isResizable() || isShade()) { return; } QRectF geom = moveResizeGeometry(); geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true)); QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedH); if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().height() > 1) { // take care of size increments qreal newbottom = workspace()->packPositionDown(this, geom.bottom() + resizeIncrements().height(), true); // check that it hasn't grown outside of the area, due to size increments if (workspace()->clientArea(MovementArea, this, QPoint(moveResizeGeometry().center().x(), (y() + newbottom) / 2)) .bottom() >= newbottom) { geom.setBottom(newbottom); } } geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH)); workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event; moveResize(geom); } void Workspace::slotWindowShrinkVertical() { if (m_activeWindow) { m_activeWindow->shrinkVertical(); } } void Window::shrinkVertical() { if (!isResizable() || isShade()) { return; } QRectF geom = moveResizeGeometry(); geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false) + 1); if (geom.height() <= 1) { return; } geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH)); if (geom.height() > 20) { workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event; moveResize(geom); } } void Workspace::quickTileWindow(QuickTileMode mode) { if (!m_activeWindow) { return; } // If the user invokes two of these commands in a one second period, try to // combine them together to enable easy and intuitive corner tiling #define FLAG(name) QuickTileMode(QuickTileFlag::name) if (!m_quickTileCombineTimer->isActive()) { m_quickTileCombineTimer->start(1000); m_lastTilingMode = mode; } else { if ( ((m_lastTilingMode == FLAG(Left) || m_lastTilingMode == FLAG(Right)) && (mode == FLAG(Top) || mode == FLAG(Bottom))) || ((m_lastTilingMode == FLAG(Top) || m_lastTilingMode == FLAG(Bottom)) && (mode == FLAG(Left) || mode == FLAG(Right))) #undef FLAG ) { mode |= m_lastTilingMode; } m_quickTileCombineTimer->stop(); } m_activeWindow->handleQuickTileShortcut(mode); } qreal Workspace::packPositionLeft(const Window *window, qreal oldX, bool leftEdge) const { qreal newX = clientArea(MaximizeArea, window).left(); if (oldX <= newX) { // try another Xinerama screen newX = clientArea(MaximizeArea, window, QPointF(window->frameGeometry().left() - 1, window->frameGeometry().center().y())) .left(); } if (oldX <= newX) { return oldX; } VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front(); for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) { if (isIrrelevant(*it, window, desktop)) { continue; } const qreal x = leftEdge ? (*it)->frameGeometry().right() : (*it)->frameGeometry().left() - 1; if (x > newX && x < oldX && !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1 // they overlap in Y direction || window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) { newX = x; } } return newX; } qreal Workspace::packPositionRight(const Window *window, qreal oldX, bool rightEdge) const { qreal newX = clientArea(MaximizeArea, window).right(); if (oldX >= newX) { // try another Xinerama screen newX = clientArea(MaximizeArea, window, QPointF(window->frameGeometry().right(), window->frameGeometry().center().y())) .right(); } if (oldX >= newX) { return oldX; } VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front(); for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) { if (isIrrelevant(*it, window, desktop)) { continue; } const qreal x = rightEdge ? (*it)->frameGeometry().left() : (*it)->frameGeometry().right() + 1; if (x < newX && x > oldX && !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1 || window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) { newX = x; } } return newX; } qreal Workspace::packPositionUp(const Window *window, qreal oldY, bool topEdge) const { qreal newY = clientArea(MaximizeArea, window).top(); if (oldY <= newY) { // try another Xinerama screen newY = clientArea(MaximizeArea, window, QPointF(window->frameGeometry().center().x(), window->frameGeometry().top() - 1)) .top(); } if (oldY <= newY) { return oldY; } VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front(); for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) { if (isIrrelevant(*it, window, desktop)) { continue; } const qreal y = topEdge ? (*it)->frameGeometry().bottom() : (*it)->frameGeometry().top() - 1; if (y > newY && y < oldY && !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1 // they overlap in X direction || window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) { newY = y; } } return newY; } qreal Workspace::packPositionDown(const Window *window, qreal oldY, bool bottomEdge) const { qreal newY = clientArea(MaximizeArea, window).bottom(); if (oldY >= newY) { // try another Xinerama screen newY = clientArea(MaximizeArea, window, QPointF(window->frameGeometry().center().x(), window->frameGeometry().bottom())) .bottom(); } if (oldY >= newY) { return oldY; } VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front(); for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) { if (isIrrelevant(*it, window, desktop)) { continue; } const qreal y = bottomEdge ? (*it)->frameGeometry().top() : (*it)->frameGeometry().bottom() + 1; if (y < newY && y > oldY && !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1 || window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) { newY = y; } } return newY; } #endif } // namespace