diff --git a/CMakeLists.txt b/CMakeLists.txt index f0795b4873..3a6f8ce317 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -149,6 +149,7 @@ if(KWIN_BUILD_SCRIPTING) scripting/scriptedeffect.cpp scripting/scriptingutils.cpp scripting/timer.cpp + scripting/scripting_model.cpp ) endif() diff --git a/scripting/scripting.cpp b/scripting/scripting.cpp index e124827174..1d71944f75 100644 --- a/scripting/scripting.cpp +++ b/scripting/scripting.cpp @@ -24,6 +24,7 @@ along with this program. If not, see . #include "meta.h" #include "scriptingutils.h" #include "workspace_wrapper.h" +#include "scripting_model.h" #include "../client.h" #include "../thumbnailitem.h" #include "../options.h" @@ -560,6 +561,11 @@ void KWin::DeclarativeScript::run() kdeclarative.setupBindings(); installScriptFunctions(kdeclarative.scriptEngine()); qmlRegisterType("org.kde.kwin", 0, 1, "ThumbnailItem"); + qmlRegisterType(); + qmlRegisterType("org.kde.kwin", 0, 1, "ClientModel"); + qmlRegisterType("org.kde.kwin", 0, 1, "ClientModelByScreen"); + qmlRegisterType("org.kde.kwin", 0, 1, "ClientModelByScreenAndDesktop"); + qmlRegisterType("org.kde.kwin", 0, 1, "ClientFilterModel"); qmlRegisterType(); m_view->rootContext()->setContextProperty("options", options); diff --git a/scripting/scripting_model.cpp b/scripting/scripting_model.cpp new file mode 100644 index 0000000000..b2d05756dd --- /dev/null +++ b/scripting/scripting_model.cpp @@ -0,0 +1,903 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2013 Martin Gräßlin + +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 "scripting_model.h" +#include "client.h" +// Qt +#include + +#include + +namespace KWin { +namespace ScriptingClientModel { + +static quint32 nextId() { + static quint32 counter = 0; + return ++counter; +} + +ClientLevel::ClientLevel(ClientModel *model, AbstractLevel *parent) + : AbstractLevel(model, parent) +{ + connect(Workspace::self(), SIGNAL(clientAdded(KWin::Client*)), SLOT(clientAdded(KWin::Client*))); + connect(Workspace::self(), SIGNAL(clientRemoved(KWin::Client*)), SLOT(clientRemoved(KWin::Client*))); + connect(model, SIGNAL(exclusionsChanged()), SLOT(reInit())); +} + +ClientLevel::~ClientLevel() +{ +} + +void ClientLevel::clientAdded(Client *client) +{ + setupClientConnections(client); + checkClient(client); +} + +void ClientLevel::clientRemoved(Client *client) +{ + removeClient(client); +} + +void ClientLevel::setupClientConnections(Client *client) +{ + connect(client, SIGNAL(desktopChanged()), SLOT(checkClient())); + connect(client, SIGNAL(screenChanged()), SLOT(checkClient())); + connect(client, SIGNAL(activitiesChanged(KWin::Toplevel*)), SLOT(checkClient())); +} + +void ClientLevel::checkClient() +{ + checkClient(static_cast(sender())); +} + +void ClientLevel::checkClient(Client *client) +{ + const bool shouldInclude = !exclude(client) && shouldAdd(client); + const bool contains = containsClient(client); + + if (shouldInclude && !contains) { + addClient(client); + } else if (!shouldInclude && contains) { + removeClient(client); + } +} + +bool ClientLevel::exclude(Client *client) const +{ + ClientModel::Exclusions exclusions = model()->exclusions(); + if (exclusions == ClientModel::NoExclusion) { + return false; + } + if (exclusions & ClientModel::DesktopWindowsExclusion) { + if (client->isDesktop()) { + return true; + } + } + if (exclusions & ClientModel::DockWindowsExclusion) { + if (client->isDock()) { + return true; + } + } + if (exclusions & ClientModel::UtilityWindowsExclusion) { + if (client->isUtility()) { + return true; + } + } + if (exclusions & ClientModel::SpecialWindowsExclusion) { + if (client->isSpecialWindow()) { + return true; + } + } + if (exclusions & ClientModel::SkipTaskbarExclusion) { + if (client->skipTaskbar()) { + return true; + } + } + if (exclusions & ClientModel::SkipPagerExclusion) { + if (client->skipPager()) { + return true; + } + } + if (exclusions & ClientModel::SwitchSwitcherExclusion) { + if (client->skipSwitcher()) { + return true; + } + } + if (exclusions & ClientModel::OtherDesktopsExclusion) { + if (!client->isOnCurrentDesktop()) { + return true; + } + } + if (exclusions & ClientModel::OtherActivitiesExclusion) { + if (!client->isOnCurrentActivity()) { + return true; + } + } + if (exclusions & ClientModel::MinimizedExclusion) { + if (client->isMinimized()) { + return true; + } + } + if (exclusions & ClientModel::NonSelectedWindowTabExclusion) { + if (!client->isCurrentTab()) { + return true; + } + } + if (exclusions & ClientModel::NotAcceptingFocusExclusion) { + if (!client->wantsInput()) { + return true; + } + } + return false; +} + +bool ClientLevel::shouldAdd(Client *client) const +{ + if (restrictions() == ClientModel::NoRestriction) { + return true; + } + if (restrictions() & ClientModel::ActivityRestriction) { + if (!client->isOnActivity(activity())) { + return false; + } + } + if (restrictions() & ClientModel::VirtualDesktopRestriction) { + if (!client->isOnDesktop(virtualDesktop())) { + return false; + } + } + if (restrictions() & ClientModel::ScreenRestriction) { + if (client->screen() != int(screen())) { + return false; + } + } + return true; +} + +void ClientLevel::addClient(Client *client) +{ + if (containsClient(client)) { + return; + } + emit beginInsert(m_clients.count(), m_clients.count(), id()); + m_clients.insert(nextId(), client); + emit endInsert(); +} + +void ClientLevel::removeClient(Client *client) +{ + int index = 0; + QMap::iterator it = m_clients.begin(); + for (; it != m_clients.end(); ++it, ++index) { + if (it.value() == client) { + break; + } + } + if (it == m_clients.end()) { + return; + } + emit beginRemove(index, index, id()); + m_clients.erase(it); + emit endRemove(); +} + +void ClientLevel::init() +{ + const ClientList &clients = Workspace::self()->clientList(); + for (ClientList::const_iterator it = clients.begin(); it != clients.end(); ++it) { + Client *client = *it; + setupClientConnections(client); + if (!exclude(client) && shouldAdd(client)) { + m_clients.insert(nextId(), client); + } + } +} + +void ClientLevel::reInit() +{ + const ClientList &clients = Workspace::self()->clientList(); + for (ClientList::const_iterator it = clients.begin(); it != clients.end(); ++it) { + checkClient((*it)); + } +} + +quint32 ClientLevel::idForRow(int row) const +{ + if (row >= m_clients.size()) { + return 0; + } + QMap::const_iterator it = m_clients.constBegin(); + for (int i=0; i::const_iterator it = m_clients.constBegin(); + it != m_clients.constEnd(); + ++it, ++row) { + if (it.key() == id) { + return row; + } + } + return -1; +} + +Client *ClientLevel::clientForId(quint32 child) const +{ + QMap::const_iterator it = m_clients.constFind(child); + if (it == m_clients.constEnd()) { + return NULL; + } + return it.value(); +} + +bool ClientLevel::containsClient(Client *client) const +{ + for (QMap::const_iterator it = m_clients.constBegin(); + it != m_clients.constEnd(); + ++it) { + if (it.value() == client) { + return true; + } + } + return false; +} + +const AbstractLevel *ClientLevel::levelForId(quint32 id) const +{ + if (id == AbstractLevel::id()) { + return this; + } + return NULL; +} + +AbstractLevel *ClientLevel::parentForId(quint32 child) const +{ + if (child == id()) { + return parentLevel(); + } + if (m_clients.contains(child)) { + return const_cast(this); + } + return NULL; +} + +AbstractLevel *AbstractLevel::create(const QList< ClientModel::LevelRestriction > &restrictions, ClientModel::LevelRestrictions parentRestrictions, ClientModel *model, AbstractLevel *parent) +{ + if (restrictions.isEmpty() || restrictions.first() == ClientModel::NoRestriction) { + ClientLevel *leaf = new ClientLevel(model, parent); + leaf->setRestrictions(parentRestrictions); + if (!parent) { + leaf->setParent(model); + } + return leaf; + } + // create a level + QList childRestrictions(restrictions); + ClientModel::LevelRestriction restriction = childRestrictions.takeFirst(); + ClientModel::LevelRestrictions childrenRestrictions = restriction | parentRestrictions; + ForkLevel *currentLevel = new ForkLevel(childRestrictions, model, parent); + currentLevel->setRestrictions(childrenRestrictions); + currentLevel->setRestriction(restriction); + if (!parent) { + currentLevel->setParent(model); + } + switch (restriction) { + case ClientModel::ActivityRestriction: { +#ifdef KWIN_BUILD_ACTIVITIES + const QStringList &activities = Workspace::self()->activityList(); + for (QStringList::const_iterator it = activities.begin(); it != activities.end(); ++it) { + AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); + if (!childLevel) { + continue; + } + childLevel->setActivity(*it); + currentLevel->addChild(childLevel); + } + break; +#else + return NULL; +#endif + } + case ClientModel::ScreenRestriction: + for (int i=0; inumScreens(); ++i) { + AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); + if (!childLevel) { + continue; + } + childLevel->setScreen(i); + currentLevel->addChild(childLevel); + } + break; + case ClientModel::VirtualDesktopRestriction: + for (uint i=1; i<=VirtualDesktopManager::self()->count(); ++i) { + AbstractLevel *childLevel = create(childRestrictions, childrenRestrictions, model, currentLevel); + if (!childLevel) { + continue; + } + childLevel->setVirtualDesktop(i); + currentLevel->addChild(childLevel); + } + break; + default: + // invalid + return NULL; + } + + return currentLevel; +} + +AbstractLevel::AbstractLevel(ClientModel *model, AbstractLevel *parent) + : QObject(parent) + , m_model(model) + , m_parent(parent) + , m_screen(0) + , m_virtualDesktop(0) + , m_activity() + , m_restriction(ClientModel::ClientModel::NoRestriction) + , m_restrictions(ClientModel::NoRestriction) + , m_id(nextId()) +{ +} + +AbstractLevel::~AbstractLevel() +{ +} + +void AbstractLevel::setRestriction(ClientModel::LevelRestriction restriction) +{ + m_restriction = restriction; +} + +void AbstractLevel::setActivity(const QString &activity) +{ + m_activity = activity; +} + +void AbstractLevel::setScreen(uint screen) +{ + m_screen = screen; +} + +void AbstractLevel::setVirtualDesktop(uint virtualDesktop) +{ + m_virtualDesktop = virtualDesktop; +} + +void AbstractLevel::setRestrictions(ClientModel::LevelRestrictions restrictions) +{ + m_restrictions = restrictions; +} + +ForkLevel::ForkLevel(const QList &childRestrictions, ClientModel *model, AbstractLevel *parent) + : AbstractLevel(model, parent) + , m_childRestrictions(childRestrictions) +{ + connect(VirtualDesktopManager::self(), SIGNAL(countChanged(uint,uint)), SLOT(desktopCountChanged(uint,uint))); + connect(QApplication::desktop(), SIGNAL(screenCountChanged(int)), SLOT(screenCountChanged(int))); +#ifdef KWIN_BUILD_ACTIVITIES + connect(Workspace::self(), SIGNAL(activityAdded(QString)), SLOT(activityAdded(QString))); + connect(Workspace::self(), SIGNAL(activityRemoved(QString)), SLOT(activityRemoved(QString))); +#endif +} + +ForkLevel::~ForkLevel() +{ +} + +void ForkLevel::desktopCountChanged(uint previousCount, uint newCount) +{ + if (restriction() != ClientModel::ClientModel::VirtualDesktopRestriction) { + return; + } + if (previousCount != uint(count())) { + return; + } + if (previousCount > newCount) { + // desktops got removed + emit beginRemove(newCount, previousCount-1, id()); + while (uint(m_children.count()) > newCount) { + delete m_children.takeLast(); + } + emit endRemove(); + } else { + // desktops got added + emit beginInsert(previousCount, newCount-1, id()); + for (uint i=previousCount+1; i<=newCount; ++i) { + AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this); + if (!childLevel) { + continue; + } + childLevel->setVirtualDesktop(i); + childLevel->init(); + addChild(childLevel); + } + emit endInsert(); + } +} + +void ForkLevel::screenCountChanged(int newCount) +{ + if (restriction() != ClientModel::ClientModel::ClientModel::ScreenRestriction) { + return; + } + const int previousCount = m_children.count(); + if (newCount == previousCount) { + return; + } + + if (previousCount > newCount) { + // screens got removed + emit beginRemove(newCount, previousCount-1, id()); + while (m_children.count() > newCount) { + delete m_children.takeLast(); + } + emit endRemove(); + } else { + // screens got added + emit beginInsert(previousCount, newCount-1, id()); + for (int i=previousCount; isetScreen(i); + childLevel->init(); + addChild(childLevel); + } + emit endInsert(); + } +} + +void ForkLevel::activityAdded(const QString &activityId) +{ +#ifdef KWIN_BUILD_ACTIVITIES + if (restriction() != ClientModel::ClientModel::ActivityRestriction) { + return; + } + // verify that our children do not contain this activity + foreach (AbstractLevel *child, m_children) { + if (child->activity() == activityId) { + return; + } + } + emit beginInsert(m_children.count(), m_children.count(), id()); + AbstractLevel *childLevel = AbstractLevel::create(m_childRestrictions, restrictions(), model(), this); + if (!childLevel) { + emit endInsert(); + return; + } + childLevel->setActivity(activityId); + childLevel->init(); + addChild(childLevel); + emit endInsert(); +#endif +} + +void ForkLevel::activityRemoved(const QString &activityId) +{ +#ifdef KWIN_BUILD_ACTIVITIES + if (restriction() != ClientModel::ClientModel::ActivityRestriction) { + return; + } + for (int i=0; iactivity() == activityId) { + emit beginRemove(i, i, id()); + delete m_children.takeAt(i); + emit endRemove(); + break; + } + } +#endif +} + +int ForkLevel::count() const +{ + return m_children.count(); +} + +void ForkLevel::addChild(AbstractLevel *child) +{ + m_children.append(child); + connect(child, SIGNAL(beginInsert(int,int,quint32)), SIGNAL(beginInsert(int,int,quint32))); + connect(child, SIGNAL(beginRemove(int,int,quint32)), SIGNAL(beginRemove(int,int,quint32))); + connect(child, SIGNAL(endInsert()), SIGNAL(endInsert())); + connect(child, SIGNAL(endRemove()), SIGNAL(endRemove())); +} + +void ForkLevel::setActivity(const QString &activity) +{ + AbstractLevel::setActivity(activity); + for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { + (*it)->setActivity(activity); + } +} + +void ForkLevel::setScreen(uint screen) +{ + AbstractLevel::setScreen(screen); + for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { + (*it)->setScreen(screen); + } +} + +void ForkLevel::setVirtualDesktop(uint virtualDesktop) +{ + AbstractLevel::setVirtualDesktop(virtualDesktop); + for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { + (*it)->setVirtualDesktop(virtualDesktop); + } +} + +void ForkLevel::init() +{ + for (QList::iterator it = m_children.begin(); it != m_children.end(); ++it) { + (*it)->init(); + } +} + +quint32 ForkLevel::idForRow(int row) const +{ + if (row >= m_children.length()) { + return 0; + } + return m_children.at(row)->id(); +} + +const AbstractLevel *ForkLevel::levelForId(quint32 id) const +{ + if (id == AbstractLevel::id()) { + return this; + } + for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { + if (const AbstractLevel *child = (*it)->levelForId(id)) { + return child; + } + } + // not found + return NULL; +} + +AbstractLevel *ForkLevel::parentForId(quint32 child) const +{ + if (child == id()) { + return parentLevel(); + } + for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { + if (AbstractLevel *parent = (*it)->parentForId(child)) { + return parent; + } + } + // not found + return NULL; +} + +int ForkLevel::rowForId(quint32 child) const +{ + if (id() == child) { + return 0; + } + for (int i=0; iid() == child) { + return i; + } + } + // do recursion + for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { + int row = (*it)->rowForId(child); + if (row != -1) { + return row; + } + } + // not found + return -1; +} + +Client *ForkLevel::clientForId(quint32 child) const +{ + for (QList::const_iterator it = m_children.constBegin(); it != m_children.constEnd(); ++it) { + if (Client *client = (*it)->clientForId(child)) { + return client; + } + } + // not found + return NULL; +} + +ClientModel::ClientModel(QObject *parent) + : QAbstractItemModel(parent) + , m_root(NULL) + , m_exclusions(NoExclusion) +{ + QHash roleNames; + roleNames.insert(Qt::DisplayRole, "display"); + roleNames.insert(ClientRole, "client"); + roleNames.insert(ScreenRole, "screen"); + roleNames.insert(DesktopRole, "desktop"); + roleNames.insert(ActivityRole, "activity"); + setRoleNames(roleNames); +} + +ClientModel::~ClientModel() +{ +} + +void ClientModel::setLevels(QList< ClientModel::LevelRestriction > restrictions) +{ + beginResetModel(); + if (m_root) { + delete m_root; + } + m_root = AbstractLevel::create(restrictions, NoRestriction, this); + connect(m_root, SIGNAL(beginInsert(int,int,quint32)), SLOT(levelBeginInsert(int,int,quint32))); + connect(m_root, SIGNAL(beginRemove(int,int,quint32)), SLOT(levelBeginRemove(int,int,quint32))); + connect(m_root, SIGNAL(endInsert()), SLOT(levelEndInsert())); + connect(m_root, SIGNAL(endRemove()), SLOT(levelEndRemove())); + m_root->init(); + endResetModel(); +} + +void ClientModel::setExclusions(ClientModel::Exclusions exclusions) +{ + if (exclusions == m_exclusions) { + return; + } + m_exclusions = exclusions; + emit exclusionsChanged(); +} + +QVariant ClientModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.column() != 0) { + return QVariant(); + } + if (const AbstractLevel *level = getLevel(index)) { + LevelRestriction restriction = level->restriction(); + if (restriction == ActivityRestriction && (role == Qt::DisplayRole || role == ActivityRole)) { + return level->activity(); + } else if (restriction == VirtualDesktopRestriction && (role == Qt::DisplayRole || role == DesktopRole)) { + return level->virtualDesktop(); + } else if (restriction ==ScreenRestriction && (role == Qt::DisplayRole || role == ScreenRole)) { + return level->screen(); + } else { + return QVariant(); + } + } + if (role == Qt::DisplayRole || role == ClientRole) { + if (Client *client = m_root->clientForId(index.internalId())) { + return qVariantFromValue(client); + } + } + return QVariant(); +} + +int ClientModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 1; +} + +int ClientModel::rowCount(const QModelIndex &parent) const +{ + if (!m_root) { + return 0; + } + if (!parent.isValid()) { + return m_root->count(); + } + if (const AbstractLevel *level = getLevel(parent)) { + if (level->id() != parent.internalId()) { + // not a real level - no children + return 0; + } + return level->count(); + } + return 0; +} + +QModelIndex ClientModel::parent(const QModelIndex &child) const +{ + if (!child.isValid() || child.column() != 0) { + return QModelIndex(); + } + return parentForId(child.internalId()); +} + +QModelIndex ClientModel::parentForId(quint32 childId) const +{ + if (childId == m_root->id()) { + // asking for parent of our toplevel + return QModelIndex(); + } + if (AbstractLevel *parentLevel = m_root->parentForId(childId)) { + if (parentLevel == m_root) { + return QModelIndex(); + } + const int row = m_root->rowForId(parentLevel->id()); + if (row == -1) { + // error + return QModelIndex(); + } + return createIndex(row, 0, parentLevel->id()); + } + return QModelIndex(); +} + +QModelIndex ClientModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column != 0 || row < 0 || !m_root) { + return QModelIndex(); + } + if (!parent.isValid()) { + if (row >= rowCount()) { + return QModelIndex(); + } + return createIndex(row, 0, m_root->idForRow(row)); + } + const AbstractLevel *parentLevel = getLevel(parent); + if (!parentLevel) { + return QModelIndex(); + } + if (row >= parentLevel->count()) { + return QModelIndex(); + } + const quint32 id = parentLevel->idForRow(row); + if (id == 0) { + return QModelIndex(); + } + return createIndex(row, column, id); +} + +const AbstractLevel *ClientModel::getLevel(const QModelIndex &index) const +{ + if (!index.isValid()) { + return m_root; + } + return m_root->levelForId(index.internalId()); +} + +void ClientModel::levelBeginInsert(int rowStart, int rowEnd, quint32 id) +{ + const int row = m_root->rowForId(id); + QModelIndex parent; + if (row != -1) { + parent = createIndex(row, 0, id); + } + beginInsertRows(parent, rowStart, rowEnd); +} + +void ClientModel::levelBeginRemove(int rowStart, int rowEnd, quint32 id) +{ + const int row = m_root->rowForId(id); + QModelIndex parent; + if (row != -1) { + parent = createIndex(row, 0, id); + } + beginRemoveRows(parent, rowStart, rowEnd); +} + +void ClientModel::levelEndInsert() +{ + endInsertRows(); +} + +void ClientModel::levelEndRemove() +{ + endRemoveRows(); +} + +#define CLIENT_MODEL_WRAPPER(name, levels) \ +name::name(QObject *parent) \ + : ClientModel(parent) \ +{ \ + setLevels(levels); \ +} \ +name::~name() {} + +CLIENT_MODEL_WRAPPER(SimpleClientModel, QList()) +CLIENT_MODEL_WRAPPER(ClientModelByScreen, QList() << ScreenRestriction) +CLIENT_MODEL_WRAPPER(ClientModelByScreenAndDesktop, QList() << ScreenRestriction << VirtualDesktopRestriction) +#undef CLIENT_MODEL_WRAPPER + +ClientFilterModel::ClientFilterModel(QObject *parent) + : QSortFilterProxyModel(parent) + , m_clientModel(NULL) +{ +} + +ClientFilterModel::~ClientFilterModel() +{ +} + +void ClientFilterModel::setClientModel(ClientModel *clientModel) +{ + if (clientModel == m_clientModel) { + return; + } + m_clientModel = clientModel; + setSourceModel(m_clientModel); + emit clientModelChanged(); +} + +void ClientFilterModel::setFilter(const QString &filter) +{ + if (filter == m_filter) { + return; + } + m_filter = filter; + emit filterChanged(); + invalidateFilter(); +} + +bool ClientFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + if (!m_clientModel) { + return false; + } + if (m_filter.isEmpty()) { + return true; + } + QModelIndex index = m_clientModel->index(sourceRow, 0, sourceParent); + if (!index.isValid()) { + return false; + } + QVariant data = index.data(); + if (!data.isValid()) { + // an invalid QVariant is valid data + return true; + } + // TODO: introduce a type as a data role and properly check, this seems dangerous + if (data.type() == QVariant::Int || data.type() == QVariant::UInt || data.type() == QVariant::String) { + // we do not filter out screen, desktop and activity + return true; + } + Client *client = qvariant_cast(data); + if (!client) { + return false; + } + if (client->caption().contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + const QString windowRole(client->windowRole()); + if (windowRole.contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + const QString resourceName(client->resourceName()); + if (resourceName.contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + const QString resourceClass(client->resourceClass()); + if (resourceClass.contains(m_filter, Qt::CaseInsensitive)) { + return true; + } + return false; +} + +} // namespace Scripting +} // namespace KWin diff --git a/scripting/scripting_model.h b/scripting/scripting_model.h new file mode 100644 index 0000000000..d4306c058e --- /dev/null +++ b/scripting/scripting_model.h @@ -0,0 +1,380 @@ +/******************************************************************** + KWin - the KDE window manager + This file is part of the KDE project. + +Copyright (C) 2013 Martin Gräßlin + +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 . +*********************************************************************/ +#ifndef KWIN_SCRIPTING_MODEL_H +#define KWIN_SCRIPTING_MODEL_H + +#include +#include +#include + +namespace KWin { +class Client; + +namespace ScriptingClientModel { + +class AbstractLevel; + +class ClientModel : public QAbstractItemModel +{ + Q_OBJECT + Q_ENUMS(Exclude) + Q_ENUMS(LevelRestriction) + Q_PROPERTY(Exclusions exclusions READ exclusions WRITE setExclusions NOTIFY exclusionsChanged) +public: + enum Exclusion { + NoExclusion = 0, + // window types + DesktopWindowsExclusion = 1 << 0, + DockWindowsExclusion = 1 << 1, + UtilityWindowsExclusion = 1 << 2, + SpecialWindowsExclusion = 1 << 3, + // windows with flags + SkipTaskbarExclusion = 1 << 4, + SkipPagerExclusion = 1 << 5, + SwitchSwitcherExclusion = 1 << 6, + // based on state + OtherDesktopsExclusion = 1 << 7, + OtherActivitiesExclusion = 1 << 8, + MinimizedExclusion = 1 << 9, + NonSelectedWindowTabExclusion = 1 << 10, + NotAcceptingFocusExclusion = 1 << 11 + }; + Q_DECLARE_FLAGS(Exclusions, Exclusion) + Q_FLAGS(Exclusions) + enum LevelRestriction { + NoRestriction = 0, + VirtualDesktopRestriction = 1 << 0, + ScreenRestriction = 1 << 1, + ActivityRestriction = 1 << 2 + }; + Q_DECLARE_FLAGS(LevelRestrictions, LevelRestriction) + Q_FLAGS(LevelRestrictions) + explicit ClientModel(QObject *parent); + virtual ~ClientModel(); + virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; + virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + virtual QModelIndex parent(const QModelIndex &child) const; + virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + + void setExclusions(ClientModel::Exclusions exclusions); + Exclusions exclusions() const; + +Q_SIGNALS: + void exclusionsChanged(); + +private Q_SLOTS: + void levelBeginInsert(int rowStart, int rowEnd, quint32 parentId); + void levelEndInsert(); + void levelBeginRemove(int rowStart, int rowEnd, quint32 parentId); + void levelEndRemove(); + +protected: + enum ClientModelRoles { + ClientRole = Qt::UserRole, + ScreenRole, + DesktopRole, + ActivityRole + }; + void setLevels(QList restrictions); + +private: + QModelIndex parentForId(quint32 childId) const; + const AbstractLevel *getLevel(const QModelIndex &index) const; + AbstractLevel *m_root; + Exclusions m_exclusions; +}; + +/** + * @brief The data structure of the Model. + * + * The model is implemented as a Tree consisting of AbstractLevels as the levels of the tree. + * A non leaf level is represented by the inheriting class @link ForkLevel, the last level above a + * leaf is represented by the inheriting class @link ClientLevel, which contains the Clients - each + * Client is one leaf. + * + * In case the tree would only consist of Clients - leafs - it has always one ClientLevel as the root + * of the tree. + * + * The number of levels in the tree is controlled by the LevelRestrictions. For each existing + * LevelRestriction a new Level is created, if there are no more restrictions a ClientLevel is created. + * + * To build up the tree the static factory method @link create has to be used. It will recursively + * build up the tree. After the tree has been build up use @link init to initialize the tree which + * will add the Clients to the ClientLevel. + * + * Each element of the tree has a unique id which can be used by the QAbstractItemModel as the + * internal id for its QModelIndex. Note: the ids have no ordering, if trying to get a specific element + * the tree performs a depth-first search. + * + */ +class AbstractLevel : public QObject +{ + Q_OBJECT +public: + virtual ~AbstractLevel(); + virtual int count() const = 0; + virtual void init() = 0; + virtual quint32 idForRow(int row) const = 0; + + uint screen() const; + uint virtualDesktop() const; + const QString &activity() const; + ClientModel::LevelRestrictions restrictions() const; + void setRestrictions(ClientModel::LevelRestrictions restrictions); + ClientModel::LevelRestriction restriction() const; + void setRestriction(ClientModel::LevelRestriction restriction); + quint32 id() const; + AbstractLevel *parentLevel() const; + virtual const AbstractLevel *levelForId(quint32 id) const = 0; + virtual AbstractLevel *parentForId(quint32 child) const = 0; + virtual int rowForId(quint32 child) const = 0; + virtual Client *clientForId(quint32 child) const = 0; + + virtual void setScreen(uint screen); + virtual void setVirtualDesktop(uint virtualDesktop); + virtual void setActivity(const QString &activity); + + static AbstractLevel *create(const QList &restrictions, ClientModel::LevelRestrictions parentRestrictions, ClientModel *model, AbstractLevel *parent = NULL); + +Q_SIGNALS: + void beginInsert(int rowStart, int rowEnd, quint32 parentId); + void endInsert(); + void beginRemove(int rowStart, int rowEnd, quint32 parentId); + void endRemove(); +protected: + AbstractLevel(ClientModel *model, AbstractLevel *parent); + ClientModel *model() const; +private: + ClientModel *m_model; + AbstractLevel *m_parent; + uint m_screen; + uint m_virtualDesktop; + QString m_activity; + ClientModel::LevelRestriction m_restriction; + ClientModel::LevelRestrictions m_restrictions; + quint32 m_id; +}; + +class ForkLevel : public AbstractLevel +{ + Q_OBJECT +public: + ForkLevel(const QList &childRestrictions, ClientModel *model, AbstractLevel *parent); + virtual ~ForkLevel(); + virtual int count() const; + virtual void init(); + virtual quint32 idForRow(int row) const; + void addChild(AbstractLevel *child); + virtual void setScreen(uint screen); + virtual void setVirtualDesktop(uint virtualDesktop); + virtual void setActivity(const QString &activity); + virtual const AbstractLevel *levelForId(quint32 id) const; + virtual AbstractLevel *parentForId(quint32 child) const; + virtual int rowForId(quint32 child) const; + virtual Client *clientForId(quint32 child) const; +private Q_SLOTS: + void desktopCountChanged(uint previousCount, uint newCount); + void screenCountChanged(int newCount); + void activityAdded(const QString &id); + void activityRemoved(const QString &id); +private: + QList m_children; + QList m_childRestrictions; +}; + +/** + * @brief The actual leafs of the model's tree containing the Client's in this branch of the tree. + * + * This class groups all the Clients of one branch of the tree and takes care of updating the tree + * when a Client changes its state in a way that it should be excluded/included or gets added or + * removed. + * + * The Clients in this group are not sorted in any particular way. It's a simple list which only + * gets added to. If some sorting should be applied, use a QSortFilterProxyModel. + */ +class ClientLevel : public AbstractLevel +{ + Q_OBJECT +public: + explicit ClientLevel(ClientModel *model, AbstractLevel *parent); + virtual ~ClientLevel(); + + void init(); + + int count() const; + quint32 idForRow(int row) const; + bool containsId(quint32 id) const; + int rowForId(quint32 row) const; + Client *clientForId(quint32 child) const; + virtual const AbstractLevel *levelForId(quint32 id) const; + virtual AbstractLevel *parentForId(quint32 child) const; +public Q_SLOTS: + void clientAdded(KWin::Client *client); + void clientRemoved(KWin::Client *client); +private Q_SLOTS: + // uses sender() + void checkClient(); + void reInit(); +private: + void checkClient(KWin::Client *client); + void setupClientConnections(Client *client); + void addClient(Client *client); + void removeClient(Client *client); + bool shouldAdd(Client *client) const; + bool exclude(Client *client) const; + bool containsClient(Client *client) const; + QMap m_clients; +}; + +class SimpleClientModel : public ClientModel +{ + Q_OBJECT +public: + SimpleClientModel(QObject *parent = NULL); + virtual ~SimpleClientModel(); +}; + +class ClientModelByScreen : public ClientModel +{ + Q_OBJECT +public: + ClientModelByScreen(QObject *parent = NULL); + virtual ~ClientModelByScreen(); +}; + +class ClientModelByScreenAndDesktop : public ClientModel +{ + Q_OBJECT +public: + ClientModelByScreenAndDesktop(QObject *parent = NULL); + virtual ~ClientModelByScreenAndDesktop(); +}; + +/** + * @brief Custom QSortFilterProxyModel to filter on Client caption, role and class. + * + */ +class ClientFilterModel : public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(KWin::ScriptingClientModel::ClientModel *clientModel READ clientModel WRITE setClientModel NOTIFY clientModelChanged) + Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) +public: + ClientFilterModel(QObject *parent = 0); + virtual ~ClientFilterModel(); + ClientModel *clientModel() const; + const QString &filter() const; + +public Q_SLOTS: + void setClientModel(ClientModel *clientModel); + void setFilter(const QString &filter); + +protected: + virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; + +Q_SIGNALS: + void clientModelChanged(); + void filterChanged(); + +private: + ClientModel *m_clientModel; + QString m_filter; +}; + +inline +int ClientLevel::count() const +{ + return m_clients.count(); +} + +inline +const QString &AbstractLevel::activity() const +{ + return m_activity; +} + +inline +AbstractLevel *AbstractLevel::parentLevel() const +{ + return m_parent; +} + +inline +ClientModel *AbstractLevel::model() const +{ + return m_model; +} + +inline +uint AbstractLevel::screen() const +{ + return m_screen; +} + +inline +uint AbstractLevel::virtualDesktop() const +{ + return m_virtualDesktop; +} + +inline +ClientModel::LevelRestriction AbstractLevel::restriction() const +{ + return m_restriction; +} + +inline +ClientModel::LevelRestrictions AbstractLevel::restrictions() const +{ + return m_restrictions; +} + +inline +quint32 AbstractLevel::id() const +{ + return m_id; +} + +inline +ClientModel::Exclusions ClientModel::exclusions() const +{ + return m_exclusions; +} + +inline +ClientModel *ClientFilterModel::clientModel() const +{ + return m_clientModel; +} + +inline +const QString &ClientFilterModel::filter() const +{ + return m_filter; +} + +} // namespace Scripting +} // namespace KWin + +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ScriptingClientModel::ClientModel::Exclusions) +Q_DECLARE_OPERATORS_FOR_FLAGS(KWin::ScriptingClientModel::ClientModel::LevelRestrictions) + +#endif // KWIN_SCRIPTING_MODEL_H