summaryrefslogtreecommitdiff
path: root/src/plugins/coreplugin/themesettings.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/coreplugin/themesettings.cpp')
-rw-r--r--src/plugins/coreplugin/themesettings.cpp465
1 files changed, 465 insertions, 0 deletions
diff --git a/src/plugins/coreplugin/themesettings.cpp b/src/plugins/coreplugin/themesettings.cpp
new file mode 100644
index 0000000000..c21dc4ad44
--- /dev/null
+++ b/src/plugins/coreplugin/themesettings.cpp
@@ -0,0 +1,465 @@
+/****************************************************************************
+**
+** Copyright (C) 2014 Thorben Kroeger <thorbenkroeger@gmail.com>.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of Qt Creator.
+**
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://www.qt.io/licensing. For further information
+** use the contact form at http://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 or version 3 as published by the Free
+** Software Foundation and appearing in the file LICENSE.LGPLv21 and
+** LICENSE.LGPLv3 included in the packaging of this file. Please review the
+** following information to ensure the GNU Lesser General Public License
+** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+****************************************************************************/
+
+#include "themesettings.h"
+#include "coreconstants.h"
+#include "icore.h"
+#include "editormanager/editormanager_p.h"
+#include "themeeditor/themesettingstablemodel.h"
+
+#include <utils/qtcassert.h>
+
+#include <QDebug>
+#include <QDir>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QSettings>
+
+#include "ui_themesettings.h"
+
+using namespace Utils;
+
+namespace Core {
+namespace Internal {
+
+const char themeNameKey[] = "ThemeName";
+
+static QString customThemesPath()
+{
+ QString path = Core::ICore::userResourcePath();
+ path.append(QLatin1String("/themes/"));
+ return path;
+}
+
+static QString createThemeFileName(const QString &pattern)
+{
+ const QString stylesPath = customThemesPath();
+ QString baseFileName = stylesPath;
+ baseFileName += pattern;
+
+ // Find an available file name
+ int i = 1;
+ QString fileName;
+ do {
+ fileName = baseFileName.arg((i == 1) ? QString() : QString::number(i));
+ ++i;
+ } while (QFile::exists(fileName));
+
+ // Create the base directory when it doesn't exist
+ if (!QFile::exists(stylesPath) && !QDir().mkpath(stylesPath)) {
+ qWarning() << "Failed to create theme directory:" << stylesPath;
+ return QString();
+ }
+ return fileName;
+}
+
+
+struct ThemeEntry
+{
+ ThemeEntry() {}
+ ThemeEntry(const QString &fileName, bool readOnly):
+ m_fileName(fileName),
+ m_readOnly(readOnly)
+ { }
+
+ QString fileName() const { return m_fileName; }
+ QString name() const;
+ bool readOnly() const { return m_readOnly; }
+
+private:
+ QString m_fileName;
+ bool m_readOnly;
+};
+
+QString ThemeEntry::name() const
+{
+ QSettings settings(m_fileName, QSettings::IniFormat);
+ QString n = settings.value(QLatin1String(themeNameKey), QCoreApplication::tr("unnamed")).toString();
+ return m_readOnly ? QCoreApplication::tr("%1 (built-in)").arg(n) : n;
+}
+
+
+class ThemeListModel : public QAbstractListModel
+{
+public:
+ ThemeListModel(QObject *parent = 0):
+ QAbstractListModel(parent)
+ {
+ }
+
+ int rowCount(const QModelIndex &parent) const
+ {
+ return parent.isValid() ? 0 : m_themes.size();
+ }
+
+ QVariant data(const QModelIndex &index, int role) const
+ {
+ if (role == Qt::DisplayRole)
+ return m_themes.at(index.row()).name();
+ return QVariant();
+ }
+
+ void removeTheme(int index)
+ {
+ beginRemoveRows(QModelIndex(), index, index);
+ m_themes.removeAt(index);
+ endRemoveRows();
+ }
+
+ void setThemes(const QList<ThemeEntry> &themes)
+ {
+ beginResetModel();
+ m_themes = themes;
+ endResetModel();
+ }
+
+ const ThemeEntry &themeAt(int index) const
+ {
+ return m_themes.at(index);
+ }
+
+private:
+ QList<ThemeEntry> m_themes;
+};
+
+
+class ThemeSettingsPrivate
+{
+public:
+ ThemeSettingsPrivate();
+ ~ThemeSettingsPrivate();
+
+public:
+ ThemeListModel *m_themeListModel;
+ bool m_refreshingThemeList;
+ Ui::ThemeSettings *m_ui;
+ QPointer<QWidget> m_widget;
+ ThemeEntry m_currentTheme;
+};
+
+ThemeSettingsPrivate::ThemeSettingsPrivate()
+ : m_themeListModel(new ThemeListModel)
+ , m_refreshingThemeList(false)
+ , m_ui(0)
+{
+ m_currentTheme = ThemeEntry(creatorTheme()->fileName(), true);
+}
+
+ThemeSettingsPrivate::~ThemeSettingsPrivate()
+{
+ delete m_themeListModel;
+}
+
+ThemeSettings::ThemeSettings()
+{
+ setId(Core::Constants::SETTINGS_ID_ENVIRONMENT);
+ setDisplayName(tr("Theme"));
+ setCategory(Core::Constants::SETTINGS_CATEGORY_CORE);
+ setDisplayCategory(QCoreApplication::translate("Core", Core::Constants::SETTINGS_TR_CATEGORY_CORE));
+ setCategoryIcon(QLatin1String(Core::Constants::SETTINGS_CATEGORY_CORE_ICON));
+
+ d = new ThemeSettingsPrivate();
+}
+
+ThemeSettings::~ThemeSettings()
+{
+ delete d;
+}
+
+void ThemeSettings::refreshThemeList()
+{
+ QList<ThemeEntry> themes;
+
+ QString resourcePath = Core::ICore::resourcePath();
+ QDir themeDir(resourcePath + QLatin1String("/themes"));
+ themeDir.setNameFilters(QStringList() << QLatin1String("*.creatortheme"));
+ themeDir.setFilter(QDir::Files);
+
+ int selected = 0;
+
+ QStringList themeList = themeDir.entryList();
+ QString defaultTheme = QFileInfo(defaultThemeFileName()).fileName();
+ if (themeList.removeAll(defaultTheme))
+ themeList.prepend(defaultTheme);
+ foreach (const QString &file, themeList) {
+ const QString fileName = themeDir.absoluteFilePath(file);
+ if (d->m_currentTheme.fileName() == fileName)
+ selected = themes.size();
+ themes.append(ThemeEntry(fileName, true));
+ }
+
+ if (themes.isEmpty())
+ qWarning() << "Warning: no themes found in path:" << themeDir.path();
+
+ themeDir.setPath(customThemesPath());
+ foreach (const QString &file, themeDir.entryList()) {
+ const QString fileName = themeDir.absoluteFilePath(file);
+ if (d->m_currentTheme.fileName() == fileName)
+ selected = themes.size();
+ themes.append(ThemeEntry(fileName, false));
+ }
+
+ d->m_currentTheme = themes[selected];
+
+ d->m_refreshingThemeList = true;
+ d->m_themeListModel->setThemes(themes);
+ d->m_ui->themeComboBox->setCurrentIndex(selected);
+ d->m_refreshingThemeList = false;
+}
+
+QString ThemeSettings::defaultThemeFileName(const QString &fileName)
+{
+ QString defaultScheme = Core::ICore::resourcePath();
+ defaultScheme += QLatin1String("/themes/");
+
+ if (!fileName.isEmpty() && QFile::exists(defaultScheme + fileName))
+ defaultScheme += fileName;
+ else
+ defaultScheme += QLatin1String("default.creatortheme");
+
+ return defaultScheme;
+}
+
+void ThemeSettings::themeSelected(int index)
+{
+ bool readOnly = true;
+ if (index != -1) {
+ // Check whether we're switching away from a changed theme
+ if (!d->m_refreshingThemeList)
+ maybeSaveTheme();
+
+ const ThemeEntry &entry = d->m_themeListModel->themeAt(index);
+ readOnly = entry.readOnly();
+ d->m_currentTheme = entry;
+
+ QSettings settings(entry.fileName(), QSettings::IniFormat);
+ Theme theme;
+ theme.readSettings(settings);
+ d->m_ui->editor->initFrom(&theme);
+ }
+ d->m_ui->copyButton->setEnabled(index != -1);
+ d->m_ui->deleteButton->setEnabled(!readOnly);
+ d->m_ui->renameButton->setEnabled(!readOnly);
+ d->m_ui->editor->setReadOnly(readOnly);
+}
+
+QWidget *ThemeSettings::widget()
+{
+ if (!d->m_widget) {
+ d->m_widget = new QWidget;
+ d->m_ui = new Ui::ThemeSettings();
+ d->m_ui->setupUi(d->m_widget);
+ d->m_ui->themeComboBox->setModel(d->m_themeListModel);
+
+ connect(d->m_ui->themeComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
+ this, &ThemeSettings::themeSelected);
+ connect(d->m_ui->copyButton, &QAbstractButton::clicked, this, &ThemeSettings::copyTheme);
+ connect(d->m_ui->renameButton, &QAbstractButton::clicked, this, &ThemeSettings::renameTheme);
+ connect(d->m_ui->deleteButton, &QAbstractButton::clicked, this, &ThemeSettings::confirmDeleteTheme);
+
+ refreshThemeList();
+ }
+ return d->m_widget;
+}
+
+void ThemeSettings::confirmDeleteTheme()
+{
+ const int index = d->m_ui->themeComboBox->currentIndex();
+ if (index == -1)
+ return;
+
+ const ThemeEntry &entry = d->m_themeListModel->themeAt(index);
+ if (entry.readOnly())
+ return;
+
+ QMessageBox *messageBox = new QMessageBox(QMessageBox::Warning,
+ tr("Delete Theme"),
+ tr("Are you sure you want to delete the theme '%1' permanently?").arg(entry.name()),
+ QMessageBox::Discard | QMessageBox::Cancel,
+ d->m_ui->deleteButton->window());
+
+ // Change the text and role of the discard button
+ QPushButton *deleteButton = static_cast<QPushButton*>(messageBox->button(QMessageBox::Discard));
+ deleteButton->setText(tr("Delete"));
+ messageBox->addButton(deleteButton, QMessageBox::AcceptRole);
+ messageBox->setDefaultButton(deleteButton);
+
+ connect(deleteButton, &QAbstractButton::clicked, messageBox, &QDialog::accept);
+ connect(messageBox, &QDialog::accepted, this, &ThemeSettings::deleteTheme);
+ messageBox->setAttribute(Qt::WA_DeleteOnClose);
+ messageBox->open();
+}
+
+void ThemeSettings::deleteTheme()
+{
+ const int index = d->m_ui->themeComboBox->currentIndex();
+ QTC_ASSERT(index != -1, return);
+
+ const ThemeEntry &entry = d->m_themeListModel->themeAt(index);
+ QTC_ASSERT(!entry.readOnly(), return);
+
+ if (QFile::remove(entry.fileName()))
+ d->m_themeListModel->removeTheme(index);
+}
+
+void ThemeSettings::copyTheme()
+{
+ QInputDialog *dialog = new QInputDialog(d->m_ui->copyButton->window());
+ dialog->setAttribute(Qt::WA_DeleteOnClose);
+ dialog->setInputMode(QInputDialog::TextInput);
+ dialog->setWindowTitle(tr("Copy Theme"));
+ dialog->setLabelText(tr("Theme name:"));
+
+ //TODO
+ //dialog->setTextValue(tr("%1 (copy)").arg(d_ptr->m_value.colorScheme().displayName()));
+
+ connect(dialog, &QInputDialog::textValueSelected, this, &ThemeSettings::copyThemeByName);
+ dialog->open();
+}
+
+void ThemeSettings::maybeSaveTheme()
+{
+ if (!d->m_ui->editor->model()->hasChanges())
+ return;
+
+ QMessageBox *messageBox = new QMessageBox(QMessageBox::Warning,
+ tr("Theme Changed"),
+ tr("The theme \"%1\" was modified, do you want to save the changes?")
+ .arg(d->m_currentTheme.name()),
+ QMessageBox::Discard | QMessageBox::Save,
+ d->m_ui->themeComboBox->window());
+
+ // Change the text of the discard button
+ QPushButton *discardButton = static_cast<QPushButton*>(messageBox->button(QMessageBox::Discard));
+ discardButton->setText(tr("Discard"));
+ messageBox->addButton(discardButton, QMessageBox::DestructiveRole);
+ messageBox->setDefaultButton(QMessageBox::Save);
+
+ if (messageBox->exec() == QMessageBox::Save) {
+ Theme newTheme;
+ d->m_ui->editor->model()->toTheme(&newTheme);
+ newTheme.writeSettings(d->m_currentTheme.fileName());
+ }
+}
+
+void ThemeSettings::renameTheme()
+{
+ int index = d->m_ui->themeComboBox->currentIndex();
+ if (index == -1)
+ return;
+ const ThemeEntry &entry = d->m_themeListModel->themeAt(index);
+
+ maybeSaveTheme();
+
+ QInputDialog *dialog = new QInputDialog(d->m_ui->renameButton->window());
+ dialog->setInputMode(QInputDialog::TextInput);
+ dialog->setWindowTitle(tr("Rename Theme"));
+ dialog->setLabelText(tr("Theme name:"));
+ dialog->setTextValue(d->m_ui->editor->model()->m_name);
+ int ret = dialog->exec();
+ QString newName = dialog->textValue();
+ delete dialog;
+
+ if (ret != QDialog::Accepted || newName.isEmpty())
+ return;
+
+ // overwrite file with new name
+ Theme newTheme;
+ d->m_ui->editor->model()->toTheme(&newTheme);
+ newTheme.setName(newName);
+ newTheme.writeSettings(entry.fileName());
+
+ refreshThemeList();
+}
+
+void ThemeSettings::copyThemeByName(const QString &name)
+{
+ int index = d->m_ui->themeComboBox->currentIndex();
+ if (index == -1)
+ return;
+
+ const ThemeEntry &entry = d->m_themeListModel->themeAt(index);
+
+ QString baseFileName = QFileInfo(entry.fileName()).completeBaseName();
+ baseFileName += QLatin1String("_copy%1.creatortheme");
+ QString fileName = createThemeFileName(baseFileName);
+
+ if (fileName.isEmpty())
+ return;
+
+ // Ask about saving any existing modifactions
+ maybeSaveTheme();
+
+ Theme newTheme;
+ d->m_ui->editor->model()->toTheme(&newTheme);
+ newTheme.setName(name);
+ newTheme.writeSettings(fileName);
+
+ d->m_currentTheme = ThemeEntry(fileName, true);
+
+ refreshThemeList();
+}
+
+void ThemeSettings::apply()
+{
+ if (!d->m_ui) // wasn't shown, can't be changed
+ return;
+
+ {
+ d->m_ui->editor->model()->toTheme(creatorTheme());
+ QPalette newPalette = creatorTheme()->palette(qApp->palette());
+ qApp->setPalette(newPalette);
+ foreach (QWidget *w, QApplication::topLevelWidgets())
+ w->update();
+ }
+
+ // save definition of theme
+ if (!d->m_currentTheme.readOnly()) {
+ Theme newTheme;
+ d->m_ui->editor->model()->toTheme(&newTheme);
+ newTheme.writeSettings(d->m_currentTheme.fileName());
+ }
+
+ // save filename of selected theme in global config
+ QSettings *settings = Core::ICore::settings();
+ settings->setValue(QLatin1String(Core::Constants::SETTINGS_THEME), d->m_currentTheme.fileName());
+}
+
+void ThemeSettings::finish()
+{
+ delete d->m_widget;
+ if (!d->m_ui) // page was never shown
+ return
+ delete d->m_ui;
+ d->m_ui = 0;
+}
+
+} // namespace Internal
+} // namespace Core