2
Copyright (c) 2008 Andrew Lake <jamboarder@yahoo.com>
3
Copyright (c) 2010 Jeremy Whiting <jpwhiting@kde.org>
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.
11
#include "desktopthemedetails.h"
13
#include "thememodel.h"
15
#include <QtGui/QComboBox>
16
#include <QtCore/QDir>
17
#include <QtCore/QTextStream>
19
#include <KDesktopFile>
20
#include <KFileDialog>
21
#include <KMessageBox>
22
#include <KStandardDirs>
24
#include <kio/netaccess.h>
25
#include <kio/copyjob.h>
26
#include <kio/deletejob.h>
29
struct ThemeItemNameType {
31
const char* m_displayItemName;
32
const char* m_themeItemPath;
33
const char* m_iconName;
36
const ThemeItemNameType themeCollectionName[] = {
37
{ "Color Scheme", I18N_NOOP2("plasma name", "Color Scheme"),"colors", "preferences-desktop-color"},
38
{ "Panel Background", I18N_NOOP2("plasma name", "Panel Background"),"widgets/panel-background", "plasma"},
39
{ "Kickoff", I18N_NOOP2("plasma name", "Kickoff"), "dialogs/kickoff", "kde"},
40
{ "Task Items", I18N_NOOP2("plasma name", "Task Items"), "widgets/tasks", "preferences-system-windows"},
41
{ "Widget Background", I18N_NOOP2("plasma name", "Widget Background"), "widgets/background", "plasma"},
42
{ "Translucent Background", I18N_NOOP2("plasma name", "Translucent Background"), "widgets/translucentbackground", "plasma"},
43
{ "Dialog Background", I18N_NOOP2("plasma name", "Dialog Background"), "dialogs/background", "plasma"},
44
{ "Analog Clock", I18N_NOOP2("plasma name", "Analog Clock"), "widgets/clock", "chronometer"},
45
{ "Notes", I18N_NOOP2("plasma name", "Notes"), "widgets/notes", "view-pim-notes"},
46
{ "Tooltip", I18N_NOOP2("plasma name", "Tooltip"), "widgets/tooltip", "plasma"},
47
{ "Pager", I18N_NOOP2("plasma name", "Pager"), "widgets/pager", "plasma"},
48
{ "Run Command Dialog", I18N_NOOP2("plasma name", "Run Command Dialog"), "dialogs/krunner", "system-run"},
49
{ "Shutdown Dialog", I18N_NOOP2("plasma name", "Shutdown Dialog"), "dialogs/shutdowndialog", "system-shutdown"},
50
{ "Icons", I18N_NOOP2("plasma name", "Icons"), "icons/", "preferences-desktop-icons"},
51
{ 0, 0,0,0 } // end of data
55
DesktopThemeDetails::DesktopThemeDetails(QWidget* parent)
60
setWindowIcon(KIcon("preferences-desktop"));
65
font.setPointSize(1.2*font.pointSize());
66
m_themeInfoName->setFont(font);
68
m_enableAdvanced->setChecked(false);
69
toggleAdvancedVisible();
72
m_themeModel = new ThemeModel(this);
73
m_theme->setModel(m_themeModel);
74
m_theme->setItemDelegate(new ThemeDelegate(m_theme));
78
connect(m_theme->selectionModel(), SIGNAL(selectionChanged(const QItemSelection, const QItemSelection)), this, SLOT(themeSelectionChanged(const QItemSelection, const QItemSelection)));
80
connect(m_enableAdvanced, SIGNAL(toggled(bool)), this, SLOT(toggleAdvancedVisible()));
81
connect(m_removeThemeButton, SIGNAL(clicked()), this, SLOT(removeTheme()));
82
connect(m_exportThemeButton, SIGNAL(clicked()), this, SLOT(exportTheme()));
84
connect(m_newThemeName, SIGNAL(editingFinished()), this, SLOT(newThemeInfoChanged()));
86
m_baseTheme = "default";
87
m_themeCustomized = false;
92
DesktopThemeDetails::~DesktopThemeDetails()
97
void DesktopThemeDetails::cleanup()
101
void DesktopThemeDetails::reloadConfig()
104
KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig("plasmarc"), "Theme");
105
const QString theme = cfg.readEntry("name", "default");
106
m_theme->setCurrentIndex(m_themeModel->indexOf(theme));
110
void DesktopThemeDetails::save()
114
if (m_newThemeName->text().isEmpty()) {
115
themeRoot = ".customized";
116
//Toggle customized theme directory name to ensure theme reload
117
if (QDir(dirs.locateLocal("data", "desktoptheme/" + themeRoot + '/', false)).exists()) {
118
themeRoot = themeRoot + '1';
121
themeRoot = m_newThemeName->text().replace(' ',"_").remove(QRegExp("[^A-Za-z0-9_]"));
125
QFile customSettingsFile;
126
bool customSettingsFileOpen = false;
128
if (m_themeCustomized || !m_newThemeName->text().isEmpty()) {
130
clearCustomized(themeRoot);
132
//Copy all files from the base theme
133
QString baseSource = dirs.locate("data", "desktoptheme/" + m_baseTheme + "/metadata.desktop");
134
baseSource = baseSource.left(baseSource.lastIndexOf('/', -1));
135
KIO::CopyJob *copyBaseTheme = KIO::copyAs(KUrl(baseSource), KUrl(dirs.locateLocal("data", "desktoptheme/" + themeRoot, true)), KIO::HideProgressInfo);
136
KIO::NetAccess::synchronousRun(copyBaseTheme, this);
138
//Prepare settings file for customized theme
139
if (isCustomized(themeRoot)) {
140
customSettingsFile.setFileName(dirs.locateLocal("data", "desktoptheme/" + themeRoot + "/settings"));
141
customSettingsFileOpen = customSettingsFile.open(QFile::WriteOnly);
142
if (customSettingsFileOpen) {
143
QTextStream out(&customSettingsFile);
144
out << "baseTheme=" + m_baseTheme + "\r\n";;
147
QString settingsSource;
149
//Copy each item theme file to new theme folder
150
QHashIterator<int, int> i(m_itemThemeReplacements);
151
while (i.hasNext()) {
153
//Get root directory where item should reside (relative to theme root)
154
QString itemRoot = "";
155
if (m_itemPaths[i.key()].lastIndexOf('/', -1) != -1) {
156
itemRoot= m_itemPaths[i.key()].left(m_itemPaths[i.key()].lastIndexOf('/', -1));
158
//Setup source and destination
161
if (i.value() != -1) {
163
source = "desktoptheme/" + m_themeRoots[i.value()] + '/' + m_itemPaths[i.key()] + '*';
164
dest = "desktoptheme/" + themeRoot + '/' + itemRoot + '/';
165
settingsSource = m_themeRoots[i.value()];
168
source = m_itemFileReplacements[i.key()];
169
dest = "desktoptheme/" + themeRoot + '/' + itemRoot + '/';
170
settingsSource = m_itemFileReplacements[i.key()];
174
//Delete item files at destination before copying (possibly there from base theme copy)
175
const QStringList deleteFiles = dirs.findAllResources("data", "desktoptheme/" + themeRoot + '/' + m_itemPaths[i.key()] + '*',
176
KStandardDirs::NoDuplicates);
177
for (int j = 0; j < deleteFiles.size(); ++j) {
178
KIO::DeleteJob *dj = KIO::del(KUrl(deleteFiles.at(j)), KIO::HideProgressInfo);
179
KIO::NetAccess::synchronousRun(dj, this);
183
dest = dirs.locateLocal("data", dest, true);
184
QStringList copyFiles;
185
if (i.value() != -1) {
186
copyFiles = dirs.findAllResources("data", source, KStandardDirs::NoDuplicates); //copy from theme
188
copyFiles << source; //copy from file
190
for (int j = 0; j < copyFiles.size(); ++j) {
191
KIO::CopyJob *cj = KIO::copy(KUrl(copyFiles.at(j)), KUrl(dest), KIO::HideProgressInfo);
192
KIO::NetAccess::synchronousRun(cj, this);
195
//Record settings file
196
if (customSettingsFileOpen) {
197
QTextStream out(&customSettingsFile);
198
out << m_items.key(i.key()) + "=" + settingsSource +"\r\n";
201
if (customSettingsFileOpen) customSettingsFile.close();
203
// Create new theme FDO desktop file
204
QFile::remove(dirs.locateLocal("data", "desktoptheme/" + themeRoot + "/metadata.desktop", false));
205
KDesktopFile df(dirs.locateLocal("data", "desktoptheme/" + themeRoot + "/metadata.desktop"));
206
KConfigGroup cg = df.desktopGroup();
207
if (isCustomized(themeRoot)) {
208
cg.writeEntry("Name",i18n("(Customized)"));
209
cg.writeEntry("Comment", i18n("User customized theme"));
210
cg.writeEntry("X-KDE-PluginInfo-Name", themeRoot);
212
cg.writeEntry("Name", m_newThemeName->text());
213
cg.writeEntry("Comment", m_newThemeDescription->text());
214
cg.writeEntry("X-KDE-PluginInfo-Author", m_newThemeAuthor->text());
215
cg.writeEntry("X-KDE-PluginInfo-Version", m_newThemeVersion->text());
219
m_themeCustomized = false;
222
m_themeModel->reload();
223
if (m_themeModel->indexOf(themeRoot).isValid()) {
224
m_theme->setCurrentIndex(m_themeModel->indexOf(themeRoot));
225
QString themeName = m_theme->currentIndex().data(Qt::DisplayRole).toString();
226
setDesktopTheme(themeRoot);
231
void DesktopThemeDetails::removeTheme()
233
bool removeTheme = true;
234
KConfigGroup cfg = KConfigGroup(KSharedConfig::openConfig("plasmarc"), "Theme");
235
QString activeTheme = cfg.readEntry("name", "default");
236
const QString theme = m_theme->currentIndex().data(ThemeModel::PackageNameRole).toString();
237
const QString themeName = m_theme->currentIndex().data(Qt::DisplayRole).toString();
240
if (m_themeCustomized) {
241
if(KMessageBox::questionYesNo(this, i18n("Theme items have been changed. Do you still wish remove the \"%1\" theme?", themeName), i18n("Remove Desktop Theme")) == KMessageBox::No) {
245
if (theme == "default") {
246
KMessageBox::information(this, i18n("Removal of the default desktop theme is not allowed."), i18n("Remove Desktop Theme"));
249
if(KMessageBox::questionYesNo(this, i18n("Are you sure you wish remove the \"%1\" theme?", themeName), i18n("Remove Desktop Theme")) == KMessageBox::No) {
257
if (theme == activeTheme) {
258
setDesktopTheme("default");
259
activeTheme = "default";
261
if (QDir(dirs.locateLocal("data", "desktoptheme/" + theme, false)).exists()) {
262
KIO::DeleteJob *deleteTheme = KIO::del(KUrl(dirs.locateLocal("data", "desktoptheme/" + theme, false)), KIO::HideProgressInfo);
263
KIO::NetAccess::synchronousRun(deleteTheme, this);
266
m_themeModel->reload();
268
m_theme->setCurrentIndex(m_themeModel->indexOf(activeTheme));
271
void DesktopThemeDetails::exportTheme()
273
const QString theme = m_theme->currentIndex().data(ThemeModel::PackageNameRole).toString();
275
if (m_themeCustomized ||
276
(isCustomized(theme) && m_newThemeName->text() == "")) {
277
KMessageBox::information(this, i18n("Please apply theme item changes (with a new theme name) before attempting to export theme."), i18n("Export Desktop Theme"));
279
QString themeStoragePath = theme;
282
const QString themePath = dirs.findResource("data", "desktoptheme/" + themeStoragePath + "/metadata.desktop");
283
if (!themePath.isEmpty()){
284
QString expFileName = KFileDialog::getSaveFileName(KUrl(), "*.zip", this, i18n("Export theme to file"));
285
if (!expFileName.endsWith(".zip"))
286
expFileName = expFileName + ".zip";
287
if (!expFileName.isEmpty()) {
288
KUrl path(themePath);
289
KZip expFile(expFileName);
290
expFile.open(QIODevice::WriteOnly);
291
expFile.addLocalDirectory(path.directory (), themeStoragePath);
299
void DesktopThemeDetails::loadThemeItems()
301
// Set up items, item paths and icons
302
m_items.clear(); // clear theme items
303
m_itemPaths.clear(); // clear theme item paths
305
for (int i = 0; themeCollectionName[i].m_type; ++i) {
306
m_items[themeCollectionName[i].m_type] = i;
307
m_itemPaths[i] = themeCollectionName[i].m_themeItemPath;
308
m_itemIcons[i] = themeCollectionName[i].m_iconName;
311
// Get installed themes
312
m_themes.clear(); // clear installed theme list
313
m_themeRoots.clear(); // clear installed theme root paths
315
QStringList themes = dirs.findAllResources("data", "desktoptheme/*/metadata.desktop",
316
KStandardDirs::NoDuplicates);
319
for (int i = 0; i < themes.size(); ++i) {
320
QString theme = themes.at(i);
321
int themeSepIndex = theme.lastIndexOf('/', -1);
322
QString themeRoot = theme.left(themeSepIndex);
323
int themeNameSepIndex = themeRoot.lastIndexOf('/', -1);
324
QString packageName = themeRoot.right(themeRoot.length() - themeNameSepIndex - 1);
325
KDesktopFile df(theme);
326
QString name = df.readName();
327
if (name.isEmpty()) {
331
if (!isCustomized(packageName) && (m_themeRoots.key(packageName, -1) == -1)) {
333
m_themeRoots[j] = packageName;
338
// Set up default item replacements
339
m_itemThemeReplacements.clear();
340
m_itemFileReplacements.clear();
341
QString currentTheme = m_theme->currentIndex().data(ThemeModel::PackageNameRole).toString();
343
if (!isCustomized(currentTheme)) {
344
// Set default replacements to current theme
345
QHashIterator<QString, int> i(m_items);
346
while (i.hasNext()) {
348
m_itemThemeReplacements[i.value()] = m_themeRoots.key(currentTheme);
350
m_baseTheme = currentTheme;
352
// Set default replacements to customized theme settings
353
QFile customSettingsFile(dirs.locateLocal("data", "desktoptheme/" + currentTheme +"/settings"));
354
if (customSettingsFile.open(QFile::ReadOnly)) {
355
QTextStream in(&customSettingsFile);
357
QStringList settingsPair;
358
while (!in.atEnd()) {
359
line = in.readLine();
360
settingsPair = line.split('=');
361
if (settingsPair.at(0) == "baseTheme") {
362
m_baseTheme = settingsPair.at(1);
364
if (m_themeRoots.key(settingsPair.at(1), -1) != -1) { // theme for current item exists
365
m_itemThemeReplacements[m_items[settingsPair.at(0)]] = m_themeRoots.key(settingsPair.at(1));
366
} else if (QFile::exists(settingsPair.at(1))) {
367
m_itemThemeReplacements[m_items[settingsPair.at(0)]] = -1;
368
m_itemFileReplacements[m_items[settingsPair.at(0)]] = settingsPair.at(1);
372
customSettingsFile.close();
376
// Build displayed list of theme items
377
m_themeItemList->setRowCount(m_items.size());
378
m_themeItemList->setColumnCount(2);
379
m_themeItemList->setHorizontalHeaderLabels(QStringList()<< i18n("Theme Item")<<i18n("Source"));
380
QString displayedItem;
381
QHashIterator<QString, int> i(m_items);
382
while (i.hasNext()) {
384
displayedItem = displayedItemText(i.value());
385
m_themeItemList->setItem(i.value(), 0, new QTableWidgetItem(displayedItem));
386
m_themeItemList->item(i.value(),0)->setIcon(KIcon(m_itemIcons[i.value()]));
387
m_themeItemList->setCellWidget(i.value(), 1, new QComboBox());
388
updateReplaceItemList(i.value());
389
m_themeItemList->resizeColumnToContents(1);
391
m_themeItemList->setSelectionBehavior(QAbstractItemView::SelectRows);
392
m_themeItemList->verticalHeader()->hide();
393
m_themeItemList->horizontalHeader()->setStretchLastSection(true);
394
m_themeItemList->horizontalHeader()->setMinimumSectionSize(120);
395
m_themeItemList->horizontalHeader()->setResizeMode(1, QHeaderView::ResizeToContents);;
396
m_themeItemList->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);;
397
m_themeItemList->setCurrentCell(0, 1);
400
void DesktopThemeDetails::updateReplaceItemList(const int& item)
402
QString currentTheme = m_theme->currentIndex().data(ThemeModel::PackageNameRole).toString();
405
// Repopulate combobox droplist
406
QComboBox *itemComboBox = static_cast<QComboBox*>(m_themeItemList->cellWidget(item,1));
407
disconnect(itemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(replacementItemChanged()));
408
itemComboBox->clear();
409
for (int i = 0; i < m_themes.size(); ++i) {
410
QString displayedDropListItem = i18n("%1 %2", m_themes.key(i), displayedItemText(item));
411
itemComboBox->addItem(displayedDropListItem);
413
itemComboBox->addItem(i18n("File..."));
415
// Set combobox value to current replacement
416
if (m_itemThemeReplacements[item] != -1) {
417
itemComboBox->setCurrentIndex(m_itemThemeReplacements[item]);
419
itemComboBox->addItem(m_itemFileReplacements[item]);
420
itemComboBox->setCurrentIndex(itemComboBox->findText(m_itemFileReplacements[item]));
422
connect(itemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(replacementItemChanged()));
425
void DesktopThemeDetails::replacementItemChanged()
427
//Check items to see if theme has been customized
428
m_themeCustomized = true;
429
QHashIterator<QString, int> i(m_items);
430
while (i.hasNext()) {
432
QComboBox *itemComboBox = static_cast<QComboBox*>(m_themeItemList->cellWidget(i.value(), 1));
433
int replacement = itemComboBox->currentIndex();
434
if (replacement <= (m_themes.size() - 1)) {
435
// Item replacement source is a theme
436
m_itemThemeReplacements[i.value()] = itemComboBox->currentIndex();
437
} else if (replacement > (m_themes.size() - 1)) {
438
// Item replacement source is a file
439
if (itemComboBox->currentText() == i18n("File...")) {
440
//Get the filename for the replacement item
441
QString translated_key = i18nc("plasma name", qPrintable( i.key() ) );
442
QString fileReplacement = KFileDialog::getOpenFileName(KUrl(), QString(), this, i18n("Select File to Use for %1",translated_key));
443
if (!fileReplacement.isEmpty()) {
444
m_itemFileReplacements[i.value()] = fileReplacement;
445
int index = itemComboBox->findText(fileReplacement);
446
if (index == -1) itemComboBox->addItem(fileReplacement);
447
disconnect(itemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(replacementItemChanged()));
448
itemComboBox->setCurrentIndex(itemComboBox->findText(fileReplacement));
449
connect(itemComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(replacementItemChanged()));
450
m_itemThemeReplacements[i.value()] = -1; //source is not a theme
451
m_itemFileReplacements[i.value()] = itemComboBox->currentText();
453
// Reset combobox to previous value if no file is selected
454
if (m_itemThemeReplacements[i.value()] != -1) {
455
itemComboBox->setCurrentIndex(m_itemThemeReplacements[i.value()]);
457
itemComboBox->setCurrentIndex(itemComboBox->findText(m_itemFileReplacements[i.value()]));
459
m_themeCustomized = false;
462
m_itemThemeReplacements[i.value()] = -1; //source is not a theme
463
m_itemFileReplacements[i.value()] = itemComboBox->currentText();
468
if (m_themeCustomized) emit changed();
471
void DesktopThemeDetails::newThemeInfoChanged()
476
void DesktopThemeDetails::resetThemeDetails()
478
QString theme = m_theme->currentIndex().data(ThemeModel::PackageNameRole).toString();
480
m_themeInfoName->setText(m_theme->currentIndex().data(Qt::DisplayRole).toString());
481
m_themeInfoDescription->setText(m_theme->currentIndex().data(ThemeModel::PackageDescriptionRole).toString());
482
QString author = m_theme->currentIndex().data(ThemeModel::PackageAuthorRole).toString();
484
if (!author.isEmpty()) {
485
m_themeInfoAuthor->setText(i18n(" Author: %1",author));
487
m_themeInfoAuthor->setText("");
489
QString version = m_theme->currentIndex().data(ThemeModel::PackageVersionRole).toString();
490
if (!version.isEmpty()) {
491
m_themeInfoVersion->setText(i18n("Version: %1",version));
493
m_themeInfoVersion->setText("");
498
m_newThemeName->clear();
499
m_newThemeAuthor->clear();
500
m_newThemeVersion->clear();
501
m_newThemeDescription->clear();
502
m_themeCustomized = false;
505
void DesktopThemeDetails::toggleAdvancedVisible()
507
m_newThemeNameLabel->setVisible(m_enableAdvanced->isChecked());
508
m_newThemeName->setVisible(m_enableAdvanced->isChecked());
509
m_newThemeAuthor->setVisible(m_enableAdvanced->isChecked());
510
m_newThemeAuthorLabel->setVisible(m_enableAdvanced->isChecked());
511
m_newThemeVersion->setVisible(m_enableAdvanced->isChecked());
512
m_newThemeVersionLabel->setVisible(m_enableAdvanced->isChecked());
513
m_newThemeDescriptionLabel->setVisible(m_enableAdvanced->isChecked());
514
m_newThemeDescription->setVisible(m_enableAdvanced->isChecked());
515
m_exportThemeButton->setVisible(m_enableAdvanced->isChecked());
516
m_removeThemeButton->setVisible(m_enableAdvanced->isChecked());
517
m_advancedLine->setVisible(m_enableAdvanced->isChecked());
520
bool DesktopThemeDetails::isCustomized(const QString& theme) {
521
if (theme == ".customized" || theme == ".customized1") {
528
void DesktopThemeDetails::clearCustomized(const QString& themeRoot) {
531
if ((isCustomized(themeRoot))) {
532
// Remove both possible unnamed customized directories
533
if (QDir(dirs.locateLocal("data", "desktoptheme/.customized", false)).exists()) {
534
KIO::DeleteJob *clearCustom = KIO::del(KUrl(dirs.locateLocal("data", "desktoptheme/.customized", false)), KIO::HideProgressInfo);
535
KIO::NetAccess::synchronousRun(clearCustom, this);
537
if (QDir(dirs.locateLocal("data", "desktoptheme/.customized1", false)).exists()) {
538
KIO::DeleteJob *clearCustom1 = KIO::del(KUrl(dirs.locateLocal("data", "desktoptheme/.customized1", false)), KIO::HideProgressInfo);
539
KIO::NetAccess::synchronousRun(clearCustom1, this);
542
if (QDir(dirs.locateLocal("data", "desktoptheme/" + themeRoot, false)).exists()) {
543
KIO::DeleteJob *clearCustom = KIO::del(KUrl(dirs.locateLocal("data", "desktoptheme/" + themeRoot, false)), KIO::HideProgressInfo);
544
KIO::NetAccess::synchronousRun(clearCustom, this);
549
QString DesktopThemeDetails::displayedItemText(int item) {
550
QString displayedText = m_items.key(item);
551
for (int i = 0; themeCollectionName[i].m_type; ++i) {
552
if (themeCollectionName[i].m_type == m_items.key(item)) {
553
displayedText = i18nc("plasma name", themeCollectionName[i].m_displayItemName);
556
return displayedText;
559
void DesktopThemeDetails::themeSelectionChanged(const QItemSelection newSelection, const QItemSelection oldSelection)
561
QString theme = m_theme->currentIndex().data(ThemeModel::PackageNameRole).toString();
562
if (theme == "default") {
563
m_removeThemeButton->setEnabled(false);
565
m_removeThemeButton->setEnabled(true);
568
Q_UNUSED(newSelection);
569
Q_UNUSED(oldSelection);
572
void DesktopThemeDetails::setDesktopTheme(QString themeRoot)
574
KConfig config(KStandardDirs::locate("config", "plasmarc"));
575
KConfigGroup cg = KConfigGroup(&config, "Theme");
576
if (themeRoot == "default") {
577
cg.deleteEntry("name");
579
cg.writeEntry("name", themeRoot);