File indexing completed on 2024-04-28 05:30:27

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
0006     SPDX-FileCopyrightText: 1997-2002 Cristian Tibirna <tibirna@kde.org>
0007     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0008     SPDX-FileCopyrightText: 2022 Natalie Clarius <natalie_clarius@yahoo.de>
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include "placement.h"
0014 
0015 #ifndef KCMRULES
0016 #include "cursor.h"
0017 #include "options.h"
0018 #include "rules.h"
0019 #include "virtualdesktops.h"
0020 #include "workspace.h"
0021 #include "x11window.h"
0022 #endif
0023 
0024 #include <QTextStream>
0025 #include <QTimer>
0026 
0027 namespace KWin
0028 {
0029 
0030 #ifndef KCMRULES
0031 
0032 Placement::Placement()
0033 {
0034     reinitCascading();
0035 }
0036 
0037 /**
0038  * Places the client \a c according to the workspace's layout policy
0039  */
0040 void Placement::place(Window *c, const QRectF &area)
0041 {
0042     PlacementPolicy policy = c->rules()->checkPlacement(PlacementDefault);
0043     if (policy != PlacementDefault) {
0044         place(c, area, policy);
0045         return;
0046     }
0047 
0048     if (c->isUtility()) {
0049         placeUtility(c, area.toRect(), options->placement());
0050     } else if (c->isDialog()) {
0051         placeDialog(c, area.toRect(), options->placement());
0052     } else if (c->isSplash()) {
0053         placeOnMainWindow(c, area.toRect()); // on mainwindow, if any, otherwise centered
0054     } else if (c->isOnScreenDisplay() || c->isNotification() || c->isCriticalNotification()) {
0055         placeOnScreenDisplay(c, area.toRect());
0056     } else if (c->isTransient() && c->hasTransientPlacementHint()) {
0057         placeTransient(c);
0058     } else if (c->isTransient() && c->surface()) {
0059         placeDialog(c, area.toRect(), options->placement());
0060     } else {
0061         place(c, area, options->placement());
0062     }
0063 }
0064 
0065 void Placement::place(Window *c, const QRectF &area, PlacementPolicy policy, PlacementPolicy nextPlacement)
0066 {
0067     if (policy == PlacementUnknown || policy == PlacementDefault) {
0068         policy = options->placement();
0069     }
0070 
0071     switch (policy) {
0072     case PlacementNone:
0073         return;
0074     case PlacementRandom:
0075         placeAtRandom(c, area.toRect(), nextPlacement);
0076         break;
0077     case PlacementCentered:
0078         placeCentered(c, area, nextPlacement);
0079         break;
0080     case PlacementZeroCornered:
0081         placeZeroCornered(c, area.toRect(), nextPlacement);
0082         break;
0083     case PlacementUnderMouse:
0084         placeUnderMouse(c, area.toRect(), nextPlacement);
0085         break;
0086     case PlacementOnMainWindow:
0087         placeOnMainWindow(c, area.toRect(), nextPlacement);
0088         break;
0089     case PlacementMaximizing:
0090         placeMaximizing(c, area.toRect(), nextPlacement);
0091         break;
0092     default:
0093         placeSmart(c, area, nextPlacement);
0094     }
0095 }
0096 
0097 /**
0098  * Place the client \a c according to a simply "random" placement algorithm.
0099  */
0100 void Placement::placeAtRandom(Window *c, const QRect &area, PlacementPolicy /*next*/)
0101 {
0102     Q_ASSERT(area.isValid());
0103 
0104     const int step = 24;
0105     static int px = step;
0106     static int py = 2 * step;
0107     int tx, ty;
0108 
0109     if (px < area.x()) {
0110         px = area.x();
0111     }
0112     if (py < area.y()) {
0113         py = area.y();
0114     }
0115 
0116     px += step;
0117     py += 2 * step;
0118 
0119     if (px > area.width() / 2) {
0120         px = area.x() + step;
0121     }
0122     if (py > area.height() / 2) {
0123         py = area.y() + step;
0124     }
0125     tx = px;
0126     ty = py;
0127     if (tx + c->width() > area.right()) {
0128         tx = area.right() - c->width();
0129         if (tx < 0) {
0130             tx = 0;
0131         }
0132         px = area.x();
0133     }
0134     if (ty + c->height() > area.bottom()) {
0135         ty = area.bottom() - c->height();
0136         if (ty < 0) {
0137             ty = 0;
0138         }
0139         py = area.y();
0140     }
0141     c->move(QPoint(tx, ty));
0142     cascadeIfCovering(c, area);
0143 }
0144 
0145 static inline bool isIrrelevant(const Window *window, const Window *regarding, VirtualDesktop *desktop)
0146 {
0147     return window == regarding
0148         || !window->isClient()
0149         || !window->isShown()
0150         || window->isShade()
0151         || !window->isOnDesktop(desktop)
0152         || !window->isOnCurrentActivity()
0153         || window->isDesktop();
0154 };
0155 
0156 /**
0157  * Place the client \a c according to a really smart placement algorithm :-)
0158  */
0159 void Placement::placeSmart(Window *window, const QRectF &area, PlacementPolicy /*next*/)
0160 {
0161     Q_ASSERT(area.isValid());
0162 
0163     /*
0164      * SmartPlacement by Cristian Tibirna (tibirna@kde.org)
0165      * adapted for kwm (16-19jan98) and for kwin (16Nov1999) using (with
0166      * permission) ideas from fvwm, authored by
0167      * Anthony Martin (amartin@engr.csulb.edu).
0168      * Xinerama supported added by Balaji Ramani (balaji@yablibli.com)
0169      * with ideas from xfce.
0170      */
0171 
0172     if (!window->frameGeometry().isValid()) {
0173         return;
0174     }
0175 
0176     const int none = 0, h_wrong = -1, w_wrong = -2; // overlap types
0177     long int overlap, min_overlap = 0;
0178     int x_optimal, y_optimal;
0179     int possible;
0180     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0181 
0182     int cxl, cxr, cyt, cyb; // temp coords
0183     int xl, xr, yt, yb; // temp coords
0184     int basket; // temp holder
0185 
0186     // get the maximum allowed windows space
0187     int x = area.left();
0188     int y = area.top();
0189     x_optimal = x;
0190     y_optimal = y;
0191 
0192     // client gabarit
0193     int ch = std::ceil(window->height());
0194     int cw = std::ceil(window->width());
0195 
0196     // Explicitly converts those to int to avoid accidentally
0197     // mixing ints and qreal in the calculations below.
0198     int area_xr = std::floor(area.x() + area.width());
0199     int area_yb = std::floor(area.y() + area.height());
0200 
0201     bool first_pass = true; // CT lame flag. Don't like it. What else would do?
0202 
0203     // loop over possible positions
0204     do {
0205         // test if enough room in x and y directions
0206         if (y + ch > area_yb && ch < area.height()) {
0207             overlap = h_wrong; // this throws the algorithm to an exit
0208         } else if (x + cw > area_xr) {
0209             overlap = w_wrong;
0210         } else {
0211             overlap = none; // initialize
0212 
0213             cxl = x;
0214             cxr = x + cw;
0215             cyt = y;
0216             cyb = y + ch;
0217             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
0218                 auto client = *l;
0219                 if (isIrrelevant(client, window, desktop)) {
0220                     continue;
0221                 }
0222                 xl = client->x();
0223                 yt = client->y();
0224                 xr = xl + client->width();
0225                 yb = yt + client->height();
0226 
0227                 // if windows overlap, calc the overall overlapping
0228                 if ((cxl < xr) && (cxr > xl) && (cyt < yb) && (cyb > yt)) {
0229                     xl = std::max(cxl, xl);
0230                     xr = std::min(cxr, xr);
0231                     yt = std::max(cyt, yt);
0232                     yb = std::min(cyb, yb);
0233                     if (client->keepAbove()) {
0234                         overlap += 16 * (xr - xl) * (yb - yt);
0235                     } else if (client->keepBelow() && !client->isDock()) { // ignore KeepBelow windows
0236                         overlap += 0; // for placement (see X11Window::belongsToLayer() for Dock)
0237                     } else {
0238                         overlap += (xr - xl) * (yb - yt);
0239                     }
0240                 }
0241             }
0242         }
0243 
0244         // CT first time we get no overlap we stop.
0245         if (overlap == none) {
0246             x_optimal = x;
0247             y_optimal = y;
0248             break;
0249         }
0250 
0251         if (first_pass) {
0252             first_pass = false;
0253             min_overlap = overlap;
0254         }
0255         // CT save the best position and the minimum overlap up to now
0256         else if (overlap >= none && overlap < min_overlap) {
0257             min_overlap = overlap;
0258             x_optimal = x;
0259             y_optimal = y;
0260         }
0261 
0262         // really need to loop? test if there's any overlap
0263         if (overlap > none) {
0264 
0265             possible = area_xr;
0266             if (possible - cw > x) {
0267                 possible -= cw;
0268             }
0269 
0270             // compare to the position of each client on the same desk
0271             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
0272                 auto client = *l;
0273                 if (isIrrelevant(client, window, desktop)) {
0274                     continue;
0275                 }
0276 
0277                 xl = client->x();
0278                 yt = client->y();
0279                 xr = xl + client->width();
0280                 yb = yt + client->height();
0281 
0282                 // if not enough room above or under the current tested client
0283                 // determine the first non-overlapped x position
0284                 if ((y < yb) && (yt < ch + y)) {
0285 
0286                     if ((xr > x) && (possible > xr)) {
0287                         possible = xr;
0288                     }
0289 
0290                     basket = xl - cw;
0291                     if ((basket > x) && (possible > basket)) {
0292                         possible = basket;
0293                     }
0294                 }
0295             }
0296             x = possible;
0297         }
0298 
0299         // ... else ==> not enough x dimension (overlap was wrong on horizontal)
0300         else if (overlap == w_wrong) {
0301             x = area.left();
0302             possible = area_yb;
0303 
0304             if (possible - ch > y) {
0305                 possible -= ch;
0306             }
0307 
0308             // test the position of each window on the desk
0309             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
0310                 auto client = *l;
0311                 if (isIrrelevant(client, window, desktop)) {
0312                     continue;
0313                 }
0314 
0315                 xl = client->x();
0316                 yt = client->y();
0317                 xr = xl + client->width();
0318                 yb = yt + client->height();
0319 
0320                 // if not enough room to the left or right of the current tested client
0321                 // determine the first non-overlapped y position
0322                 if ((yb > y) && (possible > yb)) {
0323                     possible = yb;
0324                 }
0325 
0326                 basket = yt - ch;
0327                 if ((basket > y) && (possible > basket)) {
0328                     possible = basket;
0329                 }
0330             }
0331             y = possible;
0332         }
0333     } while ((overlap != none) && (overlap != h_wrong) && (y < area_yb));
0334 
0335     if (ch >= area.height()) {
0336         y_optimal = area.top();
0337     }
0338 
0339     // place the window
0340     window->move(QPoint(x_optimal, y_optimal));
0341 }
0342 
0343 void Placement::reinitCascading()
0344 {
0345     cci.clear();
0346     const auto desktops = VirtualDesktopManager::self()->desktops();
0347     for (VirtualDesktop *desktop : desktops) {
0348         reinitCascading(desktop);
0349     }
0350 }
0351 
0352 void Placement::reinitCascading(VirtualDesktop *desktop)
0353 {
0354     cci[desktop] = DesktopCascadingInfo{
0355         .pos = QPoint(-1, -1),
0356         .col = 0,
0357         .row = 0,
0358     };
0359 }
0360 
0361 QPoint Workspace::cascadeOffset(const Window *c) const
0362 {
0363     QRect area = clientArea(PlacementArea, c, c->frameGeometry().center()).toRect();
0364     return QPoint(area.width() / 48, area.height() / 48);
0365 }
0366 
0367 /**
0368  * Place windows in a cascading order, remembering positions for each desktop
0369  */
0370 void Placement::placeCascaded(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0371 {
0372     Q_ASSERT(area.isValid());
0373 
0374     if (!c->frameGeometry().isValid()) {
0375         return;
0376     }
0377 
0378     // CT how do I get from the 'Client' class the size that NW squarish "handle"
0379     const QPoint delta = workspace()->cascadeOffset(c);
0380 
0381     VirtualDesktop *dn = c->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : c->desktops().constLast();
0382 
0383     if (nextPlacement == PlacementUnknown) {
0384         nextPlacement = PlacementSmart;
0385     }
0386 
0387     // initialize if needed
0388     if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < area.left() || cci[dn].pos.y() < area.top()) {
0389         cci[dn].pos = QPoint(area.left(), area.top());
0390         cci[dn].col = cci[dn].row = 0;
0391     }
0392 
0393     int xp = cci[dn].pos.x();
0394     int yp = cci[dn].pos.y();
0395 
0396     // here to touch in case people vote for resize on placement
0397     if ((yp + c->height()) > area.height()) {
0398         yp = area.top();
0399     }
0400 
0401     if ((xp + c->width()) > area.width()) {
0402         if (!yp) {
0403             place(c, area, nextPlacement);
0404             return;
0405         } else {
0406             xp = area.left();
0407         }
0408     }
0409 
0410     // if this isn't the first window
0411     if (cci[dn].pos.x() != area.left() && cci[dn].pos.y() != area.top()) {
0412         if (xp != area.left() && yp == area.top()) {
0413             cci[dn].col++;
0414             xp = delta.x() * cci[dn].col;
0415         }
0416         if (yp != area.top() && xp == area.left()) {
0417             cci[dn].row++;
0418             yp = delta.y() * cci[dn].row;
0419         }
0420 
0421         // last resort: if still doesn't fit, smart place it
0422         if (((xp + c->width()) > area.width() - area.left()) || ((yp + c->height()) > area.height() - area.top())) {
0423             place(c, area, nextPlacement);
0424             return;
0425         }
0426     }
0427 
0428     // place the window
0429     c->move(QPoint(xp, yp));
0430 
0431     // new position
0432     cci[dn].pos = QPoint(xp + delta.x(), yp + delta.y());
0433 }
0434 
0435 /**
0436  * Place windows centered, on top of all others
0437  */
0438 void Placement::placeCentered(Window *c, const QRectF &area, PlacementPolicy /*next*/)
0439 {
0440     Q_ASSERT(area.isValid());
0441 
0442     if (!c->frameGeometry().isValid()) {
0443         return;
0444     }
0445 
0446     const int xp = area.left() + (area.width() - c->width()) / 2;
0447     const int yp = area.top() + (area.height() - c->height()) / 2;
0448 
0449     // place the window
0450     c->move(QPoint(xp, yp));
0451     cascadeIfCovering(c, area);
0452 }
0453 
0454 /**
0455  * Place windows in the (0,0) corner, on top of all others
0456  */
0457 void Placement::placeZeroCornered(Window *c, const QRect &area, PlacementPolicy /*next*/)
0458 {
0459     Q_ASSERT(area.isValid());
0460 
0461     // get the maximum allowed windows space and desk's origin
0462     c->move(area.topLeft());
0463     cascadeIfCovering(c, area);
0464 }
0465 
0466 void Placement::placeUtility(Window *c, const QRect &area, PlacementPolicy /*next*/)
0467 {
0468     // TODO kwin should try to place utility windows next to their mainwindow,
0469     // preferably at the right edge, and going down if there are more of them
0470     // if there's not enough place outside the mainwindow, it should prefer
0471     // top-right corner
0472     // use the default placement for now
0473     place(c, area, PlacementDefault);
0474 }
0475 
0476 void Placement::placeOnScreenDisplay(Window *c, const QRect &area)
0477 {
0478     Q_ASSERT(area.isValid());
0479 
0480     // place at lower area of the screen
0481     const int x = area.left() + (area.width() - c->width()) / 2;
0482     const int y = area.top() + 2 * area.height() / 3 - c->height() / 2;
0483 
0484     c->move(QPoint(x, y));
0485 }
0486 
0487 void Placement::placeTransient(Window *c)
0488 {
0489     c->moveResize(c->transientPlacement());
0490 }
0491 
0492 void Placement::placeDialog(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0493 {
0494     placeOnMainWindow(c, area, nextPlacement);
0495 }
0496 
0497 void Placement::placeUnderMouse(Window *c, const QRect &area, PlacementPolicy /*next*/)
0498 {
0499     Q_ASSERT(area.isValid());
0500 
0501     QRectF geom = c->frameGeometry();
0502     geom.moveCenter(Cursors::self()->mouse()->pos());
0503     c->move(geom.topLeft().toPoint());
0504     c->keepInArea(area); // make sure it's kept inside workarea
0505     cascadeIfCovering(c, area);
0506 }
0507 
0508 void Placement::placeOnMainWindow(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0509 {
0510     Q_ASSERT(area.isValid());
0511 
0512     if (nextPlacement == PlacementUnknown) {
0513         nextPlacement = PlacementCentered;
0514     }
0515     if (nextPlacement == PlacementMaximizing) { // maximize if needed
0516         placeMaximizing(c, area, PlacementNone);
0517     }
0518     auto mainwindows = c->mainWindows();
0519     Window *place_on = nullptr;
0520     Window *place_on2 = nullptr;
0521     int mains_count = 0;
0522     for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
0523         if (mainwindows.count() > 1 && (*it)->isSpecialWindow()) {
0524             continue; // don't consider toolbars etc when placing
0525         }
0526         ++mains_count;
0527         place_on2 = *it;
0528         if ((*it)->isOnCurrentDesktop()) {
0529             if (place_on == nullptr) {
0530                 place_on = *it;
0531             } else {
0532                 // two or more on current desktop -> center
0533                 // That's the default at least. However, with maximizing placement
0534                 // policy as the default, the dialog should be either maximized or
0535                 // made as large as its maximum size and then placed centered.
0536                 // So the nextPlacement argument allows chaining. In this case, nextPlacement
0537                 // is Maximizing and it will call placeCentered().
0538                 place(c, area, PlacementCentered);
0539                 return;
0540             }
0541         }
0542     }
0543     if (place_on == nullptr) {
0544         // 'mains_count' is used because it doesn't include ignored mainwindows
0545         if (mains_count != 1) {
0546             place(c, area, PlacementCentered);
0547             return;
0548         }
0549         place_on = place_on2; // use the only window filtered together with 'mains_count'
0550     }
0551     if (place_on->isDesktop()) {
0552         place(c, area, PlacementCentered);
0553         return;
0554     }
0555     QRect geom = c->frameGeometry().toRect();
0556     geom.moveCenter(place_on->frameGeometry().center().toPoint());
0557     c->move(geom.topLeft());
0558     // get area again, because the mainwindow may be on different xinerama screen
0559     const QRect placementArea = workspace()->clientArea(PlacementArea, c).toRect();
0560     c->keepInArea(placementArea); // make sure it's kept inside workarea
0561 }
0562 
0563 void Placement::placeMaximizing(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0564 {
0565     Q_ASSERT(area.isValid());
0566 
0567     if (nextPlacement == PlacementUnknown) {
0568         nextPlacement = PlacementSmart;
0569     }
0570     if (c->isMaximizable() && c->maxSize().width() >= area.width() && c->maxSize().height() >= area.height()) {
0571         if (workspace()->clientArea(MaximizeArea, c) == area) {
0572             c->maximize(MaximizeFull);
0573         } else { // if the geometry doesn't match default maximize area (xinerama case?),
0574             // it's probably better to use the given area
0575             c->moveResize(area);
0576         }
0577     } else {
0578         c->moveResize(c->resizeWithChecks(c->moveResizeGeometry(), c->maxSize().boundedTo(area.size())));
0579         place(c, area, nextPlacement);
0580     }
0581 }
0582 
0583 /**
0584  * Cascade the window until it no longer fully overlaps any other window
0585  */
0586 void Placement::cascadeIfCovering(Window *window, const QRectF &area)
0587 {
0588     const QPoint offset = workspace()->cascadeOffset(window);
0589 
0590     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0591 
0592     QRectF possibleGeo = window->frameGeometry();
0593     bool noOverlap = false;
0594 
0595     // cascade until confirmed no total overlap or not enough space to cascade
0596     while (!noOverlap) {
0597         noOverlap = true;
0598         QRectF coveredArea;
0599         // check current position candidate for overlaps with other windows
0600         for (auto l = workspace()->stackingOrder().crbegin(); l != workspace()->stackingOrder().crend(); ++l) {
0601             auto other = *l;
0602             if (isIrrelevant(other, window, desktop) || !other->frameGeometry().intersects(possibleGeo)) {
0603                 continue;
0604             }
0605 
0606             if (possibleGeo.contains(other->frameGeometry()) && !coveredArea.contains(other->frameGeometry())) {
0607                 // placed window would completely overlap another window which is not already
0608                 // covered by other windows: try to cascade it from the topleft of that other
0609                 // window
0610                 noOverlap = false;
0611                 possibleGeo.moveTopLeft(other->pos() + offset);
0612                 if (possibleGeo.right() > area.right() || possibleGeo.bottom() > area.bottom()) {
0613                     // new cascaded geometry would be out of the bounds of the placement area:
0614                     // abort the cascading and keep the window in the original position
0615                     return;
0616                 }
0617                 break;
0618             }
0619 
0620             // keep track of the area occupied by other windows as we go from top to bottom
0621             // in the stacking order, so we don't need to bother trying to avoid overlap with
0622             // windows which are already covered up by other windows anyway
0623             coveredArea |= other->frameGeometry();
0624             if (coveredArea.contains(area)) {
0625                 break;
0626             }
0627         }
0628     }
0629 
0630     window->move(possibleGeo.topLeft());
0631 }
0632 
0633 void Placement::cascadeDesktop()
0634 {
0635     Workspace *ws = Workspace::self();
0636     reinitCascading(VirtualDesktopManager::self()->currentDesktop());
0637     const auto stackingOrder = ws->stackingOrder();
0638     for (Window *window : stackingOrder) {
0639         if (!window->isClient() || (!window->isOnCurrentDesktop()) || (window->isMinimized()) || (window->isOnAllDesktops()) || (!window->isMovable())) {
0640             continue;
0641         }
0642         const QRect placementArea = workspace()->clientArea(PlacementArea, window).toRect();
0643         placeCascaded(window, placementArea);
0644     }
0645 }
0646 
0647 void Placement::unclutterDesktop()
0648 {
0649     const auto &windows = Workspace::self()->windows();
0650     for (int i = windows.size() - 1; i >= 0; i--) {
0651         auto window = windows.at(i);
0652         if (!window->isClient()) {
0653             continue;
0654         }
0655         if ((!window->isOnCurrentDesktop()) || (window->isMinimized()) || (window->isOnAllDesktops()) || (!window->isMovable())) {
0656             continue;
0657         }
0658         const QRect placementArea = workspace()->clientArea(PlacementArea, window).toRect();
0659         placeSmart(window, placementArea);
0660     }
0661 }
0662 
0663 #endif
0664 
0665 const char *Placement::policyToString(PlacementPolicy policy)
0666 {
0667     const char *const policies[] = {
0668         "NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Centered",
0669         "ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing"};
0670     Q_ASSERT(policy < int(sizeof(policies) / sizeof(policies[0])));
0671     return policies[policy];
0672 }
0673 
0674 #ifndef KCMRULES
0675 
0676 // ********************
0677 // Workspace
0678 // ********************
0679 
0680 void Window::packTo(qreal left, qreal top)
0681 {
0682     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0683 
0684     const Output *oldOutput = moveResizeOutput();
0685     move(QPoint(left, top));
0686     if (moveResizeOutput() != oldOutput) {
0687         workspace()->sendWindowToOutput(this, moveResizeOutput()); // checks rule validity
0688         if (requestedMaximizeMode() != MaximizeRestore) {
0689             checkWorkspacePosition();
0690         }
0691     }
0692 }
0693 
0694 /**
0695  * Moves active window left until in bumps into another window or workarea edge.
0696  */
0697 void Workspace::slotWindowMoveLeft()
0698 {
0699     if (m_activeWindow && m_activeWindow->isMovable()) {
0700         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0701         m_activeWindow->packTo(packPositionLeft(m_activeWindow, geometry.left(), true),
0702                                geometry.y());
0703     }
0704 }
0705 
0706 void Workspace::slotWindowMoveRight()
0707 {
0708     if (m_activeWindow && m_activeWindow->isMovable()) {
0709         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0710         m_activeWindow->packTo(packPositionRight(m_activeWindow, geometry.right(), true) - geometry.width(),
0711                                geometry.y());
0712     }
0713 }
0714 
0715 void Workspace::slotWindowMoveUp()
0716 {
0717     if (m_activeWindow && m_activeWindow->isMovable()) {
0718         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0719         m_activeWindow->packTo(geometry.x(),
0720                                packPositionUp(m_activeWindow, geometry.top(), true));
0721     }
0722 }
0723 
0724 void Workspace::slotWindowMoveDown()
0725 {
0726     if (m_activeWindow && m_activeWindow->isMovable()) {
0727         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0728         m_activeWindow->packTo(geometry.x(),
0729                                packPositionDown(m_activeWindow, geometry.bottom(), true) - geometry.height());
0730     }
0731 }
0732 
0733 /** Moves the active window to the center of the screen. */
0734 void Workspace::slotWindowCenter()
0735 {
0736     if (m_activeWindow && m_activeWindow->isMovable()) {
0737         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0738         QPointF center = clientArea(MaximizeArea, m_activeWindow).center();
0739         m_activeWindow->packTo(center.x() - (geometry.width() / 2),
0740                                center.y() - (geometry.height() / 2));
0741     }
0742 }
0743 
0744 void Workspace::slotWindowExpandHorizontal()
0745 {
0746     if (m_activeWindow) {
0747         m_activeWindow->growHorizontal();
0748     }
0749 }
0750 
0751 void Window::growHorizontal()
0752 {
0753     if (!isResizable() || isShade()) {
0754         return;
0755     }
0756     QRectF geom = moveResizeGeometry();
0757     geom.setRight(workspace()->packPositionRight(this, geom.right(), true));
0758     QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedW);
0759     if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().width() > 1) { // take care of size increments
0760         qreal newright = workspace()->packPositionRight(this, geom.right() + resizeIncrements().width() - 1, true);
0761         // check that it hasn't grown outside of the area, due to size increments
0762         // TODO this may be wrong?
0763         if (workspace()->clientArea(MovementArea,
0764                                     this,
0765                                     QPoint((x() + newright) / 2, moveResizeGeometry().center().y()))
0766                 .right()
0767             >= newright) {
0768             geom.setRight(newright);
0769         }
0770     }
0771     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
0772     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
0773     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0774     moveResize(geom);
0775 }
0776 
0777 void Workspace::slotWindowShrinkHorizontal()
0778 {
0779     if (m_activeWindow) {
0780         m_activeWindow->shrinkHorizontal();
0781     }
0782 }
0783 
0784 void Window::shrinkHorizontal()
0785 {
0786     if (!isResizable() || isShade()) {
0787         return;
0788     }
0789     QRectF geom = moveResizeGeometry();
0790     geom.setRight(workspace()->packPositionLeft(this, geom.right(), false) + 1);
0791     if (geom.width() <= 1) {
0792         return;
0793     }
0794     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
0795     if (geom.width() > 20) {
0796         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0797         moveResize(geom);
0798     }
0799 }
0800 
0801 void Workspace::slotWindowExpandVertical()
0802 {
0803     if (m_activeWindow) {
0804         m_activeWindow->growVertical();
0805     }
0806 }
0807 
0808 void Window::growVertical()
0809 {
0810     if (!isResizable() || isShade()) {
0811         return;
0812     }
0813     QRectF geom = moveResizeGeometry();
0814     geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true));
0815     QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedH);
0816     if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().height() > 1) { // take care of size increments
0817         qreal newbottom = workspace()->packPositionDown(this, geom.bottom() + resizeIncrements().height(), true);
0818         // check that it hasn't grown outside of the area, due to size increments
0819         if (workspace()->clientArea(MovementArea,
0820                                     this,
0821                                     QPoint(moveResizeGeometry().center().x(), (y() + newbottom) / 2))
0822                 .bottom()
0823             >= newbottom) {
0824             geom.setBottom(newbottom);
0825         }
0826     }
0827     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
0828     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0829     moveResize(geom);
0830 }
0831 
0832 void Workspace::slotWindowShrinkVertical()
0833 {
0834     if (m_activeWindow) {
0835         m_activeWindow->shrinkVertical();
0836     }
0837 }
0838 
0839 void Window::shrinkVertical()
0840 {
0841     if (!isResizable() || isShade()) {
0842         return;
0843     }
0844     QRectF geom = moveResizeGeometry();
0845     geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false) + 1);
0846     if (geom.height() <= 1) {
0847         return;
0848     }
0849     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
0850     if (geom.height() > 20) {
0851         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0852         moveResize(geom);
0853     }
0854 }
0855 
0856 void Workspace::quickTileWindow(QuickTileMode mode)
0857 {
0858     if (!m_activeWindow) {
0859         return;
0860     }
0861 
0862     // If the user invokes two of these commands in a one second period, try to
0863     // combine them together to enable easy and intuitive corner tiling
0864 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0865     if (!m_quickTileCombineTimer->isActive()) {
0866         m_quickTileCombineTimer->start(1000);
0867         m_lastTilingMode = mode;
0868     } else {
0869         if (
0870             ((m_lastTilingMode == FLAG(Left) || m_lastTilingMode == FLAG(Right)) && (mode == FLAG(Top) || mode == FLAG(Bottom)))
0871             || ((m_lastTilingMode == FLAG(Top) || m_lastTilingMode == FLAG(Bottom)) && (mode == FLAG(Left) || mode == FLAG(Right)))
0872 #undef FLAG
0873         ) {
0874             mode |= m_lastTilingMode;
0875         }
0876         m_quickTileCombineTimer->stop();
0877     }
0878 
0879     m_activeWindow->setQuickTileMode(mode, true);
0880 }
0881 
0882 qreal Workspace::packPositionLeft(const Window *window, qreal oldX, bool leftEdge) const
0883 {
0884     qreal newX = clientArea(MaximizeArea, window).left();
0885     if (oldX <= newX) { // try another Xinerama screen
0886         newX = clientArea(MaximizeArea,
0887                           window,
0888                           QPointF(window->frameGeometry().left() - 1, window->frameGeometry().center().y()))
0889                    .left();
0890     }
0891     if (oldX <= newX) {
0892         return oldX;
0893     }
0894     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0895     for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
0896         if (isIrrelevant(*it, window, desktop)) {
0897             continue;
0898         }
0899         const qreal x = leftEdge ? (*it)->frameGeometry().right() : (*it)->frameGeometry().left() - 1;
0900         if (x > newX && x < oldX
0901             && !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1 // they overlap in Y direction
0902                  || window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) {
0903             newX = x;
0904         }
0905     }
0906     return newX;
0907 }
0908 
0909 qreal Workspace::packPositionRight(const Window *window, qreal oldX, bool rightEdge) const
0910 {
0911     qreal newX = clientArea(MaximizeArea, window).right();
0912     if (oldX >= newX) { // try another Xinerama screen
0913         newX = clientArea(MaximizeArea,
0914                           window,
0915                           QPointF(window->frameGeometry().right(), window->frameGeometry().center().y()))
0916                    .right();
0917     }
0918     if (oldX >= newX) {
0919         return oldX;
0920     }
0921     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0922     for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
0923         if (isIrrelevant(*it, window, desktop)) {
0924             continue;
0925         }
0926         const qreal x = rightEdge ? (*it)->frameGeometry().left() : (*it)->frameGeometry().right() + 1;
0927 
0928         if (x < newX && x > oldX
0929             && !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1
0930                  || window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) {
0931             newX = x;
0932         }
0933     }
0934     return newX;
0935 }
0936 
0937 qreal Workspace::packPositionUp(const Window *window, qreal oldY, bool topEdge) const
0938 {
0939     qreal newY = clientArea(MaximizeArea, window).top();
0940     if (oldY <= newY) { // try another Xinerama screen
0941         newY = clientArea(MaximizeArea,
0942                           window,
0943                           QPointF(window->frameGeometry().center().x(), window->frameGeometry().top() - 1))
0944                    .top();
0945     }
0946     if (oldY <= newY) {
0947         return oldY;
0948     }
0949     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0950     for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
0951         if (isIrrelevant(*it, window, desktop)) {
0952             continue;
0953         }
0954         const qreal y = topEdge ? (*it)->frameGeometry().bottom() : (*it)->frameGeometry().top() - 1;
0955         if (y > newY && y < oldY
0956             && !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1 // they overlap in X direction
0957                  || window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) {
0958             newY = y;
0959         }
0960     }
0961     return newY;
0962 }
0963 
0964 qreal Workspace::packPositionDown(const Window *window, qreal oldY, bool bottomEdge) const
0965 {
0966     qreal newY = clientArea(MaximizeArea, window).bottom();
0967     if (oldY >= newY) { // try another Xinerama screen
0968         newY = clientArea(MaximizeArea,
0969                           window,
0970                           QPointF(window->frameGeometry().center().x(), window->frameGeometry().bottom()))
0971                    .bottom();
0972     }
0973     if (oldY >= newY) {
0974         return oldY;
0975     }
0976     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0977     for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
0978         if (isIrrelevant(*it, window, desktop)) {
0979             continue;
0980         }
0981         const qreal y = bottomEdge ? (*it)->frameGeometry().top() : (*it)->frameGeometry().bottom() + 1;
0982         if (y < newY && y > oldY
0983             && !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1
0984                  || window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) {
0985             newY = y;
0986         }
0987     }
0988     return newY;
0989 }
0990 
0991 #endif
0992 
0993 } // namespace