Commit d245035a authored by Thomas Lübking's avatar Thomas Lübking

fix tabbing

BUG: 290959
BUG: 265160
BUG: 229292
BUG: 238279
BUG: 290758
BUG: 222831
BUG: 278275
BUG: 245747
BUG: 230000

BUG: 253697
BUG: 230570
BUG: 265977
BUG: 225337
BUG: 225339

REVIEW: 103855
parent 7d8ab4c5
......@@ -78,7 +78,7 @@ add_subdirectory( effects )
set(kwin_KDEINIT_SRCS
workspace.cpp
client.cpp
clientgroup.cpp
tabgroup.cpp
placement.cpp
atoms.cpp
utils.cpp
......
......@@ -361,8 +361,8 @@ void Workspace::takeActivity(Client* c, int flags, bool handled)
flags &= ~ActivityFocus;
handled = false; // no point, can't get clicks
}
if (c->clientGroup() && c->clientGroup()->visible() != c)
c->clientGroup()->setVisible(c);
if (c->tabGroup() && c->tabGroup()->current() != c)
c->tabGroup()->setCurrent(c);
if (!c->isShown(true)) { // shouldn't happen, call activateClient() if needed
kWarning(1212) << "takeActivity: not shown" ;
return;
......
......@@ -39,7 +39,6 @@ Bridge::Bridge(Client* cl)
return c->prototype( args2 ); \
}
BRIDGE_HELPER(bool, isActive, , , const)
BRIDGE_HELPER(bool, isCloseable, , , const)
BRIDGE_HELPER(bool, isMaximizable, , , const)
BRIDGE_HELPER(Bridge::MaximizeMode, maximizeMode, , , const)
......@@ -62,6 +61,11 @@ BRIDGE_HELPER(void, minimize, , ,)
BRIDGE_HELPER(void, showContextHelp, , ,)
BRIDGE_HELPER(void, setDesktop, int desktop, desktop,)
bool Bridge::isActive() const
{
return c->isActive() || (c->tabGroup() && c->tabGroup()->isActive());
}
void Bridge::setKeepAbove(bool set)
{
if (c->keepAbove() != set)
......@@ -93,7 +97,15 @@ bool Bridge::isSetShade() const
void Bridge::showWindowMenu(const QPoint &p)
{
c->workspace()->showWindowMenu(p, c);
c->workspace()->showWindowMenu(QRect(p,p), c);
}
void Bridge::showWindowMenu(const QPoint &p, long id)
{
Client *cc = clientForId(id);
if (!cc)
cc = c;
cc->workspace()->showWindowMenu(QRect(p,p), cc);
}
void Bridge::showWindowMenu(const QRect &p)
......@@ -207,90 +219,119 @@ QRect Bridge::transparentRect() const
return c->transparentRect().translated(-c->decorationRect().topLeft());
}
bool Bridge::isClientGroupActive()
//BEGIN TABBING
Client *Bridge::clientForId(long id) const
{
if (c->clientGroup())
return c->clientGroup()->containsActiveClient();
return isActive();
Client* client = reinterpret_cast<Client*>(id);
if (!c->workspace()->hasClient(client)) {
kWarning(1212) << "****** ARBITRARY CODE EXECUTION ATTEMPT DETECTED ******" << id;
return 0;
}
return client;
}
QList< ClientGroupItem > Bridge::clientGroupItems() const
int Bridge::tabCount() const
{
if (c->clientGroup())
return c->clientGroup()->items();
QList< ClientGroupItem > items;
QIcon icon(c->icon());
icon.addPixmap(c->miniIcon());
items.append(ClientGroupItem(c->caption(), icon));
return items;
if (c->tabGroup())
return c->tabGroup()->count();
return 1;
}
long Bridge::itemId(int index)
long Bridge::tabId(int idx) const
{
if (!c->clientGroup())
return 0;
const ClientList list = c->clientGroup()->clients();
return reinterpret_cast<long>(list.at(index));
if (c->tabGroup())
return tabIdOf(c->tabGroup()->clients().at(idx));
return tabIdOf(c);
}
int Bridge::visibleClientGroupItem()
QIcon Bridge::icon(int idx) const
{
if (c->clientGroup())
return c->clientGroup()->indexOfVisibleClient();
return 0;
if (c->tabGroup()) {
Client *tabC = c->tabGroup()->clients().at(idx);
QIcon icon(tabC->icon());
icon.addPixmap(tabC->miniIcon());
return icon;
}
return icon();
}
void Bridge::setVisibleClientGroupItem(int index)
QString Bridge::caption(int idx) const
{
if (c->clientGroup())
c->clientGroup()->setVisible(index);
if (c->tabGroup())
return c->tabGroup()->clients().at(idx)->caption();
return c->caption();
}
void Bridge::moveItemInClientGroup(int index, int before)
long Bridge::currentTabId() const
{
if (c->clientGroup())
c->clientGroup()->move(index, before);
if (c->tabGroup())
return tabIdOf(c->tabGroup()->current());
return 0;
}
void Bridge::moveItemToClientGroup(long itemId, int before)
void Bridge::setCurrentTab(long id)
{
Client* item = reinterpret_cast<Client*>(itemId);
if (!c->workspace()->hasClient(item)) {
kWarning(1212) << "****** ARBITRARY CODE EXECUTION ATTEMPT DETECTED ******";
if (c->tabGroup())
c->tabGroup()->setCurrent(clientForId(id));
}
void Bridge::tab_A_before_B(long A, long B)
{
if (!B) {
if (c->tabGroup()) {
if (Client *a = clientForId(A))
a->untab();
}
return;
}
if (item->clientGroup())
c->workspace()->moveItemToClientGroup(item->clientGroup(), item->clientGroup()->indexOfClient(item),
c->clientGroup(), before);
if (Client *a = clientForId(A))
if (Client *b = clientForId(B))
a->tabBefore(b, true);
}
void Bridge::removeFromClientGroup(int index, const QRect& newGeom)
void Bridge::tab_A_behind_B(long A, long B)
{
if (c->clientGroup())
c->clientGroup()->remove(index, newGeom);
if (!B) {
if (c->tabGroup()) {
if (Client *a = clientForId(A))
a->untab();
}
return;
}
if (Client *a = clientForId(A))
if (Client *b = clientForId(B))
a->tabBehind(b, true);
}
void Bridge::closeClientGroupItem(int index)
void Bridge::untab(long id, const QRect& newGeom)
{
if (!c->clientGroup())
return;
const ClientList list = c->clientGroup()->clients();
if (index >= 0 || index <= list.count())
list.at(index)->closeWindow();
if (c->tabGroup())
if (Client* client = clientForId(id))
if (client->untab(newGeom)) {
if (options->focusPolicyIsReasonable())
c->workspace()->takeActivity(client, ActivityFocus | ActivityRaise, true);
c->workspace()->raiseClient(client);
}
}
void Bridge::closeAllInClientGroup()
void Bridge::closeTab(long id)
{
if (c->clientGroup())
c->clientGroup()->closeAll();
if (Client* client = clientForId(id))
client->closeWindow();
}
void Bridge::displayClientMenu(int index, const QPoint& pos)
void Bridge::closeTabGroup()
{
if (c->clientGroup())
c->clientGroup()->displayClientMenu(index, pos);
if (c->tabGroup())
c->tabGroup()->closeAll();
}
//END TABBING
KDecoration::WindowOperation Bridge::buttonToWindowOperation(Qt::MouseButtons button)
{
return c->mouseButtonToWindowOperation(button);
......
......@@ -79,20 +79,26 @@ public:
virtual QRect transparentRect() const;
// Window tabbing
virtual bool isClientGroupActive();
virtual QList< ClientGroupItem > clientGroupItems() const;
virtual long itemId(int index);
virtual int visibleClientGroupItem();
virtual void setVisibleClientGroupItem(int index);
virtual void moveItemInClientGroup(int index, int before);
virtual void moveItemToClientGroup(long itemId, int before);
virtual void removeFromClientGroup(int index, const QRect& newGeom);
virtual void closeClientGroupItem(int index);
virtual void closeAllInClientGroup();
virtual void displayClientMenu(int index, const QPoint& pos);
virtual QString caption(int idx) const;
virtual void closeTab(long id);
virtual void closeTabGroup();
virtual long currentTabId() const;
virtual QIcon icon(int idx) const;
virtual void setCurrentTab(long id);
virtual void showWindowMenu(const QPoint &, long id);
virtual void tab_A_before_B(long A, long B);
virtual void tab_A_behind_B(long A, long B);
virtual int tabCount() const;
virtual long tabId(int idx) const;
virtual void untab(long id, const QRect& newGeom);
virtual WindowOperation buttonToWindowOperation(Qt::MouseButtons button);
private:
Client *clientForId(long id) const;
static inline long tabIdOf(Client *c) {
return reinterpret_cast<long>(c);
}
Client* c;
};
......
......@@ -102,7 +102,7 @@ Client::Client(Workspace* ws)
, delayedMoveResizeTimer(NULL)
, in_group(NULL)
, window_group(None)
, client_group(NULL)
, tab_group(NULL)
, in_layer(UnknownLayer)
, ping_timer(NULL)
, process_killer(NULL)
......@@ -265,15 +265,14 @@ void Client::releaseWindow(bool on_shutdown)
XUnmapWindow(display(), frameId()); // Destroying decoration would cause ugly visual effect
destroyDecoration();
cleanGrouping();
if (clientGroup())
clientGroup()->remove(this, QRect(), true);
if (!on_shutdown) {
workspace()->removeClient(this, Allowed);
// Only when the window is being unmapped, not when closing down KWin (NETWM sections 5.5,5.7)
info->setDesktop(0);
desk = 0;
info->setState(0, info->state()); // Reset all state flags
}
} else
untab();
XDeleteProperty(display(), client, atoms->kde_net_wm_user_creation_time);
XDeleteProperty(display(), client, atoms->net_frame_extents);
XDeleteProperty(display(), client, atoms->kde_net_wm_frame_strut);
......@@ -326,8 +325,6 @@ void Client::destroyClient()
workspace()->clientHidden(this);
destroyDecoration();
cleanGrouping();
if (clientGroup())
clientGroup()->remove(this, QRect(), true);
workspace()->removeClient(this, Allowed);
client = None; // invalidate
XDestroyWindow(display(), wrapper);
......@@ -791,7 +788,7 @@ bool Client::noBorder() const
bool Client::userCanSetNoBorder() const
{
return !isFullScreen() && !isShade() && (clientGroup() == NULL || !(clientGroup()->items().count() > 1));
return !isFullScreen() && !isShade() && !tabGroup();
}
void Client::setNoBorder(bool set)
......@@ -993,8 +990,8 @@ void Client::minimize(bool avoid_animation)
emit clientMinimized(this, !avoid_animation);
// Update states of all other windows in this group
if (clientGroup())
clientGroup()->updateStates(this);
if (tabGroup())
tabGroup()->updateStates(this);
emit minimizedChanged();
}
......@@ -1019,8 +1016,8 @@ void Client::unminimize(bool avoid_animation)
emit clientUnminimized(this, !avoid_animation);
// Update states of all other windows in this group
if (clientGroup())
clientGroup()->updateStates(this);
if (tabGroup())
tabGroup()->updateStates(this);
emit minimizedChanged();
}
......@@ -1123,8 +1120,8 @@ void Client::setShade(ShadeMode mode)
updateWindowRules(Rules::Shade);
// Update states of all other windows in this group
if (clientGroup())
clientGroup()->updateStates(this);
if (tabGroup())
tabGroup()->updateStates(this);
emit shadeChanged();
}
......@@ -1156,7 +1153,7 @@ void Client::updateVisibility()
{
if (deleting)
return;
if (hidden && (clientGroup() == NULL || clientGroup()->visible() == this)) {
if (hidden && isCurrentTab()) {
info->setState(NET::Hidden, NET::Hidden);
setSkipTaskbar(true, false); // Also hide from taskbar
if (compositing() && options->hiddenPreviews == HiddenPreviewsAlways)
......@@ -1165,7 +1162,7 @@ void Client::updateVisibility()
internalHide(Allowed);
return;
}
if (clientGroup() == NULL || clientGroup()->visible() == this)
if (isCurrentTab())
setSkipTaskbar(original_skip_taskbar, false); // Reset from 'hidden'
if (minimized) {
info->setState(NET::Hidden, NET::Hidden);
......@@ -1584,8 +1581,8 @@ void Client::setDesktop(int desktop)
updateWindowRules(Rules::Desktop);
// Update states of all other windows in this group
if (clientGroup())
clientGroup()->updateStates(this);
if (tabGroup())
tabGroup()->updateStates(this);
emit desktopChanged();
}
......@@ -1647,8 +1644,8 @@ void Client::updateActivities(bool includeTransients)
// TODO: add activity rule
// Update states of all other windows in this group
if (clientGroup())
clientGroup()->updateStates(this);
if (tabGroup())
tabGroup()->updateStates(this);
}
/**
......@@ -1689,8 +1686,8 @@ void Client::setOnAllDesktops(bool b)
setDesktop(workspace()->currentDesktop());
// Update states of all other windows in this group
if (clientGroup())
clientGroup()->updateStates(this);
if (tabGroup())
tabGroup()->updateStates(this);
}
/**
......@@ -1853,9 +1850,7 @@ void Client::setCaption(const QString& _s, bool force)
// Keep the same suffix in iconic name if it's set
info->setVisibleIconName(QString(cap_iconic + cap_suffix).toUtf8());
if (isManaged() && decoration != NULL) {
if (client_group)
client_group->updateItems();
if (isManaged() && decoration) {
decoration->captionChange();
}
emit captionChanged();
......@@ -1894,21 +1889,62 @@ QString Client::caption(bool full) const
return full ? cap_normal + cap_suffix : cap_normal;
}
void Client::setClientGroup(ClientGroup* group)
bool Client::tabTo(Client *other, bool behind, bool activate)
{
client_group = group;
unsigned long data[1] = {(unsigned long)workspace()->indexOfClientGroup(group)};
XChangeProperty(display(), window(), atoms->kde_net_wm_tab_group, XA_CARDINAL, 32,
PropModeReplace, (unsigned char*)(data), 1);
emit clientGroupChanged();
Q_ASSERT(other && other != this);
if (tab_group && tab_group == other->tabGroup()) { // special case: move inside group
tab_group->move(this, other, behind);
return true;
}
GeometryUpdatesBlocker blocker(this);
const bool wasBlocking = signalsBlocked();
blockSignals(true); // prevent client emitting "retabbed to nowhere" cause it's about to be entabbed the next moment
untab();
blockSignals(wasBlocking);
TabGroup *newGroup = other->tabGroup() ? other->tabGroup() : new TabGroup(other);
if (!newGroup->add(this, other, behind, activate)) {
if (newGroup->count() < 2) { // adding "c" to "to" failed for whatever reason
newGroup->remove(other);
delete newGroup;
}
return false;
}
return true;
}
bool Client::isVisibleInClientGroup() const
bool Client::untab(const QRect &toGeometry)
{
if (!client_group) {
TabGroup *group = tab_group;
if (group && group->remove(this, toGeometry)) { // remove sets the tabgroup to "0", therefore the pointer is cached
if (group->isEmpty()) {
delete group;
}
setClientShown(!(isMinimized() || isShade()));
return true;
}
return (client_group->visible() == this);
return false;
}
void Client::setTabGroup(TabGroup *group)
{
tab_group = group;
if (group) {
unsigned long data = qHash(group); //->id();
XChangeProperty(display(), window(), atoms->kde_net_wm_tab_group, XA_CARDINAL, 32,
PropModeReplace, (unsigned char*)(&data), 1);
}
else
XDeleteProperty(display(), window(), atoms->kde_net_wm_tab_group);
emit tabGroupChanged();
}
bool Client::isCurrentTab() const
{
return !tab_group || tab_group->current() == this;
}
void Client::dontMoveResize()
......@@ -1923,26 +1959,20 @@ void Client::setClientShown(bool shown)
{
if (deleting)
return; // Don't change shown status if this client is being deleted
if (shown && hidden) {
if (shown != hidden)
return; // nothing to change
hidden = !shown;
if (options->inactiveTabsSkipTaskbar)
setSkipTaskbar(hidden, false); // TODO: Causes reshuffle of the taskbar
if (shown) {
map(Allowed);
hidden = false;
//updateVisibility();
//updateAllowedActions();
if (options->inactiveTabsSkipTaskbar)
setSkipTaskbar(false, false);
takeFocus(Allowed);
autoRaise();
workspace()->updateFocusChains(this, Workspace::FocusChainMakeFirst);
}
if (!shown && !hidden) {
} else {
unmap(Allowed);
hidden = true;
//updateVisibility();
//updateAllowedActions();
if (options->inactiveTabsSkipTaskbar)
setSkipTaskbar(true, false); // TODO: Causes reshuffle of the taskbar
// Don't move tabs to the end of the list when another tab get's activated
if (!clientGroup() || clientGroup()->visible() == this)
if (isCurrentTab())
workspace()->updateFocusChains(this, Workspace::FocusChainMakeLast);
addWorkspaceRepaint(visibleRect());
}
......
......@@ -41,7 +41,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "kdecoration.h"
#include "rules.h"
#include "toplevel.h"
#include "clientgroup.h"
#include "tabgroup.h"
#ifdef HAVE_XSYNC
#include <X11/extensions/sync.h>
......@@ -221,12 +221,12 @@ class Client
/**
* The "Window Tabs" Group this Client belongs to.
**/
Q_PROPERTY(KWin::ClientGroup* clientGroup READ clientGroup NOTIFY clientGroupChanged)
Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged)
/**
* Whether this Client is the currently visible Client in its Client Group (Window Tabs).
* For change connect to the visibleChanged signal on the Client's Group.
**/
Q_PROPERTY(bool visibleInClientGroup READ isVisibleInClientGroup)
Q_PROPERTY(bool isCurrentTab READ isCurrentTab)
/**
* Minimum size as specified in WM_NORMAL_HINTS
**/
......@@ -500,8 +500,14 @@ public:
bool hasStrut() const;
// Tabbing functions
ClientGroup* clientGroup() const; // Returns a pointer to client_group
void setClientGroup(ClientGroup* group);
TabGroup* tabGroup() const; // Returns a pointer to client_group
Q_INVOKABLE inline bool tabBefore(Client *other, bool activate) { return tabTo(other, false, activate); }
Q_INVOKABLE inline bool tabBehind(Client *other, bool activate) { return tabTo(other, true, activate); }
Q_INVOKABLE bool untab(const QRect &toGeometry = QRect());
/**
* Set tab group - this is to be invoked by TabGroup::add/remove(client) and NO ONE ELSE
*/
void setTabGroup(TabGroup* group);
/*
* If shown is true the client is mapped and raised, if false
* the client is unmapped and hidden, this function is called
......@@ -515,7 +521,7 @@ public:
* client, this function stops it.
*/
void dontMoveResize();
bool isVisibleInClientGroup() const;
bool isCurrentTab() const;
/**
* Whether or not the window has a strut that expands through the invisible area of
......@@ -628,6 +634,7 @@ private:
bool processDecorationButtonPress(int button, int state, int x, int y, int x_root, int y_root,
bool ignoreMenu = false);
Client* findAutogroupCandidate() const;
protected:
virtual void debug(QDebug& stream) const;
......@@ -670,10 +677,10 @@ signals:
void iconChanged();
void skipSwitcherChanged();
/**
* Emitted whenever the Client's ClientGroup changed. That is whenever the Client is moved to
* Emitted whenever the Client's TabGroup changed. That is whenever the Client is moved to
* another group, but not when a Client gets added or removed to the Client's ClientGroup.
**/
void clientGroupChanged();
void tabGroupChanged();
private:
void exportMappingState(int s); // ICCCM 4.1.3.1, 4.1.4, NETWM 2.5.1
......@@ -744,6 +751,8 @@ private:
void updateInputWindow();
bool tabTo(Client *other, bool behind, bool activate);
Window client;
Window wrapper;
KDecoration* decoration;
......@@ -845,7 +854,7 @@ private:
QString cap_normal, cap_iconic, cap_suffix;
Group* in_group;
Window window_group;
ClientGroup* client_group;
TabGroup* tab_group;
Layer in_layer;
QTimer* ping_timer;
QProcess* process_killer;
......@@ -994,9 +1003,9 @@ inline Group* Client::group()
return in_group;
}
inline ClientGroup* Client::clientGroup() const
inline TabGroup* Client::tabGroup() const
{