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