Changeset View
Changeset View
Standalone View
Standalone View
kcms/colors/colors.cpp
- This file was added.
1 | /* | ||||
---|---|---|---|---|---|
2 | * Copyright (C) 2007 Matthew Woehlke <mw_triad@users.sourceforge.net> | ||||
3 | * Copyright (C) 2007 Jeremy Whiting <jpwhiting@kde.org> | ||||
4 | * Copyright (C) 2016 Olivier Churlaud <olivier@churlaud.com> | ||||
5 | * Copyright 2018 Kai Uwe Broulik <kde@privat.broulik.de> | ||||
6 | * | ||||
7 | * This program is free software; you can redistribute it and/or | ||||
8 | * modify it under the terms of the GNU General Public License as | ||||
9 | * published by the Free Software Foundation; either version 2 of | ||||
10 | * the License or (at your option) version 3 or any later version | ||||
11 | * accepted by the membership of KDE e.V. (or its successor approved | ||||
12 | * by the membership of KDE e.V.), which shall act as a proxy | ||||
13 | * defined in Section 14 of version 3 of the license. | ||||
14 | * | ||||
15 | * This program is distributed in the hope that it will be useful, | ||||
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
18 | * GNU General Public License for more details. | ||||
19 | * | ||||
20 | * You should have received a copy of the GNU General Public License | ||||
21 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||||
22 | */ | ||||
23 | | ||||
24 | #include "colors.h" | ||||
25 | | ||||
26 | #include <QDBusConnection> | ||||
27 | #include <QDBusMessage> | ||||
28 | #include <QFileInfo> | ||||
29 | #include <QGuiApplication> | ||||
30 | #include <QStandardItemModel> | ||||
31 | #include <QStandardPaths> | ||||
32 | #include <QQuickItem> | ||||
33 | #include <QQuickWindow> | ||||
34 | | ||||
35 | #include <KAboutData> | ||||
36 | #include <KColorScheme> | ||||
37 | #include <KConfigGroup> | ||||
38 | #include <KLocalizedString> | ||||
39 | #include <KPluginFactory> | ||||
40 | | ||||
41 | #include <KIO/DeleteJob> | ||||
42 | | ||||
43 | #include <QDebug> | ||||
44 | | ||||
45 | #include <algorithm> | ||||
46 | | ||||
47 | #include "../krdb/krdb.h" | ||||
48 | | ||||
49 | //static const QString s_defaultThemeName = QStringLiteral("/DEFAULT/"); | ||||
50 | //static const QString s_currentThemeName = QStringLiteral("/CURRENT/"); | ||||
51 | | ||||
52 | K_PLUGIN_FACTORY_WITH_JSON(KCMColorsFactory, "kcm_colors.json", registerPlugin<KCMColors>();) | ||||
53 | | ||||
54 | KCMColors::KCMColors(QObject *parent, const QVariantList &args) | ||||
55 | : KQuickAddons::ConfigModule(parent, args) | ||||
56 | , m_selectedSchemeDirty(false) | ||||
57 | , m_applyToAlien(true) | ||||
58 | , m_config(KSharedConfig::openConfig(QStringLiteral("kdeglobals"))) | ||||
59 | { | ||||
GB_2: Better: "Choose the color scheme" | |||||
60 | qmlRegisterType<QStandardItemModel>(); | ||||
61 | | ||||
62 | KAboutData *about = new KAboutData(QStringLiteral("kcm_colors"), i18n("Configure color schemes"), | ||||
63 | QStringLiteral("2.0"), QString(), KAboutLicense::GPL); | ||||
64 | about->addAuthor(i18n("Kai Uwe Broulik"), QString(), QStringLiteral("kde@privat.broulik.de")); | ||||
65 | setAboutData(about); | ||||
66 | setButtons(Apply | Default); | ||||
67 | | ||||
68 | m_model = new QStandardItemModel(this); | ||||
69 | m_model->setItemRoleNames({ | ||||
70 | {Qt::DisplayRole, QByteArrayLiteral("display")}, | ||||
71 | {SchemeNameRole, QByteArrayLiteral("schemeName")}, | ||||
72 | {PaletteRole, QByteArrayLiteral("palette")}, | ||||
73 | {EditableRole, QByteArrayLiteral("editable")}, | ||||
74 | {RemovableRole, QByteArrayLiteral("removable")}, | ||||
75 | {PendingDeletionRole, QByteArrayLiteral("pendingDeletion")} | ||||
76 | }); | ||||
77 | } | ||||
78 | | ||||
79 | KCMColors::~KCMColors() | ||||
80 | { | ||||
81 | | ||||
82 | } | ||||
83 | | ||||
84 | QStandardItemModel *KCMColors::colorsModel() const | ||||
85 | { | ||||
86 | return m_model; | ||||
87 | } | ||||
88 | | ||||
89 | QString KCMColors::selectedScheme() const | ||||
90 | { | ||||
91 | return m_selectedScheme; | ||||
92 | } | ||||
93 | | ||||
94 | void KCMColors::setSelectedScheme(const QString &scheme) | ||||
95 | { | ||||
96 | if (m_selectedScheme == scheme) { | ||||
97 | return; | ||||
98 | } | ||||
99 | | ||||
100 | const bool firstTime = m_selectedScheme.isNull(); | ||||
101 | m_selectedScheme = scheme; | ||||
102 | emit selectedSchemeChanged(); | ||||
103 | emit selectedSchemeIndexChanged(); | ||||
104 | | ||||
105 | if (!firstTime) { | ||||
106 | setNeedsSave(true); | ||||
107 | m_selectedSchemeDirty = true; | ||||
108 | } | ||||
109 | } | ||||
110 | | ||||
111 | int KCMColors::selectedSchemeIndex() const | ||||
112 | { | ||||
113 | const auto results = m_model->match(m_model->index(0, 0), SchemeNameRole, m_selectedScheme); | ||||
114 | if (results.count() == 1) { | ||||
115 | return results.first().row(); | ||||
116 | } | ||||
117 | | ||||
118 | return -1; | ||||
119 | } | ||||
120 | | ||||
121 | void KCMColors::setPendingDeletion(int index, bool pending) | ||||
122 | { | ||||
123 | QModelIndex idx = m_model->index(index, 0); | ||||
124 | | ||||
125 | m_model->setData(idx, pending, PendingDeletionRole); | ||||
126 | | ||||
127 | if (pending && selectedSchemeIndex() == index) { | ||||
128 | // move to the next non-pending theme | ||||
129 | const auto nonPending = m_model->match(idx, PendingDeletionRole, false); | ||||
130 | setSelectedScheme(nonPending.first().data(SchemeNameRole).toString()); | ||||
131 | } | ||||
132 | | ||||
133 | setNeedsSave(true); | ||||
134 | } | ||||
135 | | ||||
136 | | ||||
137 | void KCMColors::loadModel() | ||||
138 | { | ||||
139 | m_model->clear(); | ||||
140 | | ||||
141 | QStringList schemeFiles; | ||||
142 | | ||||
143 | const QStringList schemeDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("color-schemes"), QStandardPaths::LocateDirectory); | ||||
144 | for (const QString &dir : schemeDirs) { | ||||
145 | const QStringList fileNames = QDir(dir).entryList(QStringList{QStringLiteral("*.colors")}); | ||||
146 | for (const QString &file : fileNames) { | ||||
147 | const QString suffixedFileName = QStringLiteral("color-schemes/") + file; | ||||
148 | // can't use QSet because of the transform below (passing const QString as this argument discards qualifiers) | ||||
149 | if (!schemeFiles.contains(suffixedFileName)) { | ||||
150 | schemeFiles.append(suffixedFileName); | ||||
151 | } | ||||
152 | } | ||||
153 | } | ||||
154 | | ||||
155 | std::transform(schemeFiles.begin(), schemeFiles.end(), schemeFiles.begin(), [](const QString &item) { | ||||
156 | return QStandardPaths::locate(QStandardPaths::GenericDataLocation, item); | ||||
157 | }); | ||||
158 | | ||||
159 | for (const QString &schemeFile : schemeFiles) { | ||||
160 | const QFileInfo fi(schemeFile); | ||||
161 | const QString baseName = fi.baseName(); | ||||
162 | | ||||
163 | KSharedConfigPtr config = KSharedConfig::openConfig(schemeFile, KConfig::SimpleConfig); | ||||
164 | KConfigGroup group(config, "General"); | ||||
165 | const QString name = group.readEntry("Name", baseName); | ||||
166 | | ||||
167 | QStandardItem *item = new QStandardItem(name); | ||||
168 | item->setData(baseName, SchemeNameRole); | ||||
169 | item->setData(true, EditableRole); | ||||
170 | item->setData(fi.isWritable(), RemovableRole); | ||||
171 | item->setData(false, PendingDeletionRole); | ||||
172 | | ||||
173 | item->setData(KColorScheme::createApplicationPalette(config), PaletteRole); | ||||
174 | | ||||
175 | m_model->appendRow(item); | ||||
176 | } | ||||
177 | | ||||
178 | m_model->sort(0 /*column*/); | ||||
179 | | ||||
180 | // add default entry (do this here so that the current and default entry appear at the top) | ||||
181 | /*QStandardItem *defaultItem = new QStandardItem(i18nc("Default color scheme", "Default")); | ||||
182 | defaultItem->setData(s_defaultThemeName, SchemeNameRole); | ||||
183 | defaultItem->setData(true, EditableRole); | ||||
184 | defaultItem->setData(false, RemovableRole); | ||||
185 | m_model->insertRow(0, defaultItem);*/ | ||||
186 | | ||||
187 | // add current scheme entry | ||||
188 | /*QStandardItem *currentItem = new QStandardItem(i18nc("Current color scheme", "Current")); | ||||
189 | currentItem->setData(s_currentThemeName, SchemeNameRole); | ||||
190 | currentItem->setData(true, EditableRole); | ||||
191 | currentItem->setData(false, RemovableRole); | ||||
192 | m_model->insertRow(0, currentItem);*/ | ||||
193 | } | ||||
194 | | ||||
195 | void KCMColors::getNewStuff(QQuickItem *ctx) | ||||
196 | { | ||||
197 | if (!m_newStuffDialog) { | ||||
198 | m_newStuffDialog = new KNS3::DownloadDialog(QStringLiteral("colorschemes.knsrc")); | ||||
199 | m_newStuffDialog.data()->setWindowTitle(i18n("Download New Color Schemes")); | ||||
200 | m_newStuffDialog->setWindowModality(Qt::WindowModal); | ||||
201 | m_newStuffDialog->winId(); // so it creates the windowHandle(); | ||||
202 | // TODO would be lovely to scroll to and select the newly installed scheme, if any | ||||
203 | connect(m_newStuffDialog.data(), &KNS3::DownloadDialog::accepted, this, &KCMColors::loadModel); | ||||
204 | } | ||||
205 | | ||||
206 | if (ctx && ctx->window()) { | ||||
207 | m_newStuffDialog->windowHandle()->setTransientParent(ctx->window()); | ||||
208 | } | ||||
209 | | ||||
210 | m_newStuffDialog.data()->show(); | ||||
211 | } | ||||
212 | | ||||
213 | void KCMColors::installSchemeFromFile(const QUrl &url) | ||||
214 | { | ||||
215 | // load the scheme | ||||
216 | KSharedConfigPtr config = KSharedConfig::openConfig(url.toLocalFile(), KConfig::SimpleConfig); | ||||
217 | | ||||
218 | KConfigGroup group(config, "General"); | ||||
219 | const QString name = group.readEntry("Name"); | ||||
220 | | ||||
221 | if (name.isEmpty()) { | ||||
222 | emit showErrorMessage(i18n("The file is not a color scheme file.")); | ||||
223 | return; | ||||
224 | } | ||||
225 | | ||||
226 | // Do not overwrite another scheme | ||||
227 | int increment = 0; | ||||
228 | QString newName = name; | ||||
229 | QString testpath; | ||||
what would happen if this is called twice before the file_copy completes? davidedmundson: what would happen if this is called twice before the file_copy completes? | |||||
230 | do { | ||||
231 | if (increment) { | ||||
232 | newName = name + QString::number(increment); | ||||
233 | } | ||||
234 | testpath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, | ||||
235 | QStringLiteral("color-schemes/%1.colors").arg(newName)); | ||||
236 | increment++; | ||||
237 | } while (!testpath.isEmpty()); | ||||
238 | | ||||
239 | QString newPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/color-schemes/"); | ||||
240 | | ||||
241 | if (!QDir().mkpath(newPath)) { | ||||
242 | emit showErrorMessage(i18n("Failed to create 'color-scheme' data folder.")); | ||||
243 | return; | ||||
244 | } | ||||
245 | | ||||
246 | newPath += newName + QLatin1String(".colors"); | ||||
247 | | ||||
248 | if (!QFile::copy(url.toLocalFile(), newPath)) { | ||||
249 | emit showErrorMessage(i18n("Failed to copy color scheme into 'color-scheme' data folder.")); | ||||
250 | return; | ||||
251 | } | ||||
252 | | ||||
253 | // Update name | ||||
254 | KSharedConfigPtr config2 = KSharedConfig::openConfig(newPath, KConfig::SimpleConfig); | ||||
255 | KConfigGroup group2(config2, "General"); | ||||
256 | group2.writeEntry("Name", newName); | ||||
257 | config2->sync(); | ||||
258 | | ||||
259 | loadModel(); | ||||
260 | | ||||
261 | const auto results = m_model->match(m_model->index(0, 0), SchemeNameRole, newName); | ||||
262 | if (!results.isEmpty()) { | ||||
263 | setSelectedScheme(newName); | ||||
264 | } | ||||
265 | | ||||
266 | emit showSuccessMessage(i18n("Color scheme installed successfully.")); | ||||
267 | } | ||||
268 | | ||||
269 | void KCMColors::editScheme(const QString &scheme) | ||||
270 | { | ||||
271 | // TODO | ||||
272 | } | ||||
273 | | ||||
274 | void KCMColors::load() | ||||
275 | { | ||||
276 | loadModel(); | ||||
277 | | ||||
278 | m_config->markAsClean(); | ||||
279 | m_config->reparseConfiguration(); | ||||
280 | | ||||
281 | KConfigGroup group(m_config, "General"); | ||||
282 | setSelectedScheme(group.readEntry("ColorScheme")); | ||||
283 | | ||||
284 | { | ||||
285 | KConfig cfg(QStringLiteral("kcmdisplayrc"), KConfig::NoGlobals); | ||||
286 | group = KConfigGroup(&cfg, "X11"); | ||||
287 | m_applyToAlien = group.readEntry("exportKDEColors", true); | ||||
288 | } | ||||
289 | } | ||||
290 | | ||||
291 | void KCMColors::save() | ||||
292 | { | ||||
293 | if (m_selectedSchemeDirty) { | ||||
294 | saveColors(); | ||||
295 | } | ||||
296 | | ||||
297 | processPendingDeletions(); | ||||
298 | | ||||
299 | setNeedsSave(false); | ||||
300 | } | ||||
301 | | ||||
302 | void KCMColors::saveColors() | ||||
303 | { | ||||
304 | KConfigGroup grp(m_config, "General"); | ||||
305 | grp.writeEntry("ColorScheme", m_selectedScheme); | ||||
306 | | ||||
307 | const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, | ||||
308 | QStringLiteral("color-schemes/%1.colors").arg(m_selectedScheme)); | ||||
309 | | ||||
310 | KSharedConfigPtr config = KSharedConfig::openConfig(path); | ||||
311 | | ||||
312 | QStringList colorItemList; | ||||
313 | colorItemList << "BackgroundNormal" | ||||
Can you use an initializer_list here and elsewhere? See https://www.angrycane.com.br/en/2018/06/19/speeding-up-cornercases/ for a comparison nicolasfella: Can you use an initializer_list here and elsewhere?
Also QStringLiteral?
See https://www. | |||||
314 | << "BackgroundAlternate" | ||||
315 | << "ForegroundNormal" | ||||
316 | << "ForegroundInactive" | ||||
317 | << "ForegroundActive" | ||||
318 | << "ForegroundLink" | ||||
319 | << "ForegroundVisited" | ||||
320 | << "ForegroundNegative" | ||||
321 | << "ForegroundNeutral" | ||||
322 | << "ForegroundPositive" | ||||
323 | << "DecorationFocus" | ||||
324 | << "DecorationHover"; | ||||
325 | | ||||
326 | QStringList colorSetGroupList; | ||||
327 | colorSetGroupList << "Colors:View" | ||||
328 | << "Colors:Window" | ||||
329 | << "Colors:Button" | ||||
330 | << "Colors:Selection" | ||||
331 | << "Colors:Tooltip" | ||||
332 | << "Colors:Complementary"; | ||||
333 | | ||||
334 | QList <KColorScheme> colorSchemes; | ||||
335 | | ||||
336 | colorSchemes.append(KColorScheme(QPalette::Active, KColorScheme::View, config)); | ||||
337 | colorSchemes.append(KColorScheme(QPalette::Active, KColorScheme::Window, config)); | ||||
338 | colorSchemes.append(KColorScheme(QPalette::Active, KColorScheme::Button, config)); | ||||
339 | colorSchemes.append(KColorScheme(QPalette::Active, KColorScheme::Selection, config)); | ||||
340 | colorSchemes.append(KColorScheme(QPalette::Active, KColorScheme::Tooltip, config)); | ||||
341 | colorSchemes.append(KColorScheme(QPalette::Active, KColorScheme::Complementary, config)); | ||||
342 | | ||||
343 | for (int i = 0; i < colorSchemes.length(); ++i) | ||||
344 | { | ||||
345 | KConfigGroup group(m_config, colorSetGroupList.value(i)); | ||||
346 | group.writeEntry("BackgroundNormal", colorSchemes[i].background(KColorScheme::NormalBackground).color()); | ||||
347 | group.writeEntry("BackgroundAlternate", colorSchemes[i].background(KColorScheme::AlternateBackground).color()); | ||||
348 | group.writeEntry("ForegroundNormal", colorSchemes[i].foreground(KColorScheme::NormalText).color()); | ||||
349 | group.writeEntry("ForegroundInactive", colorSchemes[i].foreground(KColorScheme::InactiveText).color()); | ||||
350 | group.writeEntry("ForegroundActive", colorSchemes[i].foreground(KColorScheme::ActiveText).color()); | ||||
351 | group.writeEntry("ForegroundLink", colorSchemes[i].foreground(KColorScheme::LinkText).color()); | ||||
352 | group.writeEntry("ForegroundVisited", colorSchemes[i].foreground(KColorScheme::VisitedText).color()); | ||||
353 | group.writeEntry("ForegroundNegative", colorSchemes[i].foreground(KColorScheme::NegativeText).color()); | ||||
354 | group.writeEntry("ForegroundNeutral", colorSchemes[i].foreground(KColorScheme::NeutralText).color()); | ||||
355 | group.writeEntry("ForegroundPositive", colorSchemes[i].foreground(KColorScheme::PositiveText).color()); | ||||
356 | group.writeEntry("DecorationFocus", colorSchemes[i].decoration(KColorScheme::FocusColor).color()); | ||||
357 | group.writeEntry("DecorationHover", colorSchemes[i].decoration(KColorScheme::HoverColor).color()); | ||||
358 | } | ||||
359 | | ||||
360 | KConfigGroup groupWMTheme(config, "WM"); | ||||
361 | KConfigGroup groupWMOut(m_config, "WM"); | ||||
362 | | ||||
363 | QStringList colorItemListWM; | ||||
364 | colorItemListWM << "activeBackground" | ||||
365 | << "activeForeground" | ||||
366 | << "inactiveBackground" | ||||
367 | << "inactiveForeground" | ||||
368 | << "activeBlend" | ||||
369 | << "inactiveBlend"; | ||||
370 | | ||||
371 | QVector<QColor> defaultWMColors; | ||||
372 | defaultWMColors << QColor(71,80,87) | ||||
373 | << QColor(239,240,241) | ||||
374 | << QColor(239,240,241) | ||||
375 | << QColor(189,195,199) | ||||
376 | << QColor(255,255,255) | ||||
377 | << QColor(75,71,67); | ||||
378 | | ||||
379 | int i = 0; | ||||
380 | for (const QString &coloritem : colorItemListWM) | ||||
381 | { | ||||
382 | groupWMOut.writeEntry(coloritem, groupWMTheme.readEntry(coloritem, defaultWMColors.value(i))); | ||||
383 | ++i; | ||||
384 | } | ||||
385 | | ||||
386 | QStringList groupNameList; | ||||
387 | groupNameList << "ColorEffects:Inactive" << "ColorEffects:Disabled"; | ||||
388 | | ||||
389 | QStringList effectList; | ||||
390 | effectList << "Enable" | ||||
391 | << "ChangeSelectionColor" | ||||
392 | << "IntensityEffect" | ||||
393 | << "IntensityAmount" | ||||
394 | << "ColorEffect" | ||||
395 | << "ColorAmount" | ||||
396 | << "Color" | ||||
397 | << "ContrastEffect" | ||||
398 | << "ContrastAmount"; | ||||
399 | | ||||
400 | for (const QString &groupName : groupNameList) | ||||
401 | { | ||||
402 | | ||||
403 | KConfigGroup groupEffectOut(m_config, groupName); | ||||
404 | KConfigGroup groupEffectTheme(config, groupName); | ||||
405 | | ||||
406 | for (const QString &effect : effectList) { | ||||
407 | groupEffectOut.writeEntry(effect, groupEffectTheme.readEntry(effect)); | ||||
408 | } | ||||
409 | } | ||||
410 | | ||||
411 | m_config->sync(); | ||||
412 | | ||||
413 | runRdb(KRdbExportQtColors | KRdbExportGtkTheme | (m_applyToAlien ? KRdbExportColors : 0)); | ||||
414 | | ||||
415 | QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KGlobalSettings"), | ||||
416 | QStringLiteral("org.kde.KGlobalSettings"), | ||||
417 | QStringLiteral("notifyChange")); | ||||
418 | message.setArguments({ | ||||
419 | 0, //previous KGlobalSettings::PaletteChanged. This is now private API in khintsettings | ||||
420 | 0 //unused in palette changed but needed for the DBus signature | ||||
421 | }); | ||||
422 | QDBusConnection::sessionBus().send(message); | ||||
423 | | ||||
424 | if (qApp->platformName() == QLatin1String("xcb")) { | ||||
425 | // Send signal to all kwin instances | ||||
426 | QDBusMessage message = QDBusMessage::createSignal(QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin"), QStringLiteral("reloadConfig")); | ||||
427 | QDBusConnection::sessionBus().send(message); | ||||
428 | } | ||||
429 | | ||||
430 | m_selectedSchemeDirty = false; | ||||
431 | } | ||||
432 | | ||||
433 | void KCMColors::processPendingDeletions() | ||||
434 | { | ||||
435 | const auto pendingDeletions = m_model->match(m_model->index(0, 0), PendingDeletionRole, true, -1 /*all*/); | ||||
436 | QVector<QPersistentModelIndex> persistentPendingDeletions; | ||||
437 | // turn into persistent model index so we can delete as we go | ||||
438 | std::transform(pendingDeletions.begin(), pendingDeletions.end(), | ||||
439 | std::back_inserter(persistentPendingDeletions), [](const QModelIndex &idx) { | ||||
440 | return QPersistentModelIndex(idx); | ||||
441 | }); | ||||
442 | | ||||
443 | for (const QPersistentModelIndex &idx : persistentPendingDeletions) { | ||||
444 | const QString schemeName = idx.data(SchemeNameRole).toString(); | ||||
445 | | ||||
446 | Q_ASSERT(schemeName != m_selectedScheme); | ||||
447 | | ||||
448 | const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, | ||||
449 | QStringLiteral("color-schemes/%1.colors").arg(schemeName)); | ||||
450 | | ||||
451 | auto *job = KIO::del(QUrl::fromLocalFile(path), KIO::HideProgressInfo); | ||||
452 | // needs to block for it to work on "OK" where the dialog (kcmshell) closes | ||||
453 | job->exec(); | ||||
454 | } | ||||
455 | | ||||
456 | // remove them in a separate loop after all the delete jobs for a smoother animation | ||||
457 | for (const QPersistentModelIndex &idx : persistentPendingDeletions) { | ||||
458 | m_model->removeRow(idx.row()); | ||||
459 | } | ||||
460 | } | ||||
461 | | ||||
462 | void KCMColors::defaults() | ||||
463 | { | ||||
464 | // TODO restore default color | ||||
465 | | ||||
466 | setNeedsSave(true); | ||||
467 | } | ||||
468 | | ||||
469 | #include "colors.moc" |
Better: "Choose the color scheme"