/******************************************************************************* KWin - the KDE window manager This file is part of the KDE project. Copyright (C) 2009 Jorge Mata Copyright (C) 2009 Lucas Murray This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . *******************************************************************************/ #include "clientgroup.h" #include "client.h" #include "effects.h" namespace KWin { ClientGroup::ClientGroup( Client *c ) : clients_() , items_() , visible_( 0 ) , minSize_( 0, 0 ) , maxSize_( INT_MAX, INT_MAX ) { clients_.append( c ); updateItems(); updateMinMaxSize(); Workspace::self()->addClientGroup( this ); c->setClientShown( true ); // Ensure the client is visible c->triggerDecorationRepaint(); // TODO: Required? Maybe for creating new group? } ClientGroup::~ClientGroup() { Workspace::self()->removeClientGroup( this ); } void ClientGroup::add( Client* c, int before, bool becomeVisible ) { if( contains( c ) || !c->workspace()->decorationSupportsClientGrouping() ) return; // Remove the Client->ClientGroup reference if the client is already in another group so we // don't change the geometry of other clients in their current group by accident. However // don't REMOVE them from the actual group until we are certain that the client will be moved. ClientGroup* oldGroup = NULL; if( c->clientGroup() ) { oldGroup = c->clientGroup(); c->setClientGroup( NULL ); } // If it's not possible to have the same states then ungroup them, TODO: Check all states // We do this here as the ungroup code in updateStates() cannot be called until add() completes ShadeMode oldShadeMode = c->shadeMode(); if( c->shadeMode() != clients_[visible_]->shadeMode() ) c->setShade( clients_[visible_]->shadeMode() ); if( c->shadeMode() != clients_[visible_]->shadeMode() ) { if( oldGroup ) // Re-add to old group if required c->setClientGroup( oldGroup ); // One need to trigger decoration repaint on the group to // make sure hover animations are properly reset. clients_[visible_]->triggerDecorationRepaint(); return; } QRect oldGeom = c->geometry(); if( c->geometry() != clients_[visible_]->geometry() ) c->setGeometry( clients_[visible_]->geometry() ); if( c->geometry() != clients_[visible_]->geometry() ) { if( c->shadeMode() != oldShadeMode ) c->setShade( oldShadeMode ); // Restore old shade mode if( oldGroup ) // Re-add to old group if required c->setClientGroup( oldGroup ); clients_[visible_]->triggerDecorationRepaint(); return; } if( c->desktop() != clients_[visible_]->desktop() ) c->setDesktop( clients_[visible_]->desktop() ); if( c->desktop() != clients_[visible_]->desktop() ) { if( c->geometry() != oldGeom ) c->setGeometry( oldGeom ); // Restore old geometry if( c->shadeMode() != oldShadeMode ) c->setShade( oldShadeMode ); // Restore old shade mode if( oldGroup ) // Re-add to old group if required c->setClientGroup( oldGroup ); clients_[visible_]->triggerDecorationRepaint(); return; } // Tabbed windows MUST have a decoration if( c->noBorder() ) c->setNoBorder( false ); if( clients_[visible_]->noBorder() ) clients_[visible_]->setNoBorder( false ); // Re-add to old group if required for the effect hook if( oldGroup ) c->setClientGroup( oldGroup ); // Notify effects of merge if( effects != NULL ) static_cast(effects)->clientGroupItemAdded( c->effectWindow(), clients_[visible_]->effectWindow() ); // Actually remove from old group if required and update if( c->clientGroup() ) c->clientGroup()->remove( c ); c->setClientGroup( this ); // Let the client know which group it belongs to // Actually add to new group if( before >= 0 ) { if( visible_ >= before ) visible_++; clients_.insert( before, c ); } else clients_.append( c ); if( !becomeVisible ) // Hide before adding c->setClientShown( false ); updateItems(); updateMinMaxSize(); updateStates( clients_[visible_], c ); if( becomeVisible ) // Set visible after settings geometry setVisible( c ); // Activate the new visible window clients_[visible_]->setActive( true ); //clients_[visible_]->takeFocus( Allowed ); //clients_[visible_]->workspace()->raiseClient( clients_[visible_] ); clients_[visible_]->triggerDecorationRepaint(); } void ClientGroup::remove( int index, const QRect& newGeom, bool toNullGroup ) { remove( clients_[index], newGeom, toNullGroup ); } void ClientGroup::remove( Client* c, const QRect& newGeom, bool toNullGroup ) { if( !c ) return; if( clients_.count() < 2 ) { c->setClientGroup( NULL ); Workspace::self()->removeClientGroup( this ); // Remove immediately delete this; return; } ClientList::const_iterator i; Client* newVisible = clients_[visible_]; if( newVisible == c ) newVisible = ( visible_ != clients_.size() - 1 ) ? clients_[visible_ + 1] : clients_[visible_ - 1]; // Notify effects of removal if( effects ) static_cast(effects)->clientGroupItemRemoved( c->effectWindow(), newVisible->effectWindow() ); setVisible( newVisible ); // Display new window before removing old one clients_.removeAll( c ); visible_ = indexOfClient( newVisible ); // Index may have changed updateItems(); updateMinMaxSize(); c->setClientGroup( toNullGroup ? NULL : new ClientGroup( c )); if( newGeom.isValid() ) { // HACK: if the group was maximized, one needs to make some checks on the future client maximize mode // because the transition from maximized to MaximizeRestore is not handled properly in setGeometry when // the new geometry size is unchanged. // since newGeom has the same size as the old client geometry, one just needs to check the topLeft position of newGeom // and compare that to the group maximize mode. // when the new mode is predicted to be MaximizeRestore, one must set it manually, in order to avoid decoration artifacts Client::MaximizeMode groupMaxMode( newVisible->maximizeMode() ); if( ( ( groupMaxMode & Client::MaximizeHorizontal ) && newGeom.left() != newVisible->geometry().left() ) || ( ( groupMaxMode & Client::MaximizeVertical ) && newGeom.top() != newVisible->geometry().top() ) ) c->maximize( Client::MaximizeRestore ); c->setGeometry( newGeom ); } newVisible->triggerDecorationRepaint(); } void ClientGroup::removeAll() { while( clients_.count() > 1 ) remove( clients_.at( 1 )); } void ClientGroup::closeAll() { Client* front; ClientList list( clients_ ); while( !list.isEmpty() ) { front = list.front(); list.pop_front(); if( front != clients_[visible_] ) front->closeWindow(); } clients_[visible_]->closeWindow(); } void ClientGroup::move( int index, int before ) { move( clients_[index], ( before >= 0 && before < clients_.size() ) ? clients_[before] : NULL ); } void ClientGroup::move( Client* c, Client* before ) { if( c == before ) // Impossible to do return; Client* wasVisible = clients_[visible_]; clients_.removeAll( c ); clients_.insert( before ? indexOfClient( before ) : clients_.size(), c ); visible_ = indexOfClient( wasVisible ); updateItems(); clients_[visible_]->triggerDecorationRepaint(); } void ClientGroup::displayClientMenu( int index, const QPoint& pos ) { if( index == -1 ) index = visible_; displayClientMenu( clients_[index], pos ); } void ClientGroup::displayClientMenu( Client* c, const QPoint& pos ) { c->workspace()->showWindowMenu( pos, c ); } bool ClientGroup::containsActiveClient() { return contains( Workspace::self()->activeClient() ); } void ClientGroup::setVisible( int index ) { setVisible( clients_[index] ); } void ClientGroup::setVisible( Client* c ) { if( c == clients_[visible_] || !contains( c )) return; // Notify effects of switch if( effects != NULL ) static_cast(effects)->clientGroupItemSwitched( clients_[visible_]->effectWindow(), c->effectWindow() ); visible_ = indexOfClient( c ); c->setClientShown( true ); for( ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i ) if( (*i) != c ) (*i)->setClientShown( false ); } void ClientGroup::updateStates( Client* main, Client* only ) { for( ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); i++ ) if( (*i) != main && ( !only || (*i) == only )) { if( (*i)->isMinimized() != main->isMinimized() ) { if( main->isMinimized() ) (*i)->minimize( true ); else (*i)->unminimize( true ); } if( (*i)->isShade() != main->isShade() ) (*i)->setShade( main->isShade() ? ShadeNormal : ShadeNone ); if( (*i)->geometry() != main->geometry() ) (*i)->setGeometry( main->geometry() ); if( (*i)->desktop() != main->desktop() ) (*i)->setDesktop( main->desktop() ); if( (*i)->isOnAllDesktops() != main->isOnAllDesktops() ) (*i)->setOnAllDesktops( main->isOnAllDesktops() ); if( (*i)->activities() != main->activities() ) (*i)->setOnActivities( main->activities() ); if( (*i)->keepAbove() != main->keepAbove() ) (*i)->setKeepAbove( main->keepAbove() ); if( (*i)->keepBelow() != main->keepBelow() ) (*i)->setKeepBelow( main->keepBelow() ); // If it's not possible to have the same states then ungroup them, TODO: Check all states if( (*i)->geometry() != main->geometry() ) remove( *i ); if( (*i)->desktop() != main->desktop() ) remove( *i ); } } void ClientGroup::updateItems() { items_.clear(); for( ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i ) { QIcon icon( (*i)->icon() ); icon.addPixmap( (*i)->miniIcon() ); items_.append( ClientGroupItem( (*i)->caption(), icon )); } } void ClientGroup::updateMinMaxSize() { // Determine entire group's minimum and maximum sizes minSize_ = QSize( 0, 0 ); maxSize_ = QSize( INT_MAX, INT_MAX ); for( ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i ) { if( (*i)->minSize().width() > minSize_.width() ) minSize_.setWidth( (*i)->minSize().width() ); if( (*i)->minSize().height() > minSize_.height() ) minSize_.setHeight( (*i)->minSize().height() ); if( (*i)->maxSize().width() < maxSize_.width() ) maxSize_.setWidth( (*i)->maxSize().width() ); if( (*i)->maxSize().height() < maxSize_.height() ) maxSize_.setHeight( (*i)->maxSize().height() ); } if( minSize_.width() > maxSize_.width() || minSize_.height() > maxSize_.height() ) { //kWarning(1212) << "ClientGroup's min size is greater than its max size. Setting max to min."; maxSize_ = minSize_; } // Ensure all windows are within these sizes const QSize size = clients_[visible_]->clientSize(); QSize newSize( qBound( minSize_.width(), size.width(), maxSize_.width() ), qBound( minSize_.height(), size.height(), maxSize_.height() )); if( newSize != size ) for( ClientList::const_iterator i = clients_.constBegin(); i != clients_.constEnd(); ++i ) // TODO: Doesn't affect shaded windows? // There seems to be a race condition when using plainResize() which causes the window // to sometimes be located at new window's location instead of the visible window's location // when a window with a large min size is added to a group with a small window size. (*i)->setGeometry( QRect( clients_[visible_]->pos(), (*i)->sizeForClientSize( newSize ))); } }