qtui: Fix icon loading and improve icon theme support
authorManuel Nickschas <sputnick@quassel-irc.org>
Mon, 4 Jun 2018 22:07:01 +0000 (00:07 +0200)
committerManuel Nickschas <sputnick@quassel-irc.org>
Fri, 15 Jun 2018 23:30:32 +0000 (01:30 +0200)
Quassel relies on quite a number of icons that are part of KDE/Plasma
icon themes such as Breeze and Oxygen, but are not specified by
freedesktop.org's Icon Naming Specification. As such, icon themes
other than the afforementioned most likely don't contain all the
icons Quassel needs.

Additionally, Quassel ships several additional icons that are not part
of any theme, but are made to fit in with Breeze/Oxygen.

In order to ensure that all required icons are available, we now
assume that at least one of Breeze, Breeze Dark or Oxygen themes is
available as a fallback, either as a system-wide installation, or by
having Quassel itself deploy the required subset in its data dir
(related build options will be adapted in a follow-up commit).

Allow the user to configure which fallback theme is to be used,
and if the system icon theme should be completely overridden (as
opposed to just filling in missing icons). If the system theme
is Breeze/Oxygen, just inject the corresponding Quassel-specific
icons. If the configured system theme doesn't match the selected
fallback (and should not be overridden), create a proxy icon theme
that inherits from first the system theme and then the selected
fallback. That way, missing icons are found through the inheritance
chain.

Note that as of Qt 5.11.0, icons from the hicolor default theme are
preferred over the ones from inherited themes, which violates the
Icon Theme Specification (cf. QTBUG-68603). This affects Quassel's
application icon, which will thus always use the Breeze variant
if a non-supported system icon theme is used. Other icons will be
picked from the selected fallback theme, as expected.

src/qtui/qtui.cpp
src/qtui/qtui.h
src/qtui/qtuiapplication.cpp
src/qtui/settingspages/appearancesettingspage.cpp
src/qtui/settingspages/appearancesettingspage.ui

index 3f04f8d..0082bd0 100644 (file)
 
 #include "qtui.h"
 
+#include <QApplication>
+#include <QFile>
+#include <QFileInfo>
+#include <QIcon>
+#include <QStringList>
+
 #include "abstractnotificationbackend.h"
 #include "buffermodel.h"
 #include "chatlinemodel.h"
 #include "types.h"
 #include "util.h"
 
-QtUi *QtUi::_instance = 0;
-MainWin *QtUi::_mainWin = 0;
+QtUi *QtUi::_instance = nullptr;
+MainWin *QtUi::_mainWin = nullptr;
 QList<AbstractNotificationBackend *> QtUi::_notificationBackends;
 QList<AbstractNotificationBackend::Notification> QtUi::_notifications;
 
-QtUi::QtUi() : GraphicalUi()
+QtUi::QtUi()
+    : GraphicalUi()
+    , _systemIconTheme{QIcon::themeName()}
 {
-    if (_instance != 0) {
+    if (_instance != nullptr) {
         qWarning() << "QtUi has been instantiated again!";
         return;
     }
     _instance = this;
 
+    if (Quassel::isOptionSet("icontheme")) {
+        _systemIconTheme = Quassel::optionValue("icontheme");
+        QIcon::setThemeName(_systemIconTheme);
+    }
+
     QtUiSettings uiSettings;
     Quassel::loadTranslation(uiSettings.value("Locale", QLocale::system()).value<QLocale>());
 
+    setupIconTheme();
+
+    QApplication::setWindowIcon(QIcon::fromTheme("quassel"));
+
     setContextMenuActionProvider(new ContextMenuActionProvider(this));
     setToolBarActionProvider(new ToolBarActionProvider(this));
 
@@ -67,8 +84,8 @@ QtUi::~QtUi()
 {
     unregisterAllNotificationBackends();
     delete _mainWin;
-    _mainWin = 0;
-    _instance = 0;
+    _mainWin = nullptr;
+    _instance = nullptr;
 }
 
 
@@ -248,3 +265,133 @@ void QtUi::bufferMarkedAsRead(BufferId bufferId)
         closeNotifications(bufferId);
     }
 }
+
+
+std::vector<std::pair<QString, QString>> QtUi::availableIconThemes() const
+{
+    //: Supported icon theme names
+    static const std::vector<std::pair<QString, QString>> supported {
+        { "breeze", tr("Breeze") },
+        { "breeze-dark", tr("Breeze Dark") },
+        { "oxygen", tr("Oxygen") }
+    };
+
+    std::vector<std::pair<QString, QString>> result;
+    for (auto &&themePair : supported) {
+        for (auto &&dir : QIcon::themeSearchPaths()) {
+            if (QFileInfo{dir + "/" + themePair.first + "/index.theme"}.exists()) {
+                result.push_back(themePair);
+                break;
+            }
+        }
+    }
+
+    return result;
+}
+
+
+void QtUi::setupIconTheme()
+{
+    // Add paths to our own icon sets to the theme search paths
+    QStringList themePaths = QIcon::themeSearchPaths();
+    themePaths.removeAll(":/icons");  // this should come last
+    for (auto &&dataDir : Quassel::dataDirPaths()) {
+        QString iconDir{dataDir + "icons"};
+        if (QFileInfo{iconDir}.isDir()) {
+            themePaths << iconDir;
+        }
+    }
+    themePaths << ":/icons";
+    QIcon::setThemeSearchPaths(themePaths);
+
+    refreshIconTheme();
+}
+
+
+void QtUi::refreshIconTheme()
+{
+    // List of available fallback themes
+    QStringList availableThemes;
+    for (auto &&themePair : availableIconThemes()) {
+        availableThemes << themePair.first;
+    }
+
+    if (availableThemes.isEmpty()) {
+        // We could probably introduce a more sophisticated fallback handling, such as putting the "most important" icons into hicolor,
+        // but this just gets complex for no good reason. We really rely on a supported theme to be installed, if not system-wide, then
+        // as part of the Quassel installation (which is enabled by default anyway).
+        qWarning() << tr("No supported icon theme installed, you'll lack icons! Supported are the KDE/Plasma themes Breeze, Breeze Dark and Oxygen.");
+        return;
+    }
+
+    UiStyleSettings s;
+    QString fallbackTheme{s.value("Icons/FallbackTheme").toString()};
+
+    if (fallbackTheme.isEmpty() || !availableThemes.contains(fallbackTheme)) {
+        if (availableThemes.contains(_systemIconTheme)) {
+            fallbackTheme = _systemIconTheme;
+        }
+        else {
+            fallbackTheme = availableThemes.first();
+        }
+    }
+
+    if (_systemIconTheme.isEmpty() || _systemIconTheme == fallbackTheme || s.value("Icons/OverrideSystemTheme", false).toBool()) {
+        // We have a valid fallback theme and want to override the system theme (if it's even defined), so we're basically done
+        QIcon::setThemeName(fallbackTheme);
+        return;
+    }
+
+#if QT_VERSION >= 0x050000
+    // At this point, we have a system theme that we don't want to override, but that may not contain all
+    // required icons.
+    // We create a dummy theme that inherits first from the system theme, then from the supported fallback.
+    // This rather ugly hack allows us to inject the fallback into the inheritance chain, so non-standard
+    // icons missing in the system theme will be filled in by the fallback.
+    // Since we can't get notified when the system theme changes, this means that a restart may be required
+    // to apply a theme change... but you can't have everything, I guess.
+    if (!_dummyThemeDir) {
+        _dummyThemeDir.reset(new QTemporaryDir{});
+        if (!_dummyThemeDir->isValid() || !QDir{_dummyThemeDir->path()}.mkpath("icons/quassel-icon-proxy/apps/32")) {
+            qWarning() << "Could not create temporary directory for proxying the system icon theme, using fallback";
+            QIcon::setThemeName(fallbackTheme);
+            return;
+        }
+        // Add this to XDG_DATA_DIRS, otherwise KIconLoader complains
+        auto xdgDataDirs = qgetenv("XDG_DATA_DIRS");
+        if (!xdgDataDirs.isEmpty())
+            xdgDataDirs += ":";
+        xdgDataDirs += _dummyThemeDir->path();
+        qputenv("XDG_DATA_DIRS", xdgDataDirs);
+
+        QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << _dummyThemeDir->path() + "/icons");
+    }
+
+    QFile indexFile{_dummyThemeDir->path() + "/icons/quassel-icon-proxy/index.theme"};
+    if (!indexFile.open(QFile::WriteOnly|QFile::Truncate)) {
+        qWarning() << "Could not create index file for proxying the system icon theme, using fallback";
+        QIcon::setThemeName(fallbackTheme);
+        return;
+    }
+
+    // Write a dummy index file that is sufficient to make QIconLoader happy
+    auto indexContents = QString{
+            "[Icon Theme]\n"
+            "Name=quassel-icon-proxy\n"
+            "Inherits=%1,%2\n"
+            "Directories=apps/32\n"
+            "[apps/32]\nSize=32\nType=Fixed\n"
+    }.arg(_systemIconTheme, fallbackTheme);
+    if (indexFile.write(indexContents.toLatin1()) < 0) {
+        qWarning() << "Could not write index file for proxying the system icon theme, using fallback";
+        QIcon::setThemeName(fallbackTheme);
+        return;
+    }
+    indexFile.close();
+    QIcon::setThemeName("quassel-icon-proxy");
+#else
+    // Qt4 doesn't support QTemporaryDir. Since it's deprecated and slated to be removed soon anyway, we don't bother
+    // writing a replacement and simply don't support not overriding the system theme.
+    QIcon::setThemeName(fallbackTheme);
+#endif
+}
index 96e934f..0d13e64 100644 (file)
  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
  ***************************************************************************/
 
-#ifndef QTUI_H
-#define QTUI_H
+#pragma once
 
-#include "graphicalui.h"
+#include <memory>
+#include <tuple>
+#include <vector>
+
+#include <QStringList>
+
+#if QT_VERSION >= 0x050000
+#  include <QTemporaryDir>
+#endif
 
 #include "abstractnotificationbackend.h"
+#include "graphicalui.h"
 #include "qtuistyle.h"
 
 class MainWin;
@@ -59,6 +67,13 @@ public:
     static const QList<AbstractNotificationBackend *> &notificationBackends();
     static const QList<AbstractNotificationBackend::Notification> &activeNotifications();
 
+    /**
+     * Determine available fallback icon themes.
+     *
+     * @returns The list of supported fallback themes (Breeze (Dark), Oxygen) that are available on the system
+     */
+    std::vector<std::pair<QString, QString>> availableIconThemes() const;
+
 public slots:
     void init() override;
 
@@ -66,6 +81,14 @@ public slots:
     void closeNotification(uint notificationId);
     void closeNotifications(BufferId bufferId = BufferId());
 
+    /**
+     * Refresh the current icon theme.
+     *
+     * @note This will not detect changes in the system icon theme, so if that changes, a client restart
+     *       is required for icons to work correctly.
+     */
+    void refreshIconTheme();
+
 protected slots:
     void connectedToCore() override;
     void disconnectedFromCore() override;
@@ -79,12 +102,24 @@ protected:
 private slots:
     void useSystemTrayChanged(const QVariant &);
 
+private:
+    /**
+     * Sets up icon theme handling.
+     */
+    void setupIconTheme();
+
 private:
     static QtUi *_instance;
     static MainWin *_mainWin;
     static QList<AbstractNotificationBackend *> _notificationBackends;
     static QList<AbstractNotificationBackend::Notification> _notifications;
 
+    QString _systemIconTheme;
+
+#if QT_VERSION >= 0x050000
+    std::unique_ptr<QTemporaryDir> _dummyThemeDir;
+#endif
+
     bool _useSystemTray;
 };
 
@@ -92,5 +127,3 @@ private:
 QtUi *QtUi::instance() { return _instance ? _instance : new QtUi(); }
 QtUiStyle *QtUi::style() { return qobject_cast<QtUiStyle *>(uiStyle()); }
 MainWin *QtUi::mainWindow() { return _mainWin; }
-
-#endif
index 73e67b2..6c744c2 100644 (file)
@@ -20,8 +20,8 @@
 
 #include "qtuiapplication.h"
 
-#include <QIcon>
 #include <QDir>
+#include <QFile>
 #include <QStringList>
 
 #ifdef HAVE_KDE4
@@ -109,30 +109,6 @@ bool QtUiApplication::init()
             return false;
         }
 
-        // Checking if settings Icon Theme is valid
-        QString savedIcontheme = QtUiSettings().value("IconTheme", QVariant("")).toString();
-#ifndef WITH_OXYGEN
-        if (savedIcontheme == "oxygen")
-            QtUiSettings().remove("IconTheme");
-#endif
-#ifndef WITH_BREEZE
-        if (savedIcontheme == "breeze")
-            QtUiSettings().remove("IconTheme");
-#endif
-#ifndef WITH_BREEZE_DARK
-        if (savedIcontheme == "breezedark")
-            QtUiSettings().remove("IconTheme");
-#endif
-
-        // Set the icon theme
-        if (Quassel::isOptionSet("icontheme"))
-            QIcon::setThemeName(Quassel::optionValue("icontheme"));
-        else if (QtUiSettings().value("IconTheme", QVariant("")).toString() != "")
-            QIcon::setThemeName(QtUiSettings().value("IconTheme").toString());
-        else if (QIcon::themeName().isEmpty())
-            // Some platforms don't set a default icon theme; chances are we can find our bundled theme though
-            QIcon::setThemeName("breeze");
-
         Client::init(new QtUi());
 
         // Init UI only after the event loop has started
@@ -143,7 +119,6 @@ bool QtUiApplication::init()
             QtUi::mainWindow()->quit();
         });
 
-
         return true;
     }
     return false;
index 44c53fd..3156433 100644 (file)
 
 #include "appearancesettingspage.h"
 
+#include <QCheckBox>
+#include <QDir>
+#include <QFile>
+#include <QFileDialog>
+#include <QStyleFactory>
+
 #include "buffersettings.h"
 #include "qtui.h"
 #include "qtuisettings.h"
 #include "qtuistyle.h"
 
-#include <QCheckBox>
-#include <QFileDialog>
-#include <QStyleFactory>
-#include <QFile>
-#include <QDir>
 
 AppearanceSettingsPage::AppearanceSettingsPage(QWidget *parent)
     : SettingsPage(tr("Interface"), QString(), parent)
@@ -39,6 +40,10 @@ AppearanceSettingsPage::AppearanceSettingsPage(QWidget *parent)
 #ifdef QT_NO_SYSTEMTRAYICON
     ui.useSystemTrayIcon->hide();
 #endif
+#if QT_VERSION < 0x050000
+    // We don't support overriding the system icon theme with Qt4
+    ui.overrideSystemIconTheme->hide();
+#endif
 
     initAutoWidgets();
     initStyleComboBox();
@@ -98,21 +103,12 @@ void AppearanceSettingsPage::initLanguageComboBox()
 
 void AppearanceSettingsPage::initIconThemeComboBox()
 {
-    // TODO Replace by runtime detection
-#if defined WITH_OXYGEN || defined WITH_BREEZE || defined WITH_BREEZE_DARK
-# if defined WITH_BREEZE
-    ui.iconthemeComboBox->addItem(tr("Breeze Light"), QVariant("breeze"));
-# endif
-# if defined WITH_BREEZE_DARK
-    ui.iconthemeComboBox->addItem(tr("Breeze Dark"), QVariant("breezedark"));
-# endif
-# if defined WITH_OXYGEN
-    ui.iconthemeComboBox->addItem(tr("Oxygen"), QVariant("oxygen"));
-# endif
-#else
-    ui.iconthemeLabel->hide();
-    ui.iconthemeComboBox->hide();
-#endif
+    auto availableThemes = QtUi::instance()->availableIconThemes();
+
+    ui.iconThemeComboBox->addItem(tr("Automatic"), QString{});
+    for (auto &&p : QtUi::instance()->availableIconThemes()) {
+        ui.iconThemeComboBox->addItem(p.second, p.first);
+    }
 }
 
 
@@ -152,12 +148,15 @@ void AppearanceSettingsPage::load()
     Quassel::loadTranslation(selectedLocale());
 
     // IconTheme
-    QString icontheme = uiSettings.value("IconTheme", QVariant("")).toString();
-    if (icontheme == "")
-        ui.iconthemeComboBox->setCurrentIndex(0);
-    else
-        ui.iconthemeComboBox->setCurrentIndex(ui.iconthemeComboBox->findData(icontheme));
-    ui.iconthemeComboBox->setProperty("storedValue", ui.iconthemeComboBox->currentIndex());
+    QString icontheme = UiStyleSettings{}.value("Icons/FallbackTheme", QString{}).toString();
+    if (icontheme.isEmpty()) {
+        ui.iconThemeComboBox->setCurrentIndex(0);
+    }
+    else {
+        auto idx = ui.iconThemeComboBox->findData(icontheme);
+        ui.iconThemeComboBox->setCurrentIndex(idx > 0 ? idx : 0);
+    }
+    ui.iconThemeComboBox->setProperty("storedValue", ui.iconThemeComboBox->currentIndex());
 
     // bufferSettings:
     BufferSettings bufferSettings;
@@ -184,6 +183,7 @@ void AppearanceSettingsPage::load()
 void AppearanceSettingsPage::save()
 {
     QtUiSettings uiSettings;
+    UiStyleSettings styleSettings;
 
     if (ui.styleComboBox->currentIndex() < 1) {
         uiSettings.setValue("Style", QString(""));
@@ -202,14 +202,17 @@ void AppearanceSettingsPage::save()
     }
     ui.languageComboBox->setProperty("storedValue", ui.languageComboBox->currentIndex());
 
-    if (selectedIconTheme()=="") {
-        uiSettings.remove("IconTheme");
+    bool needsIconThemeRefresh = ui.iconThemeComboBox->currentIndex() != ui.iconThemeComboBox->property("storedValue").toInt()
+                              || ui.overrideSystemIconTheme->isChecked() != ui.overrideSystemIconTheme->property("storedValue").toBool();
+
+    auto iconTheme = selectedIconTheme();
+    if (iconTheme.isEmpty()) {
+        styleSettings.remove("Icons/FallbackTheme");
     }
     else {
-        uiSettings.setValue("IconTheme", selectedIconTheme());
-        QIcon::setThemeName(selectedIconTheme());
+        styleSettings.setValue("Icons/FallbackTheme", iconTheme);
     }
-    ui.iconthemeComboBox->setProperty("storedValue", ui.iconthemeComboBox->currentIndex());
+    ui.iconThemeComboBox->setProperty("storedValue", ui.iconThemeComboBox->currentIndex());
 
     bool needsStyleReload =
         ui.useCustomStyleSheet->isChecked() != ui.useCustomStyleSheet->property("storedValue").toBool()
@@ -247,6 +250,8 @@ void AppearanceSettingsPage::save()
     setChangedState(false);
     if (needsStyleReload)
         QtUi::style()->reload();
+    if (needsIconThemeRefresh)
+        QtUi::instance()->refreshIconTheme();
 }
 
 
@@ -264,11 +269,13 @@ QLocale AppearanceSettingsPage::selectedLocale() const
     return locale;
 }
 
+
 QString AppearanceSettingsPage::selectedIconTheme() const
 {
-    return ui.iconthemeComboBox->itemData(ui.iconthemeComboBox->currentIndex()).toString();
+    return ui.iconThemeComboBox->itemData(ui.iconThemeComboBox->currentIndex()).toString();
 }
 
+
 void AppearanceSettingsPage::chooseStyleSheet()
 {
     QString dir = ui.customStyleSheetPath->property("storedValue").toString();
@@ -293,7 +300,7 @@ bool AppearanceSettingsPage::testHasChanged()
 {
     if (ui.styleComboBox->currentIndex() != ui.styleComboBox->property("storedValue").toInt()) return true;
     if (ui.languageComboBox->currentIndex() != ui.languageComboBox->property("storedValue").toInt()) return true;
-    if (ui.iconthemeComboBox->currentIndex() != ui.iconthemeComboBox->property("storedValue").toInt()) return true;
+    if (ui.iconThemeComboBox->currentIndex() != ui.iconThemeComboBox->property("storedValue").toInt()) return true;
 
     if (SettingsPage::hasChanged(ui.userNoticesInStatusBuffer)) return true;
     if (SettingsPage::hasChanged(ui.userNoticesInDefaultBuffer)) return true;
index abf67d5..7b6af81 100644 (file)
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>549</width>
-    <height>470</height>
+    <width>755</width>
+    <height>536</height>
    </rect>
   </property>
   <property name="windowTitle">
    <item>
     <layout class="QGridLayout" name="gridLayout">
      <item row="0" column="0">
-      <widget class="QLabel" name="label_2">
-       <property name="text">
-        <string>Client style:</string>
-       </property>
-      </widget>
-     </item>
-     <item row="0" column="1">
-      <widget class="QComboBox" name="styleComboBox">
-       <property name="toolTip">
-        <string>Set application style</string>
-       </property>
-      </widget>
-     </item>
-     <item row="1" column="0">
       <widget class="QLabel" name="label_9">
        <property name="text">
         <string>Language:</string>
        </property>
       </widget>
      </item>
-     <item row="1" column="1">
+     <item row="0" column="1">
       <widget class="QComboBox" name="languageComboBox">
        <property name="toolTip">
         <string>Set the application language. Requires restart!</string>
@@ -54,7 +40,7 @@
        </item>
       </widget>
      </item>
-     <item row="1" column="2">
+     <item row="0" column="2">
       <spacer name="horizontalSpacer_3">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
       </spacer>
      </item>
+     <item row="1" column="0">
+      <widget class="QLabel" name="label_2">
+       <property name="text">
+        <string>Widget style:</string>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="1">
+      <widget class="QComboBox" name="styleComboBox">
+       <property name="toolTip">
+        <string>Set application style</string>
+       </property>
+      </widget>
+     </item>
      <item row="2" column="0">
       <widget class="QLabel" name="iconthemeLabel">
        <property name="text">
-        <string>Icon theme:</string>
+        <string>Fallback icon theme:</string>
        </property>
       </widget>
      </item>
      <item row="2" column="1">
-      <widget class="QComboBox" name="iconthemeComboBox">
+      <widget class="QComboBox" name="iconThemeComboBox">
        <property name="toolTip">
-        <string>Choose from the bundled icon themes! May need restart...</string>
+        <string>Icon theme to use for icons that are not found in the current system theme. Requires the selected theme to be installed either system-wide, or as part of the Quassel installation. Supported themes are Breeze, Breeze Dark and Oxygen, all of KDE fame.</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="2">
+      <widget class="QCheckBox" name="overrideSystemIconTheme">
+       <property name="toolTip">
+        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If enabled, uses the selected fallback icon theme instead of the configured system theme for all icons. Recommended if you want Quassel to have a consistent look-and-feel.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+       </property>
+       <property name="text">
+        <string>Override system theme</string>
+       </property>
+       <property name="checked">
+        <bool>false</bool>
+       </property>
+       <property name="settingsKey" stdset="0">
+        <string notr="true">/UiStyle/Icons/OverrideSystemTheme</string>
+       </property>
+       <property name="defaultValue" stdset="0">
+        <bool>false</bool>
        </property>
-       <item>
-        <property name="text">
-         <string>&lt;System Default&gt;</string>
-        </property>
-       </item>
       </widget>
      </item>
     </layout>
    </item>
    <item>
-    <widget class="QCheckBox" name="useCustomStyleSheet">
-     <property name="text">
-      <string>Use custom stylesheet</string>
-     </property>
-     <property name="settingsKey" stdset="0">
-      <string notr="true">/UiStyle/UseCustomStyleSheet</string>
-     </property>
-     <property name="defaultValue" stdset="0">
-      <bool>false</bool>
-     </property>
-    </widget>
-   </item>
-   <item>
-    <layout class="QHBoxLayout" name="horizontalLayout_2">
+    <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
-      <spacer name="horizontalSpacer">
-       <property name="orientation">
-        <enum>Qt::Horizontal</enum>
-       </property>
-       <property name="sizeType">
-        <enum>QSizePolicy::Fixed</enum>
+      <widget class="QCheckBox" name="useCustomStyleSheet">
+       <property name="text">
+        <string>Use custom stylesheet</string>
        </property>
-       <property name="sizeHint" stdset="0">
-        <size>
-         <width>20</width>
-         <height>20</height>
-        </size>
+       <property name="settingsKey" stdset="0">
+        <string notr="true">/UiStyle/UseCustomStyleSheet</string>
        </property>
-      </spacer>
-     </item>
-     <item>
-      <widget class="QLabel" name="label">
-       <property name="enabled">
+       <property name="defaultValue" stdset="0">
         <bool>false</bool>
        </property>
-       <property name="text">
-        <string>Path:</string>
-       </property>
       </widget>
      </item>
      <item>
      </item>
     </layout>
    </item>
-   <item>
-    <widget class="QCheckBox" name="useSystemTrayIcon">
-     <property name="text">
-      <string>Show system tray icon</string>
-     </property>
-     <property name="checked">
-      <bool>true</bool>
-     </property>
-     <property name="settingsKey" stdset="0">
-      <string notr="true">UseSystemTrayIcon</string>
-     </property>
-     <property name="defaultValue" stdset="0">
-      <bool>true</bool>
-     </property>
-    </widget>
-   </item>
    <item>
     <layout class="QGridLayout" name="gridLayout_3">
-     <item row="0" column="0">
-      <spacer name="horizontalSpacer_2">
+     <item row="2" column="1">
+      <widget class="QCheckBox" name="invertSystrayColors">
+       <property name="text">
+        <string>Invert colors</string>
+       </property>
+       <property name="settingsKey" stdset="0">
+        <string notr="true">/UiStyle/Icons/InvertTray</string>
+       </property>
+       <property name="defaultValue" stdset="0">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="2">
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item row="2" column="0">
+      <spacer name="horizontalSpacer_6">
        <property name="orientation">
         <enum>Qt::Horizontal</enum>
        </property>
        </property>
       </spacer>
      </item>
-     <item row="0" column="1">
+     <item row="1" column="1">
       <widget class="QCheckBox" name="minimizeOnClose">
        <property name="text">
         <string>Hide to tray on close button</string>
        </property>
       </widget>
      </item>
+     <item row="1" column="0">
+      <spacer name="horizontalSpacer_2">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeType">
+        <enum>QSizePolicy::Fixed</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>20</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item row="0" column="0" colspan="2">
+      <widget class="QCheckBox" name="useSystemTrayIcon">
+       <property name="text">
+        <string>Show system tray icon</string>
+       </property>
+       <property name="checked">
+        <bool>true</bool>
+       </property>
+       <property name="settingsKey" stdset="0">
+        <string notr="true">UseSystemTrayIcon</string>
+       </property>
+       <property name="defaultValue" stdset="0">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
     </layout>
    </item>
    <item>
   </layout>
  </widget>
  <tabstops>
-  <tabstop>styleComboBox</tabstop>
   <tabstop>languageComboBox</tabstop>
+  <tabstop>styleComboBox</tabstop>
+  <tabstop>iconThemeComboBox</tabstop>
+  <tabstop>overrideSystemIconTheme</tabstop>
   <tabstop>useCustomStyleSheet</tabstop>
-  <tabstop>customStyleSheetPath</tabstop>
   <tabstop>chooseStyleSheet</tabstop>
+  <tabstop>customStyleSheetPath</tabstop>
   <tabstop>useSystemTrayIcon</tabstop>
   <tabstop>minimizeOnClose</tabstop>
+  <tabstop>invertSystrayColors</tabstop>
   <tabstop>userNoticesInDefaultBuffer</tabstop>
   <tabstop>userNoticesInStatusBuffer</tabstop>
   <tabstop>userNoticesInCurrentBuffer</tabstop>
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>63</x>
-     <y>86</y>
+     <x>70</x>
+     <y>166</y>
     </hint>
     <hint type="destinationlabel">
-     <x>86</x>
-     <y>114</y>
-    </hint>
-   </hints>
-  </connection>
-  <connection>
-   <sender>useCustomStyleSheet</sender>
-   <signal>toggled(bool)</signal>
-   <receiver>label</receiver>
-   <slot>setEnabled(bool)</slot>
-   <hints>
-    <hint type="sourcelabel">
-     <x>45</x>
-     <y>80</y>
-    </hint>
-    <hint type="destinationlabel">
-     <x>38</x>
-     <y>113</y>
+     <x>321</x>
+     <y>171</y>
     </hint>
    </hints>
   </connection>
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>84</x>
-     <y>80</y>
+     <x>91</x>
+     <y>166</y>
     </hint>
     <hint type="destinationlabel">
-     <x>525</x>
-     <y>117</y>
+     <x>747</x>
+     <y>172</y>
     </hint>
    </hints>
   </connection>
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>91</x>
-     <y>143</y>
+     <x>98</x>
+     <y>206</y>
     </hint>
     <hint type="destinationlabel">
-     <x>92</x>
-     <y>174</y>
+     <x>125</x>
+     <y>238</y>
     </hint>
    </hints>
   </connection>
   <connection>
    <sender>useSystemTrayIcon</sender>
    <signal>toggled(bool)</signal>
-   <receiver>animateSystrayIcon</receiver>
+   <receiver>invertSystrayColors</receiver>
    <slot>setEnabled(bool)</slot>
    <hints>
     <hint type="sourcelabel">
-     <x>125</x>
-     <y>144</y>
+     <x>45</x>
+     <y>197</y>
     </hint>
     <hint type="destinationlabel">
-     <x>122</x>
-     <y>203</y>
+     <x>70</x>
+     <y>291</y>
     </hint>
    </hints>
   </connection>