forked from qt-creator/qt-creator
464 lines
14 KiB
C++
464 lines
14 KiB
C++
|
|
/****************************************************************************
|
||
|
|
**
|
||
|
|
** 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());
|
||
|
|
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
|