/******************************************************************************* 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)->slotClientGroupItemAdded( 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)->slotClientGroupItemRemoved( 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)->slotClientGroupItemSwitched( 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) { ClientList toBeRemoved; for (ClientList::const_iterator i = clients_.constBegin(), end = clients_.constEnd(); i != end; ++i) { Client *c = (*i); if (c != main && (!only || c == only)) { if (c->isMinimized() != main->isMinimized()) { if (main->isMinimized()) c->minimize(true); else c->unminimize(true); } if (c->isShade() != main->isShade()) c->setShade(main->isShade() ? ShadeNormal : ShadeNone); if (c->geometry() != main->geometry()) c->setGeometry(main->geometry()); if (c->desktop() != main->desktop()) c->setDesktop(main->desktop()); if (c->isOnAllDesktops() != main->isOnAllDesktops()) c->setOnAllDesktops(main->isOnAllDesktops()); if (c->activities() != main->activities()) c->setOnActivities(main->activities()); if (c->keepAbove() != main->keepAbove()) c->setKeepAbove(main->keepAbove()); if (c->keepBelow() != main->keepBelow()) c->setKeepBelow(main->keepBelow()); // If it's not possible to have the same states then ungroup them, TODO: Check all states if (c->geometry() != main->geometry() || c->desktop() != main->desktop()) toBeRemoved << c; } } for (ClientList::const_iterator i = toBeRemoved.constBegin(), end = toBeRemoved.constEnd(); i != end; ++i) 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))); } }