diff options
author | Claudio Cambra <claudio.cambra@gmail.com> | 2022-03-21 19:34:21 +0300 |
---|---|---|
committer | Claudio Cambra (Rebase PR Action) <claudio.cambra@gmail.com> | 2022-03-25 22:44:55 +0300 |
commit | af01904d066351c18b03222d2108189499aa1da2 (patch) | |
tree | 40ac63e6cb624e7fc85ebe821697d08ac679d780 | |
parent | 9292ddf89ff1c806953f5c66cc78233b61a97c07 (diff) |
Fix dark mode stuff relating to unified search, fix macOS auto dark/light theme switching not always working, fix Windows detection of dark/light theme switchingbugfix/darkmode
Signed-off-by: Claudio Cambra <claudio.cambra@gmail.com>
-rw-r--r-- | src/gui/application.cpp | 28 | ||||
-rw-r--r-- | src/gui/systray.cpp | 24 | ||||
-rw-r--r-- | src/gui/systray.h | 5 | ||||
-rw-r--r-- | src/gui/tray/ActivityItemContent.qml | 14 | ||||
-rw-r--r-- | src/gui/tray/UnifiedSearchResultListItem.qml | 5 | ||||
-rw-r--r-- | src/gui/tray/UserLine.qml | 2 | ||||
-rw-r--r-- | src/gui/tray/Window.qml | 2 | ||||
-rw-r--r-- | src/gui/tray/activitydata.cpp | 38 | ||||
-rw-r--r-- | src/gui/tray/activitydata.h | 3 | ||||
-rw-r--r-- | src/gui/tray/activitylistmodel.cpp | 74 | ||||
-rw-r--r-- | src/gui/tray/activitylistmodel.h | 3 | ||||
-rw-r--r-- | src/gui/tray/notificationhandler.cpp | 16 | ||||
-rw-r--r-- | src/gui/tray/unifiedsearchresult.h | 3 | ||||
-rw-r--r-- | src/gui/tray/unifiedsearchresultslistmodel.cpp | 82 | ||||
-rw-r--r-- | src/gui/tray/unifiedsearchresultslistmodel.h | 6 | ||||
-rw-r--r-- | src/libsync/theme.cpp | 35 | ||||
-rw-r--r-- | src/libsync/theme.h | 9 | ||||
-rw-r--r-- | test/testactivitylistmodel.cpp | 3 | ||||
-rw-r--r-- | theme.qrc.in | 2 | ||||
-rw-r--r-- | theme/Style/Style.qml | 4 |
20 files changed, 206 insertions, 152 deletions
diff --git a/src/gui/application.cpp b/src/gui/application.cpp index e6dd39142..5320ade69 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -105,6 +105,31 @@ namespace { // ---------------------------------------------------------------------------------- +#ifdef Q_OS_WIN +class WindowsNativeEventFilter : public QAbstractNativeEventFilter { +public: + bool nativeEventFilter(const QByteArray &eventType, void *message, long *result) { + const auto msg = static_cast<MSG *>(message); + if(msg->message == WM_SYSCOLORCHANGE || msg->message == WM_SETTINGCHANGE) { + if(!_guiAppInstance) { + const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance()); + if(ptr) { + _guiAppInstance.reset(ptr); + } + } + + if(_guiAppInstance) { + emit _guiAppInstance->paletteChanged(_guiAppInstance->palette()); + } + } + return false; + } + +private: + QScopedPointer<QGuiApplication> _guiAppInstance; +}; +#endif + bool Application::configVersionMigration() { QStringList deleteKeys, ignoreKeys; @@ -192,6 +217,9 @@ Application::Application(int &argc, char **argv) // Ensure OpenSSL config file is only loaded from app directory QString opensslConf = QCoreApplication::applicationDirPath() + QString("/openssl.cnf"); qputenv("OPENSSL_CONF", opensslConf.toLocal8Bit()); + + // Set up event listener for Windows theme changing + installNativeEventFilter(new WindowsNativeEventFilter()); #endif // TODO: Can't set this without breaking current config paths diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index fb9742830..9e4831abe 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -130,12 +130,6 @@ Systray::Systray() }); #endif - const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance()); - if(ptr) { - _guiAppInstance.reset(ptr); - connect(ptr, &QGuiApplication::paletteChanged, this, &Systray::darkModeChanged); - } - connect(UserModel::instance(), &UserModel::newUserSelected, this, &Systray::slotNewUserSelected); connect(UserModel::instance(), &UserModel::addAccount, @@ -231,24 +225,6 @@ bool Systray::useNormalWindow() const return cfg.showMainDialogAsNormalWindow(); } -bool Systray::darkMode() -{ -#if defined(Q_OS_MACOS) - return osXInDarkMode(); -// Windows: Check registry for dark mode -#elif defined(Q_OS_WIN) - const auto darkModeSubkey = QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"); - if (!Utility::registryKeyExists(HKEY_CURRENT_USER, darkModeSubkey)) { - return false; - } - const auto darkMode = !Utility::registryGetKeyValue(HKEY_CURRENT_USER, darkModeSubkey, QStringLiteral("AppsUseLightTheme")).toBool(); - return darkMode; -// Probably Linux -#else - return Theme::isDarkColor(QGuiApplication::palette().window().color()); -#endif -} - Q_INVOKABLE void Systray::setOpened() { _isOpen = true; diff --git a/src/gui/systray.h b/src/gui/systray.h index c0dc404ad..d5cdce358 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -40,7 +40,6 @@ public: }; #ifdef Q_OS_OSX -bool osXInDarkMode(); bool canOsXSendUserNotification(); void sendOsXUserNotification(const QString &title, const QString &message); void setTrayWindowLevelAndVisibleOnAllSpaces(QWindow *window); @@ -58,7 +57,6 @@ class Systray Q_PROPERTY(QString windowTitle READ windowTitle CONSTANT) Q_PROPERTY(bool useNormalWindow READ useNormalWindow CONSTANT) - Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged) public: static Systray *instance(); @@ -74,7 +72,6 @@ public: bool isOpen(); QString windowTitle() const; bool useNormalWindow() const; - bool darkMode(); Q_INVOKABLE void pauseResumeSync(); Q_INVOKABLE bool syncIsPaused(); @@ -97,8 +94,6 @@ signals: void showFileActivityDialog(const QString &objectName, const int objectId); void sendChatMessage(const QString &token, const QString &message, const QString &replyTo); - void darkModeChanged(); - public slots: void slotNewUserSelected(); diff --git a/src/gui/tray/ActivityItemContent.qml b/src/gui/tray/ActivityItemContent.qml index 60e3fac60..4d779a524 100644 --- a/src/gui/tray/ActivityItemContent.qml +++ b/src/gui/tray/ActivityItemContent.qml @@ -77,11 +77,7 @@ RowLayout { anchors.bottom: if(model.thumbnail !== undefined) parent.bottom cache: true - property string sourceUrl: Systray.darkMode ? - model.icon.replace("__COLOR__", "white").replace("__WHITE_GOES_HERE__", "-white") : - model.icon.replace("__COLOR__", "black").replace("__WHITE_GOES_HERE__", "") - - source: sourceUrl + source: Theme.darkMode ? model.darkIcon : model.lightIcon sourceSize.height: 64 sourceSize.width: 64 } @@ -146,9 +142,9 @@ RowLayout { id: talkReplyMessage anchors.fill: parent } - } - } - + } + } + Button { id: dismissActionButton @@ -184,7 +180,7 @@ RowLayout { contentItem: Image { anchors.fill: parent - source: parent.hovered ? Systray.darkMode ? + source: parent.hovered ? Theme.darkMode ? "image://svgimage-custom-color/clear.svg/white" : "image://svgimage-custom-color/clear.svg/black" : "image://svgimage-custom-color/clear.svg/grey" sourceSize.width: 24 diff --git a/src/gui/tray/UnifiedSearchResultListItem.qml b/src/gui/tray/UnifiedSearchResultListItem.qml index 785f8f525..ce942a4d1 100644 --- a/src/gui/tray/UnifiedSearchResultListItem.qml +++ b/src/gui/tray/UnifiedSearchResultListItem.qml @@ -2,6 +2,7 @@ import QtQml 2.15 import QtQuick 2.15 import QtQuick.Controls 2.3 import Style 1.0 +import com.nextcloud.desktopclient 1.0 MouseArea { id: unifiedSearchResultMouseArea @@ -60,8 +61,8 @@ MouseArea { height: unifiedSearchResultMouseArea.height title: model.resultTitle subline: model.subline - icons: model.icons - iconPlaceholder: model.imagePlaceholder + icons: Theme.darkMode ? model.darkIcons : model.lightIcons + iconPlaceholder: Theme.darkMode ? model.darkImagePlaceholder : model.lightImagePlaceholder isRounded: model.isRounded textLeftMargin: unifiedSearchResultMouseArea.textLeftMargin textRightMargin: unifiedSearchResultMouseArea.textRightMargin diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 3c25dc865..740cd1a91 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -61,7 +61,7 @@ MenuItem { Layout.leftMargin: 7
verticalAlignment: Qt.AlignCenter
cache: false
- source: model.avatar != "" ? model.avatar : Systray.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"
+ source: model.avatar != "" ? model.avatar : Theme.darkMode ? "image://avatars/fallbackWhite" : "image://avatars/fallbackBlack"
Layout.preferredHeight: Style.accountAvatarSize
Layout.preferredWidth: Style.accountAvatarSize
Rectangle {
diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 2cc13dd7b..53cfda8a1 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -239,7 +239,7 @@ Window { Image {
Layout.leftMargin: 12
verticalAlignment: Qt.AlignCenter
- source: Systray.darkMode ? "qrc:///client/theme/white/add.svg" : "qrc:///client/theme/black/add.svg"
+ source: Theme.darkMode ? "qrc:///client/theme/white/add.svg" : "qrc:///client/theme/black/add.svg"
sourceSize.width: Style.headerButtonIconSize
sourceSize.height: Style.headerButtonIconSize
}
diff --git a/src/gui/tray/activitydata.cpp b/src/gui/tray/activitydata.cpp index 8a077fc28..180a57f4e 100644 --- a/src/gui/tray/activitydata.cpp +++ b/src/gui/tray/activitydata.cpp @@ -63,9 +63,25 @@ OCC::Activity Activity::fromActivityJson(const QJsonObject json, const AccountPt activity._file = json.value(QStringLiteral("object_name")).toString(); activity._link = QUrl(json.value(QStringLiteral("link")).toString()); activity._dateTime = QDateTime::fromString(json.value(QStringLiteral("datetime")).toString(), Qt::ISODate); - activity._icon = json.value(QStringLiteral("icon")).toString(); + activity._darkIcon = json.value(QStringLiteral("icon")).toString(); // We have both dark and light for theming purposes + activity._lightIcon = json.value(QStringLiteral("icon")).toString(); // Some icons get changed in the ActivityListModel activity._isCurrentUserFileActivity = activity._objectType == QStringLiteral("files") && activityUser == account->davUser(); + const auto darkIconPath = QStringLiteral("qrc://:/client/theme/white/"); + const auto lightIconPath = QStringLiteral("qrc://:/client/theme/black/"); + if(activity._darkIcon.contains("change.svg")) { + activity._darkIcon = darkIconPath + QStringLiteral("change.svg"); + activity._lightIcon = lightIconPath + QStringLiteral("change.svg"); + } else if(activity._darkIcon.contains("calendar.svg")) { + activity._darkIcon = darkIconPath + QStringLiteral("calendar.svg"); + activity._lightIcon = lightIconPath + QStringLiteral("calendar.svg"); + } else if(activity._darkIcon.contains("personal.svg")) { + activity._darkIcon = darkIconPath + QStringLiteral("user.svg"); + activity._lightIcon = lightIconPath + QStringLiteral("user.svg"); + } else if(activity._darkIcon.contains("core/img/actions")) { + activity._darkIcon.insert(activity._darkIcon.indexOf(".svg"), "-white"); + } + auto richSubjectData = json.value(QStringLiteral("subject_rich")).toArray(); if(richSubjectData.size() > 1) { @@ -128,15 +144,23 @@ OCC::Activity Activity::fromActivityJson(const QJsonObject json, const AccountPt } if(!previewsData.isEmpty()) { - if(activity._icon.contains(QStringLiteral("add-color.svg"))) { - activity._icon = "qrc:///client/theme/colored/add-bordered.svg"; - } else if(activity._icon.contains(QStringLiteral("delete-color.svg"))) { - activity._icon = "qrc:///client/theme/colored/delete-bordered.svg"; - } else if(activity._icon.contains(QStringLiteral("change.svg"))) { - activity._icon = "qrc:///client/theme/colored/change-bordered.svg"; + if(activity._darkIcon.contains(QStringLiteral("add-color.svg"))) { + activity._darkIcon = "qrc:///client/theme/colored/add-bordered.svg"; + activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg"; + } else if(activity._darkIcon.contains(QStringLiteral("delete-color.svg"))) { + activity._darkIcon = "qrc:///client/theme/colored/delete-bordered.svg"; + activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg"; + } else if(activity._darkIcon.contains(QStringLiteral("change.svg"))) { + activity._darkIcon = "qrc:///client/theme/colored/change-bordered.svg"; + activity._lightIcon = "qrc:///client/theme/colored/add-bordered.svg"; } } + auto actions = json.value("actions").toArray(); + foreach (auto action, actions) { + activity._links.append(ActivityLink::createFomJsonObject(action.toObject())); + } + return activity; } diff --git a/src/gui/tray/activitydata.h b/src/gui/tray/activitydata.h index e9f1b9192..bd7b49ffc 100644 --- a/src/gui/tray/activitydata.h +++ b/src/gui/tray/activitydata.h @@ -133,7 +133,8 @@ public: QDateTime _dateTime; qint64 _expireAtMsecs = -1; QString _accName; - QString _icon; + QString _darkIcon; + QString _lightIcon; bool _isCurrentUserFileActivity = false; QVector<PreviewData> _previews; diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp index 63733b8be..ff50ae0d0 100644 --- a/src/gui/tray/activitylistmodel.cpp +++ b/src/gui/tray/activitylistmodel.cpp @@ -62,7 +62,8 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const roles[LinkRole] = "link"; roles[MessageRole] = "message"; roles[ActionRole] = "type"; - roles[ActionIconRole] = "icon"; + roles[DarkIconRole] = "darkIcon"; + roles[LightIconRole] = "lightIcon"; roles[ActionTextRole] = "subject"; roles[ActionsLinksRole] = "links"; roles[ActionsLinksContextMenuRole] = "linksContextMenu"; @@ -192,31 +193,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const }); }; - switch (role) { - case DisplayPathRole: - return getDisplayPath(); - case PathRole: - return QFileInfo(getFilePath()).path(); - case DisplayLocationRole: - return displayLocation(); - case ActionsLinksRole: { - QList<QVariant> customList; - foreach (ActivityLink activityLink, a._links) { - customList << QVariant::fromValue(activityLink); - } - return customList; - } - - case ActionsLinksContextMenuRole: { - return ActivityListModel::convertLinksToMenuEntries(a); - } - - case ActionsLinksForActionButtonsRole: { - return ActivityListModel::convertLinksToActionButtons(a); - } - - case ActionIconRole: { - auto colorIconPath = QStringLiteral("qrc:///client/theme/__COLOR__/"); // We will replace __COLOR__ in QML + const auto generateIconPath = [&]() { + auto colorIconPath = role == DarkIconRole ? QStringLiteral("qrc:///client/theme/white/") : QStringLiteral("qrc:///client/theme/black/"); if (a._type == Activity::NotificationType) { colorIconPath.append("bell.svg"); return colorIconPath; @@ -255,14 +233,40 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } } else { // We have an activity - if (a._icon.isEmpty()) { + if (a._darkIcon.isEmpty()) { colorIconPath.append("activity.svg"); return colorIconPath; } + return role == DarkIconRole ? a._darkIcon : a._lightIcon; + } + }; - return a._icon; + switch (role) { + case DisplayPathRole: + return getDisplayPath(); + case PathRole: + return QFileInfo(getFilePath()).path(); + case DisplayLocationRole: + return displayLocation(); + case ActionsLinksRole: { + QList<QVariant> customList; + foreach (ActivityLink activityLink, a._links) { + customList << QVariant::fromValue(activityLink); } + return customList; + } + + case ActionsLinksContextMenuRole: { + return ActivityListModel::convertLinksToMenuEntries(a); + } + + case ActionsLinksForActionButtonsRole: { + return ActivityListModel::convertLinksToActionButtons(a); } + + case DarkIconRole: + case LightIconRole: + return generateIconPath(); case ObjectTypeRole: return a._objectType; case ObjectIdRole: @@ -400,20 +404,6 @@ void ActivityListModel::ingestActivities(const QJsonArray &activities) auto a = Activity::fromActivityJson(json, _accountState->account()); - auto colorIconPath = QStringLiteral("qrc:///client/theme/__COLOR__/"); - if(a._icon.contains("change.svg")) { - colorIconPath.append("change.svg"); - a._icon = colorIconPath; - } else if(a._icon.contains("calendar.svg")) { - colorIconPath.append("calendar.svg"); - a._icon = colorIconPath; - } else if(a._icon.contains("personal.svg")) { - colorIconPath.append("user.svg"); - a._icon = colorIconPath; - } else if(a._icon.contains("core/img/actions")) { - a._icon.insert(a._icon.indexOf(".svg"), "__WHITE_GOES_HERE__"); - } - list.append(a); _currentItem = list.last()._id; diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h index 44b521352..ee6959f60 100644 --- a/src/gui/tray/activitylistmodel.h +++ b/src/gui/tray/activitylistmodel.h @@ -45,7 +45,8 @@ class ActivityListModel : public QAbstractListModel Q_PROPERTY(AccountState *accountState READ accountState CONSTANT) public: enum DataRole { - ActionIconRole = Qt::UserRole + 1, + DarkIconRole = Qt::UserRole + 1, + LightIconRole, AccountRole, ObjectTypeRole, ObjectIdRole, diff --git a/src/gui/tray/notificationhandler.cpp b/src/gui/tray/notificationhandler.cpp index aa5bd4155..0e79d13ad 100644 --- a/src/gui/tray/notificationhandler.cpp +++ b/src/gui/tray/notificationhandler.cpp @@ -92,15 +92,11 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j ActivityList list; foreach (auto element, notifies) { - Activity a; auto json = element.toObject(); + auto a = Activity::fromActivityJson(json, ai->account()); a._type = Activity::NotificationType; - a._accName = ai->account()->displayName(); a._id = json.value("notification_id").toInt(); - //need to know, specially for remote_share - a._objectType = json.value("object_type").toString(); - // 2 cases to consider: // - server == 24 & has Talk: notification type chat/call contains conversationToken/messageId in object_type // - server < 24 & has Talk: notification type chat/call contains _only_ the conversationToken in object_type @@ -117,10 +113,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j a._status = 0; - a._subject = json.value("subject").toString(); - a._message = json.value("message").toString(); - a._icon = json.value("icon").toString(); - QUrl link(json.value("link").toString()); if (!link.isEmpty()) { if (link.host().isEmpty()) { @@ -132,12 +124,6 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j } } a._link = link; - a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate); - - auto actions = json.value("actions").toArray(); - foreach (auto action, actions) { - a._links.append(ActivityLink::createFomJsonObject(action.toObject())); - } // Add another action to dismiss notification on server // https://github.com/owncloud/notifications/blob/master/docs/ocs-endpoint-v1.md#deleting-a-notification-for-a-user diff --git a/src/gui/tray/unifiedsearchresult.h b/src/gui/tray/unifiedsearchresult.h index bae3158d6..643c6d91c 100644 --- a/src/gui/tray/unifiedsearchresult.h +++ b/src/gui/tray/unifiedsearchresult.h @@ -42,7 +42,8 @@ struct UnifiedSearchResult bool _isRounded = false; qint32 _order = std::numeric_limits<qint32>::max(); QUrl _resourceUrl; - QString _icons; + QString _darkIcons; + QString _lightIcons; Type _type = Type::Default; }; } diff --git a/src/gui/tray/unifiedsearchresultslistmodel.cpp b/src/gui/tray/unifiedsearchresultslistmodel.cpp index 708acb7de..7d5cd95db 100644 --- a/src/gui/tray/unifiedsearchresultslistmodel.cpp +++ b/src/gui/tray/unifiedsearchresultslistmodel.cpp @@ -19,7 +19,6 @@ #include "guiutility.h" #include "folderman.h" #include "networkjobs.h" -#include "systray.h" #include <algorithm> @@ -27,47 +26,49 @@ #include <QDesktopServices> namespace { -QString imagePlaceholderUrlForProviderId(const QString &providerId) +QString imagePlaceholderUrlForProviderId(const QString &providerId, const bool darkMode) { + const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/"); if (providerId.contains(QStringLiteral("message"), Qt::CaseInsensitive) || providerId.contains(QStringLiteral("talk"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/wizard-talk.svg") : QStringLiteral("qrc:///client/theme/black/wizard-talk.svg"); + return colorIconPath % QStringLiteral("wizard-talk.svg"); } else if (providerId.contains(QStringLiteral("file"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/edit.svg") : QStringLiteral("qrc:///client/theme/black/edit.svg"); + return colorIconPath % QStringLiteral("edit.svg"); } else if (providerId.contains(QStringLiteral("deck"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/deck.svg") : QStringLiteral("qrc:///client/theme/black/deck.svg"); + return colorIconPath % QStringLiteral("deck.svg"); } else if (providerId.contains(QStringLiteral("calendar"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/calendar.svg") : QStringLiteral("qrc:///client/theme/black/calendar.svg"); + return colorIconPath % QStringLiteral("calendar.svg"); } else if (providerId.contains(QStringLiteral("mail"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/email.svg") : QStringLiteral("qrc:///client/theme/black/email.svg"); + return colorIconPath % QStringLiteral("email.svg"); } else if (providerId.contains(QStringLiteral("comment"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral("qrc:///client/theme/white/comment.svg") : QStringLiteral("qrc:///client/theme/black/comment.svg"); + return colorIconPath % QStringLiteral("comment.svg"); } - return QStringLiteral("qrc:///client/theme/change.svg"); + return colorIconPath % QStringLiteral("change.svg"); } -QString localIconPathFromIconPrefix(const QString &iconNameWithPrefix) +QString localIconPathFromIconPrefix(const QString &iconNameWithPrefix, const bool darkMode) { + const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/"); if (iconNameWithPrefix.contains(QStringLiteral("message"), Qt::CaseInsensitive) || iconNameWithPrefix.contains(QStringLiteral("talk"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/wizard-talk.svg") : QStringLiteral(":/client/theme/black/wizard-talk.svg"); + return colorIconPath % QStringLiteral("wizard-talk.svg"); } else if (iconNameWithPrefix.contains(QStringLiteral("folder"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/folder.svg") : QStringLiteral(":/client/theme/black/folder.svg"); + return colorIconPath % QStringLiteral("folder.svg"); } else if (iconNameWithPrefix.contains(QStringLiteral("deck"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/deck.svg") : QStringLiteral(":/client/theme/black/deck.svg"); + return colorIconPath % QStringLiteral("deck.svg"); } else if (iconNameWithPrefix.contains(QStringLiteral("contacts"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/wizard-groupware.svg") : QStringLiteral(":/client/theme/black/wizard-groupware.svg"); + return colorIconPath % QStringLiteral("wizard-groupware.svg"); } else if (iconNameWithPrefix.contains(QStringLiteral("calendar"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/calendar.svg") : QStringLiteral(":/client/theme/black/calendar.svg"); + return colorIconPath % QStringLiteral("calendar.svg"); } else if (iconNameWithPrefix.contains(QStringLiteral("mail"), Qt::CaseInsensitive)) { - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/email.svg") : QStringLiteral(":/client/theme/black/email.svg"); + return colorIconPath % QStringLiteral("email.svg"); } - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/change.svg") : QStringLiteral(":/client/theme/change.svg"); + return colorIconPath % QStringLiteral("change.svg"); } -QString iconUrlForDefaultIconName(const QString &defaultIconName) +QString iconUrlForDefaultIconName(const QString &defaultIconName, const bool darkMode) { const QUrl urlForIcon{defaultIconName}; @@ -75,15 +76,16 @@ QString iconUrlForDefaultIconName(const QString &defaultIconName) return defaultIconName; } + const auto colorIconPath = darkMode ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/"); + if (defaultIconName.startsWith(QStringLiteral("icon-"))) { const auto parts = defaultIconName.split(QLatin1Char('-')); if (parts.size() > 1) { - const QString blackOrWhite = OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/") : QStringLiteral(":/client/theme/black/"); - const QString blackIconFilePath = blackOrWhite + parts[1] + QStringLiteral(".svg"); + const QString blackOrWhiteIconFilePath = colorIconPath + parts[1] + QStringLiteral(".svg"); - if (QFile::exists(blackIconFilePath)) { - return blackIconFilePath; + if (QFile::exists(blackOrWhiteIconFilePath)) { + return blackOrWhiteIconFilePath; } const QString iconFilePath = QStringLiteral(":/client/theme/") + parts[1] + QStringLiteral(".svg"); @@ -93,14 +95,14 @@ QString iconUrlForDefaultIconName(const QString &defaultIconName) } } - const auto iconNameFromIconPrefix = localIconPathFromIconPrefix(defaultIconName); + const auto iconNameFromIconPrefix = localIconPathFromIconPrefix(defaultIconName, darkMode); if (!iconNameFromIconPrefix.isEmpty()) { return iconNameFromIconPrefix; } } - return OCC::Systray::instance()->darkMode() ? QStringLiteral(":/client/theme/white/change.svg") : QStringLiteral(":/client/theme/change.svg"); + return colorIconPath % QStringLiteral("change.svg"); } QString generateUrlForThumbnail(const QString &thumbnailUrl, const QUrl &serverUrl) @@ -125,7 +127,7 @@ QString generateUrlForThumbnail(const QString &thumbnailUrl, const QUrl &serverU return thumbnailUrlCopy; } -QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl) +QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl, const bool darkMode) { auto serverUrlCopy = serverUrl; @@ -144,7 +146,7 @@ QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl) } } else if (!fallbackIconCopy.isEmpty()) { // could be one of names for standard icons (e.g. icon-mail) - const auto defaultIconUrl = iconUrlForDefaultIconName(fallbackIconCopy); + const auto defaultIconUrl = iconUrlForDefaultIconName(fallbackIconCopy, darkMode); if (!defaultIconUrl.isEmpty()) { fallbackIconCopy = defaultIconUrl; } @@ -153,7 +155,7 @@ QString generateUrlForIcon(const QString &fallbackIcon, const QUrl &serverUrl) return fallbackIconCopy; } -QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QString &fallbackIcon, const QUrl &serverUrl) +QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QString &fallbackIcon, const QUrl &serverUrl, const bool darkMode) { if (thumbnailUrl.isEmpty() && fallbackIcon.isEmpty()) { return {}; @@ -165,7 +167,7 @@ QString iconsFromThumbnailAndFallbackIcon(const QString &thumbnailUrl, const QSt } const auto urlForThumbnail = generateUrlForThumbnail(thumbnailUrl, serverUrl); - const auto urlForFallbackIcon = generateUrlForIcon(fallbackIcon, serverUrl); + const auto urlForFallbackIcon = generateUrlForIcon(fallbackIcon, serverUrl, darkMode); qDebug() << "SEARCH" << urlForThumbnail << urlForFallbackIcon; @@ -204,10 +206,14 @@ QVariant UnifiedSearchResultsListModel::data(const QModelIndex &index, int role) return _results.at(index.row())._providerName; case ProviderIdRole: return _results.at(index.row())._providerId; - case ImagePlaceholderRole: - return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId); - case IconsRole: - return _results.at(index.row())._icons; + case DarkImagePlaceholderRole: + return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId, true); + case LightImagePlaceholderRole: + return imagePlaceholderUrlForProviderId(_results.at(index.row())._providerId, false); + case DarkIconsRole: + return _results.at(index.row())._darkIcons; + case LightIconsRole: + return _results.at(index.row())._lightIcons; case TitleRole: return _results.at(index.row())._title; case SublineRole: @@ -239,8 +245,10 @@ QHash<int, QByteArray> UnifiedSearchResultsListModel::roleNames() const auto roles = QAbstractListModel::roleNames(); roles[ProviderNameRole] = "providerName"; roles[ProviderIdRole] = "providerId"; - roles[IconsRole] = "icons"; - roles[ImagePlaceholderRole] = "imagePlaceholder"; + roles[DarkIconsRole] = "darkIcons"; + roles[LightIconsRole] = "lightIcons"; + roles[DarkImagePlaceholderRole] = "darkImagePlaceholder"; + roles[LightImagePlaceholderRole] = "lightImagePlaceholder"; roles[TitleRole] = "resultTitle"; roles[SublineRole] = "subline"; roles[ResourceUrlRole] = "resourceUrlRole"; @@ -577,8 +585,10 @@ void UnifiedSearchResultsListModel::parseResultsForProvider(const QJsonObject &d const auto accountUrl = (_accountState && _accountState->account()) ? _accountState->account()->url() : QUrl(); result._resourceUrl = makeResourceUrl(resourceUrl, accountUrl); - result._icons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(), - entryMap.value(QStringLiteral("icon")).toString(), accountUrl); + result._darkIcons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(), + entryMap.value(QStringLiteral("icon")).toString(), accountUrl, true); + result._lightIcons = iconsFromThumbnailAndFallbackIcon(entryMap.value(QStringLiteral("thumbnailUrl")).toString(), + entryMap.value(QStringLiteral("icon")).toString(), accountUrl, false); newEntries.push_back(result); } diff --git a/src/gui/tray/unifiedsearchresultslistmodel.h b/src/gui/tray/unifiedsearchresultslistmodel.h index 5ae811f20..2fffd99da 100644 --- a/src/gui/tray/unifiedsearchresultslistmodel.h +++ b/src/gui/tray/unifiedsearchresultslistmodel.h @@ -53,8 +53,10 @@ public: enum DataRole { ProviderNameRole = Qt::UserRole + 1, ProviderIdRole, - ImagePlaceholderRole, - IconsRole, + DarkImagePlaceholderRole, + LightImagePlaceholderRole, + DarkIconsRole, + LightIconsRole, TitleRole, SublineRole, ResourceUrlRole, diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp index 8916cba2f..34bea7287 100644 --- a/src/libsync/theme.cpp +++ b/src/libsync/theme.cpp @@ -343,6 +343,9 @@ QString Theme::hidpiFileName(const QString &iconName, const QColor &backgroundCo Theme::Theme() : QObject(nullptr) { +#if defined(Q_OS_WIN) + reserveDarkPalette = QPalette(QColor(49,49,49,255), QColor(35,35,35,255)); // Windows 11 button and window dark colours +#endif } // If this option returns true, the client only supports one folder to sync. @@ -899,16 +902,44 @@ QColor Theme::errorBoxBorderColor() const return QColor{"black"}; } -QPalette Theme::systemPalette() +void Theme::connectToPaletteSignal() { if(!_guiAppInstance) { const auto ptr = qobject_cast<QGuiApplication *>(QGuiApplication::instance()); if(ptr) { _guiAppInstance.reset(ptr); - connect(ptr, &QGuiApplication::paletteChanged, this, &Theme::systemPaletteChanged); + connect(_guiAppInstance.data(), &QGuiApplication::paletteChanged, this, &Theme::systemPaletteChanged); + connect(_guiAppInstance.data(), &QGuiApplication::paletteChanged, this, &Theme::darkModeChanged); } } +} + +QPalette Theme::systemPalette() +{ + connectToPaletteSignal(); +#if defined(Q_OS_WIN) + if(darkMode()) { + return reserveDarkPalette; + } +#endif return QGuiApplication::palette(); } +bool Theme::darkMode() +{ + connectToPaletteSignal(); +// Windows: Check registry for dark mode +#if defined(Q_OS_WIN) + const auto darkModeSubkey = QStringLiteral("Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"); + if (Utility::registryKeyExists(HKEY_CURRENT_USER, darkModeSubkey) && + !Utility::registryGetKeyValue(HKEY_CURRENT_USER, darkModeSubkey, QStringLiteral("AppsUseLightTheme")).toBool()) { + return true; + } + + return false; +#else + return Theme::isDarkColor(QGuiApplication::palette().window().color()); +#endif +} + } // end namespace client diff --git a/src/libsync/theme.h b/src/libsync/theme.h index d6c38aacb..df9303b23 100644 --- a/src/libsync/theme.h +++ b/src/libsync/theme.h @@ -69,6 +69,7 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject Q_PROPERTY(QColor errorBoxBorderColor READ errorBoxBorderColor CONSTANT) Q_PROPERTY(QPalette systemPalette READ systemPalette NOTIFY systemPaletteChanged) + Q_PROPERTY(bool darkMode READ darkMode NOTIFY darkModeChanged) public: enum CustomMediaType { oCSetupTop, // ownCloud connect page @@ -594,6 +595,7 @@ public: static constexpr const char *themePrefix = ":/client/theme/"; QPalette systemPalette(); + bool darkMode(); protected: #ifndef TOKEN_AUTH_ONLY @@ -612,14 +614,21 @@ protected: signals: void systrayUseMonoIconsChanged(bool); void systemPaletteChanged(const QPalette &palette); + void darkModeChanged(); private: Theme(Theme const &); Theme &operator=(Theme const &); + void connectToPaletteSignal(); +#if defined(Q_OS_WIN) + QPalette reserveDarkPalette; // Windows 11 button and window dark colours +#endif + static Theme *_instance; bool _mono = false; QScopedPointer<QGuiApplication> _guiAppInstance; + #ifndef TOKEN_AUTH_ONLY mutable QHash<QString, QIcon> _iconCache; #endif diff --git a/test/testactivitylistmodel.cpp b/test/testactivitylistmodel.cpp index fd521f406..24c75941e 100644 --- a/test/testactivitylistmodel.cpp +++ b/test/testactivitylistmodel.cpp @@ -598,7 +598,8 @@ private slots: QVERIFY(!index.data(OCC::ActivityListModel::AccountRole).toString().isEmpty()); QVERIFY(!index.data(OCC::ActivityListModel::ActionTextColorRole).toString().isEmpty()); - QVERIFY(!index.data(OCC::ActivityListModel::ActionIconRole).toString().isEmpty()); + QVERIFY(!index.data(OCC::ActivityListModel::DarkIconRole).toString().isEmpty()); + QVERIFY(!index.data(OCC::ActivityListModel::LightIconRole).toString().isEmpty()); QVERIFY(!index.data(OCC::ActivityListModel::PointInTimeRole).toString().isEmpty()); QVERIFY(index.data(OCC::ActivityListModel::ObjectTypeRole).canConvert<int>()); diff --git a/theme.qrc.in b/theme.qrc.in index 3823b8da1..6f42f5221 100644 --- a/theme.qrc.in +++ b/theme.qrc.in @@ -44,6 +44,7 @@ <file>theme/white/state-sync-64.png</file> <file>theme/white/state-sync-128.png</file> <file>theme/white/state-sync-256.png</file> + <file>theme/black/change.svg</file> <file>theme/black/clear.svg</file> <file>theme/black/comment.svg</file> <file>theme/black/search.svg</file> @@ -90,6 +91,7 @@ <file>theme/white/folder@2x.png</file> <file>theme/colored/folder.png</file> <file>theme/colored/folder@2x.png</file> + <file>theme/black/confirm.svg</file> <file>theme/black/control-next.svg</file> <file>theme/black/control-prev.svg</file> <file>theme/black/settings.svg</file> diff --git a/theme/Style/Style.qml b/theme/Style/Style.qml index 5cb2220c4..6c90be563 100644 --- a/theme/Style/Style.qml +++ b/theme/Style/Style.qml @@ -12,8 +12,8 @@ QtObject { readonly property color ncTextColor: Theme.systemPalette.windowText
readonly property color ncSecondaryTextColor: "#808080"
readonly property color ncHeaderTextColor: "white"
- readonly property color lightHover: Systray.darkMode ? Qt.lighter(backgroundColor, 2) : Qt.darker(backgroundColor, 1.05)
- readonly property color menuBorder: Systray.darkMode ? Qt.lighter(backgroundColor, 3) : Qt.darker(backgroundColor, 1.5)
+ readonly property color lightHover: Theme.darkMode ? Qt.lighter(backgroundColor, 2) : Qt.darker(backgroundColor, 1.05)
+ readonly property color menuBorder: ncSecondaryTextColor
readonly property color backgroundColor: Theme.systemPalette.base
// ErrorBox colors
|