Commit 1ebf620d authored by Mikhail Zolotukhin's avatar Mikhail Zolotukhin
Browse files

Rewrite GTK KCM

Summary:
After several changes, GTK Applications settings are now in
sync with KDE equivalent ones, so that GTK KCM now controls only the
theme of the GTK applications. But GTK KCM was written with the thought
that it controls entire GTK apps appearance in mind. Its code structure
is now redundant and needs to be either rewritten or purged in favour of
new one, that will be present in Application Style KCM code base.

The last approach is preferable, because of the goal of removing GTK KCM
to simplify System Settings structure. However, there are some points to
be noted:

# The consensus of where exactly to put the GTK applications settings
   in App Style KCM is not yet reached.
# A couple of changes to the kded settings sync daemon are needed, to
   provide functionality, that is left in GTK KCM (Themes
   Installation/Deletion, Preview etc) without depending on GTK libs
# There are some bugs in KCM, that are present in the moment, that
   could only be fixed with architecture changes (for example: not
   working previews, when the xsettingsd daemon is running; broken icons
   in the previews; gtk themes do not applying for flatpak apps)

Given the points above I decided to rewrite GTK KCM, so that complex
design task (1) would be solved after changes (2) are made. And also
in the process some bugs (3) will be fixed.

Now, GTK KCM is not responsible for managing GTK configs, the one who
does is the kded module, that is asked through DBus, when it is needed
to do so.
The stuff, that is managed by kded module:

# Setting selected GTK2 and GTK3 themes for applications
# Getting current GTK2 and GTK3 themes for applications
# Previewing GTK2 and GTK3 applications for selected themes

Technically 2 and 3 could be done in KCM directly, because they do not
depend on GTK libs, but I thought, that it would be logically related to
the stuff, that is done via kded module. If you think, that that
approach is not right, feel free to criticise.

The stuff, that is done in KCM directly:

# Getting the list of installed GTK themes
# Installing and removing the GTK theme

Also there are some visual changes:

# Theme deletion is now done via button near GTK theme combo, instead
   of separate dialog
# Theme installation from file is now done via button at the bottom,
   as in the other similar KCMs
# Preview button now has a title with an ellipsis to indicate, that
   it launches GTK app in a pop up window

BUG: 405405

Test Plan:
{F7847441}
# Relaunch kded5
# Open GTK KCM
# Check if gtk themes changing works
# Check if the previews are working
# Check if the local themes deletion works
# Check if theme installation from archive works
# Check if GHNS theme installation works

Reviewers: apol, #vdg, ngraham, cblack

Reviewed By: #vdg, ngraham, cblack

Subscribers: cblack, baberts, ngraham, plasma-devel

Tags: #plasma

Maniphest Tasks: T10611

Differential Revision: https://phabricator.kde.org/D26261
parent 19ca9f6c
project(kde-gtk-config)
set(PROJECT_VERSION "5.17.80")
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 3.10)
find_package(ECM 0.0.9 REQUIRED NO_MODULE)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" ${ECM_MODULE_PATH})
find_package(Qt5 REQUIRED NO_MODULE COMPONENTS Widgets Svg Test)
find_package(Qt5 REQUIRED NO_MODULE COMPONENTS Widgets DBus)
find_package(KF5 REQUIRED COMPONENTS I18n KIO ConfigWidgets NewStuff Archive KCMUtils IconThemes DBusAddons)
find_package(GTK3 REQUIRED)
find_package(GSettingSchemas REQUIRED)
......@@ -28,26 +28,17 @@ include(KDECompilerSettings NO_POLICY_SCOPE)
include(KDEClangFormat)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake kded/config.h)
# Set KI18n translation domain
add_definitions(-DTRANSLATION_DOMAIN=\"kde-gtk-config\")
set(kcm_SRCS
src/appearancegtk3.cpp
src/appearancegtk2.cpp
src/appearencegtk.cpp
src/abstractappearance.cpp
src/thread.cpp
src/installer.cpp
src/gtkconfigkcmodule.cpp
src/dialog_installer.cpp
src/dialog_uninstaller.cpp
)
ki18n_wrap_ui(kcm_SRCS
src/ui/gui.ui
src/ui/dialog_installer.ui
src/ui/dialog_uninstaller.ui
)
add_library(kcm_kdegtkconfig MODULE ${kcm_SRCS})
......@@ -63,14 +54,14 @@ target_link_libraries(kcm_kdegtkconfig
${GLIB2_LIBRARY}
${GTK3_LIBRARY}
${GOBJECT2_LIBRARY}
Qt5::Svg
Qt5::DBus
KF5::ConfigCore
KF5::I18n
KF5::KIOWidgets
KF5::NewStuff
KF5::Archive
KF5::ConfigWidgets
KF5::IconThemes
KF5::DBusAddons
)
kcoreaddons_desktop_to_json(kcm_kdegtkconfig kde-gtk-config.desktop)
......@@ -82,7 +73,6 @@ install(FILES kde-gtk-config.desktop DESTINATION ${SERVICES_INSTALL_DIR})
add_subdirectory(gtkproxies)
add_subdirectory(gtk3proxies)
add_subdirectory(icons)
add_subdirectory(tests)
add_subdirectory(kded)
# add clang-format target for all our real source files
......
......@@ -2,6 +2,7 @@ set(kscreen_daemon_SRCS
gtkconfig.cpp
configeditor.cpp
configvalueprovider.cpp
themepreviewer.cpp
)
add_library(gtkconfig MODULE ${kscreen_daemon_SRCS})
......
......@@ -35,16 +35,6 @@
#include "configeditor.h"
static void replaceValueInGtkrcContents(QString &gtkrcContents, const QString &paramName, const QString &paramValue);
static void replaceValueInXSettingsdContents(QString &xSettingsdContents, const QString &paramName, const QString &paramValue);
static void reloadGtk2Apps();
static void reloadXSettingsd();
static QString readFileContents(QFile &gtkrc);
static pid_t pidOfXSettingsd();
void ConfigEditor::setGtk3ConfigValueDconf(const QString &paramName, const QString &paramValue, const QString &category)
{
g_autoptr(GSettings) gsettings = g_settings_new(category.toUtf8().constData());
......@@ -99,7 +89,64 @@ void ConfigEditor::setGtk2ConfigValue(const QString &paramName, const QString &p
reloadGtk2Apps();
}
static QString readFileContents(QFile &file)
QString ConfigEditor::gtk2ConfigValue(const QString& paramName)
{
QString gtkrcPath = QDir::homePath() + QStringLiteral("/.gtkrc-2.0");
QFile gtkrc(gtkrcPath);
if (gtkrc.open(QIODevice::ReadWrite | QIODevice::Text)) {
const QRegularExpression regExp(paramName + QStringLiteral("=[^\n]*($|\n)"));
while (!gtkrc.atEnd()) {
QString line = gtkrc.readLine();
if (line.contains(regExp)) {
return line.split('"')[1];
}
}
}
return QStringLiteral("Breeze");
}
QString ConfigEditor::gtk3ConfigValueSettingsIni(const QString& paramName)
{
QString configLocation = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
QString gtk3ConfigPath = configLocation + QStringLiteral("/gtk-3.0/settings.ini");
KSharedConfig::Ptr gtk3Config = KSharedConfig::openConfig(gtk3ConfigPath, KConfig::NoGlobals);
KConfigGroup group = gtk3Config->group(QStringLiteral("Settings"));
return group.readEntry(paramName, QStringLiteral("Breeze"));
}
void ConfigEditor::removeLegacyGtk2Strings()
{
QString gtkrcPath = QDir::homePath() + QStringLiteral("/.gtkrc-2.0");
QFile gtkrc(gtkrcPath);
QString gtkrcContents = readFileContents(gtkrc);
// Remove "include" lines
// Example:
// include "/usr/share/themes/Adwaita-dark/gtk-2.0/gtkrc"
static const QRegularExpression includeRegExp(QStringLiteral("include .*\n"));
gtkrcContents.remove(includeRegExp);
// Remove redundant font config lines
// Example:
// style "user-font"
// {
// font_name="Noto Sans Regular"
// }
// widget_class "*" style "user-font"
static const QRegularExpression userFontStyleRegexp(QStringLiteral("style(.|\n)*{(.|\n)*}\nwidget_class.*\"user-font\""));
gtkrcContents.remove(userFontStyleRegexp);
gtkrc.remove();
gtkrc.open(QIODevice::WriteOnly | QIODevice::Text);
gtkrc.write(gtkrcContents.toUtf8());
reloadGtk2Apps();
}
QString ConfigEditor::readFileContents(QFile &file)
{
if (file.open(QIODevice::ReadWrite | QIODevice::Text)) {
return file.readAll();
......@@ -108,7 +155,7 @@ static QString readFileContents(QFile &file)
}
}
static void replaceValueInGtkrcContents(QString &gtkrcContents, const QString &paramName, const QString &paramValue)
void ConfigEditor::replaceValueInGtkrcContents(QString &gtkrcContents, const QString &paramName, const QString &paramValue)
{
const QRegularExpression regExp(paramName + QStringLiteral("=[^\n]*($|\n)"));
......@@ -133,7 +180,7 @@ static void replaceValueInGtkrcContents(QString &gtkrcContents, const QString &p
}
}
static void replaceValueInXSettingsdContents(QString &xSettingsdContents, const QString &paramName, const QString &paramValue)
void ConfigEditor::replaceValueInXSettingsdContents(QString &xSettingsdContents, const QString &paramName, const QString &paramValue)
{
const QRegularExpression regExp(paramName + QStringLiteral(" [^\n]*($|\n)"));
......@@ -158,12 +205,21 @@ static void replaceValueInXSettingsdContents(QString &xSettingsdContents, const
}
}
static void reloadGtk2Apps()
void ConfigEditor::reloadGtk2Apps()
{
QProcess::startDetached(QStandardPaths::findExecutable(QStringLiteral("reload_gtk_apps")));
}
static void reloadXSettingsd()
pid_t ConfigEditor::pidOfXSettingsd()
{
QProcess pidof;
pidof.start(QStringLiteral("pidof"), QStringList() << QStringLiteral("-s") << QStringLiteral("xsettingsd"));
pidof.waitForFinished();
QString xsettingsdPid = QString(pidof.readAllStandardOutput()).remove('\n');
return xsettingsdPid.toInt();
}
void ConfigEditor::reloadXSettingsd()
{
pid_t xSettingsdPid = pidOfXSettingsd();
if (xSettingsdPid == 0) {
......@@ -172,12 +228,3 @@ static void reloadXSettingsd()
kill(xSettingsdPid, SIGHUP);
}
}
static pid_t pidOfXSettingsd()
{
QProcess pidof;
pidof.start(QStringLiteral("pidof"), QStringList() << QStringLiteral("-s") << QStringLiteral("xsettingsd"));
pidof.waitForFinished();
QString xsettingsdPid = QString(pidof.readAllStandardOutput()).remove('\n');
return xsettingsdPid.toInt();
}
......@@ -23,6 +23,7 @@
#include <QString>
class QFile;
class QString;
namespace ConfigEditor
{
......@@ -30,4 +31,20 @@ namespace ConfigEditor
void setGtk3ConfigValueDconf(const QString &paramName, const QString &paramValue, const QString &category = QStringLiteral("org.gnome.desktop.interface"));
void setGtk3ConfigValueSettingsIni(const QString &paramName, const QString &paramValue);
void setGtk3ConfigValueXSettingsd(const QString &paramName, const QString &paramValue);
QString gtk2ConfigValue(const QString& paramName);
QString gtk3ConfigValueSettingsIni(const QString& paramName);
void removeLegacyGtk2Strings();
void replaceValueInGtkrcContents(QString &gtkrcContents, const QString &paramName, const QString &paramValue);
void replaceValueInXSettingsdContents(QString &xSettingsdContents, const QString &paramName, const QString &paramValue);
QString readFileContents(QFile &gtkrc);
void reloadGtk2Apps();
void reloadXSettingsd();
pid_t pidOfXSettingsd();
};
......@@ -19,9 +19,9 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDebug>
#include <QFont>
#include <QDBusConnection>
#include <QDBusMessage>
#include <QGuiApplication>
#include <KIconLoader>
......@@ -30,26 +30,75 @@
#include "gtkconfig.h"
#include "configvalueprovider.h"
#include "themepreviewer.h"
K_PLUGIN_CLASS_WITH_JSON(GtkConfig, "gtkconfig.json")
GtkConfig::GtkConfig(QObject *parent, const QVariantList&) :
KDEDModule(parent),
configValueProvider(new ConfigValueProvider()),
themePreviewer(new ThemePreviewer(this)),
kwinConfigWatcher(KConfigWatcher::create(KSharedConfig::openConfig(QStringLiteral("kwinrc"))))
{
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.registerService(QStringLiteral("org.kde.GtkConfig"));
dbus.registerObject(QStringLiteral("/GtkConfig"), this, QDBusConnection::ExportScriptableSlots);
connect(qGuiApp, &QGuiApplication::fontChanged, this, &GtkConfig::setFont);
connect(KIconLoader::global(), &KIconLoader::iconChanged, this, &GtkConfig::setIconTheme);
connect(kwinConfigWatcher.data(), &KConfigWatcher::configChanged, this, &GtkConfig::onKWinSettingsChange);
QDBusConnection::sessionBus().connect(QString(),
QStringLiteral("/KGlobalSettings"),
QStringLiteral("org.kde.KGlobalSettings"),
QStringLiteral("notifyChange"),
this,
SLOT(onGlobalSettingsChange(int,int)));
dbus.connect(
QString(),
QStringLiteral("/KGlobalSettings"),
QStringLiteral("org.kde.KGlobalSettings"),
QStringLiteral("notifyChange"),
this,
SLOT(onGlobalSettingsChange(int,int))
);
ConfigEditor::removeLegacyGtk2Strings();
applyAllSettings();
}
GtkConfig::~GtkConfig()
{
QDBusConnection dbus = QDBusConnection::sessionBus();
dbus.unregisterService(QStringLiteral("org.kde.GtkConfig"));
dbus.unregisterObject(QStringLiteral("/GtkConfig"));
}
void GtkConfig::setGtk2Theme(const QString &themeName) const
{
ConfigEditor::setGtk2ConfigValue(QStringLiteral("gtk-theme-name"), themeName);
}
void GtkConfig::setGtk3Theme(const QString &themeName) const
{
ConfigEditor::setGtk3ConfigValueDconf(QStringLiteral("gtk-theme"), themeName);
ConfigEditor::setGtk3ConfigValueSettingsIni(QStringLiteral("gtk-theme-name"), themeName);
ConfigEditor::setGtk3ConfigValueXSettingsd(QStringLiteral("Net/ThemeName"), themeName);
}
QString GtkConfig::gtk2Theme() const
{
return ConfigEditor::gtk2ConfigValue(QStringLiteral("gtk-theme-name"));
}
QString GtkConfig::gtk3Theme() const
{
return ConfigEditor::gtk3ConfigValueSettingsIni(QStringLiteral("gtk-theme-name"));
}
void GtkConfig::showGtk2ThemePreview(const QString& themeName) const
{
themePreviewer->showGtk2App(themeName);
}
void GtkConfig::showGtk3ThemePreview(const QString& themeName) const
{
themePreviewer->showGtk3App(themeName);
}
void GtkConfig::setFont() const
{
const QString configFontName = configValueProvider->fontName();
......
......@@ -26,10 +26,12 @@
#include "configeditor.h"
#include "configvalueprovider.h"
#include "themepreviewer.h"
class Q_DECL_EXPORT GtkConfig : public KDEDModule
{
Q_OBJECT
Q_CLASSINFO("D-Bus Interface", "org.kde.GtkConfig")
enum class SettingsChangeType {
Palette = 0,
......@@ -56,6 +58,7 @@ class Q_DECL_EXPORT GtkConfig : public KDEDModule
public:
GtkConfig(QObject *parent, const QVariantList& args);
~GtkConfig();
void setFont() const;
void setIconTheme(int iconGroup) const;
......@@ -70,10 +73,20 @@ public:
void applyAllSettings() const;
public Q_SLOTS:
Q_SCRIPTABLE void setGtk2Theme(const QString &themeName) const;
Q_SCRIPTABLE void setGtk3Theme(const QString &themeName) const;
Q_SCRIPTABLE QString gtk2Theme() const;
Q_SCRIPTABLE QString gtk3Theme() const;
Q_SCRIPTABLE void showGtk2ThemePreview(const QString &themeName) const;
Q_SCRIPTABLE void showGtk3ThemePreview(const QString &themeName) const;
void onGlobalSettingsChange(int settingsChangeType, int arg) const;
void onKWinSettingsChange(const KConfigGroup &group, const QByteArrayList &names) const;
private:
QScopedPointer<ConfigValueProvider> configValueProvider;
QScopedPointer<ThemePreviewer> themePreviewer;
KConfigWatcher::Ptr kwinConfigWatcher;
};
/*
* Copyright (C) 2019 Mikhail Zolotukhin <zomial@protonmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDir>
#include <QFile>
#include <QObject>
#include <QProcess>
#include <QProcessEnvironment>
#include <QStandardPaths>
#include <QString>
#include <QScopedPointer>
#include <KConfigGroup>
#include <KSharedConfig>
#include <signal.h>
#include "themepreviewer.h"
#include "configeditor.h"
#include "config.h"
const QString ThemePreviewer::previewGtk2ConfigPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + QStringLiteral("/gtkrc-2.0");
const QString ThemePreviewer::currentGtk2ConfigPath = QDir::homePath() + QStringLiteral("/.gtkrc-2.0");
const QString ThemePreviewer::gtk2PreviewerExecutablePath = QStandardPaths::findExecutable(QStringLiteral("gtk_preview"), {CMAKE_INSTALL_FULL_LIBEXECDIR});
const QString ThemePreviewer::gtk3PreviewerExecutablePath = QStandardPaths::findExecutable(QStringLiteral("gtk3_preview"), {CMAKE_INSTALL_FULL_LIBEXECDIR});
ThemePreviewer::ThemePreviewer(QObject *parent) : QObject(parent),
gtk2PreviewerProccess(),
gtk3PreviewerProccess()
{
QProcessEnvironment gtk2PreviewEnvironment = QProcessEnvironment::systemEnvironment();
gtk2PreviewEnvironment.insert(QStringLiteral("GTK2_RC_FILES"), previewGtk2ConfigPath);
gtk2PreviewerProccess.setProcessEnvironment(gtk2PreviewEnvironment);
connect(&gtk2PreviewerProccess, SIGNAL(finished(int)), this, SLOT(startXsettingsd()));
}
void ThemePreviewer::showGtk2App(const QString& themeName)
{
if (gtk2PreviewerProccess.state() == QProcess::ProcessState::NotRunning) {
if (QFile::exists(previewGtk2ConfigPath)) {
QFile::remove(previewGtk2ConfigPath);
}
QFile::copy(currentGtk2ConfigPath, previewGtk2ConfigPath);
QFile previewConfig(previewGtk2ConfigPath);
QString previewConfigContents = ConfigEditor::readFileContents(previewConfig);
ConfigEditor::replaceValueInGtkrcContents(previewConfigContents, QStringLiteral("gtk-theme-name"), themeName);
previewConfig.remove();
previewConfig.open(QIODevice::WriteOnly | QIODevice::Text);
previewConfig.write(previewConfigContents.toUtf8());
stopXsettingsd();
gtk2PreviewerProccess.start(gtk2PreviewerExecutablePath);
} else {
gtk2PreviewerProccess.close();
}
}
void ThemePreviewer::showGtk3App(const QString& themeName)
{
if (gtk3PreviewerProccess.state() == QProcess::ProcessState::NotRunning) {
QProcessEnvironment gtk3PreviewEnvironment = QProcessEnvironment::systemEnvironment();
gtk3PreviewEnvironment.insert(QStringLiteral("GTK_THEME"), themeName);
gtk3PreviewerProccess.setProcessEnvironment(gtk3PreviewEnvironment);
gtk3PreviewerProccess.start(gtk3PreviewerExecutablePath);
} else {
gtk3PreviewerProccess.close();
}
}
void ThemePreviewer::startXsettingsd()
{
if (gtk2PreviewerProccess.state() == QProcess::ProcessState::NotRunning &&
gtk3PreviewerProccess.state() == QProcess::ProcessState::NotRunning) {
QProcess::startDetached(QStandardPaths::findExecutable(QStringLiteral("xsettingsd")));
}
}
void ThemePreviewer::stopXsettingsd()
{
pid_t pidOfXSettingsd = ConfigEditor::pidOfXSettingsd();
if (pidOfXSettingsd > 0) {
kill(pidOfXSettingsd, SIGTERM);
}
}
/*
* Copyright (C) 2019 Mikhail Zolotukhin <zomial@protonmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <QProcess>
#include <QString>
class ThemePreviewer : QObject {
Q_OBJECT
public:
ThemePreviewer(QObject *parent);
void showGtk2App(const QString &themeName);
void showGtk3App(const QString &themeName);
private Q_SLOTS:
void startXsettingsd();
void stopXsettingsd();
private:
static const QString previewGtk2ConfigPath;
static const QString currentGtk2ConfigPath;
static const QString gtk2PreviewerExecutablePath;
static const QString gtk3PreviewerExecutablePath;
QProcess gtk2PreviewerProccess;
QProcess gtk3PreviewerProccess;
};
/* KDE GTK Configuration Module
*
* Copyright 2011 Aleix Pol Gonzalez <aleixpol@blue-systems.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) version 3, or any
* later version accepted by the membership of KDE e.V. (or its
* successor approved by the membership of KDE e.V.), which shall
* act as a proxy defined in Section 6 of version 3 of the license.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QDir>
#include <QDebug>
#include <QRegExp>
#include "abstractappearance.h"
void AbstractAppearance::setTheme(const QString& name)
{
m_settings["theme"] = name;
}
QString AbstractAppearance::getTheme() const
{
return m_settings["theme"];
}
QString AbstractAppearance::getThemeGtk3() const
{
return m_settings["themegtk3"];
}
QMap<QString,QString> AbstractAppearance::readSettingsTuples(QIODevice* device)
{
static const QRegExp valueRx(" *([a-zA-Z\\-_]+) *= *\"?([^\"\\n]+)\"?", Qt::CaseSensitive, QRegExp::RegExp2);
QMap<QString, QString> ret;
QTextStream flow(device);
while (!flow.atEnd()) {
QString line = flow.readLine();
int idxComment = line.indexOf('#');
if (idxComment >= 0) {
line = line.left(idxComment).simplified();
}
if (valueRx.exactMatch(line)) {
ret[valueRx.cap(1)] = valueRx.cap(2);
} else if (line.startsWith("include \"")) {
QString filename = line.mid(9);
filename.chop(1);
QFile f(filename);
if (f.open(QFile::Text | QFile::ReadOnly)) {
ret.unite(readSettingsTuples(&f));
} else {
qWarning() << "couldn't include " << filename;
}
}
}
return ret;
}
QStringList AbstractAppearance::installedThemesNames() const
{
QStringList themes = installedThemes();
QStringList ret;
for(const QString &theme : themes) {
ret += QDir(theme).dirName();
}
return ret;
}
bool AbstractAppearance::hasProperty(const QString& key) const
{
return !m_settings.value(key).isEmpty();
}