Changeset View
Standalone View
kcmkwin/kwinrules/kcmrules.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright (c) 2004 Lubos Lunak <l.lunak@kde.org> | ||||
3 | * Copyright (c) 2020 Ismael Asensio <isma.af@gmail.com> | ||||
4 | * | ||||
5 | * This program is free software; you can redistribute it and/or | ||||
6 | * modify it under the terms of the GNU General Public License as | ||||
7 | * published by the Free Software Foundation; either version 2 of | ||||
8 | * the License or (at your option) version 3 or any later version | ||||
9 | * accepted by the membership of KDE e.V. (or its successor approved | ||||
10 | * by the membership of KDE e.V.), which shall act as a proxy | ||||
11 | * defined in Section 14 of version 3 of the license. | ||||
12 | * | ||||
13 | * This program is distributed in the hope that it will be useful, | ||||
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
16 | * GNU General Public License for more details. | ||||
17 | * | ||||
18 | * You should have received a copy of the GNU General Public License | ||||
19 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
20 | */ | ||||
21 | | ||||
22 | #include "kcmrules.h" | ||||
23 | | ||||
24 | #include <QDBusConnection> | ||||
25 | #include <QDBusMessage> | ||||
broulik: Don't include the entire module, just include whatever `QDBus` class you're using, but it… | |||||
It's used in the save() method, to make kwin reload the configuration iasensio: It's used in the `save()` method, to make kwin reload the configuration | |||||
26 | | ||||
27 | #include <KAboutData> | ||||
28 | #include <KConfig> | ||||
29 | #include <KLocalizedString> | ||||
30 | #include <KPluginFactory> | ||||
31 | | ||||
32 | | ||||
33 | namespace KWin | ||||
34 | { | ||||
35 | | ||||
36 | KCMKWinRules::KCMKWinRules(QObject *parent, const QVariantList &arguments) | ||||
37 | : KQuickAddons::ConfigModule(parent, arguments) | ||||
38 | , m_ruleBook(new RuleBookSettings(this)) | ||||
39 | , m_rulesModel(new RulesModel(this)) | ||||
40 | { | ||||
41 | auto about = new KAboutData(QStringLiteral("kcm_kwinrules"), | ||||
42 | i18n("Window Rules"), | ||||
43 | QStringLiteral("1.0"), | ||||
44 | QString(), | ||||
45 | KAboutLicense::GPL); | ||||
46 | about->addAuthor(i18n("Ismael Asensio"), | ||||
47 | i18n("Author"), | ||||
48 | QStringLiteral("isma.af@gmail.com")); | ||||
49 | setAboutData(about); | ||||
50 | | ||||
51 | setQuickHelp(i18n("<p><h1>Window-specific Settings</h1> Here you can customize window settings specifically only" | ||||
52 | " for some windows.</p>" | ||||
53 | " <p>Please note that this configuration will not take effect if you do not use" | ||||
54 | " KWin as your window manager. If you do use a different window manager, please refer to its documentation" | ||||
55 | " for how to customize window behavior.</p>")); | ||||
56 | | ||||
57 | connect(m_rulesModel, &RulesModel::descriptionChanged, this, [this]{ | ||||
58 | if (m_editingIndex >=0 && m_editingIndex < m_ruleBook->count()) { | ||||
59 | m_rules.at(m_editingIndex)->description = m_rulesModel->description(); | ||||
60 | emit ruleBookModelChanged(); | ||||
61 | } | ||||
62 | } ); | ||||
63 | connect(m_rulesModel, &RulesModel::dataChanged, this, &KCMKWinRules::updateNeedsSave); | ||||
64 | } | ||||
65 | | ||||
66 | KCMKWinRules::~KCMKWinRules() { | ||||
67 | qDeleteAll(m_rules); | ||||
68 | } | ||||
69 | | ||||
70 | | ||||
71 | QStringList KCMKWinRules::ruleBookModel() const | ||||
72 | { | ||||
73 | QStringList ruleDescriptionList; | ||||
You don't need to delete this manually. You pass this as parent in the constructor and Qt has this parent-child releationship where a parent brutally murders its children on desturction. broulik: You don't need to `delete` this manually. You pass `this` as `parent` in the constructor and Qt… | |||||
74 | for (const Rules *rule : qAsConst(m_rules)) { | ||||
75 | ruleDescriptionList.append(rule->description); | ||||
76 | } | ||||
77 | return ruleDescriptionList; | ||||
78 | } | ||||
79 | | ||||
80 | | ||||
81 | void KCMKWinRules::load() | ||||
82 | { | ||||
83 | m_ruleBook->load(); | ||||
84 | m_rules = m_ruleBook->rules(); | ||||
85 | | ||||
86 | setNeedsSave(false); | ||||
87 | emit ruleBookModelChanged(); | ||||
88 | | ||||
89 | // Check if current index is no longer valid | ||||
90 | if (m_editingIndex >= m_rules.count()) { | ||||
91 | m_editingIndex = -1; | ||||
broulik: Why not keep it a `QVector`? We generally try to move away from `QList` | |||||
92 | pop(); | ||||
93 | emit editingIndexChanged(); | ||||
94 | } | ||||
95 | // Reset current index for rule editor | ||||
96 | if (m_editingIndex > 0) { | ||||
97 | m_rulesModel->importFromRules(m_rules.at(m_editingIndex)); | ||||
98 | } | ||||
99 | } | ||||
100 | | ||||
101 | void KCMKWinRules::save() | ||||
102 | { | ||||
103 | saveCurrentRule(); | ||||
104 | m_ruleBook->setRules(m_rules); | ||||
105 | m_ruleBook->save(); | ||||
106 | | ||||
107 | // Notify kwin to reload configuration | ||||
108 | QDBusMessage message = QDBusMessage::createSignal("/KWin", "org.kde.KWin", "reloadConfig"); | ||||
109 | QDBusConnection::sessionBus().send(message); | ||||
110 | } | ||||
111 | | ||||
112 | void KCMKWinRules::updateState() | ||||
113 | { | ||||
114 | m_ruleBook->setCount(m_rules.count()); | ||||
115 | | ||||
116 | emit editingIndexChanged(); | ||||
117 | emit ruleBookModelChanged(); | ||||
118 | | ||||
119 | updateNeedsSave(); | ||||
120 | } | ||||
121 | | ||||
122 | void KCMKWinRules::updateNeedsSave() | ||||
123 | { | ||||
124 | setNeedsSave(true); | ||||
125 | emit needsSaveChanged(); | ||||
126 | } | ||||
127 | | ||||
128 | void KCMKWinRules::saveCurrentRule() | ||||
129 | { | ||||
130 | if (m_editingIndex < 0) { | ||||
131 | return; | ||||
132 | } | ||||
133 | if (needsSave()) { | ||||
134 | delete(m_rules[m_editingIndex]); | ||||
135 | m_rules[m_editingIndex] = m_rulesModel->exportToRules(); | ||||
136 | } | ||||
137 | } | ||||
138 | | ||||
139 | | ||||
140 | int KCMKWinRules::editingIndex() const | ||||
141 | { | ||||
broulik: Could this be done in-memory without a temp file? | |||||
Yes, I want to talk to @hchain and see to expand the RuleBookSettings class to work with a list of RuleSettings objects instead of Rules, and avoid this intermediate step. iasensio: Yes, I want to talk to @hchain and see to expand the `RuleBookSettings` class to work with a… | |||||
142 | return m_editingIndex; | ||||
143 | } | ||||
144 | | ||||
145 | void KCMKWinRules::editRule(int index) | ||||
146 | { | ||||
147 | if (index < 0 || index >= m_rules.count()) { | ||||
148 | return; | ||||
149 | } | ||||
150 | saveCurrentRule(); | ||||
151 | | ||||
152 | m_editingIndex = index; | ||||
153 | m_rulesModel->importFromRules(m_rules.at(m_editingIndex)); | ||||
154 | | ||||
155 | emit editingIndexChanged(); | ||||
156 | | ||||
157 | // Show and move to Rules Editor page | ||||
158 | if (depth() < 2) { | ||||
159 | push(QStringLiteral("RulesEditor.qml")); | ||||
160 | } | ||||
161 | setCurrentIndex(1); | ||||
162 | } | ||||
163 | | ||||
164 | void KCMKWinRules::setRuleDescription(int index, const QString &description) | ||||
165 | { | ||||
166 | if (index < 0 || index >= m_rules.count() | ||||
167 | || (description == m_rules.at(index)->description)) { | ||||
168 | return; | ||||
169 | } | ||||
170 | | ||||
171 | if (index == m_editingIndex) { | ||||
172 | m_rulesModel->setDescription(description); | ||||
173 | return; | ||||
Looks like you're leaking the rules? Perhaps do a qDeleteAll(m_ruleList) in the destructor broulik: Looks like you're leaking the rules? Perhaps do a
```
qDeleteAll(m_ruleList)
```
in the… | |||||
174 | } | ||||
175 | | ||||
176 | m_rules.at(index)->description = description; | ||||
177 | | ||||
178 | emit ruleBookModelChanged(); | ||||
179 | updateNeedsSave(); | ||||
180 | } | ||||
181 | | ||||
182 | | ||||
183 | void KCMKWinRules::createRule() | ||||
184 | { | ||||
I think doing if (index < 0 || index >= m_ruleList.count()) { return; } is clear enough broulik: I think doing
```
if (index < 0 || index >= m_ruleList.count()) {
return;
}
```
is clear… | |||||
185 | | ||||
186 | m_rules.append(new Rules()); | ||||
187 | updateState(); | ||||
188 | | ||||
189 | const int newIndex = m_rules.count() - 1; | ||||
190 | editRule(newIndex); | ||||
191 | | ||||
192 | saveCurrentRule(); | ||||
193 | } | ||||
194 | | ||||
195 | void KCMKWinRules::removeRule(int index) | ||||
196 | { | ||||
197 | if (index < 0 || index >= m_rules.count()) { | ||||
198 | return; | ||||
199 | } | ||||
200 | | ||||
201 | if (m_editingIndex == index) { | ||||
202 | m_editingIndex = -1; | ||||
203 | pop(); | ||||
204 | } | ||||
205 | | ||||
206 | delete(m_rules.at(index)); | ||||
207 | m_rules.removeAt(index); | ||||
208 | | ||||
209 | updateState(); | ||||
210 | } | ||||
211 | | ||||
212 | void KCMKWinRules::moveRule(int sourceIndex, int destIndex) | ||||
213 | { | ||||
214 | const int lastIndex = m_rules.count() - 1; | ||||
215 | if (sourceIndex == destIndex | ||||
216 | || (sourceIndex < 0 || sourceIndex > lastIndex) | ||||
217 | || (destIndex < 0 || destIndex > lastIndex)) { | ||||
218 | return; | ||||
219 | } | ||||
220 | | ||||
221 | m_rules.move(sourceIndex, destIndex); | ||||
222 | | ||||
223 | if (m_editingIndex == sourceIndex) { | ||||
224 | m_editingIndex = destIndex; | ||||
225 | emit editingIndexChanged(); | ||||
226 | } else if (m_editingIndex > sourceIndex && m_editingIndex <= destIndex) { | ||||
227 | m_editingIndex -= 1; | ||||
228 | emit editingIndexChanged(); | ||||
229 | } else if (m_editingIndex < sourceIndex && m_editingIndex >= destIndex) { | ||||
230 | m_editingIndex += 1; | ||||
231 | emit editingIndexChanged(); | ||||
232 | } | ||||
233 | | ||||
234 | emit ruleBookModelChanged(); | ||||
235 | | ||||
236 | updateNeedsSave(); | ||||
237 | } | ||||
238 | | ||||
239 | void KCMKWinRules::exportToFile(const QUrl &path, int index) | ||||
240 | { | ||||
241 | Q_ASSERT(index >= 0 && index < m_rules.count()); | ||||
242 | | ||||
243 | saveCurrentRule(); | ||||
244 | | ||||
245 | const auto config = KSharedConfig::openConfig(path.toLocalFile(), KConfig::SimpleConfig); | ||||
246 | RuleSettings settings(config, m_rules.at(index)->description); | ||||
247 | | ||||
Don't use this in conjunction with QML! This creates a nested event loop which will cause all sorts of hard to find crashes. You can probably just do the file selection on the QML side, like we do in most other settings modules: import QtQuick.Dialogs 1.0 as QtDialogs Loader { id: fileDialogLoader active: false sourceComponent: QtDialogs.FileDialog { title: i18n("Import Rule") folder: shortcuts.home nameFilters: [ i18n("KWin Rules (*.kwinrule)") ] Component.onCompleted: open() onAccepted: { kcm.importRuleFromFile(fileUrls[0]) fileDialogLoader.active = false } onRejected: fileDialogLoader.active = false } } This is copied from another KCM and adjusted. It's behind a Loader as creation of this item is pretty heavy, so we want to just do it on demand. And on the import button you just do onClicked: fileDialogLoader.active = true. Perhaps you could share it for open and save, or just use two separate ones. I'm sure you'll figure it out :) broulik: Don't use this in conjunction with QML! This creates a nested event loop which will cause all… | |||||
248 | settings.setDefaults(); | ||||
249 | m_rules.at(index)->write(&settings); | ||||
250 | settings.save(); | ||||
251 | } | ||||
252 | | ||||
253 | void KCMKWinRules::importFromFile(const QUrl &path) | ||||
254 | { | ||||
255 | const auto config = KSharedConfig::openConfig(path.toLocalFile(), KConfig::SimpleConfig); | ||||
256 | const QStringList groups = config->groupList(); | ||||
257 | if (groups.isEmpty()) { | ||||
258 | return; | ||||
259 | } | ||||
260 | | ||||
261 | for (const QString &groupName : groups) { | ||||
262 | RuleSettings settings(config, groupName); | ||||
263 | | ||||
264 | const bool remove = settings.deleteRule(); | ||||
265 | const QString importDescription = settings.description(); | ||||
266 | if (importDescription.isEmpty()) { | ||||
267 | continue; | ||||
268 | } | ||||
269 | | ||||
270 | // Try to find a rule with the same description to replace | ||||
271 | int newIndex = -2; | ||||
272 | for (int index = 0; index < m_rules.count(); index++) { | ||||
273 | if (m_rules.at(index)->description == importDescription) { | ||||
274 | newIndex = index; | ||||
275 | break; | ||||
276 | } | ||||
277 | } | ||||
278 | | ||||
279 | if (remove) { | ||||
280 | removeRule(newIndex); | ||||
281 | continue; | ||||
282 | } | ||||
283 | | ||||
284 | Rules *newRule = new Rules(&settings); | ||||
285 | | ||||
286 | if (newIndex < 0) { | ||||
287 | m_rules.append(newRule); | ||||
288 | } else { | ||||
289 | delete m_rules[newIndex]; | ||||
290 | m_rules[newIndex] = newRule; | ||||
291 | } | ||||
292 | | ||||
293 | // Reset rule editor if the current rule changed when importing | ||||
294 | if (m_editingIndex == newIndex) { | ||||
295 | m_rulesModel->importFromRules(m_rules.at(m_editingIndex)); | ||||
296 | } | ||||
297 | } | ||||
298 | | ||||
299 | updateState(); | ||||
300 | } | ||||
301 | | ||||
302 | K_PLUGIN_CLASS_WITH_JSON(KCMKWinRules, "kcm_kwinrules.json"); | ||||
303 | | ||||
304 | } // namespace | ||||
305 | | ||||
306 | #include "kcmrules.moc" |
Don't include the entire module, just include whatever QDBus class you're using, but it appears unused in this file anyway?