placement.cpp 32.6 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
6
7
    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>
8

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
9
10
    SPDX-License-Identifier: GPL-2.0-or-later
*/
11
12
13
14
15

#include "placement.h"

#ifndef KCMRULES
#include "workspace.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
16
#include "x11client.h"
17
#include "cursor.h"
18
19
#include "options.h"
#include "rules.h"
Thomas Lübking's avatar
Thomas Lübking committed
20
#include "screens.h"
21
22
#endif

23
#include <QTextStream>
24
#include <QTimer>
25

26
27
28
29
30
namespace KWin
{

#ifndef KCMRULES

31
KWIN_SINGLETON_FACTORY(Placement)
Martin Flöser's avatar
Martin Flöser committed
32

33
Placement::Placement(QObject*)
Martin Flöser's avatar
Martin Flöser committed
34
{
35
36
    reinitCascading(0);
}
37

Martin Flöser's avatar
Martin Flöser committed
38
39
Placement::~Placement()
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
40
    s_self = nullptr;
Martin Flöser's avatar
Martin Flöser committed
41
42
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
43
44
/**
 * Places the client \a c according to the workspace's layout policy
45
 */
46
void Placement::place(AbstractClient *c, const QRect &area)
47
48
49
50
{
    Policy policy = c->rules()->checkPlacement(Default);
    if (policy != Default) {
        place(c, area, policy);
51
        return;
52
    }
53

54
    if (c->isUtility())
55
        placeUtility(c, area, options->placement());
56
    else if (c->isDialog())
57
        placeDialog(c, area, options->placement());
58
59
    else if (c->isSplash())
        placeOnMainWindow(c, area);   // on mainwindow, if any, otherwise centered
60
    else if (c->isOnScreenDisplay() || c->isNotification() || c->isCriticalNotification())
61
        placeOnScreenDisplay(c, area);
62
63
    else if (c->isTransient() && c->hasTransientPlacementHint())
        placeTransient(c);
64
65
    else if (c->isTransient() && c->surface())
        placeDialog(c, area, options->placement());
66
    else
67
        place(c, area, options->placement());
68
}
69

70
void Placement::place(AbstractClient *c, const QRect &area, Policy policy, Policy nextPlacement)
71
72
{
    if (policy == Unknown)
73
        policy = Default;
74
    if (policy == Default)
75
        policy = options->placement();
76
    if (policy == NoPlacement)
77
78
79
80
81
82
83
84
85
86
87
88
89
        return;
    else if (policy == Random)
        placeAtRandom(c, area, nextPlacement);
    else if (policy == Cascade)
        placeCascaded(c, area, nextPlacement);
    else if (policy == Centered)
        placeCentered(c, area, nextPlacement);
    else if (policy == ZeroCornered)
        placeZeroCornered(c, area, nextPlacement);
    else if (policy == UnderMouse)
        placeUnderMouse(c, area, nextPlacement);
    else if (policy == OnMainWindow)
        placeOnMainWindow(c, area, nextPlacement);
90
    else if (policy == Maximizing)
91
92
93
        placeMaximizing(c, area, nextPlacement);
    else
        placeSmart(c, area, nextPlacement);
94
95
96

    if (options->borderSnapZone()) {
        // snap to titlebar / snap to window borders on inner screen edges
97
        const QRect geo(c->frameGeometry());
98
        QPoint corner = geo.topLeft();
99
        const QMargins frameMargins = c->frameMargins();
100
        AbstractClient::Position titlePos = c->titlebarPosition();
101
102

        const QRect fullRect = workspace()->clientArea(FullArea, c);
103
        if (!(c->maximizeMode() & MaximizeHorizontal)) {
104
105
106
107
108
109
            if (titlePos != AbstractClient::PositionRight && geo.right() == fullRect.right()) {
                corner.rx() += frameMargins.right();
            }
            if (titlePos != AbstractClient::PositionLeft && geo.left() == fullRect.left()) {
                corner.rx() -= frameMargins.left();
            }
110
        }
111
        if (!(c->maximizeMode() & MaximizeVertical)) {
112
113
114
115
116
117
            if (titlePos != AbstractClient::PositionBottom && geo.bottom() == fullRect.bottom()) {
                corner.ry() += frameMargins.bottom();
            }
            if (titlePos != AbstractClient::PositionTop && geo.top() == fullRect.top()) {
                corner.ry() -= frameMargins.top();
            }
118
119
120
        }
        c->move(corner);
    }
121
}
122

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
123
124
/**
 * Place the client \a c according to a simply "random" placement algorithm.
125
 */
126
void Placement::placeAtRandom(AbstractClient* c, const QRect& area, Policy /*next*/)
127
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
128
129
    Q_ASSERT(area.isValid());

130
131
132
    const int step  = 24;
    static int px = step;
    static int py = 2 * step;
133
    int tx, ty;
134

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
135
136
137
138
139
140
    if (px < area.x()) {
        px = area.x();
    }
    if (py < area.y()) {
        py = area.y();
    }
141
142

    px += step;
143
    py += 2 * step;
144

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
145
146
147
148
149
150
    if (px > area.width() / 2) {
        px = area.x() + step;
    }
    if (py > area.height() / 2) {
        py = area.y() + step;
    }
151
152
    tx = px;
    ty = py;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
153
154
    if (tx + c->width() > area.right()) {
        tx = area.right() - c->width();
155
156
        if (tx < 0)
            tx = 0;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
157
        px = area.x();
158
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
159
160
    if (ty + c->height() > area.bottom()) {
        ty = area.bottom() - c->height();
161
162
        if (ty < 0)
            ty = 0;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
163
        py = area.y();
164
    }
165
166
    c->move(tx, ty);
}
167

168
// TODO: one day, there'll be C++11 ...
169
static inline bool isIrrelevant(const AbstractClient *client, const AbstractClient *regarding, int desktop)
170
171
172
173
174
175
176
177
178
179
180
{
    if (!client)
        return true;
    if (client == regarding)
        return true;
    if (!client->isShown(false))
        return true;
    if (!client->isOnDesktop(desktop))
        return true;
    if (!client->isOnCurrentActivity())
        return true;
181
182
    if (client->isDesktop())
        return true;
183
184
185
    return false;
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
186
187
/**
 * Place the client \a c according to a really smart placement algorithm :-)
188
 */
189
void Placement::placeSmart(AbstractClient* c, const QRect& area, Policy /*next*/)
190
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
191
192
    Q_ASSERT(area.isValid());

193
194
195
196
197
198
199
200
201
    /*
     * 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.
     */

202
    if (!c->frameGeometry().isValid()) {
203
204
205
        return;
    }

206
207
208
209
    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;
210
    int desktop = c->desktop() == 0 || c->isOnAllDesktops() ? VirtualDesktopManager::self()->current() : c->desktop();
211
212
213
214
215
216

    int cxl, cxr, cyt, cyb;     //temp coords
    int  xl, xr, yt, yb;     //temp coords
    int basket;                 //temp holder

    // get the maximum allowed windows space
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
217
218
    int x = area.left();
    int y = area.top();
219
220
221
222
223
224
225
226
227
    x_optimal = x; y_optimal = y;

    //client gabarit
    int ch = c->height() - 1;
    int cw = c->width()  - 1;

    bool first_pass = true; //CT lame flag. Don't like it. What else would do?

    //loop over possible positions
228
    do {
229
        //test if enough room in x and y directions
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
230
        if (y + ch > area.bottom() && ch < area.height()) {
231
            overlap = h_wrong; // this throws the algorithm to an exit
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
232
        } else if (x + cw > area.right()) {
233
            overlap = w_wrong;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
234
        } else {
235
236
237
238
            overlap = none; //initialize

            cxl = x; cxr = x + cw;
            cyt = y; cyb = y + ch;
239
            for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) {
240
                AbstractClient *client = qobject_cast<AbstractClient*>(*l);
241
                if (isIrrelevant(client, c, desktop)) {
242
243
                    continue;
                }
244
245
246
247
248
249
250
251
252
253
254
                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 = qMax(cxl, xl); xr = qMin(cxr, xr);
                    yt = qMax(cyt, yt); yb = qMin(cyb, yb);
                    if (client->keepAbove())
                        overlap += 16 * (xr - xl) * (yb - yt);
                    else if (client->keepBelow() && !client->isDock()) // ignore KeepBelow windows
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
255
                        overlap += 0; // for placement (see X11Client::belongsToLayer() for Dock)
256
257
                    else
                        overlap += (xr - xl) * (yb - yt);
258
259
                }
            }
260
        }
261
262

        //CT first time we get no overlap we stop.
263
        if (overlap == none) {
264
265
266
            x_optimal = x;
            y_optimal = y;
            break;
267
        }
268

269
        if (first_pass) {
270
271
            first_pass = false;
            min_overlap = overlap;
272
        }
273
        //CT save the best position and the minimum overlap up to now
274
        else if (overlap >= none && overlap < min_overlap) {
275
276
277
            min_overlap = overlap;
            x_optimal = x;
            y_optimal = y;
278
        }
279
280

        // really need to loop? test if there's any overlap
281
        if (overlap > none) {
282

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
283
            possible = area.right();
284
285
286
            if (possible - cw > x) possible -= cw;

            // compare to the position of each client on the same desk
287
            for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) {
288
                AbstractClient *client = qobject_cast<AbstractClient*>(*l);
289
                if (isIrrelevant(client, c, desktop)) {
290
291
                    continue;
                }
292

293
294
                xl = client->x();          yt = client->y();
                xr = xl + client->width(); yb = yt + client->height();
295

296
297
298
                // if not enough room above or under the current tested client
                // determine the first non-overlapped x position
                if ((y < yb) && (yt < ch + y)) {
299

300
                    if ((xr > x) && (possible > xr)) possible = xr;
301

302
303
                    basket = xl - cw;
                    if ((basket > x) && (possible > basket)) possible = basket;
304
305
                }
            }
306
307
            x = possible;
        }
308
309

        // ... else ==> not enough x dimension (overlap was wrong on horizontal)
310
        else if (overlap == w_wrong) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
311
312
            x = area.left();
            possible = area.bottom();
313
314
315
316

            if (possible - ch > y) possible -= ch;

            //test the position of each window on the desk
317
            for (auto l = workspace()->stackingOrder().constBegin(); l != workspace()->stackingOrder().constEnd() ; ++l) {
318
                AbstractClient *client = qobject_cast<AbstractClient*>(*l);
319
                if (isIrrelevant(client, c, desktop)) {
320
321
                    continue;
                }
322

323
324
                xl = client->x();          yt = client->y();
                xr = xl + client->width(); yb = yt + client->height();
325

326
327
328
                // 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;
329

330
331
                basket = yt - ch;
                if ((basket > y) && (possible > basket)) possible = basket;
332
            }
333
            y = possible;
334
        }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
335
    } while ((overlap != none) && (overlap != h_wrong) && (y < area.bottom()));
336

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
337
338
339
    if (ch >= area.height()) {
        y_optimal = area.top();
    }
340
341
342
343

    // place the window
    c->move(x_optimal, y_optimal);

344
}
345

346
347
348
349
void Placement::reinitCascading(int desktop)
{
    // desktop == 0 - reinit all
    if (desktop == 0) {
350
        cci.clear();
351
        for (uint i = 0; i < VirtualDesktopManager::self()->count(); ++i) {
352
            DesktopCascadingInfo inf;
353
            inf.pos = QPoint(-1, -1);
354
355
356
357
            inf.col = 0;
            inf.row = 0;
            cci.append(inf);
        }
358
    } else {
359
360
361
        cci[desktop - 1].pos = QPoint(-1, -1);
        cci[desktop - 1].col = cci[desktop - 1].row = 0;
    }
362
}
363

364
QPoint Workspace::cascadeOffset(const AbstractClient *c) const
Thomas Lübking's avatar
Thomas Lübking committed
365
{
366
    QRect area = clientArea(PlacementArea, c->frameGeometry().center(), c->desktop());
Thomas Lübking's avatar
Thomas Lübking committed
367
368
369
    return QPoint(area.width()/48, area.height()/48);
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
370
371
/**
 * Place windows in a cascading order, remembering positions for each desktop
372
 */
373
void Placement::placeCascaded(AbstractClient *c, const QRect &area, Policy nextPlacement)
374
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
375
376
    Q_ASSERT(area.isValid());

377
    if (!c->frameGeometry().isValid()) {
378
379
380
        return;
    }

381
382
383
384
385
386
    /* cascadePlacement by Cristian Tibirna (tibirna@kde.org) (30Jan98)
     */
    // work coords
    int xp, yp;

    //CT how do I get from the 'Client' class the size that NW squarish "handle"
387
    const QPoint delta = workspace()->cascadeOffset(c);
388

389
    const int dn = c->desktop() == 0 || c->isOnAllDesktops() ? (VirtualDesktopManager::self()->current() - 1) : (c->desktop() - 1);
390
391
392
393

    // initialize often used vars: width and height of c; we gain speed
    const int ch = c->height();
    const int cw = c->width();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
394
395
396
397
    const int X = area.left();
    const int Y = area.top();
    const int H = area.height();
    const int W = area.width();
398

399
    if (nextPlacement == Unknown)
400
401
        nextPlacement = Smart;

402
403
    //initialize if needed
    if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < X || cci[dn].pos.y() < Y) {
404
405
        cci[dn].pos = QPoint(X, Y);
        cci[dn].col = cci[dn].row = 0;
406
    }
407
408
409
410
411
412
413
414


    xp = cci[dn].pos.x();
    yp = cci[dn].pos.y();

    //here to touch in case people vote for resize on placement
    if ((yp + ch) > H) yp = Y;

415
416
417
418
419
420
    if ((xp + cw) > W) {
        if (!yp) {
            place(c, area, nextPlacement);
            return;
        } else xp = X;
    }
421

422
423
    //if this isn't the first window
    if (cci[dn].pos.x() != X && cci[dn].pos.y() != Y) {
424
425
426
427
        /* The following statements cause an internal compiler error with
         * egcs-2.91.66 on SuSE Linux 6.3. The equivalent forms compile fine.
         * 22-Dec-1999 CS
         *
Thomas Lübking's avatar
Thomas Lübking committed
428
429
         * if (xp != X && yp == Y) xp = delta.x() * (++(cci[dn].col));
         * if (yp != Y && xp == X) yp = delta.y() * (++(cci[dn].row));
430
         */
431
        if (xp != X && yp == Y) {
432
            ++(cci[dn].col);
Thomas Lübking's avatar
Thomas Lübking committed
433
            xp = delta.x() * cci[dn].col;
434
435
        }
        if (yp != Y && xp == X) {
436
            ++(cci[dn].row);
Thomas Lübking's avatar
Thomas Lübking committed
437
            yp = delta.y() * cci[dn].row;
438
        }
439
440

        // last resort: if still doesn't fit, smart place it
441
442
        if (((xp + cw) > W - X) || ((yp + ch) > H - Y)) {
            place(c, area, nextPlacement);
443
444
            return;
        }
445
    }
446
447
448
449
450

    // place the window
    c->move(QPoint(xp, yp));

    // new position
Thomas Lübking's avatar
Thomas Lübking committed
451
    cci[dn].pos = QPoint(xp + delta.x(), yp + delta.y());
452
}
453

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
454
455
/**
 * Place windows centered, on top of all others
456
 */
457
void Placement::placeCentered(AbstractClient* c, const QRect& area, Policy /*next*/)
458
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
459
    Q_ASSERT(area.isValid());
460

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
461
462
    const int xp = area.left() + (area.width() - c->width()) / 2;
    const int yp = area.top() + (area.height() - c->height()) / 2;
463
464
465

    // place the window
    c->move(QPoint(xp, yp));
466
}
467

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
468
469
/**
 * Place windows in the (0,0) corner, on top of all others
470
 */
471
void Placement::placeZeroCornered(AbstractClient* c, const QRect& area, Policy /*next*/)
472
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
473
474
    Q_ASSERT(area.isValid());

475
    // get the maximum allowed windows space and desk's origin
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
476
    c->move(area.topLeft());
477
}
478

479
void Placement::placeUtility(AbstractClient *c, const QRect &area, Policy /*next*/)
480
{
481
482
483
484
485
// 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
486
487
    place(c, area, Default);
}
488

489
void Placement::placeOnScreenDisplay(AbstractClient *c, const QRect &area)
490
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
491
492
    Q_ASSERT(area.isValid());

Nate Graham's avatar
Nate Graham committed
493
    // place at lower area of the screen
494
    const int x = area.left() + (area.width() -  c->width())  / 2;
Nate Graham's avatar
Nate Graham committed
495
    const int y = area.top() + 2 * area.height() / 3 - c->height() / 2;
496
497
498

    c->move(QPoint(x, y));
}
499

500
501
void Placement::placeTransient(AbstractClient *c)
{
502
503
    const auto parent = c->transientFor();
    const QRect screen =  Workspace::self()->clientArea(parent->isFullScreen() ? FullScreenArea : PlacementArea, parent);
504
    const QRect popupGeometry = c->transientPlacement(screen);
505
    c->setFrameGeometry(popupGeometry);
506

507
508
509
510
511
512
513

    // Potentially a client could set no constraint adjustments
    // and we'll be offscreen.

    // The spec implies we should place window the offscreen. However,
    // practically Qt doesn't set any constraint adjustments yet so we can't.
    // Also kwin generally doesn't let clients do what they want
514
    if (!screen.contains(c->frameGeometry())) {
515
        c->keepInArea(screen);
516
    }
517
518
}

519
void Placement::placeDialog(AbstractClient *c, const QRect &area, Policy nextPlacement)
520
521
522
{
    placeOnMainWindow(c, area, nextPlacement);
}
523

524
void Placement::placeUnderMouse(AbstractClient *c, const QRect &area, Policy /*next*/)
525
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
526
527
    Q_ASSERT(area.isValid());

528
    QRect geom = c->frameGeometry();
529
    geom.moveCenter(Cursors::self()->mouse()->pos());
530
531
532
    c->move(geom.topLeft());
    c->keepInArea(area);   // make sure it's kept inside workarea
}
533

534
void Placement::placeOnMainWindow(AbstractClient *c, const QRect &area, Policy nextPlacement)
535
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
536
537
    Q_ASSERT(area.isValid());

538
    if (nextPlacement == Unknown)
539
        nextPlacement = Centered;
540
541
    if (nextPlacement == Maximizing)   // maximize if needed
        placeMaximizing(c, area, NoPlacement);
542
    auto mainwindows = c->mainClients();
543
544
    AbstractClient* place_on = nullptr;
    AbstractClient* place_on2 = nullptr;
545
    int mains_count = 0;
546
    for (auto it = mainwindows.constBegin();
547
548
549
            it != mainwindows.constEnd();
            ++it) {
        if (mainwindows.count() > 1 && (*it)->isSpecialWindow())
550
551
552
            continue; // don't consider toolbars etc when placing
        ++mains_count;
        place_on2 = *it;
553
        if ((*it)->isOnCurrentDesktop()) {
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
554
            if (place_on == nullptr)
555
                place_on = *it;
556
557
558
559
560
561
562
563
            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, Centered);
564
565
566
                return;
            }
        }
567
    }
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
568
    if (place_on == nullptr) {
569
570
571
        // 'mains_count' is used because it doesn't include ignored mainwindows
        if (mains_count != 1) {
            place(c, area, Centered);
572
573
            return;
        }
574
575
576
577
        place_on = place_on2; // use the only window filtered together with 'mains_count'
    }
    if (place_on->isDesktop()) {
        place(c, area, Centered);
578
        return;
579
    }
580
581
    QRect geom = c->frameGeometry();
    geom.moveCenter(place_on->frameGeometry().center());
582
    c->move(geom.topLeft());
583
    // get area again, because the mainwindow may be on different xinerama screen
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
584
    const QRect placementArea = workspace()->clientArea(PlacementArea, c);
585
    c->keepInArea(placementArea);   // make sure it's kept inside workarea
586
}
587

588
void Placement::placeMaximizing(AbstractClient *c, const QRect &area, Policy nextPlacement)
589
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
590
591
    Q_ASSERT(area.isValid());

592
    if (nextPlacement == Unknown)
593
        nextPlacement = Smart;
594
    if (c->isMaximizable() && c->maxSize().width() >= area.width() && c->maxSize().height() >= area.height()) {
595
        if (workspace()->clientArea(MaximizeArea, c) == area)
596
            c->maximize(MaximizeFull);
597
598
        else { // if the geometry doesn't match default maximize area (xinerama case?),
            // it's probably better to use the given area
599
            c->setFrameGeometry(area);
600
        }
601
602
603
    } else {
        c->resizeWithChecks(c->maxSize().boundedTo(area.size()));
        place(c, area, nextPlacement);
604
    }
605
}
606

607
608
609
void Placement::cascadeDesktop()
{
    Workspace *ws = Workspace::self();
610
    const int desktop = VirtualDesktopManager::self()->current();
611
612
    reinitCascading(desktop);
    foreach (Toplevel *toplevel, ws->stackingOrder()) {
613
        auto client = qobject_cast<AbstractClient*>(toplevel);
614
615
616
617
618
619
        if (!client ||
                (!client->isOnCurrentDesktop()) ||
                (client->isMinimized())         ||
                (client->isOnAllDesktops())     ||
                (!client->isMovable()))
            continue;
620
621
        const QRect placementArea = workspace()->clientArea(PlacementArea, client);
        placeCascaded(client, placementArea);
622
623
624
625
626
    }
}

void Placement::unclutterDesktop()
{
627
    const auto &clients = Workspace::self()->allClientList();
628
    for (int i = clients.size() - 1; i >= 0; i--) {
629
        auto client = clients.at(i);
630
631
632
633
634
        if ((!client->isOnCurrentDesktop()) ||
                (client->isMinimized())     ||
                (client->isOnAllDesktops()) ||
                (!client->isMovable()))
            continue;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
635
636
        const QRect placementArea = workspace()->clientArea(PlacementArea, client);
        placeSmart(client, placementArea);
637
638
639
    }
}

640
641
#endif

642
643
644
645
646
647
const char* Placement::policyToString(Policy policy)
{
    const char* const policies[] = {
        "NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Cascade", "Centered",
        "ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing"
    };
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
648
    Q_ASSERT(policy < int(sizeof(policies) / sizeof(policies[ 0 ])));
649
    return policies[ policy ];
650
}
651
652
653
654
655
656
657
658


#ifndef KCMRULES

// ********************
// Workspace
// ********************

659
void AbstractClient::packTo(int left, int top)
660
{
661
    workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
662

663
664
665
666
667
668
669
670
671
    const int oldScreen = screen();
    move(left, top);
    if (screen() != oldScreen) {
        workspace()->sendClientToScreen(this, screen()); // checks rule validity
        if (maximizeMode() != MaximizeRestore)
            checkWorkspacePosition();
    }
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
672
673
/**
 * Moves active window left until in bumps into another window or workarea edge.
674
 */
675
void Workspace::slotWindowPackLeft()
676
677
{
    if (active_client && active_client->isMovable())
678
        active_client->packTo(packPositionLeft(active_client, active_client->frameGeometry().left(), true),
679
                              active_client->y());
680
}
681
682

void Workspace::slotWindowPackRight()
683
684
{
    if (active_client && active_client->isMovable())
685
        active_client->packTo(packPositionRight(active_client, active_client->frameGeometry().right(), true)
686
                                                - active_client->width() + 1, active_client->y());
687
}
688
689

void Workspace::slotWindowPackUp()
690
691
{
    if (active_client && active_client->isMovable())
692
        active_client->packTo(active_client->x(),
693
                              packPositionUp(active_client, active_client->frameGeometry().top(), true));
694
}
695
696

void Workspace::slotWindowPackDown()
697
698
{
    if (active_client && active_client->isMovable())
699
        active_client->packTo(active_client->x(),
700
                              packPositionDown(active_client, active_client->frameGeometry().bottom(), true) - active_client->height() + 1);
701
}
702
703

void Workspace::slotWindowGrowHorizontal()
704
705
{
    if (active_client)
706
        active_client->growHorizontal();
707
}
708

709
void AbstractClient::growHorizontal()
710
711
{
    if (!isResizable() || isShade())
712
        return;
713
    QRect geom = frameGeometry();
714
    geom.setRight(workspace()->packPositionRight(this, geom.right(), true));
715
    QSize adjsize = constrainFrameSize(geom.size(), SizeModeFixedW);
716
    if (frameGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().width() > 1) { // take care of size increments
717
        int newright = workspace()->packPositionRight(this, geom.right() + resizeIncrements().width() - 1, true);
718
719
        // check that it hasn't grown outside of the area, due to size increments
        // TODO this may be wrong?
720
        if (workspace()->clientArea(MovementArea,
721
                                   QPoint((x() + newright) / 2, frameGeometry().center().y()), desktop()).right() >= newright)
722
            geom.setRight(newright);
723
    }
724
725
    geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
    geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
726
    workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
727
    setFrameGeometry(geom);
728
}
729
730

void Workspace::slotWindowShrinkHorizontal()
731
732
{
    if (active_client)
733
        active_client->shrinkHorizontal();
734
}
735

736
void AbstractClient::shrinkHorizontal()
737
738
{
    if (!isResizable() || isShade())
739
        return;
740
    QRect geom = frameGeometry();
741
742
    geom.setRight(workspace()->packPositionLeft(this, geom.right(), false));
    if (geom.width() <= 1)
743
        return;
744
    geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedW));
745
    if (geom.width() > 20) {
746
        workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
747
        setFrameGeometry(geom);
748
    }
749
}
750
751

void Workspace::slotWindowGrowVertical()
752
753
{
    if (active_client)
754
        active_client->growVertical();
755
}
756

757
void AbstractClient::growVertical()
758
759
{
    if (!isResizable() || isShade())
760
        return;
761
    QRect geom = frameGeometry();
762
    geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true));
763
    QSize adjsize = constrainFrameSize(geom.size(), SizeModeFixedH);
764
    if (frameGeometry().size() == adjsize && geom.size() != adjsize && resizeIncrements().height() > 1) { // take care of size increments
765
        int newbottom = workspace()->packPositionDown(this, geom.bottom() + resizeIncrements().height() - 1, true);
766
        // check that it hasn't grown outside of the area, due to size increments
767
        if (workspace()->clientArea(MovementArea,
768
                                   QPoint(frameGeometry().center().x(), (y() + newbottom) / 2), desktop()).bottom() >= newbottom)
769
            geom.setBottom(newbottom);
770
    }
771
    geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
772
    workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
773
    setFrameGeometry(geom);
774
}
775
776
777


void Workspace::slotWindowShrinkVertical()
778
779
{
    if (active_client)
780
        active_client->shrinkVertical();
781
}
782

783
void AbstractClient::shrinkVertical()
784
785
{
    if (!isResizable() || isShade())
786
        return;
787
    QRect geom = frameGeometry();
788
789
    geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false));
    if (geom.height() <= 1)
790
        return;
791
    geom.setSize(constrainFrameSize(geom.size(), SizeModeFixedH));
792
    if (geom.height() > 20) {
793
        workspace()->updateFocusMousePosition(Cursors::self()->mouse()->pos()); // may cause leave event;
794
        setFrameGeometry(geom);
795
    }
796
}
797

798
void Workspace::quickTileWindow(QuickTileMode mode)
799
800
801
802
803
{
    if (!active_client) {
        return;
    }

804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
    // 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();
    }

822
    active_client->setQuickTileMode(mode, true);
823
824
}