1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6     SPDX-FileCopyrightText: 1997-2002 Cristian Tibirna <tibirna@kde.org>
7     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
8 
9     SPDX-License-Identifier: GPL-2.0-or-later
10 */
11 
12 #include "placement.h"
13 
14 #ifndef KCMRULES
15 #include "workspace.h"
16 #include "x11client.h"
17 #include "cursor.h"
18 #include "options.h"
19 #include "rules.h"
20 #include "screens.h"
21 #include "virtualdesktops.h"
22 #endif
23 
24 #include <QTextStream>
25 #include <QTimer>
26 
27 namespace KWin
28 {
29 
30 #ifndef KCMRULES
31 
KWIN_SINGLETON_FACTORY(Placement)32 KWIN_SINGLETON_FACTORY(Placement)
33 
34 Placement::Placement(QObject*)
35 {
36     reinitCascading(0);
37 }
38 
~Placement()39 Placement::~Placement()
40 {
41     s_self = nullptr;
42 }
43 
44 /**
45  * Places the client \a c according to the workspace's layout policy
46  */
place(AbstractClient * c,const QRect & area)47 void Placement::place(AbstractClient *c, const QRect &area)
48 {
49     Policy policy = c->rules()->checkPlacement(Default);
50     if (policy != Default) {
51         place(c, area, policy);
52         return;
53     }
54 
55     if (c->isUtility())
56         placeUtility(c, area, options->placement());
57     else if (c->isDialog())
58         placeDialog(c, area, options->placement());
59     else if (c->isSplash())
60         placeOnMainWindow(c, area);   // on mainwindow, if any, otherwise centered
61     else if (c->isOnScreenDisplay() || c->isNotification() || c->isCriticalNotification())
62         placeOnScreenDisplay(c, area);
63     else if (c->isTransient() && c->hasTransientPlacementHint())
64         placeTransient(c);
65     else if (c->isTransient() && c->surface())
66         placeDialog(c, area, options->placement());
67     else
68         place(c, area, options->placement());
69 }
70 
place(AbstractClient * c,const QRect & area,Policy policy,Policy nextPlacement)71 void Placement::place(AbstractClient *c, const QRect &area, Policy policy, Policy nextPlacement)
72 {
73     if (policy == Unknown)
74         policy = Default;
75     if (policy == Default)
76         policy = options->placement();
77     if (policy == NoPlacement)
78         return;
79     else if (policy == Random)
80         placeAtRandom(c, area, nextPlacement);
81     else if (policy == Cascade)
82         placeCascaded(c, area, nextPlacement);
83     else if (policy == Centered)
84         placeCentered(c, area, nextPlacement);
85     else if (policy == ZeroCornered)
86         placeZeroCornered(c, area, nextPlacement);
87     else if (policy == UnderMouse)
88         placeUnderMouse(c, area, nextPlacement);
89     else if (policy == OnMainWindow)
90         placeOnMainWindow(c, area, nextPlacement);
91     else if (policy == Maximizing)
92         placeMaximizing(c, area, nextPlacement);
93     else
94         placeSmart(c, area, nextPlacement);
95 
96     if (options->borderSnapZone()) {
97         // snap to titlebar / snap to window borders on inner screen edges
98         const QRect geo(c->moveResizeGeometry());
99         QPoint corner = geo.topLeft();
100         const QMargins frameMargins = c->frameMargins();
101         AbstractClient::Position titlePos = c->titlebarPosition();
102 
103         const QRect fullRect = workspace()->clientArea(FullArea, c);
104         if (!(c->maximizeMode() & MaximizeHorizontal)) {
105             if (titlePos != AbstractClient::PositionRight && geo.right() == fullRect.right()) {
106                 corner.rx() += frameMargins.right();
107             }
108             if (titlePos != AbstractClient::PositionLeft && geo.left() == fullRect.left()) {
109                 corner.rx() -= frameMargins.left();
110             }
111         }
112         if (!(c->maximizeMode() & MaximizeVertical)) {
113             if (titlePos != AbstractClient::PositionBottom && geo.bottom() == fullRect.bottom()) {
114                 corner.ry() += frameMargins.bottom();
115             }
116             if (titlePos != AbstractClient::PositionTop && geo.top() == fullRect.top()) {
117                 corner.ry() -= frameMargins.top();
118             }
119         }
120         c->move(corner);
121     }
122 }
123 
124 /**
125  * Place the client \a c according to a simply "random" placement algorithm.
126  */
placeAtRandom(AbstractClient * c,const QRect & area,Policy)127 void Placement::placeAtRandom(AbstractClient* c, const QRect& area, Policy /*next*/)
128 {
129     Q_ASSERT(area.isValid());
130 
131     const int step  = 24;
132     static int px = step;
133     static int py = 2 * step;
134     int tx, ty;
135 
136     if (px < area.x()) {
137         px = area.x();
138     }
139     if (py < area.y()) {
140         py = area.y();
141     }
142 
143     px += step;
144     py += 2 * step;
145 
146     if (px > area.width() / 2) {
147         px = area.x() + step;
148     }
149     if (py > area.height() / 2) {
150         py = area.y() + step;
151     }
152     tx = px;
153     ty = py;
154     if (tx + c->width() > area.right()) {
155         tx = area.right() - c->width();
156         if (tx < 0)
157             tx = 0;
158         px = area.x();
159     }
160     if (ty + c->height() > area.bottom()) {
161         ty = area.bottom() - c->height();
162         if (ty < 0)
163             ty = 0;
164         py = area.y();
165     }
166     c->move(QPoint(tx, ty));
167 }
168 
169 // TODO: one day, there'll be C++11 ...
isIrrelevant(const AbstractClient * client,const AbstractClient * regarding,int desktop)170 static inline bool isIrrelevant(const AbstractClient *client, const AbstractClient *regarding, int desktop)
171 {
172     if (!client)
173         return true;
174     if (client == regarding)
175         return true;
176     if (!client->isShown(false))
177         return true;
178     if (!client->isOnDesktop(desktop))
179         return true;
180     if (!client->isOnCurrentActivity())
181         return true;
182     if (client->isDesktop())
183         return true;
184     return false;
185 }
186 
187 /**
188  * Place the client \a c according to a really smart placement algorithm :-)
189  */
placeSmart(AbstractClient * c,const QRect & area,Policy)190 void Placement::placeSmart(AbstractClient* c, const QRect& area, Policy /*next*/)
191 {
192     Q_ASSERT(area.isValid());
193 
194     /*
195      * SmartPlacement by Cristian Tibirna (tibirna@kde.org)
196      * adapted for kwm (16-19jan98) and for kwin (16Nov1999) using (with
197      * permission) ideas from fvwm, authored by
198      * Anthony Martin (amartin@engr.csulb.edu).
199      * Xinerama supported added by Balaji Ramani (balaji@yablibli.com)
200      * with ideas from xfce.
201      */
202 
203     if (!c->frameGeometry().isValid()) {
204         return;
205     }
206 
207     const int none = 0, h_wrong = -1, w_wrong = -2; // overlap types
208     long int overlap, min_overlap = 0;
209     int x_optimal, y_optimal;
210     int possible;
211     int desktop = c->desktop() == 0 || c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop();
212 
213     int cxl, cxr, cyt, cyb;     //temp coords
214     int  xl, xr, yt, yb;     //temp coords
215     int basket;                 //temp holder
216 
217     // get the maximum allowed windows space
218     int x = area.left();
219     int y = area.top();
220     x_optimal = x; y_optimal = y;
221 
222     //client gabarit
223     int ch = c->height() - 1;
224     int cw = c->width()  - 1;
225 
226     bool first_pass = true; //CT lame flag. Don't like it. What else would do?
227 
228     //loop over possible positions
229     do {
230         //test if enough room in x and y directions
231         if (y + ch > area.bottom() && ch < area.height()) {
232             overlap = h_wrong; // this throws the algorithm to an exit
233         } else if (x + cw > area.right()) {
234             overlap = w_wrong;
235         } else {
236             overlap = none; //initialize
237 
238             cxl = x; cxr = x + cw;
239             cyt = y; cyb = y + ch;
240             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) {
241                 AbstractClient *client = qobject_cast<AbstractClient*>(*l);
242                 if (isIrrelevant(client, c, desktop)) {
243                     continue;
244                 }
245                 xl = client->x();          yt = client->y();
246                 xr = xl + client->width(); yb = yt + client->height();
247 
248                 //if windows overlap, calc the overall overlapping
249                 if ((cxl < xr) && (cxr > xl) &&
250                         (cyt < yb) && (cyb > yt)) {
251                     xl = qMax(cxl, xl); xr = qMin(cxr, xr);
252                     yt = qMax(cyt, yt); yb = qMin(cyb, yb);
253                     if (client->keepAbove())
254                         overlap += 16 * (xr - xl) * (yb - yt);
255                     else if (client->keepBelow() && !client->isDock()) // ignore KeepBelow windows
256                         overlap += 0; // for placement (see X11Client::belongsToLayer() for Dock)
257                     else
258                         overlap += (xr - xl) * (yb - yt);
259                 }
260             }
261         }
262 
263         //CT first time we get no overlap we stop.
264         if (overlap == none) {
265             x_optimal = x;
266             y_optimal = y;
267             break;
268         }
269 
270         if (first_pass) {
271             first_pass = false;
272             min_overlap = overlap;
273         }
274         //CT save the best position and the minimum overlap up to now
275         else if (overlap >= none && overlap < min_overlap) {
276             min_overlap = overlap;
277             x_optimal = x;
278             y_optimal = y;
279         }
280 
281         // really need to loop? test if there's any overlap
282         if (overlap > none) {
283 
284             possible = area.right();
285             if (possible - cw > x) possible -= cw;
286 
287             // compare to the position of each client on the same desk
288             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) {
289                 AbstractClient *client = qobject_cast<AbstractClient*>(*l);
290                 if (isIrrelevant(client, c, desktop)) {
291                     continue;
292                 }
293 
294                 xl = client->x();          yt = client->y();
295                 xr = xl + client->width(); yb = yt + client->height();
296 
297                 // if not enough room above or under the current tested client
298                 // determine the first non-overlapped x position
299                 if ((y < yb) && (yt < ch + y)) {
300 
301                     if ((xr > x) && (possible > xr)) possible = xr;
302 
303                     basket = xl - cw;
304                     if ((basket > x) && (possible > basket)) possible = basket;
305                 }
306             }
307             x = possible;
308         }
309 
310         // ... else ==> not enough x dimension (overlap was wrong on horizontal)
311         else if (overlap == w_wrong) {
312             x = area.left();
313             possible = area.bottom();
314 
315             if (possible - ch > y) possible -= ch;
316 
317             //test the position of each window on the desk
318             for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) {
319                 AbstractClient *client = qobject_cast<AbstractClient*>(*l);
320                 if (isIrrelevant(client, c, desktop)) {
321                     continue;
322                 }
323 
324                 xl = client->x();          yt = client->y();
325                 xr = xl + client->width(); yb = yt + client->height();
326 
327                 // if not enough room to the left or right of the current tested client
328                 // determine the first non-overlapped y position
329                 if ((yb > y) && (possible > yb)) possible = yb;
330 
331                 basket = yt - ch;
332                 if ((basket > y) && (possible > basket)) possible = basket;
333             }
334             y = possible;
335         }
336     } while ((overlap != none) && (overlap != h_wrong) && (y < area.bottom()));
337 
338     if (ch >= area.height()) {
339         y_optimal = area.top();
340     }
341 
342     // place the window
343     c->move(QPoint(x_optimal, y_optimal));
344 
345 }
346 
reinitCascading(int desktop)347 void Placement::reinitCascading(int desktop)
348 {
349     // desktop == 0 - reinit all
350     if (desktop == 0) {
351         cci.clear();
352         for (uint i = 0; i < VirtualDesktopManager::self()->count(); ++i) {
353             DesktopCascadingInfo inf;
354             inf.pos = QPoint(-1, -1);
355             inf.col = 0;
356             inf.row = 0;
357             cci.append(inf);
358         }
359     } else {
360         cci[desktop - 1].pos = QPoint(-1, -1);
361         cci[desktop - 1].col = cci[desktop - 1].row = 0;
362     }
363 }
364 
cascadeOffset(const AbstractClient * c) const365 QPoint Workspace::cascadeOffset(const AbstractClient *c) const
366 {
367     QRect area = clientArea(PlacementArea, c, c->frameGeometry().center());
368     return QPoint(area.width()/48, area.height()/48);
369 }
370 
371 /**
372  * Place windows in a cascading order, remembering positions for each desktop
373  */
placeCascaded(AbstractClient * c,const QRect & area,Policy nextPlacement)374 void Placement::placeCascaded(AbstractClient *c, const QRect &area, Policy nextPlacement)
375 {
376     Q_ASSERT(area.isValid());
377 
378     if (!c->frameGeometry().isValid()) {
379         return;
380     }
381 
382     /* cascadePlacement by Cristian Tibirna (tibirna@kde.org) (30Jan98)
383      */
384     // work coords
385     int xp, yp;
386 
387     //CT how do I get from the 'Client' class the size that NW squarish "handle"
388     const QPoint delta = workspace()->cascadeOffset(c);
389 
390     const int dn = c->desktop() == 0 || c->isOnAllDesktops() ? (VirtualDesktopManager::self()->current() - 1) : (c->desktop() - 1);
391 
392     // initialize often used vars: width and height of c; we gain speed
393     const int ch = c->height();
394     const int cw = c->width();
395     const int X = area.left();
396     const int Y = area.top();
397     const int H = area.height();
398     const int W = area.width();
399 
400     if (nextPlacement == Unknown)
401         nextPlacement = Smart;
402 
403     //initialize if needed
404     if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < X || cci[dn].pos.y() < Y) {
405         cci[dn].pos = QPoint(X, Y);
406         cci[dn].col = cci[dn].row = 0;
407     }
408 
409 
410     xp = cci[dn].pos.x();
411     yp = cci[dn].pos.y();
412 
413     //here to touch in case people vote for resize on placement
414     if ((yp + ch) > H) yp = Y;
415 
416     if ((xp + cw) > W) {
417         if (!yp) {
418             place(c, area, nextPlacement);
419             return;
420         } else xp = X;
421     }
422 
423     //if this isn't the first window
424     if (cci[dn].pos.x() != X && cci[dn].pos.y() != Y) {
425         /* The following statements cause an internal compiler error with
426          * egcs-2.91.66 on SuSE Linux 6.3. The equivalent forms compile fine.
427          * 22-Dec-1999 CS
428          *
429          * if (xp != X && yp == Y) xp = delta.x() * (++(cci[dn].col));
430          * if (yp != Y && xp == X) yp = delta.y() * (++(cci[dn].row));
431          */
432         if (xp != X && yp == Y) {
433             ++(cci[dn].col);
434             xp = delta.x() * cci[dn].col;
435         }
436         if (yp != Y && xp == X) {
437             ++(cci[dn].row);
438             yp = delta.y() * cci[dn].row;
439         }
440 
441         // last resort: if still doesn't fit, smart place it
442         if (((xp + cw) > W - X) || ((yp + ch) > H - Y)) {
443             place(c, area, nextPlacement);
444             return;
445         }
446     }
447 
448     // place the window
449     c->move(QPoint(xp, yp));
450 
451     // new position
452     cci[dn].pos = QPoint(xp + delta.x(), yp + delta.y());
453 }
454 
455 /**
456  * Place windows centered, on top of all others
457  */
placeCentered(AbstractClient * c,const QRect & area,Policy)458 void Placement::placeCentered(AbstractClient* c, const QRect& area, Policy /*next*/)
459 {
460     Q_ASSERT(area.isValid());
461 
462     if (!c->frameGeometry().isValid()) {
463         return;
464     }
465 
466     const int xp = area.left() + (area.width() - c->width()) / 2;
467     const int yp = area.top() + (area.height() - c->height()) / 2;
468 
469     // place the window
470     c->move(QPoint(xp, yp));
471 }
472 
473 /**
474  * Place windows in the (0,0) corner, on top of all others
475  */
placeZeroCornered(AbstractClient * c,const QRect & area,Policy)476 void Placement::placeZeroCornered(AbstractClient* c, const QRect& area, Policy /*next*/)
477 {
478     Q_ASSERT(area.isValid());
479 
480     // get the maximum allowed windows space and desk's origin
481     c->move(area.topLeft());
482 }
483 
placeUtility(AbstractClient * c,const QRect & area,Policy)484 void Placement::placeUtility(AbstractClient *c, const QRect &area, Policy /*next*/)
485 {
486 // TODO kwin should try to place utility windows next to their mainwindow,
487 // preferably at the right edge, and going down if there are more of them
488 // if there's not enough place outside the mainwindow, it should prefer
489 // top-right corner
490     // use the default placement for now
491     place(c, area, Default);
492 }
493 
placeOnScreenDisplay(AbstractClient * c,const QRect & area)494 void Placement::placeOnScreenDisplay(AbstractClient *c, const QRect &area)
495 {
496     Q_ASSERT(area.isValid());
497 
498     // place at lower area of the screen
499     const int x = area.left() + (area.width() -  c->width())  / 2;
500     const int y = area.top() + 2 * area.height() / 3 - c->height() / 2;
501 
502     c->move(QPoint(x, y));
503 }
504 
placeTransient(AbstractClient * c)505 void Placement::placeTransient(AbstractClient *c)
506 {
507     const auto parent = c->transientFor();
508     const QRect screen =  Workspace::self()->clientArea(parent->isFullScreen() ? FullScreenArea : PlacementArea, parent);
509     c->moveResize(c->transientPlacement(screen));
510 
511     // Potentially a client could set no constraint adjustments
512     // and we'll be offscreen.
513 
514     // The spec implies we should place window the offscreen. However,
515     // practically Qt doesn't set any constraint adjustments yet so we can't.
516     // Also kwin generally doesn't let clients do what they want
517     if (!screen.contains(c->moveResizeGeometry())) {
518         c->keepInArea(screen);
519     }
520 }
521 
placeDialog(AbstractClient * c,const QRect & area,Policy nextPlacement)522 void Placement::placeDialog(AbstractClient *c, const QRect &area, Policy nextPlacement)
523 {
524     placeOnMainWindow(c, area, nextPlacement);
525 }
526 
placeUnderMouse(AbstractClient * c,const QRect & area,Policy)527 void Placement::placeUnderMouse(AbstractClient *c, const QRect &area, Policy /*next*/)
528 {
529     Q_ASSERT(area.isValid());
530 
531     QRect geom = c->frameGeometry();
532     geom.moveCenter(Cursors::self()->mouse()->pos());
533     c->move(geom.topLeft());
534     c->keepInArea(area);   // make sure it's kept inside workarea
535 }
536 
placeOnMainWindow(AbstractClient * c,const QRect & area,Policy nextPlacement)537 void Placement::placeOnMainWindow(AbstractClient *c, const QRect &area, Policy nextPlacement)
538 {
539     Q_ASSERT(area.isValid());
540 
541     if (nextPlacement == Unknown)
542         nextPlacement = Centered;
543     if (nextPlacement == Maximizing)   // maximize if needed
544         placeMaximizing(c, area, NoPlacement);
545     auto mainwindows = c->mainClients();
546     AbstractClient* place_on = nullptr;
547     AbstractClient* place_on2 = nullptr;
548     int mains_count = 0;
549     for (auto it = mainwindows.constBegin();
550             it != mainwindows.constEnd();
551             ++it) {
552         if (mainwindows.count() > 1 && (*it)->isSpecialWindow())
553             continue; // don't consider toolbars etc when placing
554         ++mains_count;
555         place_on2 = *it;
556         if ((*it)->isOnCurrentDesktop()) {
557             if (place_on == nullptr)
558                 place_on = *it;
559             else {
560                 // two or more on current desktop -> center
561                 // That's the default at least. However, with maximizing placement
562                 // policy as the default, the dialog should be either maximized or
563                 // made as large as its maximum size and then placed centered.
564                 // So the nextPlacement argument allows chaining. In this case, nextPlacement
565                 // is Maximizing and it will call placeCentered().
566                 place(c, area, Centered);
567                 return;
568             }
569         }
570     }
571     if (place_on == nullptr) {
572         // 'mains_count' is used because it doesn't include ignored mainwindows
573         if (mains_count != 1) {
574             place(c, area, Centered);
575             return;
576         }
577         place_on = place_on2; // use the only window filtered together with 'mains_count'
578     }
579     if (place_on->isDesktop()) {
580         place(c, area, Centered);
581         return;
582     }
583     QRect geom = c->frameGeometry();
584     geom.moveCenter(place_on->frameGeometry().center());
585     c->move(geom.topLeft());
586     // get area again, because the mainwindow may be on different xinerama screen
587     const QRect placementArea = workspace()->clientArea(PlacementArea, c);
588     c->keepInArea(placementArea);   // make sure it's kept inside workarea
589 }
590 
placeMaximizing(AbstractClient * c,const QRect & area,Policy nextPlacement)591 void Placement::placeMaximizing(AbstractClient *c, const QRect &area, Policy nextPlacement)
592 {
593     Q_ASSERT(area.isValid());
594 
595     if (nextPlacement == Unknown)
596         nextPlacement = Smart;
597     if (c->isMaximizable() && c->maxSize().width() >= area.width() && c->maxSize().height() >= area.height()) {
598         if (workspace()->clientArea(MaximizeArea, c) == area)
599             c->maximize(MaximizeFull);
600         else { // if the geometry doesn't match default maximize area (xinerama case?),
601             // it's probably better to use the given area
602             c->moveResize(area);
603         }
604     } else {
605         c->resizeWithChecks(c->maxSize().boundedTo(area.size()));
606         place(c, area, nextPlacement);
607     }
608 }
609 
cascadeDesktop()610 void Placement::cascadeDesktop()
611 {
612     Workspace *ws = Workspace::self();
613     const int desktop = VirtualDesktopManager::self()->current();
614     reinitCascading(desktop);
615     Q_FOREACH (Toplevel *toplevel, ws->stackingOrder()) {
616         auto client = qobject_cast<AbstractClient*>(toplevel);
617         if (!client ||
618                 (!client->isOnCurrentDesktop()) ||
619                 (client->isMinimized())         ||
620                 (client->isOnAllDesktops())     ||
621                 (!client->isMovable()))
622             continue;
623         const QRect placementArea = workspace()->clientArea(PlacementArea, client);
624         placeCascaded(client, placementArea);
625     }
626 }
627 
unclutterDesktop()628 void Placement::unclutterDesktop()
629 {
630     const auto &clients = Workspace::self()->allClientList();
631     for (int i = clients.size() - 1; i >= 0; i--) {
632         auto client = clients.at(i);
633         if ((!client->isOnCurrentDesktop()) ||
634                 (client->isMinimized())     ||
635                 (client->isOnAllDesktops()) ||
636                 (!client->isMovable()))
637             continue;
638         const QRect placementArea = workspace()->clientArea(PlacementArea, client);
639         placeSmart(client, placementArea);
640     }
641 }
642 
643 #endif
644 
policyToString(Policy policy)645 const char* Placement::policyToString(Policy policy)
646 {
647     const char* const policies[] = {
648         "NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Cascade", "Centered",
649         "ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing"
650     };
651     Q_ASSERT(policy < int(sizeof(policies) / sizeof(policies[ 0 ])));
652     return policies[ policy ];
653 }
654 
655 
656 #ifndef KCMRULES
657 
658 // ********************
659 // Workspace
660 // ********************
661 
packTo(int left,int top)662 void AbstractClient::packTo(int left, int top)
663 {
664     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
665 
666     const AbstractOutput *oldOutput = output();
667     move(QPoint(left, top));
668     if (output() != oldOutput) {
669         workspace()->sendClientToOutput(this, output()); // checks rule validity
670         if (maximizeMode() != MaximizeRestore)
671             checkWorkspacePosition();
672     }
673 }
674 
675 /**
676  * Moves active window left until in bumps into another window or workarea edge.
677  */
slotWindowPackLeft()678 void Workspace::slotWindowPackLeft()
679 {
680     if (active_client && active_client->isMovable()) {
681         const QRect geometry = active_client->moveResizeGeometry();
682         active_client->packTo(packPositionLeft(active_client, geometry.left(), true),
683                               geometry.y());
684     }
685 }
686 
slotWindowPackRight()687 void Workspace::slotWindowPackRight()
688 {
689     if (active_client && active_client->isMovable()) {
690         const QRect geometry = active_client->moveResizeGeometry();
691         active_client->packTo(packPositionRight(active_client, geometry.right(), true) - geometry.width() + 1,
692                               geometry.y());
693     }
694 }
695 
slotWindowPackUp()696 void Workspace::slotWindowPackUp()
697 {
698     if (active_client && active_client->isMovable()) {
699         const QRect geometry = active_client->moveResizeGeometry();
700         active_client->packTo(geometry.x(),
701                               packPositionUp(active_client, geometry.top(), true));
702     }
703 }
704 
slotWindowPackDown()705 void Workspace::slotWindowPackDown()
706 {
707     if (active_client && active_client->isMovable()) {
708         const QRect geometry = active_client->moveResizeGeometry();
709         active_client->packTo(geometry.x(),
710                               packPositionDown(active_client, geometry.bottom(), true) - geometry.height() + 1);
711     }
712 }
713 
slotWindowGrowHorizontal()714 void Workspace::slotWindowGrowHorizontal()
715 {
716     if (active_client)
717         active_client->growHorizontal();
718 }
719 
growHorizontal()720 void AbstractClient::growHorizontal()
721 {
722     if (!isResizable() || isShade())
723         return;
724     QRect geom = moveResizeGeometry();
725     geom.setRight(workspace()->packPositionRight(this, geom.right(), true));
726     QSize adjsize = constrainFrameSize(geom.size(), SizeModeFixedW);
727     if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().width() > 1) { // take care of size increments
728         int newright = workspace()->packPositionRight(this, geom.right() + resizeIncrements().width() - 1, true);
729         // check that it hasn't grown outside of the area, due to size increments
730         // TODO this may be wrong?
731         if (workspace()->clientArea(MovementArea,
732                                     this,
733                                     QPoint((x() + newright) / 2, moveResizeGeometry().center().y())).right() >= newright)
734             geom.setRight(newright);
735     }
736     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
737     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
738     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
739     moveResize(geom);
740 }
741 
slotWindowShrinkHorizontal()742 void Workspace::slotWindowShrinkHorizontal()
743 {
744     if (active_client)
745         active_client->shrinkHorizontal();
746 }
747 
shrinkHorizontal()748 void AbstractClient::shrinkHorizontal()
749 {
750     if (!isResizable() || isShade())
751         return;
752     QRect geom = moveResizeGeometry();
753     geom.setRight(workspace()->packPositionLeft(this, geom.right(), false));
754     if (geom.width() <= 1)
755         return;
756     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
757     if (geom.width() > 20) {
758         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
759         moveResize(geom);
760     }
761 }
762 
slotWindowGrowVertical()763 void Workspace::slotWindowGrowVertical()
764 {
765     if (active_client)
766         active_client->growVertical();
767 }
768 
growVertical()769 void AbstractClient::growVertical()
770 {
771     if (!isResizable() || isShade())
772         return;
773     QRect geom = moveResizeGeometry();
774     geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true));
775     QSize adjsize = constrainFrameSize(geom.size(), SizeModeFixedH);
776     if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().height() > 1) { // take care of size increments
777         int newbottom = workspace()->packPositionDown(this, geom.bottom() + resizeIncrements().height() - 1, true);
778         // check that it hasn't grown outside of the area, due to size increments
779         if (workspace()->clientArea(MovementArea,
780                                     this,
781                                     QPoint(moveResizeGeometry().center().x(), (y() + newbottom) / 2)).bottom() >= newbottom)
782             geom.setBottom(newbottom);
783     }
784     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
785     workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
786     moveResize(geom);
787 }
788 
789 
slotWindowShrinkVertical()790 void Workspace::slotWindowShrinkVertical()
791 {
792     if (active_client)
793         active_client->shrinkVertical();
794 }
795 
shrinkVertical()796 void AbstractClient::shrinkVertical()
797 {
798     if (!isResizable() || isShade())
799         return;
800     QRect geom = moveResizeGeometry();
801     geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false));
802     if (geom.height() <= 1)
803         return;
804     geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
805     if (geom.height() > 20) {
806         workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
807         moveResize(geom);
808     }
809 }
810 
quickTileWindow(QuickTileMode mode)811 void Workspace::quickTileWindow(QuickTileMode mode)
812 {
813     if (!active_client) {
814         return;
815     }
816 
817     // If the user invokes two of these commands in a one second period, try to
818     // combine them together to enable easy and intuitive corner tiling
819 #define FLAG(name) QuickTileMode(QuickTileFlag::name)
820     if (!m_quickTileCombineTimer->isActive()) {
821         m_quickTileCombineTimer->start(1000);
822         m_lastTilingMode = mode;
823     } else {
824         if (
825             ( (m_lastTilingMode == FLAG(Left) || m_lastTilingMode == FLAG(Right)) && (mode == FLAG(Top) || mode == FLAG(Bottom)) )
826             ||
827             ( (m_lastTilingMode == FLAG(Top) || m_lastTilingMode == FLAG(Bottom)) && (mode == FLAG(Left) || mode == FLAG(Right)) )
828 #undef FLAG
829         ) {
830             mode |= m_lastTilingMode;
831         }
832         m_quickTileCombineTimer->stop();
833     }
834 
835     active_client->setQuickTileMode(mode, true);
836 }
837 
packPositionLeft(const AbstractClient * client,int oldX,bool leftEdge) const838 int Workspace::packPositionLeft(const AbstractClient *client, int oldX, bool leftEdge) const
839 {
840     int newX = clientArea(MaximizeArea, client).left();
841     if (oldX <= newX) { // try another Xinerama screen
842         newX = clientArea(MaximizeArea,
843                           client,
844                           QPoint(client->frameGeometry().left() - 1, client->frameGeometry().center().y())).left();
845     }
846     if (client->titlebarPosition() != AbstractClient::PositionLeft) {
847         const int right = newX - client->frameMargins().left();
848         QRect frameGeometry = client->frameGeometry();
849         frameGeometry.moveRight(right);
850         if (screens()->intersecting(frameGeometry) < 2) {
851             newX = right;
852         }
853     }
854     if (oldX <= newX) {
855         return oldX;
856     }
857     const int desktop = client->desktop() == 0 || client->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : client->desktop();
858     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
859         if (isIrrelevant(*it, client, desktop)) {
860             continue;
861         }
862         const int x = leftEdge ? (*it)->frameGeometry().right() + 1 : (*it)->frameGeometry().left() - 1;
863         if (x > newX && x < oldX
864                 && !(client->frameGeometry().top() > (*it)->frameGeometry().bottom()  // they overlap in Y direction
865                      || client->frameGeometry().bottom() < (*it)->frameGeometry().top())) {
866             newX = x;
867         }
868     }
869     return newX;
870 }
871 
packPositionRight(const AbstractClient * client,int oldX,bool rightEdge) const872 int Workspace::packPositionRight(const AbstractClient *client, int oldX, bool rightEdge) const
873 {
874     int newX = clientArea(MaximizeArea, client).right();
875     if (oldX >= newX) { // try another Xinerama screen
876         newX = clientArea(MaximizeArea,
877                           client,
878                           QPoint(client->frameGeometry().right() + 1, client->frameGeometry().center().y())).right();
879     }
880     if (client->titlebarPosition() != AbstractClient::PositionRight) {
881         const int right = newX + client->frameMargins().right();
882         QRect frameGeometry = client->frameGeometry();
883         frameGeometry.moveRight(right);
884         if (screens()->intersecting(frameGeometry) < 2) {
885             newX = right;
886         }
887     }
888     if (oldX >= newX) {
889         return oldX;
890     }
891     const int desktop = client->desktop() == 0 || client->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : client->desktop();
892     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
893         if (isIrrelevant(*it, client, desktop)) {
894             continue;
895         }
896         const int x = rightEdge ? (*it)->frameGeometry().left() - 1 : (*it)->frameGeometry().right() + 1;
897         if (x < newX && x > oldX
898                 && !(client->frameGeometry().top() > (*it)->frameGeometry().bottom()
899                      || client->frameGeometry().bottom() < (*it)->frameGeometry().top())) {
900             newX = x;
901         }
902     }
903     return newX;
904 }
905 
packPositionUp(const AbstractClient * client,int oldY,bool topEdge) const906 int Workspace::packPositionUp(const AbstractClient *client, int oldY, bool topEdge) const
907 {
908     int newY = clientArea(MaximizeArea, client).top();
909     if (oldY <= newY) { // try another Xinerama screen
910         newY = clientArea(MaximizeArea,
911                           client,
912                           QPoint(client->frameGeometry().center().x(), client->frameGeometry().top() - 1)).top();
913     }
914     if (client->titlebarPosition() != AbstractClient::PositionTop) {
915         const int top = newY - client->frameMargins().top();
916         QRect frameGeometry = client->frameGeometry();
917         frameGeometry.moveTop(top);
918         if (screens()->intersecting(frameGeometry) < 2) {
919             newY = top;
920         }
921     }
922     if (oldY <= newY) {
923         return oldY;
924     }
925     const int desktop = client->desktop() == 0 || client->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : client->desktop();
926     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
927         if (isIrrelevant(*it, client, desktop)) {
928             continue;
929         }
930         const int y = topEdge ? (*it)->frameGeometry().bottom() + 1 : (*it)->frameGeometry().top() - 1;
931         if (y > newY && y < oldY
932                 && !(client->frameGeometry().left() > (*it)->frameGeometry().right()  // they overlap in X direction
933                      || client->frameGeometry().right() < (*it)->frameGeometry().left())) {
934             newY = y;
935         }
936     }
937     return newY;
938 }
939 
packPositionDown(const AbstractClient * client,int oldY,bool bottomEdge) const940 int Workspace::packPositionDown(const AbstractClient *client, int oldY, bool bottomEdge) const
941 {
942     int newY = clientArea(MaximizeArea, client).bottom();
943     if (oldY >= newY) { // try another Xinerama screen
944         newY = clientArea(MaximizeArea,
945                           client,
946                           QPoint(client->frameGeometry().center().x(), client->frameGeometry().bottom() + 1)).bottom();
947     }
948     if (client->titlebarPosition() != AbstractClient::PositionBottom) {
949         const int bottom = newY + client->frameMargins().bottom();
950         QRect frameGeometry = client->frameGeometry();
951         frameGeometry.moveBottom(bottom);
952         if (screens()->intersecting(frameGeometry) < 2) {
953             newY = bottom;
954         }
955     }
956     if (oldY >= newY) {
957         return oldY;
958     }
959     const int desktop = client->desktop() == 0 || client->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : client->desktop();
960     for (auto it = m_allClients.constBegin(), end = m_allClients.constEnd(); it != end; ++it) {
961         if (isIrrelevant(*it, client, desktop)) {
962             continue;
963         }
964         const int y = bottomEdge ? (*it)->frameGeometry().top() - 1 : (*it)->frameGeometry().bottom() + 1;
965         if (y < newY && y > oldY
966                 && !(client->frameGeometry().left() > (*it)->frameGeometry().right()
967                      || client->frameGeometry().right() < (*it)->frameGeometry().left())) {
968             newY = y;
969         }
970     }
971     return newY;
972 }
973 
974 #endif
975 
976 } // namespace
977