349 lines
13 KiB
C++
349 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)));
|
|
}
|
|
|
|
}
|