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.
360 lines
12 KiB
360 lines
12 KiB
/******************************************************************************* |
|
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 "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 (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(dynamic_cast<Client*>(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) { |
|
qCWarning(KWIN_CORE, "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; |
|
qCWarning(KWIN_CORE, "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); |
|
} |
|
|
|
}
|
|
|