kwin/clientgroup.cpp

364 lines
13 KiB
C++

/*******************************************************************************
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 )));
}
}