1
/********************************************************************
2
KWin - the KDE window manager
3
This file is part of the KDE project.
5
Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6
Copyright (C) 1997 to 2002 Cristian Tibirna <tibirna@kde.org>
7
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
9
This program is free software; you can redistribute it and/or modify
10
it under the terms of the GNU General Public License as published by
11
the Free Software Foundation; either version 2 of the License, or
12
(at your option) any later version.
14
This program is distributed in the hope that it will be useful,
15
but WITHOUT ANY WARRANTY; without even the implied warranty of
16
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
GNU General Public License for more details.
19
You should have received a copy of the GNU General Public License
20
along with this program. If not, see <http://www.gnu.org/licenses/>.
21
*********************************************************************/
23
#include "placement.h"
28
#include <QTextStream>
31
#include "workspace.h"
42
Placement::Placement(Workspace* w)
50
Places the client \a c according to the workspace's layout policy
52
void Placement::place(Client* c, QRect& area)
54
Policy policy = c->rules()->checkPlacement(Default);
55
if (policy != Default) {
56
place(c, area, policy);
61
placeUtility(c, area, options->placement);
62
else if (c->isDialog())
63
placeDialog(c, area, options->placement);
64
else if (c->isSplash())
65
placeOnMainWindow(c, area); // on mainwindow, if any, otherwise centered
67
place(c, area, options->placement);
70
void Placement::place(Client* c, QRect& area, Policy policy, Policy nextPlacement)
72
if (policy == Unknown)
74
if (policy == Default)
75
policy = options->placement;
76
if (policy == NoPlacement)
78
else if (policy == Random)
79
placeAtRandom(c, area, nextPlacement);
80
else if (policy == Cascade)
81
placeCascaded(c, area, nextPlacement);
82
else if (policy == Centered)
83
placeCentered(c, area, nextPlacement);
84
else if (policy == ZeroCornered)
85
placeZeroCornered(c, area, nextPlacement);
86
else if (policy == UnderMouse)
87
placeUnderMouse(c, area, nextPlacement);
88
else if (policy == OnMainWindow)
89
placeOnMainWindow(c, area, nextPlacement);
90
else if (policy == Maximizing)
91
placeMaximizing(c, area, nextPlacement);
93
placeSmart(c, area, nextPlacement);
97
Place the client \a c according to a simply "random" placement algorithm.
99
void Placement::placeAtRandom(Client* c, const QRect& area, Policy /*next*/)
102
static int px = step;
103
static int py = 2 * step;
106
const QRect maxRect = checkArea(c, area);
108
if (px < maxRect.x())
110
if (py < maxRect.y())
116
if (px > maxRect.width() / 2)
117
px = maxRect.x() + step;
118
if (py > maxRect.height() / 2)
119
py = maxRect.y() + step;
122
if (tx + c->width() > maxRect.right()) {
123
tx = maxRect.right() - c->width();
128
if (ty + c->height() > maxRect.bottom()) {
129
ty = maxRect.bottom() - c->height();
138
Place the client \a c according to a really smart placement algorithm :-)
140
void Placement::placeSmart(Client* c, const QRect& area, Policy /*next*/)
143
* SmartPlacement by Cristian Tibirna (tibirna@kde.org)
144
* adapted for kwm (16-19jan98) and for kwin (16Nov1999) using (with
145
* permission) ideas from fvwm, authored by
146
* Anthony Martin (amartin@engr.csulb.edu).
147
* Xinerama supported added by Balaji Ramani (balaji@yablibli.com)
148
* with ideas from xfce.
151
const int none = 0, h_wrong = -1, w_wrong = -2; // overlap types
152
long int overlap, min_overlap = 0;
153
int x_optimal, y_optimal;
155
int desktop = c->desktop() == 0 || c->isOnAllDesktops() ? m_WorkspacePtr->currentDesktop() : c->desktop();
157
int cxl, cxr, cyt, cyb; //temp coords
158
int xl, xr, yt, yb; //temp coords
159
int basket; //temp holder
161
// get the maximum allowed windows space
162
const QRect maxRect = checkArea(c, area);
163
int x = maxRect.left(), y = maxRect.top();
164
x_optimal = x; y_optimal = y;
167
int ch = c->height() - 1;
168
int cw = c->width() - 1;
170
bool first_pass = true; //CT lame flag. Don't like it. What else would do?
172
//loop over possible positions
174
//test if enough room in x and y directions
175
if (y + ch > maxRect.bottom() && ch < maxRect.height())
176
overlap = h_wrong; // this throws the algorithm to an exit
177
else if (x + cw > maxRect.right())
180
overlap = none; //initialize
182
cxl = x; cxr = x + cw;
183
cyt = y; cyb = y + ch;
184
ClientList::ConstIterator l;
185
for (l = m_WorkspacePtr->stackingOrder().constBegin(); l != m_WorkspacePtr->stackingOrder().constEnd() ; ++l) {
186
if ((*l)->isOnDesktop(desktop) &&
187
(*l)->isShown(false) && (*l) != c) {
189
xl = (*l)->x(); yt = (*l)->y();
190
xr = xl + (*l)->width(); yb = yt + (*l)->height();
192
//if windows overlap, calc the overall overlapping
193
if ((cxl < xr) && (cxr > xl) &&
194
(cyt < yb) && (cyb > yt)) {
195
xl = qMax(cxl, xl); xr = qMin(cxr, xr);
196
yt = qMax(cyt, yt); yb = qMin(cyb, yb);
197
if ((*l)->keepAbove())
198
overlap += 16 * (xr - xl) * (yb - yt);
199
else if ((*l)->keepBelow() && !(*l)->isDock()) // ignore KeepBelow windows
200
overlap += 0; // for placement (see Client::belongsToLayer() for Dock)
202
overlap += (xr - xl) * (yb - yt);
208
//CT first time we get no overlap we stop.
209
if (overlap == none) {
217
min_overlap = overlap;
219
//CT save the best position and the minimum overlap up to now
220
else if (overlap >= none && overlap < min_overlap) {
221
min_overlap = overlap;
226
// really need to loop? test if there's any overlap
227
if (overlap > none) {
229
possible = maxRect.right();
230
if (possible - cw > x) possible -= cw;
232
// compare to the position of each client on the same desk
233
ClientList::ConstIterator l;
234
for (l = m_WorkspacePtr->stackingOrder().constBegin(); l != m_WorkspacePtr->stackingOrder().constEnd() ; ++l) {
236
if ((*l)->isOnDesktop(desktop) &&
237
(*l)->isShown(false) && (*l) != c) {
239
xl = (*l)->x(); yt = (*l)->y();
240
xr = xl + (*l)->width(); yb = yt + (*l)->height();
242
// if not enough room above or under the current tested client
243
// determine the first non-overlapped x position
244
if ((y < yb) && (yt < ch + y)) {
246
if ((xr > x) && (possible > xr)) possible = xr;
249
if ((basket > x) && (possible > basket)) possible = basket;
256
// ... else ==> not enough x dimension (overlap was wrong on horizontal)
257
else if (overlap == w_wrong) {
259
possible = maxRect.bottom();
261
if (possible - ch > y) possible -= ch;
263
//test the position of each window on the desk
264
ClientList::ConstIterator l;
265
for (l = m_WorkspacePtr->stackingOrder().constBegin(); l != m_WorkspacePtr->stackingOrder().constEnd() ; ++l) {
266
if ((*l)->isOnDesktop(desktop) &&
267
(*l) != c && c->isShown(false)) {
269
xl = (*l)->x(); yt = (*l)->y();
270
xr = xl + (*l)->width(); yb = yt + (*l)->height();
272
// if not enough room to the left or right of the current tested client
273
// determine the first non-overlapped y position
274
if ((yb > y) && (possible > yb)) possible = yb;
277
if ((basket > y) && (possible > basket)) possible = basket;
282
} while ((overlap != none) && (overlap != h_wrong) && (y < maxRect.bottom()));
284
if (ch >= maxRect.height())
285
y_optimal = maxRect.top();
288
c->move(x_optimal, y_optimal);
292
void Placement::reinitCascading(int desktop)
294
// desktop == 0 - reinit all
297
for (int i = 0; i < m_WorkspacePtr->numberOfDesktops(); i++) {
298
DesktopCascadingInfo inf;
299
inf.pos = QPoint(-1, -1);
305
cci[desktop - 1].pos = QPoint(-1, -1);
306
cci[desktop - 1].col = cci[desktop - 1].row = 0;
311
Place windows in a cascading order, remembering positions for each desktop
313
void Placement::placeCascaded(Client* c, QRect& area, Policy nextPlacement)
315
/* cascadePlacement by Cristian Tibirna (tibirna@kde.org) (30Jan98)
320
//CT how do I get from the 'Client' class the size that NW squarish "handle"
321
const int delta_x = 24;
322
const int delta_y = 24;
324
const int dn = c->desktop() == 0 || c->isOnAllDesktops() ? (m_WorkspacePtr->currentDesktop() - 1) : (c->desktop() - 1);
326
// get the maximum allowed windows space and desk's origin
327
QRect maxRect = checkArea(c, area);
329
// initialize often used vars: width and height of c; we gain speed
330
const int ch = c->height();
331
const int cw = c->width();
332
const int X = maxRect.left();
333
const int Y = maxRect.top();
334
const int H = maxRect.height();
335
const int W = maxRect.width();
337
if (nextPlacement == Unknown)
338
nextPlacement = Smart;
340
//initialize if needed
341
if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < X || cci[dn].pos.y() < Y) {
342
cci[dn].pos = QPoint(X, Y);
343
cci[dn].col = cci[dn].row = 0;
347
xp = cci[dn].pos.x();
348
yp = cci[dn].pos.y();
350
//here to touch in case people vote for resize on placement
351
if ((yp + ch) > H) yp = Y;
355
place(c, area, nextPlacement);
360
//if this isn't the first window
361
if (cci[dn].pos.x() != X && cci[dn].pos.y() != Y) {
362
/* The following statements cause an internal compiler error with
363
* egcs-2.91.66 on SuSE Linux 6.3. The equivalent forms compile fine.
366
* if (xp != X && yp == Y) xp = delta_x * (++(cci[dn].col));
367
* if (yp != Y && xp == X) yp = delta_y * (++(cci[dn].row));
369
if (xp != X && yp == Y) {
371
xp = delta_x * cci[dn].col;
373
if (yp != Y && xp == X) {
375
yp = delta_y * cci[dn].row;
378
// last resort: if still doesn't fit, smart place it
379
if (((xp + cw) > W - X) || ((yp + ch) > H - Y)) {
380
place(c, area, nextPlacement);
386
c->move(QPoint(xp, yp));
389
cci[dn].pos = QPoint(xp + delta_x, yp + delta_y);
393
Place windows centered, on top of all others
395
void Placement::placeCentered(Client* c, const QRect& area, Policy /*next*/)
398
// get the maximum allowed windows space and desk's origin
399
const QRect maxRect = checkArea(c, area);
401
const int xp = maxRect.left() + (maxRect.width() - c->width()) / 2;
402
const int yp = maxRect.top() + (maxRect.height() - c->height()) / 2;
405
c->move(QPoint(xp, yp));
409
Place windows in the (0,0) corner, on top of all others
411
void Placement::placeZeroCornered(Client* c, const QRect& area, Policy /*next*/)
413
// get the maximum allowed windows space and desk's origin
414
const QRect maxRect = checkArea(c, area);
417
c->move(QPoint(maxRect.left(), maxRect.top()));
420
void Placement::placeUtility(Client* c, QRect& area, Policy /*next*/)
422
// TODO kwin should try to place utility windows next to their mainwindow,
423
// preferably at the right edge, and going down if there are more of them
424
// if there's not enough place outside the mainwindow, it should prefer
426
// use the default placement for now
427
place(c, area, Default);
431
void Placement::placeDialog(Client* c, QRect& area, Policy nextPlacement)
433
placeOnMainWindow(c, area, nextPlacement);
436
void Placement::placeUnderMouse(Client* c, QRect& area, Policy /*next*/)
438
area = checkArea(c, area);
439
QRect geom = c->geometry();
440
geom.moveCenter(cursorPos());
441
c->move(geom.topLeft());
442
c->keepInArea(area); // make sure it's kept inside workarea
445
void Placement::placeOnMainWindow(Client* c, QRect& area, Policy nextPlacement)
447
if (nextPlacement == Unknown)
448
nextPlacement = Centered;
449
if (nextPlacement == Maximizing) // maximize if needed
450
placeMaximizing(c, area, NoPlacement);
451
area = checkArea(c, area);
452
ClientList mainwindows = c->mainClients();
453
Client* place_on = NULL;
454
Client* place_on2 = NULL;
456
for (ClientList::ConstIterator it = mainwindows.constBegin();
457
it != mainwindows.constEnd();
459
if (mainwindows.count() > 1 && (*it)->isSpecialWindow())
460
continue; // don't consider toolbars etc when placing
463
if ((*it)->isOnCurrentDesktop()) {
464
if (place_on == NULL)
467
// two or more on current desktop -> center
468
// That's the default at least. However, with maximizing placement
469
// policy as the default, the dialog should be either maximized or
470
// made as large as its maximum size and then placed centered.
471
// So the nextPlacement argument allows chaining. In this case, nextPlacement
472
// is Maximizing and it will call placeCentered().
473
place(c, area, Centered);
478
if (place_on == NULL) {
479
// 'mains_count' is used because it doesn't include ignored mainwindows
480
if (mains_count != 1) {
481
place(c, area, Centered);
484
place_on = place_on2; // use the only window filtered together with 'mains_count'
486
if (place_on->isDesktop()) {
487
place(c, area, Centered);
490
QRect geom = c->geometry();
491
geom.moveCenter(place_on->geometry().center());
492
c->move(geom.topLeft());
493
// get area again, because the mainwindow may be on different xinerama screen
494
area = checkArea(c, QRect());
495
c->keepInArea(area); // make sure it's kept inside workarea
498
void Placement::placeMaximizing(Client* c, QRect& area, Policy nextPlacement)
500
if (nextPlacement == Unknown)
501
nextPlacement = Smart;
502
if (c->isMaximizable() && c->maxSize().width() >= area.width() && c->maxSize().height() >= area.height()) {
503
if (m_WorkspacePtr->clientArea(MaximizeArea, c) == area)
504
c->maximize(Client::MaximizeFull);
505
else { // if the geometry doesn't match default maximize area (xinerama case?),
506
// it's probably better to use the given area
507
c->setGeometry(area);
510
c->resizeWithChecks(c->maxSize().boundedTo(area.size()));
511
place(c, area, nextPlacement);
515
QRect Placement::checkArea(const Client* c, const QRect& area)
518
return m_WorkspacePtr->clientArea(PlacementArea, c->geometry().center(), c->desktop());
525
Placement::Policy Placement::policyFromString(const QString& policy, bool no_special)
527
if (policy == "NoPlacement")
529
else if (policy == "Default" && !no_special)
531
else if (policy == "Random")
533
else if (policy == "Cascade")
535
else if (policy == "Centered")
537
else if (policy == "ZeroCornered")
539
else if (policy == "UnderMouse" && !no_special)
541
else if (policy == "OnMainWindow" && !no_special)
543
else if (policy == "Maximizing")
549
const char* Placement::policyToString(Policy policy)
551
const char* const policies[] = {
552
"NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Cascade", "Centered",
553
"ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing"
555
assert(policy < int(sizeof(policies) / sizeof(policies[ 0 ])));
556
return policies[ policy ];
562
// ********************
564
// ********************
567
Moves active window left until in bumps into another window or workarea edge.
569
void Workspace::slotWindowPackLeft()
571
if (active_client && active_client->isMovable())
572
active_client->move(packPositionLeft(active_client, active_client->geometry().left(), true),
576
void Workspace::slotWindowPackRight()
578
if (active_client && active_client->isMovable())
580
packPositionRight(active_client, active_client->geometry().right(), true)
581
- active_client->width() + 1, active_client->y());
584
void Workspace::slotWindowPackUp()
586
if (active_client && active_client->isMovable())
587
active_client->move(active_client->x(),
588
packPositionUp(active_client, active_client->geometry().top(), true));
591
void Workspace::slotWindowPackDown()
593
if (active_client && active_client->isMovable())
594
active_client->move(active_client->x(),
595
packPositionDown(active_client, active_client->geometry().bottom(), true) - active_client->height() + 1);
598
void Workspace::slotWindowGrowHorizontal()
601
active_client->growHorizontal();
604
void Client::growHorizontal()
606
if (!isResizable() || isShade())
608
QRect geom = geometry();
609
geom.setRight(workspace()->packPositionRight(this, geom.right(), true));
610
QSize adjsize = adjustedSize(geom.size(), SizemodeFixedW);
611
if (geometry().size() == adjsize && geom.size() != adjsize && xSizeHint.width_inc > 1) { // take care of size increments
612
int newright = workspace()->packPositionRight(this, geom.right() + xSizeHint.width_inc - 1, true);
613
// check that it hasn't grown outside of the area, due to size increments
614
// TODO this may be wrong?
615
if (workspace()->clientArea(MovementArea,
616
QPoint((x() + newright) / 2, geometry().center().y()), desktop()).right() >= newright)
617
geom.setRight(newright);
619
geom.setSize(adjustedSize(geom.size(), SizemodeFixedW));
623
void Workspace::slotWindowShrinkHorizontal()
626
active_client->shrinkHorizontal();
629
void Client::shrinkHorizontal()
631
if (!isResizable() || isShade())
633
QRect geom = geometry();
634
geom.setRight(workspace()->packPositionLeft(this, geom.right(), false));
635
if (geom.width() <= 1)
637
geom.setSize(adjustedSize(geom.size(), SizemodeFixedW));
638
if (geom.width() > 20)
642
void Workspace::slotWindowGrowVertical()
645
active_client->growVertical();
648
void Client::growVertical()
650
if (!isResizable() || isShade())
652
QRect geom = geometry();
653
geom.setBottom(workspace()->packPositionDown(this, geom.bottom(), true));
654
QSize adjsize = adjustedSize(geom.size(), SizemodeFixedH);
655
if (geometry().size() == adjsize && geom.size() != adjsize && xSizeHint.height_inc > 1) { // take care of size increments
656
int newbottom = workspace()->packPositionDown(this, geom.bottom() + xSizeHint.height_inc - 1, true);
657
// check that it hasn't grown outside of the area, due to size increments
658
if (workspace()->clientArea(MovementArea,
659
QPoint(geometry().center().x(), (y() + newbottom) / 2), desktop()).bottom() >= newbottom)
660
geom.setBottom(newbottom);
662
geom.setSize(adjustedSize(geom.size(), SizemodeFixedH));
667
void Workspace::slotWindowShrinkVertical()
670
active_client->shrinkVertical();
673
void Client::shrinkVertical()
675
if (!isResizable() || isShade())
677
QRect geom = geometry();
678
geom.setBottom(workspace()->packPositionUp(this, geom.bottom(), false));
679
if (geom.height() <= 1)
681
geom.setSize(adjustedSize(geom.size(), SizemodeFixedH));
682
if (geom.height() > 20)
687
void Workspace::slotWindowQuickTileLeft()
692
active_client->setQuickTileMode(QuickTileLeft, true);
695
void Workspace::slotWindowQuickTileRight()
700
active_client->setQuickTileMode(QuickTileRight, true);
703
void Workspace::slotWindowQuickTileTopLeft()
705
if (!active_client) {
708
active_client->setQuickTileMode(QuickTileTop|QuickTileLeft, true);
711
void Workspace::slotWindowQuickTileTopRight()
713
if (!active_client) {
716
active_client->setQuickTileMode(QuickTileTop|QuickTileRight, true);
719
void Workspace::slotWindowQuickTileBottomLeft()
721
if (!active_client) {
724
active_client->setQuickTileMode(QuickTileBottom|QuickTileLeft, true);
727
void Workspace::slotWindowQuickTileBottomRight()
729
if (!active_client) {
732
active_client->setQuickTileMode(QuickTileBottom|QuickTileRight, true);
735
int Workspace::packPositionLeft(const Client* cl, int oldx, bool left_edge) const
737
int newx = clientArea(MovementArea, cl).left();
738
if (oldx <= newx) // try another Xinerama screen
739
newx = clientArea(MovementArea,
740
QPoint(cl->geometry().left() - 1, cl->geometry().center().y()), cl->desktop()).left();
743
for (ClientList::ConstIterator it = clients.constBegin();
744
it != clients.constEnd();
746
if (!(*it)->isShown(false) || !(*it)->isOnDesktop(active_client->desktop()))
748
int x = left_edge ? (*it)->geometry().right() + 1 : (*it)->geometry().left() - 1;
749
if (x > newx && x < oldx
750
&& !(cl->geometry().top() > (*it)->geometry().bottom() // they overlap in Y direction
751
|| cl->geometry().bottom() < (*it)->geometry().top()))
757
int Workspace::packPositionRight(const Client* cl, int oldx, bool right_edge) const
759
int newx = clientArea(MovementArea, cl).right();
760
if (oldx >= newx) // try another Xinerama screen
761
newx = clientArea(MovementArea,
762
QPoint(cl->geometry().right() + 1, cl->geometry().center().y()), cl->desktop()).right();
765
for (ClientList::ConstIterator it = clients.constBegin();
766
it != clients.constEnd();
768
if (!(*it)->isShown(false) || !(*it)->isOnDesktop(cl->desktop()))
770
int x = right_edge ? (*it)->geometry().left() - 1 : (*it)->geometry().right() + 1;
771
if (x < newx && x > oldx
772
&& !(cl->geometry().top() > (*it)->geometry().bottom()
773
|| cl->geometry().bottom() < (*it)->geometry().top()))
779
int Workspace::packPositionUp(const Client* cl, int oldy, bool top_edge) const
781
int newy = clientArea(MovementArea, cl).top();
782
if (oldy <= newy) // try another Xinerama screen
783
newy = clientArea(MovementArea,
784
QPoint(cl->geometry().center().x(), cl->geometry().top() - 1), cl->desktop()).top();
787
for (ClientList::ConstIterator it = clients.constBegin();
788
it != clients.constEnd();
790
if (!(*it)->isShown(false) || !(*it)->isOnDesktop(cl->desktop()))
792
int y = top_edge ? (*it)->geometry().bottom() + 1 : (*it)->geometry().top() - 1;
793
if (y > newy && y < oldy
794
&& !(cl->geometry().left() > (*it)->geometry().right() // they overlap in X direction
795
|| cl->geometry().right() < (*it)->geometry().left()))
801
int Workspace::packPositionDown(const Client* cl, int oldy, bool bottom_edge) const
803
int newy = clientArea(MovementArea, cl).bottom();
804
if (oldy >= newy) // try another Xinerama screen
805
newy = clientArea(MovementArea,
806
QPoint(cl->geometry().center().x(), cl->geometry().bottom() + 1), cl->desktop()).bottom();
809
for (ClientList::ConstIterator it = clients.constBegin();
810
it != clients.constEnd();
812
if (!(*it)->isShown(false) || !(*it)->isOnDesktop(cl->desktop()))
814
int y = bottom_edge ? (*it)->geometry().top() - 1 : (*it)->geometry().bottom() + 1;
815
if (y < newy && y > oldy
816
&& !(cl->geometry().left() > (*it)->geometry().right()
817
|| cl->geometry().right() < (*it)->geometry().left()))
824
Asks the internal positioning object to place a client
826
void Workspace::place(Client* c, QRect& area)
828
initPositioning->place(c, area);
831
void Workspace::placeSmart(Client* c, const QRect& area)
833
initPositioning->placeSmart(c, area);