Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/desktop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudio Cambra <claudio.cambra@gmail.com>2022-03-21 19:34:21 +0300
committerClaudio Cambra (Rebase PR Action) <claudio.cambra@gmail.com>2022-03-25 22:44:55 +0300
commitaf01904d066351c18b03222d2108189499aa1da2 (patch)
tree40ac63e6cb624e7fc85ebe821697d08ac679d780
parent9292ddf89ff1c806953f5c66cc78233b61a97c07 (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.cpp28
-rw-r--r--src/gui/systray.cpp24
-rw-r--r--src/gui/systray.h5
-rw-r--r--src/gui/tray/ActivityItemContent.qml14
-rw-r--r--src/gui/tray/UnifiedSearchResultListItem.qml5
-rw-r--r--src/gui/tray/UserLine.qml2
-rw-r--r--src/gui/tray/Window.qml2
-rw-r--r--src/gui/tray/activitydata.cpp38
-rw-r--r--src/gui/tray/activitydata.h3
-rw-r--r--src/gui/tray/activitylistmodel.cpp74
-rw-r--r--src/gui/tray/activitylistmodel.h3
-rw-r--r--src/gui/tray/notificationhandler.cpp16
-rw-r--r--src/gui/tray/unifiedsearchresult.h3
-rw-r--r--src/gui/tray/unifiedsearchresultslistmodel.cpp82
-rw-r--r--src/gui/tray/unifiedsearchresultslistmodel.h6
-rw-r--r--src/libsync/theme.cpp35
-rw-r--r--src/libsync/theme.h9
-rw-r--r--test/testactivitylistmodel.cpp3
-rw-r--r--theme.qrc.in2
-rw-r--r--theme/Style/Style.qml4
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