Changeset View
Standalone View
kcmkwin/kwindesktop/desktopsmodel.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright (C) 2018 Eike Hein <hein@kde.org> | ||||
3 | * Copyright (C) 2018 Marco Martin <mart@kde.org> | ||||
4 | * | ||||
5 | * This program is free software; you can redistribute it and/or modify | ||||
6 | * it under the terms of the GNU General Public License as published by | ||||
7 | * the Free Software Foundation; either version 2 of the License, or | ||||
8 | * (at your option) any later version. | ||||
9 | * | ||||
10 | * This program is distributed in the hope that it will be useful, | ||||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
13 | * GNU General Public License for more details. | ||||
14 | * | ||||
15 | * You should have received a copy of the GNU General Public License | ||||
16 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
17 | */ | ||||
18 | | ||||
19 | #include "desktopsmodel.h" | ||||
20 | | ||||
21 | #include <cmath> | ||||
22 | | ||||
23 | #include <KLocalizedString> | ||||
24 | | ||||
25 | #include <QDBusArgument> | ||||
26 | #include <QDBusConnection> | ||||
27 | #include <QDBusMessage> | ||||
28 | #include <QDBusMetaType> | ||||
29 | #include <QDBusPendingCall> | ||||
30 | #include <QDBusPendingCallWatcher> | ||||
31 | #include <QDBusPendingReply> | ||||
32 | #include <QDBusServiceWatcher> | ||||
33 | #include <QDBusVariant> | ||||
34 | #include <QMetaEnum> | ||||
35 | #include <QUuid> | ||||
36 | | ||||
37 | namespace KWin | ||||
38 | { | ||||
zzagUnsubmitted Not Done zzag: ```lang=cpp
namespace KWin
{
``` | |||||
39 | | ||||
40 | static const QString s_serviceName(QStringLiteral("org.kde.KWin")); | ||||
41 | static const QString s_virtualDesktopsInterface(QStringLiteral("org.kde.KWin.VirtualDesktopManager")); | ||||
42 | static const QString s_virtDesktopsPath(QStringLiteral("/VirtualDesktopManager")); | ||||
43 | static const QString s_fdoPropertiesInterface(QStringLiteral("org.freedesktop.DBus.Properties")); | ||||
44 | | ||||
45 | DesktopsModel::DesktopsModel(QObject *parent) | ||||
46 | : QAbstractListModel(parent) | ||||
47 | , m_userModified(false) | ||||
48 | , m_serverModified(false) | ||||
49 | , m_serverSideRows(-1) | ||||
50 | , m_rows(-1) | ||||
51 | , m_synchronizing(false) | ||||
52 | { | ||||
53 | qDBusRegisterMetaType<KWin::DBusDesktopDataStruct>(); | ||||
54 | qDBusRegisterMetaType<KWin::DBusDesktopDataVector>(); | ||||
55 | | ||||
56 | m_serviceWatcher = new QDBusServiceWatcher(s_serviceName, | ||||
57 | QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange); | ||||
58 | | ||||
59 | QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceRegistered, | ||||
60 | this, [this]() { reset(); }); | ||||
61 | | ||||
62 | QObject::connect(m_serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, | ||||
63 | this, [this]() { | ||||
64 | QDBusConnection::sessionBus().disconnect( | ||||
65 | s_serviceName, | ||||
66 | s_virtDesktopsPath, | ||||
67 | s_virtualDesktopsInterface, | ||||
68 | QStringLiteral("desktopCreated"), | ||||
69 | this, | ||||
70 | SLOT(desktopCreated(QString,KWin::DBusDesktopDataStruct))); | ||||
71 | | ||||
72 | QDBusConnection::sessionBus().disconnect( | ||||
73 | s_serviceName, | ||||
74 | s_virtDesktopsPath, | ||||
75 | s_virtualDesktopsInterface, | ||||
76 | QStringLiteral("desktopRemoved"), | ||||
77 | this, | ||||
78 | SLOT(desktopRemoved(QString))); | ||||
79 | | ||||
80 | QDBusConnection::sessionBus().disconnect( | ||||
81 | s_serviceName, | ||||
82 | s_virtDesktopsPath, | ||||
83 | s_virtualDesktopsInterface, | ||||
84 | QStringLiteral("desktopDataChanged"), | ||||
85 | this, | ||||
86 | SLOT(desktopDataChanged(QString,KWin::DBusDesktopDataStruct))); | ||||
87 | | ||||
88 | | ||||
89 | QDBusConnection::sessionBus().disconnect( | ||||
90 | s_serviceName, | ||||
91 | s_virtDesktopsPath, | ||||
92 | s_virtualDesktopsInterface, | ||||
93 | QStringLiteral("rowsChanged"), | ||||
94 | this, | ||||
95 | SLOT(desktopRowsChanged(uint))); | ||||
96 | } | ||||
97 | ); | ||||
98 | | ||||
99 | reset(); | ||||
100 | } | ||||
101 | | ||||
102 | DesktopsModel::~DesktopsModel() | ||||
103 | { | ||||
104 | } | ||||
105 | | ||||
106 | QHash<int, QByteArray> DesktopsModel::roleNames() const | ||||
107 | { | ||||
108 | QHash<int, QByteArray> roles = QAbstractItemModel::roleNames(); | ||||
109 | | ||||
110 | QMetaEnum e = metaObject()->enumerator(metaObject()->indexOfEnumerator("AdditionalRoles")); | ||||
111 | | ||||
112 | for (int i = 0; i < e.keyCount(); ++i) { | ||||
113 | roles.insert(e.value(i), e.key(i)); | ||||
114 | } | ||||
115 | | ||||
116 | return roles; | ||||
117 | } | ||||
118 | | ||||
119 | QVariant DesktopsModel::data(const QModelIndex &index, int role) const | ||||
120 | { | ||||
121 | if (!index.isValid() || index.row() < 0 || index.row() > (m_desktops.count() - 1)) { | ||||
122 | return QVariant(); | ||||
123 | } | ||||
124 | | ||||
125 | if (role == Qt::DisplayRole) { | ||||
126 | return m_names.value(m_desktops.at(index.row())); | ||||
127 | } else if (role == Id) { | ||||
128 | return m_desktops.at(index.row()); | ||||
129 | } else if (role == DesktopRow) { | ||||
130 | const int rows = std::max(m_rows, 1); | ||||
131 | const int perRow = std::ceil((qreal)m_desktops.count() / (qreal)rows); | ||||
132 | | ||||
133 | return (index.row() / perRow) + 1; | ||||
134 | | ||||
135 | } | ||||
136 | | ||||
137 | return QVariant(); | ||||
138 | } | ||||
139 | | ||||
140 | int DesktopsModel::rowCount(const QModelIndex &parent) const | ||||
141 | { | ||||
142 | if (parent.isValid()) { | ||||
143 | return 0; | ||||
144 | } | ||||
145 | | ||||
146 | return m_desktops.count(); | ||||
147 | } | ||||
148 | | ||||
149 | bool DesktopsModel::ready() const | ||||
150 | { | ||||
151 | return !m_desktops.isEmpty(); | ||||
152 | } | ||||
153 | | ||||
154 | QString DesktopsModel::error() const | ||||
155 | { | ||||
156 | return m_error; | ||||
157 | } | ||||
158 | | ||||
159 | bool DesktopsModel::userModified() const | ||||
160 | { | ||||
161 | return m_userModified; | ||||
162 | } | ||||
163 | | ||||
164 | bool DesktopsModel::serverModified() const | ||||
165 | { | ||||
166 | return m_serverModified; | ||||
167 | } | ||||
168 | | ||||
169 | int DesktopsModel::rows() const | ||||
170 | { | ||||
171 | return m_rows; | ||||
172 | } | ||||
173 | | ||||
174 | void DesktopsModel::setRows(int rows) | ||||
175 | { | ||||
176 | if (!ready()) { | ||||
177 | return; | ||||
178 | } | ||||
179 | | ||||
180 | if (m_rows != rows) { | ||||
181 | m_rows = rows; | ||||
182 | | ||||
183 | emit rowsChanged(); | ||||
184 | emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector<int>{DesktopRow}); | ||||
185 | | ||||
186 | updateModifiedState(); | ||||
187 | } | ||||
188 | } | ||||
189 | | ||||
190 | void DesktopsModel::createDesktop(const QString &name) | ||||
191 | { | ||||
192 | if (!ready()) { | ||||
193 | return; | ||||
194 | } | ||||
195 | | ||||
196 | beginInsertRows(QModelIndex(), m_desktops.count(), m_desktops.count()); | ||||
197 | | ||||
198 | const QString &dummyId = QUuid::createUuid().toString(QUuid::WithoutBraces); | ||||
199 | | ||||
200 | m_desktops.append(dummyId); | ||||
201 | m_names[dummyId] = name; | ||||
202 | | ||||
203 | endInsertRows(); | ||||
davidedmundson: this is setting it to the value it already is | |||||
204 | | ||||
205 | updateModifiedState(); | ||||
206 | } | ||||
207 | | ||||
208 | void DesktopsModel::removeDesktop(const QString &id) | ||||
209 | { | ||||
210 | if (!ready() || !m_desktops.contains(id)) { | ||||
211 | return; | ||||
212 | } | ||||
213 | | ||||
214 | const int desktopIndex = m_desktops.indexOf(id); | ||||
215 | | ||||
216 | beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); | ||||
217 | | ||||
218 | m_desktops.removeAt(desktopIndex); | ||||
219 | m_names.remove(id); | ||||
220 | | ||||
221 | endRemoveRows(); | ||||
222 | | ||||
223 | updateModifiedState(); | ||||
224 | } | ||||
225 | | ||||
226 | void DesktopsModel::setDesktopName(const QString &id, const QString &name) | ||||
227 | { | ||||
228 | if (!ready() || !m_desktops.contains(id)) { | ||||
229 | return; | ||||
230 | } | ||||
231 | | ||||
232 | m_names[id] = name; | ||||
233 | | ||||
234 | const QModelIndex &idx = index(m_desktops.indexOf(id), 0); | ||||
235 | | ||||
236 | dataChanged(idx, idx, QVector<int>{Qt::DisplayRole}); | ||||
237 | | ||||
238 | updateModifiedState(); | ||||
239 | } | ||||
240 | | ||||
241 | void DesktopsModel::syncWithServer() | ||||
242 | { | ||||
243 | m_synchronizing = true; | ||||
244 | | ||||
245 | auto callFinished = [this](QDBusPendingCallWatcher *call) { | ||||
246 | QDBusPendingReply<void> reply = *call; | ||||
247 | | ||||
248 | if (reply.isError()) { | ||||
249 | handleCallError(); | ||||
250 | } | ||||
251 | | ||||
252 | call->deleteLater(); | ||||
If I have 3 desktops with 3 IDs and I delete desktop2 Here I delete 3 and then sync the names, leaving me with: it looks fine within the confines of this KCM, but as soon as we rely on those IDs for external use (even just the fact that a user might have his windows on id3 and none on id2) it'll fall apart. When you make the change sending insert/remove instead of renaming we'll need to make sure we do removal before insertion. desktopCreated relies on the index of the newly created desktop to be in the same place as m_desktops has it. If we insert first, the position will be off as the server will still have the about-to-be-deleted entries davidedmundson:
If I have 3 desktops with 3 IDs
id1 -> desktop1
id2 -> desktop2
id3 -> desktop3
and I delete… | |||||
253 | }; | ||||
254 | | ||||
Is m_desktops.last() the right one? It can be already deleted. For example, If user has two virtual desktops:
and he or she wants to delete virtual desktop called "Pizza", then Desktop 1 will be removed instead. zzag: Is m_desktops.last() the right one? It can be already deleted.
For example, If user has two… | |||||
255 | if (m_desktops.count() > m_serverSideDesktops.count()) { | ||||
256 | auto call = QDBusMessage::createMethodCall( | ||||
257 | s_serviceName, | ||||
258 | s_virtDesktopsPath, | ||||
259 | s_virtualDesktopsInterface, | ||||
260 | QStringLiteral("createDesktop")); | ||||
261 | | ||||
262 | const int newIndex = m_serverSideDesktops.count(); | ||||
263 | | ||||
264 | call.setArguments({(uint)newIndex, m_names.value(m_desktops.at(newIndex))}); | ||||
265 | | ||||
266 | QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); | ||||
267 | | ||||
268 | const auto *watcher = new QDBusPendingCallWatcher(pending, this); | ||||
269 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); | ||||
270 | | ||||
271 | return; // The change-handling slot will call syncWithServer() again, | ||||
272 | // until everything is in sync. | ||||
273 | } | ||||
274 | | ||||
275 | if (m_desktops.count() < m_serverSideDesktops.count()) { | ||||
276 | QStringListIterator i(m_serverSideDesktops); | ||||
277 | | ||||
278 | i.toBack(); | ||||
279 | | ||||
280 | while (i.hasPrevious()) { | ||||
281 | const QString &previous = i.previous(); | ||||
282 | | ||||
283 | if (!m_desktops.contains(previous)) { | ||||
284 | auto call = QDBusMessage::createMethodCall( | ||||
285 | s_serviceName, | ||||
286 | s_virtDesktopsPath, | ||||
287 | s_virtualDesktopsInterface, | ||||
288 | QStringLiteral("removeDesktop")); | ||||
289 | | ||||
290 | call.setArguments({previous}); | ||||
291 | | ||||
292 | QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); | ||||
zzag: Please indent it. | |||||
293 | | ||||
294 | const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); | ||||
295 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); | ||||
296 | | ||||
297 | return; // The change-handling slot will call syncWithServer() again, | ||||
298 | // until everything is in sync. | ||||
299 | } | ||||
300 | } | ||||
301 | } | ||||
302 | | ||||
303 | // Sync ids. Replace dummy ids in the process. | ||||
304 | for (int i = 0; i < m_serverSideDesktops.count(); ++i) { | ||||
305 | const QString oldId = m_desktops.at(i); | ||||
306 | const QString &newId = m_serverSideDesktops.at(i); | ||||
307 | m_desktops[i] = newId; | ||||
308 | m_names[newId] = m_names.take(oldId); | ||||
309 | } | ||||
310 | | ||||
311 | emit dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector<int>{Qt::DisplayRole}); | ||||
312 | | ||||
313 | // Sync names. | ||||
314 | if (m_names != m_serverSideNames) { | ||||
315 | QHashIterator<QString, QString> i(m_names); | ||||
316 | | ||||
317 | while (i.hasNext()) { | ||||
318 | i.next(); | ||||
zzag: You could also use `auto` here. ;-) | |||||
319 | | ||||
320 | if (i.value() != m_serverSideNames.value(i.key())) { | ||||
321 | auto call = QDBusMessage::createMethodCall( | ||||
322 | s_serviceName, | ||||
323 | s_virtDesktopsPath, | ||||
324 | s_virtualDesktopsInterface, | ||||
325 | QStringLiteral("setDesktopName")); | ||||
326 | | ||||
327 | call.setArguments({i.key(), i.value()}); | ||||
328 | | ||||
329 | QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); | ||||
330 | | ||||
331 | const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); | ||||
332 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); | ||||
333 | | ||||
334 | break; | ||||
335 | } | ||||
336 | } | ||||
337 | | ||||
338 | return; // The change-handling slot will call syncWithServer() again, | ||||
339 | // until everything is in sync.. | ||||
340 | } | ||||
341 | | ||||
342 | // Sync rows. | ||||
343 | if (m_rows != m_serverSideRows) { | ||||
344 | auto call = QDBusMessage::createMethodCall( | ||||
345 | s_serviceName, | ||||
346 | s_virtDesktopsPath, | ||||
347 | s_fdoPropertiesInterface, | ||||
348 | QStringLiteral("Set")); | ||||
349 | | ||||
350 | call.setArguments({s_virtualDesktopsInterface, | ||||
351 | QStringLiteral("rows"), QVariant::fromValue(QDBusVariant(QVariant((uint)m_rows)))}); | ||||
352 | | ||||
353 | QDBusPendingCall pending = QDBusConnection::sessionBus().asyncCall(call); | ||||
354 | | ||||
355 | const QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); | ||||
356 | QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, callFinished); | ||||
357 | } | ||||
358 | } | ||||
359 | | ||||
360 | void DesktopsModel::reset() | ||||
361 | { | ||||
362 | m_synchronizing = false; // Sanity. | ||||
363 | | ||||
364 | auto getAllAndConnectCall = QDBusMessage::createMethodCall( | ||||
365 | s_serviceName, | ||||
366 | s_virtDesktopsPath, | ||||
367 | s_fdoPropertiesInterface, | ||||
368 | QStringLiteral("GetAll")); | ||||
369 | | ||||
370 | getAllAndConnectCall.setArguments({s_virtualDesktopsInterface}); | ||||
371 | | ||||
372 | QDBusConnection::sessionBus().callWithCallback( | ||||
373 | getAllAndConnectCall, | ||||
374 | this, | ||||
375 | SLOT(getAllAndConnect(QDBusMessage)), | ||||
376 | SLOT(handleCallError())); | ||||
377 | } | ||||
378 | | ||||
379 | void DesktopsModel::getAllAndConnect(const QDBusMessage &msg) | ||||
Minor nitpick: In order to construct QString, we'll copy the QLatin1String (source). Maybe, use QStringLiteral instead? (Same with the QLatin1String down below) zzag: Minor nitpick: In order to construct QString, we'll copy the QLatin1String ([source](https… | |||||
380 | { | ||||
381 | const QVariantMap &data = qdbus_cast<QVariantMap>(msg.arguments().at(0).value<QDBusArgument>()); | ||||
382 | | ||||
383 | const KWin::DBusDesktopDataVector &desktops = qdbus_cast<KWin::DBusDesktopDataVector>( | ||||
384 | data.value(QStringLiteral("desktops")).value<QDBusArgument>() | ||||
385 | ); | ||||
Minor nitpick: because that's a new code, maybe use range based for loop instead? zzag: Minor nitpick: because that's a new code, maybe use range based for loop instead? | |||||
386 | | ||||
387 | const int newServerSideRows = data.value(QStringLiteral("rows")).toUInt(); | ||||
388 | QStringList newServerSideDesktops; | ||||
389 | QHash<QString,QString> newServerSideNames; | ||||
390 | | ||||
391 | for (const KWin::DBusDesktopDataStruct &d : desktops) { | ||||
392 | newServerSideDesktops.append(d.id); | ||||
393 | newServerSideNames[d.id] = d.name; | ||||
394 | } | ||||
395 | | ||||
396 | // If the server-side state changed during a KWin restart, and the | ||||
397 | // user had made notifications, the model should notify about the | ||||
398 | // change. | ||||
399 | if (m_serverSideDesktops != newServerSideDesktops | ||||
400 | || m_serverSideNames != newServerSideNames | ||||
401 | || m_serverSideRows != newServerSideRows) { | ||||
402 | if (!m_serverSideDesktops.isEmpty() || m_userModified) { | ||||
403 | m_serverModified = true; | ||||
but if it is userModified don't we need to update the ID to be the non-dummy value in case that entry is then later removed? davidedmundson: but if it is userModified don't we need to update the ID to be the non-dummy value in case that… | |||||
404 | emit serverModifiedChanged(); | ||||
405 | } | ||||
406 | | ||||
407 | m_serverSideDesktops = newServerSideDesktops; | ||||
408 | m_serverSideNames = newServerSideNames; | ||||
409 | m_serverSideRows = newServerSideRows; | ||||
410 | } | ||||
411 | | ||||
412 | // For the case KWin restarts while the KCM was open: If the user had | ||||
413 | // made no modifications, just reset to the server data. E.g. perhaps | ||||
414 | // the user intentionally nuked the KWin config while it was down, so | ||||
Is this right? If I remove a desktop in the KCM(without applying settings) and create a new one using the d-bus interface, the KCM will crash. zzag: Is this right?
If I remove a desktop in the KCM(without applying settings) and create a new… | |||||
415 | // we should follow. | ||||
416 | if (!m_userModified || m_desktops.empty()) { | ||||
417 | beginResetModel(); | ||||
davidedmundson: needs a guard.
could be emitted before the first load finishes | |||||
hein: I'll move the other connnects to the initialize slot. | |||||
Sorry for the above, I wrote it before updating the review but forgot to submit. hein: Sorry for the above, I wrote it before updating the review but forgot to submit. | |||||
418 | m_desktops = m_serverSideDesktops; | ||||
419 | m_names = m_serverSideNames; | ||||
420 | m_rows = m_serverSideRows; | ||||
421 | endResetModel(); | ||||
422 | } | ||||
423 | | ||||
424 | emit readyChanged(); | ||||
425 | | ||||
426 | auto handleConnectionError = [this]() { | ||||
427 | m_error = i18n("There was an error connecting to the compositor."); | ||||
428 | emit errorChanged(); | ||||
429 | }; | ||||
430 | | ||||
431 | bool connected = QDBusConnection::sessionBus().connect( | ||||
432 | s_serviceName, | ||||
433 | s_virtDesktopsPath, | ||||
434 | s_virtualDesktopsInterface, | ||||
435 | QStringLiteral("desktopCreated"), | ||||
436 | this, | ||||
437 | SLOT(desktopCreated(QString,KWin::DBusDesktopDataStruct))); | ||||
438 | | ||||
439 | if (!connected) { | ||||
440 | handleConnectionError(); | ||||
441 | | ||||
442 | return; | ||||
443 | } | ||||
444 | | ||||
445 | connected = QDBusConnection::sessionBus().connect( | ||||
446 | s_serviceName, | ||||
447 | s_virtDesktopsPath, | ||||
448 | s_virtualDesktopsInterface, | ||||
449 | QStringLiteral("desktopRemoved"), | ||||
450 | this, | ||||
451 | SLOT(desktopRemoved(QString))); | ||||
452 | | ||||
453 | if (!connected) { | ||||
454 | handleConnectionError(); | ||||
455 | | ||||
456 | return; | ||||
457 | } | ||||
458 | | ||||
459 | connected = QDBusConnection::sessionBus().connect( | ||||
460 | s_serviceName, | ||||
461 | s_virtDesktopsPath, | ||||
462 | s_virtualDesktopsInterface, | ||||
463 | QStringLiteral("desktopDataChanged"), | ||||
464 | this, | ||||
465 | SLOT(desktopDataChanged(QString,KWin::DBusDesktopDataStruct))); | ||||
466 | | ||||
467 | if (!connected) { | ||||
468 | handleConnectionError(); | ||||
469 | | ||||
470 | return; | ||||
471 | } | ||||
472 | | ||||
473 | connected = QDBusConnection::sessionBus().connect( | ||||
474 | s_serviceName, | ||||
475 | s_virtDesktopsPath, | ||||
476 | s_virtualDesktopsInterface, | ||||
477 | QStringLiteral("rowsChanged"), | ||||
478 | this, | ||||
479 | SLOT(desktopRowsChanged(uint))); | ||||
480 | | ||||
481 | if (!connected) { | ||||
482 | handleConnectionError(); | ||||
483 | | ||||
484 | return; | ||||
485 | } | ||||
486 | } | ||||
487 | | ||||
488 | void DesktopsModel::desktopCreated(const QString &id, const KWin::DBusDesktopDataStruct &data) | ||||
489 | { | ||||
490 | m_serverSideDesktops.insert(data.position, id); | ||||
491 | m_serverSideNames[data.id] = data.name; | ||||
492 | | ||||
493 | // If the user didn't make any changes, we can just stay in sync. | ||||
494 | if (!m_userModified) { | ||||
495 | beginInsertRows(QModelIndex(), data.position, data.position); | ||||
496 | | ||||
497 | m_desktops = m_serverSideDesktops; | ||||
498 | m_names = m_serverSideNames; | ||||
499 | | ||||
500 | endInsertRows(); | ||||
501 | } else { | ||||
502 | // Remove dummy data. | ||||
503 | const QString dummyId = m_desktops.at(data.position); | ||||
504 | m_desktops[data.position] = id; | ||||
505 | m_names.remove(dummyId); | ||||
506 | m_names[id] = data.name; | ||||
507 | const QModelIndex &idx = index(data.position, 0); | ||||
508 | emit dataChanged(idx, idx, QVector<int>{Id}); | ||||
509 | | ||||
510 | updateModifiedState(/* server */ true); | ||||
511 | } | ||||
512 | } | ||||
513 | | ||||
514 | void DesktopsModel::desktopRemoved(const QString &id) | ||||
515 | { | ||||
516 | const int desktopIndex = m_serverSideDesktops.indexOf(id); | ||||
517 | | ||||
518 | m_serverSideDesktops.removeAt(desktopIndex); | ||||
519 | m_serverSideNames.remove(id); | ||||
520 | | ||||
521 | // If the user didn't make any changes, we can just stay in sync. | ||||
522 | if (!m_userModified) { | ||||
523 | beginRemoveRows(QModelIndex(), desktopIndex, desktopIndex); | ||||
524 | | ||||
525 | m_desktops = m_serverSideDesktops; | ||||
526 | m_names = m_serverSideNames; | ||||
527 | | ||||
528 | endRemoveRows(); | ||||
529 | } else { | ||||
530 | updateModifiedState(/* server */ true); | ||||
531 | } | ||||
532 | } | ||||
533 | | ||||
534 | void DesktopsModel::desktopDataChanged(const QString &id, const KWin::DBusDesktopDataStruct &data) | ||||
535 | { | ||||
536 | const int desktopIndex = m_serverSideDesktops.indexOf(id); | ||||
537 | | ||||
538 | m_serverSideDesktops[desktopIndex] = id; | ||||
539 | m_serverSideNames[id] = data.name; | ||||
540 | | ||||
541 | // If the user didn't make any changes, we can just stay in sync. | ||||
542 | if (!m_userModified) { | ||||
543 | m_desktops = m_serverSideDesktops; | ||||
544 | m_names = m_serverSideNames; | ||||
545 | | ||||
546 | const QModelIndex &idx = index(desktopIndex, 0); | ||||
547 | | ||||
548 | dataChanged(idx, idx, QVector<int>{Qt::DisplayRole}); | ||||
549 | } else { | ||||
550 | updateModifiedState(/* server */ true); | ||||
551 | } | ||||
552 | } | ||||
553 | | ||||
554 | void DesktopsModel::desktopRowsChanged(uint rows) | ||||
555 | { | ||||
556 | // Unfortunately we sometimes get this signal from the server with an unchanged value. | ||||
557 | if ((int)rows == m_serverSideRows) { | ||||
558 | return; | ||||
559 | } | ||||
560 | | ||||
561 | m_serverSideRows = rows; | ||||
562 | | ||||
563 | // If the user didn't make any changes, we can just stay in sync. | ||||
564 | if (!m_userModified) { | ||||
565 | m_rows = m_serverSideRows; | ||||
566 | | ||||
567 | emit rowsChanged(); | ||||
568 | emit dataChanged(index(0, 0), index(m_desktops.count() - 1, 0), QVector<int>{DesktopRow}); | ||||
569 | } else { | ||||
570 | updateModifiedState(/* server */ true); | ||||
571 | } | ||||
572 | } | ||||
573 | | ||||
574 | void DesktopsModel::updateModifiedState(bool server) | ||||
575 | { | ||||
576 | // Count is the same but contents are not: The user may have | ||||
577 | // removed and created new desktops in the UI, but there were | ||||
578 | // no changes to send to the server because number and names | ||||
579 | // have remained the same. In that case we can just clean | ||||
580 | // that up here. | ||||
581 | if (m_desktops.count() == m_serverSideDesktops.count() | ||||
582 | && m_desktops != m_serverSideDesktops) { | ||||
583 | | ||||
584 | for (int i = 0; i < m_serverSideDesktops.count(); ++i) { | ||||
585 | const QString oldId = m_desktops.at(i); | ||||
586 | const QString &newId = m_serverSideDesktops.at(i); | ||||
587 | m_desktops[i] = newId; | ||||
588 | m_names[newId] = m_names.take(oldId); | ||||
589 | } | ||||
590 | | ||||
591 | emit dataChanged(index(0, 0), index(rowCount() - 1, 0), QVector<int>{Qt::DisplayRole}); | ||||
592 | } | ||||
593 | | ||||
594 | if (m_desktops == m_serverSideDesktops | ||||
595 | && m_names == m_serverSideNames | ||||
596 | && m_rows == m_serverSideRows) { | ||||
597 | | ||||
598 | m_userModified = false; | ||||
599 | emit userModifiedChanged(); | ||||
600 | | ||||
601 | m_serverModified = false; | ||||
602 | emit serverModifiedChanged(); | ||||
603 | | ||||
604 | m_synchronizing = false; | ||||
605 | } else { | ||||
606 | if (m_synchronizing) { | ||||
607 | m_serverModified = false; | ||||
608 | emit serverModifiedChanged(); | ||||
609 | | ||||
610 | syncWithServer(); | ||||
611 | } else if (server) { | ||||
612 | m_serverModified = true; | ||||
613 | emit serverModifiedChanged(); | ||||
614 | } else { | ||||
615 | m_userModified = true; | ||||
616 | emit userModifiedChanged(); | ||||
617 | } | ||||
618 | } | ||||
619 | } | ||||
620 | | ||||
621 | void DesktopsModel::handleCallError() | ||||
622 | { | ||||
623 | if (m_synchronizing) { | ||||
624 | m_synchronizing = false; | ||||
625 | | ||||
626 | m_serverModified = false; | ||||
627 | emit serverModifiedChanged(); | ||||
628 | | ||||
629 | m_error = i18n("There was an error saving the settings to the compositor."); | ||||
630 | emit errorChanged(); | ||||
631 | } else { | ||||
632 | m_error = i18n("There was an error requesting information from the compositor."); | ||||
633 | emit errorChanged(); | ||||
634 | } | ||||
635 | } | ||||
636 | | ||||
637 | } |