kwin/src/placement.cpp
Vlad Zahorodnii f8d8589ce7 Add missing size check in Placement::placeZeroCornered()
The size is required to cascade the window.
2024-08-23 09:01:56 +00:00

1021 lines
33 KiB
C++

/*
KWin - the KDE window manager
This file is part of the KDE project.
SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
SPDX-FileCopyrightText: 1997-2002 Cristian Tibirna <tibirna@kde.org>
SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
SPDX-FileCopyrightText: 2022 Natalie Clarius <natalie_clarius@yahoo.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "placement.h"
#ifndef KCMRULES
#include "cursor.h"
#include "options.h"
#include "rules.h"
#include "virtualdesktops.h"
#include "workspace.h"
#if KWIN_BUILD_X11
#include "x11window.h"
#endif
#endif
#include "window.h"
#include <QTextStream>
#include <QTimer>
namespace KWin
{
#ifndef KCMRULES
Placement::Placement()
{
reinitCascading();
}
/**
* Places the client \a c according to the workspace's layout policy
*/
void Placement::place(Window *c, const QRectF &area)
{
PlacementPolicy policy = c->rules()->checkPlacement(PlacementDefault);
if (policy != PlacementDefault) {
place(c, area, policy);
return;
}
if (c->isUtility()) {
placeUtility(c, area.toRect(), options->placement());
} else if (c->isDialog()) {
placeDialog(c, area.toRect(), options->placement());
} else if (c->isSplash()) {
placeOnMainWindow(c, area.toRect()); // on mainwindow, if any, otherwise centered
} else if (c->isOnScreenDisplay() || c->isNotification() || c->isCriticalNotification()) {
placeOnScreenDisplay(c, area.toRect());
} else if (c->isTransient() && c->hasTransientPlacementHint()) {
placeTransient(c);
} else if (c->isTransient() && c->surface()) {
placeDialog(c, area.toRect(), options->placement());
} else {
place(c, area, options->placement());
}
}
void Placement::place(Window *c, const QRectF &area, PlacementPolicy policy, PlacementPolicy nextPlacement)
{
if (policy == PlacementUnknown || policy == PlacementDefault) {
policy = options->placement();
}
switch (policy) {
case PlacementNone:
return;
case PlacementRandom:
placeAtRandom(c, area.toRect(), nextPlacement);
break;
case PlacementCentered:
placeCentered(c, area, nextPlacement);
break;
case PlacementZeroCornered:
placeZeroCornered(c, area.toRect(), nextPlacement);
break;
case PlacementUnderMouse:
placeUnderMouse(c, area.toRect(), nextPlacement);
break;
case PlacementOnMainWindow:
placeOnMainWindow(c, area.toRect(), nextPlacement);
break;
case PlacementMaximizing:
placeMaximizing(c, area.toRect(), nextPlacement);
break;
default:
placeSmart(c, area, nextPlacement);
}
}
/**
* Place the client \a c according to a simply "random" placement algorithm.
*/
void Placement::placeAtRandom(Window *c, const QRect &area, PlacementPolicy /*next*/)
{
Q_ASSERT(area.isValid());
const QSizeF size = c->size();
if (size.isEmpty()) {
return;
}
const int step = 24;
static int px = step;
static int py = 2 * step;
int tx, ty;
if (px < area.x()) {
px = area.x();
}
if (py < area.y()) {
py = area.y();
}
px += step;
py += 2 * step;
if (px > area.width() / 2) {
px = area.x() + step;
}
if (py > area.height() / 2) {
py = area.y() + step;
}
tx = px;
ty = py;
if (tx + size.width() > area.right()) {
tx = area.right() - size.width();
if (tx < 0) {
tx = 0;
}
px = area.x();
}
if (ty + size.height() > area.bottom()) {
ty = area.bottom() - size.height();
if (ty < 0) {
ty = 0;
}
py = area.y();
}
const QRectF placed = cascadeIfCovering(c, QRectF(QPointF(tx, ty), size), area);
c->move(placed.topLeft());
}
static inline bool isIrrelevant(const Window *window, const Window *regarding, VirtualDesktop *desktop)
{
return window == regarding
|| !window->isClient()
|| !window->isShown()
|| window->isShade()
|| !window->isOnDesktop(desktop)
|| !window->isOnCurrentActivity()
|| window->isDesktop();
};
/**
* Place the client \a c according to a really smart placement algorithm :-)
*/
void Placement::placeSmart(Window *window, const QRectF &area, PlacementPolicy /*next*/)
{
Q_ASSERT(area.isValid());
/*
* SmartPlacement by Cristian Tibirna (tibirna@kde.org)
* adapted for kwm (16-19jan98) and for kwin (16Nov1999) using (with
* permission) ideas from fvwm, authored by
* Anthony Martin (amartin@engr.csulb.edu).
* Xinerama supported added by Balaji Ramani (balaji@yablibli.com)
* with ideas from xfce.
*/
const QSizeF size = window->size();
if (size.isEmpty()) {
return;
}
const int none = 0, h_wrong = -1, w_wrong = -2; // overlap types
long int overlap, min_overlap = 0;
int x_optimal, y_optimal;
int possible;
VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
int cxl, cxr, cyt, cyb; // temp coords
int xl, xr, yt, yb; // temp coords
int basket; // temp holder
// get the maximum allowed windows space
int x = area.left();
int y = area.top();
x_optimal = x;
y_optimal = y;
// client gabarit
int ch = std::ceil(size.height());
int cw = std::ceil(size.width());
// Explicitly converts those to int to avoid accidentally
// mixing ints and qreal in the calculations below.
int area_xr = std::floor(area.x() + area.width());
int area_yb = std::floor(area.y() + area.height());
bool first_pass = true; // CT lame flag. Don't like it. What else would do?
// loop over possible positions
do {
// test if enough room in x and y directions
if (y + ch > area_yb && ch < area.height()) {
overlap = h_wrong; // this throws the algorithm to an exit
} else if (x + cw > area_xr) {
overlap = w_wrong;
} else {
overlap = none; // initialize
cxl = x;
cxr = x + cw;
cyt = y;
cyb = y + ch;
for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
auto client = *l;
if (isIrrelevant(client, window, desktop)) {
continue;
}
xl = client->x();
yt = client->y();
xr = xl + client->width();
yb = yt + client->height();
// if windows overlap, calc the overall overlapping
if ((cxl < xr) && (cxr > xl) && (cyt < yb) && (cyb > yt)) {
xl = std::max(cxl, xl);
xr = std::min(cxr, xr);
yt = std::max(cyt, yt);
yb = std::min(cyb, yb);
if (client->keepAbove()) {
overlap += 16 * (xr - xl) * (yb - yt);
} else if (client->keepBelow() && !client->isDock()) { // ignore KeepBelow windows
overlap += 0; // for placement (see X11Window::belongsToLayer() for Dock)
} else {
overlap += (xr - xl) * (yb - yt);
}
}
}
}
// CT first time we get no overlap we stop.
if (overlap == none) {
x_optimal = x;
y_optimal = y;
break;
}
if (first_pass) {
first_pass = false;
min_overlap = overlap;
}
// CT save the best position and the minimum overlap up to now
else if (overlap >= none && overlap < min_overlap) {
min_overlap = overlap;
x_optimal = x;
y_optimal = y;
}
// really need to loop? test if there's any overlap
if (overlap > none) {
possible = area_xr;
if (possible - cw > x) {
possible -= cw;
}
// compare to the position of each client on the same desk
for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
auto client = *l;
if (isIrrelevant(client, window, desktop)) {
continue;
}
xl = client->x();
yt = client->y();
xr = xl + client->width();
yb = yt + client->height();
// if not enough room above or under the current tested client
// determine the first non-overlapped x position
if ((y < yb) && (yt < ch + y)) {
if ((xr > x) && (possible > xr)) {
possible = xr;
}
basket = xl - cw;
if ((basket > x) && (possible > basket)) {
possible = basket;
}
}
}
x = possible;
}
// ... else ==> not enough x dimension (overlap was wrong on horizontal)
else if (overlap == w_wrong) {
x = area.left();
possible = area_yb;
if (possible - ch > y) {
possible -= ch;
}
// test the position of each window on the desk
for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd(); ++l) {
auto client = *l;
if (isIrrelevant(client, window, desktop)) {
continue;
}
xl = client->x();
yt = client->y();
xr = xl + client->width();
yb = yt + client->height();
// if not enough room to the left or right of the current tested client
// determine the first non-overlapped y position
if ((yb > y) && (possible > yb)) {
possible = yb;
}
basket = yt - ch;
if ((basket > y) && (possible > basket)) {
possible = basket;
}
}
y = possible;
}
} while ((overlap != none) && (overlap != h_wrong) && (y < area_yb));
if (ch >= area.height()) {
y_optimal = area.top();
}
// place the window
window->move(QPoint(x_optimal, y_optimal));
}
void Placement::reinitCascading()
{
cci.clear();
const auto desktops = VirtualDesktopManager::self()->desktops();
for (VirtualDesktop *desktop : desktops) {
reinitCascading(desktop);
}
}
void Placement::reinitCascading(VirtualDesktop *desktop)
{
cci[desktop] = DesktopCascadingInfo{
.pos = QPoint(-1, -1),
.col = 0,
.row = 0,
};
}
QPoint Workspace::cascadeOffset(const QRectF &area) const
{
return QPoint(area.width() / 48, area.height() / 48);
}
/**
* Place windows in a cascading order, remembering positions for each desktop
*/
void Placement::placeCascaded(Window *c, const QRect &area, PlacementPolicy nextPlacement)
{
Q_ASSERT(area.isValid());
const QSizeF size = c->size();
if (size.isEmpty()) {
return;
}
// CT how do I get from the 'Client' class the size that NW squarish "handle"
const QPoint delta = workspace()->cascadeOffset(area);
VirtualDesktop *dn = c->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : c->desktops().constLast();
if (nextPlacement == PlacementUnknown) {
nextPlacement = PlacementSmart;
}
// initialize if needed
if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < area.left() || cci[dn].pos.y() < area.top()) {
cci[dn].pos = QPoint(area.left(), area.top());
cci[dn].col = cci[dn].row = 0;
}
int xp = cci[dn].pos.x();
int yp = cci[dn].pos.y();
// here to touch in case people vote for resize on placement
if ((yp + size.height()) > area.height()) {
yp = area.top();
}
if ((xp + size.width()) > area.width()) {
if (!yp) {
place(c, area, nextPlacement);
return;
} else {
xp = area.left();
}
}
// if this isn't the first window
if (cci[dn].pos.x() != area.left() && cci[dn].pos.y() != area.top()) {
if (xp != area.left() && yp == area.top()) {
cci[dn].col++;
xp = delta.x() * cci[dn].col;
}
if (yp != area.top() && xp == area.left()) {
cci[dn].row++;
yp = delta.y() * cci[dn].row;
}
// last resort: if still doesn't fit, smart place it
if (((xp + size.width()) > area.width() - area.left()) || ((yp + size.height()) > area.height() - area.top())) {
place(c, area, nextPlacement);
return;
}
}
// place the window
c->move(QPoint(xp, yp));
// new position
cci[dn].pos = QPoint(xp + delta.x(), yp + delta.y());
}
/**
* Place windows centered, on top of all others
*/
void Placement::placeCentered(Window *c, const QRectF &area, PlacementPolicy /*next*/)
{
Q_ASSERT(area.isValid());
const QSizeF size = c->size();
if (size.isEmpty()) {
return;
}
const QPoint position(std::max(area.left() + (area.width() - size.width()) / 2, area.left()),
std::max(area.top() + (area.height() - size.height()) / 2, area.top()));
const QRectF placed = cascadeIfCovering(c, QRectF(position, size), area);
c->move(placed.topLeft());
}
/**
* Place windows in the (0,0) corner, on top of all others
*/
void Placement::placeZeroCornered(Window *c, const QRect &area, PlacementPolicy /*next*/)
{
Q_ASSERT(area.isValid());
const QSizeF size = c->size();
if (size.isEmpty()) {
return;
}
// get the maximum allowed windows space and desk's origin
const QRectF placed = cascadeIfCovering(c, QRectF(area.topLeft(), size), area);
c->move(placed.topLeft());
}
void Placement::placeUtility(Window *c, const QRect &area, PlacementPolicy /*next*/)
{
// TODO kwin should try to place utility windows next to their mainwindow,
// preferably at the right edge, and going down if there are more of them
// if there's not enough place outside the mainwindow, it should prefer
// top-right corner
// use the default placement for now
place(c, area, PlacementDefault);
}
void Placement::placeOnScreenDisplay(Window *c, const QRect &area)
{
Q_ASSERT(area.isValid());
const QSizeF size = c->size();
if (size.isEmpty()) {
return;
}
// place at lower area of the screen
const int x = area.left() + (area.width() - size.width()) / 2;
const int y = area.top() + 2 * area.height() / 3 - size.height() / 2;
c->move(QPoint(x, y));
}
void Placement::placeTransient(Window *c)
{
c->moveResize(c->transientPlacement());
}
void Placement::placeDialog(Window *c, const QRect &area, PlacementPolicy nextPlacement)
{
placeOnMainWindow(c, area, nextPlacement);
}
void Placement::placeUnderMouse(Window *c, const QRect &area, PlacementPolicy /*next*/)
{
const QSizeF size = c->size();
if (size.isEmpty()) {
return;
}
const QPointF cursorPos = Cursors::self()->mouse()->pos();
const QRectF centered(cursorPos.x() - size.width() / 2,
cursorPos.y() - size.height() / 2,
size.width(),
size.height());
const QRectF screenArea = workspace()->clientArea(PlacementArea, c, cursorPos);
const QRectF placed = cascadeIfCovering(c, c->keepInArea(centered, screenArea), screenArea);
c->move(placed.topLeft());
}
void Placement::placeOnMainWindow(Window *c, const QRect &area, PlacementPolicy nextPlacement)
{
Q_ASSERT(area.isValid());
if (nextPlacement == PlacementUnknown) {
nextPlacement = PlacementCentered;
}
if (nextPlacement == PlacementMaximizing) { // maximize if needed
placeMaximizing(c, area, PlacementNone);
}
const QSizeF size = c->size();
if (size.isEmpty()) {
return;
}
auto mainwindows = c->mainWindows();
Window *place_on = nullptr;
Window *place_on2 = nullptr;
int mains_count = 0;
for (auto it = mainwindows.constBegin(); it != mainwindows.constEnd(); ++it) {
if (mainwindows.count() > 1 && (*it)->isSpecialWindow()) {
continue; // don't consider toolbars etc when placing
}
++mains_count;
place_on2 = *it;
if ((*it)->isOnCurrentDesktop()) {
if (place_on == nullptr) {
place_on = *it;
} else {
// two or more on current desktop -> center
// That's the default at least. However, with maximizing placement
// policy as the default, the dialog should be either maximized or
// made as large as its maximum size and then placed centered.
// So the nextPlacement argument allows chaining. In this case, nextPlacement
// is Maximizing and it will call placeCentered().
place(c, area, PlacementCentered);
return;
}
}
}
if (place_on == nullptr) {
// 'mains_count' is used because it doesn't include ignored mainwindows
if (mains_count != 1) {
place(c, area, PlacementCentered);
return;
}
place_on = place_on2; // use the only window filtered together with 'mains_count'
}
if (place_on->isDesktop()) {
place(c, area, PlacementCentered);
return;
}
QRectF geom(QPointF(0, 0), size);
geom.moveCenter(place_on->frameGeometry().center().toPoint());
// get area again, because the mainwindow may be on different xinerama screen
const QRect placementArea = workspace()->clientArea(PlacementArea, c, geom.center()).toRect();
c->move(c->keepInArea(geom, placementArea).topLeft()); // make sure it's kept inside workarea
}
void Placement::placeMaximizing(Window *c, const QRect &area, PlacementPolicy nextPlacement)
{
Q_ASSERT(area.isValid());
if (nextPlacement == PlacementUnknown) {
nextPlacement = PlacementCentered;
}
if (c->isMaximizable()) {
c->maximize(MaximizeFull);
} else {
place(c, area, nextPlacement);
}
}
/**
* Cascade the window until it no longer fully overlaps any other window
*/
QRectF Placement::cascadeIfCovering(Window *window, const QRectF &geometry, const QRectF &area) const
{
const QPoint offset = workspace()->cascadeOffset(area);
VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
QRectF possibleGeo = geometry;
bool noOverlap = false;
// cascade until confirmed no total overlap or not enough space to cascade
while (!noOverlap) {
noOverlap = true;
QRectF coveredArea;
// check current position candidate for overlaps with other windows
for (auto l = workspace()->stackingOrder().crbegin(); l != workspace()->stackingOrder().crend(); ++l) {
auto other = *l;
if (isIrrelevant(other, window, desktop) || !other->frameGeometry().intersects(possibleGeo)) {
continue;
}
if (possibleGeo.contains(other->frameGeometry()) && !coveredArea.contains(other->frameGeometry())) {
// placed window would completely overlap another window which is not already
// covered by other windows: try to cascade it from the topleft of that other
// window
noOverlap = false;
possibleGeo.moveTopLeft(other->pos() + offset);
if (possibleGeo.right() > area.right() || possibleGeo.bottom() > area.bottom()) {
// new cascaded geometry would be out of the bounds of the placement area:
// abort the cascading and keep the window in the original position
return geometry;
}
break;
}
// keep track of the area occupied by other windows as we go from top to bottom
// in the stacking order, so we don't need to bother trying to avoid overlap with
// windows which are already covered up by other windows anyway
coveredArea |= other->frameGeometry();
if (coveredArea.contains(area)) {
break;
}
}
}
return possibleGeo;
}
void Placement::cascadeDesktop()
{
Workspace *ws = Workspace::self();
reinitCascading(VirtualDesktopManager::self()->currentDesktop());
const auto stackingOrder = ws->stackingOrder();
for (Window *window : stackingOrder) {
if (!window->isClient() || (!window->isOnCurrentDesktop()) || (window->isMinimized()) || (window->isOnAllDesktops()) || (!window->isMovable())) {
continue;
}
const QRect placementArea = workspace()->clientArea(PlacementArea, window).toRect();
placeCascaded(window, placementArea);
}
}
void Placement::unclutterDesktop()
{
const auto &windows = Workspace::self()->windows();
for (int i = windows.size() - 1; i >= 0; i--) {
auto window = windows.at(i);
if (!window->isClient()) {
continue;
}
if ((!window->isOnCurrentDesktop()) || (window->isMinimized()) || (window->isOnAllDesktops()) || (!window->isMovable())) {
continue;
}
const QRect placementArea = workspace()->clientArea(PlacementArea, window).toRect();
placeSmart(window, placementArea);
}
}
#endif
const char *Placement::policyToString(PlacementPolicy policy)
{
const char *const policies[] = {
"NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Centered",
"ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing"};
Q_ASSERT(policy < int(sizeof(policies) / sizeof(policies[0])));
return policies[policy];
}
#ifndef KCMRULES
// ********************
// Workspace
// ********************
void Window::packTo(qreal left, qreal top)
{
workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
const Output *oldOutput = moveResizeOutput();
move(QPoint(left, top));
if (moveResizeOutput() != oldOutput) {
sendToOutput(moveResizeOutput()); // checks rule validity
if (requestedMaximizeMode() != MaximizeRestore) {
checkWorkspacePosition();
}
}
}
/**
* Moves active window left until in bumps into another window or workarea edge.
*/
void Workspace::slotWindowMoveLeft()
{
if (m_activeWindow && m_activeWindow->isMovable()) {
const QRectF geometry = m_activeWindow->moveResizeGeometry();
m_activeWindow->packTo(packPositionLeft(m_activeWindow, geometry.left(), true),
geometry.y());
}
}
void Workspace::slotWindowMoveRight()
{
if (m_activeWindow && m_activeWindow->isMovable()) {
const QRectF geometry = m_activeWindow->moveResizeGeometry();
m_activeWindow->packTo(packPositionRight(m_activeWindow, geometry.right(), true) - geometry.width(),
geometry.y());
}
}
void Workspace::slotWindowMoveUp()
{
if (m_activeWindow && m_activeWindow->isMovable()) {
const QRectF geometry = m_activeWindow->moveResizeGeometry();
m_activeWindow->packTo(geometry.x(),
packPositionUp(m_activeWindow, geometry.top(), true));
}
}
void Workspace::slotWindowMoveDown()
{
if (m_activeWindow && m_activeWindow->isMovable()) {
const QRectF geometry = m_activeWindow->moveResizeGeometry();
m_activeWindow->packTo(geometry.x(),
packPositionDown(m_activeWindow, geometry.bottom(), true) - geometry.height());
}
}
/** Moves the active window to the center of the screen. */
void Workspace::slotWindowCenter()
{
if (m_activeWindow && m_activeWindow->isMovable()) {
const QRectF geometry = m_activeWindow->moveResizeGeometry();
QPointF center = clientArea(MaximizeArea, m_activeWindow).center();
m_activeWindow->packTo(center.x() - (geometry.width() / 2),
center.y() - (geometry.height() / 2));
}
}
void Workspace::slotWindowExpandHorizontal()
{
if (m_activeWindow) {
m_activeWindow->growHorizontal();
}
}
void Window::growHorizontal()
{
if (!isResizable() || isShade()) {
return;
}
QRectF geom = moveResizeGeometry();
geom.setRight(workspace()->packPositionRight(this, geom.right(), true));
QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedW);
if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().width() > 1) { // take care of size increments
qreal newright = workspace()->packPositionRight(this, geom.right() + resizeIncrements().width() - 1, true);
// check that it hasn't grown outside of the area, due to size increments
// TODO this may be wrong?
if (workspace()->clientArea(MovementArea,
this,
QPoint((x() + newright) / 2, moveResizeGeometry().center().y()))
.right()
>= newright) {
geom.setRight(newright);
}
}
geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
moveResize(geom);
}
void Workspace::slotWindowShrinkHorizontal()
{
if (m_activeWindow) {
m_activeWindow->shrinkHorizontal();
}
}
void Window::shrinkHorizontal()
{
if (!isResizable() || isShade()) {
return;
}
QRectF geom = moveResizeGeometry();
geom.setRight(workspace()->packPositionLeft(this, geom.right(), false) + 1);
if (geom.width() <= 1) {
return;
}
geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
if (geom.width() > 20) {
workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
moveResize(geom);
}
}
void Workspace::slotWindowExpandVertical()
{
if (m_activeWindow) {
m_activeWindow->growVertical();
}
}
void Window::growVertical()
{
if (!isResizable() || isShade()) {
return;
}
QRectF geom = moveResizeGeometry();
geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true));
QSizeF adjsize = constrainFrameSize(geom.size(), SizeModeFixedH);
if (moveResizeGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().height() > 1) { // take care of size increments
qreal newbottom = workspace()->packPositionDown(this, geom.bottom() + resizeIncrements().height(), true);
// check that it hasn't grown outside of the area, due to size increments
if (workspace()->clientArea(MovementArea,
this,
QPoint(moveResizeGeometry().center().x(), (y() + newbottom) / 2))
.bottom()
>= newbottom) {
geom.setBottom(newbottom);
}
}
geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
moveResize(geom);
}
void Workspace::slotWindowShrinkVertical()
{
if (m_activeWindow) {
m_activeWindow->shrinkVertical();
}
}
void Window::shrinkVertical()
{
if (!isResizable() || isShade()) {
return;
}
QRectF geom = moveResizeGeometry();
geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false) + 1);
if (geom.height() <= 1) {
return;
}
geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
if (geom.height() > 20) {
workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
moveResize(geom);
}
}
void Workspace::quickTileWindow(QuickTileMode mode)
{
if (!m_activeWindow) {
return;
}
// If the user invokes two of these commands in a one second period, try to
// combine them together to enable easy and intuitive corner tiling
#define FLAG(name) QuickTileMode(QuickTileFlag::name)
if (!m_quickTileCombineTimer->isActive()) {
m_quickTileCombineTimer->start(1000);
m_lastTilingMode = mode;
} else {
if (
((m_lastTilingMode == FLAG(Left) || m_lastTilingMode == FLAG(Right)) && (mode == FLAG(Top) || mode == FLAG(Bottom)))
|| ((m_lastTilingMode == FLAG(Top) || m_lastTilingMode == FLAG(Bottom)) && (mode == FLAG(Left) || mode == FLAG(Right)))
#undef FLAG
) {
mode |= m_lastTilingMode;
}
m_quickTileCombineTimer->stop();
}
m_activeWindow->handleQuickTileShortcut(mode);
}
qreal Workspace::packPositionLeft(const Window *window, qreal oldX, bool leftEdge) const
{
qreal newX = clientArea(MaximizeArea, window).left();
if (oldX <= newX) { // try another Xinerama screen
newX = clientArea(MaximizeArea,
window,
QPointF(window->frameGeometry().left() - 1, window->frameGeometry().center().y()))
.left();
}
if (oldX <= newX) {
return oldX;
}
VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
if (isIrrelevant(*it, window, desktop)) {
continue;
}
const qreal x = leftEdge ? (*it)->frameGeometry().right() : (*it)->frameGeometry().left() - 1;
if (x > newX && x < oldX
&& !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1 // they overlap in Y direction
|| window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) {
newX = x;
}
}
return newX;
}
qreal Workspace::packPositionRight(const Window *window, qreal oldX, bool rightEdge) const
{
qreal newX = clientArea(MaximizeArea, window).right();
if (oldX >= newX) { // try another Xinerama screen
newX = clientArea(MaximizeArea,
window,
QPointF(window->frameGeometry().right(), window->frameGeometry().center().y()))
.right();
}
if (oldX >= newX) {
return oldX;
}
VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
if (isIrrelevant(*it, window, desktop)) {
continue;
}
const qreal x = rightEdge ? (*it)->frameGeometry().left() : (*it)->frameGeometry().right() + 1;
if (x < newX && x > oldX
&& !(window->frameGeometry().top() > (*it)->frameGeometry().bottom() - 1
|| window->frameGeometry().bottom() - 1 < (*it)->frameGeometry().top())) {
newX = x;
}
}
return newX;
}
qreal Workspace::packPositionUp(const Window *window, qreal oldY, bool topEdge) const
{
qreal newY = clientArea(MaximizeArea, window).top();
if (oldY <= newY) { // try another Xinerama screen
newY = clientArea(MaximizeArea,
window,
QPointF(window->frameGeometry().center().x(), window->frameGeometry().top() - 1))
.top();
}
if (oldY <= newY) {
return oldY;
}
VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
if (isIrrelevant(*it, window, desktop)) {
continue;
}
const qreal y = topEdge ? (*it)->frameGeometry().bottom() : (*it)->frameGeometry().top() - 1;
if (y > newY && y < oldY
&& !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1 // they overlap in X direction
|| window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) {
newY = y;
}
}
return newY;
}
qreal Workspace::packPositionDown(const Window *window, qreal oldY, bool bottomEdge) const
{
qreal newY = clientArea(MaximizeArea, window).bottom();
if (oldY >= newY) { // try another Xinerama screen
newY = clientArea(MaximizeArea,
window,
QPointF(window->frameGeometry().center().x(), window->frameGeometry().bottom()))
.bottom();
}
if (oldY >= newY) {
return oldY;
}
VirtualDesktop *const desktop = window->isOnCurrentDesktop() ? VirtualDesktopManager::self()->currentDesktop() : window->desktops().front();
for (auto it = m_windows.constBegin(), end = m_windows.constEnd(); it != end; ++it) {
if (isIrrelevant(*it, window, desktop)) {
continue;
}
const qreal y = bottomEdge ? (*it)->frameGeometry().top() : (*it)->frameGeometry().bottom() + 1;
if (y < newY && y > oldY
&& !(window->frameGeometry().left() > (*it)->frameGeometry().right() - 1
|| window->frameGeometry().right() - 1 < (*it)->frameGeometry().left())) {
newY = y;
}
}
return newY;
}
#endif
} // namespace