You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
13 KiB
363 lines
13 KiB
/******************************************************************************* |
|
KWin - the KDE window manager |
|
This file is part of the KDE project. |
|
|
|
Copyright (C) 2009 Jorge Mata <matamax123@gmail.com> |
|
Copyright (C) 2009 Lucas Murray <lmurray@undefinedfire.com> |
|
|
|
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 <http://www.gnu.org/licenses/>. |
|
*******************************************************************************/ |
|
|
|
#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<EffectsHandlerImpl*>(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<EffectsHandlerImpl*>(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<EffectsHandlerImpl*>(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 ))); |
|
} |
|
|
|
}
|
|
|