File indexing completed on 2024-02-25 17:23:48

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(0);
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 = window->height() - 1;
0194     int cw = window->width() - 1;
0195 
0196     bool first_pass = true; // CT lame flag. Don't like it. What else would do?
0197 
0198     // loop over possible positions
0199     do {
0200         // test if enough room in x and y directions
0201         if (y + ch > area.bottom() && ch < area.height()) {
0202             overlap = h_wrong; // this throws the algorithm to an exit
0203         } else if (x + cw > area.right()) {
0204             overlap = w_wrong;
0205         } else {
0206             overlap = none; // initialize
0207 
0208             cxl = x;
0209             cxr = x + cw;
0210             cyt = y;
0211             cyb = y + ch;
0212             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
0213                 auto client = *l;
0214                 if (isIrrelevant(client, window, desktop)) {
0215                     continue;
0216                 }
0217                 xl = client->x();
0218                 yt = client->y();
0219                 xr = xl + client->width();
0220                 yb = yt + client->height();
0221 
0222                 // if windows overlap, calc the overall overlapping
0223                 if ((cxl < xr) && (cxr > xl) && (cyt < yb) && (cyb > yt)) {
0224                     xl = std::max(cxl, xl);
0225                     xr = std::min(cxr, xr);
0226                     yt = std::max(cyt, yt);
0227                     yb = std::min(cyb, yb);
0228                     if (client->keepAbove()) {
0229                         overlap += 16 * (xr - xl) * (yb - yt);
0230                     } else if (client->keepBelow() && !client->isDock()) { // ignore KeepBelow windows
0231                         overlap += 0; // for placement (see X11Window::belongsToLayer() for Dock)
0232                     } else {
0233                         overlap += (xr - xl) * (yb - yt);
0234                     }
0235                 }
0236             }
0237         }
0238 
0239         // CT first time we get no overlap we stop.
0240         if (overlap == none) {
0241             x_optimal = x;
0242             y_optimal = y;
0243             break;
0244         }
0245 
0246         if (first_pass) {
0247             first_pass = false;
0248             min_overlap = overlap;
0249         }
0250         // CT save the best position and the minimum overlap up to now
0251         else if (overlap >= none && overlap < min_overlap) {
0252             min_overlap = overlap;
0253             x_optimal = x;
0254             y_optimal = y;
0255         }
0256 
0257         // really need to loop? test if there's any overlap
0258         if (overlap > none) {
0259 
0260             possible = area.right();
0261             if (possible - cw > x) {
0262                 possible -= cw;
0263             }
0264 
0265             // compare to the position of each client on the same desk
0266             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
0267                 auto client = *l;
0268                 if (isIrrelevant(client, window, desktop)) {
0269                     continue;
0270                 }
0271 
0272                 xl = client->x();
0273                 yt = client->y();
0274                 xr = xl + client->width();
0275                 yb = yt + client->height();
0276 
0277                 // if not enough room above or under the current tested client
0278                 // determine the first non-overlapped x position
0279                 if ((y < yb) && (yt < ch + y)) {
0280 
0281                     if ((xr > x) && (possible > xr)) {
0282                         possible = xr;
0283                     }
0284 
0285                     basket = xl - cw;
0286                     if ((basket > x) && (possible > basket)) {
0287                         possible = basket;
0288                     }
0289                 }
0290             }
0291             x = possible;
0292         }
0293 
0294         // ... else ==> not enough x dimension (overlap was wrong on horizontal)
0295         else if (overlap == w_wrong) {
0296             x = area.left();
0297             possible = area.bottom();
0298 
0299             if (possible - ch > y) {
0300                 possible -= ch;
0301             }
0302 
0303             // test the position of each window on the desk
0304             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
0305                 auto client = *l;
0306                 if (isIrrelevant(client, window, desktop)) {
0307                     continue;
0308                 }
0309 
0310                 xl = client->x();
0311                 yt = client->y();
0312                 xr = xl + client->width();
0313                 yb = yt + client->height();
0314 
0315                 // if not enough room to the left or right of the current tested client
0316                 // determine the first non-overlapped y position
0317                 if ((yb > y) && (possible > yb)) {
0318                     possible = yb;
0319                 }
0320 
0321                 basket = yt - ch;
0322                 if ((basket > y) && (possible > basket)) {
0323                     possible = basket;
0324                 }
0325             }
0326             y = possible;
0327         }
0328     } while ((overlap != none) && (overlap != h_wrong) && (y < area.bottom()));
0329 
0330     if (ch >= area.height()) {
0331         y_optimal = area.top();
0332     }
0333 
0334     // place the window
0335     window->move(QPoint(x_optimal, y_optimal));
0336 }
0337 
0338 void Placement::reinitCascading(int desktop)
0339 {
0340     // desktop == 0 - reinit all
0341     if (desktop == 0) {
0342         cci.clear();
0343         for (uint i = 0; i < VirtualDesktopManager::self()->count(); ++i) {
0344             DesktopCascadingInfo inf;
0345             inf.pos = QPoint(-1, -1);
0346             inf.col = 0;
0347             inf.row = 0;
0348             cci.append(inf);
0349         }
0350     } else {
0351         cci[desktop - 1].pos = QPoint(-1, -1);
0352         cci[desktop - 1].col = cci[desktop - 1].row = 0;
0353     }
0354 }
0355 
0356 QPoint Workspace::cascadeOffset(const Window *c) const
0357 {
0358     QRect area = clientArea(PlacementArea, c, c->frameGeometry().center()).toRect();
0359     return QPoint(area.width() / 48, area.height() / 48);
0360 }
0361 
0362 /**
0363  * Place windows in a cascading order, remembering positions for each desktop
0364  */
0365 void Placement::placeCascaded(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0366 {
0367     Q_ASSERT(area.isValid());
0368 
0369     if (!c->frameGeometry().isValid()) {
0370         return;
0371     }
0372 
0373     // CT how do I get from the 'Client' class the size that NW squarish "handle"
0374     const QPoint delta = workspace()->cascadeOffset(c);
0375 
0376     const int dn = c->desktop() == 0 || c->isOnAllDesktops() ? (VirtualDesktopManager::self()->current() - 1) : (c->desktop() - 1);
0377 
0378     if (nextPlacement == PlacementUnknown) {
0379         nextPlacement = PlacementSmart;
0380     }
0381 
0382     // initialize if needed
0383     if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < area.left() || cci[dn].pos.y() < area.top()) {
0384         cci[dn].pos = QPoint(area.left(), area.top());
0385         cci[dn].col = cci[dn].row = 0;
0386     }
0387 
0388     int xp = cci[dn].pos.x();
0389     int yp = cci[dn].pos.y();
0390 
0391     // here to touch in case people vote for resize on placement
0392     if ((yp + c->height()) > area.height()) {
0393         yp = area.top();
0394     }
0395 
0396     if ((xp + c->width()) > area.width()) {
0397         if (!yp) {
0398             place(c, area, nextPlacement);
0399             return;
0400         } else {
0401             xp = area.left();
0402         }
0403     }
0404 
0405     // if this isn't the first window
0406     if (cci[dn].pos.x() != area.left() && cci[dn].pos.y() != area.top()) {
0407         if (xp != area.left() && yp == area.top()) {
0408             cci[dn].col++;
0409             xp = delta.x() * cci[dn].col;
0410         }
0411         if (yp != area.top() && xp == area.left()) {
0412             cci[dn].row++;
0413             yp = delta.y() * cci[dn].row;
0414         }
0415 
0416         // last resort: if still doesn't fit, smart place it
0417         if (((xp + c->width()) > area.width() - area.left()) || ((yp + c->height()) > area.height() - area.top())) {
0418             place(c, area, nextPlacement);
0419             return;
0420         }
0421     }
0422 
0423     // place the window
0424     c->move(QPoint(xp, yp));
0425 
0426     // new position
0427     cci[dn].pos = QPoint(xp + delta.x(), yp + delta.y());
0428 }
0429 
0430 /**
0431  * Place windows centered, on top of all others
0432  */
0433 void Placement::placeCentered(Window *c, const QRectF &area, PlacementPolicy /*next*/)
0434 {
0435     Q_ASSERT(area.isValid());
0436 
0437     if (!c->frameGeometry().isValid()) {
0438         return;
0439     }
0440 
0441     const int xp = area.left() + (area.width() - c->width()) / 2;
0442     const int yp = area.top() + (area.height() - c->height()) / 2;
0443 
0444     // place the window
0445     c->move(QPoint(xp, yp));
0446     cascadeIfCovering(c, area);
0447 }
0448 
0449 /**
0450  * Place windows in the (0,0) corner, on top of all others
0451  */
0452 void Placement::placeZeroCornered(Window *c, const QRect &area, PlacementPolicy /*next*/)
0453 {
0454     Q_ASSERT(area.isValid());
0455 
0456     // get the maximum allowed windows space and desk's origin
0457     c->move(area.topLeft());
0458     cascadeIfCovering(c, area);
0459 }
0460 
0461 void Placement::placeUtility(Window *c, const QRect &area, PlacementPolicy /*next*/)
0462 {
0463     // TODO kwin should try to place utility windows next to their mainwindow,
0464     // preferably at the right edge, and going down if there are more of them
0465     // if there's not enough place outside the mainwindow, it should prefer
0466     // top-right corner
0467     // use the default placement for now
0468     place(c, area, PlacementDefault);
0469 }
0470 
0471 void Placement::placeOnScreenDisplay(Window *c, const QRect &area)
0472 {
0473     Q_ASSERT(area.isValid());
0474 
0475     // place at lower area of the screen
0476     const int x = area.left() + (area.width() - c->width()) / 2;
0477     const int y = area.top() + 2 * area.height() / 3 - c->height() / 2;
0478 
0479     c->move(QPoint(x, y));
0480 }
0481 
0482 void Placement::placeTransient(Window *c)
0483 {
0484     const auto parent = c->transientFor();
0485     const QRectF screen = Workspace::self()->clientArea(parent->isFullScreen() ? FullScreenArea : PlacementArea, parent);
0486     c->moveResize(c->transientPlacement(screen));
0487 
0488     // Potentially a client could set no constraint adjustments
0489     // and we'll be offscreen.
0490 
0491     // The spec implies we should place window the offscreen. However,
0492     // practically Qt doesn't set any constraint adjustments yet so we can't.
0493     // Also kwin generally doesn't let clients do what they want
0494     if (!screen.contains(c->moveResizeGeometry().toAlignedRect())) {
0495         c->keepInArea(screen);
0496     }
0497 }
0498 
0499 void Placement::placeDialog(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0500 {
0501     placeOnMainWindow(c, area, nextPlacement);
0502 }
0503 
0504 void Placement::placeUnderMouse(Window *c, const QRect &area, PlacementPolicy /*next*/)
0505 {
0506     Q_ASSERT(area.isValid());
0507 
0508     QRectF geom = c->frameGeometry();
0509     geom.moveCenter(Cursors::self()->mouse()->pos());
0510     c->move(geom.topLeft().toPoint());
0511     c->keepInArea(area); // make sure it's kept inside workarea
0512     cascadeIfCovering(c, area);
0513 }
0514 
0515 void Placement::placeOnMainWindow(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0516 {
0517     Q_ASSERT(area.isValid());
0518 
0519     if (nextPlacement == PlacementUnknown) {
0520         nextPlacement = PlacementCentered;
0521     }
0522     if (nextPlacement == PlacementMaximizing) { // maximize if needed
0523         placeMaximizing(c, area, PlacementNone);
0524     }
0525     auto mainwindows = c->mainWindows();
0526     Window *place_on = nullptr;
0527     Window *place_on2 = nullptr;
0528     int mains_count = 0;
0529     for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
0530         if (mainwindows.count() > 1 && (*it)->isSpecialWindow()) {
0531             continue; // don't consider toolbars etc when placing
0532         }
0533         ++mains_count;
0534         place_on2 = *it;
0535         if ((*it)->isOnCurrentDesktop()) {
0536             if (place_on == nullptr) {
0537                 place_on = *it;
0538             } else {
0539                 // two or more on current desktop -> center
0540                 // That's the default at least. However, with maximizing placement
0541                 // policy as the default, the dialog should be either maximized or
0542                 // made as large as its maximum size and then placed centered.
0543                 // So the nextPlacement argument allows chaining. In this case, nextPlacement
0544                 // is Maximizing and it will call placeCentered().
0545                 place(c, area, PlacementCentered);
0546                 return;
0547             }
0548         }
0549     }
0550     if (place_on == nullptr) {
0551         // 'mains_count' is used because it doesn't include ignored mainwindows
0552         if (mains_count != 1) {
0553             place(c, area, PlacementCentered);
0554             return;
0555         }
0556         place_on = place_on2; // use the only window filtered together with 'mains_count'
0557     }
0558     if (place_on->isDesktop()) {
0559         place(c, area, PlacementCentered);
0560         return;
0561     }
0562     QRect geom = c->frameGeometry().toRect();
0563     geom.moveCenter(place_on->frameGeometry().center().toPoint());
0564     c->move(geom.topLeft());
0565     // get area again, because the mainwindow may be on different xinerama screen
0566     const QRect placementArea = workspace()->clientArea(PlacementArea, c).toRect();
0567     c->keepInArea(placementArea); // make sure it's kept inside workarea
0568 }
0569 
0570 void Placement::placeMaximizing(Window *c, const QRect &area, PlacementPolicy nextPlacement)
0571 {
0572     Q_ASSERT(area.isValid());
0573 
0574     if (nextPlacement == PlacementUnknown) {
0575         nextPlacement = PlacementSmart;
0576     }
0577     if (c->isMaximizable() && c->maxSize().width() >= area.width() && c->maxSize().height() >= area.height()) {
0578         if (workspace()->clientArea(MaximizeArea, c) == area) {
0579             c->maximize(MaximizeFull);
0580         } else { // if the geometry doesn't match default maximize area (xinerama case?),
0581             // it's probably better to use the given area
0582             c->moveResize(area);
0583         }
0584     } else {
0585         c->moveResize(c->resizeWithChecks(c->moveResizeGeometry(), c->maxSize().boundedTo(area.size())));
0586         place(c, area, nextPlacement);
0587     }
0588 }
0589 
0590 /**
0591  * Cascade the window until it no longer fully overlaps any other window
0592  */
0593 void Placement::cascadeIfCovering(Window *window, const QRectF &area)
0594 {
0595     const QPoint offset = workspace()->cascadeOffset(window);
0596 
0597     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0598 
0599     QRectF possibleGeo = window->frameGeometry();
0600     bool noOverlap = false;
0601 
0602     // cascade until confirmed no total overlap or not enough space to cascade
0603     while (!noOverlap) {
0604         noOverlap = true;
0605         // check current position candidate for overlaps with other windows
0606         for (auto l = workspace()->stackingOrder().crbegin(); l != workspace()->stackingOrder().crend(); ++l) {
0607             auto other = *l;
0608             if (isIrrelevant(other, window, desktop)) {
0609                 continue;
0610             }
0611 
0612             if (possibleGeo.contains(other->frameGeometry())) {
0613                 // placed window would completely overlap the other window: try to cascade it from the topleft of that other window
0614                 noOverlap = false;
0615                 possibleGeo.moveTopLeft(other->pos() + offset);
0616                 if (possibleGeo.right() > area.right() || possibleGeo.bottom() > area.bottom()) {
0617                     // new cascaded geometry would be out of the bounds of the placement area: abort the cascading and keep the window in the original position
0618                     return;
0619                 }
0620                 break;
0621             }
0622         }
0623     }
0624 
0625     window->move(possibleGeo.topLeft());
0626 }
0627 
0628 void Placement::cascadeDesktop()
0629 {
0630     Workspace *ws = Workspace::self();
0631     const int desktop = VirtualDesktopManager::self()->current();
0632     reinitCascading(desktop);
0633     const auto stackingOrder = ws->stackingOrder();
0634     for (Window *window : stackingOrder) {
0635         if (!window->isClient() || (!window->isOnCurrentDesktop()) || (window->isMinimized()) || (window->isOnAllDesktops()) || (!window->isMovable())) {
0636             continue;
0637         }
0638         const QRect placementArea = workspace()->clientArea(PlacementArea, window).toRect();
0639         placeCascaded(window, placementArea);
0640     }
0641 }
0642 
0643 void Placement::unclutterDesktop()
0644 {
0645     const auto &clients = Workspace::self()->allClientList();
0646     for (int i = clients.size() - 1; i >= 0; i--) {
0647         auto client = clients.at(i);
0648         if ((!client->isOnCurrentDesktop()) || (client->isMinimized()) || (client->isOnAllDesktops()) || (!client->isMovable())) {
0649             continue;
0650         }
0651         const QRect placementArea = workspace()->clientArea(PlacementArea, client).toRect();
0652         placeSmart(client, placementArea);
0653     }
0654 }
0655 
0656 #endif
0657 
0658 const char *Placement::policyToString(PlacementPolicy policy)
0659 {
0660     const char *const policies[] = {
0661         "NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Centered",
0662         "ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing"};
0663     Q_ASSERT(policy < int(sizeof(policies) / sizeof(policies[0])));
0664     return policies[policy];
0665 }
0666 
0667 #ifndef KCMRULES
0668 
0669 // ********************
0670 // Workspace
0671 // ********************
0672 
0673 void Window::packTo(qreal left, qreal top)
0674 {
0675     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0676 
0677     const Output *oldOutput = moveResizeOutput();
0678     move(QPoint(left, top));
0679     if (moveResizeOutput() != oldOutput) {
0680         workspace()->sendWindowToOutput(this, moveResizeOutput()); // checks rule validity
0681         if (requestedMaximizeMode() != MaximizeRestore) {
0682             checkWorkspacePosition();
0683         }
0684     }
0685 }
0686 
0687 /**
0688  * Moves active window left until in bumps into another window or workarea edge.
0689  */
0690 void Workspace::slotWindowMoveLeft()
0691 {
0692     if (m_activeWindow && m_activeWindow->isMovable()) {
0693         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0694         m_activeWindow->packTo(packPositionLeft(m_activeWindow, geometry.left(), true),
0695                                geometry.y());
0696     }
0697 }
0698 
0699 void Workspace::slotWindowMoveRight()
0700 {
0701     if (m_activeWindow && m_activeWindow->isMovable()) {
0702         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0703         m_activeWindow->packTo(packPositionRight(m_activeWindow, geometry.right(), true) - geometry.width(),
0704                                geometry.y());
0705     }
0706 }
0707 
0708 void Workspace::slotWindowMoveUp()
0709 {
0710     if (m_activeWindow && m_activeWindow->isMovable()) {
0711         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0712         m_activeWindow->packTo(geometry.x(),
0713                                packPositionUp(m_activeWindow, geometry.top(), true));
0714     }
0715 }
0716 
0717 void Workspace::slotWindowMoveDown()
0718 {
0719     if (m_activeWindow && m_activeWindow->isMovable()) {
0720         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0721         m_activeWindow->packTo(geometry.x(),
0722                                packPositionDown(m_activeWindow, geometry.bottom(), true) - geometry.height());
0723     }
0724 }
0725 
0726 /** Moves the active window to the center of the screen. */
0727 void Workspace::slotWindowCenter()
0728 {
0729     if (m_activeWindow && m_activeWindow->isMovable()) {
0730         const QRectF geometry = m_activeWindow->moveResizeGeometry();
0731         QPointF center = clientArea(MaximizeArea, m_activeWindow).center();
0732         m_activeWindow->packTo(center.x() - (geometry.width() / 2),
0733                                center.y() - (geometry.height() / 2));
0734     }
0735 }
0736 
0737 void Workspace::slotWindowExpandHorizontal()
0738 {
0739     if (m_activeWindow) {
0740         m_activeWindow->growHorizontal();
0741     }
0742 }
0743 
0744 void Window::growHorizontal()
0745 {
0746     if (!isResizable() || isShade()) {
0747         return;
0748     }
0749     QRectF geom = moveResizeGeometry();
0750     geom.setRight(workspace()->packPositionRight(this, geom.right(), true));
0751     QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedW);
0752     if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().width() > 1) { // take care of size increments
0753         qreal newright = workspace()->packPositionRight(this, geom.right() + resizeIncrements().width() - 1, true);
0754         // check that it hasn't grown outside of the area, due to size increments
0755         // TODO this may be wrong?
0756         if (workspace()->clientArea(MovementArea,
0757                                     this,
0758                                     QPoint((x() + newright) / 2, moveResizeGeometry().center().y()))
0759                 .right()
0760             >= newright) {
0761             geom.setRight(newright);
0762         }
0763     }
0764     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
0765     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
0766     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0767     moveResize(geom);
0768 }
0769 
0770 void Workspace::slotWindowShrinkHorizontal()
0771 {
0772     if (m_activeWindow) {
0773         m_activeWindow->shrinkHorizontal();
0774     }
0775 }
0776 
0777 void Window::shrinkHorizontal()
0778 {
0779     if (!isResizable() || isShade()) {
0780         return;
0781     }
0782     QRectF geom = moveResizeGeometry();
0783     geom.setRight(workspace()->packPositionLeft(this, geom.right(), false) + 1);
0784     if (geom.width() <= 1) {
0785         return;
0786     }
0787     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
0788     if (geom.width() > 20) {
0789         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0790         moveResize(geom);
0791     }
0792 }
0793 
0794 void Workspace::slotWindowExpandVertical()
0795 {
0796     if (m_activeWindow) {
0797         m_activeWindow->growVertical();
0798     }
0799 }
0800 
0801 void Window::growVertical()
0802 {
0803     if (!isResizable() || isShade()) {
0804         return;
0805     }
0806     QRectF geom = moveResizeGeometry();
0807     geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true));
0808     QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedH);
0809     if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().height() > 1) { // take care of size increments
0810         qreal newbottom = workspace()->packPositionDown(this, geom.bottom() + resizeIncrements().height(), true);
0811         // check that it hasn't grown outside of the area, due to size increments
0812         if (workspace()->clientArea(MovementArea,
0813                                     this,
0814                                     QPoint(moveResizeGeometry().center().x(), (y() + newbottom) / 2))
0815                 .bottom()
0816             >= newbottom) {
0817             geom.setBottom(newbottom);
0818         }
0819     }
0820     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
0821     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0822     moveResize(geom);
0823 }
0824 
0825 void Workspace::slotWindowShrinkVertical()
0826 {
0827     if (m_activeWindow) {
0828         m_activeWindow->shrinkVertical();
0829     }
0830 }
0831 
0832 void Window::shrinkVertical()
0833 {
0834     if (!isResizable() || isShade()) {
0835         return;
0836     }
0837     QRectF geom = moveResizeGeometry();
0838     geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false) + 1);
0839     if (geom.height() <= 1) {
0840         return;
0841     }
0842     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
0843     if (geom.height() > 20) {
0844         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
0845         moveResize(geom);
0846     }
0847 }
0848 
0849 void Workspace::quickTileWindow(QuickTileMode mode)
0850 {
0851     if (!m_activeWindow) {
0852         return;
0853     }
0854 
0855     // If the user invokes two of these commands in a one second period, try to
0856     // combine them together to enable easy and intuitive corner tiling
0857 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
0858     if (!m_quickTileCombineTimer->isActive()) {
0859         m_quickTileCombineTimer->start(1000);
0860         m_lastTilingMode = mode;
0861     } else {
0862         if (
0863             ((m_lastTilingMode == FLAG(Left) || m_lastTilingMode == FLAG(Right)) && (mode == FLAG(Top) || mode == FLAG(Bottom)))
0864             || ((m_lastTilingMode == FLAG(Top) || m_lastTilingMode == FLAG(Bottom)) && (mode == FLAG(Left) || mode == FLAG(Right)))
0865 #undef FLAG
0866         ) {
0867             mode |= m_lastTilingMode;
0868         }
0869         m_quickTileCombineTimer->stop();
0870     }
0871 
0872     m_activeWindow->setQuickTileMode(mode, true);
0873 }
0874 
0875 qreal Workspace::packPositionLeft(const Window *window, qreal oldX, bool leftEdge) const
0876 {
0877     qreal newX = clientArea(MaximizeArea, window).left();
0878     if (oldX <= newX) { // try another Xinerama screen
0879         newX = clientArea(MaximizeArea,
0880                           window,
0881                           QPointF(window->frameGeometry().left() - 1, window->frameGeometry().center().y()))
0882                    .left();
0883     }
0884     if (oldX <= newX) {
0885         return oldX;
0886     }
0887     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0888     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
0889         if (isIrrelevant(*it, window, desktop)) {
0890             continue;
0891         }
0892         const qreal x = leftEdge ? (*it)->frameGeometry().right() : (*it)->frameGeometry().left() - 1;
0893         if (x > newX && x < oldX
0894             && !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1 // they overlap in Y direction
0895                  || window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) {
0896             newX = x;
0897         }
0898     }
0899     return newX;
0900 }
0901 
0902 qreal Workspace::packPositionRight(const Window *window, qreal oldX, bool rightEdge) const
0903 {
0904     qreal newX = clientArea(MaximizeArea, window).right();
0905     if (oldX >= newX) { // try another Xinerama screen
0906         newX = clientArea(MaximizeArea,
0907                           window,
0908                           QPointF(window->frameGeometry().right(), window->frameGeometry().center().y()))
0909                    .right();
0910     }
0911     if (oldX >= newX) {
0912         return oldX;
0913     }
0914     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0915     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
0916         if (isIrrelevant(*it, window, desktop)) {
0917             continue;
0918         }
0919         const qreal x = rightEdge ? (*it)->frameGeometry().left() : (*it)->frameGeometry().right() + 1;
0920 
0921         if (x < newX && x > oldX
0922             && !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1
0923                  || window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) {
0924             newX = x;
0925         }
0926     }
0927     return newX;
0928 }
0929 
0930 qreal Workspace::packPositionUp(const Window *window, qreal oldY, bool topEdge) const
0931 {
0932     qreal newY = clientArea(MaximizeArea, window).top();
0933     if (oldY <= newY) { // try another Xinerama screen
0934         newY = clientArea(MaximizeArea,
0935                           window,
0936                           QPointF(window->frameGeometry().center().x(), window->frameGeometry().top() - 1))
0937                    .top();
0938     }
0939     if (oldY <= newY) {
0940         return oldY;
0941     }
0942     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0943     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
0944         if (isIrrelevant(*it, window, desktop)) {
0945             continue;
0946         }
0947         const qreal y = topEdge ? (*it)->frameGeometry().bottom() : (*it)->frameGeometry().top() - 1;
0948         if (y > newY && y < oldY
0949             && !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1 // they overlap in X direction
0950                  || window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) {
0951             newY = y;
0952         }
0953     }
0954     return newY;
0955 }
0956 
0957 qreal Workspace::packPositionDown(const Window *window, qreal oldY, bool bottomEdge) const
0958 {
0959     qreal newY = clientArea(MaximizeArea, window).bottom();
0960     if (oldY >= newY) { // try another Xinerama screen
0961         newY = clientArea(MaximizeArea,
0962                           window,
0963                           QPointF(window->frameGeometry().center().x(), window->frameGeometry().bottom()))
0964                    .bottom();
0965     }
0966     if (oldY >= newY) {
0967         return oldY;
0968     }
0969     VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
0970     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
0971         if (isIrrelevant(*it, window, desktop)) {
0972             continue;
0973         }
0974         const qreal y = bottomEdge ? (*it)->frameGeometry().top() : (*it)->frameGeometry().bottom() + 1;
0975         if (y < newY && y > oldY
0976             && !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1
0977                  || window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) {
0978             newY = y;
0979         }
0980     }
0981     return newY;
0982 }
0983 
0984 #endif
0985 
0986 } // namespace