diff --git a/abstract_client.h b/abstract_client.h --- a/abstract_client.h +++ b/abstract_client.h @@ -272,6 +272,10 @@ * considered unresponsive. This usually indicates that the application froze or crashed. */ Q_PROPERTY(bool unresponsive READ unresponsive NOTIFY unresponsiveChanged) + /** + * The "Window Tabs" Group this Client belongs to. + **/ + Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged SCRIPTABLE false) public: virtual ~AbstractClient(); @@ -422,9 +426,34 @@ return m_minimized; } virtual void setFullScreen(bool set, bool user = true) = 0; - virtual TabGroup *tabGroup() const; - Q_INVOKABLE virtual bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false); - virtual bool isCurrentTab() const; + // Tabbing functions + Q_INVOKABLE inline bool tabBefore(AbstractClient *other, bool activate) { return tabTo(other, false, activate); } + Q_INVOKABLE inline bool tabBehind(AbstractClient *other, bool activate) { return tabTo(other, true, activate); } + /** + * Syncs the *dynamic* @param property @param fromThisClient or the @link currentTab() to + * all members of the @link tabGroup() (if there is one) + * + * eg. if you call: + * client->setProperty("kwin_tiling_floats", true); + * client->syncTabGroupFor("kwin_tiling_floats", true) + * all clients in this tabGroup will have ::property("kwin_tiling_floats").toBool() == true + * + * WARNING: non dynamic properties are ignored - you're not supposed to alter/update such explicitly + */ + Q_INVOKABLE void syncTabGroupFor(QString property, bool fromThisClient = false); + TabGroup *tabGroup() const; + /** + * Set tab group - this is to be invoked by TabGroup::add/remove(client) and NO ONE ELSE + */ + void setTabGroup(TabGroup* group); + virtual void setClientShown(bool shown); + Q_INVOKABLE bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false); + /* + * When a click is done in the decoration and it calls the group + * to change the visible client it starts to move-resize the new + * client, this function stops it. + */ + bool isCurrentTab() const; virtual QRect geometryRestore() const = 0; virtual MaximizeMode maximizeMode() const = 0; void maximize(MaximizeMode); @@ -730,6 +759,11 @@ void hasApplicationMenuChanged(bool); void applicationMenuActiveChanged(bool); void unresponsiveChanged(bool); + /** + * 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 tabGroupChanged(); protected: AbstractClient(); @@ -1029,6 +1063,8 @@ void finishWindowRules(); void discardTemporaryRules(); + bool tabTo(AbstractClient *other, bool behind, bool activate); + private: void handlePaletteChange(); QSharedPointer m_tabBoxClient; @@ -1106,6 +1142,7 @@ QKeySequence _shortcut; WindowRules m_rules; + TabGroup* tab_group = nullptr; static bool s_haveResizeEffect; }; @@ -1173,6 +1210,11 @@ m_pendingGeometryUpdate = update; } +inline TabGroup* AbstractClient::tabGroup() const +{ + return tab_group; +} + } Q_DECLARE_METATYPE(KWin::AbstractClient*) diff --git a/abstract_client.cpp b/abstract_client.cpp --- a/abstract_client.cpp +++ b/abstract_client.cpp @@ -114,23 +114,96 @@ return false; } -TabGroup *AbstractClient::tabGroup() const +void AbstractClient::setTabGroup(TabGroup* group) { - return nullptr; + tab_group = group; + emit tabGroupChanged(); +} + +void AbstractClient::setClientShown(bool shown) +{ + Q_UNUSED(shown) } bool AbstractClient::untab(const QRect &toGeometry, bool clientRemoved) { - Q_UNUSED(toGeometry) - Q_UNUSED(clientRemoved) + TabGroup *group = tab_group; + if (group && group->remove(this)) { // remove sets the tabgroup to "0", therefore the pointer is cached + if (group->isEmpty()) { + delete group; + } + if (clientRemoved) + return true; // there's been a broadcast signal that this client is now removed - don't touch it + setClientShown(!(isMinimized() || isShade())); + bool keepSize = toGeometry.size() == size(); + bool changedSize = false; + if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { + changedSize = true; + setQuickTileMode(QuickTileFlag::None); // if we leave a quicktiled group, assume that the user wants to untile + } + if (toGeometry.isValid()) { + if (maximizeMode() != MaximizeRestore) { + changedSize = true; + maximize(MaximizeRestore); // explicitly calling for a geometry -> unmaximize + } + if (keepSize && changedSize) { + setGeometryRestore(geometry()); // checkWorkspacePosition() invokes it + QPoint cpoint = Cursor::pos(); + QPoint point = cpoint; + point.setX((point.x() - toGeometry.x()) * geometryRestore().width() / toGeometry.width()); + point.setY((point.y() - toGeometry.y()) * geometryRestore().height() / toGeometry.height()); + auto geometry_restore = geometryRestore(); + geometry_restore.moveTo(cpoint-point); + setGeometryRestore(geometry_restore); + } else { + setGeometryRestore(toGeometry); // checkWorkspacePosition() invokes it + } + setGeometry(geometryRestore()); + checkWorkspacePosition(); + } + return true; + } return false; } -bool AbstractClient::isCurrentTab() const +bool AbstractClient::tabTo(AbstractClient *other, bool behind, bool activate) { + 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; } +void AbstractClient::syncTabGroupFor(QString property, bool fromThisClient) +{ + if (tab_group) + tab_group->sync(property.toAscii().data(), fromThisClient ? this : tab_group->current()); +} + +bool AbstractClient::isCurrentTab() const +{ + return !tab_group || tab_group->current() == this; +} + xcb_timestamp_t AbstractClient::userTime() const { return XCB_TIME_CURRENT_TIME; diff --git a/activation.cpp b/activation.cpp --- a/activation.cpp +++ b/activation.cpp @@ -389,7 +389,7 @@ flags &= ~ActivityFocus; } if (c->tabGroup() && c->tabGroup()->current() != c) - c->tabGroup()->setCurrent(dynamic_cast(c)); + c->tabGroup()->setCurrent(c); if (!c->isShown(true)) { // shouldn't happen, call activateClient() if needed qCWarning(KWIN_CORE) << "takeActivity: not shown" ; return; diff --git a/client.h b/client.h --- a/client.h +++ b/client.h @@ -71,10 +71,6 @@ */ Q_PROPERTY(QSize basicUnit READ basicUnit) /** - * The "Window Tabs" Group this Client belongs to. - **/ - Q_PROPERTY(KWin::TabGroup* tabGroup READ tabGroup NOTIFY tabGroupChanged SCRIPTABLE false) - /** * A client can block compositing. That is while the Client is alive and the state is set, * Compositing is suspended and is resumed when there are no Clients blocking compositing any * more. @@ -250,40 +246,13 @@ StrutRects strutRects() const; bool hasStrut() const override; - // Tabbing functions - TabGroup* tabGroup() const override; // 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); } - /** - * Syncs the *dynamic* @param property @param fromThisClient or the @link currentTab() to - * all members of the @link tabGroup() (if there is one) - * - * eg. if you call: - * client->setProperty("kwin_tiling_floats", true); - * client->syncTabGroupFor("kwin_tiling_floats", true) - * all clients in this tabGroup will have ::property("kwin_tiling_floats").toBool() == true - * - * WARNING: non dynamic properties are ignored - you're not supposed to alter/update such explicitly - */ - Q_INVOKABLE void syncTabGroupFor(QString property, bool fromThisClient = false); - Q_INVOKABLE bool untab(const QRect &toGeometry = QRect(), bool clientRemoved = false) override; - /** - * 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 * when the tabbing group of the client switches its visible * client. */ - void setClientShown(bool shown); - /* - * When a click is done in the decoration and it calls the group - * to change the visible client it starts to move-resize the new - * client, this function stops it. - */ - bool isCurrentTab() const override; + void setClientShown(bool shown) override; /** * Whether or not the window has a strut that expands through the invisible area of @@ -408,12 +377,6 @@ void clientFullScreenSet(KWin::Client*, bool, bool); /** - * 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 tabGroupChanged(); - - /** * Emitted whenever the Client want to show it menu */ void showRequest(); @@ -497,8 +460,6 @@ void updateInputWindow(); - bool tabTo(Client *other, bool behind, bool activate); - Xcb::Property fetchShowOnScreenEdge() const; void readShowOnScreenEdge(Xcb::Property &property); /** @@ -562,7 +523,6 @@ xcb_colormap_t m_colormap; QString cap_normal, cap_iconic, cap_suffix; Group* in_group; - TabGroup* tab_group; QTimer* ping_timer; qint64 m_killHelperPID; xcb_timestamp_t m_pingTimestamp; @@ -634,11 +594,6 @@ return in_group; } -inline TabGroup* Client::tabGroup() const -{ - return tab_group; -} - inline bool Client::isShown(bool shaded_is_shown) const { return !isMinimized() && (!isShade() || shaded_is_shown) && !hidden && diff --git a/client.cpp b/client.cpp --- a/client.cpp +++ b/client.cpp @@ -108,7 +108,6 @@ , shadeHoverTimer(NULL) , m_colormap(XCB_COLORMAP_NONE) , in_group(NULL) - , tab_group(NULL) , ping_timer(NULL) , m_killHelperPID(0) , m_pingTimestamp(XCB_TIME_CURRENT_TIME) @@ -167,6 +166,17 @@ } }); + connect(this, &Client::tabGroupChanged, this, + [this] { + auto group = tabGroup(); + if (group) { + unsigned long data[] = {qHash(group)}; //->id(); + m_client.changeProperty(atoms->kde_net_wm_tab_group, XCB_ATOM_CARDINAL, 32, 1, data); + } + else + m_client.deleteProperty(atoms->kde_net_wm_tab_group); + }); + // SELI TODO: Initialize xsizehints?? } @@ -1506,95 +1516,6 @@ } } -bool Client::tabTo(Client *other, bool behind, bool activate) -{ - 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::untab(const QRect &toGeometry, bool clientRemoved) -{ - TabGroup *group = tab_group; - if (group && group->remove(this)) { // remove sets the tabgroup to "0", therefore the pointer is cached - if (group->isEmpty()) { - delete group; - } - if (clientRemoved) - return true; // there's been a broadcast signal that this client is now removed - don't touch it - setClientShown(!(isMinimized() || isShade())); - bool keepSize = toGeometry.size() == size(); - bool changedSize = false; - if (quickTileMode() != QuickTileMode(QuickTileFlag::None)) { - changedSize = true; - setQuickTileMode(QuickTileFlag::None); // if we leave a quicktiled group, assume that the user wants to untile - } - if (toGeometry.isValid()) { - if (maximizeMode() != MaximizeRestore) { - changedSize = true; - maximize(MaximizeRestore); // explicitly calling for a geometry -> unmaximize - } - if (keepSize && changedSize) { - geom_restore = geometry(); // checkWorkspacePosition() invokes it - QPoint cpoint = Cursor::pos(); - QPoint point = cpoint; - point.setX((point.x() - toGeometry.x()) * geom_restore.width() / toGeometry.width()); - point.setY((point.y() - toGeometry.y()) * geom_restore.height() / toGeometry.height()); - geom_restore.moveTo(cpoint-point); - } else { - geom_restore = toGeometry; // checkWorkspacePosition() invokes it - } - setGeometry(geom_restore); - checkWorkspacePosition(); - } - return true; - } - return false; -} - -void Client::setTabGroup(TabGroup *group) -{ - tab_group = group; - if (group) { - unsigned long data[] = {qHash(group)}; //->id(); - m_client.changeProperty(atoms->kde_net_wm_tab_group, XCB_ATOM_CARDINAL, 32, 1, data); - } - else - m_client.deleteProperty(atoms->kde_net_wm_tab_group); - emit tabGroupChanged(); -} - -bool Client::isCurrentTab() const -{ - return !tab_group || tab_group->current() == this; -} - -void Client::syncTabGroupFor(QString property, bool fromThisClient) -{ - if (tab_group) - tab_group->sync(property.toAscii().data(), fromThisClient ? this : tab_group->current()); -} - void Client::setClientShown(bool shown) { if (deleting) diff --git a/manage.cpp b/manage.cpp --- a/manage.cpp +++ b/manage.cpp @@ -370,7 +370,7 @@ } } } - if (!(tab_group || isMapped || session)) { + if (!(tabGroup() || isMapped || session)) { // Attempt to automatically group similar windows Client* similar = findAutogroupCandidate(); if (similar && !similar->noBorder()) { diff --git a/tabgroup.h b/tabgroup.h --- a/tabgroup.h +++ b/tabgroup.h @@ -28,7 +28,7 @@ namespace KWin { -class Client; +class AbstractClient; /** * This class represents a group of clients for use in window tabbing. All @@ -52,7 +52,7 @@ /** * Creates a new group containing \p c. */ - explicit TabGroup(Client* c); + explicit TabGroup(AbstractClient* c); ~TabGroup(); enum State { @@ -87,7 +87,7 @@ /** * Whether client \p c is member of this group */ - bool contains(Client* c) const; + bool contains(AbstractClient* c) const; /** * The amount of clients in this group @@ -107,22 +107,22 @@ /** * Returns the list of all the clients contained in this group in their current order. */ - const ClientList &clients() const; + const QVector &clients() const; /** * Returns the currently visible client. */ - Client* current() const; + AbstractClient* current() const; /** * Makes \p c the visible client in the group - force is only used when the window becomes ready for painting. * Any other usage just causes pointless action */ - void setCurrent(Client* c, bool force = false); + void setCurrent(AbstractClient* c, bool force = false); /** * Alignes the dynamic Qt @param property of all clients to the one of @param c */ - void sync(const char *property, Client *c); + void sync(const char *property, AbstractClient *c); /** * Returns combined minimum size of all clients in the group. @@ -138,7 +138,7 @@ * \p main as the primary client to copy the settings off. If \p only is set then only * that client is updated to match \p main. */ - void updateStates(Client* main, States states, Client* only = NULL); + void updateStates(AbstractClient* main, States states, AbstractClient* only = NULL); /** * updates geometry restrictions of this group, basically called from Client::getWmNormalHints(), otherwise rather private @@ -150,23 +150,24 @@ void maxSizeChanged(); private: + friend class AbstractClient; friend class Client; // friend bool Client::tabTo(Client*, bool, bool); - bool add(KWin::Client *c, Client *other, bool behind, bool activateC); - void move(KWin::Client* c, KWin::Client* before, bool behind); + bool add(KWin::AbstractClient *c, AbstractClient *other, bool behind, bool activateC); + void move(KWin::AbstractClient* c, KWin::AbstractClient* before, bool behind); // friend bool Client::untab(const QRect&); - bool remove(KWin::Client *c); + bool remove(KWin::AbstractClient *c); - ClientList m_clients; - Client *m_current; + QVector m_clients; + AbstractClient *m_current; QSize m_minSize; QSize m_maxSize; int m_stateUpdatesBlocked; States m_pendingUpdates; }; -inline bool TabGroup::contains(Client* c) const +inline bool TabGroup::contains(AbstractClient* c) const { return c && m_clients.contains(c); } @@ -176,7 +177,7 @@ return m_clients.count(); } -inline const ClientList &TabGroup::clients() const +inline const QVector &TabGroup::clients() const { return m_clients; } @@ -186,7 +187,7 @@ return m_clients.isEmpty(); } -inline Client* TabGroup::current() const +inline AbstractClient* TabGroup::current() const { return m_current; } diff --git a/tabgroup.cpp b/tabgroup.cpp --- a/tabgroup.cpp +++ b/tabgroup.cpp @@ -27,7 +27,7 @@ namespace KWin { -TabGroup::TabGroup(Client *c) +TabGroup::TabGroup(AbstractClient *c) : m_clients() , m_current(c) , m_minSize(c->minSize()) @@ -58,7 +58,7 @@ setCurrent(m_clients.at((index > 0) ? index - 1 : m_clients.count() - 1)); } -bool TabGroup::add(Client* c, Client *other, bool after, bool becomeVisible) +bool TabGroup::add(AbstractClient* c, AbstractClient *other, bool after, bool becomeVisible) { Q_ASSERT(!c->tabGroup()); @@ -144,7 +144,7 @@ return true; } -bool TabGroup::remove(Client* c) +bool TabGroup::remove(AbstractClient* c) { if (!c) return false; @@ -189,15 +189,15 @@ // 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) + auto list(m_clients); + for (auto 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) +void TabGroup::move(AbstractClient *c, AbstractClient *other, bool after) { if (c == other) return; @@ -222,10 +222,10 @@ bool TabGroup::isActive() const { - return contains(dynamic_cast(Workspace::self()->activeClient())); + return contains(Workspace::self()->activeClient()); } -void TabGroup::setCurrent(Client* c, bool force) +void TabGroup::setCurrent(AbstractClient* c, bool force) { if ((c == m_current && !force) || !contains(c)) return; @@ -236,18 +236,18 @@ m_current = c; c->setClientShown(true); // reduce flicker? - for (ClientList::const_iterator i = m_clients.constBegin(), end = m_clients.constEnd(); i != end; ++i) + for (auto i = m_clients.constBegin(), end = m_clients.constEnd(); i != end; ++i) (*i)->setClientShown((*i) == m_current); } -void TabGroup::sync(const char *property, Client *c) +void TabGroup::sync(const char *property, AbstractClient *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) { + for (auto i = m_clients.begin(), end = m_clients.end(); i != end; ++i) { if (*i != m_current) (*i)->setProperty(property, v); } @@ -263,7 +263,7 @@ 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) { + for (auto i = m_clients.constBegin(); i != m_clients.constEnd(); ++i) { m_minSize = m_minSize.expandedTo((*i)->minSize()); m_maxSize = m_maxSize.boundedTo((*i)->maxSize()); } @@ -276,7 +276,7 @@ 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) + for (auto i = m_clients.constBegin(), end = m_clients.constEnd(); i != end; ++i) (*i)->setGeometry(r); } } @@ -290,7 +290,7 @@ } } -void TabGroup::updateStates(Client* main, States states, Client* only) +void TabGroup::updateStates(AbstractClient* main, States states, AbstractClient* only) { if (main == only) return; // there's no need to only align "us" to "us" @@ -302,15 +302,15 @@ states |= m_pendingUpdates; m_pendingUpdates = TabGroup::None; - ClientList toBeRemoved, onlyDummy; - ClientList *list = &m_clients; + decltype(m_clients) toBeRemoved, onlyDummy; + auto *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); + for (auto i = list->constBegin(), end = list->constEnd(); i != end; ++i) { + auto *c = (*i); if (c != main) { if ((states & Minimized) && c->isMinimized() != main->isMinimized()) { if (main->isMinimized()) @@ -353,7 +353,7 @@ } } - for (ClientList::const_iterator i = toBeRemoved.constBegin(), end = toBeRemoved.constEnd(); i != end; ++i) + for (auto i = toBeRemoved.constBegin(), end = toBeRemoved.constEnd(); i != end; ++i) remove(*i); } diff --git a/useractions.cpp b/useractions.cpp --- a/useractions.cpp +++ b/useractions.cpp @@ -502,7 +502,7 @@ if (!(!m_client.isNull() && m_client.data()->tabGroup()) || !action->data().isValid()) return; - if (Client *other = action->data().value()) { + if (AbstractClient *other = action->data().value()) { m_client.data()->tabGroup()->setCurrent(other); return; } @@ -534,7 +534,7 @@ m_switchToTabMenu->addSeparator(); - for (QList::const_iterator i = m_client.data()->tabGroup()->clients().constBegin(), + for (auto i = m_client.data()->tabGroup()->clients().constBegin(), end = m_client.data()->tabGroup()->clients().constEnd(); i != end; ++i) { if ((*i)->noBorder() || *i == m_client.data()->tabGroup()->current()) continue; // cannot tab there anyway @@ -547,10 +547,10 @@ { if (m_client.isNull() || !action->data().isValid()) return; - Client *other = dynamic_cast(action->data().value()); - if (!Workspace::self()->clientList().contains(other)) // might have been lost betwenn pop-up and selection + AbstractClient *other = action->data().value(); + if (!Workspace::self()->allClientList().contains(other)) // might have been lost betwenn pop-up and selection return; - Client *c = dynamic_cast(m_client.data()); + AbstractClient *c = m_client.data(); if (!c) { return; } @@ -564,9 +564,8 @@ Q_ASSERT(m_addTabsMenu); m_addTabsMenu->clear(); - QList handled; - const ClientList &clientList = Workspace::self()->clientList(); - for (QList::const_iterator i = clientList.constBegin(), end = clientList.constEnd(); i != end; ++i) { + const auto &clientList = Workspace::self()->allClientList(); + for (auto i = clientList.constBegin(), end = clientList.constEnd(); i != end; ++i) { if (*i == m_client.data() || (*i)->noBorder()) continue; m_addTabsMenu->addAction(shortCaption((*i)->caption()))->setData(QVariant::fromValue(*i));