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