/***************************************************************** KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org> Copyright (C) 1997 to 2002 Cristian Tibirna <tibirna@kde.org> Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org> You can Freely distribute this program under the GNU General Public License. See the file "COPYING" for the exact licensing terms. ******************************************************************/ #include "placement.h" #include <tqrect.h> #include <assert.h> #ifndef KCMRULES #include "workspace.h" #include "client.h" #include "options.h" #include "rules.h" #endif namespace KWinInternal { #ifndef KCMRULES Placement::Placement(Workspace* w) { m_WorkspacePtr = w; reinitCascading( 0 ); } /*! Places the client \a c according to the workspace's layout policy */ void Placement::place(Client* c, TQRect& area ) { Policy policy = c->rules()->checkPlacement( Default ); if( policy != Default ) { place( c, area, policy ); return; } if( c->isUtility()) placeUtility(c, area, options->placement ); else if( c->isDialog()) placeDialog(c, area, options->placement ); else if( c->isSplash()) placeOnMainWindow( c, area ); // on mainwindow, if any, otherwise centered else place(c, area, options->placement); } void Placement::place(Client* c, TQRect& area, Policy policy, Policy nextPlacement ) { if( policy == Unknown ) policy = Default; if( policy == Default ) policy = options->placement; if( policy == NoPlacement ) 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); else if( policy == Maximizing ) placeMaximizing(c, area, nextPlacement); else placeSmart(c, area, nextPlacement); } /*! Place the client \a c according to a simply "random" placement algorithm. */ void Placement::placeAtRandom(Client* c, const TQRect& area, Policy /*next*/ ) { const int step = 24; static int px = step; static int py = 2 * step; int tx,ty; const TQRect maxRect = checkArea( c, area ); if (px < maxRect.x()) px = maxRect.x(); if (py < maxRect.y()) py = maxRect.y(); px += step; py += 2*step; if (px > maxRect.width()/2) px = maxRect.x() + step; if (py > maxRect.height()/2) py = maxRect.y() + step; tx = px; ty = py; if (tx + c->width() > maxRect.right()) { tx = maxRect.right() - c->width(); if (tx < 0) tx = 0; px = maxRect.x(); } if (ty + c->height() > maxRect.bottom()) { ty = maxRect.bottom() - c->height(); if (ty < 0) ty = 0; py = maxRect.y(); } c->move(tx, ty); } /*! Place the client \a c according to a really smart placement algorithm :-) */ void Placement::placeSmart(Client* c, const TQRect& area, Policy /*next*/ ) { /* * SmartPlacement by Cristian Tibirna (tibirna@kde.org) * adapted for kwm (16-19jan98) and for twin (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 int none = 0, h_wrong = -1, w_wrong = -2; // overlap types long int overlap, min_overlap = 0; int x_optimal, y_optimal; int possible; int desktop = c->desktop() == 0 || c->isOnAllDesktops() ? m_WorkspacePtr->currentDesktop() : c->desktop(); int cxl, cxr, cyt, cyb; //temp coords int xl, xr, yt, yb; //temp coords int basket; //temp holder // get the maximum allowed windows space const TQRect maxRect = checkArea( c, area ); int x = maxRect.left(), y = maxRect.top(); 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 do { //test if enough room in x and y directions if (y + ch > maxRect.bottom() && ch < maxRect.height()) overlap = h_wrong; // this throws the algorithm to an exit else if(x + cw > maxRect.right()) overlap = w_wrong; else { overlap = none; //initialize cxl = x; cxr = x + cw; cyt = y; cyb = y + ch; ClientList::ConstIterator l; for(l = m_WorkspacePtr->stackingOrder().begin(); l != m_WorkspacePtr->stackingOrder().end() ; ++l) { if((*l)->isOnDesktop(desktop) && (*l)->isShown( false ) && (*l) != c) { xl = (*l)->x(); yt = (*l)->y(); xr = xl + (*l)->width(); yb = yt + (*l)->height(); //if windows overlap, calc the overall overlapping if((cxl < xr) && (cxr > xl) && (cyt < yb) && (cyb > yt)) { xl = TQMAX(cxl, xl); xr = TQMIN(cxr, xr); yt = TQMAX(cyt, yt); yb = TQMIN(cyb, yb); if((*l)->keepAbove()) overlap += 16 * (xr - xl) * (yb - yt); else if((*l)->keepBelow() && !(*l)->isDock()) // ignore KeepBelow windows overlap += 0; // for placement (see Client::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 = maxRect.right(); if (possible - cw > x) possible -= cw; // compare to the position of each client on the same desk ClientList::ConstIterator l; for(l = m_WorkspacePtr->stackingOrder().begin(); l != m_WorkspacePtr->stackingOrder().end() ; ++l) { if ((*l)->isOnDesktop(desktop) && (*l)->isShown( false ) && (*l) != c) { xl = (*l)->x(); yt = (*l)->y(); xr = xl + (*l)->width(); yb = yt + (*l)->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 = maxRect.left(); possible = maxRect.bottom(); if (possible - ch > y) possible -= ch; //test the position of each window on the desk ClientList::ConstIterator l; for(l = m_WorkspacePtr->stackingOrder().begin(); l != m_WorkspacePtr->stackingOrder().end() ; ++l) { if((*l)->isOnDesktop(desktop) && (*l) != c && c->isShown( false )) { xl = (*l)->x(); yt = (*l)->y(); xr = xl + (*l)->width(); yb = yt + (*l)->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 < maxRect.bottom())); if(ch>= maxRect.height()) y_optimal=maxRect.top(); // place the window c->move(x_optimal, y_optimal); } void Placement::reinitCascading( int desktop ) { // desktop == 0 - reinit all if( desktop == 0 ) { cci.clear(); for( int i = 0; i < m_WorkspacePtr->numberOfDesktops(); i++) { DesktopCascadingInfo inf; inf.pos = TQPoint(-1,-1); inf.col = 0; inf.row = 0; cci.append(inf); } } else { cci[desktop - 1].pos = TQPoint(-1, -1); cci[desktop - 1].col = cci[desktop - 1].row = 0; } } /*! Place windows in a cascading order, remembering positions for each desktop */ void Placement::placeCascaded (Client* c, TQRect& area, Policy nextPlacement) { /* 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" const int delta_x = 24; const int delta_y = 24; const int dn = c->desktop() == 0 || c->isOnAllDesktops() ? (m_WorkspacePtr->currentDesktop() - 1) : (c->desktop() - 1); // get the maximum allowed windows space and desk's origin TQRect maxRect = checkArea( c, area ); // initialize often used vars: width and height of c; we gain speed const int ch = c->height(); const int cw = c->width(); const int X = maxRect.left(); const int Y = maxRect.top(); const int H = maxRect.height(); const int W = maxRect.width(); if( nextPlacement == Unknown ) nextPlacement = Smart; //initialize if needed if (cci[dn].pos.x() < 0 || cci[dn].pos.x() < X || cci[dn].pos.y() < Y ) { cci[dn].pos = TQPoint(X, Y); cci[dn].col = cci[dn].row = 0; } 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; if ((xp + cw) > W) { if (!yp) { place(c,area,nextPlacement); return; } } else { xp = X; } //if this isn't the first window if (cci[dn].pos.x() != X && cci[dn].pos.y() != Y) { /* 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 * * if (xp != X && yp == Y) xp = delta_x * (++(cci[dn].col)); * if (yp != Y && xp == X) yp = delta_y * (++(cci[dn].row)); */ if (xp != X && yp == Y) { ++(cci[dn].col); xp = delta_x * cci[dn].col; } if (yp != Y && xp == X) { ++(cci[dn].row); yp = delta_y * cci[dn].row; } // last resort: if still doesn't fit, smart place it if (((xp + cw) > W - X) || ((yp + ch) > H - Y)) { place(c,area,nextPlacement); return; } } // place the window c->move(TQPoint(xp, yp)); // new position cci[dn].pos = TQPoint(xp + delta_x, yp + delta_y); } /*! Place windows centered, on top of all others */ void Placement::placeCentered (Client* c, const TQRect& area, Policy /*next*/ ) { // get the maximum allowed windows space and desk's origin const TQRect maxRect = checkArea( c, area ); const int xp = maxRect.left() + (maxRect.width() - c->width()) / 2; const int yp = maxRect.top() + (maxRect.height() - c->height()) / 2; // place the window c->move(TQPoint(xp, yp)); } /*! Place windows in the (0,0) corner, on top of all others */ void Placement::placeZeroCornered(Client* c, const TQRect& area, Policy /*next*/ ) { // get the maximum allowed windows space and desk's origin const TQRect maxRect = checkArea( c, area ); // place the window c->move(TQPoint(maxRect.left(), maxRect.top())); } void Placement::placeUtility(Client* c, TQRect& area, Policy /*next*/ ) { // TODO twin 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, Default ); } void Placement::placeDialog(Client* c, TQRect& area, Policy nextPlacement ) { placeOnMainWindow( c, area, nextPlacement ); } void Placement::placeUnderMouse(Client* c, TQRect& area, Policy /*next*/ ) { area = checkArea( c, area ); TQRect geom = c->geometry(); geom.moveCenter( TQCursor::pos()); c->move( geom.topLeft()); c->keepInArea( area ); // make sure it's kept inside workarea } void Placement::placeOnMainWindow(Client* c, TQRect& area, Policy nextPlacement ) { if( nextPlacement == Unknown ) nextPlacement = Centered; if( nextPlacement == Maximizing ) // maximize if needed placeMaximizing( c, area, NoPlacement ); area = checkArea( c, area ); ClientList mainwindows = c->mainClients(); Client* place_on = NULL; Client* place_on2 = NULL; int mains_count = 0; for( ClientList::ConstIterator it = mainwindows.begin(); it != mainwindows.end(); ++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 == NULL ) 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, Centered ); return; } } } if( place_on == NULL ) { // 'mains_count' is used because it doesn't include ignored mainwindows if( mains_count != 1 ) { place( c, area, Centered ); return; } place_on = place_on2; // use the only window filtered together with 'mains_count' } if( place_on->isDesktop()) { place( c, area, Centered ); return; } TQRect geom = c->geometry(); geom.moveCenter( place_on->geometry().center()); c->move( geom.topLeft()); // get area again, because the mainwindow may be on different xinerama screen area = checkArea( c, TQRect()); c->keepInArea( area ); // make sure it's kept inside workarea } void Placement::placeMaximizing(Client* c, TQRect& area, Policy nextPlacement ) { if( nextPlacement == Unknown ) nextPlacement = Smart; if( c->isMaximizable() && c->maxSize().width() >= area.width() && c->maxSize().height() >= area.height()) { if( m_WorkspacePtr->clientArea( MaximizeArea, c ) == area ) c->maximize( Client::MaximizeFull ); else // if the geometry doesn't match default maximize area (xinerama case?), { // it's probably better to use the given area c->setGeometry( area ); } } else { c->resizeWithChecks( c->maxSize().boundedTo( area.size())); place( c, area, nextPlacement ); } } TQRect Placement::checkArea( const Client* c, const TQRect& area ) { if( area.isNull()) return m_WorkspacePtr->clientArea( PlacementArea, c->geometry().center(), c->desktop()); return area; } #endif Placement::Policy Placement::policyFromString( const TQString& policy, bool no_special ) { if( policy == "NoPlacement" ) return NoPlacement; else if( policy == "Default" && !no_special ) return Default; else if( policy == "Random" ) return Random; else if( policy == "Cascade" ) return Cascade; else if( policy == "Centered" ) return Centered; else if( policy == "ZeroCornered" ) return ZeroCornered; else if( policy == "UnderMouse" && !no_special) return UnderMouse; else if( policy == "OnMainWindow" && !no_special) return OnMainWindow; else if( policy == "Maximizing" ) return Maximizing; else return Smart; } const char* Placement::policyToString( Policy policy ) { const char* const policies[] = { "NoPlacement", "Default", "XXX should never see", "Random", "Smart", "Cascade", "Centered", "ZeroCornered", "UnderMouse", "OnMainWindow", "Maximizing" }; assert( policy < int( sizeof( policies ) / sizeof( policies[ 0 ] ))); return policies[ policy ]; } #ifndef KCMRULES // ******************** // Workspace // ******************** /*! Moves active window left until in bumps into another window or workarea edge. */ void Workspace::slotWindowPackLeft() { if( active_client && active_client->isMovable()) active_client->move( packPositionLeft( active_client, active_client->geometry().left(), true ), active_client->y()); } void Workspace::slotWindowPackRight() { if( active_client && active_client->isMovable()) active_client->move( packPositionRight( active_client, active_client->geometry().right(), true ) - active_client->width() + 1, active_client->y()); } void Workspace::slotWindowPackUp() { if( active_client && active_client->isMovable()) active_client->move( active_client->x(), packPositionUp( active_client, active_client->geometry().top(), true )); } void Workspace::slotWindowPackDown() { if( active_client && active_client->isMovable()) active_client->move( active_client->x(), packPositionDown( active_client, active_client->geometry().bottom(), true ) - active_client->height() + 1 ); } void Workspace::slotWindowGrowHorizontal() { if( active_client ) active_client->growHorizontal(); } void Client::growHorizontal() { if( !isResizable() || isShade()) return; TQRect geom = geometry(); geom.setRight( workspace()->packPositionRight( this, geom.right(), true )); TQSize adjsize = adjustedSize( geom.size(), SizemodeFixedW ); if( geometry().size() == adjsize && geom.size() != adjsize && xSizeHint.width_inc > 1 ) // take care of size increments { int newright = workspace()->packPositionRight( this, geom.right() + xSizeHint.width_inc - 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, TQPoint(( x() + newright ) / 2, geometry().center().y()), desktop()).right() >= newright ) geom.setRight( newright ); } geom.setSize( adjustedSize( geom.size(), SizemodeFixedW )); setGeometry( geom ); } void Workspace::slotWindowShrinkHorizontal() { if( active_client ) active_client->shrinkHorizontal(); } void Client::shrinkHorizontal() { if( !isResizable() || isShade()) return; TQRect geom = geometry(); geom.setRight( workspace()->packPositionLeft( this, geom.right(), false )); if( geom.width() <= 1 ) return; geom.setSize( adjustedSize( geom.size(), SizemodeFixedW )); if( geom.width() > 20 ) setGeometry( geom ); } void Workspace::slotWindowGrowVertical() { if( active_client ) active_client->growVertical(); } void Client::growVertical() { if( !isResizable() || isShade()) return; TQRect geom = geometry(); geom.setBottom( workspace()->packPositionDown( this, geom.bottom(), true )); TQSize adjsize = adjustedSize( geom.size(), SizemodeFixedH ); if( geometry().size() == adjsize && geom.size() != adjsize && xSizeHint.height_inc > 1 ) // take care of size increments { int newbottom = workspace()->packPositionDown( this, geom.bottom() + xSizeHint.height_inc - 1, true ); // check that it hasn't grown outside of the area, due to size increments if( workspace()->clientArea( MovementArea, TQPoint( geometry().center().x(), ( y() + newbottom ) / 2 ), desktop()).bottom() >= newbottom ) geom.setBottom( newbottom ); } geom.setSize( adjustedSize( geom.size(), SizemodeFixedH )); setGeometry( geom ); } void Workspace::slotWindowShrinkVertical() { if( active_client ) active_client->shrinkVertical(); } void Client::shrinkVertical() { if( !isResizable() || isShade()) return; TQRect geom = geometry(); geom.setBottom( workspace()->packPositionUp( this, geom.bottom(), false )); if( geom.height() <= 1 ) return; geom.setSize( adjustedSize( geom.size(), SizemodeFixedH )); if( geom.height() > 20 ) setGeometry( geom ); } int Workspace::packPositionLeft( const Client* cl, int oldx, bool left_edge ) const { int newx = clientArea( MovementArea, cl ).left(); if( oldx <= newx ) // try another Xinerama screen newx = clientArea( MovementArea, TQPoint( cl->geometry().left() - 1, cl->geometry().center().y()), cl->desktop()).left(); if( oldx <= newx ) return oldx; for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( active_client->desktop())) continue; int x = left_edge ? (*it)->geometry().right() + 1 : (*it)->geometry().left() - 1; if( x > newx && x < oldx && !( cl->geometry().top() > (*it)->geometry().bottom() // they overlap in Y direction || cl->geometry().bottom() < (*it)->geometry().top())) newx = x; } return newx; } int Workspace::packPositionRight( const Client* cl, int oldx, bool right_edge ) const { int newx = clientArea( MovementArea, cl ).right(); if( oldx >= newx ) // try another Xinerama screen newx = clientArea( MovementArea, TQPoint( cl->geometry().right() + 1, cl->geometry().center().y()), cl->desktop()).right(); if( oldx >= newx ) return oldx; for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( cl->desktop())) continue; int x = right_edge ? (*it)->geometry().left() - 1 : (*it)->geometry().right() + 1; if( x < newx && x > oldx && !( cl->geometry().top() > (*it)->geometry().bottom() || cl->geometry().bottom() < (*it)->geometry().top())) newx = x; } return newx; } int Workspace::packPositionUp( const Client* cl, int oldy, bool top_edge ) const { int newy = clientArea( MovementArea, cl ).top(); if( oldy <= newy ) // try another Xinerama screen newy = clientArea( MovementArea, TQPoint( cl->geometry().center().x(), cl->geometry().top() - 1 ), cl->desktop()).top(); if( oldy <= newy ) return oldy; for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( cl->desktop())) continue; int y = top_edge ? (*it)->geometry().bottom() + 1 : (*it)->geometry().top() - 1; if( y > newy && y < oldy && !( cl->geometry().left() > (*it)->geometry().right() // they overlap in X direction || cl->geometry().right() < (*it)->geometry().left())) newy = y; } return newy; } int Workspace::packPositionDown( const Client* cl, int oldy, bool bottom_edge ) const { int newy = clientArea( MovementArea, cl ).bottom(); if( oldy >= newy ) // try another Xinerama screen newy = clientArea( MovementArea, TQPoint( cl->geometry().center().x(), cl->geometry().bottom() + 1 ), cl->desktop()).bottom(); if( oldy >= newy ) return oldy; for( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) { if( !(*it)->isShown( false ) || !(*it)->isOnDesktop( cl->desktop())) continue; int y = bottom_edge ? (*it)->geometry().top() - 1 : (*it)->geometry().bottom() + 1; if( y < newy && y > oldy && !( cl->geometry().left() > (*it)->geometry().right() || cl->geometry().right() < (*it)->geometry().left())) newy = y; } return newy; } /*! Asks the internal positioning object to place a client */ void Workspace::place(Client* c, TQRect& area) { initPositioning->place( c, area ); } void Workspace::placeSmart(Client* c, const TQRect& area) { initPositioning->placeSmart( c, area ); } #endif } // namespace