File indexing completed on 2024-11-10 13:22:46
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