diff options
Diffstat (limited to 'kwin/geometry.cpp')
-rw-r--r-- | kwin/geometry.cpp | 2589 |
1 files changed, 2589 insertions, 0 deletions
diff --git a/kwin/geometry.cpp b/kwin/geometry.cpp new file mode 100644 index 000000000..7c64eadcf --- /dev/null +++ b/kwin/geometry.cpp @@ -0,0 +1,2589 @@ +/***************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@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. +******************************************************************/ + +/* + + This file contains things relevant to geometry, i.e. workspace size, + window positions and window sizes. + +*/ + +#include "client.h" +#include "workspace.h" + +#include <kapplication.h> +#include <kglobal.h> +#include <qpainter.h> +#include <kwin.h> + +#include "placement.h" +#include "notifications.h" +#include "geometrytip.h" +#include "rules.h" + +extern Time qt_x_time; + +namespace KWinInternal +{ + +//******************************************** +// Workspace +//******************************************** + +/*! + Resizes the workspace after an XRANDR screen size change + */ +void Workspace::desktopResized() + { + QRect geom = QApplication::desktop()->geometry(); + NETSize desktop_geometry; + desktop_geometry.width = geom.width(); + desktop_geometry.height = geom.height(); + rootInfo->setDesktopGeometry( -1, desktop_geometry ); + + updateClientArea(); + checkElectricBorders( true ); + } + +/*! + Updates the current client areas according to the current clients. + + If the area changes or force is true, the new areas are propagated to the world. + + The client area is the area that is available for clients (that + which is not taken by windows like panels, the top-of-screen menu + etc). + + \sa clientArea() + */ + +void Workspace::updateClientArea( bool force ) + { + QDesktopWidget *desktopwidget = KApplication::desktop(); + int nscreens = desktopwidget -> numScreens (); +// kdDebug () << "screens: " << nscreens << endl; + QRect* new_wareas = new QRect[ numberOfDesktops() + 1 ]; + QRect** new_sareas = new QRect*[ numberOfDesktops() + 1]; + QRect* screens = new QRect [ nscreens ]; + QRect desktopArea = desktopwidget -> geometry (); + for( int iS = 0; + iS < nscreens; + iS ++ ) + { + screens [iS] = desktopwidget -> screenGeometry (iS); + } + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + { + new_wareas[ i ] = desktopArea; + new_sareas[ i ] = new QRect [ nscreens ]; + for( int iS = 0; + iS < nscreens; + iS ++ ) + new_sareas[ i ][ iS ] = screens[ iS ]; + } + for ( ClientList::ConstIterator it = clients.begin(); it != clients.end(); ++it) + { + if( !(*it)->hasStrut()) + continue; + QRect r = (*it)->adjustedClientArea( desktopArea, desktopArea ); + if( (*it)->isOnAllDesktops()) + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + { + new_wareas[ i ] = new_wareas[ i ].intersect( r ); + for( int iS = 0; + iS < nscreens; + iS ++ ) + new_sareas[ i ][ iS ] = + new_sareas[ i ][ iS ].intersect( + (*it)->adjustedClientArea( desktopArea, screens[ iS ] ) + ); + } + else + { + new_wareas[ (*it)->desktop() ] = new_wareas[ (*it)->desktop() ].intersect( r ); + for( int iS = 0; + iS < nscreens; + iS ++ ) + { +// kdDebug () << "adjusting new_sarea: " << screens[ iS ] << endl; + new_sareas[ (*it)->desktop() ][ iS ] = + new_sareas[ (*it)->desktop() ][ iS ].intersect( + (*it)->adjustedClientArea( desktopArea, screens[ iS ] ) + ); + } + } + } +#if 0 + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + { + for( int iS = 0; + iS < nscreens; + iS ++ ) + kdDebug () << "new_sarea: " << new_sareas[ i ][ iS ] << endl; + } +#endif + // TODO topmenu update for screenarea changes? + if( topmenu_space != NULL ) + { + QRect topmenu_area = desktopArea; + topmenu_area.setTop( topMenuHeight()); + for( int i = 1; + i <= numberOfDesktops(); + ++i ) + new_wareas[ i ] = new_wareas[ i ].intersect( topmenu_area ); + } + + bool changed = force; + + if (! screenarea) + changed = true; + + for( int i = 1; + !changed && i <= numberOfDesktops(); + ++i ) + { + if( workarea[ i ] != new_wareas[ i ] ) + changed = true; + for( int iS = 0; + iS < nscreens; + iS ++ ) + if (new_sareas[ i ][ iS ] != screenarea [ i ][ iS ]) + changed = true; + } + + if ( changed ) + { + delete[] workarea; + workarea = new_wareas; + new_wareas = NULL; + delete[] screenarea; + screenarea = new_sareas; + new_sareas = NULL; + NETRect r; + for( int i = 1; i <= numberOfDesktops(); i++) + { + r.pos.x = workarea[ i ].x(); + r.pos.y = workarea[ i ].y(); + r.size.width = workarea[ i ].width(); + r.size.height = workarea[ i ].height(); + rootInfo->setWorkArea( i, r ); + } + + updateTopMenuGeometry(); + for( ClientList::ConstIterator it = clients.begin(); + it != clients.end(); + ++it) + (*it)->checkWorkspacePosition(); + for( ClientList::ConstIterator it = desktops.begin(); + it != desktops.end(); + ++it) + (*it)->checkWorkspacePosition(); + } + delete[] screens; + delete[] new_sareas; + delete[] new_wareas; + } + +void Workspace::updateClientArea() + { + updateClientArea( false ); + } + + +/*! + returns the area available for clients. This is the desktop + geometry minus windows on the dock. Placement algorithms should + refer to this rather than geometry(). + + \sa geometry() + */ +QRect Workspace::clientArea( clientAreaOption opt, const QPoint& p, int desktop ) const + { + if( desktop == NETWinInfo::OnAllDesktops || desktop == 0 ) + desktop = currentDesktop(); + QDesktopWidget *desktopwidget = KApplication::desktop(); + int screen = desktopwidget->isVirtualDesktop() ? desktopwidget->screenNumber( p ) : desktopwidget->primaryScreen(); + if( screen < 0 ) + screen = desktopwidget->primaryScreen(); + QRect sarea = screenarea // may be NULL during KWin initialization + ? screenarea[ desktop ][ screen ] + : desktopwidget->screenGeometry( screen ); + QRect warea = workarea[ desktop ].isNull() + ? QApplication::desktop()->geometry() + : workarea[ desktop ]; + switch (opt) + { + case MaximizeArea: + if (options->xineramaMaximizeEnabled) + return sarea; + else + return warea; + case MaximizeFullArea: + if (options->xineramaMaximizeEnabled) + return desktopwidget->screenGeometry( screen ); + else + return desktopwidget->geometry(); + case FullScreenArea: + if (options->xineramaFullscreenEnabled) + return desktopwidget->screenGeometry( screen ); + else + return desktopwidget->geometry(); + case PlacementArea: + if (options->xineramaPlacementEnabled) + return sarea; + else + return warea; + case MovementArea: + if (options->xineramaMovementEnabled) + return desktopwidget->screenGeometry( screen ); + else + return desktopwidget->geometry(); + case WorkArea: + return warea; + case FullArea: + return desktopwidget->geometry(); + case ScreenArea: + return desktopwidget->screenGeometry( screen ); + } + assert( false ); + return QRect(); + } + +QRect Workspace::clientArea( clientAreaOption opt, const Client* c ) const + { + return clientArea( opt, c->geometry().center(), c->desktop()); + } + +/*! + Client \a c is moved around to position \a pos. This gives the + workspace the opportunity to interveniate and to implement + snap-to-windows functionality. + */ +QPoint Workspace::adjustClientPosition( Client* c, QPoint pos ) + { + //CT 16mar98, 27May98 - magics: BorderSnapZone, WindowSnapZone + //CT adapted for kwin on 25Nov1999 + //aleXXX 02Nov2000 added second snapping mode + if (options->windowSnapZone || options->borderSnapZone ) + { + const bool sOWO=options->snapOnlyWhenOverlapping; + const QRect maxRect = clientArea(MovementArea, pos+c->rect().center(), c->desktop()); + const int xmin = maxRect.left(); + const int xmax = maxRect.right()+1; //desk size + const int ymin = maxRect.top(); + const int ymax = maxRect.bottom()+1; + + const int cx(pos.x()); + const int cy(pos.y()); + const int cw(c->width()); + const int ch(c->height()); + const int rx(cx+cw); + const int ry(cy+ch); //these don't change + + int nx(cx), ny(cy); //buffers + int deltaX(xmax); + int deltaY(ymax); //minimum distance to other clients + + int lx, ly, lrx, lry; //coords and size for the comparison client, l + + // border snap + int snap = options->borderSnapZone; //snap trigger + if (snap) + { + if ((sOWO?(cx<xmin):true) && (QABS(xmin-cx)<snap)) + { + deltaX = xmin-cx; + nx = xmin; + } + if ((sOWO?(rx>xmax):true) && (QABS(rx-xmax)<snap) && (QABS(xmax-rx) < deltaX)) + { + deltaX = rx-xmax; + nx = xmax - cw; + } + + if ((sOWO?(cy<ymin):true) && (QABS(ymin-cy)<snap)) + { + deltaY = ymin-cy; + ny = ymin; + } + if ((sOWO?(ry>ymax):true) && (QABS(ry-ymax)<snap) && (QABS(ymax-ry) < deltaY)) + { + deltaY =ry-ymax; + ny = ymax - ch; + } + } + + // windows snap + snap = options->windowSnapZone; + if (snap) + { + QValueList<Client *>::ConstIterator l; + for (l = clients.begin();l != clients.end();++l ) + { + if ((*l)->isOnDesktop(currentDesktop()) && + !(*l)->isMinimized() + && (*l) != c ) + { + lx = (*l)->x(); + ly = (*l)->y(); + lrx = lx + (*l)->width(); + lry = ly + (*l)->height(); + + if ( (( cy <= lry ) && ( cy >= ly )) || + (( ry >= ly ) && ( ry <= lry )) || + (( cy <= ly ) && ( ry >= lry )) ) + { + if ((sOWO?(cx<lrx):true) && (QABS(lrx-cx)<snap) && ( QABS(lrx -cx) < deltaX) ) + { + deltaX = QABS( lrx - cx ); + nx = lrx; + } + if ((sOWO?(rx>lx):true) && (QABS(rx-lx)<snap) && ( QABS( rx - lx )<deltaX) ) + { + deltaX = QABS(rx - lx); + nx = lx - cw; + } + } + + if ( (( cx <= lrx ) && ( cx >= lx )) || + (( rx >= lx ) && ( rx <= lrx )) || + (( cx <= lx ) && ( rx >= lrx )) ) + { + if ((sOWO?(cy<lry):true) && (QABS(lry-cy)<snap) && (QABS( lry -cy ) < deltaY)) + { + deltaY = QABS( lry - cy ); + ny = lry; + } + //if ( (QABS( ry-ly ) < snap) && (QABS( ry - ly ) < deltaY )) + if ((sOWO?(ry>ly):true) && (QABS(ry-ly)<snap) && (QABS( ry - ly ) < deltaY )) + { + deltaY = QABS( ry - ly ); + ny = ly - ch; + } + } + } + } + } + pos = QPoint(nx, ny); + } + return pos; + } + +QRect Workspace::adjustClientSize( Client* c, QRect moveResizeGeom, int mode ) + { + //adapted from adjustClientPosition on 29May2004 + //this function is called when resizing a window and will modify + //the new dimensions to snap to other windows/borders if appropriate + if ( options->windowSnapZone || options->borderSnapZone ) + { + const bool sOWO=options->snapOnlyWhenOverlapping; + + const QRect maxRect = clientArea(MovementArea, c->rect().center(), c->desktop()); + const int xmin = maxRect.left(); + const int xmax = maxRect.right(); //desk size + const int ymin = maxRect.top(); + const int ymax = maxRect.bottom(); + + const int cx(moveResizeGeom.left()); + const int cy(moveResizeGeom.top()); + const int rx(moveResizeGeom.right()); + const int ry(moveResizeGeom.bottom()); + + int newcx(cx), newcy(cy); //buffers + int newrx(rx), newry(ry); + int deltaX(xmax); + int deltaY(ymax); //minimum distance to other clients + + int lx, ly, lrx, lry; //coords and size for the comparison client, l + + // border snap + int snap = options->borderSnapZone; //snap trigger + if (snap) + { + deltaX = int(snap); + deltaY = int(snap); + +#define SNAP_BORDER_TOP \ + if ((sOWO?(newcy<ymin):true) && (QABS(ymin-newcy)<deltaY)) \ + { \ + deltaY = QABS(ymin-newcy); \ + newcy = ymin; \ + } + +#define SNAP_BORDER_BOTTOM \ + if ((sOWO?(newry>ymax):true) && (QABS(ymax-newry)<deltaY)) \ + { \ + deltaY = QABS(ymax-newcy); \ + newry = ymax; \ + } + +#define SNAP_BORDER_LEFT \ + if ((sOWO?(newcx<xmin):true) && (QABS(xmin-newcx)<deltaX)) \ + { \ + deltaX = QABS(xmin-newcx); \ + newcx = xmin; \ + } + +#define SNAP_BORDER_RIGHT \ + if ((sOWO?(newrx>xmax):true) && (QABS(xmax-newrx)<deltaX)) \ + { \ + deltaX = QABS(xmax-newrx); \ + newrx = xmax; \ + } + switch ( mode ) + { + case PositionBottomRight: + SNAP_BORDER_BOTTOM + SNAP_BORDER_RIGHT + break; + case PositionRight: + SNAP_BORDER_RIGHT + break; + case PositionBottom: + SNAP_BORDER_BOTTOM + break; + case PositionTopLeft: + SNAP_BORDER_TOP + SNAP_BORDER_LEFT + break; + case PositionLeft: + SNAP_BORDER_LEFT + break; + case PositionTop: + SNAP_BORDER_TOP + break; + case PositionTopRight: + SNAP_BORDER_TOP + SNAP_BORDER_RIGHT + break; + case PositionBottomLeft: + SNAP_BORDER_BOTTOM + SNAP_BORDER_LEFT + break; + default: + assert( false ); + break; + } + + + } + + // windows snap + snap = options->windowSnapZone; + if (snap) + { + deltaX = int(snap); + deltaY = int(snap); + QValueList<Client *>::ConstIterator l; + for (l = clients.begin();l != clients.end();++l ) + { + if ((*l)->isOnDesktop(currentDesktop()) && + !(*l)->isMinimized() + && (*l) != c ) + { + lx = (*l)->x()-1; + ly = (*l)->y()-1; + lrx =(*l)->x() + (*l)->width(); + lry =(*l)->y() + (*l)->height(); + +#define WITHIN_HEIGHT ((( newcy <= lry ) && ( newcy >= ly )) || \ + (( newry >= ly ) && ( newry <= lry )) || \ + (( newcy <= ly ) && ( newry >= lry )) ) + +#define WITHIN_WIDTH ( (( cx <= lrx ) && ( cx >= lx )) || \ + (( rx >= lx ) && ( rx <= lrx )) || \ + (( cx <= lx ) && ( rx >= lrx )) ) + +#define SNAP_WINDOW_TOP if ( (sOWO?(newcy<lry):true) \ + && WITHIN_WIDTH \ + && (QABS( lry - newcy ) < deltaY) ) { \ + deltaY = QABS( lry - newcy ); \ + newcy=lry; \ + } + +#define SNAP_WINDOW_BOTTOM if ( (sOWO?(newry>ly):true) \ + && WITHIN_WIDTH \ + && (QABS( ly - newry ) < deltaY) ) { \ + deltaY = QABS( ly - newry ); \ + newry=ly; \ + } + +#define SNAP_WINDOW_LEFT if ( (sOWO?(newcx<lrx):true) \ + && WITHIN_HEIGHT \ + && (QABS( lrx - newcx ) < deltaX)) { \ + deltaX = QABS( lrx - newcx ); \ + newcx=lrx; \ + } + +#define SNAP_WINDOW_RIGHT if ( (sOWO?(newrx>lx):true) \ + && WITHIN_HEIGHT \ + && (QABS( lx - newrx ) < deltaX)) \ + { \ + deltaX = QABS( lx - newrx ); \ + newrx=lx; \ + } + + switch ( mode ) + { + case PositionBottomRight: + SNAP_WINDOW_BOTTOM + SNAP_WINDOW_RIGHT + break; + case PositionRight: + SNAP_WINDOW_RIGHT + break; + case PositionBottom: + SNAP_WINDOW_BOTTOM + break; + case PositionTopLeft: + SNAP_WINDOW_TOP + SNAP_WINDOW_LEFT + break; + case PositionLeft: + SNAP_WINDOW_LEFT + break; + case PositionTop: + SNAP_WINDOW_TOP + break; + case PositionTopRight: + SNAP_WINDOW_TOP + SNAP_WINDOW_RIGHT + break; + case PositionBottomLeft: + SNAP_WINDOW_BOTTOM + SNAP_WINDOW_LEFT + break; + default: + assert( false ); + break; + } + } + } + } + moveResizeGeom = QRect(QPoint(newcx, newcy), QPoint(newrx, newry)); + } + return moveResizeGeom; + } + +/*! + Marks the client as being moved around by the user. + */ +void Workspace::setClientIsMoving( Client *c ) + { + Q_ASSERT(!c || !movingClient); // Catch attempts to move a second + // window while still moving the first one. + movingClient = c; + if (movingClient) + ++block_focus; + else + --block_focus; + } + +/*! + Cascades all clients on the current desktop + */ +void Workspace::cascadeDesktop() + { +// TODO XINERAMA this probably is not right for xinerama + Q_ASSERT( block_stacking_updates == 0 ); + ClientList::ConstIterator it(stackingOrder().begin()); + initPositioning->reinitCascading( currentDesktop()); + QRect area = clientArea( PlacementArea, QPoint( 0, 0 ), currentDesktop()); + for (; it != stackingOrder().end(); ++it) + { + if((!(*it)->isOnDesktop(currentDesktop())) || + ((*it)->isMinimized()) || + ((*it)->isOnAllDesktops()) || + (!(*it)->isMovable()) ) + continue; + initPositioning->placeCascaded(*it, area); + } + } + +/*! + Unclutters the current desktop by smart-placing all clients + again. + */ +void Workspace::unclutterDesktop() + { + ClientList::Iterator it(clients.fromLast()); + for (; it != clients.end(); --it) + { + if((!(*it)->isOnDesktop(currentDesktop())) || + ((*it)->isMinimized()) || + ((*it)->isOnAllDesktops()) || + (!(*it)->isMovable()) ) + continue; + initPositioning->placeSmart(*it, QRect()); + } + } + + +void Workspace::updateTopMenuGeometry( Client* c ) + { + if( !managingTopMenus()) + return; + if( c != NULL ) + { + XEvent ev; + ev.xclient.display = qt_xdisplay(); + ev.xclient.type = ClientMessage; + ev.xclient.window = c->window(); + static Atom msg_type_atom = XInternAtom( qt_xdisplay(), "_KDE_TOPMENU_MINSIZE", False ); + ev.xclient.message_type = msg_type_atom; + ev.xclient.format = 32; + ev.xclient.data.l[0] = qt_x_time; + ev.xclient.data.l[1] = topmenu_space->width(); + ev.xclient.data.l[2] = topmenu_space->height(); + ev.xclient.data.l[3] = 0; + ev.xclient.data.l[4] = 0; + XSendEvent( qt_xdisplay(), c->window(), False, NoEventMask, &ev ); + KWin::setStrut( c->window(), 0, 0, topmenu_height, 0 ); // so that kicker etc. know + c->checkWorkspacePosition(); + return; + } + // c == NULL - update all, including topmenu_space + QRect area; + area = clientArea( MaximizeFullArea, QPoint( 0, 0 ), 1 ); // HACK desktop ? + area.setHeight( topMenuHeight()); + topmenu_space->setGeometry( area ); + for( ClientList::ConstIterator it = topmenus.begin(); + it != topmenus.end(); + ++it ) + updateTopMenuGeometry( *it ); + } + +//******************************************** +// Client +//******************************************** + + +void Client::keepInArea( QRect area, bool partial ) + { + if( partial ) + { + // increase the area so that can have only 100 pixels in the area + area.setLeft( QMIN( area.left() - width() + 100, area.left())); + area.setTop( QMIN( area.top() - height() + 100, area.top())); + area.setRight( QMAX( area.right() + width() - 100, area.right())); + area.setBottom( QMAX( area.bottom() + height() - 100, area.bottom())); + } + if ( geometry().right() > area.right() && width() < area.width() ) + move( area.right() - width(), y() ); + if ( geometry().bottom() > area.bottom() && height() < area.height() ) + move( x(), area.bottom() - height() ); + if( !area.contains( geometry().topLeft() )) + { + int tx = x(); + int ty = y(); + if ( tx < area.x() ) + tx = area.x(); + if ( ty < area.y() ) + ty = area.y(); + move( tx, ty ); + } + } + +/*! + Returns \a area with the client's strut taken into account. + + Used from Workspace in updateClientArea. + */ +// TODO move to Workspace? + +QRect Client::adjustedClientArea( const QRect &desktopArea, const QRect& area ) const + { + QRect r = area; + // topmenu area is reserved in updateClientArea() + if( isTopMenu()) + return r; + NETExtendedStrut str = strut(); + QRect stareaL = QRect( + 0, + str . left_start, + str . left_width, + str . left_end - str . left_start + 1 ); + QRect stareaR = QRect ( + desktopArea . right () - str . right_width + 1, + str . right_start, + str . right_width, + str . right_end - str . right_start + 1 ); + QRect stareaT = QRect ( + str . top_start, + 0, + str . top_end - str . top_start + 1, + str . top_width); + QRect stareaB = QRect ( + str . bottom_start, + desktopArea . bottom () - str . bottom_width + 1, + str . bottom_end - str . bottom_start + 1, + str . bottom_width); + + NETExtendedStrut ext = info->extendedStrut(); + if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 + && ( str.left_width != 0 || str.right_width != 0 || str.top_width != 0 || str.bottom_width != 0 )) { + + // hack, might cause problems... this tries to guess the start/end of a + // non-extended strut; only works on windows that have exact same + // geometry as their strut (ie, if the geometry fits the width + // exactly, we will adjust length of strut to match the geometry as well; + // otherwise we use the full-edge strut) + + if (stareaT.top() == geometry().top() && stareaT.bottom() == geometry().bottom()) { + stareaT.setLeft(geometry().left()); + stareaT.setRight(geometry().right()); +// kdDebug () << "Trimming top-strut to geometry() to: " << stareaT << endl; + } + if (stareaB.top() == geometry().top() && stareaB.bottom() == geometry().bottom()) { + stareaB.setLeft(geometry().left()); + stareaB.setRight(geometry().right()); +// kdDebug () << "Trimming bottom-strut to geometry(): " << stareaB << endl; + } + if (stareaL.left() == geometry().left() && stareaL.right() == geometry().right()) { + stareaL.setTop(geometry().top()); + stareaL.setBottom(geometry().bottom()); +// kdDebug () << "Trimming left-strut to geometry(): " << stareaL << endl; + } + if (stareaR.left() == geometry().left() && stareaR.right() == geometry().right()) { + stareaR.setTop(geometry().top()); + stareaR.setBottom(geometry().bottom()); +// kdDebug () << "Trimming right-strut to geometry(): " << stareaR << endl; + } + } + + QRect screenarea = workspace()->clientArea( ScreenArea, this ); + // HACK: workarea handling is not xinerama aware, so if this strut + // reserves place at a xinerama edge that's inside the virtual screen, + // ignore the strut for workspace setting. + if( area == kapp->desktop()->geometry()) + { + if( stareaL.left() < screenarea.left()) + stareaL = QRect(); + if( stareaR.right() > screenarea.right()) + stareaR = QRect(); + if( stareaT.top() < screenarea.top()) + stareaT = QRect(); + if( stareaB.bottom() < screenarea.bottom()) + stareaB = QRect(); + } + // Handle struts at xinerama edges that are inside the virtual screen. + // They're given in virtual screen coordinates, make them affect only + // their xinerama screen. + stareaL.setLeft( KMAX( stareaL.left(), screenarea.left())); + stareaR.setRight( KMIN( stareaR.right(), screenarea.right())); + stareaT.setTop( KMAX( stareaT.top(), screenarea.top())); + stareaB.setBottom( KMIN( stareaB.bottom(), screenarea.bottom())); + + if (stareaL . intersects (area)) { +// kdDebug () << "Moving left of: " << r << " to " << stareaL.right() + 1 << endl; + r . setLeft( stareaL . right() + 1 ); + } + if (stareaR . intersects (area)) { +// kdDebug () << "Moving right of: " << r << " to " << stareaR.left() - 1 << endl; + r . setRight( stareaR . left() - 1 ); + } + if (stareaT . intersects (area)) { +// kdDebug () << "Moving top of: " << r << " to " << stareaT.bottom() + 1 << endl; + r . setTop( stareaT . bottom() + 1 ); + } + if (stareaB . intersects (area)) { +// kdDebug () << "Moving bottom of: " << r << " to " << stareaB.top() - 1 << endl; + r . setBottom( stareaB . top() - 1 ); + } + return r; + } + +NETExtendedStrut Client::strut() const + { + NETExtendedStrut ext = info->extendedStrut(); + NETStrut str = info->strut(); + if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 + && ( str.left != 0 || str.right != 0 || str.top != 0 || str.bottom != 0 )) + { + // build extended from simple + if( str.left != 0 ) + { + ext.left_width = str.left; + ext.left_start = 0; + ext.left_end = XDisplayHeight( qt_xdisplay(), DefaultScreen( qt_xdisplay())); + } + if( str.right != 0 ) + { + ext.right_width = str.right; + ext.right_start = 0; + ext.right_end = XDisplayHeight( qt_xdisplay(), DefaultScreen( qt_xdisplay())); + } + if( str.top != 0 ) + { + ext.top_width = str.top; + ext.top_start = 0; + ext.top_end = XDisplayWidth( qt_xdisplay(), DefaultScreen( qt_xdisplay())); + } + if( str.bottom != 0 ) + { + ext.bottom_width = str.bottom; + ext.bottom_start = 0; + ext.bottom_end = XDisplayWidth( qt_xdisplay(), DefaultScreen( qt_xdisplay())); + } + } + return ext; + } + +bool Client::hasStrut() const + { + NETExtendedStrut ext = strut(); + if( ext.left_width == 0 && ext.right_width == 0 && ext.top_width == 0 && ext.bottom_width == 0 ) + return false; + return true; + } + + +// updates differences to workarea edges for all directions +void Client::updateWorkareaDiffs() + { + QRect area = workspace()->clientArea( WorkArea, this ); + QRect geom = geometry(); + workarea_diff_x = computeWorkareaDiff( geom.left(), geom.right(), area.left(), area.right()); + workarea_diff_y = computeWorkareaDiff( geom.top(), geom.bottom(), area.top(), area.bottom()); + } + +// If the client was inside workarea in the x direction, and if it was close to the left/right +// edge, return the distance from the left/right edge (negative for left, positive for right) +// INT_MIN means 'not inside workarea', INT_MAX means 'not near edge'. +// In order to recognize 'at the left workarea edge' from 'at the right workarea edge' +// (i.e. negative vs positive zero), the distances are one larger in absolute value than they +// really are (i.e. 5 pixels from the left edge is -6, not -5). A bit hacky, but I'm lazy +// to rewrite it just to make it nicer. If this will ever get touched again, perhaps then. +// the y direction is done the same, just the values will be rotated: top->left, bottom->right +int Client::computeWorkareaDiff( int left, int right, int a_left, int a_right ) + { + int left_diff = left - a_left; + int right_diff = a_right - right; + if( left_diff < 0 || right_diff < 0 ) + return INT_MIN; + else // fully inside workarea in this direction direction + { + // max distance from edge where it's still considered to be close and is kept at that distance + int max_diff = ( a_right - a_left ) / 10; + if( left_diff < right_diff ) + return left_diff < max_diff ? -left_diff - 1 : INT_MAX; + else if( left_diff > right_diff ) + return right_diff < max_diff ? right_diff + 1 : INT_MAX; + return INT_MAX; // not close to workarea edge + } + } + +void Client::checkWorkspacePosition() + { + if( isDesktop()) + { + QRect area = workspace()->clientArea( FullArea, this ); + if( geometry() != area ) + setGeometry( area ); + return; + } + if( maximizeMode() != MaximizeRestore ) + // TODO update geom_restore? + changeMaximize( false, false, true ); // adjust size + + if( isFullScreen()) + { + QRect area = workspace()->clientArea( FullScreenArea, this ); + if( geometry() != area ) + setGeometry( area ); + return; + } + if( isDock()) + return; + if( isTopMenu()) + { + if( workspace()->managingTopMenus()) + { + QRect area; + ClientList mainclients = mainClients(); + if( mainclients.count() == 1 ) + area = workspace()->clientArea( MaximizeFullArea, mainclients.first()); + else + area = workspace()->clientArea( MaximizeFullArea, QPoint( 0, 0 ), desktop()); + area.setHeight( workspace()->topMenuHeight()); +// kdDebug() << "TOPMENU size adjust: " << area << ":" << this << endl; + setGeometry( area ); + } + return; + } + + if( !isShade()) // TODO + { + int old_diff_x = workarea_diff_x; + int old_diff_y = workarea_diff_y; + updateWorkareaDiffs(); + + // this can be true only if this window was mapped before KWin + // was started - in such case, don't adjust position to workarea, + // because the window already had its position, and if a window + // with a strut altering the workarea would be managed in initialization + // after this one, this window would be moved + if( workspace()->initializing()) + return; + + QRect area = workspace()->clientArea( WorkArea, this ); + QRect new_geom = geometry(); + QRect tmp_rect_x( new_geom.left(), 0, new_geom.width(), 0 ); + QRect tmp_area_x( area.left(), 0, area.width(), 0 ); + checkDirection( workarea_diff_x, old_diff_x, tmp_rect_x, tmp_area_x ); + // the x<->y swapping + QRect tmp_rect_y( new_geom.top(), 0, new_geom.height(), 0 ); + QRect tmp_area_y( area.top(), 0, area.height(), 0 ); + checkDirection( workarea_diff_y, old_diff_y, tmp_rect_y, tmp_area_y ); + new_geom = QRect( tmp_rect_x.left(), tmp_rect_y.left(), tmp_rect_x.width(), tmp_rect_y.width()); + QRect final_geom( new_geom.topLeft(), adjustedSize( new_geom.size())); + if( final_geom != new_geom ) // size increments, or size restrictions + { // adjusted size differing matters only for right and bottom edge + if( old_diff_x != INT_MAX && old_diff_x > 0 ) + final_geom.moveRight( area.right() - ( old_diff_x - 1 )); + if( old_diff_y != INT_MAX && old_diff_y > 0 ) + final_geom.moveBottom( area.bottom() - ( old_diff_y - 1 )); + } + if( final_geom != geometry() ) + setGeometry( final_geom ); + // updateWorkareaDiffs(); done already by setGeometry() + } + } + +// Try to be smart about keeping the clients visible. +// If the client was fully inside the workspace before, try to keep +// it still inside the workarea, possibly moving it or making it smaller if possible, +// and try to keep the distance from the nearest workarea edge. +// On the other hand, it it was partially moved outside of the workspace in some direction, +// don't do anything with that direction if it's still at least partially visible. If it's +// not visible anymore at all, make sure it's visible at least partially +// again (not fully, as that could(?) be potentionally annoying) by +// moving it slightly inside the workarea (those '+ 5'). +// Again, this is done for the x direction, y direction will be done by x<->y swapping +void Client::checkDirection( int new_diff, int old_diff, QRect& rect, const QRect& area ) + { + if( old_diff != INT_MIN ) // was inside workarea + { + if( old_diff == INT_MAX ) // was in workarea, but far from edge + { + if( new_diff == INT_MIN ) // is not anymore fully in workarea + { + rect.setLeft( area.left()); + rect.setRight( area.right()); + } + return; + } + if( isMovable()) + { + if( old_diff < 0 ) // was in left third, keep distance from left edge + rect.moveLeft( area.left() + ( -old_diff - 1 )); + else // old_diff > 0 // was in right third, keep distance from right edge + rect.moveRight( area.right() - ( old_diff - 1 )); + } + else if( isResizable()) + { + if( old_diff < 0 ) + rect.setLeft( area.left() + ( -old_diff - 1 ) ); + else // old_diff > 0 + rect.setRight( area.right() - ( old_diff - 1 )); + } + if( rect.width() > area.width() && isResizable()) + rect.setWidth( area.width()); + if( isMovable()) + { + if( rect.left() < area.left()) + rect.moveLeft( area.left()); + else if( rect.right() > area.right()) + rect.moveRight( area.right()); + } + } + if( rect.right() < area.left() + 5 || rect.left() > area.right() - 5 ) + { // not visible (almost) at all - try to make it at least partially visible + if( isMovable()) + { + if( rect.left() < area.left() + 5 ) + rect.moveRight( area.left() + 5 ); + if( rect.right() > area.right() - 5 ) + rect.moveLeft( area.right() - 5 ); + } + } + } + +/*! + Adjust the frame size \a frame according to he window's size hints. + */ +QSize Client::adjustedSize( const QSize& frame, Sizemode mode ) const + { + // first, get the window size for the given frame size s + + QSize wsize( frame.width() - ( border_left + border_right ), + frame.height() - ( border_top + border_bottom )); + if( wsize.isEmpty()) + wsize = QSize( 1, 1 ); + + return sizeForClientSize( wsize, mode, false ); + } + +// this helper returns proper size even if the window is shaded +// see also the comment in Client::setGeometry() +QSize Client::adjustedSize() const + { + return sizeForClientSize( clientSize()); + } + +/*! + Calculate the appropriate frame size for the given client size \a + wsize. + + \a wsize is adapted according to the window's size hints (minimum, + maximum and incremental size changes). + + */ +QSize Client::sizeForClientSize( const QSize& wsize, Sizemode mode, bool noframe ) const + { + int w = wsize.width(); + int h = wsize.height(); + if( w < 1 || h < 1 ) + { + kdWarning() << "sizeForClientSize() with empty size!" << endl; + kdWarning() << kdBacktrace() << endl; + } + if (w<1) w = 1; + if (h<1) h = 1; + + // basesize, minsize, maxsize, paspect and resizeinc have all values defined, + // even if they're not set in flags - see getWmNormalHints() + QSize min_size = minSize(); + QSize max_size = maxSize(); + if( decoration != NULL ) + { + QSize decominsize = decoration->minimumSize(); + QSize border_size( border_left + border_right, border_top + border_bottom ); + if( border_size.width() > decominsize.width()) // just in case + decominsize.setWidth( border_size.width()); + if( border_size.height() > decominsize.height()) + decominsize.setHeight( border_size.height()); + if( decominsize.width() > min_size.width()) + min_size.setWidth( decominsize.width()); + if( decominsize.height() > min_size.height()) + min_size.setHeight( decominsize.height()); + } + w = QMIN( max_size.width(), w ); + h = QMIN( max_size.height(), h ); + w = QMAX( min_size.width(), w ); + h = QMAX( min_size.height(), h ); + + int w1 = w; + int h1 = h; + int width_inc = xSizeHint.width_inc; + int height_inc = xSizeHint.height_inc; + int basew_inc = xSizeHint.min_width; // see getWmNormalHints() + int baseh_inc = xSizeHint.min_height; + w = int(( w - basew_inc ) / width_inc ) * width_inc + basew_inc; + h = int(( h - baseh_inc ) / height_inc ) * height_inc + baseh_inc; +// code for aspect ratios based on code from FVWM + /* + * The math looks like this: + * + * minAspectX dwidth maxAspectX + * ---------- <= ------- <= ---------- + * minAspectY dheight maxAspectY + * + * If that is multiplied out, then the width and height are + * invalid in the following situations: + * + * minAspectX * dheight > minAspectY * dwidth + * maxAspectX * dheight < maxAspectY * dwidth + * + */ + if( xSizeHint.flags & PAspect ) + { + double min_aspect_w = xSizeHint.min_aspect.x; // use doubles, because the values can be MAX_INT + double min_aspect_h = xSizeHint.min_aspect.y; // and multiplying would go wrong otherwise + double max_aspect_w = xSizeHint.max_aspect.x; + double max_aspect_h = xSizeHint.max_aspect.y; + // According to ICCCM 4.1.2.3 PMinSize should be a fallback for PBaseSize for size increments, + // but not for aspect ratio. Since this code comes from FVWM, handles both at the same time, + // and I have no idea how it works, let's hope nobody relies on that. + w -= xSizeHint.base_width; + h -= xSizeHint.base_height; + int max_width = max_size.width() - xSizeHint.base_width; + int min_width = min_size.width() - xSizeHint.base_width; + int max_height = max_size.height() - xSizeHint.base_height; + int min_height = min_size.height() - xSizeHint.base_height; +#define ASPECT_CHECK_GROW_W \ + if( min_aspect_w * h > min_aspect_h * w ) \ + { \ + int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ + if( w + delta <= max_width ) \ + w += delta; \ + } +#define ASPECT_CHECK_SHRINK_H_GROW_W \ + if( min_aspect_w * h > min_aspect_h * w ) \ + { \ + int delta = int( h - w * min_aspect_h / min_aspect_w ) / height_inc * height_inc; \ + if( h - delta >= min_height ) \ + h -= delta; \ + else \ + { \ + int delta = int( min_aspect_w * h / min_aspect_h - w ) / width_inc * width_inc; \ + if( w + delta <= max_width ) \ + w += delta; \ + } \ + } +#define ASPECT_CHECK_GROW_H \ + if( max_aspect_w * h < max_aspect_h * w ) \ + { \ + int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ + if( h + delta <= max_height ) \ + h += delta; \ + } +#define ASPECT_CHECK_SHRINK_W_GROW_H \ + if( max_aspect_w * h < max_aspect_h * w ) \ + { \ + int delta = int( w - max_aspect_w * h / max_aspect_h ) / width_inc * width_inc; \ + if( w - delta >= min_width ) \ + w -= delta; \ + else \ + { \ + int delta = int( w * max_aspect_h / max_aspect_w - h ) / height_inc * height_inc; \ + if( h + delta <= max_height ) \ + h += delta; \ + } \ + } + switch( mode ) + { + case SizemodeAny: +#if 0 // make SizemodeAny equal to SizemodeFixedW - prefer keeping fixed width, + // so that changing aspect ratio to a different value and back keeps the same size (#87298) + { + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_GROW_H + ASPECT_CHECK_GROW_W + break; + } +#endif + case SizemodeFixedW: + { + // the checks are order so that attempts to modify height are first + ASPECT_CHECK_GROW_H + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_GROW_W + break; + } + case SizemodeFixedH: + { + ASPECT_CHECK_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_GROW_H + break; + } + case SizemodeMax: + { + // first checks that try to shrink + ASPECT_CHECK_SHRINK_H_GROW_W + ASPECT_CHECK_SHRINK_W_GROW_H + ASPECT_CHECK_GROW_W + ASPECT_CHECK_GROW_H + break; + } + } +#undef ASPECT_CHECK_SHRINK_H_GROW_W +#undef ASPECT_CHECK_SHRINK_W_GROW_H +#undef ASPECT_CHECK_GROW_W +#undef ASPECT_CHECK_GROW_H + w += xSizeHint.base_width; + h += xSizeHint.base_height; + } + if( !rules()->checkStrictGeometry( false )) + { + // disobey increments and aspect when maximized + if( maximizeMode() & MaximizeHorizontal ) + w = w1; + if( maximizeMode() & MaximizeVertical ) + h = h1; + } + + if( !noframe ) + { + w += border_left + border_right; + h += border_top + border_bottom; + } + return rules()->checkSize( QSize( w, h )); + } + +/*! + Gets the client's normal WM hints and reconfigures itself respectively. + */ +void Client::getWmNormalHints() + { + long msize; + if (XGetWMNormalHints(qt_xdisplay(), window(), &xSizeHint, &msize) == 0 ) + xSizeHint.flags = 0; + // set defined values for the fields, even if they're not in flags + + if( ! ( xSizeHint.flags & PMinSize )) + xSizeHint.min_width = xSizeHint.min_height = 0; + if( xSizeHint.flags & PBaseSize ) + { + // PBaseSize is a fallback for PMinSize according to ICCCM 4.1.2.3 + // The other way around PMinSize is not a complete fallback for PBaseSize, + // so that's not handled here. + if( ! ( xSizeHint.flags & PMinSize )) + { + xSizeHint.min_width = xSizeHint.base_width; + xSizeHint.min_height = xSizeHint.base_height; + } + } + else + xSizeHint.base_width = xSizeHint.base_height = 0; + if( ! ( xSizeHint.flags & PMaxSize )) + xSizeHint.max_width = xSizeHint.max_height = INT_MAX; + else + { + xSizeHint.max_width = QMAX( xSizeHint.max_width, 1 ); + xSizeHint.max_height = QMAX( xSizeHint.max_height, 1 ); + } + if( xSizeHint.flags & PResizeInc ) + { + xSizeHint.width_inc = kMax( xSizeHint.width_inc, 1 ); + xSizeHint.height_inc = kMax( xSizeHint.height_inc, 1 ); + } + else + { + xSizeHint.width_inc = 1; + xSizeHint.height_inc = 1; + } + if( xSizeHint.flags & PAspect ) + { // no dividing by zero + xSizeHint.min_aspect.y = kMax( xSizeHint.min_aspect.y, 1 ); + xSizeHint.max_aspect.y = kMax( xSizeHint.max_aspect.y, 1 ); + } + else + { + xSizeHint.min_aspect.x = 1; + xSizeHint.min_aspect.y = INT_MAX; + xSizeHint.max_aspect.x = INT_MAX; + xSizeHint.max_aspect.y = 1; + } + if( ! ( xSizeHint.flags & PWinGravity )) + xSizeHint.win_gravity = NorthWestGravity; + if( isManaged()) + { // update to match restrictions + QSize new_size = adjustedSize(); + if( new_size != size() && !isFullScreen()) + { + QRect orig_geometry = geometry(); + resizeWithChecks( new_size ); + if( ( !isSpecialWindow() || isToolbar()) && !isFullScreen()) + { + // try to keep the window in its xinerama screen if possible, + // if that fails at least keep it visible somewhere + QRect area = workspace()->clientArea( MovementArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + area = workspace()->clientArea( WorkArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + } + } + } + updateAllowedActions(); // affects isResizeable() + } + +QSize Client::minSize() const + { + return rules()->checkMinSize( QSize( xSizeHint.min_width, xSizeHint.min_height )); + } + +QSize Client::maxSize() const + { + return rules()->checkMaxSize( QSize( xSizeHint.max_width, xSizeHint.max_height )); + } + +/*! + Auxiliary function to inform the client about the current window + configuration. + + */ +void Client::sendSyntheticConfigureNotify() + { + XConfigureEvent c; + c.type = ConfigureNotify; + c.send_event = True; + c.event = window(); + c.window = window(); + c.x = x() + clientPos().x(); + c.y = y() + clientPos().y(); + c.width = clientSize().width(); + c.height = clientSize().height(); + c.border_width = 0; + c.above = None; + c.override_redirect = 0; + XSendEvent( qt_xdisplay(), c.event, TRUE, StructureNotifyMask, (XEvent*)&c ); + } + +const QPoint Client::calculateGravitation( bool invert, int gravity ) const + { + int dx, dy; + dx = dy = 0; + + if( gravity == 0 ) // default (nonsense) value for the argument + gravity = xSizeHint.win_gravity; + +// dx, dy specify how the client window moves to make space for the frame + switch (gravity) + { + case NorthWestGravity: // move down right + default: + dx = border_left; + dy = border_top; + break; + case NorthGravity: // move right + dx = 0; + dy = border_top; + break; + case NorthEastGravity: // move down left + dx = -border_right; + dy = border_top; + break; + case WestGravity: // move right + dx = border_left; + dy = 0; + break; + case CenterGravity: + break; // will be handled specially + case StaticGravity: // don't move + dx = 0; + dy = 0; + break; + case EastGravity: // move left + dx = -border_right; + dy = 0; + break; + case SouthWestGravity: // move up right + dx = border_left ; + dy = -border_bottom; + break; + case SouthGravity: // move up + dx = 0; + dy = -border_bottom; + break; + case SouthEastGravity: // move up left + dx = -border_right; + dy = -border_bottom; + break; + } + if( gravity != CenterGravity ) + { // translate from client movement to frame movement + dx -= border_left; + dy -= border_top; + } + else + { // center of the frame will be at the same position client center without frame would be + dx = - ( border_left + border_right ) / 2; + dy = - ( border_top + border_bottom ) / 2; + } + if( !invert ) + return QPoint( x() + dx, y() + dy ); + else + return QPoint( x() - dx, y() - dy ); + } + +void Client::configureRequest( int value_mask, int rx, int ry, int rw, int rh, int gravity, bool from_tool ) + { + if( gravity == 0 ) // default (nonsense) value for the argument + gravity = xSizeHint.win_gravity; + if( value_mask & ( CWX | CWY )) + { + QPoint new_pos = calculateGravitation( true, gravity ); // undo gravitation + if ( value_mask & CWX ) + new_pos.setX( rx ); + if ( value_mask & CWY ) + new_pos.setY( ry ); + + // clever(?) workaround for applications like xv that want to set + // the location to the current location but miscalculate the + // frame size due to kwin being a double-reparenting window + // manager + if ( new_pos.x() == x() + clientPos().x() && new_pos.y() == y() + clientPos().y() + && gravity == NorthWestGravity && !from_tool ) + { + new_pos.setX( x()); + new_pos.setY( y()); + } + + int nw = clientSize().width(); + int nh = clientSize().height(); + if ( value_mask & CWWidth ) + nw = rw; + if ( value_mask & CWHeight ) + nh = rh; + QSize ns = sizeForClientSize( QSize( nw, nh ) ); + new_pos = rules()->checkPosition( new_pos ); + + // TODO what to do with maximized windows? + if ( maximizeMode() != MaximizeFull + || ns != size()) + { + QRect orig_geometry = geometry(); + GeometryUpdatesPostponer blocker( this ); + move( new_pos ); + plainResize( ns ); + setGeometry( QRect( calculateGravitation( false, gravity ), size())); + updateFullScreenHack( QRect( new_pos, QSize( nw, nh ))); + QRect area = workspace()->clientArea( WorkArea, this ); + if( !from_tool && ( !isSpecialWindow() || isToolbar()) && !isFullScreen() + && area.contains( orig_geometry )) + keepInArea( area ); + + // this is part of the kicker-xinerama-hack... it should be + // safe to remove when kicker gets proper ExtendedStrut support; + // see Workspace::updateClientArea() and + // Client::adjustedClientArea() + if (hasStrut ()) + workspace() -> updateClientArea (); + } + } + + if ( value_mask & (CWWidth | CWHeight ) + && ! ( value_mask & ( CWX | CWY )) ) // pure resize + { + int nw = clientSize().width(); + int nh = clientSize().height(); + if ( value_mask & CWWidth ) + nw = rw; + if ( value_mask & CWHeight ) + nh = rh; + QSize ns = sizeForClientSize( QSize( nw, nh ) ); + + if( ns != size()) // don't restore if some app sets its own size again + { + QRect orig_geometry = geometry(); + GeometryUpdatesPostponer blocker( this ); + int save_gravity = xSizeHint.win_gravity; + xSizeHint.win_gravity = gravity; + resizeWithChecks( ns ); + xSizeHint.win_gravity = save_gravity; + updateFullScreenHack( QRect( calculateGravitation( true, xSizeHint.win_gravity ), QSize( nw, nh ))); + if( !from_tool && ( !isSpecialWindow() || isToolbar()) && !isFullScreen()) + { + // try to keep the window in its xinerama screen if possible, + // if that fails at least keep it visible somewhere + QRect area = workspace()->clientArea( MovementArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + area = workspace()->clientArea( WorkArea, this ); + if( area.contains( orig_geometry )) + keepInArea( area ); + } + } + } + // No need to send synthetic configure notify event here, either it's sent together + // with geometry change, or there's no need to send it. + // Handling of the real ConfigureRequest event forces sending it, as there it's necessary. + } + +void Client::resizeWithChecks( int w, int h, ForceGeometry_t force ) + { + if( shade_geometry_change ) + assert( false ); + else if( isShade()) + { + if( h == border_top + border_bottom ) + { + kdWarning() << "Shaded geometry passed for size:" << endl; + kdWarning() << kdBacktrace() << endl; + } + } + int newx = x(); + int newy = y(); + QRect area = workspace()->clientArea( WorkArea, this ); + // don't allow growing larger than workarea + if( w > area.width()) + w = area.width(); + if( h > area.height()) + h = area.height(); + QSize tmp = adjustedSize( QSize( w, h )); // checks size constraints, including min/max size + w = tmp.width(); + h = tmp.height(); + switch( xSizeHint.win_gravity ) + { + case NorthWestGravity: // top left corner doesn't move + default: + break; + case NorthGravity: // middle of top border doesn't move + newx = ( newx + width() / 2 ) - ( w / 2 ); + break; + case NorthEastGravity: // top right corner doesn't move + newx = newx + width() - w; + break; + case WestGravity: // middle of left border doesn't move + newy = ( newy + height() / 2 ) - ( h / 2 ); + break; + case CenterGravity: // middle point doesn't move + newx = ( newx + width() / 2 ) - ( w / 2 ); + newy = ( newy + height() / 2 ) - ( h / 2 ); + break; + case StaticGravity: // top left corner of _client_ window doesn't move + // since decoration doesn't change, equal to NorthWestGravity + break; + case EastGravity: // // middle of right border doesn't move + newx = newx + width() - w; + newy = ( newy + height() / 2 ) - ( h / 2 ); + break; + case SouthWestGravity: // bottom left corner doesn't move + newy = newy + height() - h; + break; + case SouthGravity: // middle of bottom border doesn't move + newx = ( newx + width() / 2 ) - ( w / 2 ); + newy = newy + height() - h; + break; + case SouthEastGravity: // bottom right corner doesn't move + newx = newx + width() - w; + newy = newy + height() - h; + break; + } + // if it would be moved outside of workarea, keep it inside, + // see also Client::computeWorkareaDiff() + if( workarea_diff_x != INT_MIN && w <= area.width()) // was inside and can still fit + { + if( newx < area.left()) + newx = area.left(); + if( newx + w > area.right() + 1 ) + newx = area.right() + 1 - w; + assert( newx >= area.left() && newx + w <= area.right() + 1 ); // width was checked above + } + if( workarea_diff_y != INT_MIN && h <= area.height()) // was inside and can still fit + { + if( newy < area.top()) + newy = area.top(); + if( newy + h > area.bottom() + 1 ) + newy = area.bottom() + 1 - h; + assert( newy >= area.top() && newy + h <= area.bottom() + 1 ); // height was checked above + } + setGeometry( newx, newy, w, h, force ); + } + +// _NET_MOVERESIZE_WINDOW +void Client::NETMoveResizeWindow( int flags, int x, int y, int width, int height ) + { + int gravity = flags & 0xff; + int value_mask = 0; + if( flags & ( 1 << 8 )) + value_mask |= CWX; + if( flags & ( 1 << 9 )) + value_mask |= CWY; + if( flags & ( 1 << 10 )) + value_mask |= CWWidth; + if( flags & ( 1 << 11 )) + value_mask |= CWHeight; + configureRequest( value_mask, x, y, width, height, gravity, true ); + } + +/*! + Returns whether the window is moveable or has a fixed + position. + */ +bool Client::isMovable() const + { + if( !motif_may_move || isFullScreen()) + return false; + if( isSpecialWindow() && !isSplash() && !isToolbar()) // allow moving of splashscreens :) + return false; + if( maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows() ) + return false; + if( rules()->checkPosition( invalidPoint ) != invalidPoint ) // forced position + return false; + return true; + } + +/*! + Returns whether the window is resizable or has a fixed size. + */ +bool Client::isResizable() const + { + if( !motif_may_resize || isFullScreen()) + return false; + if( isSpecialWindow() ) + return false; + if( maximizeMode() == MaximizeFull && !options->moveResizeMaximizedWindows() ) + return false; + if( rules()->checkSize( QSize()).isValid()) // forced size + return false; + + QSize min = minSize(); + QSize max = maxSize(); + return min.width() < max.width() || min.height() < max.height(); + } + +/* + Returns whether the window is maximizable or not + */ +bool Client::isMaximizable() const + { + { // isMovable() and isResizable() may be false for maximized windows + // with moving/resizing maximized windows disabled + TemporaryAssign< MaximizeMode > tmp( max_mode, MaximizeRestore ); + if( !isMovable() || !isResizable() || isToolbar()) // SELI isToolbar() ? + return false; + } + if ( maximizeMode() != MaximizeRestore ) + return TRUE; + QSize max = maxSize(); +#if 0 + if( max.width() < 32767 || max.height() < 32767 ) // sizes are 16bit with X + return false; +#else + // apparently there are enough apps which specify some arbitrary value + // for their maximum size just for the fun of it + QSize areasize = workspace()->clientArea( MaximizeArea, this ).size(); + if( max.width() < areasize.width() || max.height() < areasize.height()) + return false; +#endif + return true; + } + + +/*! + Reimplemented to inform the client about the new window position. + */ +void Client::setGeometry( int x, int y, int w, int h, ForceGeometry_t force ) + { + // this code is also duplicated in Client::plainResize() + // Ok, the shading geometry stuff. Generally, code doesn't care about shaded geometry, + // simply because there are too many places dealing with geometry. Those places + // ignore shaded state and use normal geometry, which they usually should get + // from adjustedSize(). Such geometry comes here, and if the window is shaded, + // the geometry is used only for client_size, since that one is not used when + // shading. Then the frame geometry is adjusted for the shaded geometry. + // This gets more complicated in the case the code does only something like + // setGeometry( geometry()) - geometry() will return the shaded frame geometry. + // Such code is wrong and should be changed to handle the case when the window is shaded, + // for example using Client::clientSize(). + if( shade_geometry_change ) + ; // nothing + else if( isShade()) + { + if( h == border_top + border_bottom ) + { + kdDebug() << "Shaded geometry passed for size:" << endl; + kdDebug() << kdBacktrace() << endl; + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + h = border_top + border_bottom; + } + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + } + if( force == NormalGeometrySet && frame_geometry == QRect( x, y, w, h )) + return; + frame_geometry = QRect( x, y, w, h ); + updateWorkareaDiffs(); + if( postpone_geometry_updates != 0 ) + { + pending_geometry_update = true; + return; + } + resizeDecoration( QSize( w, h )); + XMoveResizeWindow( qt_xdisplay(), frameId(), x, y, w, h ); +// resizeDecoration( QSize( w, h )); + if( !isShade()) + { + QSize cs = clientSize(); + XMoveResizeWindow( qt_xdisplay(), wrapperId(), clientPos().x(), clientPos().y(), + cs.width(), cs.height()); + XMoveResizeWindow( qt_xdisplay(), window(), 0, 0, cs.width(), cs.height()); + } + updateShape(); + // SELI TODO won't this be too expensive? + updateWorkareaDiffs(); + sendSyntheticConfigureNotify(); + updateWindowRules(); + checkMaximizeGeometry(); + } + +void Client::plainResize( int w, int h, ForceGeometry_t force ) + { + // this code is also duplicated in Client::setGeometry(), and it's also commented there + if( shade_geometry_change ) + ; // nothing + else if( isShade()) + { + if( h == border_top + border_bottom ) + { + kdDebug() << "Shaded geometry passed for size:" << endl; + kdDebug() << kdBacktrace() << endl; + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + h = border_top + border_bottom; + } + } + else + { + client_size = QSize( w - border_left - border_right, h - border_top - border_bottom ); + } + if( QSize( w, h ) != rules()->checkSize( QSize( w, h ))) + { + kdDebug() << "forced size fail:" << QSize( w,h ) << ":" << rules()->checkSize( QSize( w, h )) << endl; + kdDebug() << kdBacktrace() << endl; + } + if( force == NormalGeometrySet && frame_geometry.size() == QSize( w, h )) + return; + frame_geometry.setSize( QSize( w, h )); + updateWorkareaDiffs(); + if( postpone_geometry_updates != 0 ) + { + pending_geometry_update = true; + return; + } + resizeDecoration( QSize( w, h )); + XResizeWindow( qt_xdisplay(), frameId(), w, h ); +// resizeDecoration( QSize( w, h )); + if( !isShade()) + { + QSize cs = clientSize(); + XMoveResizeWindow( qt_xdisplay(), wrapperId(), clientPos().x(), clientPos().y(), + cs.width(), cs.height()); + XMoveResizeWindow( qt_xdisplay(), window(), 0, 0, cs.width(), cs.height()); + } + updateShape(); + updateWorkareaDiffs(); + sendSyntheticConfigureNotify(); + updateWindowRules(); + checkMaximizeGeometry(); + } + +/*! + Reimplemented to inform the client about the new window position. + */ +void Client::move( int x, int y, ForceGeometry_t force ) + { + if( force == NormalGeometrySet && frame_geometry.topLeft() == QPoint( x, y )) + return; + frame_geometry.moveTopLeft( QPoint( x, y )); + updateWorkareaDiffs(); + if( postpone_geometry_updates != 0 ) + { + pending_geometry_update = true; + return; + } + XMoveWindow( qt_xdisplay(), frameId(), x, y ); + sendSyntheticConfigureNotify(); + updateWindowRules(); + checkMaximizeGeometry(); + } + + +void Client::postponeGeometryUpdates( bool postpone ) + { + if( postpone ) + { + if( postpone_geometry_updates == 0 ) + pending_geometry_update = false; + ++postpone_geometry_updates; + } + else + { + if( --postpone_geometry_updates == 0 ) + { + if( pending_geometry_update ) + { + if( isShade()) + setGeometry( QRect( pos(), adjustedSize()), ForceGeometrySet ); + else + setGeometry( geometry(), ForceGeometrySet ); + pending_geometry_update = false; + } + } + } + } + +void Client::maximize( MaximizeMode m ) + { + setMaximize( m & MaximizeVertical, m & MaximizeHorizontal ); + } + +/*! + Sets the maximization according to \a vertically and \a horizontally + */ +void Client::setMaximize( bool vertically, bool horizontally ) + { // changeMaximize() flips the state, so change from set->flip + changeMaximize( + max_mode & MaximizeVertical ? !vertically : vertically, + max_mode & MaximizeHorizontal ? !horizontally : horizontally, + false ); + } + +void Client::changeMaximize( bool vertical, bool horizontal, bool adjust ) + { + if( !isMaximizable()) + return; + + MaximizeMode old_mode = max_mode; + // 'adjust == true' means to update the size only, e.g. after changing workspace size + if( !adjust ) + { + if( vertical ) + max_mode = MaximizeMode( max_mode ^ MaximizeVertical ); + if( horizontal ) + max_mode = MaximizeMode( max_mode ^ MaximizeHorizontal ); + } + + max_mode = rules()->checkMaximize( max_mode ); + if( !adjust && max_mode == old_mode ) + return; + + GeometryUpdatesPostponer blocker( this ); + + // maximing one way and unmaximizing the other way shouldn't happen + Q_ASSERT( !( vertical && horizontal ) + || ((( max_mode & MaximizeVertical ) != 0 ) == (( max_mode & MaximizeHorizontal ) != 0 ))); + + QRect clientArea = workspace()->clientArea( MaximizeArea, this ); + + // save sizes for restoring, if maximalizing + if( !adjust && !( y() == clientArea.top() && height() == clientArea.height())) + { + geom_restore.setTop( y()); + geom_restore.setHeight( height()); + } + if( !adjust && !( x() == clientArea.left() && width() == clientArea.width())) + { + geom_restore.setLeft( x()); + geom_restore.setWidth( width()); + } + + if( !adjust ) + { + if(( vertical && !(old_mode & MaximizeVertical )) + || ( horizontal && !( old_mode & MaximizeHorizontal ))) + Notify::raise( Notify::Maximize ); + else + Notify::raise( Notify::UnMaximize ); + } + + if( decoration != NULL ) // decorations may turn off some borders when maximized + decoration->borders( border_left, border_right, border_top, border_bottom ); + + // restore partial maximizations + if ( old_mode==MaximizeFull && max_mode==MaximizeRestore ) + { + if ( maximizeModeRestore()==MaximizeVertical ) + { + max_mode = MaximizeVertical; + maxmode_restore = MaximizeRestore; + } + if ( maximizeModeRestore()==MaximizeHorizontal ) + { + max_mode = MaximizeHorizontal; + maxmode_restore = MaximizeRestore; + } + } + + switch (max_mode) + { + + case MaximizeVertical: + { + if( old_mode & MaximizeHorizontal ) // actually restoring from MaximizeFull + { + if( geom_restore.width() == 0 ) + { // needs placement + plainResize( adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH )); + workspace()->placeSmart( this, clientArea ); + } + else + setGeometry( QRect(QPoint( geom_restore.x(), clientArea.top()), + adjustedSize(QSize( geom_restore.width(), clientArea.height()), SizemodeFixedH )), ForceGeometrySet); + } + else + setGeometry( QRect(QPoint(x(), clientArea.top()), + adjustedSize(QSize(width(), clientArea.height()), SizemodeFixedH )), ForceGeometrySet); + info->setState( NET::MaxVert, NET::Max ); + break; + } + + case MaximizeHorizontal: + { + if( old_mode & MaximizeVertical ) // actually restoring from MaximizeFull + { + if( geom_restore.height() == 0 ) + { // needs placement + plainResize( adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW )); + workspace()->placeSmart( this, clientArea ); + } + else + setGeometry( QRect( QPoint(clientArea.left(), geom_restore.y()), + adjustedSize(QSize(clientArea.width(), geom_restore.height()), SizemodeFixedW )), ForceGeometrySet); + } + else + setGeometry( QRect( QPoint(clientArea.left(), y()), + adjustedSize(QSize(clientArea.width(), height()), SizemodeFixedW )), ForceGeometrySet); + info->setState( NET::MaxHoriz, NET::Max ); + break; + } + + case MaximizeRestore: + { + QRect restore = geometry(); + // when only partially maximized, geom_restore may not have the other dimension remembered + if( old_mode & MaximizeVertical ) + { + restore.setTop( geom_restore.top()); + restore.setBottom( geom_restore.bottom()); + } + if( old_mode & MaximizeHorizontal ) + { + restore.setLeft( geom_restore.left()); + restore.setRight( geom_restore.right()); + } + if( !restore.isValid()) + { + QSize s = QSize( clientArea.width()*2/3, clientArea.height()*2/3 ); + if( geom_restore.width() > 0 ) + s.setWidth( geom_restore.width()); + if( geom_restore.height() > 0 ) + s.setHeight( geom_restore.height()); + plainResize( adjustedSize( s )); + workspace()->placeSmart( this, clientArea ); + restore = geometry(); + if( geom_restore.width() > 0 ) + restore.moveLeft( geom_restore.x()); + if( geom_restore.height() > 0 ) + restore.moveTop( geom_restore.y()); + } + setGeometry( restore, ForceGeometrySet ); + info->setState( 0, NET::Max ); + break; + } + + case MaximizeFull: + { + if( !adjust ) + { + if( old_mode & MaximizeVertical ) + maxmode_restore = MaximizeVertical; + if( old_mode & MaximizeHorizontal ) + maxmode_restore = MaximizeHorizontal; + } + QSize adjSize = adjustedSize(clientArea.size(), SizemodeMax ); + QRect r = QRect(clientArea.topLeft(), adjSize); + setGeometry( r, ForceGeometrySet ); + info->setState( NET::Max, NET::Max ); + break; + } + default: + break; + } + + updateAllowedActions(); + if( decoration != NULL ) + decoration->maximizeChange(); + updateWindowRules(); + } + +void Client::resetMaximize() + { + if( max_mode == MaximizeRestore ) + return; + max_mode = MaximizeRestore; + Notify::raise( Notify::UnMaximize ); + info->setState( 0, NET::Max ); + updateAllowedActions(); + if( decoration != NULL ) + decoration->borders( border_left, border_right, border_top, border_bottom ); + if( isShade()) + setGeometry( QRect( pos(), sizeForClientSize( clientSize())), ForceGeometrySet ); + else + setGeometry( geometry(), ForceGeometrySet ); + if( decoration != NULL ) + decoration->maximizeChange(); + } + +void Client::checkMaximizeGeometry() + { + // when adding new bail-out conditions here, checkMaximizeGeometry() needs to be called + // when after the condition is no longer true + if( isShade()) + return; + if( isMove() || isResize()) // this is because of the option to disallow moving/resizing of max-ed windows + return; + // Just in case. + static int recursion_protection = 0; + if( recursion_protection > 3 ) + { + kdWarning( 1212 ) << "Check maximize overflow - you loose!" << endl; + kdWarning( 1212 ) << kdBacktrace() << endl; + return; + } + ++recursion_protection; + QRect max_area = workspace()->clientArea( MaximizeArea, this ); + if( geometry() == max_area ) + { + if( max_mode != MaximizeFull ) + maximize( MaximizeFull ); + } + else if( x() == max_area.left() && width() == max_area.width()) + { + if( max_mode != MaximizeHorizontal ) + maximize( MaximizeHorizontal ); + } + else if( y() == max_area.top() && height() == max_area.height()) + { + if( max_mode != MaximizeVertical ) + maximize( MaximizeVertical ); + } + else if( max_mode != MaximizeRestore ) + { + resetMaximize(); // not maximize( MaximizeRestore ), that'd change geometry - this is called from setGeometry() + } + --recursion_protection; + } + +bool Client::isFullScreenable( bool fullscreen_hack ) const + { + if( !rules()->checkFullScreen( true )) + return false; + if( fullscreen_hack ) + return isNormalWindow(); + if( rules()->checkStrictGeometry( false )) + { + // the app wouldn't fit exactly fullscreen geometry due its strict geometry requirements + QRect fsarea = workspace()->clientArea( FullScreenArea, this ); + if( sizeForClientSize( fsarea.size(), SizemodeAny, true ) != fsarea.size()) + return false; + } + // don't check size constrains - some apps request fullscreen despite requesting fixed size + return !isSpecialWindow(); // also better disallow only weird types to go fullscreen + } + +bool Client::userCanSetFullScreen() const + { + if( fullscreen_mode == FullScreenHack ) + return false; + if( !isFullScreenable( false )) + return false; + // isMaximizable() returns false if fullscreen + TemporaryAssign< FullScreenMode > tmp( fullscreen_mode, FullScreenNone ); + return isNormalWindow() && isMaximizable(); + } + +void Client::setFullScreen( bool set, bool user ) + { + if( !isFullScreen() && !set ) + return; + if( fullscreen_mode == FullScreenHack ) + return; + if( user && !userCanSetFullScreen()) + return; + set = rules()->checkFullScreen( set ); + setShade( ShadeNone ); + bool was_fs = isFullScreen(); + if( !was_fs ) + geom_fs_restore = geometry(); + fullscreen_mode = set ? FullScreenNormal : FullScreenNone; + if( was_fs == isFullScreen()) + return; + StackingUpdatesBlocker blocker1( workspace()); + GeometryUpdatesPostponer blocker2( this ); + workspace()->updateClientLayer( this ); // active fullscreens get different layer + info->setState( isFullScreen() ? NET::FullScreen : 0, NET::FullScreen ); + updateDecoration( false, false ); + if( isFullScreen()) + setGeometry( workspace()->clientArea( FullScreenArea, this )); + else + { + if( !geom_fs_restore.isNull()) + setGeometry( QRect( geom_fs_restore.topLeft(), adjustedSize( geom_fs_restore.size()))); + // TODO isShaded() ? + else + { // does this ever happen? + setGeometry( workspace()->clientArea( MaximizeArea, this )); + } + } + updateWindowRules(); + } + +int Client::checkFullScreenHack( const QRect& geom ) const + { + // if it's noborder window, and has size of one screen or the whole desktop geometry, it's fullscreen hack + if( noBorder() && !isUserNoBorder() && isFullScreenable( true )) + { + if( geom.size() == workspace()->clientArea( FullArea, geom.center(), desktop()).size()) + return 2; // full area fullscreen hack + if( geom.size() == workspace()->clientArea( ScreenArea, geom.center(), desktop()).size()) + return 1; // xinerama-aware fullscreen hack + } + return 0; + } + +void Client::updateFullScreenHack( const QRect& geom ) + { + int type = checkFullScreenHack( geom ); + if( fullscreen_mode == FullScreenNone && type != 0 ) + { + fullscreen_mode = FullScreenHack; + updateDecoration( false, false ); + QRect geom; + if( rules()->checkStrictGeometry( false )) + { + geom = type == 2 // 1 - it's xinerama-aware fullscreen hack, 2 - it's full area + ? workspace()->clientArea( FullArea, geom.center(), desktop()) + : workspace()->clientArea( ScreenArea, geom.center(), desktop()); + } + else + geom = workspace()->clientArea( FullScreenArea, geom.center(), desktop()); + setGeometry( geom ); + } + else if( fullscreen_mode == FullScreenHack && type == 0 ) + { + fullscreen_mode = FullScreenNone; + updateDecoration( false, false ); + // whoever called this must setup correct geometry + } + StackingUpdatesBlocker blocker( workspace()); + workspace()->updateClientLayer( this ); // active fullscreens get different layer + } + +static QRect* visible_bound = 0; +static GeometryTip* geometryTip = 0; + +void Client::drawbound( const QRect& geom ) + { + assert( visible_bound == NULL ); + visible_bound = new QRect( geom ); + doDrawbound( *visible_bound, false ); + } + +void Client::clearbound() + { + if( visible_bound == NULL ) + return; + doDrawbound( *visible_bound, true ); + delete visible_bound; + visible_bound = 0; + } + +void Client::doDrawbound( const QRect& geom, bool clear ) + { + if( decoration != NULL && decoration->drawbound( geom, clear )) + return; // done by decoration + QPainter p ( workspace()->desktopWidget() ); + p.setPen( QPen( Qt::white, 5 ) ); + p.setRasterOp( Qt::XorROP ); + // the line is 5 pixel thick, so compensate for the extra two pixels + // on outside (#88657) + QRect g = geom; + if( g.width() > 5 ) + { + g.setLeft( g.left() + 2 ); + g.setRight( g.right() - 2 ); + } + if( g.height() > 5 ) + { + g.setTop( g.top() + 2 ); + g.setBottom( g.bottom() - 2 ); + } + p.drawRect( g ); + } + +void Client::positionGeometryTip() + { + assert( isMove() || isResize()); + // Position and Size display + if (options->showGeometryTip()) + { + if( !geometryTip ) + { // save under is not necessary with opaque, and seem to make things slower + bool save_under = ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) + || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ); + geometryTip = new GeometryTip( &xSizeHint, save_under ); + } + QRect wgeom( moveResizeGeom ); // position of the frame, size of the window itself + wgeom.setWidth( wgeom.width() - ( width() - clientSize().width())); + wgeom.setHeight( wgeom.height() - ( height() - clientSize().height())); + if( isShade()) + wgeom.setHeight( 0 ); + geometryTip->setGeometry( wgeom ); + if( !geometryTip->isVisible()) + { + geometryTip->show(); + geometryTip->raise(); + } + } + } + +class EatAllPaintEvents + : public QObject + { + protected: + virtual bool eventFilter( QObject* o, QEvent* e ) + { return e->type() == QEvent::Paint && o != geometryTip; } + }; + +static EatAllPaintEvents* eater = 0; + +bool Client::startMoveResize() + { + assert( !moveResizeMode ); + assert( QWidget::keyboardGrabber() == NULL ); + assert( QWidget::mouseGrabber() == NULL ); + if( QApplication::activePopupWidget() != NULL ) + return false; // popups have grab + bool has_grab = false; + // This reportedly improves smoothness of the moveresize operation, + // something with Enter/LeaveNotify events, looks like XFree performance problem or something *shrug* + // (http://lists.kde.org/?t=107302193400001&r=1&w=2) + XSetWindowAttributes attrs; + QRect r = workspace()->clientArea( FullArea, this ); + move_resize_grab_window = XCreateWindow( qt_xdisplay(), workspace()->rootWin(), r.x(), r.y(), + r.width(), r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, 0, &attrs ); + XMapRaised( qt_xdisplay(), move_resize_grab_window ); + if( XGrabPointer( qt_xdisplay(), move_resize_grab_window, False, + ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, + GrabModeAsync, GrabModeAsync, move_resize_grab_window, cursor.handle(), qt_x_time ) == Success ) + has_grab = true; + if( XGrabKeyboard( qt_xdisplay(), frameId(), False, GrabModeAsync, GrabModeAsync, qt_x_time ) == Success ) + has_grab = true; + if( !has_grab ) // at least one grab is necessary in order to be able to finish move/resize + { + XDestroyWindow( qt_xdisplay(), move_resize_grab_window ); + move_resize_grab_window = None; + return false; + } + if ( maximizeMode() != MaximizeRestore ) + resetMaximize(); + moveResizeMode = true; + workspace()->setClientIsMoving(this); + initialMoveResizeGeom = moveResizeGeom = geometry(); + checkUnrestrictedMoveResize(); + // rule out non opaque windows from useless translucency settings, maybe resizes? + if ((isResize() && options->removeShadowsOnResize) || (isMove() && options->removeShadowsOnMove)) + setShadowSize(0); + if (rules()->checkMoveResizeMode( options->moveMode ) == Options::Opaque){ + savedOpacity_ = opacity_; + setOpacity(options->translucentMovingWindows, options->movingWindowOpacity); + } + if ( ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) + || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ) ) + { + grabXServer(); + kapp->sendPostedEvents(); + // we have server grab -> nothing should cause paint events + // unfortunately, that's not completely true, Qt may generate + // paint events on some widgets due to FocusIn(?) + // eat them, otherwise XOR painting will be broken (#58054) + // paint events for the geometrytip need to be allowed, though + eater = new EatAllPaintEvents; +// not needed anymore? kapp->installEventFilter( eater ); + } + Notify::raise( isResize() ? Notify::ResizeStart : Notify::MoveStart ); + return true; + } + +void Client::finishMoveResize( bool cancel ) + { + leaveMoveResize(); + if( cancel ) + setGeometry( initialMoveResizeGeom ); + else + setGeometry( moveResizeGeom ); + checkMaximizeGeometry(); +// FRAME update(); + Notify::raise( isResize() ? Notify::ResizeEnd : Notify::MoveEnd ); + } + +void Client::leaveMoveResize() + { + // rule out non opaque windows from useless translucency settings, maybe resizes? + if (rules()->checkMoveResizeMode( options->moveMode ) == Options::Opaque) + setOpacity(true, savedOpacity_); + if ((isResize() && options->removeShadowsOnResize) || (isMove() && options->removeShadowsOnMove)) + updateShadowSize(); + clearbound(); + if (geometryTip) + { + geometryTip->hide(); + delete geometryTip; + geometryTip = NULL; + } + if ( ( isMove() && rules()->checkMoveResizeMode( options->moveMode ) != Options::Opaque ) + || ( isResize() && rules()->checkMoveResizeMode( options->resizeMode ) != Options::Opaque ) ) + ungrabXServer(); + XUngrabKeyboard( qt_xdisplay(), qt_x_time ); + XUngrabPointer( qt_xdisplay(), qt_x_time ); + XDestroyWindow( qt_xdisplay(), move_resize_grab_window ); + move_resize_grab_window = None; + workspace()->setClientIsMoving(0); + if( move_faked_activity ) + workspace()->unfakeActivity( this ); + move_faked_activity = false; + moveResizeMode = false; + delete eater; + eater = 0; + } + +// This function checks if it actually makes sense to perform a restricted move/resize. +// If e.g. the titlebar is already outside of the workarea, there's no point in performing +// a restricted move resize, because then e.g. resize would also move the window (#74555). +// NOTE: Most of it is duplicated from handleMoveResize(). +void Client::checkUnrestrictedMoveResize() + { + if( unrestrictedMoveResize ) + return; + QRect desktopArea = workspace()->clientArea( WorkArea, moveResizeGeom.center(), desktop()); + int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; + // restricted move/resize - keep at least part of the titlebar always visible + // how much must remain visible when moved away in that direction + left_marge = KMIN( 100 + border_right, moveResizeGeom.width()); + right_marge = KMIN( 100 + border_left, moveResizeGeom.width()); + // width/height change with opaque resizing, use the initial ones + titlebar_marge = initialMoveResizeGeom.height(); + top_marge = border_bottom; + bottom_marge = border_top; + if( isResize()) + { + if( moveResizeGeom.bottom() < desktopArea.top() + top_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + unrestrictedMoveResize = true; + if( !unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top() ) // titlebar mustn't go out + unrestrictedMoveResize = true; + } + if( isMove()) + { + if( moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1 ) // titlebar mustn't go out + unrestrictedMoveResize = true; + // no need to check top_marge, titlebar_marge already handles it + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + unrestrictedMoveResize = true; + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + unrestrictedMoveResize = true; + } + } + +void Client::handleMoveResize( int x, int y, int x_root, int y_root ) + { + if(( mode == PositionCenter && !isMovable()) + || ( mode != PositionCenter && ( isShade() || !isResizable()))) + return; + + if ( !moveResizeMode ) + { + QPoint p( QPoint( x, y ) - moveOffset ); + if (p.manhattanLength() >= 6) + { + if( !startMoveResize()) + { + buttonDown = false; + setCursor( mode ); + return; + } + } + else + return; + } + + // ShadeHover or ShadeActive, ShadeNormal was already avoided above + if ( mode != PositionCenter && shade_mode != ShadeNone ) + setShade( ShadeNone ); + + QPoint globalPos( x_root, y_root ); + // these two points limit the geometry rectangle, i.e. if bottomleft resizing is done, + // the bottomleft corner should be at is at (topleft.x(), bottomright().y()) + QPoint topleft = globalPos - moveOffset; + QPoint bottomright = globalPos + invertedMoveOffset; + QRect previousMoveResizeGeom = moveResizeGeom; + + // TODO move whole group when moving its leader or when the leader is not mapped? + + // compute bounds + // NOTE: This is duped in checkUnrestrictedMoveResize(). + QRect desktopArea = workspace()->clientArea( WorkArea, globalPos, desktop()); + int left_marge, right_marge, top_marge, bottom_marge, titlebar_marge; + if( unrestrictedMoveResize ) // unrestricted, just don't let it go out completely + left_marge = right_marge = top_marge = bottom_marge = titlebar_marge = 5; + else // restricted move/resize - keep at least part of the titlebar always visible + { + // how much must remain visible when moved away in that direction + left_marge = KMIN( 100 + border_right, moveResizeGeom.width()); + right_marge = KMIN( 100 + border_left, moveResizeGeom.width()); + // width/height change with opaque resizing, use the initial ones + titlebar_marge = initialMoveResizeGeom.height(); + top_marge = border_bottom; + bottom_marge = border_top; + } + + bool update = false; + if( isResize()) + { + // first resize (without checking constrains), then snap, then check bounds, then check constrains + QRect orig = initialMoveResizeGeom; + Sizemode sizemode = SizemodeAny; + switch ( mode ) + { + case PositionTopLeft: + moveResizeGeom = QRect( topleft, orig.bottomRight() ) ; + break; + case PositionBottomRight: + moveResizeGeom = QRect( orig.topLeft(), bottomright ) ; + break; + case PositionBottomLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.y() ), QPoint( orig.right(), bottomright.y()) ) ; + break; + case PositionTopRight: + moveResizeGeom = QRect( QPoint( orig.x(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; + break; + case PositionTop: + moveResizeGeom = QRect( QPoint( orig.left(), topleft.y() ), orig.bottomRight() ) ; + sizemode = SizemodeFixedH; // try not to affect height + break; + case PositionBottom: + moveResizeGeom = QRect( orig.topLeft(), QPoint( orig.right(), bottomright.y() ) ) ; + sizemode = SizemodeFixedH; + break; + case PositionLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.top() ), orig.bottomRight() ) ; + sizemode = SizemodeFixedW; + break; + case PositionRight: + moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), orig.bottom() ) ) ; + sizemode = SizemodeFixedW; + break; + case PositionCenter: + default: + assert( false ); + break; + } + + // adjust new size to snap to other windows/borders + moveResizeGeom = workspace()->adjustClientSize( this, moveResizeGeom, mode ); + + // NOTE: This is duped in checkUnrestrictedMoveResize(). + if( moveResizeGeom.bottom() < desktopArea.top() + top_marge ) + moveResizeGeom.setBottom( desktopArea.top() + top_marge ); + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + moveResizeGeom.setTop( desktopArea.bottom() - bottom_marge ); + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + moveResizeGeom.setRight( desktopArea.left() + left_marge ); + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + moveResizeGeom.setLeft(desktopArea.right() - right_marge ); + if( !unrestrictedMoveResize && moveResizeGeom.top() < desktopArea.top() ) // titlebar mustn't go out + moveResizeGeom.setTop( desktopArea.top()); + + QSize size = adjustedSize( moveResizeGeom.size(), sizemode ); + // the new topleft and bottomright corners (after checking size constrains), if they'll be needed + topleft = QPoint( moveResizeGeom.right() - size.width() + 1, moveResizeGeom.bottom() - size.height() + 1 ); + bottomright = QPoint( moveResizeGeom.left() + size.width() - 1, moveResizeGeom.top() + size.height() - 1 ); + orig = moveResizeGeom; + switch ( mode ) + { // these 4 corners ones are copied from above + case PositionTopLeft: + moveResizeGeom = QRect( topleft, orig.bottomRight() ) ; + break; + case PositionBottomRight: + moveResizeGeom = QRect( orig.topLeft(), bottomright ) ; + break; + case PositionBottomLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.y() ), QPoint( orig.right(), bottomright.y()) ) ; + break; + case PositionTopRight: + moveResizeGeom = QRect( QPoint( orig.x(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; + break; + // The side ones can't be copied exactly - if aspect ratios are specified, both dimensions may change. + // Therefore grow to the right/bottom if needed. + // TODO it should probably obey gravity rather than always using right/bottom ? + case PositionTop: + moveResizeGeom = QRect( QPoint( orig.left(), topleft.y() ), QPoint( bottomright.x(), orig.bottom()) ) ; + break; + case PositionBottom: + moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), bottomright.y() ) ) ; + break; + case PositionLeft: + moveResizeGeom = QRect( QPoint( topleft.x(), orig.top() ), QPoint( orig.right(), bottomright.y())); + break; + case PositionRight: + moveResizeGeom = QRect( orig.topLeft(), QPoint( bottomright.x(), bottomright.y() ) ) ; + break; + case PositionCenter: + default: + assert( false ); + break; + } + if( moveResizeGeom.size() != previousMoveResizeGeom.size()) + update = true; + } + else if( isMove()) + { + assert( mode == PositionCenter ); + // first move, then snap, then check bounds + moveResizeGeom.moveTopLeft( topleft ); + moveResizeGeom.moveTopLeft( workspace()->adjustClientPosition( this, moveResizeGeom.topLeft() ) ); + // NOTE: This is duped in checkUnrestrictedMoveResize(). + if( moveResizeGeom.bottom() < desktopArea.top() + titlebar_marge - 1 ) // titlebar mustn't go out + moveResizeGeom.moveBottom( desktopArea.top() + titlebar_marge - 1 ); + // no need to check top_marge, titlebar_marge already handles it + if( moveResizeGeom.top() > desktopArea.bottom() - bottom_marge ) + moveResizeGeom.moveTop( desktopArea.bottom() - bottom_marge ); + if( moveResizeGeom.right() < desktopArea.left() + left_marge ) + moveResizeGeom.moveRight( desktopArea.left() + left_marge ); + if( moveResizeGeom.left() > desktopArea.right() - right_marge ) + moveResizeGeom.moveLeft(desktopArea.right() - right_marge ); + if( moveResizeGeom.topLeft() != previousMoveResizeGeom.topLeft()) + update = true; + } + else + assert( false ); + + if( update ) + { + if( rules()->checkMoveResizeMode + ( isResize() ? options->resizeMode : options->moveMode ) == Options::Opaque ) + { + setGeometry( moveResizeGeom ); + positionGeometryTip(); + } + else if( rules()->checkMoveResizeMode + ( isResize() ? options->resizeMode : options->moveMode ) == Options::Transparent ) + { + clearbound(); // it's necessary to move the geometry tip when there's no outline + positionGeometryTip(); // shown, otherwise it would cause repaint problems in case + drawbound( moveResizeGeom ); // they overlap; the paint event will come after this, + } // so the geometry tip will be painted above the outline + } + if ( isMove() ) + workspace()->clientMoved(globalPos, qt_x_time); + } + + +} // namespace |