2372e02752
Client used to have dedicated methods for different icon sizes instead of combining all pixmaps into one QIcon. This resulted in various parts of KWin having different access to the icons: * effects only got one pixmap of size 32x32 * decorations only got the 16x16 and 32x32 pixmaps combined into a QIcon * tabbox could request all icon sizes, but only as pixmap Now all sizes are available in one QIcon allowing to easily access the best fitting icon in a given UI.
361 lines
12 KiB
C++
361 lines
12 KiB
C++
/*******************************************************************************
|
|
KWin - the KDE window manager
|
|
This file is part of the KDE project.
|
|
|
|
Copyright (C) 2011/2012 The KWin team <kwin@kde.org>
|
|
|
|
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 "tabgroup.h"
|
|
|
|
#include "client.h"
|
|
#include "decorations.h"
|
|
#include "effects.h"
|
|
#include "workspace.h"
|
|
|
|
namespace KWin
|
|
{
|
|
|
|
TabGroup::TabGroup(Client *c)
|
|
: m_clients()
|
|
, m_current(c)
|
|
, m_minSize(c->minSize())
|
|
, m_maxSize(c->maxSize())
|
|
, m_stateUpdatesBlocked(0)
|
|
, m_pendingUpdates(TabGroup::None)
|
|
{
|
|
QIcon icon(c->icon());
|
|
m_clients << c;
|
|
c->setTabGroup(this);
|
|
c->setClientShown(true);
|
|
}
|
|
|
|
TabGroup::~TabGroup()
|
|
{
|
|
}
|
|
|
|
|
|
void TabGroup::activateNext()
|
|
{
|
|
int index = m_clients.indexOf(m_current);
|
|
setCurrent(m_clients.at((index < m_clients.count() - 1) ? index + 1 : 0));
|
|
}
|
|
|
|
void TabGroup::activatePrev()
|
|
{
|
|
int index = m_clients.indexOf(m_current);
|
|
setCurrent(m_clients.at((index > 0) ? index - 1 : m_clients.count() - 1));
|
|
}
|
|
|
|
bool TabGroup::add(Client* c, Client *other, bool after, bool becomeVisible)
|
|
{
|
|
Q_ASSERT(!c->tabGroup());
|
|
|
|
if (!decorationPlugin()->supportsTabbing() || contains(c) || !contains(other))
|
|
return false;
|
|
|
|
// Tabbed windows MUST have a decoration
|
|
c->setNoBorder(false);
|
|
if (c->noBorder())
|
|
return false;
|
|
|
|
// 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
|
|
|
|
bool cannotTab = false;
|
|
ShadeMode oldShadeMode = c->shadeMode();
|
|
QRect oldGeom = c->geometry();
|
|
int oldDesktop = c->desktop();
|
|
|
|
c->setShade(m_current->shadeMode());
|
|
cannotTab = c->shadeMode() != m_current->shadeMode();
|
|
if (!cannotTab) {
|
|
c->setDesktop(m_current->desktop());
|
|
cannotTab = c->desktop() != m_current->desktop();
|
|
}
|
|
if (!cannotTab) {
|
|
c->setGeometry(m_current->geometry());
|
|
cannotTab = c->geometry() != m_current->geometry();
|
|
}
|
|
|
|
if (cannotTab) {
|
|
c->setShade(oldShadeMode);
|
|
c->setDesktop(oldDesktop);
|
|
c->setGeometry(oldGeom);
|
|
// trigger decoration repaint on the group to make sure that hover animations are properly reset.
|
|
m_current->triggerDecorationRepaint();
|
|
return false; // cannot tab
|
|
}
|
|
|
|
// Actually add to new group ----------------------------------------
|
|
|
|
// Notify effects of merge
|
|
if (effects)
|
|
static_cast<EffectsHandlerImpl*>(effects)->slotTabAdded(c->effectWindow(), other->effectWindow());
|
|
|
|
// next: aling the client states BEFORE adding it to the group
|
|
// otherwise the caused indirect state changes would be taken as the dominating ones and break
|
|
// the main client
|
|
// example: QuickTiling is aligned to None, this restores the former QuickTiled size and alignes
|
|
// all other windows in the group - including the actual main client! - to this size and thus
|
|
// breaks the actually required alignment to the main windows geometry (because that now has the
|
|
// restored geometry of the formerly Q'tiled window) - bug #303937
|
|
updateStates(m_current, All, c);
|
|
|
|
int index = other ? m_clients.indexOf(other) : m_clients.size();
|
|
index += after;
|
|
if (index > m_clients.size())
|
|
index = m_clients.size();
|
|
|
|
m_clients.insert(index, c);
|
|
|
|
c->setTabGroup(this); // Let the client know which group it belongs to
|
|
|
|
updateMinMaxSize();
|
|
|
|
if (!becomeVisible)
|
|
c->setClientShown(false);
|
|
else {
|
|
c->setClientShown(true);
|
|
if (!effects || c->readyForPainting()) {
|
|
setCurrent(c);
|
|
if (options->focusPolicyIsReasonable())
|
|
workspace()->requestFocus( c );
|
|
}
|
|
else {
|
|
if (options->focusPolicyIsReasonable())
|
|
workspace()->requestFocus( m_current );
|
|
m_current = c; // setCurrent will be called by Toplevel::setReadyForPainting()
|
|
}
|
|
}
|
|
|
|
m_current->triggerDecorationRepaint();
|
|
return true;
|
|
}
|
|
|
|
bool TabGroup::remove(Client* c)
|
|
{
|
|
if (!c)
|
|
return false;
|
|
|
|
int index = m_clients.indexOf(c);
|
|
if (index < 0)
|
|
return false;
|
|
|
|
c->setTabGroup(NULL);
|
|
|
|
m_clients.removeAt(index);
|
|
updateMinMaxSize();
|
|
|
|
if (m_clients.count() == 1) { // split
|
|
remove(m_clients.at(0));
|
|
}
|
|
if (m_clients.isEmpty()) { // remaining singleton "tab"
|
|
c->setClientShown(true);
|
|
return true; // group is gonna be deleted after this anyway
|
|
}
|
|
|
|
if (c == m_current) {
|
|
m_current = index < m_clients.count() ? m_clients.at(index) : m_clients.last();
|
|
m_current->setClientShown(true);
|
|
|
|
if (effects) // "c -> m_current" because c was m_current
|
|
static_cast<EffectsHandlerImpl*>(effects)->slotCurrentTabAboutToChange(c->effectWindow(), m_current->effectWindow());
|
|
}
|
|
|
|
// Notify effects of removal
|
|
if (effects)
|
|
static_cast<EffectsHandlerImpl*>(effects)->slotTabRemoved(c->effectWindow(), m_current->effectWindow());
|
|
|
|
m_current->triggerDecorationRepaint();
|
|
return true;
|
|
}
|
|
|
|
void TabGroup::closeAll()
|
|
{
|
|
// NOTICE - in theory it's OK to use the list because closing sends an event to the client and
|
|
// due to the async X11 nature, the client would be released and thus removed from m_clients
|
|
// after this function exits.
|
|
// However later Wayland support or similar might not share this bahaviour - and we really had
|
|
// enough trouble with a polluted client list around the tabbing code ....
|
|
ClientList list(m_clients);
|
|
for (ClientList::const_iterator i = list.constBegin(), end = list.constEnd(); i != end; ++i)
|
|
if (*i != m_current)
|
|
(*i)->closeWindow();
|
|
|
|
m_current->closeWindow();
|
|
}
|
|
|
|
void TabGroup::move(Client *c, Client *other, bool after)
|
|
{
|
|
if (c == other)
|
|
return;
|
|
|
|
int from = m_clients.indexOf(c);
|
|
if (from < 0)
|
|
return;
|
|
|
|
int to = other ? m_clients.indexOf(other) : m_clients.size() - 1;
|
|
if (to < 0)
|
|
return;
|
|
to += after;
|
|
if (to >= m_clients.size())
|
|
to = m_clients.size() - 1;
|
|
|
|
if (from == to)
|
|
return;
|
|
|
|
m_clients.move(from, to);
|
|
m_current->triggerDecorationRepaint();
|
|
}
|
|
|
|
bool TabGroup::isActive() const
|
|
{
|
|
return contains(Workspace::self()->activeClient());
|
|
}
|
|
|
|
void TabGroup::setCurrent(Client* c, bool force)
|
|
{
|
|
if ((c == m_current && !force) || !contains(c))
|
|
return;
|
|
|
|
// Notify effects of switch
|
|
if (effects)
|
|
static_cast<EffectsHandlerImpl*>(effects)->slotCurrentTabAboutToChange(m_current->effectWindow(), c->effectWindow());
|
|
|
|
m_current = c;
|
|
c->setClientShown(true); // reduce flicker?
|
|
for (ClientList::const_iterator i = m_clients.constBegin(), end = m_clients.constEnd(); i != end; ++i)
|
|
(*i)->setClientShown((*i) == m_current);
|
|
}
|
|
|
|
void TabGroup::sync(const char *property, Client *c)
|
|
{
|
|
if (c->metaObject()->indexOfProperty(property) > -1) {
|
|
qWarning("caught attempt to sync non dynamic property: %s", property);
|
|
return;
|
|
}
|
|
QVariant v = c->property(property);
|
|
for (ClientList::iterator i = m_clients.begin(), end = m_clients.end(); i != end; ++i) {
|
|
if (*i != m_current)
|
|
(*i)->setProperty(property, v);
|
|
}
|
|
}
|
|
|
|
void TabGroup::updateMinMaxSize()
|
|
{
|
|
// Determine entire group's minimum and maximum sizes
|
|
// TODO this used to be signalled out but i didn't find a receiver - or got an idea what this would be good for
|
|
// find purpose & solution or kick it
|
|
// QSize oldMin = m_minSize;
|
|
// QSize oldMax = m_maxSize;
|
|
m_minSize = QSize(0, 0);
|
|
m_maxSize = QSize(INT_MAX, INT_MAX);
|
|
|
|
for (ClientList::const_iterator i = m_clients.constBegin(); i != m_clients.constEnd(); ++i) {
|
|
m_minSize = m_minSize.expandedTo((*i)->minSize());
|
|
m_maxSize = m_maxSize.boundedTo((*i)->maxSize());
|
|
}
|
|
|
|
// TODO: this actually resolves a conflict that should be caught when adding?
|
|
m_maxSize = m_maxSize.expandedTo(m_minSize);
|
|
|
|
// calculate this _once_ to get a common size.
|
|
// TODO this leaves another unresolved conflict about the base increment (luckily not used too often)
|
|
const QSize size = m_current->clientSize().expandedTo(m_minSize).boundedTo(m_maxSize);
|
|
if (size != m_current->clientSize()) {
|
|
const QRect r(m_current->pos(), m_current->sizeForClientSize(size));
|
|
for (ClientList::const_iterator i = m_clients.constBegin(), end = m_clients.constEnd(); i != end; ++i)
|
|
(*i)->setGeometry(r);
|
|
}
|
|
}
|
|
|
|
|
|
void TabGroup::blockStateUpdates(bool more) {
|
|
more ? ++m_stateUpdatesBlocked : --m_stateUpdatesBlocked;
|
|
if (m_stateUpdatesBlocked < 0) {
|
|
m_stateUpdatesBlocked = 0;
|
|
qWarning("TabGroup: Something is messed up with TabGroup::blockStateUpdates() invocation\nReleased more than blocked!");
|
|
}
|
|
}
|
|
|
|
void TabGroup::updateStates(Client* main, States states, Client* only)
|
|
{
|
|
if (main == only)
|
|
return; // there's no need to only align "us" to "us"
|
|
if (m_stateUpdatesBlocked > 0) {
|
|
m_pendingUpdates |= states;
|
|
return;
|
|
}
|
|
|
|
states |= m_pendingUpdates;
|
|
m_pendingUpdates = TabGroup::None;
|
|
|
|
ClientList toBeRemoved, onlyDummy;
|
|
ClientList *list = &m_clients;
|
|
if (only) {
|
|
onlyDummy << only;
|
|
list = &onlyDummy;
|
|
}
|
|
|
|
for (ClientList::const_iterator i = list->constBegin(), end = list->constEnd(); i != end; ++i) {
|
|
Client *c = (*i);
|
|
if (c != main) {
|
|
if ((states & Minimized) && c->isMinimized() != main->isMinimized()) {
|
|
if (main->isMinimized())
|
|
c->minimize(true);
|
|
else
|
|
c->unminimize(true);
|
|
}
|
|
|
|
// the order QuickTile -> Maximized -> Geometry is somewhat important because one will change the other
|
|
// don't change w/o good reason and care
|
|
if ((states & QuickTile) && c->quickTileMode() != main->quickTileMode())
|
|
c->setQuickTileMode(main->quickTileMode());
|
|
if ((states & Maximized) && c->maximizeMode() != main->maximizeMode())
|
|
c->maximize(main->maximizeMode());
|
|
// the order Shaded -> Geometry is somewhat important because one will change the other
|
|
if ((states & Shaded))
|
|
c->setShade(main->shadeMode());
|
|
if ((states & Geometry) && c->geometry() != main->geometry())
|
|
c->setGeometry(main->geometry());
|
|
if (states & Desktop) {
|
|
if (c->isOnAllDesktops() != main->isOnAllDesktops())
|
|
c->setOnAllDesktops(main->isOnAllDesktops());
|
|
if (c->desktop() != main->desktop())
|
|
c->setDesktop(main->desktop());
|
|
}
|
|
if ((states & Activity) && c->activities() != main->activities()) {
|
|
c->setOnActivities(main->activities());
|
|
}
|
|
if (states & Layer) {
|
|
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 (((states & Geometry) && c->geometry() != main->geometry()) ||
|
|
((states & Desktop) && c->desktop() != main->desktop()))
|
|
toBeRemoved << c;
|
|
}
|
|
}
|
|
|
|
for (ClientList::const_iterator i = toBeRemoved.constBegin(), end = toBeRemoved.constEnd(); i != end; ++i)
|
|
remove(*i);
|
|
}
|
|
|
|
}
|