KWinRules KCM Redesign

Summary:
Replacement KCM to configure kwin rules, using a QML-based UI.

After some work on the task T12729, it is almost feature-par with the previous module, and adapted to the recent move to KConfigXT.

Test Plan:
{F8208046}
{F8208047}

Reviewers: #plasma, #kwin, #vdg, ngraham, davidedmundson, zzag

Reviewed By: #plasma, #kwin, #vdg, ngraham, davidedmundson, zzag

Subscribers: ngraham, davidedmundson, hchain, broulik, zzag, kwin

Tags: #kwin, #vdg

Differential Revision: https://phabricator.kde.org/D28152
icc-effect-master
Ismael Asensio 2020-04-20 22:01:55 +02:00
parent 19bfa7c065
commit a04b40dadb
35 changed files with 3370 additions and 5265 deletions

View File

@ -3,20 +3,19 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kcmkwinrules\")
add_definitions(-DKCMRULES)
include_directories(../../)
set(kwinrules_SRCS ../../rulebooksettings.cpp
../../cursor.cpp
../../plugins/platforms/x11/standalone/x11cursor.cpp
../../rules.cpp
../../placement.cpp
../../utils.cpp
yesnobox.cpp
ruleswidget.cpp
ruleslist.cpp
kwinsrc.cpp
detectwidget.cpp
)
ki18n_wrap_ui(kwinrules_SRCS ruleslist.ui detectwidget.ui editshortcut.ui ruleswidgetbase.ui)
set(kwinrules_SRCS
../../rulebooksettings.cpp
../../cursor.cpp
../../plugins/platforms/x11/standalone/x11cursor.cpp
../../rules.cpp
../../placement.cpp
../../utils.cpp
kwinsrc.cpp
optionsmodel.cpp
ruleitem.cpp
rulesmodel.cpp
)
kconfig_add_kcfg_files(kwinrules_SRCS ../../rulesettings.kcfgc)
kconfig_add_kcfg_files(kwinrules_SRCS ../../rulebooksettingsbase.kcfgc)
@ -30,13 +29,11 @@ set(kwin_kcm_rules_XCB_LIBS
)
set(kcm_libs
Qt5::Concurrent
Qt5::X11Extras
Qt5::Quick
Qt5::QuickWidgets
KF5::Completion
KF5::ConfigWidgets
KF5::I18n
KF5::Service
KF5::QuickAddons
KF5::WindowSystem
KF5::XmlGui
)
@ -46,14 +43,14 @@ if (KWIN_BUILD_ACTIVITIES)
endif()
target_link_libraries(KWinRulesObjects ${kcm_libs} ${kwin_kcm_rules_XCB_LIBS})
add_executable(kwin_rules_dialog main.cpp)
add_executable(kwin_rules_dialog main.cpp rulesdialog.cpp)
target_link_libraries(kwin_rules_dialog KWinRulesObjects)
install(TARGETS kwin_rules_dialog DESTINATION ${LIBEXEC_INSTALL_DIR})
add_library(kcm_kwinrules MODULE kcm.cpp)
add_library(kcm_kwinrules MODULE kcmrules.cpp rulebookmodel.cpp)
target_link_libraries(kcm_kwinrules KWinRulesObjects)
install(TARGETS kcm_kwinrules DESTINATION ${PLUGIN_INSTALL_DIR})
kcoreaddons_desktop_to_json(kcm_kwinrules "kcm_kwinrules.desktop" SERVICE_TYPES kcmodule.desktop)
########### install files ###############
install(FILES kwinrules.desktop DESTINATION ${SERVICES_INSTALL_DIR})
install(TARGETS kcm_kwinrules DESTINATION ${PLUGIN_INSTALL_DIR}/kcms)
install(FILES kcm_kwinrules.desktop DESTINATION ${SERVICES_INSTALL_DIR})
kpackage_install_package(package kcm_kwinrules kcms)

View File

@ -1,176 +0,0 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "detectwidget.h"
#include "../../plugins/platforms/x11/standalone/x11cursor.h"
#include <KLocalizedString>
#include <QDebug>
#include <kwindowsystem.h>
#include <QLabel>
#include <QRadioButton>
#include <QCheckBox>
#include <QDialogButtonBox>
#include <QMouseEvent>
#include <QEvent>
#include <QByteArray>
#include <QTimer>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QDBusPendingCallWatcher>
#include <QDBusPendingReply>
Q_DECLARE_METATYPE(NET::WindowType)
namespace KWin
{
DetectWidget::DetectWidget(QWidget* parent)
: QWidget(parent)
{
setupUi(this);
}
DetectDialog::DetectDialog(QWidget* parent, const char* name)
: QDialog(parent)
{
setObjectName(name);
setModal(true);
setLayout(new QVBoxLayout);
widget = new DetectWidget(this);
layout()->addWidget(widget);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel, this);
layout()->addWidget(buttons);
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
}
void DetectDialog::detect(int secs)
{
QTimer::singleShot(secs*1000, this, SLOT(selectWindow()));
}
void DetectDialog::executeDialog()
{
static const char* const types[] = {
I18N_NOOP("Normal Window"),
I18N_NOOP("Desktop"),
I18N_NOOP("Dock (panel)"),
I18N_NOOP("Toolbar"),
I18N_NOOP("Torn-Off Menu"),
I18N_NOOP("Dialog Window"),
I18N_NOOP("Override Type"),
I18N_NOOP("Standalone Menubar"),
I18N_NOOP("Utility Window"),
I18N_NOOP("Splash Screen")
};
widget->class_label->setText(wmclass_class + QLatin1String(" (") + wmclass_name + ' ' + wmclass_class + ')');
widget->role_label->setText(role);
widget->match_role->setEnabled(!role.isEmpty());
if (type == NET::Unknown)
widget->type_label->setText(i18n("Unknown - will be treated as Normal Window"));
else
widget->type_label->setText(i18n(types[ type ]));
widget->title_label->setText(title);
widget->machine_label->setText(machine);
widget->adjustSize();
adjustSize();
if (width() < 4*height()/3)
resize(4*height()/3, height());
emit detectionDone(exec() == QDialog::Accepted);
}
QByteArray DetectDialog::selectedClass() const
{
if (widget->match_whole_class->isChecked())
return wmclass_name + ' ' + wmclass_class;
return wmclass_class;
}
bool DetectDialog::selectedWholeClass() const
{
return widget->match_whole_class->isChecked();
}
QByteArray DetectDialog::selectedRole() const
{
if (widget->match_role->isChecked())
return role;
return "";
}
QString DetectDialog::selectedTitle() const
{
return title;
}
Rules::StringMatch DetectDialog::titleMatch() const
{
return widget->match_title->isChecked() ? Rules::ExactMatch : Rules::UnimportantMatch;
}
bool DetectDialog::selectedWholeApp() const
{
return !widget->match_type->isChecked();
}
NET::WindowType DetectDialog::selectedType() const
{
return type;
}
QByteArray DetectDialog::selectedMachine() const
{
return machine;
}
void DetectDialog::selectWindow()
{
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
QStringLiteral("/KWin"),
QStringLiteral("org.kde.KWin"),
QStringLiteral("queryWindowInfo"));
QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
connect(callWatcher, &QDBusPendingCallWatcher::finished, this,
[this](QDBusPendingCallWatcher *self) {
QDBusPendingReply<QVariantMap> reply = *self;
self->deleteLater();
if (!reply.isValid()) {
emit detectionDone(false);
return;
}
m_windowInfo = reply.value();
wmclass_class = m_windowInfo.value("resourceClass").toByteArray();
wmclass_name = m_windowInfo.value("resourceName").toByteArray();
role = m_windowInfo.value("role").toByteArray();
type = m_windowInfo.value("type").value<NET::WindowType>();
title = m_windowInfo.value("caption").toString();
machine = m_windowInfo.value("clientMachine").toByteArray();
executeDialog();
}
);
}
} // namespace

View File

@ -1,83 +0,0 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __DETECTWIDGET_H__
#define __DETECTWIDGET_H__
#include <QDialog>
#include <kwindowsystem.h>
#include "../../rules.h"
//Added by qt3to4:
#include <QEvent>
#include <QByteArray>
#include "ui_detectwidget.h"
namespace KWin
{
class DetectWidget
: public QWidget, public Ui_DetectWidget
{
Q_OBJECT
public:
explicit DetectWidget(QWidget* parent = nullptr);
};
class DetectDialog
: public QDialog
{
Q_OBJECT
public:
explicit DetectDialog(QWidget* parent = nullptr, const char* name = nullptr);
void detect(int secs = 0);
QByteArray selectedClass() const;
bool selectedWholeClass() const;
QByteArray selectedRole() const;
bool selectedWholeApp() const;
NET::WindowType selectedType() const;
QString selectedTitle() const;
Rules::StringMatch titleMatch() const;
QByteArray selectedMachine() const;
const QVariantMap &windowInfo() const {
return m_windowInfo;
}
Q_SIGNALS:
void detectionDone(bool);
private Q_SLOTS:
void selectWindow();
private:
void executeDialog();
QByteArray wmclass_class;
QByteArray wmclass_name;
QByteArray role;
NET::WindowType type;
QString title;
QByteArray extrarole;
QByteArray machine;
DetectWidget* widget;
QVariantMap m_windowInfo;
};
} // namespace
#endif

View File

@ -1,229 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KWin::DetectWidget</class>
<widget class="QWidget" name="KWin::DetectWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>450</width>
<height>300</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupBox_2">
<property name="title">
<string>Information About Selected Window</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout">
<property name="verticalSpacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="textLabel1">
<property name="text">
<string>Class:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="class_label">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="textLabel3">
<property name="text">
<string>Role:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="role_label">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="textLabel4">
<property name="text">
<string>Type:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="type_label">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="textLabel8">
<property name="text">
<string>Title:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="title_label">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="textLabel13">
<property name="text">
<string>Machine:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="machine_label">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Match by primary class name and</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="match_whole_class">
<property name="text">
<string>Secondary class name (resulting in term in brackets)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="match_role">
<property name="text">
<string>Window role (can be used to select windows by function)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="match_type">
<property name="text">
<string>Window type (eg. all dialogs, but not the main windows)</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="match_title">
<property name="text">
<string>Window title (very specific, can fail due to content changes or translation)</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -1,161 +0,0 @@
<ui version="4.0" >
<class>EditShortcut</class>
<widget class="QWidget" name="EditShortcut" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>1194</width>
<height>447</height>
</rect>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QLabel" name="textLabel2" >
<property name="text" >
<string>A single shortcut can be easily assigned or cleared using the two buttons. Only shortcuts with modifiers can be used.&lt;p>
It is possible to have several possible shortcuts, and the first available shortcut will be used. The shortcuts are specified using shortcut sets separated by " - ". One set is specified as &lt;i>base&lt;/i>+(&lt;i>list&lt;/i>), where base are modifiers and list is a list of keys.&lt;br>
For example "&lt;b>Shift+Alt+(123) Shift+Ctrl+(ABC)&lt;/b>" will first try &lt;b>Shift+Alt+1&lt;/b>, then others with &lt;b>Shift+Ctrl+C&lt;/b> as the last one.</string>
</property>
<property name="textFormat" >
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="Line" name="line1" >
<property name="frameShape" >
<enum>QFrame::HLine</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Sunken</enum>
</property>
</widget>
</item>
<item>
<widget class="KLineEdit" native="1" name="shortcut" />
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton1" >
<property name="text" >
<string>&amp;Single Shortcut</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton2" >
<property name="text" >
<string>C&amp;lear</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType" >
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line2" >
<property name="frameShape" >
<enum>QFrame::HLine</enum>
</property>
<property name="frameShadow" >
<enum>QFrame::Sunken</enum>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KLineEdit</class>
<extends>QWidget</extends>
<header>klineedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>
<sender>pushButton1</sender>
<signal>clicked()</signal>
<receiver>EditShortcut</receiver>
<slot>editShortcut()</slot>
<hints>
<hint type="sourcelabel" >
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel" >
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>pushButton2</sender>
<signal>clicked()</signal>
<receiver>EditShortcut</receiver>
<slot>clearShortcut()</slot>
<hints>
<hint type="sourcelabel" >
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel" >
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -1,98 +0,0 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "kcm.h"
#include <QLayout>
//Added by qt3to4:
#include <QVBoxLayout>
#include <KLocalizedString>
#include <kaboutdata.h>
#include <QtDBus>
#include <QX11Info>
#include "ruleslist.h"
#include <KPluginFactory>
#include <KPluginLoader>
K_PLUGIN_FACTORY(KCMRulesFactory,
registerPlugin<KWin::KCMRules>();
)
namespace KWin
{
KCMRules::KCMRules(QWidget *parent, const QVariantList &)
: KCModule(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
widget = new KCMRulesList(this);
layout->addWidget(widget);
connect(widget, SIGNAL(changed(bool)), SLOT(moduleChanged(bool)));
KAboutData *about = new KAboutData(QStringLiteral("kcmkwinrules"),
i18n("Window-Specific Settings Configuration Module"),
QString(), QString(), KAboutLicense::GPL, i18n("(c) 2004 KWin and KControl Authors"));
about->addAuthor(i18n("Lubos Lunak"), QString(), "l.lunak@kde.org");
setAboutData(about);
setButtons(KCModule::Help | KCModule::Apply);
}
void KCMRules::load()
{
widget->load();
emit KCModule::changed(false);
}
void KCMRules::save()
{
widget->save();
emit KCModule::changed(false);
// Send signal to all kwin instances
QDBusMessage message =
QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig");
QDBusConnection::sessionBus().send(message);
}
QString KCMRules::quickHelp() const
{
return i18n("<p><h1>Window-specific Settings</h1> Here you can customize window settings specifically only"
" for some windows.</p>"
" <p>Please note that this configuration will not take effect if you do not use"
" KWin as your window manager. If you do use a different window manager, please refer to its documentation"
" for how to customize window behavior.</p>");
}
void KCMRules::moduleChanged(bool state)
{
emit KCModule::changed(state);
}
}
// i18n freeze :-/
#if 0
I18N_NOOP("Remember settings separately for every window")
I18N_NOOP("Show internal settings for remembering")
I18N_NOOP("Internal setting for remembering")
#endif
#include "kcm.moc"

View File

@ -1,16 +1,17 @@
[Desktop Entry]
Exec=kcmshell5 kwinrules
Icon=preferences-system-windows-actions
Categories=Qt;KDE;X-KDE-settings-looknfeel;
Type=Service
X-KDE-ServiceTypes=KCModule
X-DocPath=kcontrol/windowspecific/index.html
X-KDE-ServiceTypes=KCModule
X-KDE-Library=kcm_kwinrules
X-KDE-ParentApp=kcontrol
X-KDE-System-Settings-Parent-Category=windowmanagement
X-KDE-Weight=120
X-KDE-FormFactors=desktop,tablet
Name=Window Rules
Name[ar]=قواعد النوافذ
Name[bg]=Правила за прозорци

View File

@ -0,0 +1,254 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@kde.org>
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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 "kcmrules.h"
#include <QDBusConnection>
#include <QDBusMessage>
#include <KAboutData>
#include <KConfig>
#include <KLocalizedString>
#include <KPluginFactory>
namespace KWin
{
KCMKWinRules::KCMKWinRules(QObject *parent, const QVariantList &arguments)
: KQuickAddons::ConfigModule(parent, arguments)
, m_ruleBookModel(new RuleBookModel(this))
, m_rulesModel(new RulesModel(this))
{
auto about = new KAboutData(QStringLiteral("kcm_kwinrules"),
i18n("Window Rules"),
QStringLiteral("1.0"),
QString(),
KAboutLicense::GPL);
about->addAuthor(i18n("Ismael Asensio"),
i18n("Author"),
QStringLiteral("isma.af@gmail.com"));
setAboutData(about);
setQuickHelp(i18n("<p><h1>Window-specific Settings</h1> Here you can customize window settings specifically only"
" for some windows.</p>"
" <p>Please note that this configuration will not take effect if you do not use"
" KWin as your window manager. If you do use a different window manager, please refer to its documentation"
" for how to customize window behavior.</p>"));
connect(m_rulesModel, &RulesModel::descriptionChanged, this, [this]{
if (m_editIndex.isValid()) {
m_ruleBookModel->setDescriptionAt(m_editIndex.row(), m_rulesModel->description());
}
} );
connect(m_rulesModel, &RulesModel::dataChanged, this, &KCMKWinRules::updateNeedsSave);
connect(m_ruleBookModel, &RulesModel::dataChanged, this, &KCMKWinRules::updateNeedsSave);
}
void KCMKWinRules::load()
{
m_ruleBookModel->load();
m_editIndex = QModelIndex();
emit editIndexChanged();
setNeedsSave(false);
}
void KCMKWinRules::save()
{
saveCurrentRule();
m_ruleBookModel->save();
// Notify kwin to reload configuration
QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig");
QDBusConnection::sessionBus().send(message);
}
void KCMKWinRules::updateNeedsSave()
{
setNeedsSave(true);
emit needsSaveChanged();
}
void KCMKWinRules::saveCurrentRule()
{
if (m_editIndex.isValid() && needsSave()) {
m_ruleBookModel->setRuleAt(m_editIndex.row(), m_rulesModel->exportToRules());
}
}
int KCMKWinRules::editIndex() const
{
if (!m_editIndex.isValid()) {
return -1;
}
return m_editIndex.row();
}
void KCMKWinRules::setRuleDescription(int index, const QString &description)
{
if (index < 0 || index >= m_ruleBookModel->rowCount()) {
return;
}
if (m_editIndex.row() == index) {
m_rulesModel->setDescription(description);
return;
}
m_ruleBookModel->setDescriptionAt(index, description);
updateNeedsSave();
}
void KCMKWinRules::editRule(int index)
{
if (index < 0 || index >= m_ruleBookModel->rowCount()) {
return;
}
saveCurrentRule();
m_editIndex = m_ruleBookModel->index(index);
emit editIndexChanged();
m_rulesModel->importFromRules(m_ruleBookModel->ruleAt(m_editIndex.row()));
// Set the active page to rules editor (0:RulesList, 1:RulesEditor)
setCurrentIndex(1);
}
void KCMKWinRules::createRule()
{
const int newIndex = m_ruleBookModel->rowCount();
m_ruleBookModel->insertRow(newIndex);
updateNeedsSave();
editRule(newIndex);
}
void KCMKWinRules::removeRule(int index)
{
if (index < 0 || index >= m_ruleBookModel->rowCount()) {
return;
}
m_ruleBookModel->removeRow(index);
emit editIndexChanged();
updateNeedsSave();
}
void KCMKWinRules::moveRule(int sourceIndex, int destIndex)
{
const int lastIndex = m_ruleBookModel->rowCount() - 1;
if (sourceIndex == destIndex
|| (sourceIndex < 0 || sourceIndex > lastIndex)
|| (destIndex < 0 || destIndex > lastIndex)) {
return;
}
m_ruleBookModel->moveRow(QModelIndex(), sourceIndex, QModelIndex(), destIndex);
emit editIndexChanged();
updateNeedsSave();
}
void KCMKWinRules::exportToFile(const QUrl &path, const QList<int> &indexes)
{
if (indexes.isEmpty()) {
return;
}
saveCurrentRule();
const auto config = KSharedConfig::openConfig(path.toLocalFile(), KConfig::SimpleConfig);
for (const QString &groupName : config->groupList()) {
config->deleteGroup(groupName);
}
for (int index : indexes) {
if (index < 0 || index > m_ruleBookModel->rowCount()) {
continue;
}
const Rules *rule = m_ruleBookModel->ruleAt(index);
RuleSettings settings(config, rule->description);
settings.setDefaults();
rule->write(&settings);
settings.save();
}
}
void KCMKWinRules::importFromFile(const QUrl &path)
{
const auto config = KSharedConfig::openConfig(path.toLocalFile(), KConfig::SimpleConfig);
const QStringList groups = config->groupList();
if (groups.isEmpty()) {
return;
}
for (const QString &groupName : groups) {
RuleSettings settings(config, groupName);
const bool remove = settings.deleteRule();
const QString importDescription = settings.description();
if (importDescription.isEmpty()) {
continue;
}
// Try to find a rule with the same description to replace
int newIndex = -2;
for (int index = 0; index < m_ruleBookModel->rowCount(); index++) {
if (m_ruleBookModel->descriptionAt(index) == importDescription) {
newIndex = index;
break;
}
}
if (remove) {
m_ruleBookModel->removeRow(newIndex);
continue;
}
if (newIndex < 0) {
newIndex = m_ruleBookModel->rowCount();
m_ruleBookModel->insertRow(newIndex);
}
m_ruleBookModel->setRuleAt(newIndex, new Rules(&settings));
// Reset rule editor if the current rule changed when importing
if (m_editIndex.row() == newIndex) {
m_rulesModel->importFromRules(m_ruleBookModel->ruleAt(m_editIndex.row()));
}
}
updateNeedsSave();
}
K_PLUGIN_CLASS_WITH_JSON(KCMKWinRules, "kcm_kwinrules.json");
} // namespace
#include "kcmrules.moc"

View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
#pragma once
#include <rules.h>
#include "rulebookmodel.h"
#include "rulesmodel.h"
#include <KQuickAddons/ConfigModule>
namespace KWin
{
class KCMKWinRules : public KQuickAddons::ConfigModule
{
Q_OBJECT
Q_PROPERTY(RuleBookModel *ruleBookModel MEMBER m_ruleBookModel CONSTANT)
Q_PROPERTY(RulesModel *rulesModel MEMBER m_rulesModel CONSTANT)
Q_PROPERTY(int editIndex READ editIndex NOTIFY editIndexChanged)
public:
explicit KCMKWinRules(QObject *parent, const QVariantList &arguments);
Q_INVOKABLE void setRuleDescription(int index, const QString &description);
Q_INVOKABLE void editRule(int index);
Q_INVOKABLE void createRule();
Q_INVOKABLE void removeRule(int index);
Q_INVOKABLE void moveRule(int sourceIndex, int destIndex);
Q_INVOKABLE void exportToFile(const QUrl &path, const QList<int> &indexes);
Q_INVOKABLE void importFromFile(const QUrl &path);
public slots:
void load() override;
void save() override;
signals:
void editIndexChanged();
private slots:
void updateNeedsSave();
private:
int editIndex() const;
void saveCurrentRule();
private:
RuleBookModel *m_ruleBookModel;
RulesModel* m_rulesModel;
QPersistentModelIndex m_editIndex;
};
} // namespace

View File

@ -22,8 +22,8 @@
#include <KLocalizedString>
#include <kwindowsystem.h>
#include "ruleswidget.h"
#include "rulebooksettings.h"
#include "rulesdialog.h"
#include "../../rules.h"
#include <QByteArray>

View File

@ -0,0 +1,210 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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 "optionsmodel.h"
#include <KLocalizedString>
namespace KWin
{
QHash<int, QByteArray> OptionsModel::roleNames() const
{
return {
{Qt::DisplayRole, QByteArrayLiteral("display")},
{Qt::DecorationRole, QByteArrayLiteral("decoration")},
{Qt::ToolTipRole, QByteArrayLiteral("tooltip")},
{Qt::UserRole, QByteArrayLiteral("value")},
{Qt::UserRole + 1, QByteArrayLiteral("iconName")},
};
}
int OptionsModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_data.size();
}
QVariant OptionsModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
return QVariant();
}
const Data data = m_data.at(index.row());
switch (role) {
case Qt::DisplayRole:
return data.text;
case Qt::UserRole:
return data.value;
case Qt::DecorationRole:
return data.icon;
case Qt::UserRole + 1:
return data.icon.name();
case Qt::ToolTipRole:
return data.description;
}
return QVariant();
}
int OptionsModel::selectedIndex() const
{
return m_index;
}
int OptionsModel::indexOf(QVariant value) const
{
for (int index = 0; index < m_data.count(); index++) {
if (m_data.at(index).value == value) {
return index;
}
}
return -1;
}
QString OptionsModel::textOfValue(QVariant value) const
{
int index = indexOf(value);
if (index < 0 || index >= m_data.count()) {
return QString();
}
return m_data.at(index).text;
}
QVariant OptionsModel::value() const
{
if (m_data.isEmpty()) {
return QVariant();
}
return m_data.at(m_index).value;
}
void OptionsModel::setValue(QVariant value)
{
if (this->value() == value) {
return;
}
int index = indexOf(value);
if (index >= 0 && index != m_index) {
m_index = index;
emit selectedIndexChanged(index);
}
}
void OptionsModel::resetValue()
{
m_index = 0;
emit selectedIndexChanged(m_index);
}
void OptionsModel::updateModelData(const QList<Data> &data) {
beginResetModel();
m_data = data;
endResetModel();
}
RulePolicy::Type RulePolicy::type() const
{
return m_type;
}
int RulePolicy::value() const
{
if (m_type == RulePolicy::NoPolicy) {
return Rules::Apply; // To simplify external checks when rule has no policy
}
return OptionsModel::value().toInt();
}
QString RulePolicy::policyKey(const QString &key) const
{
switch (m_type) {
case NoPolicy:
return QString();
case StringMatch:
return QStringLiteral("%1match").arg(key);
case SetRule:
case ForceRule:
return QStringLiteral("%1rule").arg(key);
}
return QString();
}
QList<RulePolicy::Data> RulePolicy::policyOptions(RulePolicy::Type type)
{
static const auto stringMatchOptions = QList<RulePolicy::Data> {
{Rules::UnimportantMatch, i18n("Unimportant")},
{Rules::ExactMatch, i18n("Exact Match")},
{Rules::SubstringMatch, i18n("Substring Match")},
{Rules::RegExpMatch, i18n("Regular Expression")}
};
static const auto setRuleOptions = QList<RulePolicy::Data> {
{Rules::DontAffect,
i18n("Do Not Affect"),
i18n("The window property will not be affected and therefore the default handling for it will be used."
"\nSpecifying this will block more generic window settings from taking effect.")},
{Rules::Apply,
i18n("Apply Initially"),
i18n("The window property will be only set to the given value after the window is created."
"\nNo further changes will be affected.")},
{Rules::Remember,
i18n("Remember"),
i18n("The value of the window property will be remembered and, every time the window"
" is created, the last remembered value will be applied.")},
{Rules::Force,
i18n("Force"),
i18n("The window property will be always forced to the given value.")},
{Rules::ApplyNow,
i18n("Apply Now"),
i18n("The window property will be set to the given value immediately and will not be affected later"
"\n(this action will be deleted afterwards).")},
{Rules::ForceTemporarily,
i18n("Force Temporarily"),
i18n("The window property will be forced to the given value until it is hidden"
"\n(this action will be deleted after the window is hidden).")}
};
static auto forceRuleOptions = QList<RulePolicy::Data> {
setRuleOptions.at(0), // Rules::DontAffect
setRuleOptions.at(3), // Rules::Force
setRuleOptions.at(5), // Rules::ForceTemporarily
};
switch (type) {
case NoPolicy:
return {};
case StringMatch:
return stringMatchOptions;
case SetRule:
return setRuleOptions;
case ForceRule:
return forceRuleOptions;
}
return {};
}
} //namespace

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
#ifndef KWIN_OPTIONS_MODEL_H
#define KWIN_OPTIONS_MODEL_H
#include <rules.h>
#include <QAbstractListModel>
#include <QIcon>
#include <QVariant>
namespace KWin {
class OptionsModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int selectedIndex READ selectedIndex NOTIFY selectedIndexChanged)
public:
struct Data {
Data(const QVariant &value, const QString &text, const QIcon &icon = {}, const QString &description = {})
: value(value)
, text(text)
, icon(icon)
, description(description)
{}
Data(const QVariant &value, const QString &text, const QString &description)
: value(value)
, text(text)
, description(description)
{}
QVariant value;
QString text;
QIcon icon;
QString description;
};
public:
OptionsModel() : QAbstractListModel(), m_data(), m_index(0) {};
OptionsModel(const QList<Data> &data) : QAbstractListModel(), m_data(data), m_index(0) {};
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant value() const;
void setValue(QVariant value);
void resetValue();
void updateModelData(const QList<Data> &data);
Q_INVOKABLE int indexOf(QVariant value) const;
Q_INVOKABLE QString textOfValue(QVariant value) const;
int selectedIndex() const;
signals:
void selectedIndexChanged(int index);
public:
QList<Data> m_data;
protected:
int m_index = 0;
};
class RulePolicy : public OptionsModel
{
public:
enum Type {
NoPolicy,
StringMatch,
SetRule,
ForceRule
};
public:
RulePolicy(Type type)
: OptionsModel(policyOptions(type))
, m_type(type)
{};
Type type() const;
int value() const;
QString policyKey(const QString &key) const;
private:
static QList<Data> policyOptions(RulePolicy::Type type);
private:
Type m_type;
};
} //namespace
#endif //KWIN_OPTIONS_MODEL_H

View File

@ -0,0 +1,59 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
import QtQuick 2.14
import QtQuick.Dialogs 1.0 as QtDialogs
Loader {
id: root
active: false
property string title : i18n("Select File");
property string lastFolder : ""
property bool isSaveDialog : false
signal fileSelected(string path)
sourceComponent: QtDialogs.FileDialog {
id: fileDialog
title: root.title
selectExisting: !root.isSaveDialog
folder: (root.lastFolder == "") ? shortcuts.home : root.lastFolder
nameFilters: [ i18n("KWin Rules (*.kwinrule)") ]
defaultSuffix: "*.kwinrule"
Component.onCompleted: {
open();
}
onAccepted: {
root.lastFolder = folder;
if (fileUrl != "") {
root.fileSelected(fileUrl);
}
root.active = false;
}
onRejected: {
root.active = false;
}
}
}

View File

@ -0,0 +1,116 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14 as QQC2
import org.kde.kirigami 2.10 as Kirigami
QQC2.ComboBox {
id: optionsCombo
textRole: "display"
//TODO: After KF5 (qqc2-desktop-style) depends on Qt 5.15 this can be simplified using newer API
// (https://bugs.kde.org/show_bug.cgi?id=419521)
// valueRole: "value"
property var currentValue
onActivated: (index) => {
var modelIndex = model.index(index, 0);
currentValue = model.data(modelIndex, Qt.UserRole);
}
property bool multipleChoice: false
property int selectionMask: 0
currentIndex: multipleChoice ? -1 : model.selectedIndex
displayText: {
if (!multipleChoice) {
return currentText;
}
var selectionCount = selectionMask.toString(2).replace(/0/g, '').length;
switch (selectionCount) {
case 0:
return i18n("None selected");
case 1:
var selectedValue = selectionMask.toString(2).length - 1;
return model.textOfValue(selectedValue);
case count:
return i18n("All selected");
}
return i18np("%1 selected", "%1 selected", selectionCount);
}
delegate: QQC2.ItemDelegate {
highlighted: optionsCombo.highlightedIndex == index
width: parent.width
contentItem: RowLayout {
QQC2.CheckBox {
id: itemSelection
visible: multipleChoice
checked: (selectionMask & (1 << value))
onToggled: {
selectionMask = (selectionMask & ~(1 << value)) | (checked << value);
activated(index);
}
}
Kirigami.Icon {
source: model.decoration
Layout.preferredHeight: Kirigami.Units.iconSizes.small
Layout.preferredWidth: Kirigami.Units.iconSizes.small
}
QQC2.Label {
text: model.display
color: highlighted ? Kirigami.Theme.highlightedTextColor : Kirigami.Theme.textColor
Layout.fillWidth: true
horizontalAlignment: Text.AlignLeft
}
}
MouseArea {
anchors.fill: contentItem
enabled: multipleChoice
onClicked: {
itemSelection.toggle();
itemSelection.toggled();
}
}
QQC2.ToolTip {
text: model.tooltip
visible: hovered && (model.tooltip != "")
}
Component.onCompleted: {
//FIXME: work around bug https://bugs.kde.org/show_bug.cgi?id=403153
optionsCombo.popup.width = Math.max(implicitWidth, optionsCombo.width, optionsCombo.popup.width);
}
onActiveFocusChanged: {
if (!activeFocus) {
popup.close();
}
}
}
}

View File

@ -0,0 +1,117 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14 as QQC2
import org.kde.kirigami 2.10 as Kirigami
Kirigami.AbstractListItem {
id: ruleDelegate
property bool ruleEnabled: model.enabled
Kirigami.Theme.colorSet: Kirigami.Theme.View
width: ListView.view.width
highlighted: false
hoverEnabled: false
RowLayout {
anchors {
left: parent.left
right: parent.right
margins: Kirigami.Units.smallSpacing
}
Kirigami.Icon {
id: itemIcon
source: model.icon
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.alignment: Qt.AlignVCenter
}
QQC2.Label {
text: model.name
horizontalAlignment: Text.AlignLeft
elide: Text.ElideRight
Layout.preferredWidth: 10 * Kirigami.Units.gridUnit
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
}
RowLayout {
// This layout keeps the width constant between delegates, independent of items visibility
Layout.fillWidth: true
Layout.preferredWidth: 20 * Kirigami.Units.gridUnit
Layout.minimumWidth: 13 * Kirigami.Units.gridUnit
OptionsComboBox {
id: policyCombo
Layout.preferredWidth: 50 // 50%
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
flat: true
visible: count > 0
enabled: ruleEnabled
model: policyModel
onActivated: {
policy = currentValue;
}
}
ValueEditor {
id: valueEditor
Layout.preferredWidth: 50 // 50%
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter | Qt.AlignRight
enabled: model.enabled
ruleValue: model.value
ruleOptions: model.options
controlType: model.type
onValueEdited: (value) => {
model.value = value;
}
}
QQC2.ToolButton {
id: itemEnabled
icon.name: "edit-delete"
visible: model.selectable
Layout.alignment: Qt.AlignVCenter
onClicked: {
model.enabled = false;
}
}
}
QQC2.ToolTip {
text: model.description
visible: hovered && (text != "")
}
}
}

View File

@ -0,0 +1,270 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14 as QQC2
import org.kde.kirigami 2.10 as Kirigami
import org.kde.kcm 1.2
import org.kde.kitemmodels 1.0
import org.kde.kcms.kwinrules 1.0
ScrollViewKCM {
id: rulesEditor
property var rulesModel: kcm.rulesModel
title: rulesModel.description
view: ListView {
id: rulesView
clip: true
model: enabledRulesModel
delegate: RuleItemDelegate {}
section {
property: "section"
delegate: Kirigami.ListSectionHeader { label: section }
}
Item {
id: hintArea
visible: rulesView.count <= 4
anchors {
top: parent.contentItem.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
}
QQC2.Button {
anchors.centerIn: parent
text: i18n("Add Properties...")
icon.name: "list-add-symbolic"
onClicked: {
propertySheet.open();
}
}
}
}
// FIXME: InlineMessage.qml:241:13: QML Label: Binding loop detected for property "verticalAlignment"
header: Kirigami.InlineMessage {
Layout.fillWidth: true
Layout.fillHeight: true
text: rulesModel.warningMessage
visible: text != ""
}
footer: RowLayout {
QQC2.Button {
text: checked ? i18n("Close") : i18n("Add Properties...")
icon.name: checked ? "dialog-close" : "list-add-symbolic"
checkable: true
checked: propertySheet.sheetOpen
visible: !hintArea.visible || checked
onToggled: {
propertySheet.sheetOpen = checked;
}
}
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18n("Detect Window Properties")
icon.name: "edit-find"
onClicked: {
overlayModel.onlySuggestions = true;
rulesModel.detectWindowProperties(delaySpin.value);
}
}
QQC2.SpinBox {
id: delaySpin
Layout.preferredWidth: Kirigami.Units.gridUnit * 8
from: 0
to: 30
textFromValue: (value, locale) => {
return (value == 0) ? i18n("Instantly")
: i18np("After %1 second", "After %1 seconds", value)
}
}
}
Connections {
target: rulesModel
onSuggestionsChanged: {
propertySheet.sheetOpen = true;
}
}
Kirigami.OverlaySheet {
id: propertySheet
parent: view
header: Kirigami.Heading {
text: i18n("Select properties")
}
footer: Kirigami.SearchField {
id: searchField
horizontalAlignment: Text.AlignLeft
}
ListView {
id: overlayView
model: overlayModel
Layout.preferredWidth: Kirigami.Units.gridUnit * 28
section {
property: "section"
delegate: Kirigami.ListSectionHeader { label: section }
}
delegate: Kirigami.AbstractListItem {
id: propertyDelegate
highlighted: false
width: ListView.view.width
RowLayout {
anchors.fill: parent
Kirigami.Icon {
source: model.icon
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.alignment: Qt.AlignVCenter
}
QQC2.Label {
id: itemNameLabel
text: model.name
horizontalAlignment: Qt.AlignLeft
Layout.preferredWidth: implicitWidth
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
QQC2.ToolTip {
text: model.description
visible: hovered && (model.description != "")
}
}
QQC2.Label {
id: suggestedLabel
text: formatValue(model.suggested, model.type, model.options)
horizontalAlignment: Text.AlignRight
elide: Text.ElideRight
opacity: 0.7
Layout.maximumWidth: propertyDelegate.width - itemNameLabel.implicitWidth - Kirigami.Units.gridUnit * 6
Layout.alignment: Qt.AlignVCenter
QQC2.ToolTip {
text: suggestedLabel.text
visible: hovered && suggestedLabel.truncated
}
}
QQC2.ToolButton {
icon.name: (model.enabled) ? "dialog-ok-apply" : "list-add-symbolic"
opacity: propertyDelegate.hovered ? 1 : 0
onClicked: propertyDelegate.clicked()
Layout.preferredWidth: implicitWidth
Layout.alignment: Qt.AlignVCenter
}
}
onClicked: {
model.enabled = true;
if (model.suggested != null) {
model.value = model.suggested;
model.suggested = null;
}
}
}
}
onSheetOpenChanged: {
searchField.text = "";
if (sheetOpen) {
searchField.forceActiveFocus();
} else {
overlayModel.onlySuggestions = false;
}
}
}
function formatValue(value, type, options) {
if (value == null) {
return "";
}
switch (type) {
case RuleItem.Boolean:
return value ? i18n("Yes") : i18n("No");
case RuleItem.Percentage:
return i18n("%1 %", value);
case RuleItem.Coordinate:
var point = value.split(',');
return i18nc("Coordinates (x, y)", "(%1, %2)", point[0], point[1]);
case RuleItem.Option:
return options.textOfValue(value);
case RuleItem.FlagsOption:
var selectedValue = value.toString(2).length - 1;
return options.textOfValue(selectedValue);
}
return value;
}
KSortFilterProxyModel {
id: enabledRulesModel
sourceModel: rulesModel
filterRowCallback: (source_row, source_parent) => {
var index = sourceModel.index(source_row, 0, source_parent);
return sourceModel.data(index, RulesModel.EnabledRole);
}
}
KSortFilterProxyModel {
id: overlayModel
sourceModel: rulesModel
property bool onlySuggestions: false
onOnlySuggestionsChanged: {
invalidateFilter();
}
filterString: searchField.text.trim().toLowerCase()
filterRowCallback: (source_row, source_parent) => {
var index = sourceModel.index(source_row, 0, source_parent);
var hasSuggestion = sourceModel.data(index, RulesModel.SuggestedValueRole) != null;
var isOptional = sourceModel.data(index, RulesModel.SelectableRole);
var isEnabled = sourceModel.data(index, RulesModel.EnabledRole);
var showItem = hasSuggestion || (!onlySuggestions && isOptional && !isEnabled);
if (!showItem) {
return false;
}
if (filterString != "") {
return sourceModel.data(index, RulesModel.NameRole).toLowerCase().includes(filterString)
}
return true;
}
}
}

View File

@ -0,0 +1,252 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14 as QQC2
import QtQml.Models 2.14
import org.kde.kcm 1.2
import org.kde.kirigami 2.5 as Kirigami
ScrollViewKCM {
id: rulesListKCM
// FIXME: ScrollViewKCM.qml:73:13: QML Control: Binding loop detected for property "implicitHeight"
implicitWidth: Kirigami.Units.gridUnit * 35
implicitHeight: Kirigami.Units.gridUnit * 25
ConfigModule.columnWidth: Kirigami.Units.gridUnit * 23
ConfigModule.buttons: ConfigModule.Apply
property var selectedIndexes: []
// Manage KCM pages
Connections {
target: kcm
onEditIndexChanged: {
if (kcm.editIndex < 0) {
// If no rule is being edited, hide RulesEdidor page
kcm.pop();
} else if (kcm.depth < 2) {
// Add the RulesEditor page if it wasn't already
kcm.push("RulesEditor.qml");
}
}
}
view: ListView {
id: ruleBookView
clip: true
model: kcm.ruleBookModel
currentIndex: kcm.editIndex
delegate: Kirigami.DelegateRecycler {
width: ruleBookView.width
sourceComponent: ruleBookDelegate
}
displaced: Transition {
NumberAnimation { properties: "y"; duration: Kirigami.Units.longDuration }
}
Kirigami.Heading {
level: 3
enabled: false
visible: ruleBookView.count == 0
anchors.fill: parent
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
wrapMode: Text.WordWrap
text: i18n("No rules for specific windows are currently set");
}
}
header: Kirigami.InlineMessage {
id: exportInfo
icon.source: "document-export"
showCloseButton: true
text: i18n("Select the rules to export")
actions: [
Kirigami.Action {
iconName: "document-save"
text: i18n("Save Rules")
// FIXME: It does not update on selection changes
// enabled: selectedIndexes.length > 0
onTriggered: {
exportDialog.active = true;
}
}
]
}
footer: RowLayout {
QQC2.Button {
text: i18n("Add New...")
icon.name: "list-add-symbolic"
enabled: !exportInfo.visible
onClicked: {
kcm.createRule();
}
}
Item {
Layout.fillWidth: true
}
QQC2.Button {
text: i18n("Import...")
icon.name: "document-import"
enabled: !exportInfo.visible
onClicked: {
importDialog.active = true;
}
}
QQC2.Button {
text: i18n("Export...")
icon.name: "document-export"
enabled: ruleBookView.count > 0
checkable: true
checked: exportInfo.visible
onToggled: {
selectedIndexes = []
exportInfo.visible = checked;
}
}
}
Component {
id: ruleBookDelegate
Kirigami.AbstractListItem {
id: ruleBookItem
RowLayout {
//FIXME: If not used within DelegateRecycler, item goes on top of the first item when clicked
Kirigami.ListItemDragHandle {
visible: !exportInfo.visible
listItem: ruleBookItem
listView: ruleBookView
onMoveRequested: {
kcm.moveRule(oldIndex, newIndex);
}
}
QQC2.TextField {
id: descriptionField
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
Layout.fillWidth: true
background: Item {}
horizontalAlignment: Text.AlignLeft
text: display
onEditingFinished: {
kcm.setRuleDescription(index, text);
}
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Escape:
// On <Esc> key reset to model data before losing focus
text = display;
case Qt.Key_Enter:
case Qt.Key_Return:
case Qt.Key_Tab:
ruleBookItem.forceActiveFocus();
event.accepted = true;
break;
}
}
MouseArea {
anchors.fill: parent
enabled: exportInfo.visible
cursorShape: enabled ? Qt.PointingHandCursor : Qt.IBeamCursor
onClicked: {
itemSelectionCheck.toggle();
itemSelectionCheck.toggled();
}
}
}
Kirigami.ActionToolBar {
Layout.preferredWidth: maximumContentWidth + Kirigami.Units.smallSpacing
alignment: Qt.AlignRight
display: QQC2.Button.IconOnly
visible: !exportInfo.visible
opacity: ruleBookItem.hovered ? 1 : 0
focus: false
actions: [
Kirigami.Action {
text: i18n("Edit")
iconName: "edit-entry"
onTriggered: {
kcm.editRule(index);
}
}
,
Kirigami.Action {
text: i18n("Delete")
iconName: "entry-delete"
onTriggered: {
kcm.removeRule(index);
}
}
]
}
QQC2.CheckBox {
id: itemSelectionCheck
visible: exportInfo.visible
checked: selectedIndexes.includes(index)
onToggled: {
var position = selectedIndexes.indexOf(index);
if (checked) {
if (position < 0) { selectedIndexes.push(index); }
} else {
if (position >= 0) { selectedIndexes.splice(position, 1); }
}
}
}
}
}
}
FileDialogLoader {
id: importDialog
title: i18n("Import Rules")
isSaveDialog: false
onLastFolderChanged: {
exportDialog.lastFolder = lastFolder;
}
onFileSelected: {
kcm.importFromFile(path);
}
}
FileDialogLoader {
id: exportDialog
title: i18n("Export Rules")
isSaveDialog: true
onLastFolderChanged: {
importDialog.lastFolder = lastFolder;
}
onFileSelected: {
selectedIndexes.sort();
kcm.exportToFile(path, selectedIndexes);
exportInfo.visible = false;
}
}
}

View File

@ -0,0 +1,200 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
import QtQuick 2.14
import QtQuick.Layouts 1.14
import QtQuick.Controls 2.14 as QQC2
import org.kde.kirigami 2.10 as Kirigami
import org.kde.kquickcontrols 2.0 as KQC
import org.kde.kcms.kwinrules 1.0
Loader {
id: valueEditor
focus: true
property var ruleValue
property var ruleOptions
property int controlType
signal valueEdited(var value)
sourceComponent: {
switch (controlType) {
case RuleItem.Boolean: return booleanEditor
case RuleItem.String: return stringEditor
case RuleItem.Integer: return integerEditor
case RuleItem.Option: return optionEditor
case RuleItem.FlagsOption: return flagsEditor
case RuleItem.Percentage: return percentageEditor
case RuleItem.Coordinate: return coordinateEditor
case RuleItem.Shortcut: return shortcutEditor
default: return emptyEditor
}
}
Component {
id: emptyEditor
Item {}
}
Component {
id: booleanEditor
RowLayout {
Item {
Layout.fillWidth: true
}
QQC2.RadioButton {
text: i18n("Yes")
checked: ruleValue
Layout.margins: Kirigami.Units.smallSpacing
onToggled: valueEditor.valueEdited(checked)
}
QQC2.RadioButton {
text: i18n("No")
checked: !ruleValue
Layout.margins: Kirigami.Units.smallSpacing
onToggled: valueEditor.valueEdited(!checked)
}
}
}
Component {
id: stringEditor
QQC2.TextField {
property bool isTextEdited: false
text: ruleValue
horizontalAlignment: Text.AlignLeft
onTextEdited: { isTextEdited = true; }
onEditingFinished: {
if (isTextEdited) { valueEditor.valueEdited(text); }
isTextEdited = false;
}
}
}
Component {
id: integerEditor
QQC2.SpinBox {
editable: true
value: ruleValue
onValueModified: valueEditor.valueEdited(value)
}
}
Component {
id: optionEditor
OptionsComboBox {
flat: true
model: ruleOptions
onActivated: (index) => {
valueEditor.valueEdited(currentValue);
}
}
}
Component {
id: flagsEditor
OptionsComboBox {
flat: true
model: ruleOptions
multipleChoice: true
selectionMask: ruleValue
onActivated: {
valueEditor.valueEdited(selectionMask);
}
}
}
Component {
id: percentageEditor
RowLayout {
QQC2.Slider {
id: slider
Layout.fillWidth: true
from: 0
to: 100
value: ruleValue
onMoved: valueEditor.valueEdited(Math.round(slider.value))
}
QQC2.Label {
text: i18n("%1 %", Math.round(slider.value))
horizontalAlignment: Qt.AlignRight
Layout.minimumWidth: maxPercentage.width + Kirigami.Units.smallSpacing
Layout.margins: Kirigami.Units.smallSpacing
}
TextMetrics {
id: maxPercentage
text: i18n("%1 %", 100)
}
}
}
Component {
id: coordinateEditor
RowLayout {
id: coordItem
spacing: Kirigami.Units.smallSpacing
property var coords: ruleValue ? ruleValue.split(',') : [0, 0]
QQC2.SpinBox {
id: coordX
editable: true
Layout.preferredWidth: 50 // 50%
Layout.fillWidth: true
from: 0
to: 4098
value: coords[0]
onValueModified: valueEditor.valueEdited(coordX.value + "," + coordY.value)
}
QQC2.Label {
id: coordSeparator
Layout.preferredWidth: implicitWidth
text: i18nc("(x, y) coordinates separator in size/position","x")
horizontalAlignment: Text.AlignHCenter
}
QQC2.SpinBox {
id: coordY
editable: true
from: 0
to: 4098
Layout.preferredWidth: 50 // 50%
Layout.fillWidth: true
value: coords[1]
onValueModified: valueEditor.valueEdited(coordX.value + "," + coordY.value)
}
}
}
Component {
id: shortcutEditor
RowLayout {
Item {
Layout.fillWidth: true
}
KQC.KeySequenceItem {
keySequence: ruleValue
onCaptureFinished: valueEditor.valueEdited(keySequence)
}
}
}
}

View File

@ -0,0 +1,19 @@
[Desktop Entry]
Icon=preferences-system-windows-actions
Type=Service
Keywords=
X-KDE-ParentApp=
X-KDE-System-Settings-Parent-Category=applicationstyle
X-KDE-PluginInfo-Author=Ismael Asensio
X-KDE-PluginInfo-Email=isma.af@gmail.com
X-KDE-PluginInfo-License=GPL-2.0+
X-KDE-PluginInfo-Name=kcm_kwinrules
X-KDE-PluginInfo-Version=
X-KDE-PluginInfo-Website=https://www.kde.org/plasma-desktop
X-KDE-ServiceTypes=Plasma/Generic
X-Plasma-API=declarativeappletscript
X-Plasma-MainScript=ui/RulesList.qml
X-KDE-FormFactors=desktop,tablet
Name=Window Rules
Comment=Individual Window Behavior

View File

@ -0,0 +1,187 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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 "rulebookmodel.h"
namespace KWin
{
RuleBookModel::RuleBookModel(QObject *parent)
: QAbstractListModel(parent)
, m_ruleBook(new RuleBookSettings(this))
{
}
RuleBookModel::~RuleBookModel()
{
qDeleteAll(m_rules);
}
int RuleBookModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_rules.count();
}
QVariant RuleBookModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
return descriptionAt(index.row());
default:
return QVariant();
}
}
bool RuleBookModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
return false;
}
switch (role) {
case Qt::DisplayRole:
setDescriptionAt(index.row(), value.toString());
return true;
default:
return false;
}
}
bool RuleBookModel::insertRows(int row, int count, const QModelIndex &parent)
{
if (row < 0 || row > rowCount() || parent.isValid()) {
return false;
}
beginInsertRows(parent, row, row + count - 1);
for (int i = 0; i < count; i++) {
Rules *newRule = new Rules();
// HACK: Improve integration with RuleSettings and use directly its defaults
newRule->wmclassmatch = Rules::ExactMatch;
m_rules.insert(row + i, newRule);
}
m_ruleBook->setCount(m_rules.count());
endInsertRows();
return true;
}
bool RuleBookModel::removeRows(int row, int count, const QModelIndex &parent)
{
if (row < 0 || row > rowCount() || parent.isValid()) {
return false;
}
beginRemoveRows(parent, row, row + count - 1);
for (int i = 0; i < count; i++) {
delete m_rules.at(row + i);
}
m_rules.remove(row, count);
m_ruleBook->setCount(m_rules.count());
endRemoveRows();
return true;
}
bool RuleBookModel::moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
const QModelIndex &destinationParent, int destinationChild)
{
if (sourceParent != destinationParent || sourceParent != QModelIndex()){
return false;
}
const bool isMoveDown = destinationChild > sourceRow;
// QAbstractItemModel::beginMoveRows(): when moving rows down in the same parent,
// the rows will be placed before the destinationChild index.
if (!beginMoveRows(sourceParent, sourceRow, sourceRow + count - 1,
destinationParent, isMoveDown ? destinationChild + 1 : destinationChild)) {
return false;
}
for (int i = 0; i < count; i++) {
m_rules.insert(destinationChild + i,
m_rules.takeAt(isMoveDown ? sourceRow : sourceRow + i));
}
endMoveRows();
return true;
}
QString RuleBookModel::descriptionAt(int row) const
{
Q_ASSERT (row >= 0 && row < m_rules.count());
return m_rules.at(row)->description;
}
Rules *RuleBookModel::ruleAt(int row) const
{
Q_ASSERT (row >= 0 && row < m_rules.count());
return m_rules.at(row);
}
void RuleBookModel::setDescriptionAt(int row, const QString &description)
{
Q_ASSERT (row >= 0 && row < m_rules.count());
if (description == m_rules.at(row)->description) {
return;
}
m_rules.at(row)->description = description;
emit dataChanged(index(row), index(row), QVector<int>{Qt::DisplayRole});
}
void RuleBookModel::setRuleAt(int row, Rules *rule)
{
Q_ASSERT (row >= 0 && row < m_rules.count());
delete m_rules.at(row);
m_rules[row] = rule;
emit dataChanged(index(row), index(row), QVector<int>{Qt::DisplayRole});
}
void RuleBookModel::load()
{
beginResetModel();
m_ruleBook->load();
qDeleteAll(m_rules);
m_rules = m_ruleBook->rules();
endResetModel();
}
void RuleBookModel::save()
{
m_ruleBook->setRules(m_rules);
m_ruleBook->save();
}
} // namespace

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
#pragma once
#include "rulebooksettings.h"
#include <rules.h>
#include <QAbstractListModel>
namespace KWin
{
class RuleBookModel : public QAbstractListModel
{
Q_OBJECT
public:
explicit RuleBookModel(QObject *parent = nullptr);
~RuleBookModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count,
const QModelIndex &destinationParent, int destinationChild) override;
QString descriptionAt(int row) const;
void setDescriptionAt(int row, const QString &description);
Rules *ruleAt(int row) const;
void setRuleAt(int row, Rules *rule);
void load();
void save();
private:
RuleBookSettings *m_ruleBook;
QVector<Rules *> m_rules;
};
} // namespace

View File

@ -0,0 +1,225 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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 "ruleitem.h"
namespace KWin
{
RuleItem::RuleItem(const QString &key,
const RulePolicy::Type policyType,
const RuleItem::Type type,
const QString &name,
const QString &section,
const QIcon &icon,
const QString &description)
: m_key(key)
, m_type(type)
, m_name(name)
, m_section(section)
, m_icon(icon)
, m_description(description)
, m_flags(NoFlags)
, m_enabled(false)
, m_policy(new RulePolicy(policyType))
, m_options(nullptr)
{
reset();
}
RuleItem::~RuleItem()
{
delete m_policy;
delete m_options;
}
void RuleItem::reset()
{
m_enabled = hasFlag(AlwaysEnabled) | hasFlag(StartEnabled);
m_value = typedValue(QVariant(), m_type);
m_suggestedValue = QVariant();
m_policy->resetValue();
if (m_options) {
m_options->resetValue();
}
}
QString RuleItem::key() const
{
return m_key;
}
QString RuleItem::name() const
{
return m_name;
}
QString RuleItem::section() const
{
return m_section;
}
QString RuleItem::iconName() const
{
return m_icon.name();
}
QIcon RuleItem::icon() const
{
return m_icon;
}
QString RuleItem::description() const
{
return m_description;
}
bool RuleItem::isEnabled() const
{
return m_enabled;
}
void RuleItem::setEnabled(bool enabled)
{
m_enabled = enabled | hasFlag(AlwaysEnabled);
}
bool RuleItem::hasFlag(RuleItem::Flags flag) const
{
return m_flags.testFlag(flag);
}
void RuleItem::setFlag(RuleItem::Flags flag, bool active)
{
m_flags.setFlag(flag, active);
}
RuleItem::Type RuleItem::type() const
{
return m_type;
}
QVariant RuleItem::value() const
{
if (m_type == Option) {
return m_options->value();
}
return m_value;
}
void RuleItem::setValue(QVariant value)
{
if (m_type == Option) {
m_options->setValue(value);
}
m_value = typedValue(value, m_type);
}
QVariant RuleItem::suggestedValue() const
{
return m_suggestedValue;
}
void RuleItem::setSuggestedValue(QVariant value, bool forceValue)
{
if (forceValue) {
setValue(value);
}
m_suggestedValue = value.isNull() ? QVariant() : typedValue(value, m_type);
}
QVariant RuleItem::options() const
{
if (!m_options) {
return QVariant();
}
return QVariant::fromValue(m_options);
}
void RuleItem::setOptionsData(const QList<OptionsModel::Data> &data)
{
if (!m_options) {
if (m_type != Option && m_type != FlagsOption) {
return;
}
m_options = new OptionsModel();
}
m_options->updateModelData(data);
m_options->setValue(m_value);
}
int RuleItem::policy() const
{
return m_policy->value();
}
void RuleItem::setPolicy(int policy)
{
m_policy->setValue(policy);
}
RulePolicy::Type RuleItem::policyType() const
{
return m_policy->type();
}
QVariant RuleItem::policyModel() const
{
return QVariant::fromValue(m_policy);
}
QString RuleItem::policyKey() const
{
return m_policy->policyKey(m_key);
}
QVariant RuleItem::typedValue(const QVariant &value, const RuleItem::Type type)
{
switch (type) {
case Undefined:
case Option:
return value;
case Boolean:
return value.toBool();
case Integer:
case Percentage:
return value.toInt();
case FlagsOption:
// HACK: Currently, the only user of this is "types" property
if (value.toInt() == -1) { //NET:AllTypesMask
return 0x3FF - 0x040; //All possible flags minus NET::Override (deprecated)
}
return value.toInt();
case Coordinate:
if (value.toString().isEmpty()) {
return QStringLiteral("0,0");
}
return value.toString();
case String:
return value.toString().trimmed();
case Shortcut:
return value.toString();
}
return value;
}
} //namespace

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
#ifndef KWIN_RULEITEM_H
#define KWIN_RULEITEM_H
#include "optionsmodel.h"
#include <QFlag>
#include <QIcon>
namespace KWin
{
class RuleItem : public QObject
{
Q_OBJECT
public:
enum Type {
Undefined,
Boolean,
String,
Integer,
Option,
FlagsOption,
Percentage,
Coordinate,
Shortcut
};
Q_ENUM(Type)
enum Flags {
NoFlags = 0,
AlwaysEnabled = 1u << 0,
StartEnabled = 1u << 1,
AffectsWarning = 1u << 2,
AffectsDescription = 1u << 3,
AllFlags = 0b1111
};
public:
RuleItem() {};
RuleItem(const QString &key,
const RulePolicy::Type policyType,
const Type type,
const QString &name,
const QString &section,
const QIcon &icon = QIcon::fromTheme("window"),
const QString &description = QString("")
);
~RuleItem();
QString key() const;
QString name() const;
QString section() const;
QIcon icon() const;
QString iconName() const;
QString description() const;
bool isEnabled() const;
void setEnabled(bool enabled);
bool hasFlag(RuleItem::Flags flag) const;
void setFlag(RuleItem::Flags flag, bool active=true);
Type type() const;
QVariant value() const;
void setValue(QVariant value);
QVariant suggestedValue() const;
void setSuggestedValue(QVariant value, bool forceValue = false);
QVariant options() const;
void setOptionsData(const QList<OptionsModel::Data> &data);
RulePolicy::Type policyType() const;
int policy() const; // int belongs to anonymous enum in Rules::
void setPolicy(int policy); // int belongs to anonymous enum in Rules::
QVariant policyModel() const;
QString policyKey() const;
void reset();
private:
static QVariant typedValue(const QVariant &value, const Type type);
private:
QString m_key;
RuleItem::Type m_type;
QString m_name;
QString m_section;
QIcon m_icon;
QString m_description;
QFlags<Flags> m_flags;
bool m_enabled;
QVariant m_value;
QVariant m_suggestedValue;
RulePolicy *m_policy;
OptionsModel *m_options;
};
} //namespace
#endif //KWIN_RULEITEM_H

View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@kde.org>
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "rulesdialog.h"
#include <QDialogButtonBox>
#include <QQuickItem>
#include <QQuickView>
#include <QLayout>
#include <QPushButton>
#include <KLocalizedString>
namespace KWin
{
RulesDialog::RulesDialog(QWidget* parent, const char* name)
: QDialog(parent)
, m_rulesModel(new RulesModel(this))
{
setObjectName(name);
setModal(true);
setWindowTitle(i18n("Edit Window-Specific Settings"));
setWindowIcon(QIcon::fromTheme("preferences-system-windows-actions"));
setLayout(new QVBoxLayout);
// Init RuleEditor QML QuickView
QQuickView *quickView = new QQuickView();
quickView->setSource(QUrl::fromLocalFile(QStandardPaths::locate(
QStandardPaths::GenericDataLocation,
QStringLiteral("kpackage/kcms/kcm_kwinrules/contents/ui/RulesEditor.qml"))));
quickView->setResizeMode(QQuickView::SizeRootObjectToView);
quickView->rootObject()->setProperty("rulesModel", QVariant::fromValue(m_rulesModel));
m_quickWidget = QWidget::createWindowContainer(quickView, this);
m_quickWidget->setMinimumSize(QSize(650, 575));
layout()->addWidget(m_quickWidget);
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout()->addWidget(buttons);
}
// window is set only for Alt+F3/Window-specific settings, because the dialog
// is then related to one specific window
Rules* RulesDialog::edit(Rules* r, const QVariantMap& info, bool show_hints)
{
Q_UNUSED(show_hints);
m_rules = r;
m_rulesModel->importFromRules(m_rules);
if (!info.isEmpty()) {
m_rulesModel->setWindowProperties(info, true);
}
exec();
return m_rules;
}
void RulesDialog::accept()
{
m_rules = m_rulesModel->exportToRules();
QDialog::accept();
}
}

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@kde.org>
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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
@ -16,35 +17,37 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KWIN_RULESDIALOG_H
#define KWIN_RULESDIALOG_H
#ifndef __KCM_H__
#define __KCM_H__
#include "rulesmodel.h"
#include "../../rules.h"
#include <kcmodule.h>
#include <kconfig.h>
class KConfig;
#include <QDialog>
namespace KWin
{
class KCMRulesList;
class Rules;
class KCMRules
: public KCModule
class RulesDialog : public QDialog
{
Q_OBJECT
public:
KCMRules(QWidget *parent, const QVariantList &args);
void load() override;
void save() override;
QString quickHelp() const override;
protected Q_SLOTS:
void moduleChanged(bool state);
explicit RulesDialog(QWidget* parent = nullptr, const char* name = nullptr);
Rules* edit(Rules* r, const QVariantMap& info, bool show_hints);
protected:
void accept() override;
private:
KCMRulesList* widget;
RulesModel* m_rulesModel;
QWidget *m_quickWidget;
Rules* m_rules;
};
} // namespace
#endif
#endif // KWIN_RULESDIALOG_H

View File

@ -1,233 +0,0 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ruleslist.h"
#include <QDebug>
#include <kconfig.h>
#include <QFileDialog>
#include "../../rules.h"
#include "rulesettings.h"
#include "ruleswidget.h"
namespace KWin
{
KCMRulesList::KCMRulesList(QWidget* parent)
: QWidget(parent)
{
setupUi(this);
// connect both current/selected, so that current==selected (stupid QListBox :( )
connect(rules_listbox, SIGNAL(itemChanged(QListWidgetItem*)),
SLOT(activeChanged()));
connect(rules_listbox, SIGNAL(itemSelectionChanged()),
SLOT(activeChanged()));
connect(new_button, SIGNAL(clicked()),
SLOT(newClicked()));
connect(modify_button, SIGNAL(clicked()),
SLOT(modifyClicked()));
connect(delete_button, SIGNAL(clicked()),
SLOT(deleteClicked()));
connect(moveup_button, SIGNAL(clicked()),
SLOT(moveupClicked()));
connect(movedown_button, SIGNAL(clicked()),
SLOT(movedownClicked()));
connect(export_button, SIGNAL(clicked()),
SLOT(exportClicked()));
connect(import_button, SIGNAL(clicked()),
SLOT(importClicked()));
connect(rules_listbox, SIGNAL(itemDoubleClicked(QListWidgetItem*)),
SLOT(modifyClicked()));
load();
}
KCMRulesList::~KCMRulesList()
{
qDeleteAll(m_rules);
m_rules.clear();
}
void KCMRulesList::activeChanged()
{
QListWidgetItem *item = rules_listbox->currentItem();
int itemRow = rules_listbox->row(item);
if (item != nullptr) // make current==selected
rules_listbox->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
modify_button->setEnabled(item != nullptr);
delete_button->setEnabled(item != nullptr);
export_button->setEnabled(item != nullptr);
moveup_button->setEnabled(item != nullptr && itemRow > 0);
movedown_button->setEnabled(item != nullptr && itemRow < (rules_listbox->count() - 1));
}
void KCMRulesList::newClicked()
{
RulesDialog dlg(this);
Rules* rule = dlg.edit(nullptr, {}, false);
if (rule == nullptr)
return;
int pos = rules_listbox->currentRow() + 1;
rules_listbox->insertItem(pos , rule->description);
rules_listbox->setCurrentRow(pos, QItemSelectionModel::ClearAndSelect);
m_rules.insert(m_rules.begin() + pos, rule);
emit changed(true);
}
void KCMRulesList::modifyClicked()
{
int pos = rules_listbox->currentRow();
if (pos == -1)
return;
RulesDialog dlg(this);
Rules *rule = dlg.edit(m_rules[pos], {}, false);
if (rule == m_rules[pos])
return;
delete m_rules[pos];
m_rules[pos] = rule;
rules_listbox->item(pos)->setText(rule->description);
emit changed(true);
}
void KCMRulesList::deleteClicked()
{
int pos = rules_listbox->currentRow();
Q_ASSERT(pos != -1);
delete rules_listbox->takeItem(pos);
m_rules.erase(m_rules.begin() + pos);
emit changed(true);
}
void KCMRulesList::moveupClicked()
{
int pos = rules_listbox->currentRow();
Q_ASSERT(pos != -1);
if (pos > 0) {
QListWidgetItem * item = rules_listbox->takeItem(pos);
rules_listbox->insertItem(pos - 1 , item);
rules_listbox->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
Rules *rule = m_rules[pos];
m_rules[pos] = m_rules[pos - 1];
m_rules[pos - 1] = rule;
}
emit changed(true);
}
void KCMRulesList::movedownClicked()
{
int pos = rules_listbox->currentRow();
Q_ASSERT(pos != -1);
if (pos < int(rules_listbox->count()) - 1) {
QListWidgetItem * item = rules_listbox->takeItem(pos);
rules_listbox->insertItem(pos + 1 , item);
rules_listbox->setCurrentItem(item, QItemSelectionModel::ClearAndSelect);
Rules *rule = m_rules[pos];
m_rules[pos] = m_rules[pos + 1];
m_rules[pos + 1] = rule;
}
emit changed(true);
}
void KCMRulesList::exportClicked()
{
int pos = rules_listbox->currentRow();
Q_ASSERT(pos != -1);
QString path = QFileDialog::getSaveFileName(this, i18n("Export Rules"), QDir::home().absolutePath(),
i18n("KWin Rules (*.kwinrule)"));
if (path.isEmpty())
return;
const auto cfg = KSharedConfig::openConfig(path, KConfig::SimpleConfig);
RuleSettings settings(cfg, m_rules[pos]->description);
settings.setDefaults();
m_rules[pos]->write(&settings);
settings.save();
}
void KCMRulesList::importClicked()
{
QString path = QFileDialog::getOpenFileName(this, i18n("Import Rules"), QDir::home().absolutePath(),
i18n("KWin Rules (*.kwinrule)"));
if (path.isEmpty())
return;
const auto config = KSharedConfig::openConfig(path, KConfig::SimpleConfig);
QStringList groups = config->groupList();
if (groups.isEmpty())
return;
int pos = qMax(0, rules_listbox->currentRow());
for (const QString &group : groups) {
RuleSettings settings(config, group);
const bool remove = settings.deleteRule();
Rules *new_rule = new Rules(&settings);
// try to replace existing rule first
for (int i = 0; i < m_rules.count(); ++i) {
if (m_rules[i]->description == new_rule->description) {
delete m_rules[i];
if (remove) {
m_rules.remove(i);
delete rules_listbox->takeItem(i);
delete new_rule;
pos = qMax(0, rules_listbox->currentRow()); // might have changed!
} else
m_rules[i] = new_rule;
new_rule = nullptr;
break;
}
}
// don't add "to be deleted" if not present
if (remove) {
delete new_rule;
new_rule = nullptr;
}
// plain insertion
if (new_rule) {
m_rules.insert(pos, new_rule);
rules_listbox->insertItem(pos++, new_rule->description);
}
}
emit changed(true);
}
void KCMRulesList::load()
{
rules_listbox->clear();
m_settings.load();
m_rules = m_settings.rules();
for (const auto rule : qAsConst(m_rules)) {
rules_listbox->addItem(rule->description);
}
if (m_rules.count() > 0)
rules_listbox->setCurrentItem(rules_listbox->item(0));
else
rules_listbox->setCurrentItem(nullptr);
activeChanged();
}
void KCMRulesList::save()
{
m_settings.setRules(m_rules);
m_settings.save();
}
} // namespace

View File

@ -1,57 +0,0 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __RULESLIST_H__
#define __RULESLIST_H__
#include "ui_ruleslist.h"
#include "rulebooksettings.h"
namespace KWin
{
class Rules;
class KCMRulesList
: public QWidget, Ui_KCMRulesList
{
Q_OBJECT
public:
explicit KCMRulesList(QWidget* parent = nullptr);
~KCMRulesList() override;
void load();
void save();
Q_SIGNALS:
void changed(bool);
private Q_SLOTS:
void newClicked();
void modifyClicked();
void deleteClicked();
void moveupClicked();
void movedownClicked();
void exportClicked();
void importClicked();
void activeChanged();
private:
QVector<Rules *> m_rules;
RuleBookSettings m_settings;
};
} // namespace
#endif

View File

@ -1,122 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>KWin::KCMRulesList</class>
<widget class="QWidget" name="KWin::KCMRulesList">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>480</height>
</rect>
</property>
<layout class="QGridLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0" rowspan="14">
<widget class="QListWidget" name="rules_listbox"/>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="new_button">
<property name="text">
<string>&amp;New...</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QPushButton" name="modify_button">
<property name="text">
<string>&amp;Modify...</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QPushButton" name="delete_button">
<property name="text">
<string>Delete</string>
</property>
<property name="shortcut">
<string/>
</property>
</widget>
</item>
<item row="8" column="1">
<widget class="QPushButton" name="moveup_button">
<property name="text">
<string>Move &amp;Up</string>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QPushButton" name="movedown_button">
<property name="text">
<string>Move &amp;Down</string>
</property>
</widget>
</item>
<item row="13" column="1">
<spacer name="spacer1">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Expanding</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>294</height>
</size>
</property>
</spacer>
</item>
<item row="5" column="1">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="import_button">
<property name="text">
<string>&amp;Import</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QPushButton" name="export_button">
<property name="text">
<string>&amp;Export</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,821 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@kde.org>
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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 "rulesmodel.h"
#include <rules.h>
#include <QFileInfo>
#include <QIcon>
#include <QQmlEngine>
#include <QTemporaryFile>
#include <QtDBus>
#include <KColorSchemeManager>
#include <KConfig>
#include <KLocalizedString>
#include <KWindowSystem>
namespace KWin
{
RulesModel::RulesModel(QObject *parent)
: QAbstractListModel(parent)
{
qmlRegisterUncreatableType<RuleItem>("org.kde.kcms.kwinrules", 1, 0, "RuleItem",
QStringLiteral("Do not create objects of type RuleItem"));
qmlRegisterUncreatableType<RulesModel>("org.kde.kcms.kwinrules", 1, 0, "RulesModel",
QStringLiteral("Do not create objects of type RulesModel"));
populateRuleList();
}
RulesModel::~RulesModel()
{
}
QHash< int, QByteArray > RulesModel::roleNames() const
{
return {
{KeyRole, QByteArrayLiteral("key")},
{NameRole, QByteArrayLiteral("name")},
{IconRole, QByteArrayLiteral("icon")},
{IconNameRole, QByteArrayLiteral("iconName")},
{SectionRole, QByteArrayLiteral("section")},
{DescriptionRole, QByteArrayLiteral("description")},
{EnabledRole, QByteArrayLiteral("enabled")},
{SelectableRole, QByteArrayLiteral("selectable")},
{ValueRole, QByteArrayLiteral("value")},
{TypeRole, QByteArrayLiteral("type")},
{PolicyRole, QByteArrayLiteral("policy")},
{PolicyModelRole, QByteArrayLiteral("policyModel")},
{OptionsModelRole, QByteArrayLiteral("options")},
{SuggestedValueRole, QByteArrayLiteral("suggested")},
};
}
int RulesModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_ruleList.size();
}
QVariant RulesModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
return QVariant();
}
const RuleItem *rule = m_ruleList.at(index.row());
switch (role) {
case KeyRole:
return rule->key();
case NameRole:
return rule->name();
case IconRole:
return rule->icon();
case IconNameRole:
return rule->iconName();
case DescriptionRole:
return rule->description();
case SectionRole:
return rule->section();
case EnabledRole:
return rule->isEnabled();
case SelectableRole:
return !rule->hasFlag(RuleItem::AlwaysEnabled);
case ValueRole:
return rule->value();
case TypeRole:
return rule->type();
case PolicyRole:
return rule->policy();
case PolicyModelRole:
return rule->policyModel();
case OptionsModelRole:
return rule->options();
case SuggestedValueRole:
return rule->suggestedValue();
}
return QVariant();
}
bool RulesModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid | CheckIndexOption::ParentIsInvalid)) {
return false;
}
RuleItem *rule = m_ruleList.at(index.row());
switch (role) {
case EnabledRole:
if (value.toBool() == rule->isEnabled()) {
return true;
}
rule->setEnabled(value.toBool());
break;
case ValueRole:
if (value == rule->value()) {
return true;
}
rule->setValue(value);
break;
case PolicyRole:
if (value.toInt() == rule->policy()) {
return true;
}
rule->setPolicy(value.toInt());
break;
case SuggestedValueRole:
if (value == rule->suggestedValue()) {
return true;
}
rule->setSuggestedValue(value);
break;
default:
return false;
}
emit dataChanged(index, index, QVector<int>{role});
if (rule->hasFlag(RuleItem::AffectsDescription)) {
emit descriptionChanged();
}
if (rule->hasFlag(RuleItem::AffectsWarning)) {
emit warningMessageChanged();
}
return true;
}
RuleItem *RulesModel::addRule(RuleItem *rule)
{
m_ruleList << rule;
m_rules.insert(rule->key(), rule);
return rule;
}
bool RulesModel::hasRule(const QString& key) const
{
return m_rules.contains(key);
}
RuleItem *RulesModel::ruleItem(const QString& key) const
{
return m_rules.value(key);
}
QString RulesModel::description() const
{
const QString desc = m_rules["description"]->value().toString();
if (!desc.isEmpty()) {
return desc;
}
return defaultDescription();
}
void RulesModel::setDescription(const QString &description)
{
setData(index(0, 0), description, RulesModel::ValueRole);
}
QString RulesModel::defaultDescription() const
{
const QString wmclass = m_rules["wmclass"]->value().toString();
const QString title = m_rules["title"]->isEnabled() ? m_rules["title"]->value().toString() : QString();
if (!title.isEmpty()) {
return i18n("Window settings for %1", title);
}
if (!wmclass.isEmpty()) {
return i18n("Settings for %1", wmclass);
}
return i18n("New window settings");
}
QString RulesModel::warningMessage() const
{
if (wmclassWarning()) {
return i18n("You have specified the window class as unimportant.\n"
"This means the settings will possibly apply to windows from all applications."
" If you really want to create a generic setting, it is recommended"
" you at least limit the window types to avoid special window types.");
}
return QString();
}
bool RulesModel::wmclassWarning() const
{
const bool no_wmclass = !m_rules["wmclass"]->isEnabled()
|| m_rules["wmclass"]->policy() == Rules::UnimportantMatch;
const bool alltypes = !m_rules["types"]->isEnabled()
|| (m_rules["types"]->value() == 0)
|| (m_rules["types"]->value() == NET::AllTypesMask)
|| ((m_rules["types"]->value().toInt() | (1 << NET::Override)) == 0x3FF);
return (no_wmclass && alltypes);
}
void RulesModel::readFromSettings(RuleSettings *settings)
{
beginResetModel();
for (RuleItem *rule : qAsConst(m_ruleList)) {
const KConfigSkeletonItem *configItem = settings->findItem(rule->key());
const KConfigSkeletonItem *configPolicyItem = settings->findItem(rule->policyKey());
rule->reset();
if (!configItem) {
continue;
}
const bool isEnabled = configPolicyItem ? configPolicyItem->property() != Rules::Unused
: !configItem->property().toString().isEmpty();
rule->setEnabled(isEnabled);
const QVariant value = configItem->property();
rule->setValue(value);
if (configPolicyItem) {
const int policy = configPolicyItem->property().toInt();
rule->setPolicy(policy);
}
}
endResetModel();
emit descriptionChanged();
emit warningMessageChanged();
}
void RulesModel::writeToSettings(RuleSettings *settings) const
{
const QString description = m_rules["description"]->value().toString();
if (description.isEmpty()) {
m_rules["description"]->setValue(defaultDescription());
}
for (const RuleItem *rule : qAsConst(m_ruleList)) {
KConfigSkeletonItem *configItem = settings->findItem(rule->key());
KConfigSkeletonItem *configPolicyItem = settings->findItem(rule->policyKey());
Q_ASSERT (configItem);
if (rule->isEnabled()) {
configItem->setProperty(rule->value());
if (configPolicyItem) {
configPolicyItem->setProperty(rule->policy());
}
} else {
if (configPolicyItem) {
configPolicyItem->setProperty(Rules::Unused);
} else {
// Rules without policy gets deactivated by an empty string
configItem->setProperty(QString());
}
}
}
}
void RulesModel::importFromRules(Rules* rules)
{
QTemporaryFile tempFile;
if (!tempFile.open()) {
return;
}
const auto cfg = KSharedConfig::openConfig(tempFile.fileName(), KConfig::SimpleConfig);
RuleSettings *settings = new RuleSettings(cfg, QStringLiteral("tempSettings"));
settings->setDefaults();
if (rules) {
rules->write(settings);
}
readFromSettings(settings);
delete(settings);
}
Rules *RulesModel::exportToRules() const
{
QTemporaryFile tempFile;
if (!tempFile.open()) {
return nullptr;
}
const auto cfg = KSharedConfig::openConfig(tempFile.fileName(), KConfig::SimpleConfig);
RuleSettings *settings = new RuleSettings(cfg, QStringLiteral("tempSettings"));
writeToSettings(settings);
Rules *rules = new Rules(settings);
delete(settings);
return rules;
}
void RulesModel::populateRuleList()
{
qDeleteAll(m_ruleList);
m_ruleList.clear();
//Rule description
auto description = addRule(new RuleItem(QLatin1String("description"),
RulePolicy::NoPolicy, RuleItem::String,
i18n("Description"), i18n("Window matching"),
QIcon::fromTheme("entry-edit")));
description->setFlag(RuleItem::AlwaysEnabled);
description->setFlag(RuleItem::AffectsDescription);
// Window matching
auto wmclass = addRule(new RuleItem(QLatin1String("wmclass"),
RulePolicy::StringMatch, RuleItem::String,
i18n("Window class (application)"), i18n("Window matching"),
QIcon::fromTheme("window")));
wmclass->setFlag(RuleItem::AlwaysEnabled);
wmclass->setFlag(RuleItem::AffectsDescription);
wmclass->setFlag(RuleItem::AffectsWarning);
auto wmclasscomplete = addRule(new RuleItem(QLatin1String("wmclasscomplete"),
RulePolicy::NoPolicy, RuleItem::Boolean,
i18n("Match whole window class"), i18n("Window matching"),
QIcon::fromTheme("window")));
wmclasscomplete->setFlag(RuleItem::AlwaysEnabled);
auto types = addRule(new RuleItem(QLatin1String("types"),
RulePolicy::NoPolicy, RuleItem::FlagsOption,
i18n("Window types"), i18n("Window matching"),
QIcon::fromTheme("window-duplicate")));
types->setOptionsData(windowTypesModelData());
types->setFlag(RuleItem::AlwaysEnabled);
types->setFlag(RuleItem::AffectsWarning);
addRule(new RuleItem(QLatin1String("windowrole"),
RulePolicy::NoPolicy, RuleItem::String,
i18n("Window role"), i18n("Window matching"),
QIcon::fromTheme("dialog-object-properties")));
auto title = addRule(new RuleItem(QLatin1String("title"),
RulePolicy::StringMatch, RuleItem::String,
i18n("Window title"), i18n("Window matching"),
QIcon::fromTheme("edit-comment")));
title->setFlag(RuleItem::AffectsDescription);
addRule(new RuleItem(QLatin1String("clientmachine"),
RulePolicy::StringMatch, RuleItem::String,
i18n("Machine (hostname)"), i18n("Window matching"),
QIcon::fromTheme("computer")));
// Size & Position
addRule(new RuleItem(QLatin1String("position"),
RulePolicy::SetRule, RuleItem::Coordinate,
i18n("Position"), i18n("Size & Position"),
QIcon::fromTheme("transform-move")));
addRule(new RuleItem(QLatin1String("size"),
RulePolicy::SetRule, RuleItem::Coordinate,
i18n("Size"), i18n("Size & Position"),
QIcon::fromTheme("image-resize-symbolic")));
addRule(new RuleItem(QLatin1String("maximizehoriz"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Maximized horizontally"), i18n("Size & Position"),
QIcon::fromTheme("resizecol")));
addRule(new RuleItem(QLatin1String("maximizevert"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Maximized vertically"), i18n("Size & Position"),
QIcon::fromTheme("resizerow")));
auto desktop = addRule(new RuleItem(QLatin1String("desktop"),
RulePolicy::SetRule, RuleItem::Option,
i18n("Virtual Desktop"), i18n("Size & Position"),
QIcon::fromTheme("virtual-desktops")));
desktop->setOptionsData(virtualDesktopsModelData());
#ifdef KWIN_BUILD_ACTIVITIES
m_activities = new KActivities::Consumer(this);
auto activity = addRule(new RuleItem(QLatin1String("activity"),
RulePolicy::SetRule, RuleItem::Option,
i18n("Activity"), i18n("Size & Position"),
QIcon::fromTheme("activities")));
activity->setOptionsData(activitiesModelData());
// Activites consumer may update the available activities later
connect(m_activities, &KActivities::Consumer::activitiesChanged,
this, [this] { m_rules["activity"]->setOptionsData(activitiesModelData()); });
connect(m_activities, &KActivities::Consumer::serviceStatusChanged,
this, [this] { m_rules["activity"]->setOptionsData(activitiesModelData()); });
#endif
addRule(new RuleItem(QLatin1String("screen"),
RulePolicy::SetRule, RuleItem::Integer,
i18n("Screen"), i18n("Size & Position"),
QIcon::fromTheme("osd-shutd-screen")));
addRule(new RuleItem(QLatin1String("fullscreen"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Fullscreen"), i18n("Size & Position"),
QIcon::fromTheme("view-fullscreen")));
addRule(new RuleItem(QLatin1String("minimize"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Minimized"), i18n("Size & Position"),
QIcon::fromTheme("window-minimize")));
addRule(new RuleItem(QLatin1String("shade"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Shaded"), i18n("Size & Position"),
QIcon::fromTheme("window-shade")));
auto placement = addRule(new RuleItem(QLatin1String("placement"),
RulePolicy::ForceRule, RuleItem::Option,
i18n("Initial placement"), i18n("Size & Position"),
QIcon::fromTheme("region")));
placement->setOptionsData(placementModelData());
addRule(new RuleItem(QLatin1String("ignoregeometry"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Ignore requested geometry"), i18n("Size & Position"),
QIcon::fromTheme("view-time-schedule-baselined-remove"),
i18n("Windows can ask to appear in a certain position.\n"
"By default this overrides the placement strategy\n"
"what might be nasty if the client abuses the feature\n"
"to unconditionally popup in the middle of your screen.")));
addRule(new RuleItem(QLatin1String("minsize"),
RulePolicy::ForceRule, RuleItem::Coordinate,
i18n("Minimum Size"), i18n("Size & Position"),
QIcon::fromTheme("image-resize-symbolic")));
addRule(new RuleItem(QLatin1String("maxsize"),
RulePolicy::ForceRule, RuleItem::Coordinate,
i18n("Maximum Size"), i18n("Size & Position"),
QIcon::fromTheme("image-resize-symbolic")));
addRule(new RuleItem(QLatin1String("strictgeometry"),
RulePolicy::ForceRule, RuleItem::Boolean,
i18n("Obey geometry restrictions"), i18n("Size & Position"),
QIcon::fromTheme("transform-crop-and-resize"),
i18n("Eg. terminals or video players can ask to keep a certain aspect ratio\n"
"or only grow by values larger than one\n"
"(eg. by the dimensions of one character).\n"
"This may be pointless and the restriction prevents arbitrary dimensions\n"
"like your complete screen area.")));
// Arrangement & Access
addRule(new RuleItem(QLatin1String("above"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Keep above"), i18n("Arrangement & Access"),
QIcon::fromTheme("window-keep-above")));
addRule(new RuleItem(QLatin1String("below"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Keep below"), i18n("Arrangement & Access"),
QIcon::fromTheme("window-keep-below")));
addRule(new RuleItem(QLatin1String("skiptaskbar"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Skip taskbar"), i18n("Arrangement & Access"),
QIcon::fromTheme("kt-show-statusbar"),
i18n("Window shall (not) appear in the taskbar.")));
addRule(new RuleItem(QLatin1String("skippager"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Skip pager"), i18n("Arrangement & Access"),
QIcon::fromTheme("org.kde.plasma.pager"),
i18n("Window shall (not) appear in the manager for virtual desktops")));
addRule(new RuleItem(QLatin1String("skipswitcher"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("Skip switcher"), i18n("Arrangement & Access"),
QIcon::fromTheme("preferences-system-windows-effect-flipswitch"),
i18n("Window shall (not) appear in the Alt+Tab list")));
addRule(new RuleItem(QLatin1String("shortcut"),
RulePolicy::SetRule, RuleItem::Shortcut,
i18n("Shortcut"), i18n("Arrangement & Access"),
QIcon::fromTheme("configure-shortcuts")));
// Appearance & Fixes
addRule(new RuleItem(QLatin1String("noborder"),
RulePolicy::SetRule, RuleItem::Boolean,
i18n("No titlebar and frame"), i18n("Appearance & Fixes"),
QIcon::fromTheme("dialog-cancel")));
auto decocolor = addRule(new RuleItem(QLatin1String("decocolor"),
RulePolicy::ForceRule, RuleItem::Option,
i18n("Titlebar color scheme"), i18n("Appearance & Fixes"),
QIcon::fromTheme("preferences-desktop-theme")));
decocolor->setOptionsData(colorSchemesModelData());
addRule(new RuleItem(QLatin1String("opacityactive"),
RulePolicy::ForceRule, RuleItem::Percentage,
i18n("Active opacity"), i18n("Appearance & Fixes"),
QIcon::fromTheme("edit-opacity")));
addRule(new RuleItem(QLatin1String("opacityinactive"),
RulePolicy::ForceRule, RuleItem::Percentage,
i18n("Inactive opacity"), i18n("Appearance & Fixes"),
QIcon::fromTheme("edit-opacity")));
auto fsplevel = addRule(new RuleItem(QLatin1String("fsplevel"),
RulePolicy::ForceRule, RuleItem::Option,
i18n("Focus stealing prevention"), i18n("Appearance & Fixes"),
QIcon::fromTheme("preferences-system-windows-effect-glide"),
i18n("KWin tries to prevent windows from taking the focus\n"
"(\"activate\") while you're working in another window,\n"
"but this may sometimes fail or superact.\n"
"\"None\" will unconditionally allow this window to get the focus while\n"
"\"Extreme\" will completely prevent it from taking the focus.")));
fsplevel->setOptionsData(focusModelData());
auto fpplevel = addRule(new RuleItem(QLatin1String("fpplevel"),
RulePolicy::ForceRule, RuleItem::Option,
i18n("Focus protection"), i18n("Appearance & Fixes"),
QIcon::fromTheme("preferences-system-windows-effect-minimize"),
i18n("This controls the focus protection of the currently active window.\n"
"None will always give the focus away,\n"
"Extreme will keep it.\n"
"Otherwise it's interleaved with the stealing prevention\n"
"assigned to the window that wants the focus.")));
fpplevel->setOptionsData(focusModelData());
addRule(new RuleItem(QLatin1String("acceptfocus"),
RulePolicy::ForceRule, RuleItem::Boolean,
i18n("Accept focus"), i18n("Appearance & Fixes"),
QIcon::fromTheme("preferences-desktop-cursors"),
i18n("Windows may prevent to get the focus (activate) when being clicked.\n"
"On the other hand you might wish to prevent a window\n"
"from getting focused on a mouse click.")));
addRule(new RuleItem(QLatin1String("disableglobalshortcuts"),
RulePolicy::ForceRule, RuleItem::Boolean,
i18n("Ignore global shortcuts"), i18n("Appearance & Fixes"),
QIcon::fromTheme("input-keyboard-virtual-off"),
i18n("When used, a window will receive\n"
"all keyboard inputs while it is active, including Alt+Tab etc.\n"
"This is especially interesting for emulators or virtual machines.\n"
"\n"
"Be warned:\n"
"you won't be able to Alt+Tab out of the window\n"
"nor use any other global shortcut (such as Alt+F2 to show KRunner)\n"
"while it's active!")));
addRule(new RuleItem(QLatin1String("closeable"),
RulePolicy::ForceRule, RuleItem::Boolean,
i18n("Closeable"), i18n("Appearance & Fixes"),
QIcon::fromTheme("dialog-close")));
auto type = addRule(new RuleItem(QLatin1String("type"),
RulePolicy::ForceRule, RuleItem::Option,
i18n("Set window type"), i18n("Appearance & Fixes"),
QIcon::fromTheme("window-duplicate")));
type->setOptionsData(windowTypesModelData());
addRule(new RuleItem(QLatin1String("desktopfile"),
RulePolicy::SetRule, RuleItem::String,
i18n("Desktop file name"), i18n("Appearance & Fixes"),
QIcon::fromTheme("application-x-desktop")));
addRule(new RuleItem(QLatin1String("blockcompositing"),
RulePolicy::ForceRule, RuleItem::Boolean,
i18n("Block compositing"), i18n("Appearance & Fixes"),
QIcon::fromTheme("composite-track-on")));
}
const QHash<QString, QString> RulesModel::x11PropertyHash()
{
static const auto propertyToRule = QHash<QString, QString> {
/* The original detection dialog allows to choose depending on "Match complete window class":
* if Match Complete == false: wmclass = "resourceClass"
* if Match Complete == true: wmclass = "resourceName" + " " + "resourceClass"
*/
{ "resourceName", "wmclass" },
{ "caption", "title" },
{ "role", "windowrole" },
{ "clientMachine", "clientmachine" },
{ "x11DesktopNumber", "desktop" },
{ "maximizeHorizontal", "maximizehoriz" },
{ "maximizeVertical", "maximizevert" },
{ "minimized", "minimize" },
{ "shaded", "shade" },
{ "fullscreen", "fullscreen" },
{ "keepAbove", "above" },
{ "keepBelow", "below" },
{ "noBorder", "noborder" },
{ "skipTaskbar", "skiptaskbar" },
{ "skipPager", "skippager" },
{ "skipSwitcher", "skipswitcher" },
{ "type", "type" },
{ "desktopFile", "desktopfile" }
};
return propertyToRule;
};
void RulesModel::setWindowProperties(const QVariantMap &info, bool forceValue)
{
// Properties that cannot be directly applied via x11PropertyHash
const QString position = QStringLiteral("%1,%2").arg(info.value("x").toInt())
.arg(info.value("y").toInt());
const QString size = QStringLiteral("%1,%2").arg(info.value("width").toInt())
.arg(info.value("height").toInt());
m_rules["position"]->setSuggestedValue(position, forceValue);
m_rules["size"]->setSuggestedValue(size, forceValue);
m_rules["minsize"]->setSuggestedValue(size, forceValue);
m_rules["maxsize"]->setSuggestedValue(size, forceValue);
NET::WindowType window_type = static_cast<NET::WindowType>(info.value("type", 0).toInt());
if (window_type == NET::Unknown) {
window_type = NET::Normal;
}
m_rules["types"]->setSuggestedValue(1 << window_type, forceValue);
const auto ruleForProperty = x11PropertyHash();
for (QString &property : info.keys()) {
if (!ruleForProperty.contains(property)) {
continue;
}
const QString ruleKey = ruleForProperty.value(property, QString());
Q_ASSERT(hasRule(ruleKey));
m_rules[ruleKey]->setSuggestedValue(info.value(property), forceValue);
}
emit dataChanged(index(0), index(rowCount()-1), {RulesModel::SuggestedValueRole});
if (!forceValue) {
emit suggestionsChanged();
}
}
QList<OptionsModel::Data> RulesModel::windowTypesModelData() const
{
static const auto modelData = QList<OptionsModel::Data> {
//TODO: Find/create better icons
{ NET::Normal, i18n("Normal Window") , QIcon::fromTheme("window") },
{ NET::Dialog, i18n("Dialog Window") , QIcon::fromTheme("window-duplicate") },
{ NET::Utility, i18n("Utility Window") , QIcon::fromTheme("dialog-object-properties") },
{ NET::Dock, i18n("Dock (panel)") , QIcon::fromTheme("list-remove") },
{ NET::Toolbar, i18n("Toolbar") , QIcon::fromTheme("tools") },
{ NET::Menu, i18n("Torn-Off Menu") , QIcon::fromTheme("overflow-menu-left") },
{ NET::Splash, i18n("Splash Screen") , QIcon::fromTheme("embosstool") },
{ NET::Desktop, i18n("Desktop") , QIcon::fromTheme("desktop") },
// { NET::Override, i18n("Unmanaged Window") }, deprecated
{ NET::TopMenu, i18n("Standalone Menubar"), QIcon::fromTheme("open-menu-symbolic") }
};
return modelData;
}
QList<OptionsModel::Data> RulesModel::virtualDesktopsModelData() const
{
QList<OptionsModel::Data> modelData;
for (int desktopId = 1; desktopId <= KWindowSystem::numberOfDesktops(); ++desktopId) {
modelData << OptionsModel::Data{
desktopId,
QString::number(desktopId).rightJustified(2) + QStringLiteral(": ") + KWindowSystem::desktopName(desktopId),
QIcon::fromTheme("virtual-desktops")
};
}
modelData << OptionsModel::Data{ NET::OnAllDesktops, i18n("All Desktops"), QIcon::fromTheme("window-pin") };
return modelData;
}
QList<OptionsModel::Data> RulesModel::activitiesModelData() const
{
#ifdef KWIN_BUILD_ACTIVITIES
QList<OptionsModel::Data> modelData;
// NULL_ID from kactivities/src/lib/core/consumer.cpp
modelData << OptionsModel::Data{
QString::fromLatin1("00000000-0000-0000-0000-000000000000"),
i18n("All Activities"),
QIcon::fromTheme("activities")
};
const auto activities = m_activities->activities(KActivities::Info::Running);
if (m_activities->serviceStatus() == KActivities::Consumer::Running) {
for (const QString &activityId : activities) {
const KActivities::Info info(activityId);
modelData << OptionsModel::Data{ activityId, info.name(), QIcon::fromTheme(info.icon()) };
}
}
return modelData;
#else
return {};
#endif
}
QList<OptionsModel::Data> RulesModel::placementModelData() const
{
// From "placement.h" : Placement rule is stored as a string, not the enum value
static const auto modelData = QList<OptionsModel::Data> {
{ Placement::policyToString(Placement::Default), i18n("Default") },
{ Placement::policyToString(Placement::NoPlacement), i18n("No Placement") },
{ Placement::policyToString(Placement::Smart), i18n("Minimal Overlapping") },
{ Placement::policyToString(Placement::Maximizing), i18n("Maximized") },
{ Placement::policyToString(Placement::Cascade), i18n("Cascaded") },
{ Placement::policyToString(Placement::Centered), i18n("Centered") },
{ Placement::policyToString(Placement::Random), i18n("Random") },
{ Placement::policyToString(Placement::ZeroCornered), i18n("In Top-Left Corner") },
{ Placement::policyToString(Placement::UnderMouse), i18n("Under Mouse") },
{ Placement::policyToString(Placement::OnMainWindow), i18n("On Main Window") }
};
return modelData;
}
QList<OptionsModel::Data> RulesModel::focusModelData() const
{
static const auto modelData = QList<OptionsModel::Data> {
{ 0, i18n("None") },
{ 1, i18n("Low") },
{ 2, i18n("Normal") },
{ 3, i18n("High") },
{ 4, i18n("Extreme") }
};
return modelData;
}
QList<OptionsModel::Data> RulesModel::colorSchemesModelData() const
{
QList<OptionsModel::Data> modelData;
KColorSchemeManager schemes;
QAbstractItemModel *schemesModel = schemes.model();
// Skip row 0, which is Default scheme
for (int r = 1; r < schemesModel->rowCount(); r++) {
const QModelIndex index = schemesModel->index(r, 0);
modelData << OptionsModel::Data{
QFileInfo(index.data(Qt::UserRole).toString()).baseName(),
index.data(Qt::DisplayRole).toString(),
index.data(Qt::DecorationRole).value<QIcon>()
};
}
return modelData;
}
void RulesModel::detectWindowProperties(int secs)
{
QTimer::singleShot(secs*1000, this, &RulesModel::selectX11Window);
}
void RulesModel::selectX11Window()
{
QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KWin"),
QStringLiteral("/KWin"),
QStringLiteral("org.kde.KWin"),
QStringLiteral("queryWindowInfo"));
QDBusPendingReply<QVariantMap> async = QDBusConnection::sessionBus().asyncCall(message);
QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this);
connect(callWatcher, &QDBusPendingCallWatcher::finished, this,
[this](QDBusPendingCallWatcher *self) {
QDBusPendingReply<QVariantMap> reply = *self;
self->deleteLater();
if (!reply.isValid()) {
return;
}
const QVariantMap windowInfo = reply.value();
setWindowProperties(windowInfo);
}
);
}
} //namespace

View File

@ -0,0 +1,127 @@
/*
* Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com>
*
* 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) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* 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/>.
*/
#ifndef KWIN_RULES_MODEL_H
#define KWIN_RULES_MODEL_H
#include "ruleitem.h"
#include "rulesettings.h"
#include <rules.h>
#include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <QObject>
#ifdef KWIN_BUILD_ACTIVITIES
#include <KActivities/Consumer>
#endif
namespace KWin
{
class RulesModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
Q_PROPERTY(QString warningMessage READ warningMessage NOTIFY warningMessageChanged)
public:
enum RulesRole {
NameRole = Qt::DisplayRole,
DescriptionRole = Qt::ToolTipRole,
IconRole = Qt::DecorationRole,
IconNameRole = Qt::UserRole + 1,
KeyRole,
SectionRole,
EnabledRole,
SelectableRole,
ValueRole,
TypeRole,
PolicyRole,
PolicyModelRole,
OptionsModelRole,
SuggestedValueRole
};
Q_ENUM(RulesRole)
public:
explicit RulesModel(QObject *parent = nullptr);
~RulesModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex & index, const QVariant & value, int role) override;
bool hasRule(const QString &key) const;
RuleItem *ruleItem(const QString &key) const;
void readFromSettings(RuleSettings *settings);
void writeToSettings(RuleSettings *settings) const;
void importFromRules(Rules *rules);
Rules *exportToRules() const;
void setWindowProperties(const QVariantMap &info, bool forceValue = false);
QString description() const;
void setDescription(const QString &description);
QString warningMessage() const;
public slots:
void detectWindowProperties(int secs);
signals:
void descriptionChanged();
void warningMessageChanged();
void suggestionsChanged();
private:
void populateRuleList();
bool wmclassWarning() const;
RuleItem *addRule(RuleItem *rule);
QString defaultDescription() const;
static const QHash<QString, QString> x11PropertyHash();
QList<OptionsModel::Data> windowTypesModelData() const;
QList<OptionsModel::Data> virtualDesktopsModelData() const;
QList<OptionsModel::Data> activitiesModelData() const;
QList<OptionsModel::Data> placementModelData() const;
QList<OptionsModel::Data> focusModelData() const;
QList<OptionsModel::Data> colorSchemesModelData() const;
private slots:
void selectX11Window();
private:
QList<RuleItem *> m_ruleList;
QHash<QString, RuleItem *> m_rules;
#ifdef KWIN_BUILD_ACTIVITIES
KActivities::Consumer *m_activities;
#endif
};
}
#endif

View File

@ -1,970 +0,0 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ruleswidget.h"
#include <klineedit.h>
#include <kcombobox.h>
#include <kcolorschememanager.h>
#include <QCheckBox>
#include <QFileInfo>
#include <QLabel>
#include <KLocalizedString>
#include <QRegExp>
#ifdef KWIN_BUILD_ACTIVITIES
#include <kactivities/consumer.h>
#endif
#include <kmessagebox.h>
#include <QTabWidget>
#include <QTimer>
#include "../../rules.h"
#include "detectwidget.h"
Q_DECLARE_METATYPE(NET::WindowType)
namespace KWin
{
#define SETUP( var, type ) \
connect( enable_##var, SIGNAL(toggled(bool)), rule_##var, SLOT(setEnabled(bool))); \
connect( enable_##var, SIGNAL(toggled(bool)), this, SLOT(updateEnable##var())); \
connect( rule_##var, SIGNAL(activated(int)), this, SLOT(updateEnable##var())); \
enable_##var->setWhatsThis( enableDesc ); \
rule_##var->setWhatsThis( type##RuleDesc );
RulesWidget::RulesWidget(QWidget* parent)
: detect_dlg(nullptr)
{
Q_UNUSED(parent);
setupUi(this);
QRegularExpressionValidator* validator = new QRegularExpressionValidator(QRegularExpression("[0-9\\-+,xX:]*"), this);
maxsize->setValidator(validator);
minsize->setValidator(validator);
position->setValidator(validator);
Ui::RulesWidgetBase::size->setValidator(validator);
QString enableDesc =
i18n("Enable this checkbox to alter this window property for the specified window(s).");
QString setRuleDesc =
i18n("Specify how the window property should be affected:<ul>"
"<li><em>Do Not Affect:</em> The window property will not be affected and therefore"
" the default handling for it will be used. Specifying this will block more generic"
" window settings from taking effect.</li>"
"<li><em>Apply Initially:</em> The window property will be only set to the given value"
" after the window is created. No further changes will be affected.</li>"
"<li><em>Remember:</em> The value of the window property will be remembered and every"
" time the window is created, the last remembered value will be applied.</li>"
"<li><em>Force:</em> The window property will be always forced to the given value.</li>"
"<li><em>Apply Now:</em> The window property will be set to the given value immediately"
" and will not be affected later (this action will be deleted afterwards).</li>"
"<li><em>Force temporarily:</em> The window property will be forced to the given value"
" until it is hidden (this action will be deleted after the window is hidden).</li>"
"</ul>");
QString forceRuleDesc =
i18n("Specify how the window property should be affected:<ul>"
"<li><em>Do Not Affect:</em> The window property will not be affected and therefore"
" the default handling for it will be used. Specifying this will block more generic"
" window settings from taking effect.</li>"
"<li><em>Force:</em> The window property will be always forced to the given value.</li>"
"<li><em>Force temporarily:</em> The window property will be forced to the given value"
" until it is hidden (this action will be deleted after the window is hidden).</li>"
"</ul>");
// window tabs have enable signals done in designer
// geometry tab
SETUP(position, set);
SETUP(size, set);
SETUP(desktop, set);
SETUP(screen, set);
#ifdef KWIN_BUILD_ACTIVITIES
SETUP(activity, set);
#endif
SETUP(maximizehoriz, set);
SETUP(maximizevert, set);
SETUP(minimize, set);
SETUP(shade, set);
SETUP(fullscreen, set);
SETUP(placement, force);
// preferences tab
SETUP(above, set);
SETUP(below, set);
SETUP(noborder, set);
SETUP(decocolor, force);
SETUP(skiptaskbar, set);
SETUP(skippager, set);
SETUP(skipswitcher, set);
SETUP(acceptfocus, force);
SETUP(closeable, force);
SETUP(autogroup, force);
SETUP(autogroupfg, force);
SETUP(autogroupid, force);
SETUP(opacityactive, force);
SETUP(opacityinactive, force);
SETUP(shortcut, force);
// workarounds tab
SETUP(fsplevel, force);
SETUP(fpplevel, force);
SETUP(type, force);
SETUP(desktopfile, set);
SETUP(ignoregeometry, set);
SETUP(minsize, force);
SETUP(maxsize, force);
SETUP(strictgeometry, force);
SETUP(disableglobalshortcuts, force);
SETUP(blockcompositing, force);
connect (shortcut_edit, SIGNAL(clicked()), SLOT(shortcutEditClicked()));
edit_reg_wmclass->hide();
edit_reg_role->hide();
edit_reg_title->hide();
edit_reg_machine->hide();
#ifndef KWIN_BUILD_ACTIVITIES
rule_activity->hide();
enable_activity->hide();
activity->hide();
#endif
int i;
for (i = 1;
i <= KWindowSystem::numberOfDesktops();
++i)
desktop->addItem(QString::number(i).rightJustified(2) + ':' + KWindowSystem::desktopName(i));
desktop->addItem(i18n("All Desktops"));
#ifdef KWIN_BUILD_ACTIVITIES
m_activities = new KActivities::Consumer(this);
connect(m_activities, &KActivities::Consumer::activitiesChanged,
this, [this] { updateActivitiesList(); });
connect(m_activities, &KActivities::Consumer::serviceStatusChanged,
this, [this] { updateActivitiesList(); });
updateActivitiesList();
#endif
KColorSchemeManager *schemes = new KColorSchemeManager(this);
decocolor->setModel(schemes->model());
// hide autogrouping as it's currently not supported
// BUG 370301
line_11->hide();
enable_autogroup->hide();
autogroup->hide();
rule_autogroup->hide();
enable_autogroupid->hide();
autogroupid->hide();
rule_autogroupid->hide();
enable_autogroupfg->hide();
autogroupfg->hide();
rule_autogroupfg->hide();
}
#undef SETUP
#define UPDATE_ENABLE_SLOT(var) \
void RulesWidget::updateEnable##var() \
{ \
/* leave the label readable label_##var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 );*/ \
Ui::RulesWidgetBase::var->setEnabled( enable_##var->isChecked() && rule_##var->currentIndex() != 0 ); \
}
// geometry tab
UPDATE_ENABLE_SLOT(position)
UPDATE_ENABLE_SLOT(size)
UPDATE_ENABLE_SLOT(desktop)
UPDATE_ENABLE_SLOT(screen)
#ifdef KWIN_BUILD_ACTIVITIES
UPDATE_ENABLE_SLOT(activity)
#endif
UPDATE_ENABLE_SLOT(maximizehoriz)
UPDATE_ENABLE_SLOT(maximizevert)
UPDATE_ENABLE_SLOT(minimize)
UPDATE_ENABLE_SLOT(shade)
UPDATE_ENABLE_SLOT(fullscreen)
UPDATE_ENABLE_SLOT(placement)
// preferences tab
UPDATE_ENABLE_SLOT(above)
UPDATE_ENABLE_SLOT(below)
UPDATE_ENABLE_SLOT(noborder)
UPDATE_ENABLE_SLOT(decocolor)
UPDATE_ENABLE_SLOT(skiptaskbar)
UPDATE_ENABLE_SLOT(skippager)
UPDATE_ENABLE_SLOT(skipswitcher)
UPDATE_ENABLE_SLOT(acceptfocus)
UPDATE_ENABLE_SLOT(closeable)
UPDATE_ENABLE_SLOT(autogroup)
UPDATE_ENABLE_SLOT(autogroupfg)
UPDATE_ENABLE_SLOT(autogroupid)
UPDATE_ENABLE_SLOT(opacityactive)
UPDATE_ENABLE_SLOT(opacityinactive)
void RulesWidget::updateEnableshortcut()
{
shortcut->setEnabled(enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0);
shortcut_edit->setEnabled(enable_shortcut->isChecked() && rule_shortcut->currentIndex() != 0);
}
// workarounds tab
UPDATE_ENABLE_SLOT(fsplevel)
UPDATE_ENABLE_SLOT(fpplevel)
UPDATE_ENABLE_SLOT(type)
UPDATE_ENABLE_SLOT(ignoregeometry)
UPDATE_ENABLE_SLOT(minsize)
UPDATE_ENABLE_SLOT(maxsize)
UPDATE_ENABLE_SLOT(strictgeometry)
UPDATE_ENABLE_SLOT(disableglobalshortcuts)
UPDATE_ENABLE_SLOT(blockcompositing)
UPDATE_ENABLE_SLOT(desktopfile)
#undef UPDATE_ENABLE_SLOT
static const int set_rule_to_combo[] = {
0, // Unused
0, // Don't Affect
3, // Force
1, // Apply
2, // Remember
4, // ApplyNow
5 // ForceTemporarily
};
static const Rules::SetRule combo_to_set_rule[] = {
(Rules::SetRule)Rules::DontAffect,
(Rules::SetRule)Rules::Apply,
(Rules::SetRule)Rules::Remember,
(Rules::SetRule)Rules::Force,
(Rules::SetRule)Rules::ApplyNow,
(Rules::SetRule)Rules::ForceTemporarily
};
static const int force_rule_to_combo[] = {
0, // Unused
0, // Don't Affect
1, // Force
0, // Apply
0, // Remember
0, // ApplyNow
2 // ForceTemporarily
};
static const Rules::ForceRule combo_to_force_rule[] = {
(Rules::ForceRule)Rules::DontAffect,
(Rules::ForceRule)Rules::Force,
(Rules::ForceRule)Rules::ForceTemporarily
};
static QString positionToStr(const QPoint& p)
{
if (p == invalidPoint)
return QString();
return QString::number(p.x()) + ',' + QString::number(p.y());
}
static QPoint strToPosition(const QString& str)
{
// two numbers, with + or -, separated by any of , x X :
QRegExp reg("\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*");
if (!reg.exactMatch(str))
return invalidPoint;
return QPoint(reg.cap(1).toInt(), reg.cap(2).toInt());
}
static QString sizeToStr(const QSize& s)
{
if (!s.isValid())
return QString();
return QString::number(s.width()) + ',' + QString::number(s.height());
}
static QSize strToSize(const QString& str)
{
// two numbers, with + or -, separated by any of , x X :
QRegExp reg("\\s*([+-]?[0-9]*)\\s*[,xX:]\\s*([+-]?[0-9]*)\\s*");
if (!reg.exactMatch(str))
return QSize();
return QSize(reg.cap(1).toInt(), reg.cap(2).toInt());
}
int RulesWidget::desktopToCombo(int d) const
{
if (d >= 1 && d < desktop->count())
return d - 1;
return desktop->count() - 1; // on all desktops
}
int RulesWidget::comboToDesktop(int val) const
{
if (val == desktop->count() - 1)
return NET::OnAllDesktops;
return val + 1;
}
#ifdef KWIN_BUILD_ACTIVITIES
int RulesWidget::activityToCombo(const QString &d) const
{
// TODO: ivan - do a multiselection list
for (int i = 0; i < activity->count(); i++) {
if (activity->itemData(i).toString() == d) {
return i;
}
}
return activity->count() - 1; // on all activities
}
QString RulesWidget::comboToActivity(int val) const
{
// TODO: ivan - do a multiselection list
if (val < 0 || val >= activity->count())
return QString();
return activity->itemData(val).toString();
}
void RulesWidget::updateActivitiesList()
{
activity->clear();
// cloned from kactivities/src/lib/core/consumer.cpp
#define NULL_UUID "00000000-0000-0000-0000-000000000000"
activity->addItem(i18n("All Activities"), QString::fromLatin1(NULL_UUID));
#undef NULL_UUID
if (m_activities->serviceStatus() == KActivities::Consumer::Running) {
foreach (const QString & activityId, m_activities->activities(KActivities::Info::Running)) {
const KActivities::Info info(activityId);
activity->addItem(info.name(), activityId);
}
}
auto rules = this->rules();
if (rules->activityrule == Rules::UnusedSetRule) {
enable_activity->setChecked(false);
Ui::RulesWidgetBase::activity->setCurrentIndex(0);
} else {
enable_activity->setChecked(true);
Ui::RulesWidgetBase::activity->setCurrentIndex(activityToCombo(m_selectedActivityId));
}
updateEnableactivity();
}
#endif
static int placementToCombo(Placement::Policy placement)
{
static const int conv[] = {
1, // NoPlacement
0, // Default
0, // Unknown
6, // Random
2, // Smart
4, // Cascade
5, // Centered
7, // ZeroCornered
8, // UnderMouse
9, // OnMainWindow
3 // Maximizing
};
return conv[ placement ];
}
static Placement::Policy comboToPlacement(int val)
{
static const Placement::Policy conv[] = {
Placement::Default,
Placement::NoPlacement,
Placement::Smart,
Placement::Maximizing,
Placement::Cascade,
Placement::Centered,
Placement::Random,
Placement::ZeroCornered,
Placement::UnderMouse,
Placement::OnMainWindow
// no Placement::Unknown
};
return conv[ val ];
}
static int typeToCombo(NET::WindowType type)
{
if (type < NET::Normal || type > NET::Splash ||
type == NET::Override) // The user must NOT set a window to be unmanaged.
// This case is not handled in KWin and will lead to segfaults.
// Even iff it was supported, it would mean to allow the user to shoot himself
// since an unmanaged window has to manage itself, what is probably not the case when the hint is not set.
// Rule opportunity might be a relict from the Motif Hint window times of KDE1
return 0; // Normal
static const int conv[] = {
0, // Normal
7, // Desktop
3, // Dock
4, // Toolbar
5, // Menu
1, // Dialog
8, // Override - ignored.
9, // TopMenu
2, // Utility
6 // Splash
};
return conv[ type ];
}
static NET::WindowType comboToType(int val)
{
static const NET::WindowType conv[] = {
NET::Normal,
NET::Dialog,
NET::Utility,
NET::Dock,
NET::Toolbar,
NET::Menu,
NET::Splash,
NET::Desktop,
NET::TopMenu
};
return conv[ val ];
}
#define GENERIC_RULE( var, func, Type, type, uimethod, uimethod0 ) \
if ( rules->var##rule == Rules::Unused##Type##Rule ) \
{ \
enable_##var->setChecked( false ); \
rule_##var->setCurrentIndex( 0 ); \
Ui::RulesWidgetBase::var->uimethod0; \
updateEnable##var(); \
} \
else \
{ \
enable_##var->setChecked( true ); \
rule_##var->setCurrentIndex( type##_rule_to_combo[ rules->var##rule ] ); \
Ui::RulesWidgetBase::var->uimethod( func( rules->var )); \
updateEnable##var(); \
}
#define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setChecked, setChecked( false ))
#define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setText, setText( QString() ))
#define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setCurrentIndex, setCurrentIndex( 0 ))
#define SPINBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, setValue, setValue(0))
#define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setChecked, setChecked( false ))
#define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setText, setText( QString() ))
#define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setCurrentIndex, setCurrentIndex( 0 ))
#define SPINBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, setValue, setValue(0))
void RulesWidget::setRules(Rules* rules)
{
Rules tmp;
if (rules == nullptr)
rules = &tmp; // empty
description->setText(rules->description);
wmclass->setText(rules->wmclass);
whole_wmclass->setChecked(rules->wmclasscomplete);
wmclass_match->setCurrentIndex(rules->wmclassmatch);
wmclassMatchChanged();
role->setText(rules->windowrole);
role_match->setCurrentIndex(rules->windowrolematch);
roleMatchChanged();
types->item(0)->setSelected(rules->types & NET::NormalMask);
types->item(1)->setSelected(rules->types & NET::DialogMask);
types->item(2)->setSelected(rules->types & NET::UtilityMask);
types->item(3)->setSelected(rules->types & NET::DockMask);
types->item(4)->setSelected(rules->types & NET::ToolbarMask);
types->item(5)->setSelected(rules->types & NET::MenuMask);
types->item(6)->setSelected(rules->types & NET::SplashMask);
types->item(7)->setSelected(rules->types & NET::DesktopMask);
types->item(8)->setSelected(rules->types & NET::OverrideMask);
types->item(9)->setSelected(rules->types & NET::TopMenuMask);
title->setText(rules->title);
title_match->setCurrentIndex(rules->titlematch);
titleMatchChanged();
machine->setText(rules->clientmachine);
machine_match->setCurrentIndex(rules->clientmachinematch);
machineMatchChanged();
LINEEDIT_SET_RULE(position, positionToStr);
LINEEDIT_SET_RULE(size, sizeToStr);
COMBOBOX_SET_RULE(desktop, desktopToCombo);
SPINBOX_SET_RULE(screen, inc);
#ifdef KWIN_BUILD_ACTIVITIES
m_selectedActivityId = rules->activity;
COMBOBOX_SET_RULE(activity, activityToCombo);
#endif
CHECKBOX_SET_RULE(maximizehoriz,);
CHECKBOX_SET_RULE(maximizevert,);
CHECKBOX_SET_RULE(minimize,);
CHECKBOX_SET_RULE(shade,);
CHECKBOX_SET_RULE(fullscreen,);
COMBOBOX_FORCE_RULE(placement, placementToCombo);
CHECKBOX_SET_RULE(above,);
CHECKBOX_SET_RULE(below,);
CHECKBOX_SET_RULE(noborder,);
auto decoColorToCombo = [this](const QString &value) {
for (int i = 0; i < decocolor->count(); ++i) {
if (decocolor->itemData(i).toString() == value) {
return i;
}
}
// search for Breeze
for (int i = 0; i < decocolor->count(); ++i) {
if (QFileInfo(decocolor->itemData(i).toString()).baseName() == QStringLiteral("Breeze")) {
return i;
}
}
return 0;
};
COMBOBOX_FORCE_RULE(decocolor, decoColorToCombo);
CHECKBOX_SET_RULE(skiptaskbar,);
CHECKBOX_SET_RULE(skippager,);
CHECKBOX_SET_RULE(skipswitcher,);
CHECKBOX_FORCE_RULE(acceptfocus,);
CHECKBOX_FORCE_RULE(closeable,);
CHECKBOX_FORCE_RULE(autogroup,);
CHECKBOX_FORCE_RULE(autogroupfg,);
LINEEDIT_FORCE_RULE(autogroupid,);
SPINBOX_FORCE_RULE(opacityactive,);
SPINBOX_FORCE_RULE(opacityinactive,);
LINEEDIT_SET_RULE(shortcut,);
COMBOBOX_FORCE_RULE(fsplevel,);
COMBOBOX_FORCE_RULE(fpplevel,);
COMBOBOX_FORCE_RULE(type, typeToCombo);
CHECKBOX_SET_RULE(ignoregeometry,);
LINEEDIT_FORCE_RULE(minsize, sizeToStr);
LINEEDIT_FORCE_RULE(maxsize, sizeToStr);
CHECKBOX_FORCE_RULE(strictgeometry,);
CHECKBOX_FORCE_RULE(disableglobalshortcuts,);
CHECKBOX_FORCE_RULE(blockcompositing,);
LINEEDIT_SET_RULE(desktopfile,)
}
#undef GENERIC_RULE
#undef CHECKBOX_SET_RULE
#undef LINEEDIT_SET_RULE
#undef COMBOBOX_SET_RULE
#undef SPINBOX_SET_RULE
#undef CHECKBOX_FORCE_RULE
#undef LINEEDIT_FORCE_RULE
#undef COMBOBOX_FORCE_RULE
#undef SPINBOX_FORCE_RULE
#define GENERIC_RULE( var, func, Type, type, uimethod ) \
if ( enable_##var->isChecked() && rule_##var->currentIndex() >= 0) \
{ \
rules->var##rule = combo_to_##type##_rule[ rule_##var->currentIndex() ]; \
rules->var = func( Ui::RulesWidgetBase::var->uimethod()); \
} \
else \
rules->var##rule = Rules::Unused##Type##Rule;
#define CHECKBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, isChecked )
#define LINEEDIT_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, text )
#define COMBOBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, currentIndex )
#define SPINBOX_SET_RULE( var, func ) GENERIC_RULE( var, func, Set, set, value)
#define CHECKBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, isChecked )
#define LINEEDIT_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, text )
#define COMBOBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, currentIndex )
#define SPINBOX_FORCE_RULE( var, func ) GENERIC_RULE( var, func, Force, force, value)
Rules* RulesWidget::rules() const
{
Rules* rules = new Rules();
rules->description = description->text();
rules->wmclass = wmclass->text().toUtf8();
rules->wmclasscomplete = whole_wmclass->isChecked();
rules->wmclassmatch = static_cast< Rules::StringMatch >(wmclass_match->currentIndex());
rules->windowrole = role->text().toUtf8();
rules->windowrolematch = static_cast< Rules::StringMatch >(role_match->currentIndex());
rules->types = {};
bool all_types = true;
for (int i = 0;
i < types->count();
++i)
if (!types->item(i)->isSelected())
all_types = false;
if (all_types) // if all types are selected, use AllTypesMask (for future expansion)
rules->types = NET::AllTypesMask;
else {
rules->types |= types->item(0)->isSelected() ? NET::NormalMask : NET::WindowTypeMask(0);
rules->types |= types->item(1)->isSelected() ? NET::DialogMask : NET::WindowTypeMask(0);
rules->types |= types->item(2)->isSelected() ? NET::UtilityMask : NET::WindowTypeMask(0);
rules->types |= types->item(3)->isSelected() ? NET::DockMask : NET::WindowTypeMask(0);
rules->types |= types->item(4)->isSelected() ? NET::ToolbarMask : NET::WindowTypeMask(0);
rules->types |= types->item(5)->isSelected() ? NET::MenuMask : NET::WindowTypeMask(0);
rules->types |= types->item(6)->isSelected() ? NET::SplashMask : NET::WindowTypeMask(0);
rules->types |= types->item(7)->isSelected() ? NET::DesktopMask : NET::WindowTypeMask(0);
rules->types |= types->item(8)->isSelected() ? NET::OverrideMask : NET::WindowTypeMask(0);
rules->types |= types->item(9)->isSelected() ? NET::TopMenuMask : NET::WindowTypeMask(0);
}
rules->title = title->text();
rules->titlematch = static_cast< Rules::StringMatch >(title_match->currentIndex());
rules->clientmachine = machine->text().toUtf8();
rules->clientmachinematch = static_cast< Rules::StringMatch >(machine_match->currentIndex());
LINEEDIT_SET_RULE(position, strToPosition);
LINEEDIT_SET_RULE(size, strToSize);
COMBOBOX_SET_RULE(desktop, comboToDesktop);
SPINBOX_SET_RULE(screen, dec);
#ifdef KWIN_BUILD_ACTIVITIES
COMBOBOX_SET_RULE(activity, comboToActivity);
#endif
CHECKBOX_SET_RULE(maximizehoriz,);
CHECKBOX_SET_RULE(maximizevert,);
CHECKBOX_SET_RULE(minimize,);
CHECKBOX_SET_RULE(shade,);
CHECKBOX_SET_RULE(fullscreen,);
COMBOBOX_FORCE_RULE(placement, comboToPlacement);
CHECKBOX_SET_RULE(above,);
CHECKBOX_SET_RULE(below,);
CHECKBOX_SET_RULE(noborder,);
auto comboToDecocolor = [this](int index) -> QString {
return decocolor->itemData(index).toString();
};
COMBOBOX_FORCE_RULE(decocolor, comboToDecocolor);
CHECKBOX_SET_RULE(skiptaskbar,);
CHECKBOX_SET_RULE(skippager,);
CHECKBOX_SET_RULE(skipswitcher,);
CHECKBOX_FORCE_RULE(acceptfocus,);
CHECKBOX_FORCE_RULE(closeable,);
CHECKBOX_FORCE_RULE(autogroup,);
CHECKBOX_FORCE_RULE(autogroupfg,);
LINEEDIT_FORCE_RULE(autogroupid,);
SPINBOX_FORCE_RULE(opacityactive,);
SPINBOX_FORCE_RULE(opacityinactive,);
LINEEDIT_SET_RULE(shortcut,);
COMBOBOX_FORCE_RULE(fsplevel,);
COMBOBOX_FORCE_RULE(fpplevel,);
COMBOBOX_FORCE_RULE(type, comboToType);
CHECKBOX_SET_RULE(ignoregeometry,);
LINEEDIT_FORCE_RULE(minsize, strToSize);
LINEEDIT_FORCE_RULE(maxsize, strToSize);
CHECKBOX_FORCE_RULE(strictgeometry,);
CHECKBOX_FORCE_RULE(disableglobalshortcuts,);
CHECKBOX_FORCE_RULE(blockcompositing,);
LINEEDIT_SET_RULE(desktopfile,);
return rules;
}
#undef GENERIC_RULE
#undef CHECKBOX_SET_RULE
#undef LINEEDIT_SET_RULE
#undef COMBOBOX_SET_RULE
#undef SPINBOX_SET_RULE
#undef CHECKBOX_FORCE_RULE
#undef LINEEDIT_FORCE_RULE
#undef COMBOBOX_FORCE_RULE
#undef SPINBOX_FORCE_RULE
#define STRING_MATCH_COMBO( type ) \
void RulesWidget::type##MatchChanged() \
{ \
edit_reg_##type->setEnabled( type##_match->currentIndex() == Rules::RegExpMatch ); \
type->setEnabled( type##_match->currentIndex() != Rules::UnimportantMatch ); \
}
STRING_MATCH_COMBO(wmclass)
STRING_MATCH_COMBO(role)
STRING_MATCH_COMBO(title)
STRING_MATCH_COMBO(machine)
#undef STRING_MATCH_COMBO
void RulesWidget::detectClicked()
{
Q_ASSERT(detect_dlg == nullptr);
detect_dlg = new DetectDialog;
connect(detect_dlg, SIGNAL(detectionDone(bool)), this, SLOT(detected(bool)));
detect_dlg->detect(Ui::RulesWidgetBase::detection_delay->value());
Ui::RulesWidgetBase::detect->setEnabled(false);
}
void RulesWidget::detected(bool ok)
{
if (ok) {
wmclass->setText(detect_dlg->selectedClass());
wmclass_match->setCurrentIndex(Rules::ExactMatch);
wmclassMatchChanged(); // grrr
whole_wmclass->setChecked(detect_dlg->selectedWholeClass());
role->setText(detect_dlg->selectedRole());
role_match->setCurrentIndex(detect_dlg->selectedRole().isEmpty()
? Rules::UnimportantMatch : Rules::ExactMatch);
roleMatchChanged();
if (detect_dlg->selectedWholeApp()) {
for (int i = 0;
i < types->count();
++i)
types->item(i)->setSelected(true);
} else {
NET::WindowType type = detect_dlg->selectedType();
for (int i = 0;
i < types->count();
++i)
types->item(i)->setSelected(false);
types->item(typeToCombo(type))->setSelected(true);
}
title->setText(detect_dlg->selectedTitle());
title_match->setCurrentIndex(detect_dlg->titleMatch());
titleMatchChanged();
machine->setText(detect_dlg->selectedMachine());
machine_match->setCurrentIndex(Rules::UnimportantMatch);
machineMatchChanged();
// prefill values from to window to settings which already set
prefillUnusedValues(detect_dlg->windowInfo());
}
delete detect_dlg;
detect_dlg = nullptr;
detect_dlg_ok = ok;
Ui::RulesWidgetBase::detect->setEnabled(true);
}
#define GENERIC_PREFILL( var, func, info, uimethod ) \
if ( !enable_##var->isChecked()) \
{ \
Ui::RulesWidgetBase::var->uimethod( func( info )); \
}
#define CHECKBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setChecked )
#define LINEEDIT_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setText )
#define COMBOBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setCurrentIndex )
#define SPINBOX_PREFILL( var, func, info ) GENERIC_PREFILL( var, func, info, setValue )
void RulesWidget::prefillUnusedValues(const QVariantMap& info)
{
const QSize windowSize{info.value("width").toInt(), info.value("height").toInt()};
LINEEDIT_PREFILL(position, positionToStr, QPoint(info.value("x").toInt(), info.value("y").toInt()));
LINEEDIT_PREFILL(size, sizeToStr, windowSize);
COMBOBOX_PREFILL(desktop, desktopToCombo, info.value("x11DesktopNumber").toInt());
// COMBOBOX_PREFILL(activity, activityToCombo, info.activity()); // TODO: ivan
CHECKBOX_PREFILL(maximizehoriz, , info.value("maximizeHorizontal").toBool());
CHECKBOX_PREFILL(maximizevert, , info.value("maximizeVertical").toBool());
CHECKBOX_PREFILL(minimize, , info.value("minimized").toBool());
CHECKBOX_PREFILL(shade, , info.value("shaded").toBool());
CHECKBOX_PREFILL(fullscreen, , info.value("fullscreen").toBool());
//COMBOBOX_PREFILL( placement, placementToCombo );
CHECKBOX_PREFILL(above, , info.value("keepAbove").toBool());
CHECKBOX_PREFILL(below, , info.value("keepBelow").toBool());
CHECKBOX_PREFILL(noborder, , info.value("noBorder").toBool());
CHECKBOX_PREFILL(skiptaskbar, , info.value("skipTaskbar").toBool());
CHECKBOX_PREFILL(skippager, , info.value("skipPager").toBool());
CHECKBOX_PREFILL(skipswitcher, , info.value("skipSwitcher").toBool());
//CHECKBOX_PREFILL( acceptfocus, );
//CHECKBOX_PREFILL( closeable, );
//CHECKBOX_PREFILL( autogroup, );
//CHECKBOX_PREFILL( autogroupfg, );
//LINEEDIT_PREFILL( autogroupid, );
SPINBOX_PREFILL(opacityactive, , 100 /*get the actual opacity somehow*/);
SPINBOX_PREFILL(opacityinactive, , 100 /*get the actual opacity somehow*/);
//LINEEDIT_PREFILL( shortcut, );
//COMBOBOX_PREFILL( fsplevel, );
//COMBOBOX_PREFILL( fpplevel, );
COMBOBOX_PREFILL(type, typeToCombo, info.value("type").value<NET::WindowType>());
//CHECKBOX_PREFILL( ignoregeometry, );
LINEEDIT_PREFILL(minsize, sizeToStr, windowSize);
LINEEDIT_PREFILL(maxsize, sizeToStr, windowSize);
//CHECKBOX_PREFILL( strictgeometry, );
//CHECKBOX_PREFILL( disableglobalshortcuts, );
//CHECKBOX_PREFILL( blockcompositing, );
LINEEDIT_PREFILL(desktopfile, , info.value("desktopFile").toString());
}
#undef GENERIC_PREFILL
#undef CHECKBOX_PREFILL
#undef LINEEDIT_PREFILL
#undef COMBOBOX_PREFILL
#undef SPINBOX_PREFILL
bool RulesWidget::finalCheck()
{
if (description->text().isEmpty()) {
if (!wmclass->text().isEmpty())
description->setText(i18n("Settings for %1", wmclass->text()));
else
description->setText(i18n("Unnamed entry"));
}
bool all_types = true;
for (int i = 0;
i < types->count();
++i)
if (!types->item(i)->isSelected())
all_types = false;
if (wmclass_match->currentIndex() == Rules::UnimportantMatch && all_types) {
if (KMessageBox::warningContinueCancel(window(),
i18n("You have specified the window class as unimportant.\n"
"This means the settings will possibly apply to windows from all applications. "
"If you really want to create a generic setting, it is recommended you at least "
"limit the window types to avoid special window types.")) != KMessageBox::Continue)
return false;
}
return true;
}
void RulesWidget::prepareWindowSpecific(const QVariantMap &info)
{
tabs->setCurrentIndex(1); // geometry tab, skip tab for window identification
prefillUnusedValues(info);
}
void RulesWidget::shortcutEditClicked()
{
QPointer<EditShortcutDialog> dlg = new EditShortcutDialog(window());
dlg->setShortcut(shortcut->text());
if (dlg->exec() == QDialog::Accepted)
shortcut->setText(dlg->shortcut());
delete dlg;
}
RulesDialog::RulesDialog(QWidget* parent, const char* name)
: QDialog(parent)
{
setObjectName(name);
setModal(true);
setWindowTitle(i18n("Edit Window-Specific Settings"));
setWindowIcon(QIcon::fromTheme("preferences-system-windows-actions"));
setLayout(new QVBoxLayout);
widget = new RulesWidget(this);
layout()->addWidget(widget);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout()->addWidget(buttons);
}
// window is set only for Alt+F3/Window-specific settings, because the dialog
// is then related to one specific window
Rules* RulesDialog::edit(Rules* r, const QVariantMap& info, bool show_hints)
{
rules = r;
widget->setRules(rules);
if (!info.isEmpty())
{
widget->prepareWindowSpecific(info);
}
if (show_hints)
QTimer::singleShot(0, this, SLOT(displayHints()));
exec();
return rules;
}
void RulesDialog::displayHints()
{
QString str = "<qt><p>";
str += i18n("This configuration dialog allows altering settings only for the selected window"
" or application. Find the setting you want to affect, enable the setting using the checkbox,"
" select in what way the setting should be affected and to which value.");
#if 0 // maybe later
str += "</p><p>" + i18n("Consult the documentation for more details.");
#endif
str += "</p></qt>";
KMessageBox::information(this, str, QString(), "displayhints");
}
void RulesDialog::accept()
{
if (!widget->finalCheck())
return;
rules = widget->rules();
QDialog::accept();
}
EditShortcut::EditShortcut(QWidget* parent)
: QWidget(parent)
{
setupUi(this);
}
void EditShortcut::editShortcut()
{
QPointer< ShortcutDialog > dlg = new ShortcutDialog(QKeySequence(shortcut->text()), window());
if (dlg->exec() == QDialog::Accepted)
shortcut->setText(dlg->shortcut().toString());
delete dlg;
}
void EditShortcut::clearShortcut()
{
shortcut->clear();
}
EditShortcutDialog::EditShortcutDialog(QWidget* parent, const char* name)
: QDialog(parent)
, widget(new EditShortcut(this))
{
setObjectName(name);
setModal(true);
setWindowTitle(i18n("Edit Shortcut"));
setLayout(new QVBoxLayout);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
layout()->addWidget(widget);
layout()->addWidget(buttons);
}
void EditShortcutDialog::setShortcut(const QString& cut)
{
widget->shortcut->setText(cut);
}
QString EditShortcutDialog::shortcut() const
{
return widget->shortcut->text();
}
ShortcutDialog::ShortcutDialog(const QKeySequence& cut, QWidget* parent)
: QDialog(parent)
, widget(new KKeySequenceWidget(this))
{
widget->setKeySequence(cut);
// It's a global shortcut so don't allow multikey shortcuts
widget->setMultiKeyShortcutsAllowed(false);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
setLayout(new QVBoxLayout);
layout()->addWidget(widget);
layout()->addWidget(buttons);
}
void ShortcutDialog::accept()
{
QKeySequence seq = shortcut();
if (!seq.isEmpty()) {
if (seq[0] == Qt::Key_Escape) {
reject();
return;
}
if (seq[0] == Qt::Key_Space
|| (seq[0] & Qt::KeyboardModifierMask) == 0) {
// clear
widget->clearKeySequence();
QDialog::accept();
return;
}
}
QDialog::accept();
}
QKeySequence ShortcutDialog::shortcut() const
{
return widget->keySequence();
}
} // namespace

View File

@ -1,178 +0,0 @@
/*
* Copyright (c) 2004 Lubos Lunak <l.lunak@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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __RULESWIDGET_H__
#define __RULESWIDGET_H__
#include <config-kwin.h>
#include <QDialog>
#include <kkeysequencewidget.h>
#include "ui_ruleswidgetbase.h"
#include "ui_editshortcut.h"
#ifdef KWIN_BUILD_ACTIVITIES
namespace KActivities {
class Consumer;
} // namespace KActivities
#endif
namespace KWin
{
class Rules;
class DetectDialog;
class RulesWidget
: public QWidget, public Ui::RulesWidgetBase
{
Q_OBJECT
public:
explicit RulesWidget(QWidget* parent = nullptr);
void setRules(Rules* r);
Rules* rules() const;
bool finalCheck();
void prepareWindowSpecific(const QVariantMap &info);
Q_SIGNALS:
void changed(bool state);
protected Q_SLOTS:
void detectClicked();
void wmclassMatchChanged();
void roleMatchChanged();
void titleMatchChanged();
void machineMatchChanged();
void shortcutEditClicked();
private Q_SLOTS:
// geometry tab
void updateEnableposition();
void updateEnablesize();
void updateEnabledesktop();
void updateEnablescreen();
#ifdef KWIN_BUILD_ACTIVITIES
void updateEnableactivity();
#endif
void updateEnablemaximizehoriz();
void updateEnablemaximizevert();
void updateEnableminimize();
void updateEnableshade();
void updateEnablefullscreen();
void updateEnableplacement();
// preferences tab
void updateEnableabove();
void updateEnablebelow();
void updateEnablenoborder();
void updateEnabledecocolor();
void updateEnableskiptaskbar();
void updateEnableskippager();
void updateEnableskipswitcher();
void updateEnableacceptfocus();
void updateEnablecloseable();
void updateEnableautogroup();
void updateEnableautogroupfg();
void updateEnableautogroupid();
void updateEnableopacityactive();
void updateEnableopacityinactive();
// workarounds tab
void updateEnablefsplevel();
void updateEnablefpplevel();
void updateEnabletype();
void updateEnableignoregeometry();
void updateEnableminsize();
void updateEnablemaxsize();
void updateEnablestrictgeometry();
void updateEnableshortcut();
void updateEnabledisableglobalshortcuts();
void updateEnableblockcompositing();
void updateEnabledesktopfile();
// internal
void detected(bool);
private:
int desktopToCombo(int d) const;
int comboToDesktop(int val) const;
#ifdef KWIN_BUILD_ACTIVITIES
int activityToCombo(const QString &d) const;
QString comboToActivity(int val) const;
void updateActivitiesList();
KActivities::Consumer *m_activities;
QString m_selectedActivityId; // we need this for async activity loading
#endif
int comboToTiling(int val) const;
int inc(int i) const { return i+1; }
int dec(int i) const { return i-1; }
void prefillUnusedValues(const QVariantMap& info);
DetectDialog* detect_dlg;
bool detect_dlg_ok;
};
class RulesDialog
: public QDialog
{
Q_OBJECT
public:
explicit RulesDialog(QWidget* parent = nullptr, const char* name = nullptr);
Rules* edit(Rules* r, const QVariantMap& info, bool show_hints);
protected:
void accept() override;
private Q_SLOTS:
void displayHints();
private:
RulesWidget* widget;
Rules* rules;
};
class EditShortcut
: public QWidget, public Ui_EditShortcut
{
Q_OBJECT
public:
explicit EditShortcut(QWidget* parent = nullptr);
protected Q_SLOTS:
void editShortcut();
void clearShortcut();
};
class EditShortcutDialog
: public QDialog
{
Q_OBJECT
public:
explicit EditShortcutDialog(QWidget* parent = nullptr, const char* name = nullptr);
void setShortcut(const QString& cut);
QString shortcut() const;
private:
EditShortcut* widget;
};
// slightly duped from utils.cpp
class ShortcutDialog
: public QDialog
{
Q_OBJECT
public:
explicit ShortcutDialog(const QKeySequence& cut, QWidget* parent = nullptr);
void accept() override;
QKeySequence shortcut() const;
private:
KKeySequenceWidget* widget;
};
} // namespace
#endif

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
/*
* Copyright (c) 2011 Thomas Lübking <thomas.luebking@web.de>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "yesnobox.h"
YesNoBox::YesNoBox( QWidget *parent )
: QWidget(parent)
{
QHBoxLayout *l = new QHBoxLayout(this);
l->setContentsMargins(0, 0, 0, 0);
l->addWidget(yes = new QRadioButton(i18n("Yes"), this));
l->addWidget(no = new QRadioButton(i18n("No"), this));
l->addStretch(100);
no->setChecked(true);
connect(yes, SIGNAL(clicked(bool)), this, SIGNAL(clicked(bool)));
connect(yes, SIGNAL(toggled(bool)), this, SIGNAL(toggled(bool)));
connect(no, SIGNAL(clicked(bool)), this, SLOT(noClicked(bool)));
}

View File

@ -1,46 +0,0 @@
/*
* Copyright (c) 2011 Thomas Lübking <thomas.luebking@web.de>
*
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef YESNOBOX_H
#define YESNOBOX_H
#include <QHBoxLayout>
#include <QRadioButton>
#include <KLocalizedString>
class YesNoBox : public QWidget {
Q_OBJECT
public:
explicit YesNoBox( QWidget *parent );
bool isChecked() { return yes->isChecked(); }
public Q_SLOTS:
void setChecked(bool b) { yes->setChecked(b); }
void toggle() { yes->toggle(); }
Q_SIGNALS:
void clicked(bool checked = false);
void toggled(bool checked);
private Q_SLOTS:
void noClicked(bool checked) { emit clicked(!checked); }
private:
QRadioButton *yes, *no;
};
#endif // YESNOBOX_H