/***************************************************************** 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 <tqpainter.h> #include <twin.h> #include "placement.h" #include "notifications.h" #include "geometrytip.h" #include "rules.h" namespace KWinInternal { //******************************************** // Workspace //******************************************** /*! Resizes the workspace after an XRANDR screen size change */ void Workspace::desktopResized() { //printf("Workspace::desktopResized()\n\r"); TQRect geom = KApplication::desktop()->geometry(); NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry( -1, desktop_geometry ); updateClientArea( true ); checkElectricBorders( true ); } /*! Resizes the workspace after kdesktop signals a desktop resize */ void Workspace::kDestopResized() { //printf("Workspace::kDesktopResized()\n\r"); TQRect geom = KApplication::desktop()->geometry(); NETSize desktop_geometry; desktop_geometry.width = geom.width(); desktop_geometry.height = geom.height(); rootInfo->setDesktopGeometry( -1, desktop_geometry ); updateClientArea( true ); 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 ) { TQDesktopWidget *desktopwidget = KApplication::desktop(); int nscreens = desktopwidget -> numScreens (); // kdDebug () << "screens: " << nscreens << endl; TQRect* new_wareas = new TQRect[ numberOfDesktops() + 1 ]; TQRect** new_sareas = new TQRect*[ numberOfDesktops() + 1]; TQRect* screens = new TQRect [ nscreens ]; TQRect 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 TQRect [ 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; TQRect 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 ) { TQRect 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() */ TQRect Workspace::clientArea( clientAreaOption opt, int screen, int desktop ) const { if( desktop == NETWinInfo::OnAllDesktops || desktop == 0 ) desktop = currentDesktop(); TQDesktopWidget *desktopwidget = kapp->desktop(); TQRect sarea = screenarea // may be NULL during KWin initialization ? screenarea[ desktop ][ screen ] : desktopwidget->screenGeometry( screen ); TQRect warea = workarea[ desktop ].isNull() ? kapp->desktop()->geometry() : workarea[ desktop ]; switch (opt) { case MaximizeArea: if (options->xineramaMaximizeEnabled) if (desktopwidget->numScreens() < 2) return warea; else return sarea; else return warea; case MaximizeFullArea: if (options->xineramaMaximizeEnabled) if (desktopwidget->numScreens() < 2) return desktopwidget->geometry(); else return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case FullScreenArea: if (options->xineramaFullscreenEnabled) if (desktopwidget->numScreens() < 2) return desktopwidget->geometry(); else return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case PlacementArea: if (options->xineramaPlacementEnabled) if (desktopwidget->numScreens() < 2) return warea; else return sarea; else return warea; case MovementArea: if (options->xineramaMovementEnabled) if (desktopwidget->numScreens() < 2) return desktopwidget->geometry(); else return desktopwidget->screenGeometry( screen ); else return desktopwidget->geometry(); case WorkArea: return warea; case FullArea: return desktopwidget->geometry(); case ScreenArea: if (desktopwidget->numScreens() < 2) return desktopwidget->geometry(); else return desktopwidget->screenGeometry( screen ); } assert( false ); return TQRect(); } TQRect Workspace::clientArea( clientAreaOption opt, const TQPoint& p, int desktop ) const { TQDesktopWidget *desktopwidget = KApplication::desktop(); int screen = desktopwidget->screenNumber( p ); if( screen < 0 ) screen = desktopwidget->primaryScreen(); return clientArea( opt, screen, desktop ); } TQRect 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. */ TQPoint Workspace::adjustClientPosition( Client* c, TQPoint pos ) { //CT 16mar98, 27May98 - magics: BorderSnapZone, WindowSnapZone //CT adapted for twin on 25Nov1999 //aleXXX 02Nov2000 added second snapping mode if (options->windowSnapZone || options->borderSnapZone ) { const bool sOWO=options->snapOnlyWhenOverlapping; const TQRect 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) { TQValueList<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 = TQPoint(nx, ny); } return pos; } TQRect Workspace::adjustClientSize( Client* c, TQRect 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 TQRect 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); TQValueList<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 = TQRect(TQPoint(newcx, newcy), TQPoint(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()); TQRect area = clientArea( PlacementArea, TQPoint( 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, TQRect()); } } void Workspace::updateTopMenuGeometry( Client* c ) { if( !managingTopMenus()) return; if( c != NULL ) { XEvent ev; ev.xclient.display = tqt_xdisplay(); ev.xclient.type = ClientMessage; ev.xclient.window = c->window(); static Atom msg_type_atom = XInternAtom( tqt_xdisplay(), "_KDE_TOPMENU_MINSIZE", False ); ev.xclient.message_type = msg_type_atom; ev.xclient.format = 32; ev.xclient.data.l[0] = GET_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( tqt_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 TQRect area; area = clientArea( MaximizeFullArea, TQPoint( 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( TQRect 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? TQRect Client::adjustedClientArea( const TQRect &desktopArea, const TQRect& area ) const { TQRect r = area; // topmenu area is reserved in updateClientArea() if( isTopMenu()) return r; NETExtendedStrut str = strut(); TQRect stareaL = TQRect( 0, str . left_start, str . left_width, str . left_end - str . left_start + 1 ); TQRect stareaR = TQRect ( desktopArea . right () - str . right_width + 1, str . right_start, str . right_width, str . right_end - str . right_start + 1 ); TQRect stareaT = TQRect ( str . top_start, 0, str . top_end - str . top_start + 1, str . top_width); TQRect stareaB = TQRect ( 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; } } TQRect 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 = TQRect(); if( stareaR.right() > screenarea.right()) stareaR = TQRect(); if( stareaT.top() < screenarea.top()) stareaT = TQRect(); if( stareaB.bottom() < screenarea.bottom()) stareaB = TQRect(); } // 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( tqt_xdisplay(), DefaultScreen( tqt_xdisplay())); } if( str.right != 0 ) { ext.right_width = str.right; ext.right_start = 0; ext.right_end = XDisplayHeight( tqt_xdisplay(), DefaultScreen( tqt_xdisplay())); } if( str.top != 0 ) { ext.top_width = str.top; ext.top_start = 0; ext.top_end = XDisplayWidth( tqt_xdisplay(), DefaultScreen( tqt_xdisplay())); } if( str.bottom != 0 ) { ext.bottom_width = str.bottom; ext.bottom_start = 0; ext.bottom_end = XDisplayWidth( tqt_xdisplay(), DefaultScreen( tqt_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() { TQRect area = workspace()->clientArea( WorkArea, this ); TQRect 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()) { TQRect area = workspace()->clientArea( FullArea, this ); if( geometry() != area ) setGeometry( area ); return; } if( isFullScreen()) { TQRect area = workspace()->clientArea( FullScreenArea, this ); if( geometry() != area ) setGeometry( area ); return; } if( isDock()) return; if( isTopMenu()) { if( workspace()->managingTopMenus()) { TQRect area; ClientList mainclients = mainClients(); if( mainclients.count() == 1 ) area = workspace()->clientArea( MaximizeFullArea, mainclients.first()); else area = workspace()->clientArea( MaximizeFullArea, TQPoint( 0, 0 ), desktop()); area.setHeight( workspace()->topMenuHeight()); // kdDebug() << "TOPMENU size adjust: " << area << ":" << this << endl; setGeometry( area ); } return; } if( maximizeMode() != MaximizeRestore ) // TODO update geom_restore? changeMaximize( false, false, true ); // adjust size 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; TQRect area = workspace()->clientArea( WorkArea, this ); TQRect new_geom = geometry(); TQRect tmp_rect_x( new_geom.left(), 0, new_geom.width(), 0 ); TQRect 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 TQRect tmp_rect_y( new_geom.top(), 0, new_geom.height(), 0 ); TQRect tmp_area_y( area.top(), 0, area.height(), 0 ); checkDirection( workarea_diff_y, old_diff_y, tmp_rect_y, tmp_area_y ); new_geom = TQRect( tmp_rect_x.left(), tmp_rect_y.left(), tmp_rect_x.width(), tmp_rect_y.width()); TQRect 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, TQRect& rect, const TQRect& 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 ); } } if (!moveResizeMode && options->shadowEnabled(isActive())) { // If the user is manually resizing, let Client::leaveMoveResize() // decide when to redraw the shadow removeShadow(); drawIntersectingShadows(); if (options->shadowEnabled(isActive())) drawDelayedShadow(); } } /*! Adjust the frame size \a frame according to he window's size hints. */ TQSize Client::adjustedSize( const TQSize& frame, Sizemode mode ) const { // first, get the window size for the given frame size s TQSize wsize( frame.width() - ( border_left + border_right ), frame.height() - ( border_top + border_bottom )); if( wsize.isEmpty()) wsize = TQSize( 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() TQSize 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). */ TQSize Client::sizeForClientSize( const TQSize& 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() TQSize min_size = minSize(); TQSize max_size = maxSize(); if( decoration != NULL ) { TQSize decominsize = decoration->minimumSize(); TQSize 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( TQSize( w, h )); } /*! Gets the client's normal WM hints and reconfigures itself respectively. */ void Client::getWmNormalHints() { long msize; if (XGetWMNormalHints(tqt_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 TQSize new_size = adjustedSize(); if( new_size != size() && !isFullScreen()) { TQRect 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 TQRect 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() } TQSize Client::minSize() const { return rules()->checkMinSize( TQSize( xSizeHint.min_width, xSizeHint.min_height )); } TQSize Client::maxSize() const { return rules()->checkMaxSize( TQSize( 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( tqt_xdisplay(), c.event, TRUE, StructureNotifyMask, (XEvent*)&c ); } const TQPoint 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 TQPoint( x() + dx, y() + dy ); else return TQPoint( 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 )) { TQPoint 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 twin 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; TQSize ns = sizeForClientSize( TQSize( nw, nh ) ); new_pos = rules()->checkPosition( new_pos ); // TODO what to do with maximized windows? if ( maximizeMode() != MaximizeFull || ns != size()) { TQRect orig_geometry = geometry(); GeometryUpdatesPostponer blocker( this ); move( new_pos ); plainResize( ns ); setGeometry( TQRect( calculateGravitation( false, gravity ), size())); updateFullScreenHack( TQRect( new_pos, TQSize( nw, nh ))); TQRect 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; TQSize ns = sizeForClientSize( TQSize( nw, nh ) ); if( ns != size()) // don't restore if some app sets its own size again { TQRect orig_geometry = geometry(); GeometryUpdatesPostponer blocker( this ); int save_gravity = xSizeHint.win_gravity; xSizeHint.win_gravity = gravity; resizeWithChecks( ns ); xSizeHint.win_gravity = save_gravity; updateFullScreenHack( TQRect( calculateGravitation( true, xSizeHint.win_gravity ), TQSize( 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 TQRect 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(); TQRect 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(); TQSize tmp = adjustedSize( TQSize( 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( TQSize()).isValid()) // forced size return false; TQSize min = minSize(); TQSize max = maxSize(); return min.width() < max.width() || min.height() < max.height(); } /* Returns whether the window is maximizable or not */ bool Client::isMaximizable() const { if( isModalSystemNotification()) return false; { // 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; TQSize 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 TQSize 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 = TQSize( w - border_left - border_right, h - border_top - border_bottom ); h = border_top + border_bottom; } } else { client_size = TQSize( w - border_left - border_right, h - border_top - border_bottom ); } if( force == NormalGeometrySet && frame_geometry == TQRect( x, y, w, h )) return; frame_geometry = TQRect( x, y, w, h ); updateWorkareaDiffs(); if( postpone_geometry_updates != 0 ) { pending_geometry_update = true; return; } resizeDecoration( TQSize( w, h )); XMoveResizeWindow( tqt_xdisplay(), frameId(), x, y, w, h ); // resizeDecoration( TQSize( w, h )); if( !isShade()) { TQSize cs = clientSize(); XMoveResizeWindow( tqt_xdisplay(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); XMoveResizeWindow( tqt_xdisplay(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); // SELI TODO won't this be too expensive? updateWorkareaDiffs(); sendSyntheticConfigureNotify(); updateWindowRules(); checkMaximizeGeometry(); workspace()->checkActiveScreen( this ); } 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 = TQSize( w - border_left - border_right, h - border_top - border_bottom ); h = border_top + border_bottom; } } else { client_size = TQSize( w - border_left - border_right, h - border_top - border_bottom ); } if( TQSize( w, h ) != rules()->checkSize( TQSize( w, h ))) { kdDebug() << "forced size fail:" << TQSize( w,h ) << ":" << rules()->checkSize( TQSize( w, h )) << endl; kdDebug() << kdBacktrace() << endl; } if( force == NormalGeometrySet && frame_geometry.size() == TQSize( w, h )) return; frame_geometry.setSize( TQSize( w, h )); updateWorkareaDiffs(); if( postpone_geometry_updates != 0 ) { pending_geometry_update = true; return; } resizeDecoration( TQSize( w, h )); XResizeWindow( tqt_xdisplay(), frameId(), w, h ); // resizeDecoration( TQSize( w, h )); if( !isShade()) { TQSize cs = clientSize(); XMoveResizeWindow( tqt_xdisplay(), wrapperId(), clientPos().x(), clientPos().y(), cs.width(), cs.height()); XMoveResizeWindow( tqt_xdisplay(), window(), 0, 0, cs.width(), cs.height()); } updateShape(); updateWorkareaDiffs(); sendSyntheticConfigureNotify(); updateWindowRules(); checkMaximizeGeometry(); workspace()->checkActiveScreen( this ); } /*! 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() == TQPoint( x, y )) return; frame_geometry.moveTopLeft( TQPoint( x, y )); updateWorkareaDiffs(); if( postpone_geometry_updates != 0 ) { pending_geometry_update = true; return; } XMoveWindow( tqt_xdisplay(), frameId(), x, y ); sendSyntheticConfigureNotify(); updateWindowRules(); checkMaximizeGeometry(); workspace()->checkActiveScreen( this ); } 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( TQRect( 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 ))); TQRect 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(TQSize(width(), clientArea.height()), SizemodeFixedH )); workspace()->placeSmart( this, clientArea ); } else setGeometry( TQRect(TQPoint( geom_restore.x(), clientArea.top()), adjustedSize(TQSize( geom_restore.width(), clientArea.height()), SizemodeFixedH )), ForceGeometrySet); } else setGeometry( TQRect(TQPoint(x(), clientArea.top()), adjustedSize(TQSize(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(TQSize(clientArea.width(), height()), SizemodeFixedW )); workspace()->placeSmart( this, clientArea ); } else setGeometry( TQRect( TQPoint(clientArea.left(), geom_restore.y()), adjustedSize(TQSize(clientArea.width(), geom_restore.height()), SizemodeFixedW )), ForceGeometrySet); } else setGeometry( TQRect( TQPoint(clientArea.left(), y()), adjustedSize(TQSize(clientArea.width(), height()), SizemodeFixedW )), ForceGeometrySet); info->setState( NET::MaxHoriz, NET::Max ); break; } case MaximizeRestore: { TQRect 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()) { TQSize s = TQSize( 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; } TQSize adjSize = adjustedSize(clientArea.size(), SizemodeMax ); TQRect r = TQRect(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( TQRect( 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; TQRect 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 TQRect 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( TQRect( 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 TQRect& 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 TQRect& geom ) { int type = checkFullScreenHack( geom ); if( fullscreen_mode == FullScreenNone && type != 0 ) { fullscreen_mode = FullScreenHack; updateDecoration( false, false ); TQRect 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 TQRect* visible_bound = 0; static GeometryTip* geometryTip = 0; void Client::drawbound( const TQRect& geom ) { assert( visible_bound == NULL ); visible_bound = new TQRect( 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 TQRect& geom, bool clear ) { if( decoration != NULL && decoration->drawbound( geom, clear )) return; // done by decoration TQPainter p ( workspace()->desktopWidget() ); p.setPen( TQPen( Qt::white, 5 ) ); p.setRasterOp( TQt::XorROP ); // the line is 5 pixel thick, so compensate for the extra two pixels // on outside (#88657) TQRect 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 ); } TQRect 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( TQObject* o, TQEvent* e ) { return e->type() == TQEvent::Paint && TQT_BASE_OBJECT(o) != TQT_BASE_OBJECT(geometryTip); } }; static EatAllPaintEvents* eater = 0; bool Client::startMoveResize() { assert( !moveResizeMode ); assert( TQWidget::keyboardGrabber() == NULL ); assert( TQWidget::mouseGrabber() == NULL ); if( TQApplication::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; TQRect r = workspace()->clientArea( FullArea, this ); move_resize_grab_window = XCreateWindow( tqt_xdisplay(), workspace()->rootWin(), r.x(), r.y(), r.width(), r.height(), 0, CopyFromParent, InputOnly, CopyFromParent, 0, &attrs ); XMapRaised( tqt_xdisplay(), move_resize_grab_window ); if( XGrabPointer( tqt_xdisplay(), move_resize_grab_window, False, ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask | LeaveWindowMask, GrabModeAsync, GrabModeAsync, move_resize_grab_window, cursor.handle(), GET_QT_X_TIME() ) == Success ) has_grab = true; if( XGrabKeyboard( tqt_xdisplay(), frameId(), False, GrabModeAsync, GrabModeAsync, GET_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( tqt_xdisplay(), move_resize_grab_window ); move_resize_grab_window = None; return false; } if ( maximizeMode() != MaximizeRestore ) resetMaximize(); removeShadow(); 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( tqt_xdisplay(), GET_QT_X_TIME() ); XUngrabPointer( tqt_xdisplay(), GET_QT_X_TIME() ); XDestroyWindow( tqt_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; if (options->shadowEnabled(isActive())) { drawIntersectingShadows(); updateOpacityCache(); } } // 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; TQRect 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 ) { TQPoint p( TQPoint( 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 ); TQPoint 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()) TQPoint topleft = globalPos - moveOffset; TQPoint bottomright = globalPos + invertedMoveOffset; TQRect 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(). TQRect 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 constraints), then snap, then check bounds, then check constraints TQRect orig = initialMoveResizeGeom; Sizemode sizemode = SizemodeAny; switch ( mode ) { case PositionTopLeft: moveResizeGeom = TQRect( topleft, orig.bottomRight() ) ; break; case PositionBottomRight: moveResizeGeom = TQRect( orig.topLeft(), bottomright ) ; break; case PositionBottomLeft: moveResizeGeom = TQRect( TQPoint( topleft.x(), orig.y() ), TQPoint( orig.right(), bottomright.y()) ) ; break; case PositionTopRight: moveResizeGeom = TQRect( TQPoint( orig.x(), topleft.y() ), TQPoint( bottomright.x(), orig.bottom()) ) ; break; case PositionTop: moveResizeGeom = TQRect( TQPoint( orig.left(), topleft.y() ), orig.bottomRight() ) ; sizemode = SizemodeFixedH; // try not to affect height break; case PositionBottom: moveResizeGeom = TQRect( orig.topLeft(), TQPoint( orig.right(), bottomright.y() ) ) ; sizemode = SizemodeFixedH; break; case PositionLeft: moveResizeGeom = TQRect( TQPoint( topleft.x(), orig.top() ), orig.bottomRight() ) ; sizemode = SizemodeFixedW; break; case PositionRight: moveResizeGeom = TQRect( orig.topLeft(), TQPoint( 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()); TQSize size = adjustedSize( moveResizeGeom.size(), sizemode ); // the new topleft and bottomright corners (after checking size constrains), if they'll be needed topleft = TQPoint( moveResizeGeom.right() - size.width() + 1, moveResizeGeom.bottom() - size.height() + 1 ); bottomright = TQPoint( 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 = TQRect( topleft, orig.bottomRight() ) ; break; case PositionBottomRight: moveResizeGeom = TQRect( orig.topLeft(), bottomright ) ; break; case PositionBottomLeft: moveResizeGeom = TQRect( TQPoint( topleft.x(), orig.y() ), TQPoint( orig.right(), bottomright.y()) ) ; break; case PositionTopRight: moveResizeGeom = TQRect( TQPoint( orig.x(), topleft.y() ), TQPoint( 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 = TQRect( TQPoint( orig.left(), topleft.y() ), TQPoint( bottomright.x(), orig.bottom()) ) ; break; case PositionBottom: moveResizeGeom = TQRect( orig.topLeft(), TQPoint( bottomright.x(), bottomright.y() ) ) ; break; case PositionLeft: moveResizeGeom = TQRect( TQPoint( topleft.x(), orig.top() ), TQPoint( orig.right(), bottomright.y())); break; case PositionRight: moveResizeGeom = TQRect( orig.topLeft(), TQPoint( 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, GET_QT_X_TIME()); } } // namespace