/**************************************************************************** ** ** Copyright (C) 2014 Thorben Kroeger . ** 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 #include #include #include #include #include #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 &themes) { beginResetModel(); m_themes = themes; endResetModel(); } const ThemeEntry &themeAt(int index) const { return m_themes.at(index); } private: QList m_themes; }; class ThemeSettingsPrivate { public: ThemeSettingsPrivate(); ~ThemeSettingsPrivate(); public: ThemeListModel *m_themeListModel; bool m_refreshingThemeList; Ui::ThemeSettings *m_ui; QPointer 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 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(&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(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(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()); 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