From 7f764f81083ebf1655ec1826af8934c2c9d549a2 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 27 Oct 2019 15:19:20 +0100 Subject: [PATCH 001/120] Added basic UI [WIP] for new tray window Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/traywindow.qml | 225 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 src/gui/traywindow.qml diff --git a/src/gui/traywindow.qml b/src/gui/traywindow.qml new file mode 100644 index 000000000000..d6bb09015f86 --- /dev/null +++ b/src/gui/traywindow.qml @@ -0,0 +1,225 @@ +import QtQuick 2.9 +import QtQuick.Window 2.2 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +Window { + id: trayWindow + visible: true + width: 420 + height: 500 + color: "transparent" + flags: Qt.FramelessWindowHint + + Component.onCompleted: { + setX((Screen.width - width) - 18); + setY(Screen.height - (height + 60)); + } + + Rectangle { + id: trayWindowBackground + anchors.fill: parent + radius: 10 + + Rectangle { + id: trayWindowHeaderBackground + anchors.left: trayWindowBackground.left + anchors.top: trayWindowBackground.top + height: 60 + width: parent.width + radius: 10 + color: "#0082c9" + + Rectangle { + anchors.left: trayWindowHeaderBackground.left + anchors.bottom: trayWindowHeaderBackground.bottom + height: 30 + width: parent.width + color: "#0082c9" + } + + RowLayout { + id: trayWindowHeaderLayout + spacing: 2 + anchors.fill: parent + + Item { + id: avatarButtonContainer + Layout.alignment: Qt.AlignLeft + width: (trayWindowHeaderBackground.height - 12) + height: (trayWindowHeaderBackground.height - 12) + Layout.margins: 4 + Image { + id: currentAvatarButton + width: (trayWindowHeaderBackground.height - 12) + height: (trayWindowHeaderBackground.height - 12) + antialiasing: true + Layout.margins: 4 + source: "../avatar.png" + } + + Button { + id: currentAccountButton + width: (trayWindowHeaderBackground.height + 4) + height: (trayWindowHeaderBackground.height) + display: AbstractButton.IconOnly + flat: true + + MouseArea { + id: accountBtnMouseArea + width: currentAccountButton.width + accountLabels.width + 8 + height: trayWindowHeaderBackground.height - 6 + onClicked: + { + accountMenu.popup() + } + + Menu { + id: accountMenu + background: Rectangle { + id: menubackground + implicitWidth: 200 + implicitHeight: 40 + anchors.fill: parent + radius: 10 + } + + MenuItem { text: "test" } + } + } + + background: + Rectangle { + color: "transparent" + } + } + } + + Column { + id: accountLabels + Layout.leftMargin: 0 + spacing: 4 + Layout.alignment: Qt.AlignLeft + //anchors.left: currentAvatarButton.right + Label { + id: syncStatusLabel + text: "Everything up to date" + color: "white" + font.pointSize: 9 + font.bold: true + } + Label { + id: currentUserLabel + text: "freddie@nextcloud.com" + color: "white" + font.pointSize: 8 + } + } + + Label { + text: "\u25BC" + Layout.bottomMargin: 10 + Layout.alignment: Qt.AlignLeft | Qt.AlignBottom + font.pointSize: 9 + color: "white" + verticalAlignment: Qt.AlignBottom + } + + Item { + id: trayWindowHeaderSpacer + Layout.fillWidth: true + } + + Button { + id: openLocalFolderButton + rightPadding: 2 + leftPadding: 2 + Layout.alignment: Qt.AlignRight + display: AbstractButton.IconOnly + flat: true + Layout.preferredWidth: (trayWindowHeaderBackground.height - 12) + Layout.preferredHeight: (trayWindowHeaderBackground.height - 12) + + icon.source: "../files.png" + icon.color: "transparent" + + MouseArea { + id: folderBtnMouseArea + anchors.fill: parent + onClicked: + { + } + } + + background: + Rectangle { + color: "transparent" + } + } + + Button { + id: trayWindowTalkButton + rightPadding: 2 + leftPadding: 2 + Layout.alignment: Qt.AlignRight + display: AbstractButton.IconOnly + Layout.preferredWidth: (trayWindowHeaderBackground.height - 12) + Layout.preferredHeight: (trayWindowHeaderBackground.height - 12) + flat: true + Layout.margins: 4 + + icon.source: "../talk.png" + icon.color: "transparent" + + MouseArea { + id: talkBtnMouseArea + anchors.fill: parent + onClicked: + { + } + } + + background: + Rectangle { + color: "transparent" + } + } + + Button { + id: trayWindowAppsButton + rightPadding: 2 + leftPadding: 2 + Layout.alignment: Qt.AlignRight + display: AbstractButton.IconOnly + Layout.preferredWidth: (trayWindowHeaderBackground.height - 12) + Layout.preferredHeight: (trayWindowHeaderBackground.height - 12) + flat: true + Layout.margins: 4 + + icon.source: "../apps.png" + icon.color: "transparent" + + MouseArea { + id: appsBtnMouseArea + anchors.fill: parent + onClicked: + { + } + } + + background: + Rectangle { + color: "transparent" + } + } + } + } // Rectangle trayWindowHeaderBackground + + /*ListView { + anchors.top: trayWindowHeaderBackground.bottom + Layout.fillWidth: true + Layout.fillHeight: true + }*/ + + } // Rectangle trayWindowBackground +} From 1e43c29484ed51a61931d6d9f1c6ce5d8bd9e52f Mon Sep 17 00:00:00 2001 From: Camila San Date: Sun, 27 Oct 2019 16:51:07 +0100 Subject: [PATCH 002/120] Add new qml tray icon. This is a work in progress. Signed-off-by: Camila San --- client.qrc | 4 ++++ src/gui/owncloudgui.cpp | 15 +++++++++++++++ src/gui/systemtray.qml | 20 ++++++++++++++++++++ src/gui/traywindow.qml | 5 +++-- 4 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 src/gui/systemtray.qml diff --git a/client.qrc b/client.qrc index f9952965a480..a5494f5428db 100644 --- a/client.qrc +++ b/client.qrc @@ -31,4 +31,8 @@ resources/state-info.svg + + src/gui/systemtray.qml + src/gui/traywindow.qml + diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index d3828aed62ec..c18705fb875c 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -55,6 +55,12 @@ #include #include +#include +#include +#include +#include +#include + namespace OCC { const char propertyAccountC[] = "oc_account"; @@ -88,6 +94,15 @@ ownCloudGui::ownCloudGui(Application *parent) _tray->show(); + // QML System tray + QQmlEngine *engine = new QQmlEngine; + //engine.load(QUrl(QStringLiteral(":/qml/src/gui/systemtray.qml"))); + QQmlComponent systrayrtest(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/systemtray.qml"))); + QQmlContext *ctxt = engine->contextForObject(systrayrtest.create()); + // TODO hack to pass the icon to QML + //ctxt->setContextProperty("theme", QLatin1String("colored")); + //ctxt->setContextProperty("filename", "state-offline"); + ProgressDispatcher *pd = ProgressDispatcher::instance(); connect(pd, &ProgressDispatcher::progressInfo, this, &ownCloudGui::slotUpdateProgress); diff --git a/src/gui/systemtray.qml b/src/gui/systemtray.qml new file mode 100644 index 000000000000..c1398bebf47f --- /dev/null +++ b/src/gui/systemtray.qml @@ -0,0 +1,20 @@ +import QtQuick 2.0 +import Qt.labs.platform 1.1 + +SystemTrayIcon { + visible: true + //icon.source: "qrc:/client/theme/colored/state-offiline-32.png" + icon.source: "qrc:/client/theme/colored/state-sync-32.png"; + + Component.onCompleted: { + showMessage("Desktop Client 2.7", "New QML menu!", 1000) + } + + onActivated: { + var component = Qt.createComponent("qrc:/qml/src/gui/traywindow.qml") + win = component.createObject() + win.show() + win.raise() + win.requestActivate() + } +} diff --git a/src/gui/traywindow.qml b/src/gui/traywindow.qml index d6bb09015f86..c40aa5273049 100644 --- a/src/gui/traywindow.qml +++ b/src/gui/traywindow.qml @@ -12,8 +12,9 @@ Window { flags: Qt.FramelessWindowHint Component.onCompleted: { - setX((Screen.width - width) - 18); - setY(Screen.height - (height + 60)); + // desktopAvailableWidth and Height doesn't include the system tray bar + setX(Screen.desktopAvailableWidth - width); + setY(Screen.desktopAvailableHeight + height); } Rectangle { From f9f1b49298893872d22cc52102b9213159860ae5 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 28 Oct 2019 10:36:09 +0100 Subject: [PATCH 003/120] Added new vector graphics for tray window header icons Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- theme/white/caret-white.svg | 1 + theme/white/folder.svg | 1 + theme/white/more-apps.svg | 1 + theme/white/talk-app.svg | 1 + 4 files changed, 4 insertions(+) create mode 100644 theme/white/caret-white.svg create mode 100644 theme/white/folder.svg create mode 100644 theme/white/more-apps.svg create mode 100644 theme/white/talk-app.svg diff --git a/theme/white/caret-white.svg b/theme/white/caret-white.svg new file mode 100644 index 000000000000..c97d6e264074 --- /dev/null +++ b/theme/white/caret-white.svg @@ -0,0 +1 @@ + diff --git a/theme/white/folder.svg b/theme/white/folder.svg new file mode 100644 index 000000000000..003e8b3fb80d --- /dev/null +++ b/theme/white/folder.svg @@ -0,0 +1 @@ + diff --git a/theme/white/more-apps.svg b/theme/white/more-apps.svg new file mode 100644 index 000000000000..ee5b522769f8 --- /dev/null +++ b/theme/white/more-apps.svg @@ -0,0 +1 @@ + diff --git a/theme/white/talk-app.svg b/theme/white/talk-app.svg new file mode 100644 index 000000000000..867f2ec449b5 --- /dev/null +++ b/theme/white/talk-app.svg @@ -0,0 +1 @@ + From 73667a536731f2d15ee5e251f836b64685f0f78e Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 29 Oct 2019 08:13:05 +0100 Subject: [PATCH 004/120] Updated vecto icons and names, disabled init position (breaks on multiple monitors on windows), integrated new icons in qml Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/CMakeLists.txt | 2 ++ src/gui/systemtray.qml | 4 ++-- src/gui/traywindow.qml | 22 +++++++++---------- theme.qrc | 12 ++++++---- .../white/{caret-white.svg => caret-down.svg} | 0 5 files changed, 23 insertions(+), 17 deletions(-) rename theme/white/{caret-white.svg => caret-down.svg} (100%) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 2d6e98595997..faf22230c5b7 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -32,6 +32,8 @@ set(client_UI_SRCS shareusergroupwidget.ui shareuserline.ui sslerrordialog.ui + systemtray.qml + traywindow.qml addcertificatedialog.ui proxyauthdialog.ui mnemonicdialog.ui diff --git a/src/gui/systemtray.qml b/src/gui/systemtray.qml index c1398bebf47f..2d6c88d8ddfd 100644 --- a/src/gui/systemtray.qml +++ b/src/gui/systemtray.qml @@ -3,7 +3,7 @@ import Qt.labs.platform 1.1 SystemTrayIcon { visible: true - //icon.source: "qrc:/client/theme/colored/state-offiline-32.png" + //icon.source: "qrc:/client/theme/colored/state-offline-32.png" icon.source: "qrc:/client/theme/colored/state-sync-32.png"; Component.onCompleted: { @@ -12,7 +12,7 @@ SystemTrayIcon { onActivated: { var component = Qt.createComponent("qrc:/qml/src/gui/traywindow.qml") - win = component.createObject() + var win = component.createObject() win.show() win.raise() win.requestActivate() diff --git a/src/gui/traywindow.qml b/src/gui/traywindow.qml index c40aa5273049..56fbff3d4e06 100644 --- a/src/gui/traywindow.qml +++ b/src/gui/traywindow.qml @@ -12,9 +12,11 @@ Window { flags: Qt.FramelessWindowHint Component.onCompleted: { - // desktopAvailableWidth and Height doesn't include the system tray bar - setX(Screen.desktopAvailableWidth - width); - setY(Screen.desktopAvailableHeight + height); + /* desktopAvailableWidth and Height doesn't include the system tray bar + but breaks application anyway on windows when using multi monitor setup, + will look for a better solution later, for now just get this thing complete */ + //setX(Screen.desktopAvailableWidth - width); + //setY(Screen.desktopAvailableHeight + height); } Rectangle { @@ -56,7 +58,7 @@ Window { height: (trayWindowHeaderBackground.height - 12) antialiasing: true Layout.margins: 4 - source: "../avatar.png" + //source: "../avatar.png" } Button { @@ -117,13 +119,11 @@ Window { } } - Label { - text: "\u25BC" + Image { Layout.bottomMargin: 10 Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - font.pointSize: 9 - color: "white" verticalAlignment: Qt.AlignBottom + source: "qrc:///client/theme/white/caret-down.svg" } Item { @@ -141,7 +141,7 @@ Window { Layout.preferredWidth: (trayWindowHeaderBackground.height - 12) Layout.preferredHeight: (trayWindowHeaderBackground.height - 12) - icon.source: "../files.png" + icon.source: "qrc:///client/theme/white/folder.svg" icon.color: "transparent" MouseArea { @@ -169,7 +169,7 @@ Window { flat: true Layout.margins: 4 - icon.source: "../talk.png" + icon.source: "qrc:///client/theme/white/talk-app.svg" icon.color: "transparent" MouseArea { @@ -197,7 +197,7 @@ Window { flat: true Layout.margins: 4 - icon.source: "../apps.png" + icon.source: "qrc:///client/theme/white/more-apps.svg" icon.color: "transparent" MouseArea { diff --git a/theme.qrc b/theme.qrc index 006056b87bee..8b593df98ebf 100644 --- a/theme.qrc +++ b/theme.qrc @@ -43,7 +43,7 @@ theme/white/state-sync-64.png theme/white/state-sync-128.png theme/white/state-sync-256.png - theme/black/state-error-32.png + theme/black/state-error-32.png theme/black/state-error-64.png theme/black/state-error-128.png theme/black/state-error-256.png @@ -80,7 +80,7 @@ theme/colored/state-warning-128.png theme/colored/state-warning-256.png theme/black/control-next.svg - theme/black/control-prev.svg + theme/black/control-prev.svg theme/black/state-error.svg theme/black/state-error-16.png theme/black/state-offline.svg @@ -101,8 +101,8 @@ theme/black/state-warning-64.png theme/black/state-warning-128.png theme/black/state-warning-256.png - theme/white/control-next.svg - theme/white/control-prev.svg + theme/white/control-next.svg + theme/white/control-prev.svg theme/white/state-error.svg theme/white/state-error-16.png theme/white/state-offline.svg @@ -131,5 +131,9 @@ theme/colored/wizard-nextcloud@2x.png theme/colored/wizard-talk.png theme/colored/wizard-talk@2x.png + theme/white/folder.svg + theme/white/more-apps.svg + theme/white/talk-app.svg + theme/white/caret-down.svg diff --git a/theme/white/caret-white.svg b/theme/white/caret-down.svg similarity index 100% rename from theme/white/caret-white.svg rename to theme/white/caret-down.svg From 6135eb26adffd8954097057071462c6b02eff469 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 29 Oct 2019 23:25:00 +0100 Subject: [PATCH 005/120] Finished (static) header design and hover effects Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/traywindow.qml | 262 +++++++++++++++++++++++++++-------------- 1 file changed, 175 insertions(+), 87 deletions(-) diff --git a/src/gui/traywindow.qml b/src/gui/traywindow.qml index 56fbff3d4e06..c4beaeb5a559 100644 --- a/src/gui/traywindow.qml +++ b/src/gui/traywindow.qml @@ -2,11 +2,12 @@ import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 Window { id: trayWindow visible: true - width: 420 + width: 400 height: 500 color: "transparent" flags: Qt.FramelessWindowHint @@ -30,7 +31,7 @@ Window { anchors.top: trayWindowBackground.top height: 60 width: parent.width - radius: 10 + radius: 9 color: "#0082c9" Rectangle { @@ -43,43 +44,29 @@ Window { RowLayout { id: trayWindowHeaderLayout - spacing: 2 + spacing: 0 anchors.fill: parent - Item { - id: avatarButtonContainer - Layout.alignment: Qt.AlignLeft - width: (trayWindowHeaderBackground.height - 12) - height: (trayWindowHeaderBackground.height - 12) - Layout.margins: 4 - Image { - id: currentAvatarButton - width: (trayWindowHeaderBackground.height - 12) - height: (trayWindowHeaderBackground.height - 12) - antialiasing: true - Layout.margins: 4 - //source: "../avatar.png" - } + Button { + id: currentAccountButton + Layout.preferredWidth: 220 + Layout.preferredHeight: (trayWindowHeaderBackground.height) + display: AbstractButton.IconOnly + flat: true - Button { - id: currentAccountButton - width: (trayWindowHeaderBackground.height + 4) - height: (trayWindowHeaderBackground.height) - display: AbstractButton.IconOnly - flat: true - - MouseArea { - id: accountBtnMouseArea - width: currentAccountButton.width + accountLabels.width + 8 - height: trayWindowHeaderBackground.height - 6 - onClicked: - { - accountMenu.popup() - } + MouseArea { + id: accountBtnMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: + { + accountMenu.popup() + } - Menu { - id: accountMenu - background: Rectangle { + Menu { + id: accountMenu + background: + Rectangle { id: menubackground implicitWidth: 200 implicitHeight: 40 @@ -87,43 +74,101 @@ Window { radius: 10 } - MenuItem { text: "test" } - } - } + MenuItem { text: "test" } + } + } - background: + background: + Item { + id: leftHoverContainer + height: currentAccountButton.height + width: currentAccountButton.width Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 color: "transparent" + clip: true + Rectangle { + width: currentAccountButton.width + height: currentAccountButton.height + radius: 10 + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } } - } - } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.bottom: leftHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.right: leftHoverContainer.right + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.right: leftHoverContainer.right + anchors.bottom: leftHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + } - Column { - id: accountLabels - Layout.leftMargin: 0 - spacing: 4 - Layout.alignment: Qt.AlignLeft - //anchors.left: currentAvatarButton.right - Label { - id: syncStatusLabel - text: "Everything up to date" - color: "white" - font.pointSize: 9 - font.bold: true - } - Label { - id: currentUserLabel - text: "freddie@nextcloud.com" - color: "white" - font.pointSize: 8 - } - } + RowLayout { + id: accountControlRowLayout + height: currentAccountButton.height + width: currentAccountButton.width + + Image { + id: currentAccountAvatar + width: (trayWindowHeaderBackground.height - 12) + height: (trayWindowHeaderBackground.height - 12) + Layout.leftMargin: 6 + verticalAlignment: Qt.AlignCenter + //source: "file" + } - Image { - Layout.bottomMargin: 10 - Layout.alignment: Qt.AlignLeft | Qt.AlignBottom - verticalAlignment: Qt.AlignBottom - source: "qrc:///client/theme/white/caret-down.svg" + Column { + id: accountLabels + spacing: 4 + Layout.alignment: Qt.AlignLeft + Label { + id: syncStatusLabel + text: "Up to date" + color: "white" + font.pointSize: 9 + font.bold: true + } + Label { + id: currentUserLabel + text: "cloud.nextcloud.com" + color: "white" + font.pointSize: 8 + } + } + + /*Item { + Layout.preferredWidth: 6 + }*/ + + Image { + Layout.alignment: Qt.AlignLeft + verticalAlignment: Qt.AlignCenter + Layout.margins: 12 + //source: "../../theme/white/caret-down.svg" + source: "qrc:///client/theme/white/caret-down.svg" + } + } } Item { @@ -133,48 +178,48 @@ Window { Button { id: openLocalFolderButton - rightPadding: 2 - leftPadding: 2 Layout.alignment: Qt.AlignRight display: AbstractButton.IconOnly + Layout.preferredWidth: (trayWindowHeaderBackground.height) + Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - Layout.preferredWidth: (trayWindowHeaderBackground.height - 12) - Layout.preferredHeight: (trayWindowHeaderBackground.height - 12) + //icon.source: "../../theme/white/folder.svg" icon.source: "qrc:///client/theme/white/folder.svg" icon.color: "transparent" MouseArea { id: folderBtnMouseArea anchors.fill: parent + hoverEnabled: true onClicked: { } - } + } - background: - Rectangle { - color: "transparent" - } + background: + Rectangle { + color: folderBtnMouseArea.containsMouse ? "white" : "transparent" + opacity: 0.2 + } } Button { id: trayWindowTalkButton - rightPadding: 2 - leftPadding: 2 Layout.alignment: Qt.AlignRight display: AbstractButton.IconOnly - Layout.preferredWidth: (trayWindowHeaderBackground.height - 12) - Layout.preferredHeight: (trayWindowHeaderBackground.height - 12) + Layout.preferredWidth: (trayWindowHeaderBackground.height) + Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - Layout.margins: 4 + //icon.source: "../../theme/white/talk-app.svg" icon.source: "qrc:///client/theme/white/talk-app.svg" icon.color: "transparent" MouseArea { id: talkBtnMouseArea anchors.fill: parent + hoverEnabled: true onClicked: { } @@ -182,35 +227,78 @@ Window { background: Rectangle { - color: "transparent" + color: talkBtnMouseArea.containsMouse ? "white" : "transparent" + opacity: 0.2 } } Button { id: trayWindowAppsButton - rightPadding: 2 - leftPadding: 2 Layout.alignment: Qt.AlignRight display: AbstractButton.IconOnly - Layout.preferredWidth: (trayWindowHeaderBackground.height - 12) - Layout.preferredHeight: (trayWindowHeaderBackground.height - 12) + Layout.preferredWidth: (trayWindowHeaderBackground.height) + Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - Layout.margins: 4 + //icon.source: "../../theme/white/more-apps.svg" icon.source: "qrc:///client/theme/white/more-apps.svg" icon.color: "transparent" MouseArea { id: appsBtnMouseArea anchors.fill: parent + hoverEnabled: true onClicked: { } } background: + Item { + id: rightHoverContainer + height: trayWindowAppsButton.height + width: trayWindowAppsButton.width + Rectangle { + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } + Rectangle { + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + anchors.bottom: rightHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } Rectangle { - color: "transparent" + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + anchors.bottom: rightHoverContainer.bottom + anchors.right: rightHoverContainer.right + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } + Rectangle { + id: rightHoverContainerClipper + anchors.right: rightHoverContainer.right + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + color: "transparent" + clip: true + Rectangle { + width: trayWindowAppsButton.width + height: trayWindowAppsButton.height + anchors.right: rightHoverContainerClipper.right + radius: 10 + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } + } } } } From 06b251063eace31536a604cfb6dd674766f2cfa1 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 30 Oct 2019 20:47:21 +0100 Subject: [PATCH 006/120] Initial ListView design/modeling for activity list Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/traywindow.qml | 81 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/src/gui/traywindow.qml b/src/gui/traywindow.qml index c4beaeb5a559..828ec230fc66 100644 --- a/src/gui/traywindow.qml +++ b/src/gui/traywindow.qml @@ -128,7 +128,7 @@ Window { id: accountControlRowLayout height: currentAccountButton.height width: currentAccountButton.width - + spacing: 0 Image { id: currentAccountAvatar width: (trayWindowHeaderBackground.height - 12) @@ -142,6 +142,7 @@ Window { id: accountLabels spacing: 4 Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 6 Label { id: syncStatusLabel text: "Up to date" @@ -304,11 +305,81 @@ Window { } } // Rectangle trayWindowHeaderBackground - /*ListView { + ListView { + id: activityListView anchors.top: trayWindowHeaderBackground.bottom - Layout.fillWidth: true - Layout.fillHeight: true - }*/ + width: trayWindowBackground.width + height: trayWindowBackground.height - trayWindowHeaderBackground.height + clip: true + + model: ListModel {} + + delegate: RowLayout { + id: activityItem + width: activityListView.width + height: trayWindowHeaderLayout.height + spacing: 0 + Image { + id: activityIcon + Layout.leftMargin: 6 + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + verticalAlignment: Qt.AlignCenter + source: "qrc:///client/theme/black/state-sync.svg" + sourceSize.height: 48 + sourceSize.width: 48 + } + Column { + Layout.leftMargin: 6 + spacing: 4 + Layout.alignment: Qt.AlignLeft + Text { + id: activityTextTitle + text: name + font.pointSize: 9 + } + Text { + id: activityTextInfo + text: "Lorem ipsum dolor sit amet" + font.pointSize: 8 + } + } + Item { + id: activityItemFiller + Layout.fillWidth: true + } + Button { + Layout.preferredWidth: activityItem.height + Layout.preferredHeight: activityItem.height + Layout.alignment: Qt.AlignRight + flat: true + display: AbstractButton.IconOnly + icon.source: "qrc:///client/resources/files.svg" + icon.color: "transparent" + } + Button { + Layout.preferredWidth: activityItem.height + Layout.preferredHeight: activityItem.height + Layout.alignment: Qt.AlignRight + flat: true + display: AbstractButton.IconOnly + icon.source: "qrc:///client/resources/public.svg" + icon.color: "transparent" + } + } + + add: Transition { + NumberAnimation { properties: "y"; from: -60; duration: 150; easing.type: Easing.Linear } + } + + displaced: Transition { + NumberAnimation { properties: "y"; duration: 150; easing.type: Easing.Linear } + } + + focus: true + Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count }) + Keys.onTabPressed: model.remove(3) + } } // Rectangle trayWindowBackground } From c6ff66be79fa9616c981d950439f0e0b048c72c3 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 4 Nov 2019 12:48:07 +0100 Subject: [PATCH 007/120] Smoother deletions: Added animation for removeDisplaced including sequential opacity change and displacement animation Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/traywindow.qml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gui/traywindow.qml b/src/gui/traywindow.qml index 828ec230fc66..a49bf2b9dcb2 100644 --- a/src/gui/traywindow.qml +++ b/src/gui/traywindow.qml @@ -369,11 +369,22 @@ Window { } add: Transition { - NumberAnimation { properties: "y"; from: -60; duration: 150; easing.type: Easing.Linear } + NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } + } + + remove: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 } + } + + removeDisplaced: Transition { + SequentialAnimation { + PauseAnimation { duration: 100} + NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } + } } displaced: Transition { - NumberAnimation { properties: "y"; duration: 150; easing.type: Easing.Linear } + NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } } focus: true From 03c0d8ba5aede5dbf49ff3f504af8c0660bf455d Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 29 Nov 2019 17:06:35 +0100 Subject: [PATCH 008/120] Minor preps for c++ ActivityListModel impl. in QML --- src/gui/systray.cpp | 2 +- src/gui/traywindow.qml | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index a380be35d6e4..5006214494ca 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -31,7 +31,7 @@ namespace OCC { void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) { #ifdef USE_FDO_NOTIFICATIONS - if(QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) { + if (QDBusInterface(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE).isValid()) { QList args = QList() << APPLICATION_NAME << quint32(0) << APPLICATION_ICON_NAME << title << message << QStringList() << QVariantMap() << qint32(-1); QDBusMessage method = QDBusMessage::createMethodCall(NOTIFICATIONS_SERVICE, NOTIFICATIONS_PATH, NOTIFICATIONS_IFACE, "Notify"); diff --git a/src/gui/traywindow.qml b/src/gui/traywindow.qml index a49bf2b9dcb2..c008eb0f00e0 100644 --- a/src/gui/traywindow.qml +++ b/src/gui/traywindow.qml @@ -305,6 +305,10 @@ Window { } } // Rectangle trayWindowHeaderBackground + ListModel { + id: activityListModel + } + ListView { id: activityListView anchors.top: trayWindowHeaderBackground.bottom @@ -312,7 +316,7 @@ Window { height: trayWindowBackground.height - trayWindowHeaderBackground.height clip: true - model: ListModel {} + model: activityListModel delegate: RowLayout { id: activityItem @@ -388,8 +392,10 @@ Window { } focus: true - Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count }) - Keys.onTabPressed: model.remove(3) + + // For interactive ListView/Animation testing only + //Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count }) + //Keys.onTabPressed: model.remove(3) } } // Rectangle trayWindowBackground From 08d4c568869ba316aeb2f8913eb2231564aa91d5 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 2 Dec 2019 14:10:18 +0100 Subject: [PATCH 009/120] WIP: Extending systray class, transitioning towas independent ActivityModel inclusion --- src/gui/owncloudgui.cpp | 14 ++------------ src/gui/settingsdialog.cpp | 2 ++ src/gui/systray.cpp | 27 +++++++++++++++++++++++++++ src/gui/systray.h | 19 ++++++++++++++++++- 4 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index c18705fb875c..028e9e40bbfa 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -82,26 +82,16 @@ ownCloudGui::ownCloudGui(Application *parent) { _tray = new Systray(); _tray->setParent(this); - // for the beginning, set the offline icon until the account was verified _tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true, /*currently visible?*/ false)); - connect(_tray.data(), &QSystemTrayIcon::activated, - this, &ownCloudGui::slotTrayClicked); - setupActions(); setupContextMenu(); _tray->show(); - // QML System tray - QQmlEngine *engine = new QQmlEngine; - //engine.load(QUrl(QStringLiteral(":/qml/src/gui/systemtray.qml"))); - QQmlComponent systrayrtest(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/systemtray.qml"))); - QQmlContext *ctxt = engine->contextForObject(systrayrtest.create()); - // TODO hack to pass the icon to QML - //ctxt->setContextProperty("theme", QLatin1String("colored")); - //ctxt->setContextProperty("filename", "state-offline"); + connect(_tray.data(), &QSystemTrayIcon::activated, + this, &ownCloudGui::slotTrayClicked); ProgressDispatcher *pd = ProgressDispatcher::instance(); connect(pd, &ProgressDispatcher::progressInfo, this, diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index e91b96853be9..da40ffff1129 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -223,6 +223,8 @@ void SettingsDialog::accountAdded(AccountState *s) _activitySettings[s] = new ActivitySettings(s, this); + + // if this is not the first account, then before we continue to add more accounts we add a separator if(AccountManager::instance()->accounts().first().data() != s && AccountManager::instance()->accounts().size() >= 1){ diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 5006214494ca..0e318796a2ee 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -12,10 +12,14 @@ * for more details. */ +#include "accountmanager.h" #include "systray.h" #include "theme.h" #include "config.h" +#include +#include + #ifdef USE_FDO_NOTIFICATIONS #include #include @@ -28,6 +32,29 @@ namespace OCC { +Systray::Systray() // TODO: make singleton, provide ::instance() + : _currentAccount(nullptr) + , _trayContext(nullptr) +{ + _currentAccount = AccountManager::instance()->accounts().first(); + + QQmlEngine *engine = new QQmlEngine; + QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/systemtray.qml"))); + _trayContext = engine->contextForObject(systray.create()); + + // TODO: hack to pass the icon to QML + //ctxt->setContextProperty("theme", QLatin1String("colored")); + //ctxt->setContextProperty("filename", "state-offline"); +} + +Systray::~Systray() +{ +} + +void Systray::slotChangeActivityModel() +{ +} + void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) { #ifdef USE_FDO_NOTIFICATIONS diff --git a/src/gui/systray.h b/src/gui/systray.h index 523d4b689cad..eb20a000efb3 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -16,6 +16,9 @@ #define SYSTRAY_H #include +#include + +#include "accountmanager.h" class QIcon; @@ -26,16 +29,30 @@ bool canOsXSendUserNotification(); void sendOsXUserNotification(const QString &title, const QString &message); #endif +namespace Ui { + class Systray; +} + /** * @brief The Systray class * @ingroup gui */ -class Systray : public QSystemTrayIcon +class Systray + : public QSystemTrayIcon { Q_OBJECT public: + explicit Systray(); + ~Systray(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); + +private slots: + void slotChangeActivityModel(); + +private: + AccountStatePtr _currentAccount; + QQmlContext *_trayContext; }; } // namespace OCC From 478281d8535dea8611bd01502ab83f3e9165bec1 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 2 Dec 2019 22:45:14 +0100 Subject: [PATCH 010/120] Restructures and refactorings: New systray class, handling of current user selection, integration of user information in tray window header --- client.qrc | 4 +- src/common/utility.h | 2 + src/gui/CMakeLists.txt | 4 +- src/gui/owncloudgui.cpp | 1 + src/gui/systray.cpp | 49 +++++++++++++++++++-- src/gui/systray.h | 8 +++- src/gui/{systemtray.qml => tray/init.qml} | 2 +- src/gui/{traywindow.qml => tray/window.qml} | 11 ++--- 8 files changed, 66 insertions(+), 15 deletions(-) rename src/gui/{systemtray.qml => tray/init.qml} (97%) rename src/gui/{traywindow.qml => tray/window.qml} (97%) diff --git a/client.qrc b/client.qrc index a5494f5428db..2eb351d00aa3 100644 --- a/client.qrc +++ b/client.qrc @@ -32,7 +32,7 @@ - src/gui/systemtray.qml - src/gui/traywindow.qml + src/gui/tray/init.qml + src/gui/tray/window.qml diff --git a/src/common/utility.h b/src/common/utility.h index 803f690c218d..e8a2435466ce 100644 --- a/src/common/utility.h +++ b/src/common/utility.h @@ -20,6 +20,7 @@ #ifndef UTILITY_H #define UTILITY_H + #include "ocsynclib.h" #include #include @@ -29,6 +30,7 @@ #include #include #include +#include #include #include diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 8b0214d39c28..19b95cd44638 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -32,11 +32,11 @@ set(client_UI_SRCS shareusergroupwidget.ui shareuserline.ui sslerrordialog.ui - systemtray.qml - traywindow.qml addcertificatedialog.ui proxyauthdialog.ui mnemonicdialog.ui + tray/init.qml + tray/window.qml wizard/owncloudadvancedsetuppage.ui wizard/owncloudconnectionmethoddialog.ui wizard/owncloudhttpcredspage.ui diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 028e9e40bbfa..3a4c8651b8f9 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -81,6 +81,7 @@ ownCloudGui::ownCloudGui(Application *parent) , _app(parent) { _tray = new Systray(); + //qmlRegisterType("nc.desktop.systray.backend", 1, 0, "Systray"); _tray->setParent(this); // for the beginning, set the offline icon until the account was verified _tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true, /*currently visible?*/ false)); diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 0e318796a2ee..bba08c524896 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -14,6 +14,7 @@ #include "accountmanager.h" #include "systray.h" +#include "../common/utility.h" #include "theme.h" #include "config.h" @@ -36,23 +37,63 @@ Systray::Systray() // TODO: make singleton, provide ::instance() : _currentAccount(nullptr) , _trayContext(nullptr) { - _currentAccount = AccountManager::instance()->accounts().first(); - + // Create QML tray engine, build component, set C++ backend context used in window.qml QQmlEngine *engine = new QQmlEngine; - QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/systemtray.qml"))); + QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/init.qml"))); _trayContext = engine->contextForObject(systray.create()); + systray.engine()->rootContext()->setContextProperty("systrayBackend", this); + // TODO: hack to pass the icon to QML //ctxt->setContextProperty("theme", QLatin1String("colored")); //ctxt->setContextProperty("filename", "state-offline"); + + if (!AccountManager::instance()->accounts().isEmpty()) { + + slotChangeActivityModel(AccountManager::instance()->accounts().first()); + } + + //_trayContext->setContextProperty("serverTest", QVariant("Test")); + //connect(AccountManager::instance(), &AccountManager::accountAdded, + // this, &Systray::slotChangeActivityModel); } Systray::~Systray() { } -void Systray::slotChangeActivityModel() +Q_INVOKABLE QIcon Systray::currentAvatar() const +{ + QImage userAvatarImg = _currentAccount->account()->avatar(); + QIcon userAvatar(QPixmap::fromImage(userAvatarImg)); + return userAvatar; +} + +Q_INVOKABLE QString Systray::currentAccountServer() const +{ + QString serverUrl = _currentAccount->account()->url().toString(); + if (serverUrl.length() > 25) { + serverUrl.truncate(23); + serverUrl.append(QByteArray("...")); + } + return serverUrl; +} + +Q_INVOKABLE QString Systray::currentAccountUser() const +{ + QString userName = _currentAccount->account()->davDisplayName(); + if (userName.length() > 19) { + userName.truncate(17); + userName.append(QByteArray("...")); + } + return userName; +} + +void Systray::slotChangeActivityModel(const AccountStatePtr account) { + _currentAccount = account; + emit currentUserChanged(); + bool test; } void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) diff --git a/src/gui/systray.h b/src/gui/systray.h index eb20a000efb3..d9eedd0dec4a 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -46,9 +46,15 @@ class Systray ~Systray(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); + Q_INVOKABLE QIcon currentAvatar() const; + Q_INVOKABLE QString currentAccountServer() const; + Q_INVOKABLE QString currentAccountUser() const; + +signals: + void currentUserChanged(); private slots: - void slotChangeActivityModel(); + void slotChangeActivityModel(const AccountStatePtr account); private: AccountStatePtr _currentAccount; diff --git a/src/gui/systemtray.qml b/src/gui/tray/init.qml similarity index 97% rename from src/gui/systemtray.qml rename to src/gui/tray/init.qml index 2d6c88d8ddfd..c918fd9805f3 100644 --- a/src/gui/systemtray.qml +++ b/src/gui/tray/init.qml @@ -11,7 +11,7 @@ SystemTrayIcon { } onActivated: { - var component = Qt.createComponent("qrc:/qml/src/gui/traywindow.qml") + var component = Qt.createComponent("qrc:/qml/src/gui/tray/window.qml") var win = component.createObject() win.show() win.raise() diff --git a/src/gui/traywindow.qml b/src/gui/tray/window.qml similarity index 97% rename from src/gui/traywindow.qml rename to src/gui/tray/window.qml index c008eb0f00e0..1275b68de115 100644 --- a/src/gui/traywindow.qml +++ b/src/gui/tray/window.qml @@ -5,6 +5,7 @@ import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 Window { + id: trayWindow visible: true width: 400 @@ -135,7 +136,7 @@ Window { height: (trayWindowHeaderBackground.height - 12) Layout.leftMargin: 6 verticalAlignment: Qt.AlignCenter - //source: "file" + source: "image://avatars/current.png" } Column { @@ -144,15 +145,15 @@ Window { Layout.alignment: Qt.AlignLeft Layout.leftMargin: 6 Label { - id: syncStatusLabel - text: "Up to date" + id: currentAccountUser + text: systrayBackend.currentAccountUser() color: "white" font.pointSize: 9 font.bold: true } Label { - id: currentUserLabel - text: "cloud.nextcloud.com" + id: currentAccountServer + text: systrayBackend.currentAccountServer() color: "white" font.pointSize: 8 } From c21e4f5982d8cc2c08e695b87c57dc235fb328d8 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 2 Dec 2019 22:48:39 +0100 Subject: [PATCH 011/120] Clean up test variable --- src/gui/systray.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index bba08c524896..5c70956b70bf 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -93,7 +93,6 @@ void Systray::slotChangeActivityModel(const AccountStatePtr account) { _currentAccount = account; emit currentUserChanged(); - bool test; } void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) From dac1c9b41394a1eaaeb32f5ce64d0276ac240830 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 3 Dec 2019 07:24:59 +0100 Subject: [PATCH 012/120] Avatar, full user name and server address now reflected in QML gui, provided by C++ backend Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/common/utility.cpp | 14 +++++++------- src/gui/systray.cpp | 16 ++++++++++++---- src/gui/systray.h | 2 +- src/gui/tray/window.qml | 4 +++- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/common/utility.cpp b/src/common/utility.cpp index c0a22177f0cb..8229cab0579e 100644 --- a/src/common/utility.cpp +++ b/src/common/utility.cpp @@ -255,7 +255,7 @@ void Utility::usleep(int usec) } // This can be overriden from the tests -OCSYNC_EXPORT bool fsCasePreserving_override = []()-> bool { +OCSYNC_EXPORT bool fsCasePreserving_override = []() -> bool { QByteArray env = qgetenv("OWNCLOUD_TEST_CASE_PRESERVING"); if (!env.isEmpty()) return env.toInt(); @@ -362,12 +362,12 @@ QString Utility::fileNameForGuiUse(const QString &fName) QByteArray Utility::normalizeEtag(QByteArray etag) { /* strip "XXXX-gzip" */ - if(etag.startsWith('"') && etag.endsWith("-gzip\"")) { + if (etag.startsWith('"') && etag.endsWith("-gzip\"")) { etag.chop(6); etag.remove(0, 1); } /* strip trailing -gzip */ - if(etag.endsWith("-gzip")) { + if (etag.endsWith("-gzip")) { etag.chop(5); } /* strip normal quotes */ @@ -400,7 +400,7 @@ void Utility::crash() // without compiler warnings about possible truncation uint Utility::convertSizeToUint(size_t &convertVar) { - if( convertVar > UINT_MAX ) { + if (convertVar > UINT_MAX) { //throw std::bad_cast(); convertVar = UINT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD } @@ -409,7 +409,7 @@ uint Utility::convertSizeToUint(size_t &convertVar) uint Utility::convertSizeToInt(size_t &convertVar) { - if( convertVar > INT_MAX ) { + if (convertVar > INT_MAX) { //throw std::bad_cast(); convertVar = INT_MAX; // intentionally default to wrong value here to not crash: exception handling TBD } @@ -465,7 +465,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from) if (floor(secs / 3600.0) > 0) { int hours = floor(secs / 3600.0); - if(hours == 1){ + if (hours == 1) { return (QObject::tr("%n hour ago", "", hours)); } else { return (QObject::tr("%n hours ago", "", hours)); @@ -480,7 +480,7 @@ QString Utility::timeAgoInWords(const QDateTime &dt, const QDateTime &from) return QObject::tr("Less than a minute ago"); } - } else if(minutes == 1){ + } else if (minutes == 1) { return (QObject::tr("%n minute ago", "", minutes)); } else { return (QObject::tr("%n minutes ago", "", minutes)); diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 5c70956b70bf..4a74bf6e2e74 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -62,11 +62,19 @@ Systray::~Systray() { } -Q_INVOKABLE QIcon Systray::currentAvatar() const +// TODO: Lots of memory shifting here +// Probably OK because the avatar is not changing a trillion times per second +// But should consider moving to a generic ImageProvider helper class for img/QML-provision +Q_INVOKABLE QString Systray::currentAvatar() const { - QImage userAvatarImg = _currentAccount->account()->avatar(); - QIcon userAvatar(QPixmap::fromImage(userAvatarImg)); - return userAvatar; + QByteArray bArray; + QBuffer buffer(&bArray); + buffer.open(QIODevice::WriteOnly); + AvatarJob::makeCircularAvatar(_currentAccount->account()->avatar()).save(&buffer, "PNG"); + + QString img("data:image/png;base64,"); + img.append(QString::fromLatin1(bArray.toBase64().data())); + return img; } Q_INVOKABLE QString Systray::currentAccountServer() const diff --git a/src/gui/systray.h b/src/gui/systray.h index d9eedd0dec4a..400fcf3ae0b4 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -46,7 +46,7 @@ class Systray ~Systray(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); - Q_INVOKABLE QIcon currentAvatar() const; + Q_INVOKABLE QString currentAvatar() const; Q_INVOKABLE QString currentAccountServer() const; Q_INVOKABLE QString currentAccountUser() const; diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 1275b68de115..5eef8bf2515d 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -136,7 +136,9 @@ Window { height: (trayWindowHeaderBackground.height - 12) Layout.leftMargin: 6 verticalAlignment: Qt.AlignCenter - source: "image://avatars/current.png" + source: systrayBackend.currentAvatar() + Layout.preferredHeight: (trayWindowHeaderBackground.height -12) + Layout.preferredWidth: (trayWindowHeaderBackground.height -12) } Column { From 53abf5a316c7469cfc8d5e13daa9ce507586dcb9 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 3 Dec 2019 17:25:11 +0100 Subject: [PATCH 013/120] WIP: Building User/MenuModel backend for tray menu Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- client.qrc | 1 + src/gui/CMakeLists.txt | 2 + src/gui/systray.cpp | 11 ++- src/gui/systray.h | 4 + src/gui/tray/UserLine.qml | 80 +++++++++++++++++++ src/gui/tray/menumodel.cpp | 78 ++++++++++++++++++ src/gui/tray/menumodel.h | 52 ++++++++++++ src/gui/tray/window.qml | 160 +++++++++++++++++++++---------------- 8 files changed, 316 insertions(+), 72 deletions(-) create mode 100644 src/gui/tray/UserLine.qml create mode 100644 src/gui/tray/menumodel.cpp create mode 100644 src/gui/tray/menumodel.h diff --git a/client.qrc b/client.qrc index 2eb351d00aa3..0f3b69f5b2ef 100644 --- a/client.qrc +++ b/client.qrc @@ -34,5 +34,6 @@ src/gui/tray/init.qml src/gui/tray/window.qml + src/gui/tray/UserLine.qml diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 19b95cd44638..99e2d223da3e 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -37,6 +37,7 @@ set(client_UI_SRCS mnemonicdialog.ui tray/init.qml tray/window.qml + tray/UserLine.qml wizard/owncloudadvancedsetuppage.ui wizard/owncloudconnectionmethoddialog.ui wizard/owncloudhttpcredspage.ui @@ -107,6 +108,7 @@ set(client_SRCS elidedlabel.cpp iconjob.cpp remotewipe.cpp + tray/menumodel.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp creds/oauth.cpp diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 4a74bf6e2e74..5e73f23bbb5f 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -14,9 +14,9 @@ #include "accountmanager.h" #include "systray.h" -#include "../common/utility.h" #include "theme.h" #include "config.h" +#include "tray/menumodel.h" #include #include @@ -36,13 +36,15 @@ namespace OCC { Systray::Systray() // TODO: make singleton, provide ::instance() : _currentAccount(nullptr) , _trayContext(nullptr) + , _accountMenuModel(nullptr) { // Create QML tray engine, build component, set C++ backend context used in window.qml QQmlEngine *engine = new QQmlEngine; QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/init.qml"))); _trayContext = engine->contextForObject(systray.create()); - systray.engine()->rootContext()->setContextProperty("systrayBackend", this); + _accountMenuModel = new UserModel(); + systray.engine()->rootContext()->setContextProperty("systrayBackend", _accountMenuModel); // TODO: hack to pass the icon to QML //ctxt->setContextProperty("theme", QLatin1String("colored")); @@ -62,6 +64,11 @@ Systray::~Systray() { } +Q_INVOKABLE int Systray::numAccounts() const +{ + return AccountManager::instance()->accounts().size(); +} + // TODO: Lots of memory shifting here // Probably OK because the avatar is not changing a trillion times per second // But should consider moving to a generic ImageProvider helper class for img/QML-provision diff --git a/src/gui/systray.h b/src/gui/systray.h index 400fcf3ae0b4..2c817e8d62c3 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -19,6 +19,7 @@ #include #include "accountmanager.h" +#include "tray/menumodel.h" class QIcon; @@ -46,9 +47,11 @@ class Systray ~Systray(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); + Q_INVOKABLE QString currentAvatar() const; Q_INVOKABLE QString currentAccountServer() const; Q_INVOKABLE QString currentAccountUser() const; + Q_INVOKABLE int numAccounts() const; signals: void currentUserChanged(); @@ -59,6 +62,7 @@ private slots: private: AccountStatePtr _currentAccount; QQmlContext *_trayContext; + UserModel *_accountMenuModel; }; } // namespace OCC diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml new file mode 100644 index 000000000000..f2b4e4a9e523 --- /dev/null +++ b/src/gui/tray/UserLine.qml @@ -0,0 +1,80 @@ +import QtQuick 2.9 +import QtQuick.Window 2.2 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 + +MenuItem { + + id: userLine + visible: true + width: 220 + height: 60 + //color: "transparent" + + Rectangle { + id: userLineBackground + anchors.fill: parent + color: "transparent" + + RowLayout { + id: userLineLayout + spacing: 0 + anchors.fill: parent + + Button { + id: currentAccountButton + Layout.preferredWidth: 220 + Layout.preferredHeight: (userLineBackground.height) + display: AbstractButton.IconOnly + flat: true + + MouseArea { + id: accountBtnMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: + { + // + } + } + + RowLayout { + id: accountControlRowLayout + height: currentAccountButton.height + width: currentAccountButton.width + spacing: 0 + Image { + id: currentAccountAvatar + width: (userLineBackground.height - 12) + height: (userLineBackground.height - 12) + Layout.leftMargin: 6 + verticalAlignment: Qt.AlignCenter + source: avatar + Layout.preferredHeight: (userLineBackground.height -12) + Layout.preferredWidth: (userLineBackground.height -12) + } + + Column { + id: accountLabels + spacing: 4 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 6 + Label { + id: currentAccountUser + text: name + color: "black" + font.pointSize: 9 + font.bold: true + } + Label { + id: currentAccountServer + text: server + color: "black" + font.pointSize: 8 + } + } + } + } + } + } // Rectangle userLineBackground +} // MenuItem userLine diff --git a/src/gui/tray/menumodel.cpp b/src/gui/tray/menumodel.cpp new file mode 100644 index 000000000000..ebe771058d06 --- /dev/null +++ b/src/gui/tray/menumodel.cpp @@ -0,0 +1,78 @@ +#include "accountmanager.h" +#include "menumodel.h" + +namespace OCC { + +User::User(const QString &name, const QString &server, const QString &avatar) + : _name(name) + , _server(server) + , _avatar(avatar) +{ +} + +QString User::name() const +{ + return _name; +} + +QString User::server() const +{ + return _server; +} + +QString User::avatar() const +{ + return _avatar; +} + +UserModel::UserModel(QObject *parent) + : QAbstractListModel() +{ + for (size_t i = 0; i < AccountManager::instance()->accounts().size(); i++) { + addUser(User("test", "test", "test")); + } +} + +UserModel::~UserModel() +{ +} + +void UserModel::addUser(const User &user) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _users << user; + endInsertRows(); +} + +int UserModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return _users.count(); +} + +QVariant UserModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= _users.count()) { + return QVariant(); + } + + const User &user = _users[index.row()]; + if (role == NameRole) { + return user.name(); + } else if (role == ServerRole) { + return user.server(); + } else if (role == AvatarRole) { + return user.avatar(); + } + return QVariant(); +} + +QHash UserModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "name"; + roles[ServerRole] = "server"; + roles[AvatarRole] = "avatar"; + return roles; +} +} \ No newline at end of file diff --git a/src/gui/tray/menumodel.h b/src/gui/tray/menumodel.h new file mode 100644 index 000000000000..b0e557ee7001 --- /dev/null +++ b/src/gui/tray/menumodel.h @@ -0,0 +1,52 @@ +#ifndef USERMODEL_H +#define USERMODEL_H + +#include +#include + +namespace OCC { + +class User +{ +public: + User(const QString &name, const QString &server, const QString &avatar); + + QString name() const; + QString server() const; + QString avatar() const; + +private: + QString _name; + QString _server; + QString _avatar; +}; + +class UserModel : public QAbstractListModel +{ + Q_OBJECT + +public: + UserModel(QObject *parent = 0); + virtual ~UserModel(); + + void addUser(const User &user); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + enum UserRoles { + NameRole = Qt::UserRole + 1, + ServerRole, + AvatarRole + }; + +protected: + QHash roleNames() const; + +private: + QList _users; +}; + +} +#endif // USERMODEL_H \ No newline at end of file diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 5eef8bf2515d..a816e3e8f26e 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -1,3 +1,4 @@ +import QtQml 2.2 import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.2 @@ -14,11 +15,11 @@ Window { flags: Qt.FramelessWindowHint Component.onCompleted: { - /* desktopAvailableWidth and Height doesn't include the system tray bar + /* desktopAvailableWidth and Height doesn't include the system tray bar but breaks application anyway on windows when using multi monitor setup, will look for a better solution later, for now just get this thing complete */ - //setX(Screen.desktopAvailableWidth - width); - //setY(Screen.desktopAvailableHeight + height); + //setX(Screen.desktopAvailableWidth - width); + //setY(Screen.desktopAvailableHeight + height); } Rectangle { @@ -61,21 +62,40 @@ Window { hoverEnabled: true onClicked: { - accountMenu.popup() + accountMenu.open() } Menu { id: accountMenu - background: - Rectangle { - id: menubackground - implicitWidth: 200 - implicitHeight: 40 - anchors.fill: parent - radius: 10 + x: (currentAccountButton.x + 2) + y: (currentAccountButton.y + currentAccountButton.height + 2) + width: (currentAccountButton.width - 4) + + background: Rectangle { + border.color: "#0082c9" + radius: 4 + } + + /*ListView { + model: systrayBackend + delegate: UserLine { + text: name } + }*/ + + Instantiator { + model: systrayBackend + delegate: UserLine { + } + onObjectAdded: accountMenu.insertItem(index, object) + onObjectRemoved: accountMenu.removeItem(object) + } + + MenuSeparator {} - MenuItem { text: "test" } + MenuItem { text: "Login/Logout" } + MenuItem { text: "Add Account" } + MenuItem { text: "Remove Account" } } } @@ -84,46 +104,46 @@ Window { id: leftHoverContainer height: currentAccountButton.height width: currentAccountButton.width + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + color: "transparent" + clip: true Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - color: "transparent" - clip: true - Rectangle { - width: currentAccountButton.width - height: currentAccountButton.height - radius: 10 - color: "white" - opacity: 0.2 - visible: accountBtnMouseArea.containsMouse - } - } - Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - anchors.bottom: leftHoverContainer.bottom - color: "white" - opacity: 0.2 - visible: accountBtnMouseArea.containsMouse - } - Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - anchors.right: leftHoverContainer.right - color: "white" - opacity: 0.2 - visible: accountBtnMouseArea.containsMouse - } - Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - anchors.right: leftHoverContainer.right - anchors.bottom: leftHoverContainer.bottom + width: currentAccountButton.width + height: currentAccountButton.height + radius: 10 color: "white" opacity: 0.2 visible: accountBtnMouseArea.containsMouse } } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.bottom: leftHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.right: leftHoverContainer.right + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.right: leftHoverContainer.right + anchors.bottom: leftHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + } RowLayout { id: accountControlRowLayout @@ -202,10 +222,10 @@ Window { } background: - Rectangle { + Rectangle { color: folderBtnMouseArea.containsMouse ? "white" : "transparent" opacity: 0.2 - } + } } Button { @@ -227,13 +247,13 @@ Window { onClicked: { } - } + } - background: + background: Rectangle { - color: talkBtnMouseArea.containsMouse ? "white" : "transparent" - opacity: 0.2 - } + color: talkBtnMouseArea.containsMouse ? "white" : "transparent" + opacity: 0.2 + } } Button { @@ -255,9 +275,9 @@ Window { onClicked: { } - } + } - background: + background: Item { id: rightHoverContainer height: trayWindowAppsButton.height @@ -286,24 +306,24 @@ Window { opacity: 0.2 visible: appsBtnMouseArea.containsMouse } + Rectangle { + id: rightHoverContainerClipper + anchors.right: rightHoverContainer.right + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + color: "transparent" + clip: true Rectangle { - id: rightHoverContainerClipper - anchors.right: rightHoverContainer.right - width: trayWindowAppsButton.width / 2 - height: trayWindowAppsButton.height / 2 - color: "transparent" - clip: true - Rectangle { - width: trayWindowAppsButton.width - height: trayWindowAppsButton.height - anchors.right: rightHoverContainerClipper.right - radius: 10 - color: "white" - opacity: 0.2 - visible: appsBtnMouseArea.containsMouse - } + width: trayWindowAppsButton.width + height: trayWindowAppsButton.height + anchors.right: rightHoverContainerClipper.right + radius: 10 + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse } } + } } } } // Rectangle trayWindowHeaderBackground From 44783992823fd04790da7987ecc81cbfb0d8c0c1 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 3 Dec 2019 18:50:34 +0100 Subject: [PATCH 014/120] More stuff regarding tray/UserModel.cpp Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/CMakeLists.txt | 2 +- src/gui/systray.cpp | 43 +-------- src/gui/systray.h | 7 +- src/gui/tray/UserModel.cpp | 108 ++++++++++++++++++++++ src/gui/tray/{menumodel.h => UserModel.h} | 13 ++- src/gui/tray/menumodel.cpp | 78 ---------------- src/gui/tray/window.qml | 16 +--- 7 files changed, 124 insertions(+), 143 deletions(-) create mode 100644 src/gui/tray/UserModel.cpp rename src/gui/tray/{menumodel.h => UserModel.h} (74%) delete mode 100644 src/gui/tray/menumodel.cpp diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 99e2d223da3e..a1b7fc2490f6 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -108,7 +108,7 @@ set(client_SRCS elidedlabel.cpp iconjob.cpp remotewipe.cpp - tray/menumodel.cpp + tray/UserModel.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp creds/oauth.cpp diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 5e73f23bbb5f..d34f300bfda3 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -16,7 +16,7 @@ #include "systray.h" #include "theme.h" #include "config.h" -#include "tray/menumodel.h" +#include "tray/UserModel.h" #include #include @@ -55,7 +55,6 @@ Systray::Systray() // TODO: make singleton, provide ::instance() slotChangeActivityModel(AccountManager::instance()->accounts().first()); } - //_trayContext->setContextProperty("serverTest", QVariant("Test")); //connect(AccountManager::instance(), &AccountManager::accountAdded, // this, &Systray::slotChangeActivityModel); } @@ -64,46 +63,6 @@ Systray::~Systray() { } -Q_INVOKABLE int Systray::numAccounts() const -{ - return AccountManager::instance()->accounts().size(); -} - -// TODO: Lots of memory shifting here -// Probably OK because the avatar is not changing a trillion times per second -// But should consider moving to a generic ImageProvider helper class for img/QML-provision -Q_INVOKABLE QString Systray::currentAvatar() const -{ - QByteArray bArray; - QBuffer buffer(&bArray); - buffer.open(QIODevice::WriteOnly); - AvatarJob::makeCircularAvatar(_currentAccount->account()->avatar()).save(&buffer, "PNG"); - - QString img("data:image/png;base64,"); - img.append(QString::fromLatin1(bArray.toBase64().data())); - return img; -} - -Q_INVOKABLE QString Systray::currentAccountServer() const -{ - QString serverUrl = _currentAccount->account()->url().toString(); - if (serverUrl.length() > 25) { - serverUrl.truncate(23); - serverUrl.append(QByteArray("...")); - } - return serverUrl; -} - -Q_INVOKABLE QString Systray::currentAccountUser() const -{ - QString userName = _currentAccount->account()->davDisplayName(); - if (userName.length() > 19) { - userName.truncate(17); - userName.append(QByteArray("...")); - } - return userName; -} - void Systray::slotChangeActivityModel(const AccountStatePtr account) { _currentAccount = account; diff --git a/src/gui/systray.h b/src/gui/systray.h index 2c817e8d62c3..2a0e22db7f6f 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -19,7 +19,7 @@ #include #include "accountmanager.h" -#include "tray/menumodel.h" +#include "tray/UserModel.h" class QIcon; @@ -48,11 +48,6 @@ class Systray void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); - Q_INVOKABLE QString currentAvatar() const; - Q_INVOKABLE QString currentAccountServer() const; - Q_INVOKABLE QString currentAccountUser() const; - Q_INVOKABLE int numAccounts() const; - signals: void currentUserChanged(); diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp new file mode 100644 index 000000000000..841c2e4be726 --- /dev/null +++ b/src/gui/tray/UserModel.cpp @@ -0,0 +1,108 @@ +#include "accountmanager.h" +#include "UserModel.h" + +namespace OCC { + +User::User(const AccountStatePtr &account) + : _account(account) +{ +} + +QString User::name() const +{ + return _account->account()->davDisplayName(); +} + +QString User::server() const +{ + QString serverUrl = _account->account()->url().toString(); + serverUrl.replace(QLatin1String("https://"), QLatin1String("")); + serverUrl.replace(QLatin1String("http://"), QLatin1String("")); + return serverUrl; +} + +// TODO: Lots of memory shifting here +// Probably OK because the avatar is not changing a trillion times per second +// But should consider moving to a generic ImageProvider helper class for img/QML-provision +QString User::avatar() const +{ + QByteArray bArray; + QBuffer buffer(&bArray); + buffer.open(QIODevice::WriteOnly); + AvatarJob::makeCircularAvatar(_account->account()->avatar()).save(&buffer, "PNG"); + + QString img("data:image/png;base64,"); + img.append(QString::fromLatin1(bArray.toBase64().data())); + return img; +} + +/*-------------------------------------------------------------------------------------*/ + +UserModel::UserModel(QObject *parent) + : QAbstractListModel() + , _currentUser() +{ + for (int i = 0; i < AccountManager::instance()->accounts().size(); i++) { + addUser(User(AccountManager::instance()->accounts().at(i))); + } + _currentUser = &_users.first(); +} + +UserModel::~UserModel() +{ +} + +Q_INVOKABLE QString UserModel::currentUserAvatar() +{ + return _currentUser->avatar(); +} + +Q_INVOKABLE QString UserModel::currentUserName() +{ + return _currentUser->name(); +} + +Q_INVOKABLE QString UserModel::currentUserServer() +{ + return _currentUser->server(); +} + +void UserModel::addUser(const User &user) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _users << user; + endInsertRows(); +} + +int UserModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return _users.count(); +} + +QVariant UserModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= _users.count()) { + return QVariant(); + } + + const User &user = _users[index.row()]; + if (role == NameRole) { + return user.name(); + } else if (role == ServerRole) { + return user.server(); + } else if (role == AvatarRole) { + return user.avatar(); + } + return QVariant(); +} + +QHash UserModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "name"; + roles[ServerRole] = "server"; + roles[AvatarRole] = "avatar"; + return roles; +} +} \ No newline at end of file diff --git a/src/gui/tray/menumodel.h b/src/gui/tray/UserModel.h similarity index 74% rename from src/gui/tray/menumodel.h rename to src/gui/tray/UserModel.h index b0e557ee7001..0e6b381a6d07 100644 --- a/src/gui/tray/menumodel.h +++ b/src/gui/tray/UserModel.h @@ -4,21 +4,21 @@ #include #include +#include "accountmanager.h" + namespace OCC { class User { public: - User(const QString &name, const QString &server, const QString &avatar); + User(const AccountStatePtr &account); QString name() const; QString server() const; QString avatar() const; private: - QString _name; - QString _server; - QString _avatar; + AccountStatePtr _account; }; class UserModel : public QAbstractListModel @@ -35,6 +35,10 @@ class UserModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Q_INVOKABLE QString currentUserAvatar(); + Q_INVOKABLE QString currentUserName(); + Q_INVOKABLE QString currentUserServer(); + enum UserRoles { NameRole = Qt::UserRole + 1, ServerRole, @@ -46,6 +50,7 @@ class UserModel : public QAbstractListModel private: QList _users; + User *_currentUser; }; } diff --git a/src/gui/tray/menumodel.cpp b/src/gui/tray/menumodel.cpp deleted file mode 100644 index ebe771058d06..000000000000 --- a/src/gui/tray/menumodel.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "accountmanager.h" -#include "menumodel.h" - -namespace OCC { - -User::User(const QString &name, const QString &server, const QString &avatar) - : _name(name) - , _server(server) - , _avatar(avatar) -{ -} - -QString User::name() const -{ - return _name; -} - -QString User::server() const -{ - return _server; -} - -QString User::avatar() const -{ - return _avatar; -} - -UserModel::UserModel(QObject *parent) - : QAbstractListModel() -{ - for (size_t i = 0; i < AccountManager::instance()->accounts().size(); i++) { - addUser(User("test", "test", "test")); - } -} - -UserModel::~UserModel() -{ -} - -void UserModel::addUser(const User &user) -{ - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _users << user; - endInsertRows(); -} - -int UserModel::rowCount(const QModelIndex &parent) const -{ - Q_UNUSED(parent); - return _users.count(); -} - -QVariant UserModel::data(const QModelIndex &index, int role) const -{ - if (index.row() < 0 || index.row() >= _users.count()) { - return QVariant(); - } - - const User &user = _users[index.row()]; - if (role == NameRole) { - return user.name(); - } else if (role == ServerRole) { - return user.server(); - } else if (role == AvatarRole) { - return user.avatar(); - } - return QVariant(); -} - -QHash UserModel::roleNames() const -{ - QHash roles; - roles[NameRole] = "name"; - roles[ServerRole] = "server"; - roles[AvatarRole] = "avatar"; - return roles; -} -} \ No newline at end of file diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index a816e3e8f26e..6d09cc64231d 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -76,17 +76,9 @@ Window { radius: 4 } - /*ListView { - model: systrayBackend - delegate: UserLine { - text: name - } - }*/ - Instantiator { model: systrayBackend - delegate: UserLine { - } + delegate: UserLine {} onObjectAdded: accountMenu.insertItem(index, object) onObjectRemoved: accountMenu.removeItem(object) } @@ -156,7 +148,7 @@ Window { height: (trayWindowHeaderBackground.height - 12) Layout.leftMargin: 6 verticalAlignment: Qt.AlignCenter - source: systrayBackend.currentAvatar() + source: systrayBackend.currentUserAvatar() Layout.preferredHeight: (trayWindowHeaderBackground.height -12) Layout.preferredWidth: (trayWindowHeaderBackground.height -12) } @@ -168,14 +160,14 @@ Window { Layout.leftMargin: 6 Label { id: currentAccountUser - text: systrayBackend.currentAccountUser() + text: systrayBackend.currentUserName() color: "white" font.pointSize: 9 font.bold: true } Label { id: currentAccountServer - text: systrayBackend.currentAccountServer() + text: systrayBackend.currentUserServer() color: "white" font.pointSize: 8 } From 57084fbd3e43bf4a806dfe1162854a2949c9785c Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 3 Dec 2019 22:15:25 +0100 Subject: [PATCH 015/120] WIP: working UserModel.cpp and account menu Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- client.qrc | 1 + src/gui/tray/UserLine.qml | 14 +++++--- src/gui/tray/UserModel.cpp | 72 +++++++++++++++++++++++++++++++++++--- src/gui/tray/UserModel.h | 10 ++++++ src/gui/tray/window.qml | 25 +++++++++---- 5 files changed, 106 insertions(+), 16 deletions(-) diff --git a/client.qrc b/client.qrc index 0f3b69f5b2ef..21acbacf4ab9 100644 --- a/client.qrc +++ b/client.qrc @@ -11,6 +11,7 @@ resources/lock-https.png resources/lock-https@2x.png resources/account.png + resources/account.svg resources/more.svg resources/delete.png resources/close.svg diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index f2b4e4a9e523..3b6ff0b0f667 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -13,6 +13,7 @@ MenuItem { Rectangle { id: userLineBackground + height: 60 anchors.fill: parent color: "transparent" @@ -45,20 +46,18 @@ MenuItem { spacing: 0 Image { id: currentAccountAvatar - width: (userLineBackground.height - 12) - height: (userLineBackground.height - 12) Layout.leftMargin: 6 verticalAlignment: Qt.AlignCenter source: avatar - Layout.preferredHeight: (userLineBackground.height -12) - Layout.preferredWidth: (userLineBackground.height -12) + Layout.preferredHeight: (userLineBackground.height -16) + Layout.preferredWidth: (userLineBackground.height -16) } Column { id: accountLabels spacing: 4 Layout.alignment: Qt.AlignLeft - Layout.leftMargin: 6 + Layout.leftMargin: 12 Label { id: currentAccountUser text: name @@ -73,6 +72,11 @@ MenuItem { font.pointSize: 8 } } + + Item { + id: userLineSpacer + Layout.fillWidth: true + } } } } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 841c2e4be726..a57039d1019c 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -1,6 +1,8 @@ #include "accountmanager.h" #include "UserModel.h" +#include + namespace OCC { User::User(const AccountStatePtr &account) @@ -10,7 +12,8 @@ User::User(const AccountStatePtr &account) QString User::name() const { - return _account->account()->davDisplayName(); + // If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username + return (_account->account()->davDisplayName() == "") ? _account->account()->credentials()->user() : _account->account()->davDisplayName(); } QString User::server() const @@ -31,27 +34,83 @@ QString User::avatar() const buffer.open(QIODevice::WriteOnly); AvatarJob::makeCircularAvatar(_account->account()->avatar()).save(&buffer, "PNG"); + // If AvatarJob doesn't deliver anything, fall back to placeholder image (may be due to missing login session) + if (buffer.size() == 0) { + QIcon(":/client/resources/account.svg").pixmap(QSize(250, 250)).save(&buffer, "PNG"); + } + QString img("data:image/png;base64,"); img.append(QString::fromLatin1(bArray.toBase64().data())); return img; } +QString User::id() const +{ + return _account->account()->id(); +} + +bool User::isConnected() const +{ + return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); +} + +void User::login() +{ + _account->signIn(); +} + +void User::logout() +{ + _account->signOutByUi(); +} + /*-------------------------------------------------------------------------------------*/ UserModel::UserModel(QObject *parent) : QAbstractListModel() - , _currentUser() + , _currentUser(nullptr) { + // TODO: Remember selected user from last quit via settings file + // this is the reason why this looks like an unnecessary double check atm + if (AccountManager::instance()->accounts().size() > 0) { + addCurrentUser(AccountManager::instance()->accounts().first()); + } else { + return; + } + for (int i = 0; i < AccountManager::instance()->accounts().size(); i++) { - addUser(User(AccountManager::instance()->accounts().at(i))); + auto user = AccountManager::instance()->accounts().at(i); + if (user->account()->id() != _currentUser->id()) { + addUser(user); + } } - _currentUser = &_users.first(); } UserModel::~UserModel() { } +Q_INVOKABLE void UserModel::login() +{ + _currentUser->login(); +} + +Q_INVOKABLE void UserModel::logout() +{ + _currentUser->logout(); +} + +Q_INVOKABLE int UserModel::numUsers() +{ + auto test = _users.size(); + return _users.size(); +} + +Q_INVOKABLE bool UserModel::isCurrentUserConnected() +{ + return _currentUser->isConnected(); +} + Q_INVOKABLE QString UserModel::currentUserAvatar() { return _currentUser->avatar(); @@ -74,6 +133,11 @@ void UserModel::addUser(const User &user) endInsertRows(); } +void UserModel::addCurrentUser(const User &user) +{ + _currentUser = new User(user); +} + int UserModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 0e6b381a6d07..3e115c39c0e7 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -13,9 +13,13 @@ class User public: User(const AccountStatePtr &account); + bool isConnected() const; + void login(); + void logout(); QString name() const; QString server() const; QString avatar() const; + QString id() const; private: AccountStatePtr _account; @@ -30,11 +34,17 @@ class UserModel : public QAbstractListModel virtual ~UserModel(); void addUser(const User &user); + void addCurrentUser(const User &user); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + Q_INVOKABLE void login(); + Q_INVOKABLE void logout(); + + Q_INVOKABLE int numUsers(); + Q_INVOKABLE bool isCurrentUserConnected(); Q_INVOKABLE QString currentUserAvatar(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 6d09cc64231d..7c9add790e8a 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -83,11 +83,24 @@ Window { onObjectRemoved: accountMenu.removeItem(object) } - MenuSeparator {} + MenuSeparator { id: accountMenuSeparator } - MenuItem { text: "Login/Logout" } + MenuItem { + text: (systrayBackend.isCurrentUserConnected() ? "Logout" : "Login") + onClicked: (systrayBackend.isCurrentUserConnected() + ? systrayBackend.logout() + : systrayBackend.login() ) + } MenuItem { text: "Add Account" } MenuItem { text: "Remove Account" } + + Component.onCompleted: { + if(systrayBackend.numUsers() === 0) { + accountMenuSeparator.height = 0 + } else { + accountMenuSeparator.height = 13 + } + } } } @@ -144,13 +157,11 @@ Window { spacing: 0 Image { id: currentAccountAvatar - width: (trayWindowHeaderBackground.height - 12) - height: (trayWindowHeaderBackground.height - 12) - Layout.leftMargin: 6 + Layout.leftMargin: 8 verticalAlignment: Qt.AlignCenter source: systrayBackend.currentUserAvatar() - Layout.preferredHeight: (trayWindowHeaderBackground.height -12) - Layout.preferredWidth: (trayWindowHeaderBackground.height -12) + Layout.preferredHeight: (trayWindowHeaderBackground.height -16) + Layout.preferredWidth: (trayWindowHeaderBackground.height -16) } Column { From 4e0997dbdf9bb3a63dffd46b1c8aa4e8b53feb49 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 4 Dec 2019 08:06:56 +0100 Subject: [PATCH 016/120] Minor fixes and enhancements Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index a57039d1019c..4544f3ee44fb 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -1,4 +1,5 @@ #include "accountmanager.h" +#include "owncloudgui.h" #include "UserModel.h" #include @@ -56,6 +57,7 @@ bool User::isConnected() const void User::login() { + _account->account()->resetRejectedCertificates(); _account->signIn(); } From 3a0ccf3697e1d97b646768a08c20883b3c54daf4 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 4 Dec 2019 13:00:26 +0100 Subject: [PATCH 017/120] connected UserModel login/logout signals, minor fixes Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/accountmanager.h | 7 +++---- src/gui/owncloudgui.cpp | 6 ++++++ src/gui/systray.cpp | 2 +- src/gui/tray/UserModel.cpp | 24 ++++++++++-------------- src/gui/tray/UserModel.h | 12 +++++++++--- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index fabc6ca5b620..4719b8109d07 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -89,6 +89,9 @@ class AccountManager : public QObject // Adds an account to the tracked list, emitting accountAdded() void addAccountState(AccountState *accountState); + AccountManager() {} + QList _accounts; + public slots: /// Saves account data, not including the credentials void saveAccount(Account *a); @@ -104,9 +107,5 @@ public slots: void accountAdded(AccountState *account); void accountRemoved(AccountState *account); void removeAccountFolders(AccountState *account); - -private: - AccountManager() {} - QList _accounts; }; } diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 3a4c8651b8f9..3d328403cf0d 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -113,6 +113,12 @@ ownCloudGui::ownCloudGui(Application *parent) this, &ownCloudGui::slotShowOptionalTrayMessage); connect(Logger::instance(), &Logger::guiMessage, this, &ownCloudGui::slotShowGuiMessage); + + + connect(UserModel::instance(), &UserModel::login, + this, &ownCloudGui::slotLogin); + connect(UserModel::instance(), &UserModel::logout, + this, &ownCloudGui::slotLogout); } #ifdef WITH_LIBCLOUDPROVIDERS diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index d34f300bfda3..74bc1347ec18 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -43,7 +43,7 @@ Systray::Systray() // TODO: make singleton, provide ::instance() QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/init.qml"))); _trayContext = engine->contextForObject(systray.create()); - _accountMenuModel = new UserModel(); + _accountMenuModel = UserModel::instance(); systray.engine()->rootContext()->setContextProperty("systrayBackend", _accountMenuModel); // TODO: hack to pass the icon to QML diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 4544f3ee44fb..aec5287c493d 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -68,6 +68,16 @@ void User::logout() /*-------------------------------------------------------------------------------------*/ +UserModel* UserModel::_instance = nullptr; + +UserModel *UserModel::instance() +{ + if (_instance == nullptr) { + _instance = new UserModel(); + } + return _instance; +} + UserModel::UserModel(QObject *parent) : QAbstractListModel() , _currentUser(nullptr) @@ -88,20 +98,6 @@ UserModel::UserModel(QObject *parent) } } -UserModel::~UserModel() -{ -} - -Q_INVOKABLE void UserModel::login() -{ - _currentUser->login(); -} - -Q_INVOKABLE void UserModel::logout() -{ - _currentUser->logout(); -} - Q_INVOKABLE int UserModel::numUsers() { auto test = _users.size(); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 3e115c39c0e7..f2fb8f16daf1 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -30,8 +30,8 @@ class UserModel : public QAbstractListModel Q_OBJECT public: - UserModel(QObject *parent = 0); - virtual ~UserModel(); + static UserModel *instance(); + virtual ~UserModel() {}; void addUser(const User &user); void addCurrentUser(const User &user); @@ -40,7 +40,7 @@ class UserModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - Q_INVOKABLE void login(); + Q_INVOKABLE void logout(); Q_INVOKABLE int numUsers(); @@ -55,10 +55,16 @@ class UserModel : public QAbstractListModel AvatarRole }; +signals: + Q_INVOKABLE void login(); + Q_INVOKABLE void logout(); + protected: QHash roleNames() const; private: + static UserModel *_instance; + UserModel(QObject *parent = 0); QList _users; User *_currentUser; }; From 90ace3fedc00531bef1e74f789addbb51812ae7d Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 29 Nov 2019 17:06:35 +0100 Subject: [PATCH 018/120] Minor preps for c++ ActivityListModel impl. in QML Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/accountmanager.h | 7 +++---- src/gui/owncloudgui.cpp | 6 ++++++ src/gui/systray.cpp | 2 +- src/gui/tray/UserModel.cpp | 24 ++++++++++-------------- src/gui/tray/UserModel.h | 12 +++++++++--- 5 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/gui/accountmanager.h b/src/gui/accountmanager.h index fabc6ca5b620..4719b8109d07 100644 --- a/src/gui/accountmanager.h +++ b/src/gui/accountmanager.h @@ -89,6 +89,9 @@ class AccountManager : public QObject // Adds an account to the tracked list, emitting accountAdded() void addAccountState(AccountState *accountState); + AccountManager() {} + QList _accounts; + public slots: /// Saves account data, not including the credentials void saveAccount(Account *a); @@ -104,9 +107,5 @@ public slots: void accountAdded(AccountState *account); void accountRemoved(AccountState *account); void removeAccountFolders(AccountState *account); - -private: - AccountManager() {} - QList _accounts; }; } diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 3a4c8651b8f9..3d328403cf0d 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -113,6 +113,12 @@ ownCloudGui::ownCloudGui(Application *parent) this, &ownCloudGui::slotShowOptionalTrayMessage); connect(Logger::instance(), &Logger::guiMessage, this, &ownCloudGui::slotShowGuiMessage); + + + connect(UserModel::instance(), &UserModel::login, + this, &ownCloudGui::slotLogin); + connect(UserModel::instance(), &UserModel::logout, + this, &ownCloudGui::slotLogout); } #ifdef WITH_LIBCLOUDPROVIDERS diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index d34f300bfda3..74bc1347ec18 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -43,7 +43,7 @@ Systray::Systray() // TODO: make singleton, provide ::instance() QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/init.qml"))); _trayContext = engine->contextForObject(systray.create()); - _accountMenuModel = new UserModel(); + _accountMenuModel = UserModel::instance(); systray.engine()->rootContext()->setContextProperty("systrayBackend", _accountMenuModel); // TODO: hack to pass the icon to QML diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 4544f3ee44fb..aec5287c493d 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -68,6 +68,16 @@ void User::logout() /*-------------------------------------------------------------------------------------*/ +UserModel* UserModel::_instance = nullptr; + +UserModel *UserModel::instance() +{ + if (_instance == nullptr) { + _instance = new UserModel(); + } + return _instance; +} + UserModel::UserModel(QObject *parent) : QAbstractListModel() , _currentUser(nullptr) @@ -88,20 +98,6 @@ UserModel::UserModel(QObject *parent) } } -UserModel::~UserModel() -{ -} - -Q_INVOKABLE void UserModel::login() -{ - _currentUser->login(); -} - -Q_INVOKABLE void UserModel::logout() -{ - _currentUser->logout(); -} - Q_INVOKABLE int UserModel::numUsers() { auto test = _users.size(); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 3e115c39c0e7..f2fb8f16daf1 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -30,8 +30,8 @@ class UserModel : public QAbstractListModel Q_OBJECT public: - UserModel(QObject *parent = 0); - virtual ~UserModel(); + static UserModel *instance(); + virtual ~UserModel() {}; void addUser(const User &user); void addCurrentUser(const User &user); @@ -40,7 +40,7 @@ class UserModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - Q_INVOKABLE void login(); + Q_INVOKABLE void logout(); Q_INVOKABLE int numUsers(); @@ -55,10 +55,16 @@ class UserModel : public QAbstractListModel AvatarRole }; +signals: + Q_INVOKABLE void login(); + Q_INVOKABLE void logout(); + protected: QHash roleNames() const; private: + static UserModel *_instance; + UserModel(QObject *parent = 0); QList _users; User *_currentUser; }; From d1d1f9bb587292239ea0755bafec8b35ac7b7074 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 4 Dec 2019 14:19:23 +0100 Subject: [PATCH 019/120] Connected AccountWizard with new account action in tray menu, minor fixes Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/owncloudgui.cpp | 2 ++ src/gui/tray/UserLine.qml | 16 +++++++++------- src/gui/tray/UserModel.cpp | 25 +++++++++++++------------ src/gui/tray/UserModel.h | 5 ++--- src/gui/tray/window.qml | 12 +++++++++--- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 3d328403cf0d..7823fe75f90c 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -119,6 +119,8 @@ ownCloudGui::ownCloudGui(Application *parent) this, &ownCloudGui::slotLogin); connect(UserModel::instance(), &UserModel::logout, this, &ownCloudGui::slotLogout); + connect(UserModel::instance(), &UserModel::addAccount, + this, &ownCloudGui::slotNewAccountWizard); } #ifdef WITH_LIBCLOUDPROVIDERS diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 3b6ff0b0f667..ffc9884864c9 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -23,7 +23,7 @@ MenuItem { anchors.fill: parent Button { - id: currentAccountButton + id: accountButton Layout.preferredWidth: 220 Layout.preferredHeight: (userLineBackground.height) display: AbstractButton.IconOnly @@ -31,7 +31,9 @@ MenuItem { MouseArea { id: accountBtnMouseArea - anchors.fill: parent + anchors.centerIn: parent + Layout.preferredWidth: (accountButton.width - 4) + Layout.preferredHeight: (accountButton.height - 4) hoverEnabled: true onClicked: { @@ -41,11 +43,11 @@ MenuItem { RowLayout { id: accountControlRowLayout - height: currentAccountButton.height - width: currentAccountButton.width + height: accountButton.height + width: accountButton.width spacing: 0 Image { - id: currentAccountAvatar + id: accountAvatar Layout.leftMargin: 6 verticalAlignment: Qt.AlignCenter source: avatar @@ -59,14 +61,14 @@ MenuItem { Layout.alignment: Qt.AlignLeft Layout.leftMargin: 12 Label { - id: currentAccountUser + id: accountUser text: name color: "black" font.pointSize: 9 font.bold: true } Label { - id: currentAccountServer + id: accountServer text: server color: "black" font.pointSize: 8 diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index aec5287c493d..36709b31620e 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -14,7 +14,15 @@ User::User(const AccountStatePtr &account) QString User::name() const { // If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username - return (_account->account()->davDisplayName() == "") ? _account->account()->credentials()->user() : _account->account()->davDisplayName(); + QString name = _account->account()->davDisplayName(); + if (name == "") { + name = _account->account()->credentials()->user(); + } + if (name.size() > 19) { + name.truncate(17); + name.append("..."); + } + return name; } QString User::server() const @@ -22,6 +30,10 @@ QString User::server() const QString serverUrl = _account->account()->url().toString(); serverUrl.replace(QLatin1String("https://"), QLatin1String("")); serverUrl.replace(QLatin1String("http://"), QLatin1String("")); + if (serverUrl.size() > 24) { + serverUrl.truncate(22); + serverUrl.append("..."); + } return serverUrl; } @@ -55,17 +67,6 @@ bool User::isConnected() const return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); } -void User::login() -{ - _account->account()->resetRejectedCertificates(); - _account->signIn(); -} - -void User::logout() -{ - _account->signOutByUi(); -} - /*-------------------------------------------------------------------------------------*/ UserModel* UserModel::_instance = nullptr; diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index f2fb8f16daf1..8e299690e3ea 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -40,9 +40,6 @@ class UserModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - - Q_INVOKABLE void logout(); - Q_INVOKABLE int numUsers(); Q_INVOKABLE bool isCurrentUserConnected(); Q_INVOKABLE QString currentUserAvatar(); @@ -58,6 +55,8 @@ class UserModel : public QAbstractListModel signals: Q_INVOKABLE void login(); Q_INVOKABLE void logout(); + Q_INVOKABLE void addAccount(); + Q_INVOKABLE void removeAccount(); protected: QHash roleNames() const; diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 7c9add790e8a..a6532af2ed28 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -73,7 +73,7 @@ Window { background: Rectangle { border.color: "#0082c9" - radius: 4 + radius: 2 } Instantiator { @@ -91,8 +91,14 @@ Window { ? systrayBackend.logout() : systrayBackend.login() ) } - MenuItem { text: "Add Account" } - MenuItem { text: "Remove Account" } + MenuItem { + text: "Add Account" + onClicked: systrayBackend.addAccount() + } + MenuItem { + text: "Remove Account" + onClicked: systrayBackend.removeAccount() + } Component.onCompleted: { if(systrayBackend.numUsers() === 0) { From 3a4ca5e190a526f305b49828a36e9c645d4a4b9d Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 4 Dec 2019 16:19:42 +0100 Subject: [PATCH 020/120] Working on account switching Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/accountsettings.cpp | 6 ++++++ src/gui/owncloudgui.cpp | 2 -- src/gui/tray/UserLine.qml | 23 ++++++++++------------- src/gui/tray/UserModel.cpp | 16 ++++++++++++++-- src/gui/tray/UserModel.h | 5 +++++ src/gui/tray/window.qml | 13 +++++++++---- 6 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index a7b39d606423..faaf02d4ce5d 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -217,7 +217,10 @@ void AccountSettings::createAccountToolbox() _addAccountAction = new QAction(tr("Add new"), this); menu->addAction(_addAccountAction); + connect(_addAccountAction, &QAction::triggered, this, &AccountSettings::slotOpenAccountWizard); + connect(UserModel::instance(), &UserModel::addAccount, + this, &AccountSettings::slotOpenAccountWizard); _toggleSignInOutAction = new QAction(tr("Log out"), this); connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState); @@ -225,7 +228,10 @@ void AccountSettings::createAccountToolbox() QAction *action = new QAction(tr("Remove"), this); menu->addAction(action); + connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount); + connect(UserModel::instance(), &UserModel::removeAccount, + this, &AccountSettings::slotOpenAccountWizard); ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' ')); ui->_accountToolbox->setMenu(menu); diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 7823fe75f90c..3d328403cf0d 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -119,8 +119,6 @@ ownCloudGui::ownCloudGui(Application *parent) this, &ownCloudGui::slotLogin); connect(UserModel::instance(), &UserModel::logout, this, &ownCloudGui::slotLogout); - connect(UserModel::instance(), &UserModel::addAccount, - this, &ownCloudGui::slotNewAccountWizard); } #ifdef WITH_LIBCLOUDPROVIDERS diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index ffc9884864c9..9489f077eefa 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -7,7 +7,7 @@ MenuItem { id: userLine visible: true - width: 220 + width: 216 height: 60 //color: "transparent" @@ -24,21 +24,18 @@ MenuItem { Button { id: accountButton - Layout.preferredWidth: 220 - Layout.preferredHeight: (userLineBackground.height) + anchors.centerIn: parent + Layout.preferredWidth: (userLine.width - 4) + Layout.preferredHeight: (userLineBackground.height - 2) display: AbstractButton.IconOnly flat: true - MouseArea { - id: accountBtnMouseArea - anchors.centerIn: parent - Layout.preferredWidth: (accountButton.width - 4) - Layout.preferredHeight: (accountButton.height - 4) - hoverEnabled: true - onClicked: - { - // - } + background: Rectangle { + color: "transparent" + } + + onClicked: { + systrayBackend.switchUser(index) } RowLayout { diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 36709b31620e..a7db2dd0cbf2 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -69,7 +69,7 @@ bool User::isConnected() const /*-------------------------------------------------------------------------------------*/ -UserModel* UserModel::_instance = nullptr; +UserModel *UserModel::_instance = nullptr; UserModel *UserModel::instance() { @@ -91,9 +91,14 @@ UserModel::UserModel(QObject *parent) return; } + refreshUserList(); +} + +void UserModel::refreshUserList() +{ for (int i = 0; i < AccountManager::instance()->accounts().size(); i++) { auto user = AccountManager::instance()->accounts().at(i); - if (user->account()->id() != _currentUser->id()) { + if ((user->account()->id() != _currentUser->id())) { addUser(user); } } @@ -125,6 +130,13 @@ Q_INVOKABLE QString UserModel::currentUserServer() return _currentUser->server(); } +Q_INVOKABLE void UserModel::switchUser(const int id) +{ + addCurrentUser(_users.at(id)); + refreshUserList(); + emit refreshCurrentUserGui(); +} + void UserModel::addUser(const User &user) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 8e299690e3ea..8e433385f611 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -45,6 +45,7 @@ class UserModel : public QAbstractListModel Q_INVOKABLE QString currentUserAvatar(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); + Q_INVOKABLE void switchUser(const int id); enum UserRoles { NameRole = Qt::UserRole + 1, @@ -58,6 +59,8 @@ class UserModel : public QAbstractListModel Q_INVOKABLE void addAccount(); Q_INVOKABLE void removeAccount(); + Q_INVOKABLE void refreshCurrentUserGui(); + protected: QHash roleNames() const; @@ -66,6 +69,8 @@ class UserModel : public QAbstractListModel UserModel(QObject *parent = 0); QList _users; User *_currentUser; + + void refreshUserList(); }; } diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index a6532af2ed28..f13012c20252 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -22,6 +22,15 @@ Window { //setY(Screen.desktopAvailableHeight + height); } + Connections { + target: systrayBackend + onRefreshCurrentUserGui: { + currentAccountAvatar.source = systrayBackend.currentUserAvatar() + currentAccountUser.text = systrayBackend.currentUserName() + currentAccountServer.text = systrayBackend.currentUserServer() + } + } + Rectangle { id: trayWindowBackground anchors.fill: parent @@ -190,10 +199,6 @@ Window { } } - /*Item { - Layout.preferredWidth: 6 - }*/ - Image { Layout.alignment: Qt.AlignLeft verticalAlignment: Qt.AlignCenter From 5eff8608fe40b935762afe5a047400ea8d954a72 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 5 Dec 2019 08:21:19 +0100 Subject: [PATCH 021/120] Account swtich logic and UI updates Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 18 +++++++--- src/gui/tray/UserModel.cpp | 71 +++++++++++++++++++++++--------------- src/gui/tray/UserModel.h | 22 +++++++----- src/gui/tray/window.qml | 3 ++ 4 files changed, 73 insertions(+), 41 deletions(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 9489f077eefa..d694aaaf2909 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -5,15 +5,23 @@ import QtQuick.Layouts 1.3 MenuItem { + Connections { + target: systrayBackend + onRefreshUserMenu: { + userLine.visible = isCurrentUser ? false : true + userLine.height = isCurrentUser ? 0 : 60 + } + } + id: userLine - visible: true + visible: isCurrentUser ? false : true width: 216 - height: 60 + height: isCurrentUser ? 0 : 60 //color: "transparent" Rectangle { id: userLineBackground - height: 60 + height: userLine.height anchors.fill: parent color: "transparent" @@ -35,7 +43,7 @@ MenuItem { } onClicked: { - systrayBackend.switchUser(index) + systrayBackend.switchCurrentUser(index) } RowLayout { @@ -45,7 +53,7 @@ MenuItem { spacing: 0 Image { id: accountAvatar - Layout.leftMargin: 6 + Layout.leftMargin: 4 verticalAlignment: Qt.AlignCenter source: avatar Layout.preferredHeight: (userLineBackground.height -16) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index a7db2dd0cbf2..4e42f48ef6b2 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -6,11 +6,22 @@ namespace OCC { -User::User(const AccountStatePtr &account) +User::User(AccountStatePtr &account, const bool &isCurrent) : _account(account) + , _isCurrentUser(isCurrent) { } +bool User::operator==(const User &rhs) const +{ + return (this->_account->account() == rhs._account->account()); +} + +void User::setCurrentUser(const bool &isCurrent) +{ + _isCurrentUser = isCurrent; +} + QString User::name() const { // If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username @@ -57,9 +68,9 @@ QString User::avatar() const return img; } -QString User::id() const +bool User::isCurrentUser() const { - return _account->account()->id(); + return _isCurrentUser; } bool User::isConnected() const @@ -81,72 +92,73 @@ UserModel *UserModel::instance() UserModel::UserModel(QObject *parent) : QAbstractListModel() - , _currentUser(nullptr) + , _currentUserId() { // TODO: Remember selected user from last quit via settings file // this is the reason why this looks like an unnecessary double check atm - if (AccountManager::instance()->accounts().size() > 0) { - addCurrentUser(AccountManager::instance()->accounts().first()); + /*if (AccountManager::instance()->accounts().size() > 0) { + addUser(AccountManager::instance()->accounts().first(), true); } else { return; + }*/ + if (AccountManager::instance()->accounts().size() > 0) { + initUserList(); } - - refreshUserList(); } -void UserModel::refreshUserList() +void UserModel::initUserList() { for (int i = 0; i < AccountManager::instance()->accounts().size(); i++) { auto user = AccountManager::instance()->accounts().at(i); - if ((user->account()->id() != _currentUser->id())) { - addUser(user); - } + addUser(user); } + _users.first().setCurrentUser(true); } Q_INVOKABLE int UserModel::numUsers() { - auto test = _users.size(); return _users.size(); } Q_INVOKABLE bool UserModel::isCurrentUserConnected() { - return _currentUser->isConnected(); + return _users[_currentUserId].isConnected(); } Q_INVOKABLE QString UserModel::currentUserAvatar() { - return _currentUser->avatar(); + return _users[_currentUserId].avatar(); } Q_INVOKABLE QString UserModel::currentUserName() { - return _currentUser->name(); + return _users[_currentUserId].name(); } Q_INVOKABLE QString UserModel::currentUserServer() { - return _currentUser->server(); + return _users[_currentUserId].server(); } -Q_INVOKABLE void UserModel::switchUser(const int id) -{ - addCurrentUser(_users.at(id)); - refreshUserList(); - emit refreshCurrentUserGui(); -} - -void UserModel::addUser(const User &user) +void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) { + auto newUser = User(user, isCurrent); beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _users << user; + _users << newUser; + if (isCurrent) { + _currentUserId = _users.indexOf(newUser); + } endInsertRows(); } -void UserModel::addCurrentUser(const User &user) +Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) { - _currentUser = new User(user); + _users[_currentUserId].setCurrentUser(false); + _users[id].setCurrentUser(true); + _currentUserId = id; + emit refreshCurrentUserGui(); + emit refreshUserMenu(); + emit newUserSelected(); } int UserModel::rowCount(const QModelIndex &parent) const @@ -168,6 +180,8 @@ QVariant UserModel::data(const QModelIndex &index, int role) const return user.server(); } else if (role == AvatarRole) { return user.avatar(); + } else if (role == IsCurrentUserRole) { + return user.isCurrentUser(); } return QVariant(); } @@ -178,6 +192,7 @@ QHash UserModel::roleNames() const roles[NameRole] = "name"; roles[ServerRole] = "server"; roles[AvatarRole] = "avatar"; + roles[IsCurrentUserRole] = "isCurrentUser"; return roles; } } \ No newline at end of file diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 8e433385f611..b8a9eb776979 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -11,9 +11,13 @@ namespace OCC { class User { public: - User(const AccountStatePtr &account); + User(AccountStatePtr &account, const bool &isCurrent = false); + + bool operator==(const User &) const; bool isConnected() const; + bool isCurrentUser() const; + void setCurrentUser(const bool &isCurrent); void login(); void logout(); QString name() const; @@ -23,18 +27,17 @@ class User private: AccountStatePtr _account; + bool _isCurrentUser; }; class UserModel : public QAbstractListModel { Q_OBJECT - public: static UserModel *instance(); virtual ~UserModel() {}; - void addUser(const User &user); - void addCurrentUser(const User &user); + void addUser(AccountStatePtr &user, const bool &isCurrent = false); int rowCount(const QModelIndex &parent = QModelIndex()) const; @@ -45,12 +48,13 @@ class UserModel : public QAbstractListModel Q_INVOKABLE QString currentUserAvatar(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); - Q_INVOKABLE void switchUser(const int id); + Q_INVOKABLE void switchCurrentUser(const int &id); enum UserRoles { NameRole = Qt::UserRole + 1, ServerRole, - AvatarRole + AvatarRole, + IsCurrentUserRole }; signals: @@ -60,6 +64,8 @@ class UserModel : public QAbstractListModel Q_INVOKABLE void removeAccount(); Q_INVOKABLE void refreshCurrentUserGui(); + Q_INVOKABLE void newUserSelected(); + Q_INVOKABLE void refreshUserMenu(); protected: QHash roleNames() const; @@ -68,9 +74,9 @@ class UserModel : public QAbstractListModel static UserModel *_instance; UserModel(QObject *parent = 0); QList _users; - User *_currentUser; + int _currentUserId; - void refreshUserList(); + void initUserList(); }; } diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index f13012c20252..85cff3b5bd06 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -29,6 +29,9 @@ Window { currentAccountUser.text = systrayBackend.currentUserName() currentAccountServer.text = systrayBackend.currentUserServer() } + onNewUserSelected: { + accountMenu.close() + } } Rectangle { From c18611d4af82f295b911bd2f27a001b957067491 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 5 Dec 2019 11:38:29 +0100 Subject: [PATCH 022/120] Finished account switch back- and frontend in tray menuwindow Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 2 ++ src/gui/tray/UserLine.qml | 2 +- src/gui/tray/UserModel.cpp | 52 +++++++++++++++++++++++--------------- src/gui/tray/UserModel.h | 15 +++++++++-- src/gui/tray/window.qml | 24 +++++++++--------- 5 files changed, 60 insertions(+), 35 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 74bc1347ec18..6e326c4cd9db 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -44,7 +44,9 @@ Systray::Systray() // TODO: make singleton, provide ::instance() _trayContext = engine->contextForObject(systray.create()); _accountMenuModel = UserModel::instance(); + systray.engine()->addImageProvider("avatars", new ImageProvider); systray.engine()->rootContext()->setContextProperty("systrayBackend", _accountMenuModel); + // TODO: hack to pass the icon to QML //ctxt->setContextProperty("theme", QLatin1String("colored")); diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index d694aaaf2909..fa80d2a2bd25 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -55,7 +55,7 @@ MenuItem { id: accountAvatar Layout.leftMargin: 4 verticalAlignment: Qt.AlignCenter - source: avatar + source: ("image://avatars/" + index) Layout.preferredHeight: (userLineBackground.height -16) Layout.preferredWidth: (userLineBackground.height -16) } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 4e42f48ef6b2..63513003f540 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -41,30 +41,19 @@ QString User::server() const QString serverUrl = _account->account()->url().toString(); serverUrl.replace(QLatin1String("https://"), QLatin1String("")); serverUrl.replace(QLatin1String("http://"), QLatin1String("")); - if (serverUrl.size() > 24) { - serverUrl.truncate(22); + if (serverUrl.size() > 21) { + serverUrl.truncate(19); serverUrl.append("..."); } return serverUrl; } -// TODO: Lots of memory shifting here -// Probably OK because the avatar is not changing a trillion times per second -// But should consider moving to a generic ImageProvider helper class for img/QML-provision -QString User::avatar() const +QImage User::avatar() const { - QByteArray bArray; - QBuffer buffer(&bArray); - buffer.open(QIODevice::WriteOnly); - AvatarJob::makeCircularAvatar(_account->account()->avatar()).save(&buffer, "PNG"); - - // If AvatarJob doesn't deliver anything, fall back to placeholder image (may be due to missing login session) - if (buffer.size() == 0) { - QIcon(":/client/resources/account.svg").pixmap(QSize(250, 250)).save(&buffer, "PNG"); + QImage img = AvatarJob::makeCircularAvatar(_account->account()->avatar()); + if (img.isNull()) { + img = QImage(":/client/resources/account.svg"); } - - QString img("data:image/png;base64,"); - img.append(QString::fromLatin1(bArray.toBase64().data())); return img; } @@ -125,11 +114,16 @@ Q_INVOKABLE bool UserModel::isCurrentUserConnected() return _users[_currentUserId].isConnected(); } -Q_INVOKABLE QString UserModel::currentUserAvatar() +QImage UserModel::currentUserAvatar() { return _users[_currentUserId].avatar(); } +QImage UserModel::avatarById(const int &id) +{ + return _users[id].avatar(); +} + Q_INVOKABLE QString UserModel::currentUserName() { return _users[_currentUserId].name(); @@ -156,9 +150,9 @@ Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) _users[_currentUserId].setCurrentUser(false); _users[id].setCurrentUser(true); _currentUserId = id; - emit refreshCurrentUserGui(); - emit refreshUserMenu(); emit newUserSelected(); + emit refreshUserMenu(); + emit refreshCurrentUserGui(); } int UserModel::rowCount(const QModelIndex &parent) const @@ -195,4 +189,22 @@ QHash UserModel::roleNames() const roles[IsCurrentUserRole] = "isCurrentUser"; return roles; } + +/*-------------------------------------------------------------------------------------*/ + +ImageProvider::ImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image) +{ +} + +QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) +{ + if (id == "currentUser") { + return UserModel::instance()->currentUserAvatar(); + } else { + int uid = id.toInt(); + return UserModel::instance()->avatarById(uid); + } +} + } \ No newline at end of file diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index b8a9eb776979..ddd958fd4376 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -2,7 +2,9 @@ #define USERMODEL_H #include +#include #include +#include #include "accountmanager.h" @@ -22,7 +24,7 @@ class User void logout(); QString name() const; QString server() const; - QString avatar() const; + QImage avatar() const; QString id() const; private: @@ -43,9 +45,11 @@ class UserModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QImage currentUserAvatar(); + QImage avatarById(const int &id); + Q_INVOKABLE int numUsers(); Q_INVOKABLE bool isCurrentUserConnected(); - Q_INVOKABLE QString currentUserAvatar(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); Q_INVOKABLE void switchCurrentUser(const int &id); @@ -79,5 +83,12 @@ class UserModel : public QAbstractListModel void initUserList(); }; +class ImageProvider : public QQuickImageProvider +{ +public: + ImageProvider(); + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; +}; + } #endif // USERMODEL_H \ No newline at end of file diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 85cff3b5bd06..ade716b5d0f3 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -25,7 +25,8 @@ Window { Connections { target: systrayBackend onRefreshCurrentUserGui: { - currentAccountAvatar.source = systrayBackend.currentUserAvatar() + currentAccountAvatar.source = "" + currentAccountAvatar.source = "image://avatars/currentUser" currentAccountUser.text = systrayBackend.currentUserName() currentAccountServer.text = systrayBackend.currentUserServer() } @@ -64,6 +65,7 @@ Window { Button { id: currentAccountButton Layout.preferredWidth: 220 + Layout.maximumWidth: 220 Layout.preferredHeight: (trayWindowHeaderBackground.height) display: AbstractButton.IconOnly flat: true @@ -74,6 +76,7 @@ Window { hoverEnabled: true onClicked: { + accountMenuLoginLogout.text = (systrayBackend.isCurrentUserConnected() ? "Logout" : "Login") accountMenu.open() } @@ -98,7 +101,7 @@ Window { MenuSeparator { id: accountMenuSeparator } MenuItem { - text: (systrayBackend.isCurrentUserConnected() ? "Logout" : "Login") + id: accountMenuLoginLogout onClicked: (systrayBackend.isCurrentUserConnected() ? systrayBackend.logout() : systrayBackend.login() ) @@ -177,7 +180,8 @@ Window { id: currentAccountAvatar Layout.leftMargin: 8 verticalAlignment: Qt.AlignCenter - source: systrayBackend.currentUserAvatar() + cache: false + source: "image://avatars/currentUser" Layout.preferredHeight: (trayWindowHeaderBackground.height -16) Layout.preferredWidth: (trayWindowHeaderBackground.height -16) } @@ -202,21 +206,20 @@ Window { } } + Item { + id: trayWindowHeaderSpacer + Layout.fillWidth: true + } + Image { Layout.alignment: Qt.AlignLeft verticalAlignment: Qt.AlignCenter Layout.margins: 12 - //source: "../../theme/white/caret-down.svg" source: "qrc:///client/theme/white/caret-down.svg" } } } - Item { - id: trayWindowHeaderSpacer - Layout.fillWidth: true - } - Button { id: openLocalFolderButton Layout.alignment: Qt.AlignRight @@ -225,7 +228,6 @@ Window { Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - //icon.source: "../../theme/white/folder.svg" icon.source: "qrc:///client/theme/white/folder.svg" icon.color: "transparent" @@ -253,7 +255,6 @@ Window { Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - //icon.source: "../../theme/white/talk-app.svg" icon.source: "qrc:///client/theme/white/talk-app.svg" icon.color: "transparent" @@ -281,7 +282,6 @@ Window { Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - //icon.source: "../../theme/white/more-apps.svg" icon.source: "qrc:///client/theme/white/more-apps.svg" icon.color: "transparent" From ab9897b397bceead599e506f7335feee91430812 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 6 Dec 2019 14:50:34 +0100 Subject: [PATCH 023/120] Added frontend invokation to open local folder Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.h | 1 + src/gui/tray/window.qml | 1 + 2 files changed, 2 insertions(+) diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index ddd958fd4376..6d8a7d04f536 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -62,6 +62,7 @@ class UserModel : public QAbstractListModel }; signals: + Q_INVOKABLE void openLocalFolder(); Q_INVOKABLE void login(); Q_INVOKABLE void logout(); Q_INVOKABLE void addAccount(); diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index ade716b5d0f3..91584260749c 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -237,6 +237,7 @@ Window { hoverEnabled: true onClicked: { + systrayBackend.openLocalFolder() } } From ae7eddf7c937888cff8953c7aae9b99630ae9420 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 8 Dec 2019 11:32:22 +0100 Subject: [PATCH 024/120] Fixed inconsistent module import versions (based on Qt 5.9) Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 182 ++++---- src/gui/tray/window.qml | 885 +++++++++++++++++++------------------- 2 files changed, 533 insertions(+), 534 deletions(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index fa80d2a2bd25..7dd648fda9dd 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -1,91 +1,91 @@ -import QtQuick 2.9 -import QtQuick.Window 2.2 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 - -MenuItem { - - Connections { - target: systrayBackend - onRefreshUserMenu: { - userLine.visible = isCurrentUser ? false : true - userLine.height = isCurrentUser ? 0 : 60 - } - } - - id: userLine - visible: isCurrentUser ? false : true - width: 216 - height: isCurrentUser ? 0 : 60 - //color: "transparent" - - Rectangle { - id: userLineBackground - height: userLine.height - anchors.fill: parent - color: "transparent" - - RowLayout { - id: userLineLayout - spacing: 0 - anchors.fill: parent - - Button { - id: accountButton - anchors.centerIn: parent - Layout.preferredWidth: (userLine.width - 4) - Layout.preferredHeight: (userLineBackground.height - 2) - display: AbstractButton.IconOnly - flat: true - - background: Rectangle { - color: "transparent" - } - - onClicked: { - systrayBackend.switchCurrentUser(index) - } - - RowLayout { - id: accountControlRowLayout - height: accountButton.height - width: accountButton.width - spacing: 0 - Image { - id: accountAvatar - Layout.leftMargin: 4 - verticalAlignment: Qt.AlignCenter - source: ("image://avatars/" + index) - Layout.preferredHeight: (userLineBackground.height -16) - Layout.preferredWidth: (userLineBackground.height -16) - } - - Column { - id: accountLabels - spacing: 4 - Layout.alignment: Qt.AlignLeft - Layout.leftMargin: 12 - Label { - id: accountUser - text: name - color: "black" - font.pointSize: 9 - font.bold: true - } - Label { - id: accountServer - text: server - color: "black" - font.pointSize: 8 - } - } - - Item { - id: userLineSpacer - Layout.fillWidth: true - } - } - } - } - } // Rectangle userLineBackground -} // MenuItem userLine +import QtQuick 2.9 +import QtQuick.Window 2.2 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.2 + +MenuItem { + + Connections { + target: systrayBackend + onRefreshUserMenu: { + userLine.visible = isCurrentUser ? false : true + userLine.height = isCurrentUser ? 0 : 60 + } + } + + id: userLine + visible: isCurrentUser ? false : true + width: 216 + height: isCurrentUser ? 0 : 60 + //color: "transparent" + + Rectangle { + id: userLineBackground + height: userLine.height + anchors.fill: parent + color: "transparent" + + RowLayout { + id: userLineLayout + spacing: 0 + anchors.fill: parent + + Button { + id: accountButton + anchors.centerIn: parent + Layout.preferredWidth: (userLine.width - 4) + Layout.preferredHeight: (userLineBackground.height - 2) + display: AbstractButton.IconOnly + flat: true + + background: Rectangle { + color: "transparent" + } + + onClicked: { + systrayBackend.switchCurrentUser(index) + } + + RowLayout { + id: accountControlRowLayout + height: accountButton.height + width: accountButton.width + spacing: 0 + Image { + id: accountAvatar + Layout.leftMargin: 4 + verticalAlignment: Qt.AlignCenter + source: avatar + Layout.preferredHeight: (userLineBackground.height -16) + Layout.preferredWidth: (userLineBackground.height -16) + } + + Column { + id: accountLabels + spacing: 4 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 12 + Label { + id: accountUser + text: name + color: "black" + font.pointSize: 9 + font.bold: true + } + Label { + id: accountServer + text: server + color: "black" + font.pointSize: 8 + } + } + + Item { + id: userLineSpacer + Layout.fillWidth: true + } + } + } + } + } // Rectangle userLineBackground +} // MenuItem userLine diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 91584260749c..45d2455d3fb7 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -1,443 +1,442 @@ -import QtQml 2.2 -import QtQuick 2.9 -import QtQuick.Window 2.2 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 -import QtGraphicalEffects 1.0 - -Window { - - id: trayWindow - visible: true - width: 400 - height: 500 - color: "transparent" - flags: Qt.FramelessWindowHint - - Component.onCompleted: { - /* desktopAvailableWidth and Height doesn't include the system tray bar - but breaks application anyway on windows when using multi monitor setup, - will look for a better solution later, for now just get this thing complete */ - //setX(Screen.desktopAvailableWidth - width); - //setY(Screen.desktopAvailableHeight + height); - } - - Connections { - target: systrayBackend - onRefreshCurrentUserGui: { - currentAccountAvatar.source = "" - currentAccountAvatar.source = "image://avatars/currentUser" - currentAccountUser.text = systrayBackend.currentUserName() - currentAccountServer.text = systrayBackend.currentUserServer() - } - onNewUserSelected: { - accountMenu.close() - } - } - - Rectangle { - id: trayWindowBackground - anchors.fill: parent - radius: 10 - - Rectangle { - id: trayWindowHeaderBackground - anchors.left: trayWindowBackground.left - anchors.top: trayWindowBackground.top - height: 60 - width: parent.width - radius: 9 - color: "#0082c9" - - Rectangle { - anchors.left: trayWindowHeaderBackground.left - anchors.bottom: trayWindowHeaderBackground.bottom - height: 30 - width: parent.width - color: "#0082c9" - } - - RowLayout { - id: trayWindowHeaderLayout - spacing: 0 - anchors.fill: parent - - Button { - id: currentAccountButton - Layout.preferredWidth: 220 - Layout.maximumWidth: 220 - Layout.preferredHeight: (trayWindowHeaderBackground.height) - display: AbstractButton.IconOnly - flat: true - - MouseArea { - id: accountBtnMouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: - { - accountMenuLoginLogout.text = (systrayBackend.isCurrentUserConnected() ? "Logout" : "Login") - accountMenu.open() - } - - Menu { - id: accountMenu - x: (currentAccountButton.x + 2) - y: (currentAccountButton.y + currentAccountButton.height + 2) - width: (currentAccountButton.width - 4) - - background: Rectangle { - border.color: "#0082c9" - radius: 2 - } - - Instantiator { - model: systrayBackend - delegate: UserLine {} - onObjectAdded: accountMenu.insertItem(index, object) - onObjectRemoved: accountMenu.removeItem(object) - } - - MenuSeparator { id: accountMenuSeparator } - - MenuItem { - id: accountMenuLoginLogout - onClicked: (systrayBackend.isCurrentUserConnected() - ? systrayBackend.logout() - : systrayBackend.login() ) - } - MenuItem { - text: "Add Account" - onClicked: systrayBackend.addAccount() - } - MenuItem { - text: "Remove Account" - onClicked: systrayBackend.removeAccount() - } - - Component.onCompleted: { - if(systrayBackend.numUsers() === 0) { - accountMenuSeparator.height = 0 - } else { - accountMenuSeparator.height = 13 - } - } - } - } - - background: - Item { - id: leftHoverContainer - height: currentAccountButton.height - width: currentAccountButton.width - Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - color: "transparent" - clip: true - Rectangle { - width: currentAccountButton.width - height: currentAccountButton.height - radius: 10 - color: "white" - opacity: 0.2 - visible: accountBtnMouseArea.containsMouse - } - } - Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - anchors.bottom: leftHoverContainer.bottom - color: "white" - opacity: 0.2 - visible: accountBtnMouseArea.containsMouse - } - Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - anchors.right: leftHoverContainer.right - color: "white" - opacity: 0.2 - visible: accountBtnMouseArea.containsMouse - } - Rectangle { - width: currentAccountButton.width / 2 - height: currentAccountButton.height / 2 - anchors.right: leftHoverContainer.right - anchors.bottom: leftHoverContainer.bottom - color: "white" - opacity: 0.2 - visible: accountBtnMouseArea.containsMouse - } - } - - RowLayout { - id: accountControlRowLayout - height: currentAccountButton.height - width: currentAccountButton.width - spacing: 0 - Image { - id: currentAccountAvatar - Layout.leftMargin: 8 - verticalAlignment: Qt.AlignCenter - cache: false - source: "image://avatars/currentUser" - Layout.preferredHeight: (trayWindowHeaderBackground.height -16) - Layout.preferredWidth: (trayWindowHeaderBackground.height -16) - } - - Column { - id: accountLabels - spacing: 4 - Layout.alignment: Qt.AlignLeft - Layout.leftMargin: 6 - Label { - id: currentAccountUser - text: systrayBackend.currentUserName() - color: "white" - font.pointSize: 9 - font.bold: true - } - Label { - id: currentAccountServer - text: systrayBackend.currentUserServer() - color: "white" - font.pointSize: 8 - } - } - - Item { - id: trayWindowHeaderSpacer - Layout.fillWidth: true - } - - Image { - Layout.alignment: Qt.AlignLeft - verticalAlignment: Qt.AlignCenter - Layout.margins: 12 - source: "qrc:///client/theme/white/caret-down.svg" - } - } - } - - Button { - id: openLocalFolderButton - Layout.alignment: Qt.AlignRight - display: AbstractButton.IconOnly - Layout.preferredWidth: (trayWindowHeaderBackground.height) - Layout.preferredHeight: (trayWindowHeaderBackground.height) - flat: true - - icon.source: "qrc:///client/theme/white/folder.svg" - icon.color: "transparent" - - MouseArea { - id: folderBtnMouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: - { - systrayBackend.openLocalFolder() - } - } - - background: - Rectangle { - color: folderBtnMouseArea.containsMouse ? "white" : "transparent" - opacity: 0.2 - } - } - - Button { - id: trayWindowTalkButton - Layout.alignment: Qt.AlignRight - display: AbstractButton.IconOnly - Layout.preferredWidth: (trayWindowHeaderBackground.height) - Layout.preferredHeight: (trayWindowHeaderBackground.height) - flat: true - - icon.source: "qrc:///client/theme/white/talk-app.svg" - icon.color: "transparent" - - MouseArea { - id: talkBtnMouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: - { - } - } - - background: - Rectangle { - color: talkBtnMouseArea.containsMouse ? "white" : "transparent" - opacity: 0.2 - } - } - - Button { - id: trayWindowAppsButton - Layout.alignment: Qt.AlignRight - display: AbstractButton.IconOnly - Layout.preferredWidth: (trayWindowHeaderBackground.height) - Layout.preferredHeight: (trayWindowHeaderBackground.height) - flat: true - - icon.source: "qrc:///client/theme/white/more-apps.svg" - icon.color: "transparent" - - MouseArea { - id: appsBtnMouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: - { - } - } - - background: - Item { - id: rightHoverContainer - height: trayWindowAppsButton.height - width: trayWindowAppsButton.width - Rectangle { - width: trayWindowAppsButton.width / 2 - height: trayWindowAppsButton.height / 2 - color: "white" - opacity: 0.2 - visible: appsBtnMouseArea.containsMouse - } - Rectangle { - width: trayWindowAppsButton.width / 2 - height: trayWindowAppsButton.height / 2 - anchors.bottom: rightHoverContainer.bottom - color: "white" - opacity: 0.2 - visible: appsBtnMouseArea.containsMouse - } - Rectangle { - width: trayWindowAppsButton.width / 2 - height: trayWindowAppsButton.height / 2 - anchors.bottom: rightHoverContainer.bottom - anchors.right: rightHoverContainer.right - color: "white" - opacity: 0.2 - visible: appsBtnMouseArea.containsMouse - } - Rectangle { - id: rightHoverContainerClipper - anchors.right: rightHoverContainer.right - width: trayWindowAppsButton.width / 2 - height: trayWindowAppsButton.height / 2 - color: "transparent" - clip: true - Rectangle { - width: trayWindowAppsButton.width - height: trayWindowAppsButton.height - anchors.right: rightHoverContainerClipper.right - radius: 10 - color: "white" - opacity: 0.2 - visible: appsBtnMouseArea.containsMouse - } - } - } - } - } - } // Rectangle trayWindowHeaderBackground - - ListModel { - id: activityListModel - } - - ListView { - id: activityListView - anchors.top: trayWindowHeaderBackground.bottom - width: trayWindowBackground.width - height: trayWindowBackground.height - trayWindowHeaderBackground.height - clip: true - - model: activityListModel - - delegate: RowLayout { - id: activityItem - width: activityListView.width - height: trayWindowHeaderLayout.height - spacing: 0 - Image { - id: activityIcon - Layout.leftMargin: 6 - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 - verticalAlignment: Qt.AlignCenter - source: "qrc:///client/theme/black/state-sync.svg" - sourceSize.height: 48 - sourceSize.width: 48 - } - Column { - Layout.leftMargin: 6 - spacing: 4 - Layout.alignment: Qt.AlignLeft - Text { - id: activityTextTitle - text: name - font.pointSize: 9 - } - Text { - id: activityTextInfo - text: "Lorem ipsum dolor sit amet" - font.pointSize: 8 - } - } - Item { - id: activityItemFiller - Layout.fillWidth: true - } - Button { - Layout.preferredWidth: activityItem.height - Layout.preferredHeight: activityItem.height - Layout.alignment: Qt.AlignRight - flat: true - display: AbstractButton.IconOnly - icon.source: "qrc:///client/resources/files.svg" - icon.color: "transparent" - } - Button { - Layout.preferredWidth: activityItem.height - Layout.preferredHeight: activityItem.height - Layout.alignment: Qt.AlignRight - flat: true - display: AbstractButton.IconOnly - icon.source: "qrc:///client/resources/public.svg" - icon.color: "transparent" - } - } - - add: Transition { - NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } - } - - remove: Transition { - NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 } - } - - removeDisplaced: Transition { - SequentialAnimation { - PauseAnimation { duration: 100} - NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } - } - } - - displaced: Transition { - NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } - } - - focus: true - - // For interactive ListView/Animation testing only - //Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count }) - //Keys.onTabPressed: model.remove(3) - } - - } // Rectangle trayWindowBackground -} +import QtQml 2.0 +import QtQuick 2.9 +import QtQuick.Window 2.2 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.2 +import QtGraphicalEffects 1.0 + +Window { + + id: trayWindow + visible: true + width: 400 + height: 500 + color: "transparent" + flags: Qt.FramelessWindowHint + + Component.onCompleted: { + /* desktopAvailableWidth and Height doesn't include the system tray bar + but breaks application anyway on windows when using multi monitor setup, + will look for a better solution later, for now just get this thing complete */ + //setX(Screen.desktopAvailableWidth - width); + //setY(Screen.desktopAvailableHeight + height); + } + + Connections { + target: systrayBackend + onRefreshCurrentUserGui: { + currentAccountAvatar.source = systrayBackend.currentUserAvatar() + currentAccountUser.text = systrayBackend.currentUserName() + currentAccountServer.text = systrayBackend.currentUserServer() + } + onNewUserSelected: { + accountMenu.close() + } + } + + Rectangle { + id: trayWindowBackground + anchors.fill: parent + radius: 10 + + Rectangle { + id: trayWindowHeaderBackground + anchors.left: trayWindowBackground.left + anchors.top: trayWindowBackground.top + height: 60 + width: parent.width + radius: 9 + color: "#0082c9" + + Rectangle { + anchors.left: trayWindowHeaderBackground.left + anchors.bottom: trayWindowHeaderBackground.bottom + height: 30 + width: parent.width + color: "#0082c9" + } + + RowLayout { + id: trayWindowHeaderLayout + spacing: 0 + anchors.fill: parent + + Button { + id: currentAccountButton + Layout.preferredWidth: 220 + Layout.preferredHeight: (trayWindowHeaderBackground.height) + display: AbstractButton.IconOnly + flat: true + + MouseArea { + id: accountBtnMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: + { + accountMenu.open() + } + + Menu { + id: accountMenu + x: (currentAccountButton.x + 2) + y: (currentAccountButton.y + currentAccountButton.height + 2) + width: (currentAccountButton.width - 4) + + background: Rectangle { + border.color: "#0082c9" + radius: 2 + } + + Instantiator { + model: systrayBackend + delegate: UserLine {} + onObjectAdded: accountMenu.insertItem(index, object) + onObjectRemoved: accountMenu.removeItem(object) + } + + MenuSeparator { id: accountMenuSeparator } + + MenuItem { + text: (systrayBackend.isCurrentUserConnected() ? "Logout" : "Login") + onClicked: (systrayBackend.isCurrentUserConnected() + ? systrayBackend.logout() + : systrayBackend.login() ) + } + MenuItem { + text: "Add Account" + onClicked: systrayBackend.addAccount() + } + MenuItem { + text: "Remove Account" + onClicked: systrayBackend.removeAccount() + } + + Component.onCompleted: { + if(systrayBackend.numUsers() === 0) { + accountMenuSeparator.height = 0 + } else { + accountMenuSeparator.height = 13 + } + } + } + } + + background: + Item { + id: leftHoverContainer + height: currentAccountButton.height + width: currentAccountButton.width + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + color: "transparent" + clip: true + Rectangle { + width: currentAccountButton.width + height: currentAccountButton.height + radius: 10 + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.bottom: leftHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.right: leftHoverContainer.right + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + Rectangle { + width: currentAccountButton.width / 2 + height: currentAccountButton.height / 2 + anchors.right: leftHoverContainer.right + anchors.bottom: leftHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: accountBtnMouseArea.containsMouse + } + } + + RowLayout { + id: accountControlRowLayout + height: currentAccountButton.height + width: currentAccountButton.width + spacing: 0 + Image { + id: currentAccountAvatar + Layout.leftMargin: 8 + verticalAlignment: Qt.AlignCenter + source: systrayBackend.currentUserAvatar() + Layout.preferredHeight: (trayWindowHeaderBackground.height -16) + Layout.preferredWidth: (trayWindowHeaderBackground.height -16) + } + + Column { + id: accountLabels + spacing: 4 + Layout.alignment: Qt.AlignLeft + Layout.leftMargin: 6 + Label { + id: currentAccountUser + text: systrayBackend.currentUserName() + color: "white" + font.pointSize: 9 + font.bold: true + } + Label { + id: currentAccountServer + text: systrayBackend.currentUserServer() + color: "white" + font.pointSize: 8 + } + } + + Image { + Layout.alignment: Qt.AlignLeft + verticalAlignment: Qt.AlignCenter + Layout.margins: 12 + //source: "../../theme/white/caret-down.svg" + source: "qrc:///client/theme/white/caret-down.svg" + } + } + } + + Item { + id: trayWindowHeaderSpacer + Layout.fillWidth: true + } + + Button { + id: openLocalFolderButton + Layout.alignment: Qt.AlignRight + display: AbstractButton.IconOnly + Layout.preferredWidth: (trayWindowHeaderBackground.height) + Layout.preferredHeight: (trayWindowHeaderBackground.height) + flat: true + + //icon.source: "../../theme/white/folder.svg" + icon.source: "qrc:///client/theme/white/folder.svg" + icon.color: "transparent" + + MouseArea { + id: folderBtnMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: + { + } + } + + background: + Rectangle { + color: folderBtnMouseArea.containsMouse ? "white" : "transparent" + opacity: 0.2 + } + } + + Button { + id: trayWindowTalkButton + Layout.alignment: Qt.AlignRight + display: AbstractButton.IconOnly + Layout.preferredWidth: (trayWindowHeaderBackground.height) + Layout.preferredHeight: (trayWindowHeaderBackground.height) + flat: true + + //icon.source: "../../theme/white/talk-app.svg" + icon.source: "qrc:///client/theme/white/talk-app.svg" + icon.color: "transparent" + + MouseArea { + id: talkBtnMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: + { + } + } + + background: + Rectangle { + color: talkBtnMouseArea.containsMouse ? "white" : "transparent" + opacity: 0.2 + } + } + + Button { + id: trayWindowAppsButton + Layout.alignment: Qt.AlignRight + display: AbstractButton.IconOnly + Layout.preferredWidth: (trayWindowHeaderBackground.height) + Layout.preferredHeight: (trayWindowHeaderBackground.height) + flat: true + + //icon.source: "../../theme/white/more-apps.svg" + icon.source: "qrc:///client/theme/white/more-apps.svg" + icon.color: "transparent" + + MouseArea { + id: appsBtnMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: + { + } + } + + background: + Item { + id: rightHoverContainer + height: trayWindowAppsButton.height + width: trayWindowAppsButton.width + Rectangle { + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } + Rectangle { + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + anchors.bottom: rightHoverContainer.bottom + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } + Rectangle { + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + anchors.bottom: rightHoverContainer.bottom + anchors.right: rightHoverContainer.right + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } + Rectangle { + id: rightHoverContainerClipper + anchors.right: rightHoverContainer.right + width: trayWindowAppsButton.width / 2 + height: trayWindowAppsButton.height / 2 + color: "transparent" + clip: true + Rectangle { + width: trayWindowAppsButton.width + height: trayWindowAppsButton.height + anchors.right: rightHoverContainerClipper.right + radius: 10 + color: "white" + opacity: 0.2 + visible: appsBtnMouseArea.containsMouse + } + } + } + } + } + } // Rectangle trayWindowHeaderBackground + + ListModel { + id: activityListModel + } + + ListView { + id: activityListView + anchors.top: trayWindowHeaderBackground.bottom + width: trayWindowBackground.width + height: trayWindowBackground.height - trayWindowHeaderBackground.height + clip: true + + model: activityListModel + + delegate: RowLayout { + id: activityItem + width: activityListView.width + height: trayWindowHeaderLayout.height + spacing: 0 + Image { + id: activityIcon + Layout.leftMargin: 6 + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + verticalAlignment: Qt.AlignCenter + source: "qrc:///client/theme/black/state-sync.svg" + sourceSize.height: 48 + sourceSize.width: 48 + } + Column { + Layout.leftMargin: 6 + spacing: 4 + Layout.alignment: Qt.AlignLeft + Text { + id: activityTextTitle + text: name + font.pointSize: 9 + } + Text { + id: activityTextInfo + text: "Lorem ipsum dolor sit amet" + font.pointSize: 8 + } + } + Item { + id: activityItemFiller + Layout.fillWidth: true + } + Button { + Layout.preferredWidth: activityItem.height + Layout.preferredHeight: activityItem.height + Layout.alignment: Qt.AlignRight + flat: true + display: AbstractButton.IconOnly + icon.source: "qrc:///client/resources/files.svg" + icon.color: "transparent" + } + Button { + Layout.preferredWidth: activityItem.height + Layout.preferredHeight: activityItem.height + Layout.alignment: Qt.AlignRight + flat: true + display: AbstractButton.IconOnly + icon.source: "qrc:///client/resources/public.svg" + icon.color: "transparent" + } + } + + add: Transition { + NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } + } + + remove: Transition { + NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 } + } + + removeDisplaced: Transition { + SequentialAnimation { + PauseAnimation { duration: 100} + NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } + } + } + + displaced: Transition { + NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } + } + + focus: true + + // For interactive ListView/Animation testing only + //Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count }) + //Keys.onTabPressed: model.remove(3) + } + + } // Rectangle trayWindowBackground +} From 66bfccc738684516b29164776b14086a1b65caa9 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 8 Dec 2019 13:18:46 +0100 Subject: [PATCH 025/120] Replaced .engine() by pointer access b/c of backward compatibility Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 6e326c4cd9db..8c27009808f6 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -39,14 +39,15 @@ Systray::Systray() // TODO: make singleton, provide ::instance() , _accountMenuModel(nullptr) { // Create QML tray engine, build component, set C++ backend context used in window.qml + // Use pointer instead of engine() helper function until Qt 5.12 is minimum standard QQmlEngine *engine = new QQmlEngine; QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/init.qml"))); _trayContext = engine->contextForObject(systray.create()); _accountMenuModel = UserModel::instance(); - systray.engine()->addImageProvider("avatars", new ImageProvider); - systray.engine()->rootContext()->setContextProperty("systrayBackend", _accountMenuModel); - + + engine->addImageProvider("avatars", new ImageProvider); + engine->rootContext()->setContextProperty("systrayBackend", _accountMenuModel); // TODO: hack to pass the icon to QML //ctxt->setContextProperty("theme", QLatin1String("colored")); From c45d2212a5a3d4323902ca6cfa26b8742e268875 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 8 Dec 2019 20:18:03 +0100 Subject: [PATCH 026/120] Removal of init.qml plus minor fixes Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 2 +- src/gui/tray/init.qml | 20 -------------------- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 src/gui/tray/init.qml diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 8c27009808f6..caabd6c29c66 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -41,7 +41,7 @@ Systray::Systray() // TODO: make singleton, provide ::instance() // Create QML tray engine, build component, set C++ backend context used in window.qml // Use pointer instead of engine() helper function until Qt 5.12 is minimum standard QQmlEngine *engine = new QQmlEngine; - QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/init.qml"))); + QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); _trayContext = engine->contextForObject(systray.create()); _accountMenuModel = UserModel::instance(); diff --git a/src/gui/tray/init.qml b/src/gui/tray/init.qml deleted file mode 100644 index c918fd9805f3..000000000000 --- a/src/gui/tray/init.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick 2.0 -import Qt.labs.platform 1.1 - -SystemTrayIcon { - visible: true - //icon.source: "qrc:/client/theme/colored/state-offline-32.png" - icon.source: "qrc:/client/theme/colored/state-sync-32.png"; - - Component.onCompleted: { - showMessage("Desktop Client 2.7", "New QML menu!", 1000) - } - - onActivated: { - var component = Qt.createComponent("qrc:/qml/src/gui/tray/window.qml") - var win = component.createObject() - win.show() - win.raise() - win.requestActivate() - } -} From 6a04fe8ab61ea3baf99c79e7248742413deb4a42 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 9 Dec 2019 06:43:20 +0100 Subject: [PATCH 027/120] Remove init.qml from CMakeLists and resource file Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- client.qrc | 1 - src/gui/CMakeLists.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/client.qrc b/client.qrc index 21acbacf4ab9..8f0454c26267 100644 --- a/client.qrc +++ b/client.qrc @@ -33,7 +33,6 @@ - src/gui/tray/init.qml src/gui/tray/window.qml src/gui/tray/UserLine.qml diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index a1b7fc2490f6..35102e0cca90 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -35,7 +35,6 @@ set(client_UI_SRCS addcertificatedialog.ui proxyauthdialog.ui mnemonicdialog.ui - tray/init.qml tray/window.qml tray/UserLine.qml wizard/owncloudadvancedsetuppage.ui From bb45a5f67e6badfb12263e4b3ea1cd0434ffd2a8 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 30 Dec 2019 11:39:21 +0100 Subject: [PATCH 028/120] Added popup show/hide logic Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/owncloudgui.cpp | 3 ++- src/gui/systray.cpp | 7 ++++--- src/gui/systray.h | 3 +++ src/gui/tray/UserModel.cpp | 2 +- src/gui/tray/UserModel.h | 5 ++++- src/gui/tray/window.qml | 13 +++++++++++++ 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index f48a8bb2728e..dd48dc0a3de8 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -188,7 +188,8 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) raiseDialog(_settingsDialog.data()); } #else - slotOpenSettingsDialog(); + UserModel::instance()->showWindow(); + //slotOpenSettingsDialog(); #endif } } diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index caabd6c29c66..3e12e83638de 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -35,14 +35,15 @@ namespace OCC { Systray::Systray() // TODO: make singleton, provide ::instance() : _currentAccount(nullptr) + , _trayComponent(nullptr) , _trayContext(nullptr) , _accountMenuModel(nullptr) { // Create QML tray engine, build component, set C++ backend context used in window.qml // Use pointer instead of engine() helper function until Qt 5.12 is minimum standard QQmlEngine *engine = new QQmlEngine; - QQmlComponent systray(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); - _trayContext = engine->contextForObject(systray.create()); + _trayComponent = new QQmlComponent(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); + _trayContext = engine->contextForObject(_trayComponent->create()); _accountMenuModel = UserModel::instance(); @@ -54,12 +55,12 @@ Systray::Systray() // TODO: make singleton, provide ::instance() //ctxt->setContextProperty("filename", "state-offline"); if (!AccountManager::instance()->accounts().isEmpty()) { - slotChangeActivityModel(AccountManager::instance()->accounts().first()); } //connect(AccountManager::instance(), &AccountManager::accountAdded, // this, &Systray::slotChangeActivityModel); + UserModel::instance()->hideWindow(); } Systray::~Systray() diff --git a/src/gui/systray.h b/src/gui/systray.h index 2a0e22db7f6f..bf532d0d7281 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -47,6 +47,8 @@ class Systray ~Systray(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); + void showWindow(); + void hideWindow(); signals: void currentUserChanged(); @@ -56,6 +58,7 @@ private slots: private: AccountStatePtr _currentAccount; + QQmlComponent *_trayComponent; QQmlContext *_trayContext; UserModel *_accountMenuModel; }; diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 63513003f540..225cb241e0ec 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -114,7 +114,7 @@ Q_INVOKABLE bool UserModel::isCurrentUserConnected() return _users[_currentUserId].isConnected(); } -QImage UserModel::currentUserAvatar() +Q_INVOKABLE QImage UserModel::currentUserAvatar() { return _users[_currentUserId].avatar(); } diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 6d8a7d04f536..aeb2716ee4e0 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -45,9 +45,9 @@ class UserModel : public QAbstractListModel QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - QImage currentUserAvatar(); QImage avatarById(const int &id); + Q_INVOKABLE QImage currentUserAvatar(); Q_INVOKABLE int numUsers(); Q_INVOKABLE bool isCurrentUserConnected(); Q_INVOKABLE QString currentUserName(); @@ -72,6 +72,9 @@ class UserModel : public QAbstractListModel Q_INVOKABLE void newUserSelected(); Q_INVOKABLE void refreshUserMenu(); + Q_INVOKABLE void hideWindow(); + Q_INVOKABLE void showWindow(); + protected: QHash roleNames() const; diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 45d2455d3fb7..e59ae314b535 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -14,6 +14,12 @@ Window { color: "transparent" flags: Qt.FramelessWindowHint + onActiveChanged: { + if(!active) { + trayWindow.hide(); + } + } + Component.onCompleted: { /* desktopAvailableWidth and Height doesn't include the system tray bar but breaks application anyway on windows when using multi monitor setup, @@ -32,6 +38,13 @@ Window { onNewUserSelected: { accountMenu.close() } + onShowWindow: { + trayWindow.show(); + trayWindow.requestActivate(); + } + onHideWindow: { + trayWindow.hide(); + } } Rectangle { From 6f8ffc03577379ebbc6dbf86e424d15e315d947e Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 30 Dec 2019 11:52:07 +0100 Subject: [PATCH 029/120] Backend code separation & structure cleanup Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/owncloudgui.cpp | 2 +- src/gui/systray.cpp | 8 +++----- src/gui/systray.h | 6 +++--- src/gui/tray/UserModel.h | 3 --- src/gui/tray/window.qml | 34 +++++++++++++++++++--------------- 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index dd48dc0a3de8..c975a162e458 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -188,7 +188,7 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) raiseDialog(_settingsDialog.data()); } #else - UserModel::instance()->showWindow(); + _tray->showWindow(); //slotOpenSettingsDialog(); #endif } diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 3e12e83638de..97f513706250 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -37,7 +37,6 @@ Systray::Systray() // TODO: make singleton, provide ::instance() : _currentAccount(nullptr) , _trayComponent(nullptr) , _trayContext(nullptr) - , _accountMenuModel(nullptr) { // Create QML tray engine, build component, set C++ backend context used in window.qml // Use pointer instead of engine() helper function until Qt 5.12 is minimum standard @@ -45,10 +44,9 @@ Systray::Systray() // TODO: make singleton, provide ::instance() _trayComponent = new QQmlComponent(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); _trayContext = engine->contextForObject(_trayComponent->create()); - _accountMenuModel = UserModel::instance(); - engine->addImageProvider("avatars", new ImageProvider); - engine->rootContext()->setContextProperty("systrayBackend", _accountMenuModel); + engine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); + engine->rootContext()->setContextProperty("systrayBackend", this); // TODO: hack to pass the icon to QML //ctxt->setContextProperty("theme", QLatin1String("colored")); @@ -60,7 +58,7 @@ Systray::Systray() // TODO: make singleton, provide ::instance() //connect(AccountManager::instance(), &AccountManager::accountAdded, // this, &Systray::slotChangeActivityModel); - UserModel::instance()->hideWindow(); + hideWindow(); } Systray::~Systray() diff --git a/src/gui/systray.h b/src/gui/systray.h index bf532d0d7281..dde45be1a9d6 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -47,12 +47,13 @@ class Systray ~Systray(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); - void showWindow(); - void hideWindow(); signals: void currentUserChanged(); + Q_INVOKABLE void hideWindow(); + Q_INVOKABLE void showWindow(); + private slots: void slotChangeActivityModel(const AccountStatePtr account); @@ -60,7 +61,6 @@ private slots: AccountStatePtr _currentAccount; QQmlComponent *_trayComponent; QQmlContext *_trayContext; - UserModel *_accountMenuModel; }; } // namespace OCC diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index aeb2716ee4e0..d6a3ec7c704b 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -72,9 +72,6 @@ class UserModel : public QAbstractListModel Q_INVOKABLE void newUserSelected(); Q_INVOKABLE void refreshUserMenu(); - Q_INVOKABLE void hideWindow(); - Q_INVOKABLE void showWindow(); - protected: QHash roleNames() const; diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index e59ae314b535..12eb10e7fa21 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -29,15 +29,19 @@ Window { } Connections { - target: systrayBackend + target: userModelBackend onRefreshCurrentUserGui: { - currentAccountAvatar.source = systrayBackend.currentUserAvatar() - currentAccountUser.text = systrayBackend.currentUserName() - currentAccountServer.text = systrayBackend.currentUserServer() + currentAccountAvatar.source = userModelBackend.currentUserAvatar() + currentAccountUser.text = userModelBackend.currentUserName() + currentAccountServer.text = userModelBackend.currentUserServer() } onNewUserSelected: { accountMenu.close() } + } + + Connections { + target: systrayBackend onShowWindow: { trayWindow.show(); trayWindow.requestActivate(); @@ -102,7 +106,7 @@ Window { } Instantiator { - model: systrayBackend + model: userModelBackend delegate: UserLine {} onObjectAdded: accountMenu.insertItem(index, object) onObjectRemoved: accountMenu.removeItem(object) @@ -111,22 +115,22 @@ Window { MenuSeparator { id: accountMenuSeparator } MenuItem { - text: (systrayBackend.isCurrentUserConnected() ? "Logout" : "Login") - onClicked: (systrayBackend.isCurrentUserConnected() - ? systrayBackend.logout() - : systrayBackend.login() ) + text: (userModelBackend.isCurrentUserConnected() ? "Logout" : "Login") + onClicked: (userModelBackend.isCurrentUserConnected() + ? userModelBackend.logout() + : userModelBackend.login() ) } MenuItem { text: "Add Account" - onClicked: systrayBackend.addAccount() + onClicked: userModelBackend.addAccount() } MenuItem { text: "Remove Account" - onClicked: systrayBackend.removeAccount() + onClicked: userModelBackend.removeAccount() } Component.onCompleted: { - if(systrayBackend.numUsers() === 0) { + if(userModelBackend.numUsers() === 0) { accountMenuSeparator.height = 0 } else { accountMenuSeparator.height = 13 @@ -190,7 +194,7 @@ Window { id: currentAccountAvatar Layout.leftMargin: 8 verticalAlignment: Qt.AlignCenter - source: systrayBackend.currentUserAvatar() + source: userModelBackend.currentUserAvatar() Layout.preferredHeight: (trayWindowHeaderBackground.height -16) Layout.preferredWidth: (trayWindowHeaderBackground.height -16) } @@ -202,14 +206,14 @@ Window { Layout.leftMargin: 6 Label { id: currentAccountUser - text: systrayBackend.currentUserName() + text: userModelBackend.currentUserName() color: "white" font.pointSize: 9 font.bold: true } Label { id: currentAccountServer - text: systrayBackend.currentUserServer() + text: userModelBackend.currentUserServer() color: "white" font.pointSize: 8 } From 44bfc79caac1bbd8e1187fc84435219ea2af2a9b Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 30 Dec 2019 14:14:28 +0100 Subject: [PATCH 030/120] Implemented platform agnostic tray window positioning logic Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 58 +++++++++++++++++++++++++++++++++++++++++ src/gui/systray.h | 3 +++ src/gui/tray/window.qml | 10 ++----- 3 files changed, 63 insertions(+), 8 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 97f513706250..520450dd9829 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -18,8 +18,10 @@ #include "config.h" #include "tray/UserModel.h" +#include #include #include +#include #ifdef USE_FDO_NOTIFICATIONS #include @@ -97,4 +99,60 @@ void Systray::setToolTip(const QString &tip) QSystemTrayIcon::setToolTip(tr("%1: %2").arg(Theme::instance()->appNameGUI(), tip)); } +int Systray::calcTrayWindowX() +{ + QScreen* trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); + // get coordinates from top center point of tray icon + int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); + int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); + + if ( (trayScreen->geometry().width() - trayIconTopCenterX) < (trayScreen->geometry().width() * 0.5) ) { + // tray icon is on right side of the screen + if ( ((trayScreen->geometry().width() - trayIconTopCenterX) < trayScreen->geometry().height() - trayIconTopCenterY) + && ((trayScreen->geometry().width() - trayIconTopCenterX) < trayIconTopCenterY) ) { + // taskbar is on the right + return trayScreen->availableSize().width() - 400 - 6; + } else { + // taskbar is on the bottom or top + if (trayIconTopCenterX - (400 * 0.5) < 0) { + return 6; + } else if (trayIconTopCenterX - (400 * 0.5) > trayScreen->geometry().width()) { + return trayScreen->geometry().width() - 406; + } else { + return trayIconTopCenterX - (400 * 0.5); + } + } + } else { + // tray icon is on left side of the screen + return (trayScreen->geometry().width() - trayScreen->availableGeometry().width()) + 6; + } +} +int Systray::calcTrayWindowY() +{ + QScreen *trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); + // get coordinates from top center point of tray icon + int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); + int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); + + if ( (trayScreen->geometry().height() - trayIconTopCenterY) < (trayScreen->geometry().height() * 0.5) ) { + // tray icon is on bottom side of the screen + if ( ((trayScreen->geometry().height() - trayIconTopCenterY) < trayScreen->geometry().width() - trayIconTopCenterX ) + && ((trayScreen->geometry().height() - trayIconTopCenterY) < trayIconTopCenterX) ) { + // taskbar is on the bottom + return trayScreen->availableSize().height() - 500 - 6; + } else { + // taskbar is on the right or left + if (trayIconTopCenterY - 500 > 0) { + return trayIconTopCenterY - 500; + } else { + return 6; + } + } + } else { + // tray icon is on the top + return (trayScreen->geometry().height() - trayScreen->availableGeometry().height()) + 6; + } +} + + } // namespace OCC diff --git a/src/gui/systray.h b/src/gui/systray.h index dde45be1a9d6..f99f024e673e 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -48,6 +48,9 @@ class Systray void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); + Q_INVOKABLE int calcTrayWindowX(); + Q_INVOKABLE int calcTrayWindowY(); + signals: void currentUserChanged(); diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 12eb10e7fa21..b01df1cf585e 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -20,14 +20,6 @@ Window { } } - Component.onCompleted: { - /* desktopAvailableWidth and Height doesn't include the system tray bar - but breaks application anyway on windows when using multi monitor setup, - will look for a better solution later, for now just get this thing complete */ - //setX(Screen.desktopAvailableWidth - width); - //setY(Screen.desktopAvailableHeight + height); - } - Connections { target: userModelBackend onRefreshCurrentUserGui: { @@ -45,6 +37,8 @@ Window { onShowWindow: { trayWindow.show(); trayWindow.requestActivate(); + trayWindow.setX( systrayBackend.calcTrayWindowX()); + trayWindow.setY( systrayBackend.calcTrayWindowY()); } onHideWindow: { trayWindow.hide(); From d4b334636e70263f3e8344102dfb2dd8196c548b Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 2 Jan 2020 08:56:07 +0100 Subject: [PATCH 031/120] Fall back to primary screen on QT version < 5.10 instead of icon position detection Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 520450dd9829..642b49453be3 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -101,7 +101,12 @@ void Systray::setToolTip(const QString &tip) int Systray::calcTrayWindowX() { - QScreen* trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + QScreen *trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); +#else + QScreen *trayScreen = QGuiApplication::primaryScreen(); +#endif + // get coordinates from top center point of tray icon int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); @@ -129,7 +134,12 @@ int Systray::calcTrayWindowX() } int Systray::calcTrayWindowY() { +#if QT_VERSION >= QT_VERSION_CHECK(5,10,0) QScreen *trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); +#else + QScreen *trayScreen = QGuiApplication::primaryScreen(); +#endif + // get coordinates from top center point of tray icon int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); From 36467c1e3a6db6244bcf12b3b4151f6eaba28c9b Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 2 Jan 2020 09:55:37 +0100 Subject: [PATCH 032/120] Added folder search for User instance, added open folder button connection Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 20 ++++++++++++++++++++ src/gui/tray/UserModel.h | 7 ++++--- src/gui/tray/window.qml | 3 +-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 225cb241e0ec..2b89b0cc93cd 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -2,6 +2,7 @@ #include "owncloudgui.h" #include "UserModel.h" +#include #include namespace OCC { @@ -22,6 +23,20 @@ void User::setCurrentUser(const bool &isCurrent) _isCurrentUser = isCurrent; } +Folder* User::getFolder() +{ + foreach (Folder *folder, FolderMan::instance()->map()) { + if (folder->accountState() == _account.data()) { + return folder; + } + } +} + +void User::openLocalFolder() +{ + QDesktopServices::openUrl(this->getFolder()->path()); +} + QString User::name() const { // If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username @@ -145,6 +160,11 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) endInsertRows(); } +Q_INVOKABLE void UserModel::openCurrentAccountLocalFolder() +{ + _users[_currentUserId].openLocalFolder(); +} + Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) { _users[_currentUserId].setCurrentUser(false); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index d6a3ec7c704b..848798d83beb 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -7,6 +7,7 @@ #include #include "accountmanager.h" +#include "folderman.h" namespace OCC { @@ -20,8 +21,8 @@ class User bool isConnected() const; bool isCurrentUser() const; void setCurrentUser(const bool &isCurrent); - void login(); - void logout(); + Folder* getFolder(); + void openLocalFolder(); QString name() const; QString server() const; QImage avatar() const; @@ -47,6 +48,7 @@ class UserModel : public QAbstractListModel QImage avatarById(const int &id); + Q_INVOKABLE void openCurrentAccountLocalFolder(); Q_INVOKABLE QImage currentUserAvatar(); Q_INVOKABLE int numUsers(); Q_INVOKABLE bool isCurrentUserConnected(); @@ -62,7 +64,6 @@ class UserModel : public QAbstractListModel }; signals: - Q_INVOKABLE void openLocalFolder(); Q_INVOKABLE void login(); Q_INVOKABLE void logout(); Q_INVOKABLE void addAccount(); diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index b01df1cf585e..803d8fbae726 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -217,7 +217,6 @@ Window { Layout.alignment: Qt.AlignLeft verticalAlignment: Qt.AlignCenter Layout.margins: 12 - //source: "../../theme/white/caret-down.svg" source: "qrc:///client/theme/white/caret-down.svg" } } @@ -236,7 +235,6 @@ Window { Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - //icon.source: "../../theme/white/folder.svg" icon.source: "qrc:///client/theme/white/folder.svg" icon.color: "transparent" @@ -246,6 +244,7 @@ Window { hoverEnabled: true onClicked: { + userModelBackend.openCurrentAccountLocalFolder(); } } From 556a1a5ef2b132e39bd199b96040f41e84286928 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 2 Jan 2020 10:39:53 +0100 Subject: [PATCH 033/120] Added ActivityModel in new tray backend, implemented as delegate in UI Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 1 + src/gui/tray/UserModel.cpp | 78 ++++++++++++++++++++++++++++++++++---- src/gui/tray/UserModel.h | 36 +++++++++++++++++- src/gui/tray/window.qml | 12 ++---- 4 files changed, 110 insertions(+), 17 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 642b49453be3..97d6a1a03444 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -48,6 +48,7 @@ Systray::Systray() // TODO: make singleton, provide ::instance() engine->addImageProvider("avatars", new ImageProvider); engine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); + engine->rootContext()->setContextProperty("activityModel", ActivityModel::instance()); engine->rootContext()->setContextProperty("systrayBackend", this); // TODO: hack to pass the icon to QML diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 2b89b0cc93cd..95591226f1e6 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -23,7 +23,7 @@ void User::setCurrentUser(const bool &isCurrent) _isCurrentUser = isCurrent; } -Folder* User::getFolder() +Folder *User::getFolder() { foreach (Folder *folder, FolderMan::instance()->map()) { if (folder->accountState() == _account.data()) { @@ -99,12 +99,6 @@ UserModel::UserModel(QObject *parent) , _currentUserId() { // TODO: Remember selected user from last quit via settings file - // this is the reason why this looks like an unnecessary double check atm - /*if (AccountManager::instance()->accounts().size() > 0) { - addUser(AccountManager::instance()->accounts().first(), true); - } else { - return; - }*/ if (AccountManager::instance()->accounts().size() > 0) { initUserList(); } @@ -212,6 +206,76 @@ QHash UserModel::roleNames() const /*-------------------------------------------------------------------------------------*/ +QString UserActivity::type() const +{ + return "Test"; +} +QString UserActivity::fileName() const +{ + return "Test"; +} + +QString UserActivity::info() const +{ + return "Test"; +} + + +ActivityModel *ActivityModel::_instance = nullptr; + +ActivityModel *ActivityModel::instance() +{ + if (_instance == nullptr) { + _instance = new ActivityModel(); + } + return _instance; +} + +ActivityModel::ActivityModel(QObject *parent) + : QAbstractListModel() +{ +} + +QHash ActivityModel::roleNames() const +{ + QHash roles; + roles[TypeRole] = "type"; + roles[FileNameRole] = "filename"; + roles[InfoRole] = "info"; + return roles; +} + +int ActivityModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return _activities.count(); +} + +QVariant ActivityModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= _activities.count()) { + return QVariant(); + } + + const UserActivity &activity = _activities[index.row()]; + if (role == TypeRole) + return activity.type(); + else if (role == FileNameRole) + return activity.fileName(); + else if (role == InfoRole) + return activity.info(); + return QVariant(); +} + +void ActivityModel::addActivity(const UserActivity &activity) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _activities << activity; + endInsertRows(); +} + +/*-------------------------------------------------------------------------------------*/ + ImageProvider::ImageProvider() : QQuickImageProvider(QQuickImageProvider::Image) { diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 848798d83beb..35eb3f9c2d4f 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -21,7 +21,7 @@ class User bool isConnected() const; bool isCurrentUser() const; void setCurrentUser(const bool &isCurrent); - Folder* getFolder(); + Folder *getFolder(); void openLocalFolder(); QString name() const; QString server() const; @@ -85,6 +85,40 @@ class UserModel : public QAbstractListModel void initUserList(); }; +class UserActivity +{ +public: + QString type() const; + QString fileName() const; + QString info() const; +}; + +class ActivityModel : public QAbstractListModel +{ + Q_OBJECT +public: + static ActivityModel *instance(); + virtual ~ActivityModel() {}; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + void addActivity(const UserActivity &activity); + + enum ActivityRoles { + TypeRole = Qt::UserRole + 1, + FileNameRole, + InfoRole + }; + +protected: + QHash roleNames() const; + +private: + static ActivityModel *_instance; + ActivityModel(QObject *parent = 0); + QList _activities; +}; + class ImageProvider : public QQuickImageProvider { public: diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 803d8fbae726..28e61f466518 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -263,7 +263,6 @@ Window { Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - //icon.source: "../../theme/white/talk-app.svg" icon.source: "qrc:///client/theme/white/talk-app.svg" icon.color: "transparent" @@ -291,7 +290,6 @@ Window { Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - //icon.source: "../../theme/white/more-apps.svg" icon.source: "qrc:///client/theme/white/more-apps.svg" icon.color: "transparent" @@ -355,10 +353,6 @@ Window { } } // Rectangle trayWindowHeaderBackground - ListModel { - id: activityListModel - } - ListView { id: activityListView anchors.top: trayWindowHeaderBackground.bottom @@ -366,7 +360,7 @@ Window { height: trayWindowBackground.height - trayWindowHeaderBackground.height clip: true - model: activityListModel + model: activityModel delegate: RowLayout { id: activityItem @@ -389,12 +383,12 @@ Window { Layout.alignment: Qt.AlignLeft Text { id: activityTextTitle - text: name + text: filename font.pointSize: 9 } Text { id: activityTextInfo - text: "Lorem ipsum dolor sit amet" + text: info font.pointSize: 8 } } From b32310b8a6b298e7bc96df14205e00d8bd1f8c00 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 13:09:29 +0100 Subject: [PATCH 034/120] Gigantic ton of changes and deletions: ActivityListModel, tray GUI, Account logic. Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/CMakeLists.txt | 10 +- src/gui/accountsettings.ui | 2 +- src/gui/accountstate.cpp | 25 +- src/gui/accountstate.h | 4 + src/gui/activityitemdelegate.cpp | 407 ----------- src/gui/activityitemdelegate.h | 94 --- src/gui/activitywidget.cpp | 653 ------------------ src/gui/activitywidget.h | 165 ----- src/gui/activitywidget.ui | 98 --- src/gui/generalsettings.ui | 2 +- src/gui/networksettings.ui | 2 +- src/gui/owncloudgui.cpp | 11 - src/gui/servernotificationhandler.cpp | 165 ----- src/gui/servernotificationhandler.h | 50 -- src/gui/settingsdialog.cpp | 72 +- src/gui/settingsdialog.h | 6 - src/gui/settingsdialog.ui | 2 +- src/gui/systray.cpp | 17 +- src/gui/systray.h | 1 + .../ActivityData.cpp} | 0 .../{activitydata.h => tray/ActivityData.h} | 0 .../ActivityListModel.cpp} | 37 +- .../ActivityListModel.h} | 17 +- src/gui/tray/UserLine.qml | 7 +- src/gui/tray/UserModel.cpp | 99 +-- src/gui/tray/UserModel.h | 42 +- src/gui/tray/window.qml | 26 +- src/libsync/capabilities.cpp | 3 +- 28 files changed, 139 insertions(+), 1878 deletions(-) delete mode 100644 src/gui/activityitemdelegate.cpp delete mode 100644 src/gui/activityitemdelegate.h delete mode 100644 src/gui/activitywidget.cpp delete mode 100644 src/gui/activitywidget.h delete mode 100644 src/gui/activitywidget.ui delete mode 100644 src/gui/servernotificationhandler.cpp delete mode 100644 src/gui/servernotificationhandler.h rename src/gui/{activitydata.cpp => tray/ActivityData.cpp} (100%) rename src/gui/{activitydata.h => tray/ActivityData.h} (100%) rename src/gui/{activitylistmodel.cpp => tray/ActivityListModel.cpp} (93%) rename src/gui/{activitylistmodel.h => tray/ActivityListModel.h} (88%) diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 9b0455a6b5bd..af364ca4ed3d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -24,7 +24,6 @@ set(client_UI_SRCS ignorelisteditor.ui ignorelisttablewidget.ui networksettings.ui - activitywidget.ui synclogdialog.ui settingsdialog.ui sharedialog.ui @@ -35,7 +34,7 @@ set(client_UI_SRCS addcertificatedialog.ui proxyauthdialog.ui mnemonicdialog.ui - tray/window.qml + tray/Window.qml tray/UserLine.qml wizard/flow2authwidget.ui wizard/owncloudadvancedsetuppage.ui @@ -75,10 +74,6 @@ set(client_SRCS openfilemanager.cpp owncloudgui.cpp owncloudsetupwizard.cpp - activitydata.cpp - activitylistmodel.cpp - activitywidget.cpp - activityitemdelegate.cpp selectivesyncdialog.cpp settingsdialog.cpp sharedialog.cpp @@ -101,12 +96,13 @@ set(client_SRCS synclogdialog.cpp tooltipupdater.cpp notificationconfirmjob.cpp - servernotificationhandler.cpp guiutility.cpp elidedlabel.cpp headerbanner.cpp iconjob.cpp remotewipe.cpp + tray/ActivityData.cpp + tray/ActivityListModel.cpp tray/UserModel.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui index f52adf4c73b2..6249018bd19b 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -6,7 +6,7 @@ 0 0 - 582 + 516 557 diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 7893a7a3b332..9d2fa1830f57 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -74,6 +74,11 @@ AccountPtr AccountState::account() const return _account; } +bool AccountState::hasTalk() const +{ + return _hasTalk; +} + AccountState::ConnectionStatus AccountState::connectionStatus() const { return _connectionStatus; @@ -89,6 +94,23 @@ AccountState::State AccountState::state() const return _state; } +void AccountState::setTalkCapability() +{ + QNetworkAccessManager *manager = new QNetworkAccessManager(this); + connect(manager, &QNetworkAccessManager::finished, + this, [=](QNetworkReply *reply) { + if (reply->error()) { + return; + } + //TODO: This is **** dirty, but atm I don't see a capability-way to determine talk integration + _hasTalk = (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 303) ? true : false; + }); + QNetworkRequest request; + QString url = this->account()->url().toString() + "/apps/spreed"; + request.setUrl(QUrl(url)); + manager->get(request); +} + void AccountState::setState(State state) { if (_state != state) { @@ -267,7 +289,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta // Come online gradually from 503 or maintenance mode if (status == ConnectionValidator::Connected && (_connectionStatus == ConnectionValidator::ServiceUnavailable - || _connectionStatus == ConnectionValidator::MaintenanceMode)) { + || _connectionStatus == ConnectionValidator::MaintenanceMode)) { if (!_timeSinceMaintenanceOver.isValid()) { qCInfo(lcAccountState) << "AccountState reconnection: delaying for" << _maintenanceToConnectedDelay << "ms"; @@ -293,6 +315,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta case ConnectionValidator::Connected: if (_state != Connected) { setState(Connected); + setTalkCapability(); } break; case ConnectionValidator::Undefined: diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 6ed9aa265692..7633bd78dfa2 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -101,6 +101,8 @@ class AccountState : public QObject, public QSharedData bool isSignedOut() const; + bool hasTalk() const; + /** A user-triggered sign out which disconnects, stops syncs * for the account and forgets the password. */ void signOutByUi(); @@ -161,6 +163,7 @@ public slots: private: void setState(State state); + void setTalkCapability(); signals: void stateChanged(int state); @@ -182,6 +185,7 @@ protected Q_SLOTS: ConnectionStatus _connectionStatus; QStringList _connectionErrors; bool _waitingForNewCredentials; + bool _hasTalk; QElapsedTimer _timeSinceLastETagCheck; QPointer _connectionValidator; QByteArray _notificationsEtagResponseHeader; diff --git a/src/gui/activityitemdelegate.cpp b/src/gui/activityitemdelegate.cpp deleted file mode 100644 index 2b4231e07292..000000000000 --- a/src/gui/activityitemdelegate.cpp +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include "activityitemdelegate.h" -#include "activitylistmodel.h" -#include "folderstatusmodel.h" -#include "folderman.h" -#include "accountstate.h" -#include "activitydata.h" -#include -#include - -#include -#include -#include - -#define FIXME_USE_HIGH_DPI_RATIO -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - #include -#endif - -#define HASQT5_11 (QT_VERSION >= QT_VERSION_CHECK(5,11,0)) - -namespace OCC { - -int ActivityItemDelegate::_iconHeight = 0; -int ActivityItemDelegate::_margin = 0; -int ActivityItemDelegate::_primaryButtonWidth = 0; -int ActivityItemDelegate::_secondaryButtonWidth = 0; -int ActivityItemDelegate::_spaceBetweenButtons = 0; -int ActivityItemDelegate::_timeWidth = 0; -int ActivityItemDelegate::_buttonHeight = 0; -const QString ActivityItemDelegate::_remote_share("remote_share"); -const QString ActivityItemDelegate::_call("call"); - -ActivityItemDelegate::ActivityItemDelegate() - : QStyledItemDelegate() -{ - customizeStyle(); -} - -int ActivityItemDelegate::iconHeight() -{ - if (_iconHeight == 0) { - QStyleOptionViewItem option; - QFont font = option.font; - - QFontMetrics fm(font); - - _iconHeight = qRound(fm.height() / 5.0 * 8.0); - } - return _iconHeight; -} - -int ActivityItemDelegate::rowHeight() -{ - if (_margin == 0) { - QStyleOptionViewItem opt; - - QFont f = opt.font; - QFontMetrics fm(f); - - _margin = fm.height() / 2; - -#if defined(Q_OS_WIN) - _margin += 5; -#endif - } - return iconHeight() + 5 * _margin; -} - -QSize ActivityItemDelegate::sizeHint(const QStyleOptionViewItem &option, - const QModelIndex & /* index */) const -{ - QFont font = option.font; - - return QSize(0, rowHeight()); -} - -void ActivityItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, - const QModelIndex &index) const -{ - QStyledItemDelegate::paint(painter, option, index); - QFont font = option.font; - QFontMetrics fm(font); - int margin = fm.height() / 2.5; - painter->save(); - int iconSize = 16; - int iconOffset = qRound(fm.height() / 4.0 * 7.0); - int offset = 4; - const bool isSelected = (option.state & QStyle::State_Selected); -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - const int device_pixel_ration = QApplication::desktop()->devicePixelRatio(); - int pixel_ratio = (device_pixel_ration > 1 ? device_pixel_ration : 1); -#endif - - // get the data - Activity::Type activityType = qvariant_cast(index.data(ActionRole)); - QIcon actionIcon; - const ActivityListModel::ActionIcon icn = qvariant_cast(index.data(ActionIconRole)); - switch(icn.iconType) { - case ActivityListModel::ActivityIconType::iconUseCached: actionIcon = icn.cachedIcon; break; - case ActivityListModel::ActivityIconType::iconActivity: actionIcon = (isSelected ? _iconActivity_sel : _iconActivity); break; - case ActivityListModel::ActivityIconType::iconBell: actionIcon = (isSelected ? _iconBell_sel : _iconBell); break; - case ActivityListModel::ActivityIconType::iconStateError: actionIcon = _iconStateError; break; - case ActivityListModel::ActivityIconType::iconStateWarning: actionIcon = _iconStateWarning; break; - case ActivityListModel::ActivityIconType::iconStateInfo: actionIcon = _iconStateInfo; break; - case ActivityListModel::ActivityIconType::iconStateSync: actionIcon = _iconStateSync; break; - } - QString objectType = qvariant_cast(index.data(ObjectTypeRole)); - QString actionText = qvariant_cast(index.data(ActionTextRole)); - QString messageText = qvariant_cast(index.data(MessageRole)); - QList customList = index.data(ActionsLinksRole).toList(); - QString timeText = qvariant_cast(index.data(PointInTimeRole)); - bool accountOnline = qvariant_cast(index.data(AccountConnectedRole)); - - // activity/notification icons - QRect actionIconRect = option.rect; - actionIconRect.setLeft(option.rect.left() + iconOffset/3); - actionIconRect.setRight(option.rect.left() + iconOffset); - actionIconRect.setTop(option.rect.top() + qRound((option.rect.height() - 16)/3.0)); - - // subject text rect - QRect actionTextBox = actionIconRect; -#if (HASQT5_11) - int actionTextBoxWidth = fm.horizontalAdvance(actionText); -#else - int actionTextBoxWidth = fm.width(actionText); -#endif - actionTextBox.setTop(option.rect.top() + margin + offset/2); - actionTextBox.setHeight(fm.height()); - actionTextBox.setLeft(actionIconRect.right() + margin); -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - actionTextBoxWidth *= pixel_ratio; -#endif - actionTextBox.setRight(actionTextBox.left() + actionTextBoxWidth + margin); - - // message text rect - QRect messageTextBox = actionTextBox; -#if (HASQT5_11) - int messageTextWidth = fm.horizontalAdvance(messageText); -#else - int messageTextWidth = fm.width(messageText); -#endif - int messageTextTop = option.rect.top() + fm.height() + margin; - if(actionText.isEmpty()) messageTextTop = option.rect.top() + margin + offset/2; - messageTextBox.setTop(messageTextTop); - messageTextBox.setHeight(fm.height()); - messageTextBox.setBottom(messageTextBox.top() + fm.height()); - messageTextBox.setRight(messageTextBox.left() + messageTextWidth + margin); - if(messageText.isEmpty()){ - messageTextBox.setHeight(0); - messageTextBox.setBottom(messageTextBox.top()); - } - - // time box rect - QRect timeBox = messageTextBox; -#if (HASQT5_11) - int timeTextWidth = fm.horizontalAdvance(timeText); -#else - int timeTextWidth = fm.width(timeText); -#endif - int timeTop = option.rect.top() + fm.height() + fm.height() + margin + offset/2; - if(messageText.isEmpty() || actionText.isEmpty()) - timeTop = option.rect.top() + fm.height() + margin; - timeBox.setTop(timeTop); - timeBox.setHeight(fm.height()); - timeBox.setBottom(timeBox.top() + fm.height()); -#ifdef FIXME_USE_HIGH_DPI_RATIO - // FIXME: Find a better way to calculate the text width on high-dpi displays (Retina). - timeTextWidth *= pixel_ratio; -#endif - timeBox.setRight(timeBox.left() + timeTextWidth + margin); - - // buttons - default values - int rightMargin = margin; - int leftMargin = margin * offset; - int top = option.rect.top() + margin; - int buttonSize = option.rect.height()/2; - int right = option.rect.right() - rightMargin; - int left = right - buttonSize; - - QStyleOptionButton secondaryButton; - secondaryButton.rect = option.rect; - secondaryButton.features |= QStyleOptionButton::Flat; - secondaryButton.state |= QStyle::State_None; - secondaryButton.rect.setLeft(left); - secondaryButton.rect.setRight(right); - secondaryButton.rect.setTop(top + margin); - secondaryButton.rect.setHeight(iconSize); - - QStyleOptionButton primaryButton; - primaryButton.rect = option.rect; - primaryButton.features |= QStyleOptionButton::DefaultButton; - primaryButton.state |= QStyle::State_Raised; - primaryButton.rect.setTop(top); - primaryButton.rect.setHeight(buttonSize); - - right = secondaryButton.rect.left() - rightMargin; - left = secondaryButton.rect.left() - leftMargin; - - primaryButton.rect.setRight(right); - - if(activityType == Activity::Type::NotificationType){ - - // Secondary will be 'Dismiss' or '...' multiple options button - secondaryButton.icon = (isSelected ? _iconClose_sel : _iconClose); - if(customList.size() > 1) - secondaryButton.icon = (isSelected ? _iconMore_sel : _iconMore); - secondaryButton.iconSize = QSize(iconSize, iconSize); - - // Primary button will be 'More Information' or 'Accept' - primaryButton.text = tr("More information"); - if(objectType == _remote_share) primaryButton.text = tr("Accept"); - if(objectType == _call) primaryButton.text = tr("Join"); - -#if (HASQT5_11) - primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text)); -#else - primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text)); -#endif - - // save info to be able to filter mouse clicks - _buttonHeight = buttonSize; - _primaryButtonWidth = primaryButton.rect.size().width(); - _secondaryButtonWidth = secondaryButton.rect.size().width(); - _spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right(); - - } else if(activityType == Activity::SyncResultType){ - - // Secondary will be 'open file manager' with the folder icon - secondaryButton.icon = _iconFolder; - secondaryButton.iconSize = QSize(iconSize, iconSize); - - // Primary button will be 'open browser' - primaryButton.text = tr("Open Browser"); - -#if (HASQT5_11) - primaryButton.rect.setLeft(left - margin * 2 - fm.horizontalAdvance(primaryButton.text)); -#else - primaryButton.rect.setLeft(left - margin * 2 - fm.width(primaryButton.text)); -#endif - - // save info to be able to filter mouse clicks - _buttonHeight = buttonSize; - _primaryButtonWidth = primaryButton.rect.size().width(); - _secondaryButtonWidth = secondaryButton.rect.size().width(); - _spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right(); - - } else if(activityType == Activity::SyncFileItemType){ - - // Secondary will be 'open file manager' with the folder icon - secondaryButton.icon = _iconFolder; - secondaryButton.iconSize = QSize(iconSize, iconSize); - - // No primary button on this case - // Whatever error we have at this case it is local, there is no point on opening the browser - _primaryButtonWidth = 0; - _secondaryButtonWidth = secondaryButton.rect.size().width(); - _spaceBetweenButtons = secondaryButton.rect.left() - primaryButton.rect.right(); - - } else { - _spaceBetweenButtons = leftMargin; - _primaryButtonWidth = 0; - _secondaryButtonWidth = 0; - } - - // draw the icon - QPixmap pm = actionIcon.pixmap(iconSize, iconSize, QIcon::Normal); - painter->drawPixmap(QPoint(actionIconRect.left(), actionIconRect.top()), pm); - - // change pen color if use is not online - QPalette p = option.palette; - if(!accountOnline) - p.setCurrentColorGroup(QPalette::Disabled); - - // change pen color if the line is selected - if (isSelected) - painter->setPen(p.color(QPalette::HighlightedText)); - else - painter->setPen(p.color(QPalette::Text)); - - // calculate space for text - use the max possible before using the elipses - int spaceLeftForText = option.rect.width() - (actionIconRect.width() + margin + rightMargin + leftMargin) - - (_primaryButtonWidth + _secondaryButtonWidth + _spaceBetweenButtons); - - // draw the subject - const QString elidedAction = fm.elidedText(actionText, Qt::ElideRight, spaceLeftForText); - painter->drawText(actionTextBox, elidedAction); - - // draw the buttons - if(activityType == Activity::Type::NotificationType || activityType == Activity::Type::SyncResultType) { - primaryButton.palette = p; - if (isSelected) - primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::HighlightedText)); - else - primaryButton.palette.setColor(QPalette::ButtonText, p.color(QPalette::Text)); - - QApplication::style()->drawControl(QStyle::CE_PushButton, &primaryButton, painter); - } - - // Since they are errors on local syncing, there is nothing to do in the server - if(activityType != Activity::Type::ActivityType) - QApplication::style()->drawControl(QStyle::CE_PushButton, &secondaryButton, painter); - - // draw the message - // change pen color for the message - if(!messageText.isEmpty()){ - const QString elidedMessage = fm.elidedText(messageText, Qt::ElideRight, spaceLeftForText); - painter->drawText(messageTextBox, elidedMessage); - } - - // change pen color for the time - if (isSelected) - painter->setPen(p.color(QPalette::Disabled, QPalette::HighlightedText)); - else - painter->setPen(p.color(QPalette::Disabled, QPalette::Text)); - - // draw the time - const QString elidedTime = fm.elidedText(timeText, Qt::ElideRight, spaceLeftForText); - painter->drawText(timeBox, elidedTime); - - painter->restore(); -} - -bool ActivityItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index) -{ - Activity::Type activityType = qvariant_cast(index.data(ActionRole)); - if(activityType != Activity::Type::ActivityType){ - if (event->type() == QEvent::MouseButtonRelease){ - QMouseEvent *mouseEvent = (QMouseEvent*)event; - if(mouseEvent){ - int mouseEventX = mouseEvent->x(); - int mouseEventY = mouseEvent->y(); - int buttonsWidth = _primaryButtonWidth + _spaceBetweenButtons + _secondaryButtonWidth; - int x = option.rect.left() + option.rect.width() - buttonsWidth - _timeWidth; - int y = option.rect.top(); - - // clickable area for ... - if (mouseEventX > x && mouseEventX < x + buttonsWidth){ - if(mouseEventY > y && mouseEventY < y + _buttonHeight){ - - // ...primary button ('more information' or 'accept' on notifications or 'open browser' on errors) - if (mouseEventX > x && mouseEventX < x + _primaryButtonWidth){ - emit primaryButtonClickedOnItemView(index); - - // ...secondary button ('dismiss' on notifications or 'open file manager' on errors) - } else { - x += _primaryButtonWidth + _spaceBetweenButtons; - if (mouseEventX > x && mouseEventX < x + _secondaryButtonWidth) - emit secondaryButtonClickedOnItemView(index); - } - } - } - } - } - } - - return QStyledItemDelegate::editorEvent(event, model, option, index); -} - -void ActivityItemDelegate::slotStyleChanged() -{ - customizeStyle(); -} - -void ActivityItemDelegate::customizeStyle() -{ - QPalette pal; - pal.setColor(QPalette::Base, QColor(0,0,0)); // use dark background colour to invert icons - - _iconClose = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg")); - _iconClose_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/close.svg"), pal); - _iconMore = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg")); - _iconMore_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/more.svg"), pal); - - _iconFolder = QIcon(QLatin1String(":/client/resources/folder.svg")); - - _iconActivity = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png")); - _iconActivity_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/activity.png"), pal); - _iconBell = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg")); - _iconBell_sel = Theme::createColorAwareIcon(QLatin1String(":/client/resources/bell.svg"), pal); - - _iconStateError = QIcon(QLatin1String(":/client/resources/state-error.svg")); - _iconStateWarning = QIcon(QLatin1String(":/client/resources/state-warning.svg")); - _iconStateInfo = QIcon(QLatin1String(":/client/resources/state-info.svg")); - _iconStateSync = QIcon(QLatin1String(":/client/resources/state-sync.svg")); -} - -} // namespace OCC diff --git a/src/gui/activityitemdelegate.h b/src/gui/activityitemdelegate.h deleted file mode 100644 index 908889eee0be..000000000000 --- a/src/gui/activityitemdelegate.h +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * Copyright (C) by Olivier Goffart - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#pragma once -#include -#include - -class QMouseEvent; - -namespace OCC { - -/** - * @brief The ActivityItemDelegate class - * @ingroup gui - */ -class ActivityItemDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - enum datarole { ActionIconRole = Qt::UserRole + 1, - UserIconRole, - AccountRole, - ObjectTypeRole, - ActionsLinksRole, - ActionTextRole, - ActionRole, - MessageRole, - PathRole, - LinkRole, - PointInTimeRole, - AccountConnectedRole, - SyncFileStatusRole }; - - ActivityItemDelegate(); - - void paint(QPainter *, const QStyleOptionViewItem &, const QModelIndex &) const override; - QSize sizeHint(const QStyleOptionViewItem &, const QModelIndex &) const override; - bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, - const QModelIndex &index) override; - - static int rowHeight(); - static int iconHeight(); - -public slots: - void slotStyleChanged(); - -signals: - void primaryButtonClickedOnItemView(const QModelIndex &index); - void secondaryButtonClickedOnItemView(const QModelIndex &index); - -private: - void customizeStyle(); - - static int _margin; - static int _iconHeight; - static int _primaryButtonWidth; - static int _secondaryButtonWidth; - static int _spaceBetweenButtons; - static int _timeWidth; - static int _buttonHeight; - static const QString _remote_share; - static const QString _call; - - QIcon _iconClose; - QIcon _iconClose_sel; - QIcon _iconMore; - QIcon _iconMore_sel; - - QIcon _iconFolder; - - QIcon _iconActivity; - QIcon _iconActivity_sel; - QIcon _iconBell; - QIcon _iconBell_sel; - - QIcon _iconStateError; - QIcon _iconStateWarning; - QIcon _iconStateInfo; - QIcon _iconStateSync; -}; - -} // namespace OCC diff --git a/src/gui/activitywidget.cpp b/src/gui/activitywidget.cpp deleted file mode 100644 index 469b0d14ea3d..000000000000 --- a/src/gui/activitywidget.cpp +++ /dev/null @@ -1,653 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include -#include - -#include "activitylistmodel.h" -#include "activitywidget.h" -#include "syncresult.h" -#include "logger.h" -#include "theme.h" -#include "folderman.h" -#include "syncfileitem.h" -#include "folder.h" -#include "openfilemanager.h" -#include "owncloudpropagator.h" -#include "account.h" -#include "accountstate.h" -#include "accountmanager.h" -#include "activityitemdelegate.h" -#include "QProgressIndicator.h" -#include "notificationconfirmjob.h" -#include "servernotificationhandler.h" -#include "theme.h" -#include "ocsjob.h" -#include "configfile.h" -#include "guiutility.h" -#include "socketapi.h" -#include "ui_activitywidget.h" -#include "syncengine.h" - -#include - -// time span in milliseconds which has to be between two -// refreshes of the notifications -#define NOTIFICATION_REQUEST_FREE_PERIOD 15000 - -namespace OCC { - -ActivityWidget::ActivityWidget(AccountState *accountState, QWidget *parent) - : QWidget(parent) - , _ui(new Ui::ActivityWidget) - , _notificationRequestsRunning(0) - , _accountState(accountState) - , _accept(tr("Accept")) - , _remote_share("remote_share") -{ - _ui->setupUi(this); - -// Adjust copyToClipboard() when making changes here! -#if defined(Q_OS_MAC) - _ui->_activityList->setMinimumWidth(400); -#endif - - _model = new ActivityListModel(accountState, this); - ActivityItemDelegate *delegate = new ActivityItemDelegate; - delegate->setParent(this); - _ui->_activityList->setItemDelegate(delegate); - _ui->_activityList->setAlternatingRowColors(true); - _ui->_activityList->setModel(_model); - - showLabels(); - - connect(_model, &ActivityListModel::activityJobStatusCode, - this, &ActivityWidget::slotAccountActivityStatus); - - connect(_model, &QAbstractItemModel::rowsInserted, this, &ActivityWidget::rowsInserted); - - connect(delegate, &ActivityItemDelegate::primaryButtonClickedOnItemView, this, &ActivityWidget::slotPrimaryButtonClickedOnListView); - connect(delegate, &ActivityItemDelegate::secondaryButtonClickedOnItemView, this, &ActivityWidget::slotSecondaryButtonClickedOnListView); - connect(_ui->_activityList, &QListView::activated, this, &ActivityWidget::slotOpenFile); - - connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo, - this, &ActivityWidget::slotProgressInfo); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, - this, &ActivityWidget::slotItemCompleted); - connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, - this, &ActivityWidget::addError); - - _removeTimer.setInterval(1000); - - // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) - connect(this, &ActivityWidget::styleChanged, delegate, &ActivityItemDelegate::slotStyleChanged); -} - -ActivityWidget::~ActivityWidget() -{ - delete _ui; -} - -void ActivityWidget::slotProgressInfo(const QString &folder, const ProgressInfo &progress) -{ - if (progress.status() == ProgressInfo::Reconcile) { - // Wipe all non-persistent entries - as well as the persistent ones - // in cases where a local discovery was done. - auto f = FolderMan::instance()->folder(folder); - if (!f) - return; - const auto &engine = f->syncEngine(); - const auto style = engine.lastLocalDiscoveryStyle(); - foreach (Activity activity, _model->errorsList()) { - if (activity._folder != folder){ - continue; - } - - if (style == LocalDiscoveryStyle::FilesystemOnly){ - _model->removeActivityFromActivityList(activity); - continue; - } - - if(activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()){ - _model->removeActivityFromActivityList(activity); - continue; - } - - if(activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()){ - _model->removeActivityFromActivityList(activity); - continue; - } - - - if(activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()) { - _model->removeActivityFromActivityList(activity); - continue; - } - - - if(!QFileInfo(f->path() + activity._file).exists()){ - _model->removeActivityFromActivityList(activity); - continue; - } - - auto path = QFileInfo(activity._file).dir().path().toUtf8(); - if (path == ".") - path.clear(); - - if(engine.shouldDiscoverLocally(path)) - _model->removeActivityFromActivityList(activity); - } - - } - - if (progress.status() == ProgressInfo::Done) { - // We keep track very well of pending conflicts. - // Inform other components about them. - QStringList conflicts; - foreach (Activity activity, _model->errorsList()) { - if (activity._folder == folder - && activity._status == SyncFileItem::Conflict) { - conflicts.append(activity._file); - } - } - - emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts); - } -} - -void ActivityWidget::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item){ - auto folderInstance = FolderMan::instance()->folder(folder); - - if (!folderInstance) - return; - - // check if we are adding it to the right account and if it is useful information (protocol errors) - if(folderInstance->accountState() == _accountState){ - qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString; - - Activity activity; - activity._type = Activity::SyncFileItemType; //client activity - activity._status = item->_status; - activity._dateTime = QDateTime::currentDateTime(); - activity._message = item->_originalFile; - activity._link = folderInstance->accountState()->account()->url(); - activity._accName = folderInstance->accountState()->account()->displayName(); - activity._file = item->_file; - activity._folder = folder; - - if(item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success){ - qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully."; - activity._message.prepend(" "); - activity._message.prepend(tr("Synced")); - _model->addSyncFileItemToActivityList(activity); - } else { - qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString; - activity._subject = item->_errorString; - - if(item->_status == SyncFileItem::Status::FileIgnored) { - _model->addIgnoredFileToList(activity); - } else { - // add 'protocol error' to activity list - _model->addErrorToActivityList(activity); - } - } - } -} - -void ActivityWidget::addError(const QString &folderAlias, const QString &message, - ErrorCategory category) -{ - auto folderInstance = FolderMan::instance()->folder(folderAlias); - if (!folderInstance) - return; - - if(folderInstance->accountState() == _accountState){ - qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message; - - Activity activity; - activity._type = Activity::SyncResultType; - activity._status = SyncResult::Error; - activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate); - activity._subject = message; - activity._message = folderInstance->shortGuiLocalPath(); - activity._link = folderInstance->shortGuiLocalPath(); - activity._accName = folderInstance->accountState()->account()->displayName(); - activity._folder = folderAlias; - - - if (category == ErrorCategory::InsufficientRemoteStorage) { - ActivityLink link; - link._label = tr("Retry all uploads"); - link._link = folderInstance->path(); - link._verb = ""; - link._isPrimary = true; - activity._links.append(link); - } - - // add 'other errors' to activity list - _model->addErrorToActivityList(activity); - } -} - - -void ActivityWidget::slotPrimaryButtonClickedOnListView(const QModelIndex &index){ - QUrl link = qvariant_cast(index.data(ActivityItemDelegate::LinkRole)); - QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString(); - if(!link.isEmpty()){ - qCWarning(lcActivity) << "Opening" << link.toString() << "in browser for Notification/Activity" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - Utility::openBrowser(link, this); - } else if(objectType == _remote_share){ - QVariant customItem = index.data(ActivityItemDelegate::ActionsLinksRole).toList().first(); - ActivityLink actionLink = qvariant_cast(customItem); - if(actionLink._label == _accept){ - qCWarning(lcActivity) << objectType << "action" << actionLink._label << "for" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString(); - slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row()); - } else { - qCWarning(lcActivity) << "Failed: " << objectType << "action" << actionLink._label << "for" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - } - } -} - -void ActivityWidget::slotSecondaryButtonClickedOnListView(const QModelIndex &index){ - QList customList = index.data(ActivityItemDelegate::ActionsLinksRole).toList(); - QString objectType = index.data(ActivityItemDelegate::ObjectTypeRole).toString(); - - QList actionLinks; - foreach(QVariant customItem, customList){ - actionLinks << qvariant_cast(customItem); - } - - if(objectType == _remote_share && actionLinks.first()._label == _accept) - actionLinks.removeFirst(); - - if(qvariant_cast(index.data(ActivityItemDelegate::ActionRole)) == Activity::Type::NotificationType){ - const QString accountName = index.data(ActivityItemDelegate::AccountRole).toString(); - if(actionLinks.size() == 1){ - if(actionLinks.at(0)._verb == "DELETE"){ - qCWarning(lcActivity) << "Dismissing Notification/Activity" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - slotSendNotificationRequest(index.data(ActivityItemDelegate::AccountRole).toString(), actionLinks.at(0)._link, actionLinks.at(0)._verb, index.row()); - } - } else if(actionLinks.size() > 1){ - QMenu menu; - qCWarning(lcActivity) << "Displaying menu for Notification/Activity" << qvariant_cast(index.data(ActivityItemDelegate::ActionTextRole)); - foreach (ActivityLink actionLink, actionLinks) { - QAction *menuAction = new QAction(actionLink._label, &menu); - connect(menuAction, &QAction::triggered, this, [this, index, accountName, actionLink] { - this->slotSendNotificationRequest(accountName, actionLink._link, actionLink._verb, index.row()); - }); - menu.addAction(menuAction); - } - menu.exec(QCursor::pos()); - } - } - - Activity::Type activityType = qvariant_cast(index.data(ActivityItemDelegate::ActionRole)); - if(activityType == Activity::Type::SyncFileItemType || activityType == Activity::Type::SyncResultType) - slotOpenFile(index); -} - -void ActivityWidget::slotNotificationRequestFinished(int statusCode) -{ - int row = sender()->property("activityRow").toInt(); - - // the ocs API returns stat code 100 or 200 inside the xml if it succeeded. - if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) { - qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible."; - } else { - // to do use the model to rebuild the list or remove the item - qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list."; - _model->removeActivityFromActivityList(row); - } -} - -void ActivityWidget::slotRefreshActivities() -{ - _model->slotRefreshActivity(); -} - -void ActivityWidget::slotRefreshNotifications() -{ - // start a server notification handler if no notification requests - // are running - if (_notificationRequestsRunning == 0) { - ServerNotificationHandler *snh = new ServerNotificationHandler(_accountState); - connect(snh, &ServerNotificationHandler::newNotificationList, - this, &ActivityWidget::slotBuildNotificationDisplay); - - snh->slotFetchNotifications(); - } else { - qCWarning(lcActivity) << "Notification request counter not zero."; - } -} - -void ActivityWidget::slotRemoveAccount() -{ - _model->slotRemoveAccount(); -} - -void ActivityWidget::showLabels() -{ - _ui->_bottomLabel->hide(); // hide whatever was there before - QString t(""); - QSetIterator i(_accountsWithoutActivities); - while (i.hasNext()) { - t.append(tr("
Account %1 does not have activities enabled.").arg(i.next())); - } - if(!t.isEmpty()){ - _ui->_bottomLabel->setTextFormat(Qt::RichText); - _ui->_bottomLabel->setText(t); - _ui->_bottomLabel->show(); - } -} - -void ActivityWidget::slotAccountActivityStatus(int statusCode) -{ - if (!(_accountState && _accountState->account())) { - return; - } - if (statusCode == 999) { - _accountsWithoutActivities.insert(_accountState->account()->displayName()); - } else { - _accountsWithoutActivities.remove(_accountState->account()->displayName()); - } - - checkActivityWidgetVisibility(); - showLabels(); -} - -// FIXME: Reused from protocol widget. Move over to utilities. -QString ActivityWidget::timeString(QDateTime dt, QLocale::FormatType format) const -{ - const QLocale loc = QLocale::system(); - QString dtFormat = loc.dateTimeFormat(format); - static const QRegExp re("(HH|H|hh|h):mm(?!:s)"); - dtFormat.replace(re, "\\1:mm:ss"); - return loc.toString(dt, dtFormat); -} - -void ActivityWidget::storeActivityList(QTextStream &ts) -{ - ActivityList activities = _model->activityList(); - - foreach (Activity activity, activities) { - ts << right - // account name - << qSetFieldWidth(activity._accName.length()) - << activity._accName - // separator - << qSetFieldWidth(2) << " - " - - // date and time - << qSetFieldWidth(activity._dateTime.toString().length()) - << activity._dateTime.toString() - // separator - << qSetFieldWidth(2) << " - " - - // fileq - << qSetFieldWidth(activity._file.length()) - << activity._file - // separator - << qSetFieldWidth(2) << " - " - - // subject - << qSetFieldWidth(activity._subject.length()) - << activity._subject - // separator - << qSetFieldWidth(2) << " - " - - // message - << qSetFieldWidth(activity._message.length()) - << activity._message - << endl; - } -} - -void ActivityWidget::checkActivityWidgetVisibility() -{ - int accountCount = AccountManager::instance()->accounts().count(); - bool hasAccountsWithActivity = - _accountsWithoutActivities.count() != accountCount; - - _ui->_activityList->setVisible(hasAccountsWithActivity); - - emit hideActivityTab(!hasAccountsWithActivity); -} - -void ActivityWidget::slotOpenFile(QModelIndex indx) -{ - qCDebug(lcActivity) << indx.isValid() << indx.data(ActivityItemDelegate::PathRole).toString() << QFile::exists(indx.data(ActivityItemDelegate::PathRole).toString()); - if (indx.isValid()) { - QString fullPath = indx.data(ActivityItemDelegate::PathRole).toString(); - if(!fullPath.isEmpty()){ - if (QFile::exists(fullPath)) { - showInFileManager(fullPath); - } - } - } -} - -// GUI: Display the notifications. -// All notifications in list are coming from the same account -// but in the _widgetForNotifId hash widgets for all accounts are -// collected. -void ActivityWidget::slotBuildNotificationDisplay(const ActivityList &list) -{ - // Whether a new notification was added to the list - bool newNotificationShown = false; - - _model->clearNotifications(); - - foreach (auto activity, list) { - if (_blacklistedNotifications.contains(activity)) { - qCInfo(lcActivity) << "Activity in blacklist, skip"; - continue; - } - - // handle gui logs. In order to NOT annoy the user with every fetching of the - // notifications the notification id is stored in a Set. Only if an id - // is not in the set, it qualifies for guiLog. - // Important: The _guiLoggedNotifications set must be wiped regularly which - // will repeat the gui log. - - // after one hour, clear the gui log notification store - if (_guiLogTimer.elapsed() > 60 * 60 * 1000) { - _guiLoggedNotifications.clear(); - } - - if (!_guiLoggedNotifications.contains(activity._id)) { - newNotificationShown = true; - _guiLoggedNotifications.insert(activity._id); - - // Assemble a tray notification for the NEW notification - ConfigFile cfg; - if(cfg.optionalServerNotifications()){ - if(AccountManager::instance()->accounts().count() == 1){ - emit guiLog(activity._subject, ""); - } else { - emit guiLog(activity._subject, activity._accName); - } - } - } - - _model->addNotificationToActivityList(activity); - } - - // restart the gui log timer now that we show a new notification - if(newNotificationShown) { - _guiLogTimer.start(); - } -} - -void ActivityWidget::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row) -{ - qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName; - - const QStringList validVerbs = QStringList() << "GET" - << "PUT" - << "POST" - << "DELETE"; - - if (validVerbs.contains(verb)) { - AccountStatePtr acc = AccountManager::instance()->account(accountName); - if (acc) { - NotificationConfirmJob *job = new NotificationConfirmJob(acc->account()); - QUrl l(link); - job->setLinkAndVerb(l, verb); - job->setProperty("activityRow", QVariant::fromValue(row)); - connect(job, &AbstractNetworkJob::networkError, - this, &ActivityWidget::slotNotifyNetworkError); - connect(job, &NotificationConfirmJob::jobFinished, - this, &ActivityWidget::slotNotifyServerFinished); - job->start(); - - // count the number of running notification requests. If this member var - // is larger than zero, no new fetching of notifications is started - _notificationRequestsRunning++; - } - } else { - qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb; - } -} - -void ActivityWidget::endNotificationRequest(int replyCode) -{ - _notificationRequestsRunning--; - slotNotificationRequestFinished(replyCode); -} - -void ActivityWidget::slotNotifyNetworkError(QNetworkReply *reply) -{ - NotificationConfirmJob *job = qobject_cast(sender()); - if (!job) { - return; - } - - int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - - endNotificationRequest(resultCode); - qCWarning(lcActivity) << "Server notify job failed with code " << resultCode; -} - -void ActivityWidget::slotNotifyServerFinished(const QString &reply, int replyCode) -{ - NotificationConfirmJob *job = qobject_cast(sender()); - if (!job) { - return; - } - - endNotificationRequest(replyCode); - qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply; -} - -void ActivityWidget::slotStyleChanged() -{ - // Notify the other widgets (Dark-/Light-Mode switching) - emit styleChanged(); -} - -/* ==================================================================== */ - -ActivitySettings::ActivitySettings(AccountState *accountState, QWidget *parent) - : QWidget(parent) - , _accountState(accountState) -{ - _vbox = new QVBoxLayout(this); - setLayout(_vbox); - - _activityWidget = new ActivityWidget(_accountState, this); - - _vbox->insertWidget(1, _activityWidget); - connect(_activityWidget, &ActivityWidget::guiLog, this, &ActivitySettings::guiLog); - connect(&_notificationCheckTimer, &QTimer::timeout, - this, &ActivitySettings::slotRegularNotificationCheck); - - // Add a progress indicator to spin if the acitivity list is updated. - _progressIndicator = new QProgressIndicator(this); - - // connect a model signal to stop the animation - connect(_activityWidget, &ActivityWidget::rowsInserted, _progressIndicator, &QProgressIndicator::stopAnimation); - connect(_activityWidget, &ActivityWidget::rowsInserted, this, &ActivitySettings::slotDisplayActivities); - - // Connect styleChanged events to our widgets, so they can adapt (Dark-/Light-Mode switching) - connect(this, &ActivitySettings::styleChanged, _activityWidget, &ActivityWidget::slotStyleChanged); -} - -void ActivitySettings::slotDisplayActivities(){ - _vbox->removeWidget(_progressIndicator); -} - -void ActivitySettings::setNotificationRefreshInterval(std::chrono::milliseconds interval) -{ - qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval"; - _notificationCheckTimer.start(interval.count()); -} - -void ActivitySettings::slotRemoveAccount() -{ - _activityWidget->slotRemoveAccount(); -} - -void ActivitySettings::slotRefresh() -{ - // QElapsedTimer isn't actually constructed as invalid. - if (!_timeSinceLastCheck.contains(_accountState)) { - _timeSinceLastCheck[_accountState].invalidate(); - } - QElapsedTimer &timer = _timeSinceLastCheck[_accountState]; - - // Fetch Activities only if visible and if last check is longer than 15 secs ago - if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) { - qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000; - return; - } - if (_accountState && _accountState->isConnected()) { - if (isVisible() || !timer.isValid()) { - _vbox->insertWidget(0, _progressIndicator); - _vbox->setAlignment(_progressIndicator, Qt::AlignHCenter); - _progressIndicator->startAnimation(); - _activityWidget->slotRefreshActivities(); - } - _activityWidget->slotRefreshNotifications(); - timer.start(); - } -} - -void ActivitySettings::slotRegularNotificationCheck() -{ - slotRefresh(); -} - -bool ActivitySettings::event(QEvent *e) -{ - if (e->type() == QEvent::Show) { - slotRefresh(); - } - return QWidget::event(e); -} - -ActivitySettings::~ActivitySettings() -{ -} - -void ActivitySettings::slotStyleChanged() -{ - if(_progressIndicator) - _progressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text)); - - // Notify the other widgets (Dark-/Light-Mode switching) - emit styleChanged(); -} - -} diff --git a/src/gui/activitywidget.h b/src/gui/activitywidget.h deleted file mode 100644 index ee271c62a10f..000000000000 --- a/src/gui/activitywidget.h +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#ifndef ACTIVITYWIDGET_H -#define ACTIVITYWIDGET_H - -#include -#include -#include -#include -#include - -#include "progressdispatcher.h" -#include "owncloudgui.h" -#include "account.h" -#include "activitydata.h" -#include "accountmanager.h" - -#include "ui_activitywidget.h" - -class QPushButton; -class QProgressIndicator; - -namespace OCC { - -class Account; -class AccountStatusPtr; -class JsonApiJob; -class ActivityListModel; - -namespace Ui { - class ActivityWidget; -} -class Application; - -/** - * @brief The ActivityWidget class - * @ingroup gui - * - * The list widget to display the activities, contained in the - * subsequent ActivitySettings widget. - */ - -class ActivityWidget : public QWidget -{ - Q_OBJECT -public: - explicit ActivityWidget(AccountState *accountState, QWidget *parent = nullptr); - ~ActivityWidget(); - QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); } - void storeActivityList(QTextStream &ts); - - /** - * Adjusts the activity tab's and some widgets' visibility - * - * Based on whether activities are enabled and whether notifications are - * available. - */ - void checkActivityWidgetVisibility(); - -public slots: - void slotOpenFile(QModelIndex indx); - void slotRefreshActivities(); - void slotRefreshNotifications(); - void slotRemoveAccount(); - void slotAccountActivityStatus(int statusCode); - void addError(const QString &folderAlias, const QString &message, ErrorCategory category); - void slotProgressInfo(const QString &folder, const ProgressInfo &progress); - void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); - void slotStyleChanged(); - -signals: - void guiLog(const QString &, const QString &); - void rowsInserted(); - void hideActivityTab(bool); - void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); - void styleChanged(); - -private slots: - void slotBuildNotificationDisplay(const ActivityList &list); - void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); - void slotNotifyNetworkError(QNetworkReply *); - void slotNotifyServerFinished(const QString &reply, int replyCode); - void endNotificationRequest(int replyCode); - void slotNotificationRequestFinished(int statusCode); - void slotPrimaryButtonClickedOnListView(const QModelIndex &index); - void slotSecondaryButtonClickedOnListView(const QModelIndex &index); - -private: - void customizeStyle(); - void showLabels(); - QString timeString(QDateTime dt, QLocale::FormatType format) const; - Ui::ActivityWidget *_ui; - QSet _accountsWithoutActivities; - QElapsedTimer _guiLogTimer; - QSet _guiLoggedNotifications; - ActivityList _blacklistedNotifications; - - QTimer _removeTimer; - - // number of currently running notification requests. If non zero, - // no query for notifications is started. - int _notificationRequestsRunning; - - ActivityListModel *_model; - AccountState *_accountState; - const QString _accept; - const QString _remote_share; -}; - - -/** - * @brief The ActivitySettings class - * @ingroup gui - * - * Implements a tab for the settings dialog, displaying the three activity - * lists. - */ -class ActivitySettings : public QWidget -{ - Q_OBJECT -public: - explicit ActivitySettings(AccountState *accountState, QWidget *parent = nullptr); - - ~ActivitySettings(); - QSize sizeHint() const override { return ownCloudGui::settingsDialogSize(); } - -public slots: - void slotRefresh(); - void slotRemoveAccount(); - void setNotificationRefreshInterval(std::chrono::milliseconds interval); - void slotStyleChanged(); - -private slots: - void slotRegularNotificationCheck(); - void slotDisplayActivities(); - -signals: - void guiLog(const QString &, const QString &); - void styleChanged(); - -private: - bool event(QEvent *e) override; - - ActivityWidget *_activityWidget; - QProgressIndicator *_progressIndicator; - QVBoxLayout *_vbox; - QTimer _notificationCheckTimer; - QHash _timeSinceLastCheck; - - AccountState *_accountState; -}; -} -#endif // ActivityWIDGET_H diff --git a/src/gui/activitywidget.ui b/src/gui/activitywidget.ui deleted file mode 100644 index 7e5e9f73c49e..000000000000 --- a/src/gui/activitywidget.ui +++ /dev/null @@ -1,98 +0,0 @@ - - - OCC::ActivityWidget - - - - 0 - 0 - 652 - 556 - - - - Form - - - - QLayout::SetDefaultConstraint - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - 0 - - - - QFrame::StyledPanel - - - QFrame::Sunken - - - 1 - - - QAbstractScrollArea::AdjustToContents - - - QAbstractItemView::NoEditTriggers - - - false - - - Qt::IgnoreAction - - - QListView::Adjust - - - QListView::ListMode - - - 0 - - - true - - - - - - - - 0 - 0 - - - - TextLabel - - - Qt::RichText - - - - - - - _activityList - - - - diff --git a/src/gui/generalsettings.ui b/src/gui/generalsettings.ui index fcdfc073c9be..1c7c25015893 100644 --- a/src/gui/generalsettings.ui +++ b/src/gui/generalsettings.ui @@ -6,7 +6,7 @@ 0 0 - 785 + 516 523 diff --git a/src/gui/networksettings.ui b/src/gui/networksettings.ui index 5d7dc94e8781..b9391cf7e59d 100644 --- a/src/gui/networksettings.ui +++ b/src/gui/networksettings.ui @@ -6,7 +6,7 @@ 0 0 - 563 + 516 444 diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 27662f715ad0..8916c28468b5 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -207,10 +207,6 @@ void ownCloudGui::slotSyncStateChange(Folder *folder) || result.status() == SyncResult::Error) { Logger::instance()->enterNextLogFile(); } - - if (result.status() == SyncResult::NotYetStarted) { - _settingsDialog->slotRefreshActivity(folder->accountState()); - } } void ownCloudGui::slotFoldersChanged() @@ -792,10 +788,7 @@ void ownCloudGui::setupActions() _navLinksMenu->setEnabled(false); _actionSettings = new QAction(tr("Settings …"), this); _actionNewAccountWizard = new QAction(tr("New account …"), this); - _actionRecent = new QAction(tr("View more activity …"), this); - _actionRecent->setEnabled(true); - QObject::connect(_actionRecent, &QAction::triggered, this, &ownCloudGui::slotShowSyncProtocol); QObject::connect(_actionSettings, &QAction::triggered, this, &ownCloudGui::slotShowSettings); QObject::connect(_actionNewAccountWizard, &QAction::triggered, this, &ownCloudGui::slotNewAccountWizard); _actionHelp = new QAction(tr("Help"), this); @@ -909,8 +902,6 @@ void ownCloudGui::slotRebuildRecentMenus() } else { _recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false); } - // add a more... entry. - _recentActionsMenu->addAction(_actionRecent); } /// Returns true if the completion of a given item should show up in the @@ -970,8 +961,6 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & _actionStatus->setText(msg); } - _actionRecent->setIcon(QIcon()); // Fixme: Set a "in-progress"-item eventually. - if (!progress._lastCompletedItem.isEmpty() && shouldShowInRecentsMenu(progress._lastCompletedItem)) { if (Progress::isWarningKind(progress._lastCompletedItem._status)) { diff --git a/src/gui/servernotificationhandler.cpp b/src/gui/servernotificationhandler.cpp deleted file mode 100644 index bcf38603357d..000000000000 --- a/src/gui/servernotificationhandler.cpp +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#include "servernotificationhandler.h" -#include "accountstate.h" -#include "capabilities.h" -#include "networkjobs.h" - -#include "iconjob.h" - -#include -#include - -namespace OCC { - -Q_LOGGING_CATEGORY(lcServerNotification, "nextcloud.gui.servernotification", QtInfoMsg) - -const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v2/notifications"); -const char propertyAccountStateC[] = "oc_account_state"; -const int successStatusCode = 200; -const int notModifiedStatusCode = 304; -QMap ServerNotificationHandler::iconCache; - -ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent) - : QObject(parent) - , _accountState(accountState) -{ -} - -void ServerNotificationHandler::slotFetchNotifications() -{ - // check connectivity and credentials - if (!(_accountState && _accountState->isConnected() && - _accountState->account() && _accountState->account()->credentials() && - _accountState->account()->credentials()->ready())) { - deleteLater(); - return; - } - // check if the account has notifications enabled. If the capabilities are - // not yet valid, its assumed that notifications are available. - if (_accountState->account()->capabilities().isValid()) { - if (!_accountState->account()->capabilities().notificationsAvailable()) { - qCInfo(lcServerNotification) << "Account" << _accountState->account()->displayName() << "does not have notifications enabled."; - deleteLater(); - return; - } - } - - // if the previous notification job has finished, start next. - _notificationJob = new JsonApiJob(_accountState->account(), notificationsPath, this); - QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived, - this, &ServerNotificationHandler::slotNotificationsReceived); - QObject::connect(_notificationJob.data(), &JsonApiJob::etagResponseHeaderReceived, - this, &ServerNotificationHandler::slotEtagResponseHeaderReceived); - _notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue(_accountState)); - _notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader()); - _notificationJob->start(); -} - -void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){ - if(statusCode == successStatusCode){ - qCWarning(lcServerNotification) << "New Notification ETag Response Header received " << value; - AccountState *account = qvariant_cast(sender()->property(propertyAccountStateC)); - account->setNotificationsEtagResponseHeader(value); - } -} - -void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData){ - QPixmap pixmap; - pixmap.loadFromData(iconData); - iconCache.insert(sender()->property("activityId").toInt(), QIcon(pixmap)); -} - -void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode) -{ - if (statusCode != successStatusCode && statusCode != notModifiedStatusCode) { - qCWarning(lcServerNotification) << "Notifications failed with status code " << statusCode; - deleteLater(); - return; - } - - if (statusCode == notModifiedStatusCode) { - qCWarning(lcServerNotification) << "Status code " << statusCode << " Not Modified - No new notifications."; - deleteLater(); - return; - } - - auto notifies = json.object().value("ocs").toObject().value("data").toArray(); - - AccountState *ai = qvariant_cast(sender()->property(propertyAccountStateC)); - - ActivityList list; - - foreach (auto element, notifies) { - Activity a; - auto json = element.toObject(); - 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(); - a._status = 0; - - a._subject = json.value("subject").toString(); - a._message = json.value("message").toString(); - - if(!json.value("icon").toString().isEmpty()){ - IconJob *iconJob = new IconJob(QUrl(json.value("icon").toString())); - iconJob->setProperty("activityId", a._id); - connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded); - } - - QUrl link(json.value("link").toString()); - if (!link.isEmpty()) { - if(link.host().isEmpty()){ - link.setScheme(ai->account()->url().scheme()); - link.setHost(ai->account()->url().host()); - } - if (link.port() == -1) { - link.setPort(ai->account()->url().port()); - } - } - a._link = link; - a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate); - - auto actions = json.value("actions").toArray(); - foreach (auto action, actions) { - auto actionJson = action.toObject(); - ActivityLink al; - al._label = QUrl::fromPercentEncoding(actionJson.value("label").toString().toUtf8()); - al._link = actionJson.value("link").toString(); - al._verb = actionJson.value("type").toString().toUtf8(); - al._isPrimary = actionJson.value("primary").toBool(); - - a._links.append(al); - } - - // 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 - ActivityLink al; - al._label = tr("Dismiss"); - al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString(); - al._verb = "DELETE"; - al._isPrimary = false; - a._links.append(al); - - list.append(a); - } - emit newNotificationList(list); - - deleteLater(); -} -} diff --git a/src/gui/servernotificationhandler.h b/src/gui/servernotificationhandler.h deleted file mode 100644 index f0276da524d1..000000000000 --- a/src/gui/servernotificationhandler.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) by Klaas Freitag - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * for more details. - */ - -#ifndef SERVERNOTIFICATIONHANDLER_H -#define SERVERNOTIFICATIONHANDLER_H - -#include - -#include "activitywidget.h" - -class QJsonDocument; - -namespace OCC { - -class ServerNotificationHandler : public QObject -{ - Q_OBJECT -public: - explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr); - static QMap iconCache; - -signals: - void newNotificationList(ActivityList); - -public slots: - void slotFetchNotifications(); - -private slots: - void slotNotificationsReceived(const QJsonDocument &json, int statusCode); - void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode); - void slotIconDownloaded(QByteArray iconData); - -private: - QPointer _notificationJob; - AccountState *_accountState; -}; -} - -#endif // SERVERNOTIFICATIONHANDLER_H diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index a80de4da5aa2..6fa7bfa47059 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -23,7 +23,6 @@ #include "configfile.h" #include "progressdispatcher.h" #include "owncloudgui.h" -#include "activitywidget.h" #include "accountmanager.h" #include @@ -189,39 +188,13 @@ void SettingsDialog::showFirstPage() } } -void SettingsDialog::showActivityPage() -{ - if (auto account = qvariant_cast(sender()->property("account"))) { - _activitySettings[account]->show(); - _ui->stack->setCurrentWidget(_activitySettings[account]); - } -} - void SettingsDialog::showIssuesList(AccountState *account) { - for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) { + /*for (auto it = _actionGroupWidgets.begin(); it != _actionGroupWidgets.end(); ++it) { if (it.value() == _activitySettings[account]) { it.key()->activate(QAction::ActionEvent::Trigger); break; } - } -} - -void SettingsDialog::activityAdded(AccountState *s){ - _ui->stack->addWidget(_activitySettings[s]); - connect(_activitySettings[s], &ActivitySettings::guiLog, _gui, - &ownCloudGui::slotShowOptionalTrayMessage); - - ConfigFile cfg; - _activitySettings[s]->setNotificationRefreshInterval(cfg.notificationRefreshInterval()); - - // Note: all the actions have a '\n' because the account name is in two lines and - // all buttons must have the same size in order to keep a good layout - QAction *action = createColorAwareAction(QLatin1String(":/client/resources/activity.png"), tr("Activity")); - action->setProperty("account", QVariant::fromValue(s)); - _toolBar->insertAction(_actionBefore, action); - _actionGroup->addAction(action); - _actionGroupWidgets.insert(action, _activitySettings[s]); - connect(action, &QAction::triggered, this, &SettingsDialog::showActivityPage); + }*/ } void SettingsDialog::accountAdded(AccountState *s) @@ -229,16 +202,6 @@ void SettingsDialog::accountAdded(AccountState *s) auto height = _toolBar->sizeHint().height(); bool brandingSingleAccount = !Theme::instance()->multiAccount(); - _activitySettings[s] = new ActivitySettings(s, this); - - - - // if this is not the first account, then before we continue to add more accounts we add a separator - if(AccountManager::instance()->accounts().first().data() != s && - AccountManager::instance()->accounts().size() >= 1){ - _actionGroupWidgets.insert(_toolBar->insertSeparator(_actionBefore), _activitySettings[s]); - } - QAction *accountAction; QImage avatar = s->account()->avatar(); const QString actionText = brandingSingleAccount ? tr("Account") : s->account()->displayName(); @@ -270,15 +233,8 @@ void SettingsDialog::accountAdded(AccountState *s) connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged); connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged); - // Refresh immediatly when getting online - connect(s, &AccountState::isConnectedChanged, this, &SettingsDialog::slotRefreshActivityAccountStateSender); - // Connect styleChanged event, to adapt (Dark-/Light-Mode switching) connect(this, &SettingsDialog::styleChanged, accountSettings, &AccountSettings::slotStyleChanged); - connect(this, &SettingsDialog::styleChanged, _activitySettings[s], &ActivitySettings::slotStyleChanged); - - activityAdded(s); - slotRefreshActivity(s); } void SettingsDialog::slotAccountAvatarChanged() @@ -334,19 +290,6 @@ void SettingsDialog::accountRemoved(AccountState *s) _actionForAccount.remove(s->account().data()); } - if(_activitySettings.contains(s)){ - _activitySettings[s]->slotRemoveAccount(); - _activitySettings[s]->hide(); - - // get the settings widget and the separator - foreach(QAction *action, _actionGroupWidgets.keys(_activitySettings[s])){ - _actionGroupWidgets.remove(action); - _toolBar->removeAction(action); - } - _toolBar->widgetForAction(_actionBefore)->hide(); - _activitySettings.remove(s); - } - // Hide when the last account is deleted. We want to enter the same // state we'd be in the client was started up without an account // configured. @@ -416,15 +359,4 @@ QAction *SettingsDialog::createColorAwareAction(const QString &iconPath, const Q return createActionWithIcon(coloredIcon, text, iconPath); } -void SettingsDialog::slotRefreshActivityAccountStateSender() -{ - slotRefreshActivity(qobject_cast(sender())); -} - -void SettingsDialog::slotRefreshActivity(AccountState *accountState) -{ - if (accountState->isConnected()) - _activitySettings[accountState]->slotRefresh(); -} - } // namespace OCC diff --git a/src/gui/settingsdialog.h b/src/gui/settingsdialog.h index f5ec654d967f..8242522c29b7 100644 --- a/src/gui/settingsdialog.h +++ b/src/gui/settingsdialog.h @@ -37,7 +37,6 @@ class AccountSettings; class Application; class FolderMan; class ownCloudGui; -class ActivitySettings; /** * @brief The SettingsDialog class @@ -55,11 +54,8 @@ class SettingsDialog : public QDialog public slots: void showFirstPage(); - void showActivityPage(); void showIssuesList(AccountState *account); void slotSwitchPage(QAction *action); - void slotRefreshActivity(AccountState *accountState); - void slotRefreshActivityAccountStateSender(); void slotAccountAvatarChanged(); void slotAccountDisplayNameChanged(); @@ -78,7 +74,6 @@ private slots: private: void customizeStyle(); - void activityAdded(AccountState *); QAction *createColorAwareAction(const QString &iconName, const QString &fileName); QAction *createActionWithIcon(const QIcon &icon, const QString &text, const QString &iconPath = QString()); @@ -95,7 +90,6 @@ private slots: QHash _actionForAccount; QToolBar *_toolBar; - QMap _activitySettings; ownCloudGui *_gui; }; diff --git a/src/gui/settingsdialog.ui b/src/gui/settingsdialog.ui index 3f886ed222bc..18632134f605 100644 --- a/src/gui/settingsdialog.ui +++ b/src/gui/settingsdialog.ui @@ -6,7 +6,7 @@ 0 0 - 693 + 516 457 diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 97d6a1a03444..c377c69bb2a4 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -42,14 +42,12 @@ Systray::Systray() // TODO: make singleton, provide ::instance() { // Create QML tray engine, build component, set C++ backend context used in window.qml // Use pointer instead of engine() helper function until Qt 5.12 is minimum standard - QQmlEngine *engine = new QQmlEngine; - _trayComponent = new QQmlComponent(engine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); - _trayContext = engine->contextForObject(_trayComponent->create()); - - engine->addImageProvider("avatars", new ImageProvider); - engine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); - engine->rootContext()->setContextProperty("activityModel", ActivityModel::instance()); - engine->rootContext()->setContextProperty("systrayBackend", this); + _trayEngine = new QQmlEngine; + _trayEngine->addImageProvider("avatars", new ImageProvider); + _trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); + _trayEngine->rootContext()->setContextProperty("systrayBackend", this); + _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); + _trayContext = _trayEngine->contextForObject(_trayComponent->create()); // TODO: hack to pass the icon to QML //ctxt->setContextProperty("theme", QLatin1String("colored")); @@ -59,8 +57,6 @@ Systray::Systray() // TODO: make singleton, provide ::instance() slotChangeActivityModel(AccountManager::instance()->accounts().first()); } - //connect(AccountManager::instance(), &AccountManager::accountAdded, - // this, &Systray::slotChangeActivityModel); hideWindow(); } @@ -72,6 +68,7 @@ void Systray::slotChangeActivityModel(const AccountStatePtr account) { _currentAccount = account; emit currentUserChanged(); + _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); } void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) diff --git a/src/gui/systray.h b/src/gui/systray.h index f99f024e673e..50c277186bc4 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -62,6 +62,7 @@ private slots: private: AccountStatePtr _currentAccount; + QQmlEngine *_trayEngine; QQmlComponent *_trayComponent; QQmlContext *_trayContext; }; diff --git a/src/gui/activitydata.cpp b/src/gui/tray/ActivityData.cpp similarity index 100% rename from src/gui/activitydata.cpp rename to src/gui/tray/ActivityData.cpp diff --git a/src/gui/activitydata.h b/src/gui/tray/ActivityData.h similarity index 100% rename from src/gui/activitydata.h rename to src/gui/tray/ActivityData.h diff --git a/src/gui/activitylistmodel.cpp b/src/gui/tray/ActivityListModel.cpp similarity index 93% rename from src/gui/activitylistmodel.cpp rename to src/gui/tray/ActivityListModel.cpp index eeb0cc836fe2..4a875d2f610c 100644 --- a/src/gui/activitylistmodel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -24,21 +24,18 @@ #include "accountmanager.h" #include "folderman.h" #include "accessmanager.h" -#include "activityitemdelegate.h" -#include "activitydata.h" -#include "activitylistmodel.h" +#include "ActivityData.h" +#include "ActivityListModel.h" #include "theme.h" -#include "servernotificationhandler.h" - namespace OCC { Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg) -ActivityListModel::ActivityListModel(AccountState *accountState, QWidget *parent) - : QAbstractListModel(parent) +ActivityListModel::ActivityListModel(AccountState *accountState, QObject* parent) + : QAbstractListModel() , _accountState(accountState) { } @@ -61,7 +58,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const QStringList list; switch (role) { - case ActivityItemDelegate::PathRole: + case PathRole: if(!a._file.isEmpty()){ auto folder = FolderMan::instance()->folder(a._folder); QString relPath(a._file); @@ -79,7 +76,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } } return QVariant(); - case ActivityItemDelegate::ActionsLinksRole:{ + case ActionsLinksRole:{ QList customList; foreach (ActivityLink customItem, a._links) { QVariant customVariant; @@ -88,16 +85,16 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } return customList; } - case ActivityItemDelegate::ActionIconRole:{ + case ActionIconRole:{ ActionIcon actionIcon; if(a._type == Activity::NotificationType){ - QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); + /*QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); if(!cachedIcon.isNull()) { actionIcon.iconType = ActivityIconType::iconUseCached; actionIcon.cachedIcon = cachedIcon; } else { actionIcon.iconType = ActivityIconType::iconBell; - } + }*/ } else if(a._type == Activity::SyncResultType){ actionIcon.iconType = ActivityIconType::iconStateError; } else if(a._type == Activity::SyncFileItemType){ @@ -123,24 +120,24 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const icn.setValue(actionIcon); return icn; } - case ActivityItemDelegate::ObjectTypeRole: + case ObjectTypeRole: return a._objectType; - case ActivityItemDelegate::ActionRole:{ + case ActionRole:{ QVariant type; type.setValue(a._type); return type; } - case ActivityItemDelegate::ActionTextRole: + case ActionTextRole: return a._subject; - case ActivityItemDelegate::MessageRole: + case MessageRole: return a._message; - case ActivityItemDelegate::LinkRole: + case LinkRole: return a._link; - case ActivityItemDelegate::AccountRole: + case AccountRole: return a._accName; - case ActivityItemDelegate::PointInTimeRole: + case PointInTimeRole: return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime())); - case ActivityItemDelegate::AccountConnectedRole: + case AccountConnectedRole: return (ast && ast->isConnected()); default: return QVariant(); diff --git a/src/gui/activitylistmodel.h b/src/gui/tray/ActivityListModel.h similarity index 88% rename from src/gui/activitylistmodel.h rename to src/gui/tray/ActivityListModel.h index 1ff352beb331..1353015921fe 100644 --- a/src/gui/activitylistmodel.h +++ b/src/gui/tray/ActivityListModel.h @@ -47,12 +47,27 @@ class ActivityListModel : public QAbstractListModel iconStateInfo, iconStateSync }; + + enum datarole { ActionIconRole = Qt::UserRole + 1, + UserIconRole, + AccountRole, + ObjectTypeRole, + ActionsLinksRole, + ActionTextRole, + ActionRole, + MessageRole, + PathRole, + LinkRole, + PointInTimeRole, + AccountConnectedRole, + SyncFileStatusRole }; + struct ActionIcon { ActivityIconType iconType; QIcon cachedIcon; }; - explicit ActivityListModel(AccountState *accountState, QWidget *parent = nullptr); + explicit ActivityListModel(AccountState *accountState, QObject* parent = 0); QVariant data(const QModelIndex &index, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 7dd648fda9dd..8aaa58398d63 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -6,7 +6,7 @@ import QtQuick.Layouts 1.2 MenuItem { Connections { - target: systrayBackend + target: userModelBackend onRefreshUserMenu: { userLine.visible = isCurrentUser ? false : true userLine.height = isCurrentUser ? 0 : 60 @@ -17,7 +17,6 @@ MenuItem { visible: isCurrentUser ? false : true width: 216 height: isCurrentUser ? 0 : 60 - //color: "transparent" Rectangle { id: userLineBackground @@ -43,7 +42,7 @@ MenuItem { } onClicked: { - systrayBackend.switchCurrentUser(index) + userModelBackend.switchCurrentUser(index) } RowLayout { @@ -55,7 +54,7 @@ MenuItem { id: accountAvatar Layout.leftMargin: 4 verticalAlignment: Qt.AlignCenter - source: avatar + source: ("image://avatars/" + index) Layout.preferredHeight: (userLineBackground.height -16) Layout.preferredWidth: (userLineBackground.height -16) } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 95591226f1e6..9ee8ae42a7fe 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -10,6 +10,7 @@ namespace OCC { User::User(AccountStatePtr &account, const bool &isCurrent) : _account(account) , _isCurrentUser(isCurrent) + , _activityModel(new ActivityListModel(_account.data())) { } @@ -32,6 +33,11 @@ Folder *User::getFolder() } } +ActivityListModel *User::getActivityModel() +{ + return _activityModel; +} + void User::openLocalFolder() { QDesktopServices::openUrl(this->getFolder()->path()); @@ -67,11 +73,17 @@ QImage User::avatar() const { QImage img = AvatarJob::makeCircularAvatar(_account->account()->avatar()); if (img.isNull()) { - img = QImage(":/client/resources/account.svg"); + img = AvatarJob::makeCircularAvatar(QImage(":/client/resources/account.png")); } return img; } +bool User::serverHasTalk() const +{ + auto test = _account->hasTalk(); + return _account->hasTalk(); +} + bool User::isCurrentUser() const { return _isCurrentUser; @@ -79,6 +91,7 @@ bool User::isCurrentUser() const bool User::isConnected() const { + bool test = (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); } @@ -143,17 +156,26 @@ Q_INVOKABLE QString UserModel::currentUserServer() return _users[_currentUserId].server(); } +Q_INVOKABLE bool UserModel::currentServerHasTalk() +{ + return _users[_currentUserId].serverHasTalk(); +} + void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) { - auto newUser = User(user, isCurrent); beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _users << newUser; + _users << User(user, isCurrent); if (isCurrent) { - _currentUserId = _users.indexOf(newUser); + _currentUserId = _users.indexOf(_users.last()); } endInsertRows(); } +int UserModel::currentUserIndex() +{ + return _currentUserId; +} + Q_INVOKABLE void UserModel::openCurrentAccountLocalFolder() { _users[_currentUserId].openLocalFolder(); @@ -204,74 +226,9 @@ QHash UserModel::roleNames() const return roles; } -/*-------------------------------------------------------------------------------------*/ - -QString UserActivity::type() const -{ - return "Test"; -} -QString UserActivity::fileName() const -{ - return "Test"; -} - -QString UserActivity::info() const -{ - return "Test"; -} - - -ActivityModel *ActivityModel::_instance = nullptr; - -ActivityModel *ActivityModel::instance() -{ - if (_instance == nullptr) { - _instance = new ActivityModel(); - } - return _instance; -} - -ActivityModel::ActivityModel(QObject *parent) - : QAbstractListModel() -{ -} - -QHash ActivityModel::roleNames() const -{ - QHash roles; - roles[TypeRole] = "type"; - roles[FileNameRole] = "filename"; - roles[InfoRole] = "info"; - return roles; -} - -int ActivityModel::rowCount(const QModelIndex &parent) const +ActivityListModel *UserModel::currentActivityModel() { - Q_UNUSED(parent); - return _activities.count(); -} - -QVariant ActivityModel::data(const QModelIndex &index, int role) const -{ - if (index.row() < 0 || index.row() >= _activities.count()) { - return QVariant(); - } - - const UserActivity &activity = _activities[index.row()]; - if (role == TypeRole) - return activity.type(); - else if (role == FileNameRole) - return activity.fileName(); - else if (role == InfoRole) - return activity.info(); - return QVariant(); -} - -void ActivityModel::addActivity(const UserActivity &activity) -{ - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _activities << activity; - endInsertRows(); + return _users[currentUserIndex()].getActivityModel(); } /*-------------------------------------------------------------------------------------*/ diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 35eb3f9c2d4f..f93d33d8de0a 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -6,6 +6,7 @@ #include #include +#include "ActivityListModel.h" #include "accountmanager.h" #include "folderman.h" @@ -22,15 +23,18 @@ class User bool isCurrentUser() const; void setCurrentUser(const bool &isCurrent); Folder *getFolder(); + ActivityListModel *getActivityModel(); void openLocalFolder(); QString name() const; QString server() const; + bool serverHasTalk() const; QImage avatar() const; QString id() const; private: AccountStatePtr _account; bool _isCurrentUser; + ActivityListModel *_activityModel; }; class UserModel : public QAbstractListModel @@ -41,6 +45,7 @@ class UserModel : public QAbstractListModel virtual ~UserModel() {}; void addUser(AccountStatePtr &user, const bool &isCurrent = false); + int currentUserIndex(); int rowCount(const QModelIndex &parent = QModelIndex()) const; @@ -54,8 +59,11 @@ class UserModel : public QAbstractListModel Q_INVOKABLE bool isCurrentUserConnected(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); + Q_INVOKABLE bool currentServerHasTalk(); Q_INVOKABLE void switchCurrentUser(const int &id); + ActivityListModel *currentActivityModel(); + enum UserRoles { NameRole = Qt::UserRole + 1, ServerRole, @@ -85,40 +93,6 @@ class UserModel : public QAbstractListModel void initUserList(); }; -class UserActivity -{ -public: - QString type() const; - QString fileName() const; - QString info() const; -}; - -class ActivityModel : public QAbstractListModel -{ - Q_OBJECT -public: - static ActivityModel *instance(); - virtual ~ActivityModel() {}; - - int rowCount(const QModelIndex &parent = QModelIndex()) const; - QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - void addActivity(const UserActivity &activity); - - enum ActivityRoles { - TypeRole = Qt::UserRole + 1, - FileNameRole, - InfoRole - }; - -protected: - QHash roleNames() const; - -private: - static ActivityModel *_instance; - ActivityModel(QObject *parent = 0); - QList _activities; -}; - class ImageProvider : public QQuickImageProvider { public: diff --git a/src/gui/tray/window.qml b/src/gui/tray/window.qml index 28e61f466518..337302960676 100644 --- a/src/gui/tray/window.qml +++ b/src/gui/tray/window.qml @@ -20,15 +20,25 @@ Window { } } + onVisibleChanged: { + currentAccountAvatar.source = "" + currentAccountAvatar.source = "image://avatars/currentUser" + currentAccountUser.text = userModelBackend.currentUserName(); + currentAccountServer.text = userModelBackend.currentUserServer(); + trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false; + } + Connections { target: userModelBackend onRefreshCurrentUserGui: { - currentAccountAvatar.source = userModelBackend.currentUserAvatar() - currentAccountUser.text = userModelBackend.currentUserName() - currentAccountServer.text = userModelBackend.currentUserServer() + currentAccountAvatar.source = "" + currentAccountAvatar.source = "image://avatars/currentUser" + currentAccountUser.text = userModelBackend.currentUserName(); + currentAccountServer.text = userModelBackend.currentUserServer(); } onNewUserSelected: { - accountMenu.close() + accountMenu.close(); + trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false; } } @@ -85,6 +95,7 @@ Window { hoverEnabled: true onClicked: { + accMenuLoginButton.text = (userModelBackend.isCurrentUserConnected() ? "Logout" : "Login") accountMenu.open() } @@ -109,7 +120,7 @@ Window { MenuSeparator { id: accountMenuSeparator } MenuItem { - text: (userModelBackend.isCurrentUserConnected() ? "Logout" : "Login") + id: accMenuLoginButton onClicked: (userModelBackend.isCurrentUserConnected() ? userModelBackend.logout() : userModelBackend.login() ) @@ -188,7 +199,8 @@ Window { id: currentAccountAvatar Layout.leftMargin: 8 verticalAlignment: Qt.AlignCenter - source: userModelBackend.currentUserAvatar() + cache: false + source: "image://avatars/currentUser" Layout.preferredHeight: (trayWindowHeaderBackground.height -16) Layout.preferredWidth: (trayWindowHeaderBackground.height -16) } @@ -262,6 +274,7 @@ Window { Layout.preferredWidth: (trayWindowHeaderBackground.height) Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true + visible: userModelBackend.currentServerHasTalk() ? true : false icon.source: "qrc:///client/theme/white/talk-app.svg" icon.color: "transparent" @@ -289,6 +302,7 @@ Window { Layout.preferredWidth: (trayWindowHeaderBackground.height) Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true + visible: false icon.source: "qrc:///client/theme/white/more-apps.svg" icon.color: "transparent" diff --git a/src/libsync/capabilities.cpp b/src/libsync/capabilities.cpp index 39df7d1677f8..d0c5f727f7db 100644 --- a/src/libsync/capabilities.cpp +++ b/src/libsync/capabilities.cpp @@ -103,7 +103,8 @@ bool Capabilities::isValid() const return !_capabilities.isEmpty(); } -bool Capabilities::hasActivities() const { +bool Capabilities::hasActivities() const +{ return _capabilities.contains("activity"); } From 02cb43180ca3efe1bb058777f9a4a0b7f4ed3e67 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 13:12:04 +0100 Subject: [PATCH 035/120] Fix git not recognizing case sensitive changes Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/{window.qml => Window.qml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/gui/tray/{window.qml => Window.qml} (100%) diff --git a/src/gui/tray/window.qml b/src/gui/tray/Window.qml similarity index 100% rename from src/gui/tray/window.qml rename to src/gui/tray/Window.qml From 25e09815bec44ff87657083209694e096e110935 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 13:17:01 +0100 Subject: [PATCH 036/120] Fixed client.qrc Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- client.qrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.qrc b/client.qrc index 8f0454c26267..509f8052c8e2 100644 --- a/client.qrc +++ b/client.qrc @@ -33,7 +33,7 @@
- src/gui/tray/window.qml + src/gui/tray/Window.qml src/gui/tray/UserLine.qml From 687a99279d7d0a6c8159f1505227c5f64f25cbbc Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 13:39:10 +0100 Subject: [PATCH 037/120] Has function for ActivityModel, typos Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 2 +- src/gui/tray/ActivityListModel.cpp | 8 ++++++++ src/gui/tray/ActivityListModel.h | 10 +++++++--- src/gui/tray/Window.qml | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index c377c69bb2a4..c5f1d1723cd5 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -46,7 +46,7 @@ Systray::Systray() // TODO: make singleton, provide ::instance() _trayEngine->addImageProvider("avatars", new ImageProvider); _trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); _trayEngine->rootContext()->setContextProperty("systrayBackend", this); - _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/window.qml"))); + _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"))); _trayContext = _trayEngine->contextForObject(_trayComponent->create()); // TODO: hack to pass the icon to QML diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 4a875d2f610c..48ec7d018f51 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -40,6 +40,14 @@ ActivityListModel::ActivityListModel(AccountState *accountState, QObject* parent { } +QHash ActivityListModel::roleNames() const +{ + QHash roles; + roles[PathRole] = "path"; + roles[MessageRole] = "message"; + return roles; +} + QVariant ActivityListModel::data(const QModelIndex &index, int role) const { Activity a; diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index 1353015921fe..d633bb07f537 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -17,7 +17,7 @@ #include -#include "activitydata.h" +#include "ActivityData.h" class QJsonDocument; @@ -48,8 +48,9 @@ class ActivityListModel : public QAbstractListModel iconStateSync }; - enum datarole { ActionIconRole = Qt::UserRole + 1, - UserIconRole, + enum datarole { + ActionIconRole = Qt::UserRole + 1, + UserIconRole, AccountRole, ObjectTypeRole, ActionsLinksRole, @@ -95,6 +96,9 @@ private slots: signals: void activityJobStatusCode(int statusCode); +protected: + QHash roleNames() const; + private: void startFetchJob(); void combineActivityLists(); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 337302960676..0735d61613ac 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -397,12 +397,12 @@ Window { Layout.alignment: Qt.AlignLeft Text { id: activityTextTitle - text: filename + text: path font.pointSize: 9 } Text { id: activityTextInfo - text: info + text: message font.pointSize: 8 } } From faf46fcf60b5e8a550d993c4a233d5b3be6f4802 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 13:42:07 +0100 Subject: [PATCH 038/120] More typo fix Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/ActivityData.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/tray/ActivityData.cpp b/src/gui/tray/ActivityData.cpp index 866c97956f97..9378a6596903 100644 --- a/src/gui/tray/ActivityData.cpp +++ b/src/gui/tray/ActivityData.cpp @@ -14,7 +14,7 @@ #include -#include "activitydata.h" +#include "ActivityData.h" namespace OCC { From f01c47e0ec01acc18c8280a47763a9eeb4994854 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 14:00:07 +0100 Subject: [PATCH 039/120] Fix server adress retreival in tray window buttons Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 32 ++++++++++++++++++++++++++------ src/gui/tray/UserModel.h | 4 +++- src/gui/tray/Window.qml | 3 ++- 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 9ee8ae42a7fe..b7c6d5fc711e 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -57,14 +57,16 @@ QString User::name() const return name; } -QString User::server() const +QString User::server(bool shortened) const { QString serverUrl = _account->account()->url().toString(); - serverUrl.replace(QLatin1String("https://"), QLatin1String("")); - serverUrl.replace(QLatin1String("http://"), QLatin1String("")); - if (serverUrl.size() > 21) { - serverUrl.truncate(19); - serverUrl.append("..."); + if (shortened) { + serverUrl.replace(QLatin1String("https://"), QLatin1String("")); + serverUrl.replace(QLatin1String("http://"), QLatin1String("")); + if (serverUrl.size() > 21) { + serverUrl.truncate(19); + serverUrl.append("..."); + } } return serverUrl; } @@ -181,6 +183,24 @@ Q_INVOKABLE void UserModel::openCurrentAccountLocalFolder() _users[_currentUserId].openLocalFolder(); } +Q_INVOKABLE void UserModel::openCurrentAccountTalk() +{ + QString url = _users[_currentUserId].server(false) + "/apps/spreed"; + if (!(url.contains("http://") || url.contains("https://"))) { + url = "https://" + _users[_currentUserId].server(false) + "/apps/spreed"; + } + QDesktopServices::openUrl(QUrl(url)); +} + +Q_INVOKABLE void UserModel::openCurrentAccountServer() +{ + QString url = _users[_currentUserId].server(false); + if (! (url.contains("http://") || url.contains("https://")) ) { + url = "https://" + _users[_currentUserId].server(false); + } + QDesktopServices::openUrl(QUrl(url)); +} + Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) { _users[_currentUserId].setCurrentUser(false); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index f93d33d8de0a..4b5008ead19a 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -26,7 +26,7 @@ class User ActivityListModel *getActivityModel(); void openLocalFolder(); QString name() const; - QString server() const; + QString server(bool shortened = true) const; bool serverHasTalk() const; QImage avatar() const; QString id() const; @@ -54,6 +54,8 @@ class UserModel : public QAbstractListModel QImage avatarById(const int &id); Q_INVOKABLE void openCurrentAccountLocalFolder(); + Q_INVOKABLE void openCurrentAccountTalk(); + Q_INVOKABLE void openCurrentAccountServer(); Q_INVOKABLE QImage currentUserAvatar(); Q_INVOKABLE int numUsers(); Q_INVOKABLE bool isCurrentUserConnected(); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 0735d61613ac..37e8658e01bb 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -285,6 +285,7 @@ Window { hoverEnabled: true onClicked: { + userModelBackend.openCurrentAccountTalk(); } } @@ -302,7 +303,6 @@ Window { Layout.preferredWidth: (trayWindowHeaderBackground.height) Layout.preferredHeight: (trayWindowHeaderBackground.height) flat: true - visible: false icon.source: "qrc:///client/theme/white/more-apps.svg" icon.color: "transparent" @@ -313,6 +313,7 @@ Window { hoverEnabled: true onClicked: { + userModelBackend.openCurrentAccountServer(); } } From e0641df727810c223eee501c85824c13362663fd Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 3 Jan 2020 16:15:15 +0100 Subject: [PATCH 040/120] First real activity connection Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 37 ++++++++++++++---------------- src/gui/systray.h | 3 +-- src/gui/tray/ActivityListModel.cpp | 31 +++++++++++++++++++------ src/gui/tray/UserModel.cpp | 12 +++++++++- src/gui/tray/UserModel.h | 2 ++ src/gui/tray/Window.qml | 18 +++++++++++++-- 6 files changed, 71 insertions(+), 32 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index c5f1d1723cd5..aaf35a10b6e5 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -18,6 +18,7 @@ #include "config.h" #include "tray/UserModel.h" +#include #include #include #include @@ -36,8 +37,7 @@ namespace OCC { Systray::Systray() // TODO: make singleton, provide ::instance() - : _currentAccount(nullptr) - , _trayComponent(nullptr) + : _trayComponent(nullptr) , _trayContext(nullptr) { // Create QML tray engine, build component, set C++ backend context used in window.qml @@ -46,17 +46,17 @@ Systray::Systray() // TODO: make singleton, provide ::instance() _trayEngine->addImageProvider("avatars", new ImageProvider); _trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); _trayEngine->rootContext()->setContextProperty("systrayBackend", this); + _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"))); _trayContext = _trayEngine->contextForObject(_trayComponent->create()); - // TODO: hack to pass the icon to QML - //ctxt->setContextProperty("theme", QLatin1String("colored")); - //ctxt->setContextProperty("filename", "state-offline"); - if (!AccountManager::instance()->accounts().isEmpty()) { - slotChangeActivityModel(AccountManager::instance()->accounts().first()); + slotChangeActivityModel(); } + connect(UserModel::instance(), &UserModel::newUserSelected, + this, &Systray::slotChangeActivityModel); + hideWindow(); } @@ -64,11 +64,10 @@ Systray::~Systray() { } -void Systray::slotChangeActivityModel(const AccountStatePtr account) +void Systray::slotChangeActivityModel() { - _currentAccount = account; - emit currentUserChanged(); _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); + emit currentUserChanged(); } void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) @@ -109,10 +108,10 @@ int Systray::calcTrayWindowX() int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); - if ( (trayScreen->geometry().width() - trayIconTopCenterX) < (trayScreen->geometry().width() * 0.5) ) { + if ((trayScreen->geometry().width() - trayIconTopCenterX) < (trayScreen->geometry().width() * 0.5)) { // tray icon is on right side of the screen - if ( ((trayScreen->geometry().width() - trayIconTopCenterX) < trayScreen->geometry().height() - trayIconTopCenterY) - && ((trayScreen->geometry().width() - trayIconTopCenterX) < trayIconTopCenterY) ) { + if (((trayScreen->geometry().width() - trayIconTopCenterX) < trayScreen->geometry().height() - trayIconTopCenterY) + && ((trayScreen->geometry().width() - trayIconTopCenterX) < trayIconTopCenterY)) { // taskbar is on the right return trayScreen->availableSize().width() - 400 - 6; } else { @@ -132,20 +131,20 @@ int Systray::calcTrayWindowX() } int Systray::calcTrayWindowY() { -#if QT_VERSION >= QT_VERSION_CHECK(5,10,0) +#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); #else QScreen *trayScreen = QGuiApplication::primaryScreen(); #endif - + // get coordinates from top center point of tray icon int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); - if ( (trayScreen->geometry().height() - trayIconTopCenterY) < (trayScreen->geometry().height() * 0.5) ) { + if ((trayScreen->geometry().height() - trayIconTopCenterY) < (trayScreen->geometry().height() * 0.5)) { // tray icon is on bottom side of the screen - if ( ((trayScreen->geometry().height() - trayIconTopCenterY) < trayScreen->geometry().width() - trayIconTopCenterX ) - && ((trayScreen->geometry().height() - trayIconTopCenterY) < trayIconTopCenterX) ) { + if (((trayScreen->geometry().height() - trayIconTopCenterY) < trayScreen->geometry().width() - trayIconTopCenterX) + && ((trayScreen->geometry().height() - trayIconTopCenterY) < trayIconTopCenterX)) { // taskbar is on the bottom return trayScreen->availableSize().height() - 500 - 6; } else { @@ -161,6 +160,4 @@ int Systray::calcTrayWindowY() return (trayScreen->geometry().height() - trayScreen->availableGeometry().height()) + 6; } } - - } // namespace OCC diff --git a/src/gui/systray.h b/src/gui/systray.h index 50c277186bc4..bb02eecef447 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -58,10 +58,9 @@ class Systray Q_INVOKABLE void showWindow(); private slots: - void slotChangeActivityModel(const AccountStatePtr account); + void slotChangeActivityModel(); private: - AccountStatePtr _currentAccount; QQmlEngine *_trayEngine; QQmlComponent *_trayComponent; QQmlContext *_trayContext; diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 48ec7d018f51..3c8b50839690 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -45,6 +45,10 @@ QHash ActivityListModel::roleNames() const QHash roles; roles[PathRole] = "path"; roles[MessageRole] = "message"; + roles[ActionRole] = "type"; + roles[ActionIconRole] = "icon"; + roles[ActionTextRole] = "subject"; + roles[ObjectTypeRole] = "objectType"; return roles; } @@ -73,7 +77,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const if(folder) relPath.prepend(folder->remotePath()); list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); if (list.count() > 0) { - return QVariant(list.at(0)); + QString path = "file:///" + QString(list.at(0)); + return QUrl(path); } // File does not exist anymore? Let's try to open its path if(QFileInfo(relPath).exists()) { @@ -83,7 +88,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } } } - return QVariant(); + return QString(); case ActionsLinksRole:{ QList customList; foreach (ActivityLink customItem, a._links) { @@ -96,13 +101,13 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const case ActionIconRole:{ ActionIcon actionIcon; if(a._type == Activity::NotificationType){ - /*QIcon cachedIcon = ServerNotificationHandler::iconCache.value(a._id); + QIcon cachedIcon; if(!cachedIcon.isNull()) { actionIcon.iconType = ActivityIconType::iconUseCached; actionIcon.cachedIcon = cachedIcon; } else { actionIcon.iconType = ActivityIconType::iconBell; - }*/ + } } else if(a._type == Activity::SyncResultType){ actionIcon.iconType = ActivityIconType::iconStateError; } else if(a._type == Activity::SyncFileItemType){ @@ -131,13 +136,25 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const case ObjectTypeRole: return a._objectType; case ActionRole:{ - QVariant type; - type.setValue(a._type); - return type; + switch (a._type) { + case Activity::ActivityType: + return "Activity"; + case Activity::NotificationType: + return "Notification"; + case Activity::SyncFileItemType: + return "File"; + case Activity::SyncResultType: + return "Sync"; + default: + return QVariant(); + } } case ActionTextRole: return a._subject; case MessageRole: + if (a._message.isEmpty()) { + return QString("No description available."); + } return a._message; case LinkRole: return a._link; diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index b7c6d5fc711e..e7103a65ad21 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -86,6 +86,11 @@ bool User::serverHasTalk() const return _account->hasTalk(); } +bool User::hasActivities() const +{ + return _account->account()->capabilities().hasActivities(); +} + bool User::isCurrentUser() const { return _isCurrentUser; @@ -195,7 +200,7 @@ Q_INVOKABLE void UserModel::openCurrentAccountTalk() Q_INVOKABLE void UserModel::openCurrentAccountServer() { QString url = _users[_currentUserId].server(false); - if (! (url.contains("http://") || url.contains("https://")) ) { + if (!(url.contains("http://") || url.contains("https://"))) { url = "https://" + _users[_currentUserId].server(false); } QDesktopServices::openUrl(QUrl(url)); @@ -251,6 +256,11 @@ ActivityListModel *UserModel::currentActivityModel() return _users[currentUserIndex()].getActivityModel(); } +bool UserModel::currentUserHasActivities() +{ + return _users[currentUserIndex()].hasActivities(); +} + /*-------------------------------------------------------------------------------------*/ ImageProvider::ImageProvider() diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 4b5008ead19a..d68c2bc9bbc3 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -28,6 +28,7 @@ class User QString name() const; QString server(bool shortened = true) const; bool serverHasTalk() const; + bool hasActivities() const; QImage avatar() const; QString id() const; @@ -61,6 +62,7 @@ class UserModel : public QAbstractListModel Q_INVOKABLE bool isCurrentUserConnected(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); + Q_INVOKABLE bool currentUserHasActivities(); Q_INVOKABLE bool currentServerHasTalk(); Q_INVOKABLE void switchCurrentUser(const int &id); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 37e8658e01bb..23a6f5febbb2 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -393,17 +393,22 @@ Window { sourceSize.width: 48 } Column { + id: activityTextColumn Layout.leftMargin: 6 spacing: 4 Layout.alignment: Qt.AlignLeft Text { id: activityTextTitle - text: path + text: subject + width: 220 + elide: Text.ElideRight font.pointSize: 9 } Text { id: activityTextInfo - text: message + text: path + width: 220 + elide: Text.ElideRight font.pointSize: 8 } } @@ -412,19 +417,28 @@ Window { Layout.fillWidth: true } Button { + id: activityButton1 Layout.preferredWidth: activityItem.height Layout.preferredHeight: activityItem.height Layout.alignment: Qt.AlignRight flat: true + hoverEnabled: false + visible: (path === "") ? false : true display: AbstractButton.IconOnly icon.source: "qrc:///client/resources/files.svg" icon.color: "transparent" + + onClicked: + { + Qt.openUrlExternally(path) + } } Button { Layout.preferredWidth: activityItem.height Layout.preferredHeight: activityItem.height Layout.alignment: Qt.AlignRight flat: true + hoverEnabled: false display: AbstractButton.IconOnly icon.source: "qrc:///client/resources/public.svg" icon.color: "transparent" From ff92adf3e083fcdebcc997c1d39f158c93f6ad9a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 4 Jan 2020 10:09:01 +0100 Subject: [PATCH 041/120] Tray account menu wording/positioning Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 23a6f5febbb2..65650bbdb20e 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -95,7 +95,7 @@ Window { hoverEnabled: true onClicked: { - accMenuLoginButton.text = (userModelBackend.isCurrentUserConnected() ? "Logout" : "Login") + accMenuLoginButton.text = (userModelBackend.isCurrentUserConnected() ? "Log out" : "Log in") accountMenu.open() } @@ -119,6 +119,10 @@ Window { MenuSeparator { id: accountMenuSeparator } + MenuItem { + text: "Add account" + onClicked: userModelBackend.addAccount() + } MenuItem { id: accMenuLoginButton onClicked: (userModelBackend.isCurrentUserConnected() @@ -126,11 +130,7 @@ Window { : userModelBackend.login() ) } MenuItem { - text: "Add Account" - onClicked: userModelBackend.addAccount() - } - MenuItem { - text: "Remove Account" + text: "Remove account" onClicked: userModelBackend.removeAccount() } From ed9c06583aea53ab14b5d4f0bd09a6fe34e959cc Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 4 Jan 2020 12:08:30 +0100 Subject: [PATCH 042/120] Fix macOS popup y position, neglecting unneccesary calculations Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index aaf35a10b6e5..2a98337279bd 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -131,6 +131,11 @@ int Systray::calcTrayWindowX() } int Systray::calcTrayWindowY() { +#ifdef Q_OS_OSX + // macOS menu bar is always 22 (effective) pixels and at the top + // don't use availableGeometry() here, because this also excludes the dock + return 22+6; +#else #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QScreen *trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); #else @@ -157,7 +162,7 @@ int Systray::calcTrayWindowY() } } else { // tray icon is on the top - return (trayScreen->geometry().height() - trayScreen->availableGeometry().height()) + 6; +#endif } } } // namespace OCC From dc9744448c0881991a9fd05b5396e19a015445e2 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 4 Jan 2020 12:15:01 +0100 Subject: [PATCH 043/120] Fix wrong macro clause Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 2a98337279bd..82673db4743b 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -132,7 +132,7 @@ int Systray::calcTrayWindowX() int Systray::calcTrayWindowY() { #ifdef Q_OS_OSX - // macOS menu bar is always 22 (effective) pixels and at the top + // macOS menu bar is always 22 (effective) pixels // don't use availableGeometry() here, because this also excludes the dock return 22+6; #else @@ -162,7 +162,8 @@ int Systray::calcTrayWindowY() } } else { // tray icon is on the top -#endif + return (trayScreen->geometry().height() - trayScreen->availableGeometry().height()) + 6; } +#endif } } // namespace OCC From d653618b0aa2578afdd3489b52b9032fa8221bb2 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 4 Jan 2020 12:18:06 +0100 Subject: [PATCH 044/120] Fix blurry caret svg due to https://bugreports.qt.io/browse/QTBUG-44863 Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 65650bbdb20e..6ddbabb672d7 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -230,6 +230,8 @@ Window { verticalAlignment: Qt.AlignCenter Layout.margins: 12 source: "qrc:///client/theme/white/caret-down.svg" + sourceSize.width: 20 + sourceSize.height: 20 } } } From aa9921c6f57f76f79f7f8e98d535019951cd9ab9 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 4 Jan 2020 17:22:56 +0100 Subject: [PATCH 045/120] Quit/settings button in tray menu, disable context menu, font size fixes Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/owncloudgui.cpp | 21 +++++++-------------- src/gui/systray.h | 2 ++ src/gui/tray/UserLine.qml | 4 ++-- src/gui/tray/Window.qml | 38 ++++++++++++++++++++++++++------------ 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 8916c28468b5..a8c4a7f3491d 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -85,6 +85,12 @@ ownCloudGui::ownCloudGui(Application *parent) connect(_tray.data(), &QSystemTrayIcon::activated, this, &ownCloudGui::slotTrayClicked); + connect(_tray.data(), &Systray::openSettings, + this, &ownCloudGui::slotShowSettings); + + connect(_tray.data(), &Systray::shutdown, + this, &ownCloudGui::slotShutdown); + ProgressDispatcher *pd = ProgressDispatcher::instance(); connect(pd, &ProgressDispatcher::progressInfo, this, &ownCloudGui::slotUpdateProgress); @@ -171,17 +177,7 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) raiseDialog(shareDialog); } } else { -#ifdef Q_OS_MAC - // on macOS, a left click always opens menu. - // However if the settings dialog is already visible but hidden - // by other applications, this will bring it to the front. - if (!_settingsDialog.isNull() && _settingsDialog->isVisible()) { - raiseDialog(_settingsDialog.data()); - } -#else _tray->showWindow(); - //slotOpenSettingsDialog(); -#endif } } // FIXME: Also make sure that any auto updater dialogue https://github.com/owncloud/client/issues/5613 @@ -522,10 +518,6 @@ void ownCloudGui::setupContextMenu() _recentActionsMenu = new QMenu(tr("Recent Changes"), _contextMenu.data()); - // this must be called only once after creating the context menu, or - // it will trigger a bug in Ubuntu's SNI bridge patch (11.10, 12.04). - _tray->setContextMenu(_contextMenu.data()); - // The tray menu is surprisingly problematic. Being able to switch to // a minimal version of it is a useful workaround and testing tool. if (minimalTrayMenu()) { @@ -1099,6 +1091,7 @@ void ownCloudGui::slotShutdown() _settingsDialog->close(); if (!_logBrowser.isNull()) _logBrowser->deleteLater(); + _app->quit(); } void ownCloudGui::slotToggleLogBrowser() diff --git a/src/gui/systray.h b/src/gui/systray.h index bb02eecef447..b2254841c1f6 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -53,6 +53,8 @@ class Systray signals: void currentUserChanged(); + void openSettings(); + void shutdown(); Q_INVOKABLE void hideWindow(); Q_INVOKABLE void showWindow(); diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 8aaa58398d63..4ade26f23f5b 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -68,14 +68,14 @@ MenuItem { id: accountUser text: name color: "black" - font.pointSize: 9 + font.pixelSize: 12 font.bold: true } Label { id: accountServer text: server color: "black" - font.pointSize: 8 + font.pixelSize: 10 } } diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 6ddbabb672d7..42a63a9bc8ea 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -113,33 +113,47 @@ Window { Instantiator { model: userModelBackend delegate: UserLine {} - onObjectAdded: accountMenu.insertItem(index, object) + onObjectAdded: accountMenu.insertItem(3, object) onObjectRemoved: accountMenu.removeItem(object) } - MenuSeparator { id: accountMenuSeparator } - - MenuItem { - text: "Add account" - onClicked: userModelBackend.addAccount() - } MenuItem { id: accMenuLoginButton onClicked: (userModelBackend.isCurrentUserConnected() ? userModelBackend.logout() : userModelBackend.login() ) } + MenuItem { text: "Remove account" onClicked: userModelBackend.removeAccount() } - Component.onCompleted: { - if(userModelBackend.numUsers() === 0) { + MenuSeparator { id: accountMenuSeparator } + + MenuItem { + text: "Add account" + onClicked: userModelBackend.addAccount() + } + + MenuSeparator { id: otherMenuSeparator } + + MenuItem { + text: "Open settings" + onClicked: systrayBackend.openSettings() + } + + MenuItem { + text: "Quit Nextcloud" + onClicked: systrayBackend.shutdown() + } + + Component.onCompleted: {/* + if(userModelBackend.numUsers() === 1) { accountMenuSeparator.height = 0 } else { accountMenuSeparator.height = 13 - } + }*/ } } } @@ -214,14 +228,14 @@ Window { id: currentAccountUser text: userModelBackend.currentUserName() color: "white" - font.pointSize: 9 + font.pixelSize: 12 font.bold: true } Label { id: currentAccountServer text: userModelBackend.currentUserServer() color: "white" - font.pointSize: 8 + font.pixelSize: 10 } } From a90995cf15e19c7fcdc548901fe00eb23c71beec Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 5 Jan 2020 16:25:32 +0100 Subject: [PATCH 046/120] Focus and popup improvements for macOS Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/owncloudgui.cpp | 2 ++ src/gui/systray.cpp | 18 +++++++++++++++++- src/gui/systray.h | 4 ++++ src/gui/tray/Window.qml | 10 ++++++++++ 4 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index a8c4a7f3491d..24eb9aea9d3e 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -176,6 +176,8 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) Q_ASSERT(shareDialog.data()); raiseDialog(shareDialog); } + } else if (_tray->isOpen()) { + _tray->hideWindow(); } else { _tray->showWindow(); } diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 82673db4743b..db61cfa82715 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -37,7 +37,8 @@ namespace OCC { Systray::Systray() // TODO: make singleton, provide ::instance() - : _trayComponent(nullptr) + : _isOpen(false) + , _trayComponent(nullptr) , _trayContext(nullptr) { // Create QML tray engine, build component, set C++ backend context used in window.qml @@ -70,6 +71,21 @@ void Systray::slotChangeActivityModel() emit currentUserChanged(); } +bool Systray::isOpen() +{ + return _isOpen; +} + +Q_INVOKABLE void Systray::setOpened() +{ + _isOpen = true; +} + +Q_INVOKABLE void Systray::setClosed() +{ + _isOpen = false; +} + void Systray::showMessage(const QString &title, const QString &message, MessageIcon icon, int millisecondsTimeoutHint) { #ifdef USE_FDO_NOTIFICATIONS diff --git a/src/gui/systray.h b/src/gui/systray.h index b2254841c1f6..6fe451c00c0f 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -47,9 +47,12 @@ class Systray ~Systray(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); + bool isOpen(); Q_INVOKABLE int calcTrayWindowX(); Q_INVOKABLE int calcTrayWindowY(); + Q_INVOKABLE void setOpened(); + Q_INVOKABLE void setClosed(); signals: void currentUserChanged(); @@ -63,6 +66,7 @@ private slots: void slotChangeActivityModel(); private: + bool _isOpen; QQmlEngine *_trayEngine; QQmlComponent *_trayComponent; QQmlContext *_trayContext; diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 42a63a9bc8ea..a761885b7753 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -17,6 +17,7 @@ Window { onActiveChanged: { if(!active) { trayWindow.hide(); + systrayBackend.setClosed(); } } @@ -45,13 +46,17 @@ Window { Connections { target: systrayBackend onShowWindow: { + accountMenu.close(); trayWindow.show(); + trayWindow.raise(); trayWindow.requestActivate(); trayWindow.setX( systrayBackend.calcTrayWindowX()); trayWindow.setY( systrayBackend.calcTrayWindowY()); + systrayBackend.setOpened(); } onHideWindow: { trayWindow.hide(); + systrayBackend.setClosed(); } } @@ -138,6 +143,11 @@ Window { MenuSeparator { id: otherMenuSeparator } + MenuItem { + text: systrayBackend.syncIsPaused() ? "Resume syncing" : "Pause syncing" + onClicked: systrayBackend.pauseResumeSync() + } + MenuItem { text: "Open settings" onClicked: systrayBackend.openSettings() From 330ff96ee2f5cc1117a7419f7dcbce4e48417fec Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 5 Jan 2020 21:06:42 +0100 Subject: [PATCH 047/120] Code cleanup regarding context menu. Pause/resume logic implemented. Halfway through remodeling account menu. Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/owncloudgui.cpp | 486 ++----------------------------------- src/gui/owncloudgui.h | 30 +-- src/gui/systray.cpp | 18 ++ src/gui/systray.h | 6 + src/gui/tray/UserLine.qml | 11 +- src/gui/tray/UserModel.cpp | 10 +- src/gui/tray/Window.qml | 36 ++- src/libsync/theme.cpp | 23 +- src/libsync/theme.h | 8 +- 9 files changed, 83 insertions(+), 545 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 24eb9aea9d3e..54dd1faac0c7 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -72,19 +72,24 @@ ownCloudGui::ownCloudGui(Application *parent) , _app(parent) { _tray = new Systray(); - //qmlRegisterType("nc.desktop.systray.backend", 1, 0, "Systray"); _tray->setParent(this); // for the beginning, set the offline icon until the account was verified - _tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true, /*currently visible?*/ false)); - - setupActions(); - setupContextMenu(); + _tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true)); _tray->show(); connect(_tray.data(), &QSystemTrayIcon::activated, this, &ownCloudGui::slotTrayClicked); + connect(_tray.data(), &Systray::pauseSync, + this, &ownCloudGui::slotPauseAllFolders); + + connect(_tray.data(), &Systray::pauseSync, + this, &ownCloudGui::slotUnpauseAllFolders); + + connect(_tray.data(), &Systray::openHelp, + this, &ownCloudGui::slotHelp); + connect(_tray.data(), &Systray::openSettings, this, &ownCloudGui::slotShowSettings); @@ -99,11 +104,6 @@ ownCloudGui::ownCloudGui(Application *parent) connect(folderMan, &FolderMan::folderSyncStateChange, this, &ownCloudGui::slotSyncStateChange); - connect(AccountManager::instance(), &AccountManager::accountAdded, - this, &ownCloudGui::updateContextMenuNeeded); - connect(AccountManager::instance(), &AccountManager::accountRemoved, - this, &ownCloudGui::updateContextMenuNeeded); - connect(Logger::instance(), &Logger::guiLog, this, &ownCloudGui::slotShowTrayMessage); connect(Logger::instance(), &Logger::optionalGuiLog, @@ -158,13 +158,6 @@ void ownCloudGui::slotOpenSettingsDialog() void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) { - if (_workaroundFakeDoubleClick) { - static QElapsedTimer last_click; - if (last_click.isValid() && last_click.elapsed() < 200) { - return; - } - last_click.start(); - } // Left click if (reason == QSystemTrayIcon::Trigger) { @@ -189,7 +182,6 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason) void ownCloudGui::slotSyncStateChange(Folder *folder) { slotComputeOverallSyncStatus(); - updateContextMenuNeeded(); if (!folder) { return; // Valid, just a general GUI redraw was needed. @@ -210,7 +202,6 @@ void ownCloudGui::slotSyncStateChange(Folder *folder) void ownCloudGui::slotFoldersChanged() { slotComputeOverallSyncStatus(); - updateContextMenuNeeded(); } void ownCloudGui::slotOpenPath(const QString &path) @@ -220,7 +211,6 @@ void ownCloudGui::slotOpenPath(const QString &path) void ownCloudGui::slotAccountStateChanged() { - updateContextMenuNeeded(); slotComputeOverallSyncStatus(); } @@ -246,7 +236,7 @@ void ownCloudGui::slotComputeOverallSyncStatus() // Don't overwrite the status if we're currently syncing if (FolderMan::instance()->currentSyncFolder()) return; - _actionStatus->setText(text); + //_actionStatus->setText(text); }; foreach (auto a, AccountManager::instance()->accounts()) { @@ -266,7 +256,7 @@ void ownCloudGui::slotComputeOverallSyncStatus() } if (!problemAccounts.empty()) { - _tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible())); + _tray->setIcon(Theme::instance()->folderOfflineIcon(true)); if (allDisconnected) { setStatusText(tr("Disconnected")); } else { @@ -296,12 +286,12 @@ void ownCloudGui::slotComputeOverallSyncStatus() } if (allSignedOut) { - _tray->setIcon(Theme::instance()->folderOfflineIcon(true, contextMenuVisible())); + _tray->setIcon(Theme::instance()->folderOfflineIcon(true)); _tray->setToolTip(tr("Please sign in")); setStatusText(tr("Signed out")); return; } else if (allPaused) { - _tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true, contextMenuVisible())); + _tray->setIcon(Theme::instance()->syncStateIcon(SyncResult::Paused, true)); _tray->setToolTip(tr("Account synchronization is disabled")); setStatusText(tr("Synchronization is paused")); return; @@ -328,7 +318,7 @@ void ownCloudGui::slotComputeOverallSyncStatus() iconStatus = SyncResult::Problem; } - QIcon statusIcon = Theme::instance()->syncStateIcon(iconStatus, true, contextMenuVisible()); + QIcon statusIcon = Theme::instance()->syncStateIcon(iconStatus, true); _tray->setIcon(statusIcon); // create the tray blob message, check if we have an defined state @@ -366,377 +356,6 @@ void ownCloudGui::slotComputeOverallSyncStatus() } } -void ownCloudGui::addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu) -{ - // Only show the name in the action if it's not part of an - // account sub menu. - QString browserOpen = tr("Open in browser"); - if (!separateMenu) { - browserOpen = tr("Open %1 in browser").arg(Theme::instance()->appNameGUI()); - } - auto actionOpenoC = menu->addAction(browserOpen); - actionOpenoC->setProperty(propertyAccountC, QVariant::fromValue(accountState->account())); - QObject::connect(actionOpenoC, &QAction::triggered, this, &ownCloudGui::slotOpenOwnCloud); - - FolderMan *folderMan = FolderMan::instance(); - bool firstFolder = true; - bool singleSyncFolder = folderMan->map().size() == 1 && Theme::instance()->singleSyncFolder(); - bool onePaused = false; - bool allPaused = true; - foreach (Folder *folder, folderMan->map()) { - if (folder->accountState() != accountState.data()) { - continue; - } - - if (folder->syncPaused()) { - onePaused = true; - } else { - allPaused = false; - } - - if (firstFolder && !singleSyncFolder) { - firstFolder = false; - menu->addSeparator(); - menu->addAction(tr("Managed Folders:"))->setDisabled(true); - } - - QAction *action = menu->addAction(tr("Open folder '%1'").arg(folder->shortGuiLocalPath())); - auto alias = folder->alias(); - connect(action, &QAction::triggered, this, [this, alias] { this->slotFolderOpenAction(alias); }); - } - - menu->addSeparator(); - if (separateMenu) { - if (onePaused) { - QAction *enable = menu->addAction(tr("Resume all folders")); - enable->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(enable, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders); - } - if (!allPaused) { - QAction *enable = menu->addAction(tr("Pause all folders")); - enable->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(enable, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders); - } - - if (accountState->isSignedOut()) { - QAction *signin = menu->addAction(tr("Log in …")); - signin->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(signin, &QAction::triggered, this, &ownCloudGui::slotLogin); - } else { - QAction *signout = menu->addAction(tr("Log out")); - signout->setProperty(propertyAccountC, QVariant::fromValue(accountState)); - connect(signout, &QAction::triggered, this, &ownCloudGui::slotLogout); - } - } -} - -void ownCloudGui::slotContextMenuAboutToShow() -{ - _contextMenuVisibleManual = true; - - // Update icon in sys tray, as it might change depending on the context menu state - slotComputeOverallSyncStatus(); - - if (!_workaroundNoAboutToShowUpdate) { - updateContextMenu(); - } -} - -void ownCloudGui::slotContextMenuAboutToHide() -{ - _contextMenuVisibleManual = false; - - // Update icon in sys tray, as it might change depending on the context menu state - slotComputeOverallSyncStatus(); -} - -bool ownCloudGui::contextMenuVisible() const -{ - // On some platforms isVisible doesn't work and always returns false, - // elsewhere aboutToHide is unreliable. - if (_workaroundManualVisibility) - return _contextMenuVisibleManual; - return _contextMenu->isVisible(); -} - -static bool minimalTrayMenu() -{ - static QByteArray var = qgetenv("OWNCLOUD_MINIMAL_TRAY_MENU"); - return !var.isEmpty(); -} - -static bool updateWhileVisible() -{ - static QByteArray var = qgetenv("OWNCLOUD_TRAY_UPDATE_WHILE_VISIBLE"); - if (var == "1") { - return true; - } else if (var == "0") { - return false; - } else { - // triggers bug on OS X: https://bugreports.qt.io/browse/QTBUG-54845 - // or flickering on Xubuntu - return false; - } -} - -static QByteArray envForceQDBusTrayWorkaround() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_QDBUS_TRAY_WORKAROUND"); - return var; -} - -static QByteArray envForceWorkaroundShowAndHideTray() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_SHOW_HIDE"); - return var; -} - -static QByteArray envForceWorkaroundNoAboutToShowUpdate() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_NO_ABOUT_TO_SHOW"); - return var; -} - -static QByteArray envForceWorkaroundFakeDoubleClick() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_FAKE_DOUBLE_CLICK"); - return var; -} - -static QByteArray envForceWorkaroundManualVisibility() -{ - static QByteArray var = qgetenv("OWNCLOUD_FORCE_TRAY_MANUAL_VISIBILITY"); - return var; -} - -void ownCloudGui::setupContextMenu() -{ - if (_contextMenu) { - return; - } - - _contextMenu.reset(new QMenu()); - _contextMenu->setTitle(Theme::instance()->appNameGUI()); - - _recentActionsMenu = new QMenu(tr("Recent Changes"), _contextMenu.data()); - - // The tray menu is surprisingly problematic. Being able to switch to - // a minimal version of it is a useful workaround and testing tool. - if (minimalTrayMenu()) { - _contextMenu->addAction(_actionQuit); - return; - } - - auto applyEnvVariable = [](bool *sw, const QByteArray &value) { - if (value == "1") - *sw = true; - if (value == "0") - *sw = false; - }; - - // This is an old compound flag that people might still depend on - bool qdbusmenuWorkarounds = false; - applyEnvVariable(&qdbusmenuWorkarounds, envForceQDBusTrayWorkaround()); - if (qdbusmenuWorkarounds) { - _workaroundFakeDoubleClick = true; - _workaroundNoAboutToShowUpdate = true; - _workaroundShowAndHideTray = true; - } - -#ifdef Q_OS_MAC - // https://bugreports.qt.io/browse/QTBUG-54633 - _workaroundNoAboutToShowUpdate = true; - _workaroundManualVisibility = true; -#endif - -#ifdef Q_OS_LINUX - // For KDE sessions if the platform plugin is missing, - // neither aboutToShow() updates nor the isVisible() call - // work. At least aboutToHide is reliable. - // https://github.com/owncloud/client/issues/6545 - static QByteArray xdgCurrentDesktop = qgetenv("XDG_CURRENT_DESKTOP"); - static QByteArray desktopSession = qgetenv("DESKTOP_SESSION"); - bool isKde = - xdgCurrentDesktop.contains("KDE") - || desktopSession.contains("plasma") - || desktopSession.contains("kde"); - QObject *platformMenu = reinterpret_cast(_tray->contextMenu()->platformMenu()); - if (isKde && platformMenu && platformMenu->metaObject()->className() == QLatin1String("QDBusPlatformMenu")) { - _workaroundManualVisibility = true; - _workaroundNoAboutToShowUpdate = true; - } -#endif - - applyEnvVariable(&_workaroundNoAboutToShowUpdate, envForceWorkaroundNoAboutToShowUpdate()); - applyEnvVariable(&_workaroundFakeDoubleClick, envForceWorkaroundFakeDoubleClick()); - applyEnvVariable(&_workaroundShowAndHideTray, envForceWorkaroundShowAndHideTray()); - applyEnvVariable(&_workaroundManualVisibility, envForceWorkaroundManualVisibility()); - - qCInfo(lcApplication) << "Tray menu workarounds:" - << "noabouttoshow:" << _workaroundNoAboutToShowUpdate - << "fakedoubleclick:" << _workaroundFakeDoubleClick - << "showhide:" << _workaroundShowAndHideTray - << "manualvisibility:" << _workaroundManualVisibility; - - - connect(&_delayedTrayUpdateTimer, &QTimer::timeout, this, &ownCloudGui::updateContextMenu); - _delayedTrayUpdateTimer.setInterval(2 * 1000); - _delayedTrayUpdateTimer.setSingleShot(true); - - connect(_contextMenu.data(), SIGNAL(aboutToShow()), SLOT(slotContextMenuAboutToShow())); - // unfortunately aboutToHide is unreliable, it seems to work on OSX though - connect(_contextMenu.data(), SIGNAL(aboutToHide()), SLOT(slotContextMenuAboutToHide())); - - // Populate the context menu now. - updateContextMenu(); -} - -void ownCloudGui::updateContextMenu() -{ - if (minimalTrayMenu()) { - return; - } - - // If it's visible, we can't update live, and it won't be updated lazily: reschedule - if (contextMenuVisible() && !updateWhileVisible() && _workaroundNoAboutToShowUpdate) { - if (!_delayedTrayUpdateTimer.isActive()) { - _delayedTrayUpdateTimer.start(); - } - return; - } - - if (_workaroundShowAndHideTray) { - // To make tray menu updates work with these bugs (see setupContextMenu) - // we need to hide and show the tray icon. We don't want to do that - // while it's visible! - if (contextMenuVisible()) { - if (!_delayedTrayUpdateTimer.isActive()) { - _delayedTrayUpdateTimer.start(); - } - return; - } - _tray->hide(); - } - - _contextMenu->clear(); - slotRebuildRecentMenus(); - - // We must call deleteLater because we might be called from the press in one of the actions. - foreach (auto menu, _accountMenus) { - menu->deleteLater(); - } - _accountMenus.clear(); - - auto accountList = AccountManager::instance()->accounts(); - - bool isConfigured = (!accountList.isEmpty()); - bool atLeastOneConnected = false; - bool atLeastOnePaused = false; - bool atLeastOneNotPaused = false; - foreach (auto a, accountList) { - if (a->isConnected()) { - atLeastOneConnected = true; - } - } - foreach (auto f, FolderMan::instance()->map()) { - if (f->syncPaused()) { - atLeastOnePaused = true; - } else { - atLeastOneNotPaused = true; - } - } - - if (accountList.count() > 1) { - foreach (AccountStatePtr account, accountList) { - QMenu *accountMenu = new QMenu(account->account()->displayName(), _contextMenu.data()); - _accountMenus.append(accountMenu); - _contextMenu->addMenu(accountMenu); - - addAccountContextMenu(account, accountMenu, true); - fetchNavigationApps(account); - } - } else if (accountList.count() == 1) { - addAccountContextMenu(accountList.first(), _contextMenu.data(), false); - fetchNavigationApps(accountList.first()); - } - - _contextMenu->addSeparator(); - - _contextMenu->addAction(_actionStatus); - if (isConfigured && atLeastOneConnected) { - _contextMenu->addMenu(_recentActionsMenu); - } - - _contextMenu->addSeparator(); - - if (_navLinksMenu) { - _contextMenu->addMenu(_navLinksMenu); - } - - _contextMenu->addSeparator(); - - if (accountList.isEmpty()) { - _contextMenu->addAction(_actionNewAccountWizard); - } - _contextMenu->addAction(_actionSettings); - if (!Theme::instance()->helpUrl().isEmpty()) { - _contextMenu->addAction(_actionHelp); - } - - if (_actionCrash) { - _contextMenu->addAction(_actionCrash); - } - - _contextMenu->addSeparator(); - - if (atLeastOnePaused) { - QString text; - if (accountList.count() > 1) { - text = tr("Resume all synchronization"); - } else { - text = tr("Resume synchronization"); - } - QAction *action = _contextMenu->addAction(text); - connect(action, &QAction::triggered, this, &ownCloudGui::slotUnpauseAllFolders); - } - if (atLeastOneNotPaused) { - QString text; - if (accountList.count() > 1) { - text = tr("Pause all synchronization"); - } else { - text = tr("Pause synchronization"); - } - QAction *action = _contextMenu->addAction(text); - connect(action, &QAction::triggered, this, &ownCloudGui::slotPauseAllFolders); - } - _contextMenu->addAction(_actionQuit); - - if (_workaroundShowAndHideTray) { - _tray->show(); - } -} - -void ownCloudGui::updateContextMenuNeeded() -{ - // if it's visible and we can update live: update now - if (contextMenuVisible() && updateWhileVisible()) { - // Note: don't update while visible on OSX - // https://bugreports.qt.io/browse/QTBUG-54845 - updateContextMenu(); - return; - } - - // if we can't lazily update: update later - if (_workaroundNoAboutToShowUpdate) { - // Note: don't update immediately even in the invisible case - // as that can lead to extremely frequent menu updates - if (!_delayedTrayUpdateTimer.isActive()) { - _delayedTrayUpdateTimer.start(); - } - return; - } -} - void ownCloudGui::slotShowTrayMessage(const QString &title, const QString &msg) { if (_tray) @@ -774,30 +393,6 @@ void ownCloudGui::slotFolderOpenAction(const QString &alias) } } -void ownCloudGui::setupActions() -{ - _actionStatus = new QAction(tr("Unknown status"), this); - _actionStatus->setEnabled(false); - _navLinksMenu = new QMenu(tr("Apps")); - _navLinksMenu->setEnabled(false); - _actionSettings = new QAction(tr("Settings …"), this); - _actionNewAccountWizard = new QAction(tr("New account …"), this); - - QObject::connect(_actionSettings, &QAction::triggered, this, &ownCloudGui::slotShowSettings); - QObject::connect(_actionNewAccountWizard, &QAction::triggered, this, &ownCloudGui::slotNewAccountWizard); - _actionHelp = new QAction(tr("Help"), this); - QObject::connect(_actionHelp, &QAction::triggered, this, &ownCloudGui::slotHelp); - _actionQuit = new QAction(tr("Quit %1").arg(Theme::instance()->appNameGUI()), this); - QObject::connect(_actionQuit, SIGNAL(triggered(bool)), _app, SLOT(quit())); - - if (_app->debugMode()) { - _actionCrash = new QAction(tr("Crash now", "Only shows in debug mode to allow testing the crash handler"), this); - connect(_actionCrash, &QAction::triggered, _app, &Application::slotCrash); - } else { - _actionCrash = nullptr; - } -} - void ownCloudGui::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){ if(statusCode == 200){ qCDebug(lcApplication) << "New navigation apps ETag Response Header received " << value; @@ -875,7 +470,7 @@ void ownCloudGui::slotNavigationAppsFetched(const QJsonDocument &reply, int stat } } } else if(accountList.size() == 1){ - buildNavigationAppsMenu(account, _contextMenu.data()); + //buildNavigationAppsMenu(account, _contextMenu.data()); } } } @@ -885,40 +480,17 @@ void ownCloudGui::slotOcsError(int statusCode, const QString &message) emit serverError(statusCode, message); } -void ownCloudGui::slotRebuildRecentMenus() -{ - _recentActionsMenu->clear(); - if (!_recentItemsActions.isEmpty()) { - foreach (QAction *a, _recentItemsActions) { - _recentActionsMenu->addAction(a); - } - _recentActionsMenu->addSeparator(); - } else { - _recentActionsMenu->addAction(tr("No items synced recently"))->setEnabled(false); - } -} - -/// Returns true if the completion of a given item should show up in the -/// 'Recent Activity' menu -static bool shouldShowInRecentsMenu(const SyncFileItem &item) -{ - return !Progress::isIgnoredKind(item._status) - && item._instruction != CSYNC_INSTRUCTION_EVAL - && item._instruction != CSYNC_INSTRUCTION_NONE; -} - - void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &progress) { Q_UNUSED(folder); if (progress.status() == ProgressInfo::Discovery) { if (!progress._currentDiscoveredRemoteFolder.isEmpty()) { - _actionStatus->setText(tr("Checking for changes in remote '%1'") - .arg(progress._currentDiscoveredRemoteFolder)); + //_actionStatus->setText(tr("Checking for changes in remote '%1'") + //.arg(progress._currentDiscoveredRemoteFolder)); } else if (!progress._currentDiscoveredLocalFolder.isEmpty()) { - _actionStatus->setText(tr("Checking for changes in local '%1'") - .arg(progress._currentDiscoveredLocalFolder)); + //_actionStatus->setText(tr("Checking for changes in local '%1'") + //.arg(progress._currentDiscoveredLocalFolder)); } } else if (progress.status() == ProgressInfo::Done) { QTimer::singleShot(2000, this, &ownCloudGui::slotComputeOverallSyncStatus); @@ -941,7 +513,7 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & .arg(currentFile) .arg(totalFileCount); } - _actionStatus->setText(msg); + //_actionStatus->setText(msg); } else { QString totalSizeStr = Utility::octetsToString(progress.totalSize()); QString msg; @@ -952,16 +524,10 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & msg = tr("Syncing %1") .arg(totalSizeStr); } - _actionStatus->setText(msg); + //_actionStatus->setText(msg); } - if (!progress._lastCompletedItem.isEmpty() - && shouldShowInRecentsMenu(progress._lastCompletedItem)) { - if (Progress::isWarningKind(progress._lastCompletedItem._status)) { - // display a warn icon if warnings happened. - QIcon warnIcon(":/client/resources/warning"); - _actionRecent->setIcon(warnIcon); - } + if (!progress._lastCompletedItem.isEmpty()) { QString kindStr = Progress::asResultString(progress._lastCompletedItem); QString timeStr = QTime::currentTime().toString("hh:mm"); @@ -980,12 +546,6 @@ void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo & _recentItemsActions.takeFirst()->deleteLater(); } _recentItemsActions.append(action); - - // Update the "Recent" menu if the context menu is being shown, - // otherwise it'll be updated later, when the context menu is opened. - if (updateWhileVisible() && contextMenuVisible()) { - slotRebuildRecentMenus(); - } } } diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index 98af71bb792a..518be8b16597 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -64,25 +64,16 @@ class ownCloudGui : public QObject bool cloudProviderApiAvailable(); #endif - /// Whether the tray menu is visible - bool contextMenuVisible() const; - signals: void setupProxy(); void serverError(int code, const QString &message); void isShowingSettingsDialog(); public slots: - void setupContextMenu(); - void updateContextMenu(); - void updateContextMenuNeeded(); - void slotContextMenuAboutToShow(); - void slotContextMenuAboutToHide(); void slotComputeOverallSyncStatus(); void slotShowTrayMessage(const QString &title, const QString &msg); void slotShowOptionalTrayMessage(const QString &title, const QString &msg); void slotFolderOpenAction(const QString &alias); - void slotRebuildRecentMenus(); void slotUpdateProgress(const QString &folder, const ProgressInfo &progress); void slotShowGuiMessage(const QString &title, const QString &message); void slotFoldersChanged(); @@ -126,22 +117,12 @@ private slots: private: void setPauseOnAllFoldersHelper(bool pause); - void setupActions(); - void addAccountContextMenu(AccountStatePtr accountState, QMenu *menu, bool separateMenu); void fetchNavigationApps(AccountStatePtr account); void buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu); QPointer _tray; QPointer _settingsDialog; QPointer _logBrowser; - // tray's menu - QScopedPointer _contextMenu; - - // Manually tracking whether the context menu is visible via aboutToShow - // and aboutToHide. Unfortunately aboutToHide isn't reliable everywhere - // so this only gets used with _workaroundManualVisibility (when the tray's - // isVisible() is unreliable) - bool _contextMenuVisibleManual = false; #ifdef WITH_LIBCLOUDPROVIDERS QDBusConnection _bus; @@ -149,21 +130,12 @@ private slots: QMenu *_recentActionsMenu; QVector _accountMenus; - bool _workaroundShowAndHideTray = false; - bool _workaroundNoAboutToShowUpdate = false; - bool _workaroundFakeDoubleClick = false; - bool _workaroundManualVisibility = false; - QTimer _delayedTrayUpdateTimer; QMap> _shareDialogs; QAction *_actionNewAccountWizard; QAction *_actionSettings; - QAction *_actionStatus; QAction *_actionEstimate; - QAction *_actionRecent; - QAction *_actionHelp; - QAction *_actionQuit; - QAction *_actionCrash; + QMenu *_navLinksMenu; QMap _navApps; diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index db61cfa82715..93240c133d9f 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -38,6 +38,7 @@ namespace OCC { Systray::Systray() // TODO: make singleton, provide ::instance() : _isOpen(false) + , _syncIsPaused(false) , _trayComponent(nullptr) , _trayContext(nullptr) { @@ -182,4 +183,21 @@ int Systray::calcTrayWindowY() } #endif } + +bool Systray::syncIsPaused() +{ + return _syncIsPaused; +} + +void Systray::pauseResumeSync() +{ + if (_syncIsPaused) { + _syncIsPaused = false; + emit resumeSync(); + } else { + _syncIsPaused = true; + emit pauseSync(); + } +} + } // namespace OCC diff --git a/src/gui/systray.h b/src/gui/systray.h index 6fe451c00c0f..3460940a3862 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -49,15 +49,20 @@ class Systray void setToolTip(const QString &tip); bool isOpen(); + Q_INVOKABLE void pauseResumeSync(); Q_INVOKABLE int calcTrayWindowX(); Q_INVOKABLE int calcTrayWindowY(); + Q_INVOKABLE bool syncIsPaused(); Q_INVOKABLE void setOpened(); Q_INVOKABLE void setClosed(); signals: void currentUserChanged(); void openSettings(); + void openHelp(); void shutdown(); + void pauseSync(); + void resumeSync(); Q_INVOKABLE void hideWindow(); Q_INVOKABLE void showWindow(); @@ -67,6 +72,7 @@ private slots: private: bool _isOpen; + bool _syncIsPaused; QQmlEngine *_trayEngine; QQmlComponent *_trayComponent; QQmlContext *_trayContext; diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 4ade26f23f5b..0d4fb0f08906 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -8,15 +8,12 @@ MenuItem { Connections { target: userModelBackend onRefreshUserMenu: { - userLine.visible = isCurrentUser ? false : true - userLine.height = isCurrentUser ? 0 : 60 } } id: userLine - visible: isCurrentUser ? false : true width: 216 - height: isCurrentUser ? 0 : 60 + height: 60 Rectangle { id: userLineBackground @@ -52,7 +49,7 @@ MenuItem { spacing: 0 Image { id: accountAvatar - Layout.leftMargin: 4 + Layout.leftMargin: 2 verticalAlignment: Qt.AlignCenter source: ("image://avatars/" + index) Layout.preferredHeight: (userLineBackground.height -16) @@ -66,14 +63,18 @@ MenuItem { Layout.leftMargin: 12 Label { id: accountUser + width: 120 text: name + elide: Text.ElideRight color: "black" font.pixelSize: 12 font.bold: true } Label { id: accountServer + width: 120 text: server + elide: Text.ElideRight color: "black" font.pixelSize: 10 } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index e7103a65ad21..22b49396bcfb 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -50,10 +50,6 @@ QString User::name() const if (name == "") { name = _account->account()->credentials()->user(); } - if (name.size() > 19) { - name.truncate(17); - name.append("..."); - } return name; } @@ -63,10 +59,6 @@ QString User::server(bool shortened) const if (shortened) { serverUrl.replace(QLatin1String("https://"), QLatin1String("")); serverUrl.replace(QLatin1String("http://"), QLatin1String("")); - if (serverUrl.size() > 21) { - serverUrl.truncate(19); - serverUrl.append("..."); - } } return serverUrl; } @@ -278,4 +270,4 @@ QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize & } } -} \ No newline at end of file +} diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index a761885b7753..d7bbe4a94665 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -100,7 +100,7 @@ Window { hoverEnabled: true onClicked: { - accMenuLoginButton.text = (userModelBackend.isCurrentUserConnected() ? "Log out" : "Log in") + syncPauseButton.text = systrayBackend.syncIsPaused() ? "Resume sync for all" : "Pause sync for all" accountMenu.open() } @@ -109,6 +109,7 @@ Window { x: (currentAccountButton.x + 2) y: (currentAccountButton.y + currentAccountButton.height + 2) width: (currentAccountButton.width - 4) + closePolicy: "CloseOnPressOutside" background: Rectangle { border.color: "#0082c9" @@ -118,33 +119,19 @@ Window { Instantiator { model: userModelBackend delegate: UserLine {} - onObjectAdded: accountMenu.insertItem(3, object) + onObjectAdded: accountMenu.insertItem(index, object) onObjectRemoved: accountMenu.removeItem(object) } - MenuItem { - id: accMenuLoginButton - onClicked: (userModelBackend.isCurrentUserConnected() - ? userModelBackend.logout() - : userModelBackend.login() ) - } - - MenuItem { - text: "Remove account" - onClicked: userModelBackend.removeAccount() - } - - MenuSeparator { id: accountMenuSeparator } - MenuItem { text: "Add account" onClicked: userModelBackend.addAccount() } - MenuSeparator { id: otherMenuSeparator } + MenuSeparator { id: accountMenuSeparator } MenuItem { - text: systrayBackend.syncIsPaused() ? "Resume syncing" : "Pause syncing" + id: syncPauseButton onClicked: systrayBackend.pauseResumeSync() } @@ -153,6 +140,11 @@ Window { onClicked: systrayBackend.openSettings() } + MenuItem { + text: "Help" + onClicked: systrayBackend.openHelp() + } + MenuItem { text: "Quit Nextcloud" onClicked: systrayBackend.shutdown() @@ -236,23 +228,27 @@ Window { Layout.leftMargin: 6 Label { id: currentAccountUser + width: 128 text: userModelBackend.currentUserName() + elide: Text.ElideRight color: "white" font.pixelSize: 12 font.bold: true } Label { id: currentAccountServer + width: 128 text: userModelBackend.currentUserServer() + elide: Text.ElideRight color: "white" font.pixelSize: 10 } } Image { - Layout.alignment: Qt.AlignLeft + Layout.alignment: Qt.AlignRight verticalAlignment: Qt.AlignCenter - Layout.margins: 12 + Layout.margins: 8 source: "qrc:///client/theme/white/caret-down.svg" sourceSize.width: 20 sourceSize.height: 20 diff --git a/src/libsync/theme.cpp b/src/libsync/theme.cpp index 8b2b15e5c061..08bf1152180f 100644 --- a/src/libsync/theme.cpp +++ b/src/libsync/theme.cpp @@ -123,11 +123,11 @@ QIcon Theme::applicationIcon() const * helper to load a icon from either the icon theme the desktop provides or from * the apps Qt resources. */ -QIcon Theme::themeIcon(const QString &name, bool sysTray, bool sysTrayMenuVisible) const +QIcon Theme::themeIcon(const QString &name, bool sysTray) const { QString flavor; if (sysTray) { - flavor = systrayIconFlavor(_mono, sysTrayMenuVisible); + flavor = systrayIconFlavor(_mono); } else { flavor = QLatin1String("colored"); } @@ -170,7 +170,7 @@ QIcon Theme::themeIcon(const QString &name, bool sysTray, bool sysTrayMenuVisibl #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) // This defines the icon as a template and enables automatic macOS color handling // See https://bugreports.qt.io/browse/QTBUG-42109 - cached.setIsMask(_mono && sysTray && !sysTrayMenuVisible); + cached.setIsMask(_mono && sysTray); #endif #endif @@ -270,18 +270,11 @@ QString Theme::defaultClientFolder() const return appName(); } -QString Theme::systrayIconFlavor(bool mono, bool sysTrayMenuVisible) const +QString Theme::systrayIconFlavor(bool mono) const { - Q_UNUSED(sysTrayMenuVisible) QString flavor; if (mono) { flavor = Utility::hasDarkSystray() ? QLatin1String("white") : QLatin1String("black"); - -#ifdef Q_OS_MAC - if (sysTrayMenuVisible) { - flavor = QLatin1String("white"); - } -#endif } else { flavor = QLatin1String("colored"); } @@ -395,7 +388,7 @@ QVariant Theme::customMedia(CustomMediaType type) return re; } -QIcon Theme::syncStateIcon(SyncResult::Status status, bool sysTray, bool sysTrayMenuVisible) const +QIcon Theme::syncStateIcon(SyncResult::Status status, bool sysTray) const { // FIXME: Mind the size! QString statusIcon; @@ -427,7 +420,7 @@ QIcon Theme::syncStateIcon(SyncResult::Status status, bool sysTray, bool sysTray statusIcon = QLatin1String("state-error"); } - return themeIcon(statusIcon, sysTray, sysTrayMenuVisible); + return themeIcon(statusIcon, sysTray); } QIcon Theme::folderDisabledIcon() const @@ -435,9 +428,9 @@ QIcon Theme::folderDisabledIcon() const return themeIcon(QLatin1String("state-pause")); } -QIcon Theme::folderOfflineIcon(bool sysTray, bool sysTrayMenuVisible) const +QIcon Theme::folderOfflineIcon(bool sysTray) const { - return themeIcon(QLatin1String("state-offline"), sysTray, sysTrayMenuVisible); + return themeIcon(QLatin1String("state-offline"), sysTray); } QColor Theme::wizardHeaderTitleColor() const diff --git a/src/libsync/theme.h b/src/libsync/theme.h index 966c9b5c9e8f..12a643d0099d 100644 --- a/src/libsync/theme.h +++ b/src/libsync/theme.h @@ -93,10 +93,10 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject /** * get an sync state icon */ - virtual QIcon syncStateIcon(SyncResult::Status, bool sysTray = false, bool sysTrayMenuVisible = false) const; + virtual QIcon syncStateIcon(SyncResult::Status, bool sysTray = false) const; virtual QIcon folderDisabledIcon() const; - virtual QIcon folderOfflineIcon(bool sysTray = false, bool sysTrayMenuVisible = false) const; + virtual QIcon folderOfflineIcon(bool sysTray = false) const; virtual QIcon applicationIcon() const; #endif @@ -166,7 +166,7 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject virtual QString enforcedLocale() const { return QString(); } /** colored, white or black */ - QString systrayIconFlavor(bool mono, bool sysTrayMenuVisible = false) const; + QString systrayIconFlavor(bool mono) const; #ifndef TOKEN_AUTH_ONLY /** @@ -449,7 +449,7 @@ class OWNCLOUDSYNC_EXPORT Theme : public QObject protected: #ifndef TOKEN_AUTH_ONLY - QIcon themeIcon(const QString &name, bool sysTray = false, bool sysTrayMenuVisible = false) const; + QIcon themeIcon(const QString &name, bool sysTray = false) const; #endif Theme(); From 6ac1a4a35354642db0f2b24dd5a99063f9717e3a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 5 Jan 2020 21:31:09 +0100 Subject: [PATCH 048/120] Preps for UserLine submenu Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 9 ++------- src/gui/tray/Window.qml | 2 +- theme.qrc | 1 + theme/black/caret-down.svg | 1 + 4 files changed, 5 insertions(+), 8 deletions(-) create mode 100644 theme/black/caret-down.svg diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 0d4fb0f08906..be8a63775f4e 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -12,7 +12,7 @@ MenuItem { } id: userLine - width: 216 + width: 220 height: 60 Rectangle { @@ -79,13 +79,8 @@ MenuItem { font.pixelSize: 10 } } - - Item { - id: userLineSpacer - Layout.fillWidth: true - } } - } + } // accountButton } } // Rectangle userLineBackground } // MenuItem userLine diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index d7bbe4a94665..2e918319dfc7 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -108,7 +108,7 @@ Window { id: accountMenu x: (currentAccountButton.x + 2) y: (currentAccountButton.y + currentAccountButton.height + 2) - width: (currentAccountButton.width - 4) + width: (currentAccountButton.width) closePolicy: "CloseOnPressOutside" background: Rectangle { diff --git a/theme.qrc b/theme.qrc index 8b593df98ebf..e24188f3090e 100644 --- a/theme.qrc +++ b/theme.qrc @@ -135,5 +135,6 @@ theme/white/more-apps.svg theme/white/talk-app.svg theme/white/caret-down.svg + theme/black/caret-down.svg diff --git a/theme/black/caret-down.svg b/theme/black/caret-down.svg new file mode 100644 index 000000000000..bc3a4e0c5063 --- /dev/null +++ b/theme/black/caret-down.svg @@ -0,0 +1 @@ + From 9c0221a0fa14f6fdbb80ddada13e2c6d58e326b5 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 10 Jan 2020 16:28:53 +0100 Subject: [PATCH 049/120] UserLine drafting, bugfixes, restuctures Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/accountsettings.cpp | 2 - src/gui/owncloudgui.cpp | 6 --- src/gui/tray/UserLine.qml | 82 ++++++++++++++++++++++++++----------- src/gui/tray/UserModel.cpp | 24 +++++++++++ src/gui/tray/UserModel.h | 9 ++-- src/gui/tray/Window.qml | 8 +++- 6 files changed, 93 insertions(+), 38 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index a7fd0f5a1773..604abace1c51 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -232,8 +232,6 @@ void AccountSettings::createAccountToolbox() menu->addAction(action); connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount); - connect(UserModel::instance(), &UserModel::removeAccount, - this, &AccountSettings::slotOpenAccountWizard); _ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' ')); _ui->_accountToolbox->setMenu(menu); diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 54dd1faac0c7..96718c22946c 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -110,12 +110,6 @@ ownCloudGui::ownCloudGui(Application *parent) this, &ownCloudGui::slotShowOptionalTrayMessage); connect(Logger::instance(), &Logger::guiMessage, this, &ownCloudGui::slotShowGuiMessage); - - - connect(UserModel::instance(), &UserModel::login, - this, &ownCloudGui::slotLogin); - connect(UserModel::instance(), &UserModel::logout, - this, &ownCloudGui::slotLogout); } #ifdef WITH_LIBCLOUDPROVIDERS diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index be8a63775f4e..aeabdfea5f93 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -4,33 +4,19 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 MenuItem { - - Connections { - target: userModelBackend - onRefreshUserMenu: { - } - } - id: userLine - width: 220 height: 60 - Rectangle { - id: userLineBackground - height: userLine.height - anchors.fill: parent - color: "transparent" - RowLayout { id: userLineLayout spacing: 0 - anchors.fill: parent + width: 220 + height: 60 Button { id: accountButton - anchors.centerIn: parent - Layout.preferredWidth: (userLine.width - 4) - Layout.preferredHeight: (userLineBackground.height - 2) + Layout.preferredWidth: (userLineLayout.width * (5/6)) + Layout.preferredHeight: (userLineLayout.height) display: AbstractButton.IconOnly flat: true @@ -49,21 +35,22 @@ MenuItem { spacing: 0 Image { id: accountAvatar - Layout.leftMargin: 2 + Layout.leftMargin: 4 verticalAlignment: Qt.AlignCenter + cache: false source: ("image://avatars/" + index) - Layout.preferredHeight: (userLineBackground.height -16) - Layout.preferredWidth: (userLineBackground.height -16) + Layout.preferredHeight: (userLineLayout.height -16) + Layout.preferredWidth: (userLineLayout.height -16) } Column { id: accountLabels spacing: 4 Layout.alignment: Qt.AlignLeft - Layout.leftMargin: 12 + Layout.leftMargin: 6 Label { id: accountUser - width: 120 + width: 128 text: name elide: Text.ElideRight color: "black" @@ -72,7 +59,7 @@ MenuItem { } Label { id: accountServer - width: 120 + width: 128 text: server elide: Text.ElideRight color: "black" @@ -81,6 +68,51 @@ MenuItem { } } } // accountButton + + Button { + id: userMoreButton + Layout.preferredWidth: (userLineLayout.width * (1/6)) + Layout.preferredHeight: userLineLayout.height + flat: true + + icon.source: "qrc:///client/resources/more.svg" + icon.color: "transparent" + + MouseArea { + id: userMoreButtonMouseArea + anchors.fill: parent + hoverEnabled: true + onClicked: + { + userMoreButtonMenu.popup() + } + } + background: + Rectangle { + color: userMoreButtonMouseArea.containsMouse ? "grey" : "transparent" + opacity: 0.2 + } + + Menu { + id: userMoreButtonMenu + width: 100 + + background: Rectangle { + border.color: "#0082c9" + radius: 2 + } + + MenuItem { + text: userModelBackend.isCurrentUserConnected() ? "Log out" : "Log in" + onClicked: { + userModelBackend.isCurrentUserConnected() ? userModelBackend.logout(index) : userModelBackend.logout(index) + } + } + + MenuItem { + text: "Remove Account" + } + } + } } - } // Rectangle userLineBackground } // MenuItem userLine diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 22b49396bcfb..1c285d0e10e6 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -43,6 +43,16 @@ void User::openLocalFolder() QDesktopServices::openUrl(this->getFolder()->path()); } +void User::login() const +{ + _account->signIn(); +} + +void User::logout() const +{ + _account->signOutByUi(); +} + QString User::name() const { // If davDisplayName is empty (can be several reasons, simplest is missing login at startup), fall back to username @@ -208,6 +218,20 @@ Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) emit refreshCurrentUserGui(); } +Q_INVOKABLE void UserModel::login(const int &id) { + _users[id].login(); +} + +Q_INVOKABLE void UserModel::logout(const int &id) +{ + _users[id].logout(); +} + +Q_INVOKABLE void UserModel::removeAccount(const int &id) +{ + _users[id].logout(); +} + int UserModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index d68c2bc9bbc3..fc6f63989498 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -31,6 +31,8 @@ class User bool hasActivities() const; QImage avatar() const; QString id() const; + void login() const; + void logout() const; private: AccountStatePtr _account; @@ -65,6 +67,9 @@ class UserModel : public QAbstractListModel Q_INVOKABLE bool currentUserHasActivities(); Q_INVOKABLE bool currentServerHasTalk(); Q_INVOKABLE void switchCurrentUser(const int &id); + Q_INVOKABLE void login(const int &id); + Q_INVOKABLE void logout(const int &id); + Q_INVOKABLE void removeAccount(const int &id); ActivityListModel *currentActivityModel(); @@ -76,11 +81,7 @@ class UserModel : public QAbstractListModel }; signals: - Q_INVOKABLE void login(); - Q_INVOKABLE void logout(); Q_INVOKABLE void addAccount(); - Q_INVOKABLE void removeAccount(); - Q_INVOKABLE void refreshCurrentUserGui(); Q_INVOKABLE void newUserSelected(); Q_INVOKABLE void refreshUserMenu(); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 2e918319dfc7..9c54a46511b7 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -108,7 +108,7 @@ Window { id: accountMenu x: (currentAccountButton.x + 2) y: (currentAccountButton.y + currentAccountButton.height + 2) - width: (currentAccountButton.width) + width: (currentAccountButton.width - 2) closePolicy: "CloseOnPressOutside" background: Rectangle { @@ -116,7 +116,13 @@ Window { radius: 2 } + onClosed: { + userLineInstantiator.active = false; + userLineInstantiator.active = true; + } + Instantiator { + id: userLineInstantiator model: userModelBackend delegate: UserLine {} onObjectAdded: accountMenu.insertItem(index, object) From 80e21560eb902c4cf7fe845265428ec957ec9d84 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 14:57:22 +0100 Subject: [PATCH 050/120] More UserLine, avatar fixes Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/CMakeLists.txt | 6 +++--- src/gui/tray/UserLine.qml | 4 +++- src/gui/tray/UserModel.cpp | 19 ++++++++++++++----- src/gui/tray/UserModel.h | 4 ++-- theme.qrc | 2 ++ theme/black/user.svg | 1 + theme/white/user.svg | 1 + 7 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 theme/black/user.svg create mode 100644 theme/white/user.svg diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index af364ca4ed3d..b919758deb4d 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -1,5 +1,5 @@ project(gui) -find_package(Qt5 REQUIRED COMPONENTS Widgets) +find_package(Qt5 REQUIRED COMPONENTS Widgets Svg) set(CMAKE_AUTOMOC TRUE) set(CMAKE_AUTOUIC TRUE) set(CMAKE_AUTORCC TRUE) @@ -297,7 +297,7 @@ else() endif() add_library(updater STATIC ${updater_SRCS}) -target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Network Qt5::Xml Qt5::WebEngineWidgets) +target_link_libraries(updater ${synclib_NAME} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml Qt5::WebEngineWidgets) target_include_directories(updater PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES @@ -307,7 +307,7 @@ set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES set_target_properties( ${APPLICATION_EXECUTABLE} PROPERTIES INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}/${APPLICATION_EXECUTABLE};${CMAKE_INSTALL_RPATH}" ) -target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Network Qt5::Xml) +target_link_libraries( ${APPLICATION_EXECUTABLE} Qt5::Widgets Qt5::Svg Qt5::Network Qt5::Xml) target_link_libraries( ${APPLICATION_EXECUTABLE} ${synclib_NAME} ) target_link_libraries( ${APPLICATION_EXECUTABLE} updater ) target_link_libraries( ${APPLICATION_EXECUTABLE} ${OS_SPECIFIC_LINK_LIBRARIES} ) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index aeabdfea5f93..afccfeb40268 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -91,11 +91,13 @@ MenuItem { Rectangle { color: userMoreButtonMouseArea.containsMouse ? "grey" : "transparent" opacity: 0.2 + height: userMoreButton.height - 2 + y: userMoreButton.y + 1 } Menu { id: userMoreButtonMenu - width: 100 + width: 120 background: Rectangle { border.color: "#0082c9" diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 1c285d0e10e6..9c4f4a58515c 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -4,6 +4,8 @@ #include #include +#include +#include namespace OCC { @@ -73,18 +75,25 @@ QString User::server(bool shortened) const return serverUrl; } -QImage User::avatar() const +QImage User::avatar(bool whiteBg) const { QImage img = AvatarJob::makeCircularAvatar(_account->account()->avatar()); if (img.isNull()) { - img = AvatarJob::makeCircularAvatar(QImage(":/client/resources/account.png")); + QImage image(128, 128, QImage::Format_ARGB32); + image.fill(Qt::GlobalColor::transparent); + QPainter painter(&image); + + QSvgRenderer renderer(QString(whiteBg ? ":/client/theme/black/user.svg" : ":/client/theme/white/user.svg")); + renderer.render(&painter); + + return image; + } else { + return img; } - return img; } bool User::serverHasTalk() const { - auto test = _account->hasTalk(); return _account->hasTalk(); } @@ -152,7 +161,7 @@ Q_INVOKABLE QImage UserModel::currentUserAvatar() QImage UserModel::avatarById(const int &id) { - return _users[id].avatar(); + return _users[id].avatar(true); } Q_INVOKABLE QString UserModel::currentUserName() diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index fc6f63989498..49d9a3abb754 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -29,7 +29,7 @@ class User QString server(bool shortened = true) const; bool serverHasTalk() const; bool hasActivities() const; - QImage avatar() const; + QImage avatar(bool whiteBg = false) const; QString id() const; void login() const; void logout() const; @@ -106,4 +106,4 @@ class ImageProvider : public QQuickImageProvider }; } -#endif // USERMODEL_H \ No newline at end of file +#endif // USERMODEL_H diff --git a/theme.qrc b/theme.qrc index e24188f3090e..710ef0ff2a15 100644 --- a/theme.qrc +++ b/theme.qrc @@ -136,5 +136,7 @@ theme/white/talk-app.svg theme/white/caret-down.svg theme/black/caret-down.svg + theme/white/user.svg + theme/black/user.svg diff --git a/theme/black/user.svg b/theme/black/user.svg new file mode 100644 index 000000000000..14aef2cd9803 --- /dev/null +++ b/theme/black/user.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/theme/white/user.svg b/theme/white/user.svg new file mode 100644 index 000000000000..991242ce44ba --- /dev/null +++ b/theme/white/user.svg @@ -0,0 +1 @@ + \ No newline at end of file From a021f62e96e93f447d3a0b601c8a1b4c23ad714a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 15:05:37 +0100 Subject: [PATCH 051/120] Tray creation timing, account menu reinstantiating on visible change Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/owncloudgui.cpp | 2 ++ src/gui/systray.cpp | 11 ++++++++--- src/gui/systray.h | 1 + src/gui/tray/Window.qml | 8 +++----- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 96718c22946c..b071e2d3e0b8 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -110,6 +110,8 @@ ownCloudGui::ownCloudGui(Application *parent) this, &ownCloudGui::slotShowOptionalTrayMessage); connect(Logger::instance(), &Logger::guiMessage, this, &ownCloudGui::slotShowGuiMessage); + + _tray->create(); } #ifdef WITH_LIBCLOUDPROVIDERS diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 93240c133d9f..c7bbe2fe3045 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -50,7 +50,6 @@ Systray::Systray() // TODO: make singleton, provide ::instance() _trayEngine->rootContext()->setContextProperty("systrayBackend", this); _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"))); - _trayContext = _trayEngine->contextForObject(_trayComponent->create()); if (!AccountManager::instance()->accounts().isEmpty()) { slotChangeActivityModel(); @@ -58,14 +57,20 @@ Systray::Systray() // TODO: make singleton, provide ::instance() connect(UserModel::instance(), &UserModel::newUserSelected, this, &Systray::slotChangeActivityModel); - - hideWindow(); } Systray::~Systray() { } +void Systray::create() +{ + if (_trayContext == nullptr) { + _trayContext = _trayEngine->contextForObject(_trayComponent->create()); + hideWindow(); + } +} + void Systray::slotChangeActivityModel() { _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); diff --git a/src/gui/systray.h b/src/gui/systray.h index 3460940a3862..a09fc1c6cfed 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -45,6 +45,7 @@ class Systray public: explicit Systray(); ~Systray(); + void create(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); bool isOpen(); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 9c54a46511b7..4cea7dd6dd17 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -27,6 +27,9 @@ Window { currentAccountUser.text = userModelBackend.currentUserName(); currentAccountServer.text = userModelBackend.currentUserServer(); trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false; + + userLineInstantiator.active = false; + userLineInstantiator.active = true; } Connections { @@ -116,11 +119,6 @@ Window { radius: 2 } - onClosed: { - userLineInstantiator.active = false; - userLineInstantiator.active = true; - } - Instantiator { id: userLineInstantiator model: userModelBackend From 3da982f0739175b1bc022c89231eab24db1f96f1 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 15:32:59 +0100 Subject: [PATCH 052/120] Implemented account removal Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 3 +++ src/gui/tray/UserModel.cpp | 12 +++++++++++- src/gui/tray/UserModel.h | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index afccfeb40268..1bfd65c5598a 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -113,6 +113,9 @@ MenuItem { MenuItem { text: "Remove Account" + onClicked: { + userModelBackend.removeAccount(index) + } } } } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 9c4f4a58515c..86ff62f9927d 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -109,10 +109,15 @@ bool User::isCurrentUser() const bool User::isConnected() const { - bool test = (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); return (_account->connectionStatus() == AccountState::ConnectionStatus::Connected); } +void User::removeAccount() const +{ + AccountManager::instance()->deleteAccount(_account.data()); + AccountManager::instance()->save(); +} + /*-------------------------------------------------------------------------------------*/ UserModel *UserModel::_instance = nullptr; @@ -239,6 +244,11 @@ Q_INVOKABLE void UserModel::logout(const int &id) Q_INVOKABLE void UserModel::removeAccount(const int &id) { _users[id].logout(); + _users[id].removeAccount(); + if (_users.count() > 1) { + id == 0 ? switchCurrentUser(1) : switchCurrentUser(0); + } + _users.removeAt(id); } int UserModel::rowCount(const QModelIndex &parent) const diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 49d9a3abb754..5a2e9cadf163 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -33,6 +33,7 @@ class User QString id() const; void login() const; void logout() const; + void removeAccount() const; private: AccountStatePtr _account; From 32741162d93ee1c4c3fec6d7b84884d97726916a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 15:47:36 +0100 Subject: [PATCH 053/120] Detect newly added user and rebuild account menu automatically Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 32 ++++++++++++++++++++++++-------- src/gui/tray/UserModel.h | 3 ++- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 86ff62f9927d..1a72a3eab342 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -136,17 +136,23 @@ UserModel::UserModel(QObject *parent) { // TODO: Remember selected user from last quit via settings file if (AccountManager::instance()->accounts().size() > 0) { - initUserList(); + buildUserList(); } + + connect(AccountManager::instance(), &AccountManager::accountAdded, + this, &UserModel::buildUserList); } -void UserModel::initUserList() +void UserModel::buildUserList() { for (int i = 0; i < AccountManager::instance()->accounts().size(); i++) { auto user = AccountManager::instance()->accounts().at(i); addUser(user); } - _users.first().setCurrentUser(true); + if (_init) { + _users.first().setCurrentUser(true); + _init = false; + } } Q_INVOKABLE int UserModel::numUsers() @@ -186,12 +192,22 @@ Q_INVOKABLE bool UserModel::currentServerHasTalk() void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) { - beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _users << User(user, isCurrent); - if (isCurrent) { - _currentUserId = _users.indexOf(_users.last()); + bool containsUser = false; + for (int i = 0; i < _users.size(); i++) { + if (_users[i] == user) { + containsUser = true; + continue; + } + } + + if (!containsUser) { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _users << User(user, isCurrent); + if (isCurrent) { + _currentUserId = _users.indexOf(_users.last()); + } + endInsertRows(); } - endInsertRows(); } int UserModel::currentUserIndex() diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 5a2e9cadf163..ee7198641394 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -95,8 +95,9 @@ class UserModel : public QAbstractListModel UserModel(QObject *parent = 0); QList _users; int _currentUserId; + bool _init = true; - void initUserList(); + void buildUserList(); }; class ImageProvider : public QQuickImageProvider From 9af7d4ad06f44d17cee72559b973ee9336e98487 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 16:41:20 +0100 Subject: [PATCH 054/120] Fix crash on startup when no users configured Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 1a72a3eab342..8a14fc1b2da2 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -167,7 +167,17 @@ Q_INVOKABLE bool UserModel::isCurrentUserConnected() Q_INVOKABLE QImage UserModel::currentUserAvatar() { - return _users[_currentUserId].avatar(); + if (_users.count() >= 1) { + return _users[_currentUserId].avatar(); + } else { + QImage image(128, 128, QImage::Format_ARGB32); + image.fill(Qt::GlobalColor::transparent); + QPainter painter(&image); + QSvgRenderer renderer(QString(":/client/theme/white/user.svg")); + renderer.render(&painter); + + return image; + } } QImage UserModel::avatarById(const int &id) @@ -177,17 +187,29 @@ QImage UserModel::avatarById(const int &id) Q_INVOKABLE QString UserModel::currentUserName() { - return _users[_currentUserId].name(); + if (_users.count() >= 1) { + return _users[_currentUserId].name(); + } else { + return QString("No users"); + } } Q_INVOKABLE QString UserModel::currentUserServer() { - return _users[_currentUserId].server(); + if (_users.count() >= 1) { + return _users[_currentUserId].server(); + } else { + return QString(""); + } } Q_INVOKABLE bool UserModel::currentServerHasTalk() { - return _users[_currentUserId].serverHasTalk(); + if (_users.count() >= 1) { + return _users[_currentUserId].serverHasTalk(); + } else { + return false; + } } void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) From 6485ef480b7ab2080790ac9650f20f6880fdbc3c Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 17:06:42 +0100 Subject: [PATCH 055/120] Added confirmation dialog for account removal Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/generalsettings.cpp | 3 ++- src/gui/systray.cpp | 3 +++ src/gui/tray/UserModel.cpp | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp index 632ae43f838a..d803299fa47d 100644 --- a/src/gui/generalsettings.cpp +++ b/src/gui/generalsettings.cpp @@ -116,7 +116,8 @@ GeneralSettings::GeneralSettings(QWidget *parent) connect(_ui->ignoredFilesButton, &QAbstractButton::clicked, this, &GeneralSettings::slotIgnoreFilesEditor); // accountAdded means the wizard was finished and the wizard might change some options. - connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings); + // Update 01/2020: Show new tray window instead + // connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings); customizeStyle(); } diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index c7bbe2fe3045..2157c75795fd 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -57,6 +57,9 @@ Systray::Systray() // TODO: make singleton, provide ::instance() connect(UserModel::instance(), &UserModel::newUserSelected, this, &Systray::slotChangeActivityModel); + + connect(AccountManager::instance(), &AccountManager::accountAdded, + this, &Systray::showWindow); } Systray::~Systray() diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 8a14fc1b2da2..33506f62d92d 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -4,8 +4,10 @@ #include #include +#include #include #include +#include namespace OCC { @@ -281,6 +283,21 @@ Q_INVOKABLE void UserModel::logout(const int &id) Q_INVOKABLE void UserModel::removeAccount(const int &id) { + QMessageBox messageBox(QMessageBox::Question, + tr("Confirm Account Removal"), + tr("

Do you really want to remove the connection to the account %1?

" + "

Note: This will not delete any files.

") + .arg(_users[id].name()), + QMessageBox::NoButton); + QPushButton *yesButton = + messageBox.addButton(tr("Remove connection"), QMessageBox::YesRole); + messageBox.addButton(tr("Cancel"), QMessageBox::NoRole); + + messageBox.exec(); + if (messageBox.clickedButton() != yesButton) { + return; + } + _users[id].logout(); _users[id].removeAccount(); if (_users.count() > 1) { From 738127525f0a8621500c30d40f02dd0918022ad0 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 17:14:44 +0100 Subject: [PATCH 056/120] Fix comment Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/generalsettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp index d803299fa47d..561ae25d108c 100644 --- a/src/gui/generalsettings.cpp +++ b/src/gui/generalsettings.cpp @@ -117,7 +117,7 @@ GeneralSettings::GeneralSettings(QWidget *parent) // accountAdded means the wizard was finished and the wizard might change some options. // Update 01/2020: Show new tray window instead - // connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings); + connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings); customizeStyle(); } From 86b915d42ebff3c2c36a66fc15ba2085ae12c895 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 17:34:56 +0100 Subject: [PATCH 057/120] Systray now a singleton with instance(), show tray on finished wizard Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/application.cpp | 2 +- src/gui/generalsettings.cpp | 1 - src/gui/owncloudgui.cpp | 2 +- src/gui/settingsdialog.cpp | 1 - src/gui/systray.cpp | 16 +++++++++++----- src/gui/systray.h | 7 +++++-- 6 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 51af86f92354..45b06ae403f5 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -390,7 +390,7 @@ void Application::slotownCloudWizardDone(int res) Utility::setLaunchOnStartup(_theme->appName(), _theme->appNameGUI(), true); } - _gui->slotShowSettings(); + Systray::instance()->showWindow(); } } diff --git a/src/gui/generalsettings.cpp b/src/gui/generalsettings.cpp index 561ae25d108c..632ae43f838a 100644 --- a/src/gui/generalsettings.cpp +++ b/src/gui/generalsettings.cpp @@ -116,7 +116,6 @@ GeneralSettings::GeneralSettings(QWidget *parent) connect(_ui->ignoredFilesButton, &QAbstractButton::clicked, this, &GeneralSettings::slotIgnoreFilesEditor); // accountAdded means the wizard was finished and the wizard might change some options. - // Update 01/2020: Show new tray window instead connect(AccountManager::instance(), &AccountManager::accountAdded, this, &GeneralSettings::loadMiscSettings); customizeStyle(); diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index b071e2d3e0b8..5375786fd051 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -71,7 +71,7 @@ ownCloudGui::ownCloudGui(Application *parent) , _recentActionsMenu(nullptr) , _app(parent) { - _tray = new Systray(); + _tray = Systray::instance(); _tray->setParent(this); // for the beginning, set the offline icon until the account was verified _tray->setIcon(Theme::instance()->folderOfflineIcon(/*systray?*/ true)); diff --git a/src/gui/settingsdialog.cpp b/src/gui/settingsdialog.cpp index 6fa7bfa47059..53ff923b8768 100644 --- a/src/gui/settingsdialog.cpp +++ b/src/gui/settingsdialog.cpp @@ -229,7 +229,6 @@ void SettingsDialog::accountAdded(AccountState *s) connect(accountSettings, &AccountSettings::folderChanged, _gui, &ownCloudGui::slotFoldersChanged); connect(accountSettings, &AccountSettings::openFolderAlias, _gui, &ownCloudGui::slotFolderOpenAction); - connect(accountSettings, &AccountSettings::showIssuesList, this, &SettingsDialog::showIssuesList); connect(s->account().data(), &Account::accountChangedAvatar, this, &SettingsDialog::slotAccountAvatarChanged); connect(s->account().data(), &Account::accountChangedDisplayName, this, &SettingsDialog::slotAccountDisplayNameChanged); diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 2157c75795fd..a241e5fc2855 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -36,7 +36,17 @@ namespace OCC { -Systray::Systray() // TODO: make singleton, provide ::instance() +Systray *Systray::_instance = nullptr; + +Systray *Systray::instance() +{ + if (_instance == nullptr) { + _instance = new Systray(); + } + return _instance; +} + +Systray::Systray() : _isOpen(false) , _syncIsPaused(false) , _trayComponent(nullptr) @@ -62,10 +72,6 @@ Systray::Systray() // TODO: make singleton, provide ::instance() this, &Systray::showWindow); } -Systray::~Systray() -{ -} - void Systray::create() { if (_trayContext == nullptr) { diff --git a/src/gui/systray.h b/src/gui/systray.h index a09fc1c6cfed..60a44a3f9107 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -43,8 +43,9 @@ class Systray { Q_OBJECT public: - explicit Systray(); - ~Systray(); + static Systray *instance(); + virtual ~Systray() {}; + void create(); void showMessage(const QString &title, const QString &message, MessageIcon icon = Information, int millisecondsTimeoutHint = 10000); void setToolTip(const QString &tip); @@ -72,6 +73,8 @@ private slots: void slotChangeActivityModel(); private: + static Systray *_instance; + Systray(); bool _isOpen; bool _syncIsPaused; QQmlEngine *_trayEngine; From cc671dfad305bc03cc208206351a4785444cea35 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 19:56:56 +0100 Subject: [PATCH 058/120] Fix local folder URL Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 33506f62d92d..2018daf0c3ae 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -44,7 +44,8 @@ ActivityListModel *User::getActivityModel() void User::openLocalFolder() { - QDesktopServices::openUrl(this->getFolder()->path()); + QString path = "file://" + this->getFolder()->path(); + QDesktopServices::openUrl(path); } void User::login() const From 578a3b4ef433f49ec303291879da825c67213eeb Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 11 Jan 2020 20:28:00 +0100 Subject: [PATCH 059/120] Streamlined 'Add account' button with account entries Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 27 ++++++++++++++++++++++++++- theme.qrc | 2 ++ theme/black/add.svg | 1 + theme/white/add.svg | 1 + 4 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 theme/black/add.svg create mode 100644 theme/white/add.svg diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 4cea7dd6dd17..4c8072e84a68 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -128,7 +128,32 @@ Window { } MenuItem { - text: "Add account" + id: addAccountButton + height: 60 + + RowLayout { + width: addAccountButton.width + height: addAccountButton.height + spacing: 0 + + Image { + Layout.leftMargin: 8 + verticalAlignment: Qt.AlignCenter + source: "qrc:///client/theme/black/add.svg" + sourceSize.width: addAccountButton.height - 24 + sourceSize.height: addAccountButton.height - 24 + } + Label { + Layout.leftMargin: 10 + text: "Add account" + color: "black" + font.pixelSize: 12 + } + Item { + Layout.fillWidth: true + Layout.fillHeight: true + } + } onClicked: userModelBackend.addAccount() } diff --git a/theme.qrc b/theme.qrc index 710ef0ff2a15..42bda010fd40 100644 --- a/theme.qrc +++ b/theme.qrc @@ -138,5 +138,7 @@ theme/black/caret-down.svg theme/white/user.svg theme/black/user.svg + theme/white/add.svg + theme/black/add.svg diff --git a/theme/black/add.svg b/theme/black/add.svg new file mode 100644 index 000000000000..4b2d2133078e --- /dev/null +++ b/theme/black/add.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/theme/white/add.svg b/theme/white/add.svg new file mode 100644 index 000000000000..0604ed0994b2 --- /dev/null +++ b/theme/white/add.svg @@ -0,0 +1 @@ + \ No newline at end of file From bc8f64bb983fa57ac6ed4d516210483f7ec79d7a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 08:50:13 +0100 Subject: [PATCH 060/120] Generalized connection check, now working for every user in account menu Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 4 ++-- src/gui/tray/UserModel.cpp | 4 ++-- src/gui/tray/UserModel.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 1bfd65c5598a..82188aad818c 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -105,9 +105,9 @@ MenuItem { } MenuItem { - text: userModelBackend.isCurrentUserConnected() ? "Log out" : "Log in" + text: userModelBackend.isUserConnected(index) ? "Log out" : "Log in" onClicked: { - userModelBackend.isCurrentUserConnected() ? userModelBackend.logout(index) : userModelBackend.logout(index) + userModelBackend.isUserConnected(index) ? userModelBackend.logout(index) : userModelBackend.logout(index) } } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 2018daf0c3ae..f8223d424e90 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -163,9 +163,9 @@ Q_INVOKABLE int UserModel::numUsers() return _users.size(); } -Q_INVOKABLE bool UserModel::isCurrentUserConnected() +Q_INVOKABLE bool UserModel::isUserConnected(const int &id) { - return _users[_currentUserId].isConnected(); + return _users[id].isConnected(); } Q_INVOKABLE QImage UserModel::currentUserAvatar() diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index ee7198641394..c1a390daff58 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -62,11 +62,11 @@ class UserModel : public QAbstractListModel Q_INVOKABLE void openCurrentAccountServer(); Q_INVOKABLE QImage currentUserAvatar(); Q_INVOKABLE int numUsers(); - Q_INVOKABLE bool isCurrentUserConnected(); Q_INVOKABLE QString currentUserName(); Q_INVOKABLE QString currentUserServer(); Q_INVOKABLE bool currentUserHasActivities(); Q_INVOKABLE bool currentServerHasTalk(); + Q_INVOKABLE bool isUserConnected(const int &id); Q_INVOKABLE void switchCurrentUser(const int &id); Q_INVOKABLE void login(const int &id); Q_INVOKABLE void logout(const int &id); From 8ec7cfa7008d1bba63f5f01338594cc665b937d3 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 09:10:06 +0100 Subject: [PATCH 061/120] Connection status indicators as avatar overlay Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 9 +++++++++ src/gui/tray/UserModel.cpp | 5 +++++ src/gui/tray/UserModel.h | 1 + src/gui/tray/Window.qml | 11 +++++++++++ 4 files changed, 26 insertions(+) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 82188aad818c..d2db68ff345a 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -41,6 +41,15 @@ MenuItem { source: ("image://avatars/" + index) Layout.preferredHeight: (userLineLayout.height -16) Layout.preferredWidth: (userLineLayout.height -16) + Image { + id: accountStateIndicator + source: userModelBackend.isUserConnected(index) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" + cache: false + anchors.bottom: accountAvatar.bottom + anchors.right: accountAvatar.right + sourceSize.width: 16 + sourceSize.height: 16 + } } Column { diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index f8223d424e90..e4d8b5ecc046 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -163,6 +163,11 @@ Q_INVOKABLE int UserModel::numUsers() return _users.size(); } +Q_INVOKABLE int UserModel::currentUserId() +{ + return _currentUserId; +} + Q_INVOKABLE bool UserModel::isUserConnected(const int &id) { return _users[id].isConnected(); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index c1a390daff58..18d0e334b83f 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -66,6 +66,7 @@ class UserModel : public QAbstractListModel Q_INVOKABLE QString currentUserServer(); Q_INVOKABLE bool currentUserHasActivities(); Q_INVOKABLE bool currentServerHasTalk(); + Q_INVOKABLE int currentUserId(); Q_INVOKABLE bool isUserConnected(const int &id); Q_INVOKABLE void switchCurrentUser(const int &id); Q_INVOKABLE void login(const int &id); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 4c8072e84a68..c311b05039e4 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -39,6 +39,8 @@ Window { currentAccountAvatar.source = "image://avatars/currentUser" currentAccountUser.text = userModelBackend.currentUserName(); currentAccountServer.text = userModelBackend.currentUserServer(); + currentAccountStateIndicator.source = "" + currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" } onNewUserSelected: { accountMenu.close(); @@ -248,6 +250,15 @@ Window { source: "image://avatars/currentUser" Layout.preferredHeight: (trayWindowHeaderBackground.height -16) Layout.preferredWidth: (trayWindowHeaderBackground.height -16) + Image { + id: currentAccountStateIndicator + source: userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" + cache: false + anchors.bottom: currentAccountAvatar.bottom + anchors.right: currentAccountAvatar.right + sourceSize.width: 16 + sourceSize.height: 16 + } } Column { From a2c9e369c3906acd60b09917aad610a0114a6678 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 10:42:40 +0100 Subject: [PATCH 062/120] call resetRejectedCertificates on fresh login Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index e4d8b5ecc046..8b76c76feb8e 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -50,6 +50,7 @@ void User::openLocalFolder() void User::login() const { + _account->account()->resetRejectedCertificates(); _account->signIn(); } From 212e1f80f5e44d5f5ff55c503d6e918049b3cd3a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 10:46:14 +0100 Subject: [PATCH 063/120] emit signal for current user GUI refresh on login/logout Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 8b76c76feb8e..67e49c48d1f8 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -281,11 +281,13 @@ Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) Q_INVOKABLE void UserModel::login(const int &id) { _users[id].login(); + emit refreshCurrentUserGui(); } Q_INVOKABLE void UserModel::logout(const int &id) { _users[id].logout(); + emit refreshCurrentUserGui(); } Q_INVOKABLE void UserModel::removeAccount(const int &id) From 7f35178c70b18b79b355999d664206567abef87d Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 11:06:48 +0100 Subject: [PATCH 064/120] More fixes to login/logout logic Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 3 ++- src/gui/tray/Window.qml | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index d2db68ff345a..834c7001b6d2 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -116,7 +116,8 @@ MenuItem { MenuItem { text: userModelBackend.isUserConnected(index) ? "Log out" : "Log in" onClicked: { - userModelBackend.isUserConnected(index) ? userModelBackend.logout(index) : userModelBackend.logout(index) + userModelBackend.isUserConnected(index) ? userModelBackend.logout(index) : userModelBackend.login(index) + accountMenu.close() } } diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index c311b05039e4..cda5374b51d6 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -27,6 +27,8 @@ Window { currentAccountUser.text = userModelBackend.currentUserName(); currentAccountServer.text = userModelBackend.currentUserServer(); trayWindowTalkButton.visible = userModelBackend.currentServerHasTalk() ? true : false; + currentAccountStateIndicator.source = "" + currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" userLineInstantiator.active = false; userLineInstantiator.active = true; @@ -121,6 +123,11 @@ Window { radius: 2 } + onClosed: { + userLineInstantiator.active = false; + userLineInstantiator.active = true; + } + Instantiator { id: userLineInstantiator model: userModelBackend From b76da25ef119e21d1b8eeb683924a31f0cf28469 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 16:16:24 +0100 Subject: [PATCH 065/120] Model/View improvements, also fixes crash on account removal Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 9 +++-- src/gui/tray/UserModel.cpp | 81 ++++++++++++++++++++++---------------- src/gui/tray/UserModel.h | 14 ++++--- src/gui/tray/Window.qml | 3 +- 4 files changed, 61 insertions(+), 46 deletions(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 834c7001b6d2..6d41fc7d944f 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -38,12 +38,12 @@ MenuItem { Layout.leftMargin: 4 verticalAlignment: Qt.AlignCenter cache: false - source: ("image://avatars/" + index) + source: ("image://avatars/" + id) Layout.preferredHeight: (userLineLayout.height -16) Layout.preferredWidth: (userLineLayout.height -16) Image { id: accountStateIndicator - source: userModelBackend.isUserConnected(index) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" + source: isConnected ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" cache: false anchors.bottom: accountAvatar.bottom anchors.right: accountAvatar.right @@ -114,9 +114,9 @@ MenuItem { } MenuItem { - text: userModelBackend.isUserConnected(index) ? "Log out" : "Log in" + text: isConnected ? "Log out" : "Log in" onClicked: { - userModelBackend.isUserConnected(index) ? userModelBackend.logout(index) : userModelBackend.login(index) + isConnected ? userModelBackend.logout(index) : userModelBackend.login(index) accountMenu.close() } } @@ -125,6 +125,7 @@ MenuItem { text: "Remove Account" onClicked: { userModelBackend.removeAccount(index) + accountMenu.close() } } } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 67e49c48d1f8..510bbcbbcfa8 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -11,16 +11,17 @@ namespace OCC { -User::User(AccountStatePtr &account, const bool &isCurrent) - : _account(account) +User::User(AccountStatePtr &account, const bool &isCurrent, QObject* parent) + : QObject(parent) + , _account(account) , _isCurrentUser(isCurrent) , _activityModel(new ActivityListModel(_account.data())) { } -bool User::operator==(const User &rhs) const +AccountPtr User::account() const { - return (this->_account->account() == rhs._account->account()); + return _account->account(); } void User::setCurrentUser(const bool &isCurrent) @@ -135,7 +136,7 @@ UserModel *UserModel::instance() } UserModel::UserModel(QObject *parent) - : QAbstractListModel() + : QAbstractListModel(parent) , _currentUserId() { // TODO: Remember selected user from last quit via settings file @@ -154,7 +155,7 @@ void UserModel::buildUserList() addUser(user); } if (_init) { - _users.first().setCurrentUser(true); + _users.first()->setCurrentUser(true); _init = false; } } @@ -171,13 +172,13 @@ Q_INVOKABLE int UserModel::currentUserId() Q_INVOKABLE bool UserModel::isUserConnected(const int &id) { - return _users[id].isConnected(); + return _users[id]->isConnected(); } Q_INVOKABLE QImage UserModel::currentUserAvatar() { if (_users.count() >= 1) { - return _users[_currentUserId].avatar(); + return _users[_currentUserId]->avatar(); } else { QImage image(128, 128, QImage::Format_ARGB32); image.fill(Qt::GlobalColor::transparent); @@ -191,13 +192,13 @@ Q_INVOKABLE QImage UserModel::currentUserAvatar() QImage UserModel::avatarById(const int &id) { - return _users[id].avatar(true); + return _users[id]->avatar(true); } Q_INVOKABLE QString UserModel::currentUserName() { if (_users.count() >= 1) { - return _users[_currentUserId].name(); + return _users[_currentUserId]->name(); } else { return QString("No users"); } @@ -206,7 +207,7 @@ Q_INVOKABLE QString UserModel::currentUserName() Q_INVOKABLE QString UserModel::currentUserServer() { if (_users.count() >= 1) { - return _users[_currentUserId].server(); + return _users[_currentUserId]->server(); } else { return QString(""); } @@ -215,7 +216,7 @@ Q_INVOKABLE QString UserModel::currentUserServer() Q_INVOKABLE bool UserModel::currentServerHasTalk() { if (_users.count() >= 1) { - return _users[_currentUserId].serverHasTalk(); + return _users[_currentUserId]->serverHasTalk(); } else { return false; } @@ -225,7 +226,7 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) { bool containsUser = false; for (int i = 0; i < _users.size(); i++) { - if (_users[i] == user) { + if (_users[i]->account() == user->account()) { containsUser = true; continue; } @@ -233,7 +234,7 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) if (!containsUser) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); - _users << User(user, isCurrent); + _users << new User(user, isCurrent); if (isCurrent) { _currentUserId = _users.indexOf(_users.last()); } @@ -248,45 +249,44 @@ int UserModel::currentUserIndex() Q_INVOKABLE void UserModel::openCurrentAccountLocalFolder() { - _users[_currentUserId].openLocalFolder(); + _users[_currentUserId]->openLocalFolder(); } Q_INVOKABLE void UserModel::openCurrentAccountTalk() { - QString url = _users[_currentUserId].server(false) + "/apps/spreed"; + QString url = _users[_currentUserId]->server(false) + "/apps/spreed"; if (!(url.contains("http://") || url.contains("https://"))) { - url = "https://" + _users[_currentUserId].server(false) + "/apps/spreed"; + url = "https://" + _users[_currentUserId]->server(false) + "/apps/spreed"; } QDesktopServices::openUrl(QUrl(url)); } Q_INVOKABLE void UserModel::openCurrentAccountServer() { - QString url = _users[_currentUserId].server(false); + QString url = _users[_currentUserId]->server(false); if (!(url.contains("http://") || url.contains("https://"))) { - url = "https://" + _users[_currentUserId].server(false); + url = "https://" + _users[_currentUserId]->server(false); } QDesktopServices::openUrl(QUrl(url)); } Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) { - _users[_currentUserId].setCurrentUser(false); - _users[id].setCurrentUser(true); + _users[_currentUserId]->setCurrentUser(false); + _users[id]->setCurrentUser(true); _currentUserId = id; emit newUserSelected(); - emit refreshUserMenu(); emit refreshCurrentUserGui(); } Q_INVOKABLE void UserModel::login(const int &id) { - _users[id].login(); + _users[id]->login(); emit refreshCurrentUserGui(); } Q_INVOKABLE void UserModel::logout(const int &id) { - _users[id].logout(); + _users[id]->logout(); emit refreshCurrentUserGui(); } @@ -296,7 +296,7 @@ Q_INVOKABLE void UserModel::removeAccount(const int &id) tr("Confirm Account Removal"), tr("

Do you really want to remove the connection to the account %1?

" "

Note: This will not delete any files.

") - .arg(_users[id].name()), + .arg(_users[id]->name()), QMessageBox::NoButton); QPushButton *yesButton = messageBox.addButton(tr("Remove connection"), QMessageBox::YesRole); @@ -307,12 +307,18 @@ Q_INVOKABLE void UserModel::removeAccount(const int &id) return; } - _users[id].logout(); - _users[id].removeAccount(); - if (_users.count() > 1) { + if (_users[id]->isCurrentUser() && _users.count() > 1) { id == 0 ? switchCurrentUser(1) : switchCurrentUser(0); } + + _users[id]->logout(); + _users[id]->removeAccount(); + + beginRemoveRows(QModelIndex(), id, id); _users.removeAt(id); + endRemoveRows(); + + emit refreshCurrentUserGui(); } int UserModel::rowCount(const QModelIndex &parent) const @@ -327,15 +333,18 @@ QVariant UserModel::data(const QModelIndex &index, int role) const return QVariant(); } - const User &user = _users[index.row()]; if (role == NameRole) { - return user.name(); + return _users[index.row()]->name(); } else if (role == ServerRole) { - return user.server(); + return _users[index.row()]->server(); } else if (role == AvatarRole) { - return user.avatar(); + return _users[index.row()]->avatar(); } else if (role == IsCurrentUserRole) { - return user.isCurrentUser(); + return _users[index.row()]->isCurrentUser(); + } else if (role == IsConnectedRole) { + return _users[index.row()]->isConnected(); + } else if (role == IdRole) { + return index.row(); } return QVariant(); } @@ -347,17 +356,19 @@ QHash UserModel::roleNames() const roles[ServerRole] = "server"; roles[AvatarRole] = "avatar"; roles[IsCurrentUserRole] = "isCurrentUser"; + roles[IsConnectedRole] = "isConnected"; + roles[IdRole] = "id"; return roles; } ActivityListModel *UserModel::currentActivityModel() { - return _users[currentUserIndex()].getActivityModel(); + return _users[currentUserIndex()]->getActivityModel(); } bool UserModel::currentUserHasActivities() { - return _users[currentUserIndex()].hasActivities(); + return _users[currentUserIndex()]->hasActivities(); } /*-------------------------------------------------------------------------------------*/ diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 18d0e334b83f..9f9ad55d7e30 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -12,12 +12,13 @@ namespace OCC { -class User +class User : public QObject { + Q_OBJECT public: - User(AccountStatePtr &account, const bool &isCurrent = false); + User(AccountStatePtr &account, const bool &isCurrent = false, QObject* parent = 0); - bool operator==(const User &) const; + AccountPtr account() const; bool isConnected() const; bool isCurrentUser() const; @@ -79,14 +80,15 @@ class UserModel : public QAbstractListModel NameRole = Qt::UserRole + 1, ServerRole, AvatarRole, - IsCurrentUserRole + IsCurrentUserRole, + IsConnectedRole, + IdRole }; signals: Q_INVOKABLE void addAccount(); Q_INVOKABLE void refreshCurrentUserGui(); Q_INVOKABLE void newUserSelected(); - Q_INVOKABLE void refreshUserMenu(); protected: QHash roleNames() const; @@ -94,7 +96,7 @@ class UserModel : public QAbstractListModel private: static UserModel *_instance; UserModel(QObject *parent = 0); - QList _users; + QList _users; int _currentUserId; bool _init = true; diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index cda5374b51d6..a9094761571f 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -1,4 +1,5 @@ -import QtQml 2.0 +import QtQml 2.1 +import QtQml.Models 2.1 import QtQuick 2.9 import QtQuick.Window 2.2 import QtQuick.Controls 2.2 From 1c2916052d4ab9e4645443fda70d7aa4c94c412f Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 17:52:51 +0100 Subject: [PATCH 066/120] Bunch of fixes and optimizations for activityList Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/accountsettings.cpp | 4 ++++ src/gui/systray.cpp | 6 +----- src/gui/systray.h | 2 +- src/gui/tray/UserLine.qml | 6 +++++- src/gui/tray/Window.qml | 8 ++++---- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 604abace1c51..c28096b968e8 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -192,6 +192,10 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged); slotAccountStateChanged(); + if (!AccountManager::instance()->accounts().isEmpty()) { + Systray::instance()->slotChangeActivityModel(); + } + connect(&_quotaInfo, &QuotaInfo::quotaUpdated, this, &AccountSettings::slotUpdateQuota); diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index a241e5fc2855..f26ad8717d9c 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -61,10 +61,6 @@ Systray::Systray() _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"))); - if (!AccountManager::instance()->accounts().isEmpty()) { - slotChangeActivityModel(); - } - connect(UserModel::instance(), &UserModel::newUserSelected, this, &Systray::slotChangeActivityModel); @@ -83,7 +79,7 @@ void Systray::create() void Systray::slotChangeActivityModel() { _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); - emit currentUserChanged(); + //UserModel::instance()->currentActivityModel()->slotRefreshActivity(); } bool Systray::isOpen() diff --git a/src/gui/systray.h b/src/gui/systray.h index 60a44a3f9107..7125c1090216 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -69,7 +69,7 @@ class Systray Q_INVOKABLE void hideWindow(); Q_INVOKABLE void showWindow(); -private slots: +public slots: void slotChangeActivityModel(); private: diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 6d41fc7d944f..700951fefd5d 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -25,7 +25,11 @@ MenuItem { } onClicked: { - userModelBackend.switchCurrentUser(index) + if (!isCurrentUser) { + userModelBackend.switchCurrentUser(id) + } else { + accountMenu.close() + } } RowLayout { diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index a9094761571f..1cc2dd542d8d 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -31,6 +31,10 @@ Window { currentAccountStateIndicator.source = "" currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" + if (userModelBackend.isUserConnected(userModelBackend.currentUserId())) { + systrayBackend.slotChangeActivityModel() + } + userLineInstantiator.active = false; userLineInstantiator.active = true; } @@ -535,10 +539,6 @@ Window { } focus: true - - // For interactive ListView/Animation testing only - //Keys.onSpacePressed: model.insert(0, { "name": "Item " + model.count }) - //Keys.onTabPressed: model.remove(3) } } // Rectangle trayWindowBackground From 163ca3b58ab6f6098393e440f54145f99fc374bf Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 19:53:02 +0100 Subject: [PATCH 067/120] Removed old account toolbox Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/accountsettings.cpp | 75 ++----------------------------------- src/gui/accountsettings.h | 3 -- src/gui/accountsettings.ui | 9 +---- src/gui/systray.cpp | 1 - 4 files changed, 4 insertions(+), 84 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index c28096b968e8..20822b0683a1 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -143,9 +143,6 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) _ui->_folderList->setAttribute(Qt::WA_Hover, true); _ui->_folderList->installEventFilter(mouseCursorChanger); - createAccountToolbox(); - connect(AccountManager::instance(), &AccountManager::accountAdded, - this, &AccountSettings::slotAccountAdded); connect(this, &AccountSettings::removeAccountFolders, AccountManager::instance(), &AccountManager::removeAccountFolders); connect(_ui->_folderList, &QWidget::customContextMenuRequested, @@ -211,37 +208,10 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) _ui->encryptionMessage->hide(); } - customizeStyle(); -} - - -void AccountSettings::createAccountToolbox() -{ - QMenu *menu = new QMenu(); - - connect(menu, &QMenu::aboutToShow, this, &AccountSettings::slotMenuBeforeShow); - - _addAccountAction = new QAction(tr("Add new"), this); - menu->addAction(_addAccountAction); - - connect(_addAccountAction, &QAction::triggered, this, &AccountSettings::slotOpenAccountWizard); connect(UserModel::instance(), &UserModel::addAccount, - this, &AccountSettings::slotOpenAccountWizard); - - _toggleSignInOutAction = new QAction(tr("Log out"), this); - connect(_toggleSignInOutAction, &QAction::triggered, this, &AccountSettings::slotToggleSignInState); - menu->addAction(_toggleSignInOutAction); + this, &AccountSettings::slotOpenAccountWizard); - QAction *action = new QAction(tr("Remove"), this); - menu->addAction(action); - - connect(action, &QAction::triggered, this, &AccountSettings::slotDeleteAccount); - - _ui->_accountToolbox->setText(tr("Account") + QLatin1Char(' ')); - _ui->_accountToolbox->setMenu(menu); - _ui->_accountToolbox->setPopupMode(QToolButton::InstantPopup); - - slotAccountAdded(_accountState); + customizeStyle(); } @@ -257,24 +227,6 @@ void AccountSettings::slotNewMnemonicGenerated() _ui->encryptionMessage->show(); } -void AccountSettings::slotMenuBeforeShow() { - if (_menuShown) { - return; - } - - auto menu = _ui->_accountToolbox->menu(); - - // We can't check this during the initial creation as there is no account yet then - if (_accountState->account()->capabilities().clientSideEncryptionAvaliable()) { - QAction *mnemonic = new QAction(tr("Show E2E mnemonic"), this); - connect(mnemonic, &QAction::triggered, this, &AccountSettings::requesetMnemonic); - menu->addAction(mnemonic); - } - - _menuShown = true; -} - - QString AccountSettings::selectedFolderAlias() const { QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex(); @@ -1068,21 +1020,12 @@ void AccountSettings::slotAccountStateChanged() // sync user interface buttons. refreshSelectiveSyncStatus(); - /* set the correct label for the Account toolbox button */ - if (_accountState) { - if (_accountState->isSignedOut()) { - _toggleSignInOutAction->setText(tr("Log in")); - } else { - _toggleSignInOutAction->setText(tr("Log out")); - } - } - if (state == AccountState::State::Connected) { /* TODO: We should probably do something better here. * Verify if the user has a private key already uploaded to the server, * if it has, do not offer to create one. */ - qCInfo(lcAccountSettings) << "Accout" << accountsState()->account()->displayName() + qCInfo(lcAccountSettings) << "Account" << accountsState()->account()->displayName() << "Client Side Encryption" << accountsState()->account()->capabilities().clientSideEncryptionAvaliable(); } } @@ -1198,18 +1141,6 @@ void AccountSettings::refreshSelectiveSyncStatus() } } -void AccountSettings::slotAccountAdded(AccountState *) -{ - // if the theme is limited to single account, the button must hide if - // there is already one account. - int s = AccountManager::instance()->accounts().size(); - if (s > 0 && !Theme::instance()->multiAccount()) { - _addAccountAction->setVisible(false); - } else { - _addAccountAction->setVisible(true); - } -} - void AccountSettings::slotDeleteAccount() { // Deleting the account potentially deletes 'this', so diff --git a/src/gui/accountsettings.h b/src/gui/accountsettings.h index 886ebed2361a..e620029a5c30 100644 --- a/src/gui/accountsettings.h +++ b/src/gui/accountsettings.h @@ -90,7 +90,6 @@ protected slots: void slotDeleteAccount(); void slotToggleSignInState(); void slotOpenAccountWizard(); - void slotAccountAdded(AccountState *); void refreshSelectiveSyncStatus(); void slotMarkSubfolderEncrypted(const FolderStatusModel::SubFolderInfo* folderInfo); void slotMarkSubfolderDecrypted(const FolderStatusModel::SubFolderInfo* folderInfo); @@ -100,8 +99,6 @@ protected slots: void doExpand(); void slotLinkActivated(const QString &link); - void slotMenuBeforeShow(); - // Encryption Related Stuff. void slotShowMnemonic(const QString &mnemonic); void slotNewMnemonicGenerated(); diff --git a/src/gui/accountsettings.ui b/src/gui/accountsettings.ui index 6249018bd19b..eafb4605d78e 100644 --- a/src/gui/accountsettings.ui +++ b/src/gui/accountsettings.ui @@ -6,7 +6,7 @@ 0 0 - 516 + 581 557 @@ -184,13 +184,6 @@ - - - - ... - - - diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index f26ad8717d9c..52222586dd83 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -79,7 +79,6 @@ void Systray::create() void Systray::slotChangeActivityModel() { _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); - //UserModel::instance()->currentActivityModel()->slotRefreshActivity(); } bool Systray::isOpen() From 715924cac68061c5e47281bea4a0a6b743805b3a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 21:19:18 +0100 Subject: [PATCH 068/120] Show activitylist on initial tray opening when logged in Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/accountsettings.cpp | 4 ---- src/gui/application.cpp | 2 ++ src/gui/owncloudgui.cpp | 4 ++++ src/gui/owncloudgui.h | 1 + src/gui/systray.cpp | 1 + src/gui/tray/UserModel.cpp | 9 ++++++++- src/gui/tray/UserModel.h | 1 + src/gui/tray/Window.qml | 5 +---- 8 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/gui/accountsettings.cpp b/src/gui/accountsettings.cpp index 20822b0683a1..1c9e3b47d6ca 100644 --- a/src/gui/accountsettings.cpp +++ b/src/gui/accountsettings.cpp @@ -189,10 +189,6 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent) connect(_accountState, &AccountState::stateChanged, this, &AccountSettings::slotAccountStateChanged); slotAccountStateChanged(); - if (!AccountManager::instance()->accounts().isEmpty()) { - Systray::instance()->slotChangeActivityModel(); - } - connect(&_quotaInfo, &QuotaInfo::quotaUpdated, this, &AccountSettings::slotUpdateQuota); diff --git a/src/gui/application.cpp b/src/gui/application.cpp index 45b06ae403f5..4cfcab31d265 100644 --- a/src/gui/application.cpp +++ b/src/gui/application.cpp @@ -266,6 +266,8 @@ Application::Application(int &argc, char **argv) // Allow other classes to hook into isShowingSettingsDialog() signals (re-auth widgets, for example) connect(_gui.data(), &ownCloudGui::isShowingSettingsDialog, this, &Application::slotGuiIsShowingSettings); + + _gui->createTray(); } Application::~Application() diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index 5375786fd051..b62122395b89 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -111,6 +111,10 @@ ownCloudGui::ownCloudGui(Application *parent) connect(Logger::instance(), &Logger::guiMessage, this, &ownCloudGui::slotShowGuiMessage); +} + +void ownCloudGui::createTray() +{ _tray->create(); } diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index 518be8b16597..9efd34e8b9be 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -63,6 +63,7 @@ class ownCloudGui : public QObject void setupCloudProviders(); bool cloudProviderApiAvailable(); #endif + void createTray(); signals: void setupProxy(); diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 52222586dd83..62efff23a953 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -71,6 +71,7 @@ Systray::Systray() void Systray::create() { if (_trayContext == nullptr) { + _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); _trayContext = _trayEngine->contextForObject(_trayComponent->create()); hideWindow(); } diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 510bbcbbcfa8..aea75264ac52 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -275,8 +275,8 @@ Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) _users[_currentUserId]->setCurrentUser(false); _users[id]->setCurrentUser(true); _currentUserId = id; - emit newUserSelected(); emit refreshCurrentUserGui(); + emit newUserSelected(); } Q_INVOKABLE void UserModel::login(const int &id) { @@ -371,6 +371,13 @@ bool UserModel::currentUserHasActivities() return _users[currentUserIndex()]->hasActivities(); } +void UserModel::fetchCurrentActivityModel() +{ + if (_users[currentUserId()]->isConnected()) { + _users[currentUserId()]->getActivityModel()->fetchMore(QModelIndex()); + } +} + /*-------------------------------------------------------------------------------------*/ ImageProvider::ImageProvider() diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 9f9ad55d7e30..3775e66b2cc3 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -58,6 +58,7 @@ class UserModel : public QAbstractListModel QImage avatarById(const int &id); + Q_INVOKABLE void fetchCurrentActivityModel(); Q_INVOKABLE void openCurrentAccountLocalFolder(); Q_INVOKABLE void openCurrentAccountTalk(); Q_INVOKABLE void openCurrentAccountServer(); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 1cc2dd542d8d..169c02ac8bf0 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -31,10 +31,6 @@ Window { currentAccountStateIndicator.source = "" currentAccountStateIndicator.source = userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" - if (userModelBackend.isUserConnected(userModelBackend.currentUserId())) { - systrayBackend.slotChangeActivityModel() - } - userLineInstantiator.active = false; userLineInstantiator.active = true; } @@ -65,6 +61,7 @@ Window { trayWindow.setX( systrayBackend.calcTrayWindowX()); trayWindow.setY( systrayBackend.calcTrayWindowY()); systrayBackend.setOpened(); + userModelBackend.fetchCurrentActivityModel(); } onHideWindow: { trayWindow.hide(); From 58f1c3234d775c2242c033e475acba38bd305972 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sun, 12 Jan 2020 21:37:08 +0100 Subject: [PATCH 069/120] Temperarily disabling animations due to lags - FIXME Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 169c02ac8bf0..41de1806cb62 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -517,14 +517,17 @@ Window { } add: Transition { + enabled: false NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } } remove: Transition { + enabled: false NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 } } removeDisplaced: Transition { + enabled: false SequentialAnimation { PauseAnimation { duration: 100} NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } @@ -532,6 +535,7 @@ Window { } displaced: Transition { + enabled: false NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } } From 9319210c4c2b13e8900a2bce77176d775cafa07a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 08:33:10 +0100 Subject: [PATCH 070/120] Fix local folder not opening on windows, add extra slash Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index aea75264ac52..eb010174bfce 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -45,7 +45,11 @@ ActivityListModel *User::getActivityModel() void User::openLocalFolder() { +#ifdef Q_OS_WIN + QString path = "file:///" + this->getFolder()->path(); +#else QString path = "file://" + this->getFolder()->path(); +#endif QDesktopServices::openUrl(path); } From 9e6df7a4dcb6d37d7653459a38aef9174d6bd01a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 08:43:51 +0100 Subject: [PATCH 071/120] Add populate transition Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 41de1806cb62..885dbd384d74 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -516,18 +516,19 @@ Window { } } + populate: Transition { + NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } + } + add: Transition { - enabled: false NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } } remove: Transition { - enabled: false NumberAnimation { property: "opacity"; from: 1.0; to: 0; duration: 100 } } removeDisplaced: Transition { - enabled: false SequentialAnimation { PauseAnimation { duration: 100} NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } @@ -535,7 +536,6 @@ Window { } displaced: Transition { - enabled: false NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } } From 17fcfd4e411cb6d78bd8bc0b0263ef9773d2534d Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 09:12:44 +0100 Subject: [PATCH 072/120] Show ListView delegate only when model is populated (prevent dummy delegate) Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 885dbd384d74..dea3fbc73fbd 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -453,6 +453,7 @@ Window { width: activityListView.width height: trayWindowHeaderLayout.height spacing: 0 + visible: (activityListView.model.rowCount() > 0) Image { id: activityIcon Layout.leftMargin: 6 From 1ebcd3a0fb36df0bbaeba2353d9f790552b4552c Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 09:59:34 +0100 Subject: [PATCH 073/120] Icon selection, svg path transfers, scrollbar Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- client.qrc | 1 + src/gui/tray/ActivityListModel.cpp | 29 +++++++---------------------- src/gui/tray/ActivityListModel.h | 19 +------------------ src/gui/tray/Window.qml | 8 +++++--- 4 files changed, 14 insertions(+), 43 deletions(-) diff --git a/client.qrc b/client.qrc index 509f8052c8e2..de385dfb20ac 100644 --- a/client.qrc +++ b/client.qrc @@ -2,6 +2,7 @@ resources/settings.png resources/settings@2x.png + resources/activity.svg resources/activity.png resources/activity@2x.png resources/network.png diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 3c8b50839690..0f0b7f4ad839 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include @@ -56,10 +55,6 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const { Activity a; - // filter the get action here - // send only the text of the get action - // if there is more than one send the icon? the ... - if (!index.isValid()) return QVariant(); @@ -99,39 +94,29 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const return customList; } case ActionIconRole:{ - ActionIcon actionIcon; if(a._type == Activity::NotificationType){ - QIcon cachedIcon; - if(!cachedIcon.isNull()) { - actionIcon.iconType = ActivityIconType::iconUseCached; - actionIcon.cachedIcon = cachedIcon; - } else { - actionIcon.iconType = ActivityIconType::iconBell; - } + return "qrc:///client/resources/bell.svg"; } else if(a._type == Activity::SyncResultType){ - actionIcon.iconType = ActivityIconType::iconStateError; + return "qrc:///client/resources/state-error.svg"; } else if(a._type == Activity::SyncFileItemType){ if(a._status == SyncFileItem::NormalError || a._status == SyncFileItem::FatalError || a._status == SyncFileItem::DetailError || a._status == SyncFileItem::BlacklistedError) { - actionIcon.iconType = ActivityIconType::iconStateError; + return "qrc:///client/resources/state-error.svg"; } else if(a._status == SyncFileItem::SoftError || a._status == SyncFileItem::Conflict || a._status == SyncFileItem::Restoration || a._status == SyncFileItem::FileLocked){ - actionIcon.iconType = ActivityIconType::iconStateWarning; + return "qrc:///client/resources/state-warning.svg"; } else if(a._status == SyncFileItem::FileIgnored){ - actionIcon.iconType = ActivityIconType::iconStateInfo; + return "qrc:///client/resources/state-info.svg"; } else { - actionIcon.iconType = ActivityIconType::iconStateSync; + return "qrc:///client/resources/state-sync.svg"; } } else { - actionIcon.iconType = ActivityIconType::iconActivity; + return "qrc:///client/resources/activity.svg"; } - QVariant icn; - icn.setValue(actionIcon); - return icn; } case ObjectTypeRole: return a._objectType; diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index d633bb07f537..c5976e451979 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -38,17 +38,7 @@ class ActivityListModel : public QAbstractListModel { Q_OBJECT public: - enum ActivityIconType { - iconUseCached = 0, - iconActivity, - iconBell, - iconStateError, - iconStateWarning, - iconStateInfo, - iconStateSync - }; - - enum datarole { + enum DataRole { ActionIconRole = Qt::UserRole + 1, UserIconRole, AccountRole, @@ -63,11 +53,6 @@ class ActivityListModel : public QAbstractListModel AccountConnectedRole, SyncFileStatusRole }; - struct ActionIcon { - ActivityIconType iconType; - QIcon cachedIcon; - }; - explicit ActivityListModel(AccountState *accountState, QObject* parent = 0); QVariant data(const QModelIndex &index, int role) const override; @@ -118,6 +103,4 @@ private slots: }; } -Q_DECLARE_METATYPE(OCC::ActivityListModel::ActionIcon) - #endif // ACTIVITYLISTMODEL_H diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index dea3fbc73fbd..e7e8c6907746 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -445,6 +445,9 @@ Window { width: trayWindowBackground.width height: trayWindowBackground.height - trayWindowHeaderBackground.height clip: true + ScrollBar.vertical: ScrollBar { + id: listViewScrollbar + } model: activityModel @@ -460,7 +463,8 @@ Window { Layout.preferredWidth: 48 Layout.preferredHeight: 48 verticalAlignment: Qt.AlignCenter - source: "qrc:///client/theme/black/state-sync.svg" + cache: true + source: icon sourceSize.height: 48 sourceSize.width: 48 } @@ -539,8 +543,6 @@ Window { displaced: Transition { NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } } - - focus: true } } // Rectangle trayWindowBackground From 96a74d9ef07e830f9c383008667b5579e87eeecd Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 11:46:49 +0100 Subject: [PATCH 074/120] Minor improvements Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/ActivityListModel.cpp | 132 ++++++++++++++++++----------- src/gui/tray/ActivityListModel.h | 1 + src/gui/tray/Window.qml | 17 ++-- 3 files changed, 93 insertions(+), 57 deletions(-) diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 0f0b7f4ad839..9e6ae09ddd1f 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -33,7 +33,7 @@ namespace OCC { Q_LOGGING_CATEGORY(lcActivity, "nextcloud.gui.activity", QtInfoMsg) -ActivityListModel::ActivityListModel(AccountState *accountState, QObject* parent) +ActivityListModel::ActivityListModel(AccountState *accountState, QObject *parent) : QAbstractListModel() , _accountState(accountState) { @@ -42,7 +42,9 @@ ActivityListModel::ActivityListModel(AccountState *accountState, QObject* parent QHash ActivityListModel::roleNames() const { QHash roles; + roles[DisplayPathRole] = "displaypath"; roles[PathRole] = "path"; + roles[LinkRole] = "link"; roles[MessageRole] = "message"; roles[ActionRole] = "type"; roles[ActionIconRole] = "icon"; @@ -65,18 +67,32 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const QStringList list; switch (role) { + case DisplayPathRole: + if (!a._file.isEmpty()) { + auto folder = FolderMan::instance()->folder(a._folder); + QString relPath(a._file); + if (folder) { + relPath.prepend(folder->remotePath()); + } + list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); + if (list.count() > 0) { + return list.at(0); + } + } + return QString(); case PathRole: - if(!a._file.isEmpty()){ + if (!a._file.isEmpty()) { auto folder = FolderMan::instance()->folder(a._folder); QString relPath(a._file); - if(folder) relPath.prepend(folder->remotePath()); + if (folder) + relPath.prepend(folder->remotePath()); list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); if (list.count() > 0) { QString path = "file:///" + QString(list.at(0)); return QUrl(path); } // File does not exist anymore? Let's try to open its path - if(QFileInfo(relPath).exists()) { + if (QFileInfo(relPath).exists()) { list = FolderMan::instance()->findFileInLocalFolders(QFileInfo(relPath).path(), ast->account()); if (list.count() > 0) { return QVariant(list.at(0)); @@ -84,7 +100,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } } return QString(); - case ActionsLinksRole:{ + case ActionsLinksRole: { QList customList; foreach (ActivityLink customItem, a._links) { QVariant customVariant; @@ -93,34 +109,34 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } return customList; } - case ActionIconRole:{ - if(a._type == Activity::NotificationType){ + case ActionIconRole: { + if (a._type == Activity::NotificationType) { return "qrc:///client/resources/bell.svg"; - } else if(a._type == Activity::SyncResultType){ + } else if (a._type == Activity::SyncResultType) { return "qrc:///client/resources/state-error.svg"; - } else if(a._type == Activity::SyncFileItemType){ - if(a._status == SyncFileItem::NormalError - || a._status == SyncFileItem::FatalError - || a._status == SyncFileItem::DetailError - || a._status == SyncFileItem::BlacklistedError) { - return "qrc:///client/resources/state-error.svg"; - } else if(a._status == SyncFileItem::SoftError - || a._status == SyncFileItem::Conflict - || a._status == SyncFileItem::Restoration - || a._status == SyncFileItem::FileLocked){ - return "qrc:///client/resources/state-warning.svg"; - } else if(a._status == SyncFileItem::FileIgnored){ - return "qrc:///client/resources/state-info.svg"; - } else { - return "qrc:///client/resources/state-sync.svg"; - } + } else if (a._type == Activity::SyncFileItemType) { + if (a._status == SyncFileItem::NormalError + || a._status == SyncFileItem::FatalError + || a._status == SyncFileItem::DetailError + || a._status == SyncFileItem::BlacklistedError) { + return "qrc:///client/resources/state-error.svg"; + } else if (a._status == SyncFileItem::SoftError + || a._status == SyncFileItem::Conflict + || a._status == SyncFileItem::Restoration + || a._status == SyncFileItem::FileLocked) { + return "qrc:///client/resources/state-warning.svg"; + } else if (a._status == SyncFileItem::FileIgnored) { + return "qrc:///client/resources/state-info.svg"; + } else { + return "qrc:///client/resources/state-sync.svg"; + } } else { return "qrc:///client/resources/activity.svg"; } } case ObjectTypeRole: return a._objectType; - case ActionRole:{ + case ActionRole: { switch (a._type) { case Activity::ActivityType: return "Activity"; @@ -141,8 +157,13 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const return QString("No description available."); } return a._message; - case LinkRole: - return a._link; + case LinkRole: { + if (a._link.isEmpty()) { + return ""; + } else { + return a._link; + } + } case AccountRole: return a._accName; case PointInTimeRole: @@ -231,76 +252,86 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st combineActivityLists(); } -void ActivityListModel::addErrorToActivityList(Activity activity) { +void ActivityListModel::addErrorToActivityList(Activity activity) +{ qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject; _notificationErrorsLists.prepend(activity); combineActivityLists(); } -void ActivityListModel::addIgnoredFileToList(Activity newActivity) { +void ActivityListModel::addIgnoredFileToList(Activity newActivity) +{ qCInfo(lcActivity) << "First checking for duplicates then add file to the notification list of ignored files: " << newActivity._file; bool duplicate = false; - if(_listOfIgnoredFiles.size() == 0){ + if (_listOfIgnoredFiles.size() == 0) { _notificationIgnoredFiles = newActivity; _notificationIgnoredFiles._subject = tr("Files from the ignore list as well as symbolic links are not synced. This includes:"); _listOfIgnoredFiles.append(newActivity); return; } - foreach(Activity activity, _listOfIgnoredFiles){ - if(activity._file == newActivity._file){ + foreach (Activity activity, _listOfIgnoredFiles) { + if (activity._file == newActivity._file) { duplicate = true; break; } } - if(!duplicate){ + if (!duplicate) { _notificationIgnoredFiles._message.append(", " + newActivity._file); } } -void ActivityListModel::addNotificationToActivityList(Activity activity) { +void ActivityListModel::addNotificationToActivityList(Activity activity) +{ qCInfo(lcActivity) << "Notification successfully added to the notification list: " << activity._subject; _notificationLists.prepend(activity); combineActivityLists(); } -void ActivityListModel::clearNotifications() { +void ActivityListModel::clearNotifications() +{ qCInfo(lcActivity) << "Clear the notifications"; _notificationLists.clear(); combineActivityLists(); } -void ActivityListModel::removeActivityFromActivityList(int row) { +void ActivityListModel::removeActivityFromActivityList(int row) +{ Activity activity = _finalList.at(row); removeActivityFromActivityList(activity); combineActivityLists(); } -void ActivityListModel::addSyncFileItemToActivityList(Activity activity) { +void ActivityListModel::addSyncFileItemToActivityList(Activity activity) +{ qCInfo(lcActivity) << "Successfully added to the activity list: " << activity._subject; _syncFileItemLists.prepend(activity); combineActivityLists(); } -void ActivityListModel::removeActivityFromActivityList(Activity activity) { +void ActivityListModel::removeActivityFromActivityList(Activity activity) +{ qCInfo(lcActivity) << "Activity/Notification/Error successfully dismissed: " << activity._subject; qCInfo(lcActivity) << "Trying to remove Activity/Notification/Error from view... "; int index = -1; - if(activity._type == Activity::ActivityType){ + if (activity._type == Activity::ActivityType) { index = _activityLists.indexOf(activity); - if(index != -1) _activityLists.removeAt(index); - } else if(activity._type == Activity::NotificationType){ + if (index != -1) + _activityLists.removeAt(index); + } else if (activity._type == Activity::NotificationType) { index = _notificationLists.indexOf(activity); - if(index != -1) _notificationLists.removeAt(index); + if (index != -1) + _notificationLists.removeAt(index); } else { index = _notificationErrorsLists.indexOf(activity); - if(index != -1) _notificationErrorsLists.removeAt(index); + if (index != -1) + _notificationErrorsLists.removeAt(index); } - if(index != -1){ + if (index != -1) { qCInfo(lcActivity) << "Activity/Notification/Error successfully removed from the list."; qCInfo(lcActivity) << "Updating Activity/Notification/Error view."; combineActivityLists(); @@ -311,24 +342,24 @@ void ActivityListModel::combineActivityLists() { ActivityList resultList; - if(_notificationErrorsLists.count() > 0) { + if (_notificationErrorsLists.count() > 0) { std::sort(_notificationErrorsLists.begin(), _notificationErrorsLists.end()); resultList.append(_notificationErrorsLists); } - if(_listOfIgnoredFiles.size() > 0) + if (_listOfIgnoredFiles.size() > 0) resultList.append(_notificationIgnoredFiles); - if(_notificationLists.count() > 0) { + if (_notificationLists.count() > 0) { std::sort(_notificationLists.begin(), _notificationLists.end()); resultList.append(_notificationLists); } - if(_syncFileItemLists.count() > 0) { + if (_syncFileItemLists.count() > 0) { std::sort(_syncFileItemLists.begin(), _syncFileItemLists.end()); resultList.append(_syncFileItemLists); } - if(_activityLists.count() > 0) { + if (_activityLists.count() > 0) { std::sort(_activityLists.begin(), _activityLists.end()); resultList.append(_activityLists); } @@ -342,7 +373,8 @@ void ActivityListModel::combineActivityLists() endInsertRows(); } -bool ActivityListModel::canFetchActivities() const { +bool ActivityListModel::canFetchActivities() const +{ return _accountState->isConnected() && _accountState->account()->capabilities().hasActivities(); } diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index c5976e451979..f073504a0034 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -47,6 +47,7 @@ class ActivityListModel : public QAbstractListModel ActionTextRole, ActionRole, MessageRole, + DisplayPathRole, PathRole, LinkRole, PointInTimeRole, diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index e7e8c6907746..602e15163a53 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -61,7 +61,6 @@ Window { trayWindow.setX( systrayBackend.calcTrayWindowX()); trayWindow.setY( systrayBackend.calcTrayWindowY()); systrayBackend.setOpened(); - userModelBackend.fetchCurrentActivityModel(); } onHideWindow: { trayWindow.hide(); @@ -470,7 +469,7 @@ Window { } Column { id: activityTextColumn - Layout.leftMargin: 6 + Layout.leftMargin: 4 spacing: 4 Layout.alignment: Qt.AlignLeft Text { @@ -482,7 +481,7 @@ Window { } Text { id: activityTextInfo - text: path + text: displaypath width: 220 elide: Text.ElideRight font.pointSize: 8 @@ -499,13 +498,12 @@ Window { Layout.alignment: Qt.AlignRight flat: true hoverEnabled: false - visible: (path === "") ? false : true + visible: (path !== "") ? true : false display: AbstractButton.IconOnly icon.source: "qrc:///client/resources/files.svg" icon.color: "transparent" - onClicked: - { + onClicked: { Qt.openUrlExternally(path) } } @@ -515,14 +513,19 @@ Window { Layout.alignment: Qt.AlignRight flat: true hoverEnabled: false + visible: (link !== "") ? true : false display: AbstractButton.IconOnly icon.source: "qrc:///client/resources/public.svg" icon.color: "transparent" + + onClicked: { + Qt.openUrlExternally(link) + } } } populate: Transition { - NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } + // prevent animations on initial list population } add: Transition { From 9e82ba60b4b4eb94b22ca6fc2c7cef4095564ca7 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 12:20:41 +0100 Subject: [PATCH 075/120] Integrated local sync activities in activitymodel Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 42 ++++++++++++++++++++++++++++++++++++++ src/gui/tray/UserModel.h | 3 +++ src/gui/tray/Window.qml | 2 +- 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index eb010174bfce..9dd6295222ed 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -17,6 +17,48 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject* parent) , _isCurrentUser(isCurrent) , _activityModel(new ActivityListModel(_account.data())) { + connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, + this, &User::slotItemCompleted); +} + +void User::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) +{ + auto folderInstance = FolderMan::instance()->folder(folder); + + if (!folderInstance) + return; + + // check if we are adding it to the right account and if it is useful information (protocol errors) + if (folderInstance->accountState() == _account.data()) { + qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in " << item->_errorString; + + Activity activity; + activity._type = Activity::SyncFileItemType; //client activity + activity._status = item->_status; + activity._dateTime = QDateTime::currentDateTime(); + activity._message = item->_originalFile; + activity._link = folderInstance->accountState()->account()->url(); + activity._accName = folderInstance->accountState()->account()->displayName(); + activity._file = item->_file; + activity._folder = folder; + + if (item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success) { + qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully."; + activity._message.prepend(" "); + activity._message.prepend(tr("Synced")); + _activityModel->addSyncFileItemToActivityList(activity); + } else { + qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString; + activity._subject = item->_errorString; + + if (item->_status == SyncFileItem::Status::FileIgnored) { + _activityModel->addIgnoredFileToList(activity); + } else { + // add 'protocol error' to activity list + _activityModel->addErrorToActivityList(activity); + } + } + } } AccountPtr User::account() const diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 3775e66b2cc3..f8ec80b52ddd 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -36,6 +36,9 @@ class User : public QObject void logout() const; void removeAccount() const; +public slots: + void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); + private: AccountStatePtr _account; bool _isCurrentUser; diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 602e15163a53..0991304b6c18 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -474,7 +474,7 @@ Window { Layout.alignment: Qt.AlignLeft Text { id: activityTextTitle - text: subject + text: (type === "Activity") ? subject : message width: 220 elide: Text.ElideRight font.pointSize: 9 From 84066634e7fc67765d0e0b77ddbc978d75224133 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 12:54:38 +0100 Subject: [PATCH 076/120] Integrated local error messages in activitymodel Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 36 ++++++++++++++++++++++++++++++++++++ src/gui/tray/UserModel.h | 1 + 2 files changed, 37 insertions(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 9dd6295222ed..caaa2b8f2fd4 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -19,6 +19,42 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject* parent) { connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, this, &User::slotItemCompleted); + connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, + this, &User::slotAddError); +} + +void User::slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category) +{ + auto folderInstance = FolderMan::instance()->folder(folderAlias); + if (!folderInstance) + return; + + if (folderInstance->accountState() == _account.data()) { + qCWarning(lcActivity) << "Item " << folderInstance->shortGuiLocalPath() << " retrieved resulted in " << message; + + Activity activity; + activity._type = Activity::SyncResultType; + activity._status = SyncResult::Error; + activity._dateTime = QDateTime::fromString(QDateTime::currentDateTime().toString(), Qt::ISODate); + activity._subject = message; + activity._message = folderInstance->shortGuiLocalPath(); + activity._link = folderInstance->shortGuiLocalPath(); + activity._accName = folderInstance->accountState()->account()->displayName(); + activity._folder = folderAlias; + + + if (category == ErrorCategory::InsufficientRemoteStorage) { + ActivityLink link; + link._label = tr("Retry all uploads"); + link._link = folderInstance->path(); + link._verb = ""; + link._isPrimary = true; + activity._links.append(link); + } + + // add 'other errors' to activity list + _activityModel->addErrorToActivityList(activity); + } } void User::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index f8ec80b52ddd..4bec4db085b7 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -38,6 +38,7 @@ class User : public QObject public slots: void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); + void slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category); private: AccountStatePtr _account; From eeefbe57afb270514dc52c3210460ae5690e5b8d Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 13:10:32 +0100 Subject: [PATCH 077/120] Added progressInfo / cleanup to acitivtymodel Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 69 ++++++++++++++++++++++++++++++++++++++ src/gui/tray/UserModel.h | 1 + 2 files changed, 70 insertions(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index caaa2b8f2fd4..f78d90d9dea3 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -1,6 +1,7 @@ #include "accountmanager.h" #include "owncloudgui.h" #include "UserModel.h" +#include "syncengine.h" #include #include @@ -17,12 +18,80 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject* parent) , _isCurrentUser(isCurrent) , _activityModel(new ActivityListModel(_account.data())) { + connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo, + this, &User::slotProgressInfo); connect(ProgressDispatcher::instance(), &ProgressDispatcher::itemCompleted, this, &User::slotItemCompleted); connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, this, &User::slotAddError); } +void User::slotProgressInfo(const QString &folder, const ProgressInfo &progress) +{ + if (progress.status() == ProgressInfo::Reconcile) { + // Wipe all non-persistent entries - as well as the persistent ones + // in cases where a local discovery was done. + auto f = FolderMan::instance()->folder(folder); + if (!f) + return; + const auto &engine = f->syncEngine(); + const auto style = engine.lastLocalDiscoveryStyle(); + foreach (Activity activity, _activityModel->errorsList()) { + if (activity._folder != folder) { + continue; + } + + if (style == LocalDiscoveryStyle::FilesystemOnly) { + _activityModel->removeActivityFromActivityList(activity); + continue; + } + + if (activity._status == SyncFileItem::Conflict && !QFileInfo(f->path() + activity._file).exists()) { + _activityModel->removeActivityFromActivityList(activity); + continue; + } + + if (activity._status == SyncFileItem::FileLocked && !QFileInfo(f->path() + activity._file).exists()) { + _activityModel->removeActivityFromActivityList(activity); + continue; + } + + + if (activity._status == SyncFileItem::FileIgnored && !QFileInfo(f->path() + activity._file).exists()) { + _activityModel->removeActivityFromActivityList(activity); + continue; + } + + + if (!QFileInfo(f->path() + activity._file).exists()) { + _activityModel->removeActivityFromActivityList(activity); + continue; + } + + auto path = QFileInfo(activity._file).dir().path().toUtf8(); + if (path == ".") + path.clear(); + + if (engine.shouldDiscoverLocally(path)) + _activityModel->removeActivityFromActivityList(activity); + } + } + + if (progress.status() == ProgressInfo::Done) { + // We keep track very well of pending conflicts. + // Inform other components about them. + QStringList conflicts; + foreach (Activity activity, _activityModel->errorsList()) { + if (activity._folder == folder + && activity._status == SyncFileItem::Conflict) { + conflicts.append(activity._file); + } + } + + emit ProgressDispatcher::instance()->folderConflicts(folder, conflicts); + } +} + void User::slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category) { auto folderInstance = FolderMan::instance()->folder(folderAlias); diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 4bec4db085b7..7ab4cf5b503c 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -38,6 +38,7 @@ class User : public QObject public slots: void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); + void slotProgressInfo(const QString &folder, const ProgressInfo &progress); void slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category); private: From e4b19d0cb5f83ca566859933ce6143f5c877f4d7 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 14:35:58 +0100 Subject: [PATCH 078/120] Activity refresh in background Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/CMakeLists.txt | 1 + src/gui/tray/NotificationHandler.cpp | 152 ++++++++++++++++++++ src/gui/tray/NotificationHandler.h | 36 +++++ src/gui/tray/UserModel.cpp | 200 ++++++++++++++++++++++++++- src/gui/tray/UserModel.h | 26 ++++ src/gui/tray/Window.qml | 1 + 6 files changed, 410 insertions(+), 6 deletions(-) create mode 100644 src/gui/tray/NotificationHandler.cpp create mode 100644 src/gui/tray/NotificationHandler.h diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index b919758deb4d..4ccf3abaaee9 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -104,6 +104,7 @@ set(client_SRCS tray/ActivityData.cpp tray/ActivityListModel.cpp tray/UserModel.cpp + tray/NotificationHandler.cpp creds/credentialsfactory.cpp creds/httpcredentialsgui.cpp creds/oauth.cpp diff --git a/src/gui/tray/NotificationHandler.cpp b/src/gui/tray/NotificationHandler.cpp new file mode 100644 index 000000000000..f1594b4dd683 --- /dev/null +++ b/src/gui/tray/NotificationHandler.cpp @@ -0,0 +1,152 @@ +#include "NotificationHandler.h" + +#include "accountstate.h" +#include "capabilities.h" +#include "networkjobs.h" + +#include "iconjob.h" + +#include +#include + +namespace OCC { + +Q_LOGGING_CATEGORY(lcServerNotification, "nextcloud.gui.servernotification", QtInfoMsg) + +const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/api/v2/notifications"); +const char propertyAccountStateC[] = "oc_account_state"; +const int successStatusCode = 200; +const int notModifiedStatusCode = 304; +QMap ServerNotificationHandler::iconCache; + +ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent) + : QObject(parent) + , _accountState(accountState) +{ +} + +void ServerNotificationHandler::slotFetchNotifications() +{ + // check connectivity and credentials + if (!(_accountState && _accountState->isConnected() && _accountState->account() && _accountState->account()->credentials() && _accountState->account()->credentials()->ready())) { + deleteLater(); + return; + } + // check if the account has notifications enabled. If the capabilities are + // not yet valid, its assumed that notifications are available. + if (_accountState->account()->capabilities().isValid()) { + if (!_accountState->account()->capabilities().notificationsAvailable()) { + qCInfo(lcServerNotification) << "Account" << _accountState->account()->displayName() << "does not have notifications enabled."; + deleteLater(); + return; + } + } + + // if the previous notification job has finished, start next. + _notificationJob = new JsonApiJob(_accountState->account(), notificationsPath, this); + QObject::connect(_notificationJob.data(), &JsonApiJob::jsonReceived, + this, &ServerNotificationHandler::slotNotificationsReceived); + QObject::connect(_notificationJob.data(), &JsonApiJob::etagResponseHeaderReceived, + this, &ServerNotificationHandler::slotEtagResponseHeaderReceived); + _notificationJob->setProperty(propertyAccountStateC, QVariant::fromValue(_accountState)); + _notificationJob->addRawHeader("If-None-Match", _accountState->notificationsEtagResponseHeader()); + _notificationJob->start(); +} + +void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode) +{ + if (statusCode == successStatusCode) { + qCWarning(lcServerNotification) << "New Notification ETag Response Header received " << value; + AccountState *account = qvariant_cast(sender()->property(propertyAccountStateC)); + account->setNotificationsEtagResponseHeader(value); + } +} + +void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData) +{ + QPixmap pixmap; + pixmap.loadFromData(iconData); + iconCache.insert(sender()->property("activityId").toInt(), QIcon(pixmap)); +} + +void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode) +{ + if (statusCode != successStatusCode && statusCode != notModifiedStatusCode) { + qCWarning(lcServerNotification) << "Notifications failed with status code " << statusCode; + deleteLater(); + return; + } + + if (statusCode == notModifiedStatusCode) { + qCWarning(lcServerNotification) << "Status code " << statusCode << " Not Modified - No new notifications."; + deleteLater(); + return; + } + + auto notifies = json.object().value("ocs").toObject().value("data").toArray(); + + AccountState *ai = qvariant_cast(sender()->property(propertyAccountStateC)); + + ActivityList list; + + foreach (auto element, notifies) { + Activity a; + auto json = element.toObject(); + 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(); + a._status = 0; + + a._subject = json.value("subject").toString(); + a._message = json.value("message").toString(); + + if (!json.value("icon").toString().isEmpty()) { + IconJob *iconJob = new IconJob(QUrl(json.value("icon").toString())); + iconJob->setProperty("activityId", a._id); + connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded); + } + + QUrl link(json.value("link").toString()); + if (!link.isEmpty()) { + if (link.host().isEmpty()) { + link.setScheme(ai->account()->url().scheme()); + link.setHost(ai->account()->url().host()); + } + if (link.port() == -1) { + link.setPort(ai->account()->url().port()); + } + } + a._link = link; + a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate); + + auto actions = json.value("actions").toArray(); + foreach (auto action, actions) { + auto actionJson = action.toObject(); + ActivityLink al; + al._label = QUrl::fromPercentEncoding(actionJson.value("label").toString().toUtf8()); + al._link = actionJson.value("link").toString(); + al._verb = actionJson.value("type").toString().toUtf8(); + al._isPrimary = actionJson.value("primary").toBool(); + + a._links.append(al); + } + + // 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 + ActivityLink al; + al._label = tr("Dismiss"); + al._link = Utility::concatUrlPath(ai->account()->url(), notificationsPath + "/" + QString::number(a._id)).toString(); + al._verb = "DELETE"; + al._isPrimary = false; + a._links.append(al); + + list.append(a); + } + emit newNotificationList(list); + + deleteLater(); +} +} \ No newline at end of file diff --git a/src/gui/tray/NotificationHandler.h b/src/gui/tray/NotificationHandler.h new file mode 100644 index 000000000000..01caef428e1d --- /dev/null +++ b/src/gui/tray/NotificationHandler.h @@ -0,0 +1,36 @@ +#ifndef NOTIFICATIONHANDLER_H +#define NOTIFICATIONHANDLER_H + +#include + +#include "UserModel.h" + +class QJsonDocument; + +namespace OCC { + +class ServerNotificationHandler : public QObject +{ + Q_OBJECT +public: + explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr); + static QMap iconCache; + +signals: + void newNotificationList(ActivityList); + +public slots: + void slotFetchNotifications(); + +private slots: + void slotNotificationsReceived(const QJsonDocument &json, int statusCode); + void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode); + void slotIconDownloaded(QByteArray iconData); + +private: + QPointer _notificationJob; + AccountState *_accountState; +}; +} + +#endif // NOTIFICATIONHANDLER_H \ No newline at end of file diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index f78d90d9dea3..2d2da8ec61bb 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -1,7 +1,12 @@ +#include "NotificationHandler.h" +#include "UserModel.h" + #include "accountmanager.h" #include "owncloudgui.h" -#include "UserModel.h" #include "syncengine.h" +#include "ocsjob.h" +#include "configfile.h" +#include "notificationconfirmjob.h" #include #include @@ -10,13 +15,18 @@ #include #include +// time span in milliseconds which has to be between two +// refreshes of the notifications +#define NOTIFICATION_REQUEST_FREE_PERIOD 15000 + namespace OCC { -User::User(AccountStatePtr &account, const bool &isCurrent, QObject* parent) +User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent) : QObject(parent) , _account(account) , _isCurrentUser(isCurrent) , _activityModel(new ActivityListModel(_account.data())) + , _notificationRequestsRunning(0) { connect(ProgressDispatcher::instance(), &ProgressDispatcher::progressInfo, this, &User::slotProgressInfo); @@ -24,6 +34,183 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject* parent) this, &User::slotItemCompleted); connect(ProgressDispatcher::instance(), &ProgressDispatcher::syncError, this, &User::slotAddError); + + connect(&_notificationCheckTimer, &QTimer::timeout, + this, &User::slotRefresh); + + connect(_account.data(), &AccountState::stateChanged, + [=]() { if (isConnected()) {slotRefresh();} }); +} + +void User::slotBuildNotificationDisplay(const ActivityList &list) +{ + // Whether a new notification was added to the list + bool newNotificationShown = false; + + _activityModel->clearNotifications(); + + foreach (auto activity, list) { + if (_blacklistedNotifications.contains(activity)) { + qCInfo(lcActivity) << "Activity in blacklist, skip"; + continue; + } + + // handle gui logs. In order to NOT annoy the user with every fetching of the + // notifications the notification id is stored in a Set. Only if an id + // is not in the set, it qualifies for guiLog. + // Important: The _guiLoggedNotifications set must be wiped regularly which + // will repeat the gui log. + + // after one hour, clear the gui log notification store + if (_guiLogTimer.elapsed() > 60 * 60 * 1000) { + _guiLoggedNotifications.clear(); + } + + if (!_guiLoggedNotifications.contains(activity._id)) { + newNotificationShown = true; + _guiLoggedNotifications.insert(activity._id); + + // Assemble a tray notification for the NEW notification + ConfigFile cfg; + if (cfg.optionalServerNotifications()) { + if (AccountManager::instance()->accounts().count() == 1) { + emit guiLog(activity._subject, ""); + } else { + emit guiLog(activity._subject, activity._accName); + } + } + } + + _activityModel->addNotificationToActivityList(activity); + } + + // restart the gui log timer now that we show a new notification + if (newNotificationShown) { + _guiLogTimer.start(); + } +} + +void User::setNotificationRefreshInterval(std::chrono::milliseconds interval) +{ + qCDebug(lcActivity) << "Starting Notification refresh timer with " << interval.count() / 1000 << " sec interval"; + _notificationCheckTimer.start(interval.count()); +} + +void User::slotRefresh() +{ + // QElapsedTimer isn't actually constructed as invalid. + if (!_timeSinceLastCheck.contains(_account.data())) { + _timeSinceLastCheck[_account.data()].invalidate(); + } + QElapsedTimer &timer = _timeSinceLastCheck[_account.data()]; + + // Fetch Activities only if visible and if last check is longer than 15 secs ago + if (timer.isValid() && timer.elapsed() < NOTIFICATION_REQUEST_FREE_PERIOD) { + qCDebug(lcActivity) << "Do not check as last check is only secs ago: " << timer.elapsed() / 1000; + return; + } + if (_account.data() && _account.data()->isConnected()) { + if (!timer.isValid()) { + this->slotRefreshActivities(); + } + this->slotRefreshNotifications(); + timer.start(); + } +} + +void User::slotRefreshActivities() +{ + _activityModel->slotRefreshActivity(); +} + +void User::slotRefreshNotifications() +{ + // start a server notification handler if no notification requests + // are running + if (_notificationRequestsRunning == 0) { + ServerNotificationHandler *snh = new ServerNotificationHandler(_account.data()); + connect(snh, &ServerNotificationHandler::newNotificationList, + this, &User::slotBuildNotificationDisplay); + + snh->slotFetchNotifications(); + } else { + qCWarning(lcActivity) << "Notification request counter not zero."; + } +} + +void User::slotNotificationRequestFinished(int statusCode) +{ + int row = sender()->property("activityRow").toInt(); + + // the ocs API returns stat code 100 or 200 inside the xml if it succeeded. + if (statusCode != OCS_SUCCESS_STATUS_CODE && statusCode != OCS_SUCCESS_STATUS_CODE_V2) { + qCWarning(lcActivity) << "Notification Request to Server failed, leave notification visible."; + } else { + // to do use the model to rebuild the list or remove the item + qCWarning(lcActivity) << "Notification Request to Server successed, rebuilding list."; + _activityModel->removeActivityFromActivityList(row); + } +} + +void User::slotEndNotificationRequest(int replyCode) +{ + _notificationRequestsRunning--; + slotNotificationRequestFinished(replyCode); +} + +void User::slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row) +{ + qCInfo(lcActivity) << "Server Notification Request " << verb << link << "on account" << accountName; + + const QStringList validVerbs = QStringList() << "GET" + << "PUT" + << "POST" + << "DELETE"; + + if (validVerbs.contains(verb)) { + AccountStatePtr acc = AccountManager::instance()->account(accountName); + if (acc) { + NotificationConfirmJob *job = new NotificationConfirmJob(acc->account()); + QUrl l(link); + job->setLinkAndVerb(l, verb); + job->setProperty("activityRow", QVariant::fromValue(row)); + connect(job, &AbstractNetworkJob::networkError, + this, &User::slotNotifyNetworkError); + connect(job, &NotificationConfirmJob::jobFinished, + this, &User::slotNotifyServerFinished); + job->start(); + + // count the number of running notification requests. If this member var + // is larger than zero, no new fetching of notifications is started + _notificationRequestsRunning++; + } + } else { + qCWarning(lcActivity) << "Notification Links: Invalid verb:" << verb; + } +} + +void User::slotNotifyNetworkError(QNetworkReply *reply) +{ + NotificationConfirmJob *job = qobject_cast(sender()); + if (!job) { + return; + } + + int resultCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + slotEndNotificationRequest(resultCode); + qCWarning(lcActivity) << "Server notify job failed with code " << resultCode; +} + +void User::slotNotifyServerFinished(const QString &reply, int replyCode) +{ + NotificationConfirmJob *job = qobject_cast(sender()); + if (!job) { + return; + } + + slotEndNotificationRequest(replyCode); + qCInfo(lcActivity) << "Server Notification reply code" << replyCode << reply; } void User::slotProgressInfo(const QString &folder, const ProgressInfo &progress) @@ -390,6 +577,8 @@ void UserModel::addUser(AccountStatePtr &user, const bool &isCurrent) _currentUserId = _users.indexOf(_users.last()); } endInsertRows(); + ConfigFile cfg; + _users.last()->setNotificationRefreshInterval(cfg.notificationRefreshInterval()); } } @@ -430,7 +619,8 @@ Q_INVOKABLE void UserModel::switchCurrentUser(const int &id) emit newUserSelected(); } -Q_INVOKABLE void UserModel::login(const int &id) { +Q_INVOKABLE void UserModel::login(const int &id) +{ _users[id]->login(); emit refreshCurrentUserGui(); } @@ -524,9 +714,7 @@ bool UserModel::currentUserHasActivities() void UserModel::fetchCurrentActivityModel() { - if (_users[currentUserId()]->isConnected()) { - _users[currentUserId()]->getActivityModel()->fetchMore(QModelIndex()); - } + _users[currentUserId()]->slotRefresh(); } /*-------------------------------------------------------------------------------------*/ diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 7ab4cf5b503c..f7d5092f8b9d 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -3,12 +3,14 @@ #include #include +#include #include #include #include "ActivityListModel.h" #include "accountmanager.h" #include "folderman.h" +#include namespace OCC { @@ -36,15 +38,39 @@ class User : public QObject void logout() const; void removeAccount() const; +signals: + void guiLog(const QString &, const QString &); + public slots: void slotItemCompleted(const QString &folder, const SyncFileItemPtr &item); void slotProgressInfo(const QString &folder, const ProgressInfo &progress); void slotAddError(const QString &folderAlias, const QString &message, ErrorCategory category); + void slotNotificationRequestFinished(int statusCode); + void slotNotifyNetworkError(QNetworkReply *reply); + void slotEndNotificationRequest(int replyCode); + void slotNotifyServerFinished(const QString &reply, int replyCode); + void slotSendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row); + void slotBuildNotificationDisplay(const ActivityList &list); + void slotRefreshNotifications(); + void slotRefreshActivities(); + void slotRefresh(); + void setNotificationRefreshInterval(std::chrono::milliseconds interval); private: AccountStatePtr _account; bool _isCurrentUser; ActivityListModel *_activityModel; + ActivityList _blacklistedNotifications; + + QTimer _notificationCheckTimer; + QHash _timeSinceLastCheck; + + QElapsedTimer _guiLogTimer; + QSet _guiLoggedNotifications; + + // number of currently running notification requests. If non zero, + // no query for notifications is started. + int _notificationRequestsRunning; }; class UserModel : public QAbstractListModel diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 0991304b6c18..7b38106bc5ba 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -61,6 +61,7 @@ Window { trayWindow.setX( systrayBackend.calcTrayWindowX()); trayWindow.setY( systrayBackend.calcTrayWindowY()); systrayBackend.setOpened(); + userModelBackend.fetchCurrentActivityModel(); } onHideWindow: { trayWindow.hide(); From f748a4bb5012a97f7d52f8ef37a7b8fb794aefc4 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Mon, 13 Jan 2020 22:11:57 +0100 Subject: [PATCH 079/120] Made tray window positioning HighDPI aware Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 92 +++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 62efff23a953..b01681c28296 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -125,36 +125,44 @@ void Systray::setToolTip(const QString &tip) int Systray::calcTrayWindowX() { -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QScreen *trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); +#ifdef Q_OS_OSX + // macOS handles DPI awareness differently + // and menu bar is always at the top, icons starting from the right + + QPoint topLeft = this->geometry().topLeft(); + QPoint topRight = this->geometry().topRight(); + int trayIconTopCenterX = (topRight - ((topRight - topLeft) * 0.5)).x(); + return trayIconTopCenterX - (400 * 0.5); #else - QScreen *trayScreen = QGuiApplication::primaryScreen(); -#endif + + int screenWidth = trayScreen->geometry().width(); + int screenHeight = trayScreen->geometry().height(); + int availableWidth = trayScreen->availableGeometry().width(); + int availableHeight = trayScreen->availableGeometry().height(); + QPoint topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio(); + QPoint topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio(); // get coordinates from top center point of tray icon - int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); - int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); - - if ((trayScreen->geometry().width() - trayIconTopCenterX) < (trayScreen->geometry().width() * 0.5)) { - // tray icon is on right side of the screen - if (((trayScreen->geometry().width() - trayIconTopCenterX) < trayScreen->geometry().height() - trayIconTopCenterY) - && ((trayScreen->geometry().width() - trayIconTopCenterX) < trayIconTopCenterY)) { - // taskbar is on the right - return trayScreen->availableSize().width() - 400 - 6; + int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x(); + int trayIconTopCenterY = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).y(); + + if (availableHeight < screenHeight) { + // taskbar is on top or bottom + if (trayIconTopCenterX + (400 * 0.5) > availableWidth) { + return availableWidth - 400 - 12; } else { - // taskbar is on the bottom or top - if (trayIconTopCenterX - (400 * 0.5) < 0) { - return 6; - } else if (trayIconTopCenterX - (400 * 0.5) > trayScreen->geometry().width()) { - return trayScreen->geometry().width() - 406; - } else { - return trayIconTopCenterX - (400 * 0.5); - } + return trayIconTopCenterX - (400 * 0.5); } } else { - // tray icon is on left side of the screen - return (trayScreen->geometry().width() - trayScreen->availableGeometry().width()) + 6; + if (trayScreen->availableGeometry().x() > trayScreen->geometry().x()) { + // on the left + return (screenWidth - availableWidth) + 6; + } else { + // on the right + return screenWidth - 400 - (screenWidth - availableWidth) - 6; + } } +#endif } int Systray::calcTrayWindowY() { @@ -162,34 +170,30 @@ int Systray::calcTrayWindowY() // macOS menu bar is always 22 (effective) pixels // don't use availableGeometry() here, because this also excludes the dock return 22+6; -#else -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - QScreen *trayScreen = QGuiApplication::screenAt(this->geometry().topRight()); #else QScreen *trayScreen = QGuiApplication::primaryScreen(); -#endif + int screenWidth = trayScreen->geometry().width(); + int screenHeight = trayScreen->geometry().height(); + int availableHeight = trayScreen->availableGeometry().height(); + QPoint topRightDpiAware = this->geometry().topRight() / trayScreen->devicePixelRatio(); + QPoint topLeftDpiAware = this->geometry().topLeft() / trayScreen->devicePixelRatio(); // get coordinates from top center point of tray icon - int trayIconTopCenterX = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).x(); - int trayIconTopCenterY = (this->geometry().topRight() - ((this->geometry().topRight() - this->geometry().topLeft()) * 0.5)).y(); - - if ((trayScreen->geometry().height() - trayIconTopCenterY) < (trayScreen->geometry().height() * 0.5)) { - // tray icon is on bottom side of the screen - if (((trayScreen->geometry().height() - trayIconTopCenterY) < trayScreen->geometry().width() - trayIconTopCenterX) - && ((trayScreen->geometry().height() - trayIconTopCenterY) < trayIconTopCenterX)) { - // taskbar is on the bottom - return trayScreen->availableSize().height() - 500 - 6; + int trayIconTopCenterX = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).x(); + int trayIconTopCenterY = (topRightDpiAware - ((topRightDpiAware - topLeftDpiAware) * 0.5)).y(); + + if (availableHeight < screenHeight) { + // taskbar is on top or bottom + if (trayScreen->availableGeometry().y() > trayScreen->geometry().y()) { + // on top + return (screenHeight - availableHeight) + 6; } else { - // taskbar is on the right or left - if (trayIconTopCenterY - 500 > 0) { - return trayIconTopCenterY - 500; - } else { - return 6; - } + // on bottom + return screenHeight - 500 - (screenHeight - availableHeight) - 6; } } else { - // tray icon is on the top - return (trayScreen->geometry().height() - trayScreen->availableGeometry().height()) + 6; + // on the left or right + return (trayIconTopCenterY - 500 + 12); } #endif } From 65b47d4613c0b290e2ba08c4cc9fb79d26a56f0b Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 14 Jan 2020 07:59:28 +0100 Subject: [PATCH 080/120] Make qml UI strings translatable Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 4 ++-- src/gui/tray/Window.qml | 18 +++++------------- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 700951fefd5d..0c2025032e80 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -118,7 +118,7 @@ MenuItem { } MenuItem { - text: isConnected ? "Log out" : "Log in" + text: isConnected ? qsTr("Log out") : qsTr("Log in") onClicked: { isConnected ? userModelBackend.logout(index) : userModelBackend.login(index) accountMenu.close() @@ -126,7 +126,7 @@ MenuItem { } MenuItem { - text: "Remove Account" + text: qsTr("Remove Account") onClicked: { userModelBackend.removeAccount(index) accountMenu.close() diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 7b38106bc5ba..af9641239a37 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -109,7 +109,7 @@ Window { hoverEnabled: true onClicked: { - syncPauseButton.text = systrayBackend.syncIsPaused() ? "Resume sync for all" : "Pause sync for all" + syncPauseButton.text = systrayBackend.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all") accountMenu.open() } @@ -156,7 +156,7 @@ Window { } Label { Layout.leftMargin: 10 - text: "Add account" + text: qsTr("Add account") color: "black" font.pixelSize: 12 } @@ -176,27 +176,19 @@ Window { } MenuItem { - text: "Open settings" + text: qsTr("Open settings") onClicked: systrayBackend.openSettings() } MenuItem { - text: "Help" + text: qsTr("Help") onClicked: systrayBackend.openHelp() } MenuItem { - text: "Quit Nextcloud" + text: qsTr("Quit Nextcloud") onClicked: systrayBackend.shutdown() } - - Component.onCompleted: {/* - if(userModelBackend.numUsers() === 1) { - accountMenuSeparator.height = 0 - } else { - accountMenuSeparator.height = 13 - }*/ - } } } From c09dd9287cb53b00934b5dca7ecd92ac83964390 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 14 Jan 2020 08:55:39 +0100 Subject: [PATCH 081/120] Fix missing trayScreen init in non-OSX clause Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index b01681c28296..b817054d133a 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -134,7 +134,7 @@ int Systray::calcTrayWindowX() int trayIconTopCenterX = (topRight - ((topRight - topLeft) * 0.5)).x(); return trayIconTopCenterX - (400 * 0.5); #else - + QScreen *trayScreen = QGuiApplication::primaryScreen(); int screenWidth = trayScreen->geometry().width(); int screenHeight = trayScreen->geometry().height(); int availableWidth = trayScreen->availableGeometry().width(); From 29138cc53392b2f7a159dc5e220940a68da45483 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Tue, 14 Jan 2020 19:48:21 +0100 Subject: [PATCH 082/120] Font and icon size improvements, svg transfer to new folder Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/ActivityListModel.cpp | 14 +++++++------- src/gui/tray/Window.qml | 25 ++++++++++++++++--------- theme.qrc | 3 +++ theme/black/activity.svg | 2 ++ theme/black/bell.svg | 3 +++ theme/black/state-info.svg | 1 + 6 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 theme/black/activity.svg create mode 100644 theme/black/bell.svg create mode 100644 theme/black/state-info.svg diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 9e6ae09ddd1f..964b1f7acd1d 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -111,27 +111,27 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } case ActionIconRole: { if (a._type == Activity::NotificationType) { - return "qrc:///client/resources/bell.svg"; + return "qrc:///client/theme/black/bell.svg"; } else if (a._type == Activity::SyncResultType) { - return "qrc:///client/resources/state-error.svg"; + return "qrc:///client/theme/black/state-error.svg"; } else if (a._type == Activity::SyncFileItemType) { if (a._status == SyncFileItem::NormalError || a._status == SyncFileItem::FatalError || a._status == SyncFileItem::DetailError || a._status == SyncFileItem::BlacklistedError) { - return "qrc:///client/resources/state-error.svg"; + return "qrc:///client/theme/black/state-error.svg"; } else if (a._status == SyncFileItem::SoftError || a._status == SyncFileItem::Conflict || a._status == SyncFileItem::Restoration || a._status == SyncFileItem::FileLocked) { - return "qrc:///client/resources/state-warning.svg"; + return "qrc:///client/theme/black/state-warning.svg"; } else if (a._status == SyncFileItem::FileIgnored) { - return "qrc:///client/resources/state-info.svg"; + return "qrc:///client/theme/black/state-info.svg"; } else { - return "qrc:///client/resources/state-sync.svg"; + return "qrc:///client/theme/black/state-sync.svg"; } } else { - return "qrc:///client/resources/activity.svg"; + return "qrc:///client/theme/black/activity.svg"; } } case ObjectTypeRole: diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index af9641239a37..638d6549f750 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -172,21 +172,25 @@ Window { MenuItem { id: syncPauseButton + font.pixelSize: 12 onClicked: systrayBackend.pauseResumeSync() } MenuItem { text: qsTr("Open settings") + font.pixelSize: 12 onClicked: systrayBackend.openSettings() } MenuItem { text: qsTr("Help") + font.pixelSize: 12 onClicked: systrayBackend.openHelp() } MenuItem { text: qsTr("Quit Nextcloud") + font.pixelSize: 12 onClicked: systrayBackend.shutdown() } } @@ -449,16 +453,17 @@ Window { height: trayWindowHeaderLayout.height spacing: 0 visible: (activityListView.model.rowCount() > 0) + Image { id: activityIcon - Layout.leftMargin: 6 - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 + Layout.leftMargin: 8 + Layout.preferredWidth: activityButton1.icon.width + Layout.preferredHeight: activityButton1.icon.height verticalAlignment: Qt.AlignCenter cache: true source: icon - sourceSize.height: 48 - sourceSize.width: 48 + sourceSize.height: activityButton1.icon.height + sourceSize.width: activityButton1.icon.width } Column { id: activityTextColumn @@ -468,16 +473,18 @@ Window { Text { id: activityTextTitle text: (type === "Activity") ? subject : message - width: 220 + width: 236 elide: Text.ElideRight - font.pointSize: 9 + font.pixelSize: 12 } + Text { id: activityTextInfo text: displaypath - width: 220 + height: (displaypath === "") ? 0 : activityTextTitle.height + width: 236 elide: Text.ElideRight - font.pointSize: 8 + font.pixelSize: 10 } } Item { diff --git a/theme.qrc b/theme.qrc index 42bda010fd40..2a2881e96302 100644 --- a/theme.qrc +++ b/theme.qrc @@ -140,5 +140,8 @@ theme/black/user.svg theme/white/add.svg theme/black/add.svg + theme/black/activity.svg + theme/black/bell.svg + theme/black/state-info.svg diff --git a/theme/black/activity.svg b/theme/black/activity.svg new file mode 100644 index 000000000000..b6282ba77d28 --- /dev/null +++ b/theme/black/activity.svg @@ -0,0 +1,2 @@ + + diff --git a/theme/black/bell.svg b/theme/black/bell.svg new file mode 100644 index 000000000000..9de878f8d313 --- /dev/null +++ b/theme/black/bell.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/theme/black/state-info.svg b/theme/black/state-info.svg new file mode 100644 index 000000000000..762de0370ff5 --- /dev/null +++ b/theme/black/state-info.svg @@ -0,0 +1 @@ + \ No newline at end of file From b57b8cfb66a16ede3552933b9c8a72b9562d73d4 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 15 Jan 2020 08:01:54 +0100 Subject: [PATCH 083/120] Resized add account icon, adapted layout for new size Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 638d6549f750..92f2a848a8f3 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -140,7 +140,7 @@ Window { MenuItem { id: addAccountButton - height: 60 + height: 50 RowLayout { width: addAccountButton.width @@ -148,14 +148,14 @@ Window { spacing: 0 Image { - Layout.leftMargin: 8 + Layout.leftMargin: 14 verticalAlignment: Qt.AlignCenter source: "qrc:///client/theme/black/add.svg" - sourceSize.width: addAccountButton.height - 24 - sourceSize.height: addAccountButton.height - 24 + sourceSize.width: openLocalFolderButton.icon.width + sourceSize.height: openLocalFolderButton.icon.height } Label { - Layout.leftMargin: 10 + Layout.leftMargin: 14 text: qsTr("Add account") color: "black" font.pixelSize: 12 From cb328d6cea33186110068eb1d5293d9f7721b261 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Wed, 15 Jan 2020 16:23:46 +0100 Subject: [PATCH 084/120] Fetch server Apps in AccountState (moved from ownCloudGui) - Add a new class AccountApp to keep them in an AccountAppList and also save properties like ID and Icon URL. - Clear the app list upon re-fetch to avoid endlessly growing lists like in the previous implementation in ownCloudGui. Signed-off-by: Michael Schuster --- src/gui/accountstate.cpp | 100 +++++++++++++++++++++++++++++++++++++++ src/gui/accountstate.h | 37 +++++++++++++++ src/gui/owncloudgui.cpp | 94 ------------------------------------ src/gui/owncloudgui.h | 12 ----- 4 files changed, 137 insertions(+), 106 deletions(-) diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 9d2fa1830f57..b9b42eca548d 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -20,6 +20,7 @@ #include "creds/httpcredentials.h" #include "logger.h" #include "configfile.h" +#include "ocsnavigationappsjob.h" #include #include @@ -27,6 +28,7 @@ #include #include +#include #include #include @@ -259,6 +261,9 @@ void AccountState::checkConnectivity() // Use a small authed propfind as a minimal ping when we're // already connected. conValidator->checkAuthentication(); + + // Get the Apps available on the server. + fetchNavigationApps(); } else { // Check the server and then the auth. @@ -316,6 +321,9 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta if (_state != Connected) { setState(Connected); setTalkCapability(); + + // Get the Apps available on the server. + fetchNavigationApps(); } break; case ConnectionValidator::Undefined: @@ -428,4 +436,96 @@ std::unique_ptr AccountState::settings() return s; } +void AccountState::fetchNavigationApps(){ + OcsNavigationAppsJob *job = new OcsNavigationAppsJob(_account); + job->addRawHeader("If-None-Match", navigationAppsEtagResponseHeader()); + connect(job, &OcsNavigationAppsJob::appsJobFinished, this, &AccountState::slotNavigationAppsFetched); + connect(job, &OcsNavigationAppsJob::etagResponseHeaderReceived, this, &AccountState::slotEtagResponseHeaderReceived); + connect(job, &OcsNavigationAppsJob::ocsError, this, &AccountState::slotOcsError); + job->getNavigationApps(); +} + +void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){ + if(statusCode == 200){ + qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value; + setNavigationAppsEtagResponseHeader(value); + } +} + +void AccountState::slotOcsError(int statusCode, const QString &message) +{ + qCDebug(lcAccountState) << "Error " << statusCode << " while fetching new navigation apps: " << message; +} + +void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode) +{ + if(_account){ + if (statusCode == 304) { + qCWarning(lcAccountState) << "Status code " << statusCode << " Not Modified - No new navigation apps."; + } else { + _apps.clear(); + _hasTalk = false; + + if(!reply.isEmpty()){ + printf("%s\n", reply.toJson().toStdString().c_str()); + auto element = reply.object().value("ocs").toObject().value("data"); + auto navLinks = element.toArray(); + + if(navLinks.size() > 0){ + foreach (const QJsonValue &value, navLinks) { + auto navLink = value.toObject(); + + AccountApp *app = new AccountApp(navLink.value("name").toString(), QUrl(navLink.value("href").toString()), + navLink.value("id").toString(), QUrl(navLink.value("icon").toString())); + + _apps << app; + } + } + } + + emit hasFetchedNavigationApps(); + } + } +} + +AccountAppList AccountState::appList() const +{ + return _apps; +} + +/*-------------------------------------------------------------------------------------*/ + +AccountApp::AccountApp(const QString &name, const QUrl &url, + const QString &id, const QUrl &iconUrl, + QObject *parent) + : QObject(parent) + , _name(name) + , _url(url) + , _id(id) + , _iconUrl(iconUrl) +{ +} + +QString AccountApp::name() const +{ + return _name; +} + +QUrl AccountApp::url() const +{ + return _url; +} + +QString AccountApp::id() const +{ + return _id; +} + +QUrl AccountApp::iconUrl() const +{ + return _iconUrl; +} + +/*-------------------------------------------------------------------------------------*/ + } // namespace OCC diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 7633bd78dfa2..8bacdcd9c7db 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -29,9 +29,11 @@ namespace OCC { class AccountState; class Account; +class AccountApp; class RemoteWipe; typedef QExplicitlySharedDataPointer AccountStatePtr; +typedef QList AccountAppList; /** * @brief Extra info about an ownCloud server account. @@ -103,6 +105,8 @@ class AccountState : public QObject, public QSharedData bool hasTalk() const; + AccountAppList appList() const; + /** A user-triggered sign out which disconnects, stops syncs * for the account and forgets the password. */ void signOutByUi(); @@ -164,10 +168,12 @@ public slots: private: void setState(State state); void setTalkCapability(); + void fetchNavigationApps(); signals: void stateChanged(int state); void isConnectedChanged(); + void hasFetchedNavigationApps(); protected Q_SLOTS: void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors); @@ -179,6 +185,10 @@ protected Q_SLOTS: void slotCredentialsFetched(AbstractCredentials *creds); void slotCredentialsAsked(AbstractCredentials *creds); + void slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode); + void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode); + void slotOcsError(int statusCode, const QString &message); + private: AccountPtr _account; State _state; @@ -209,7 +219,34 @@ protected Q_SLOTS: */ RemoteWipe *_remoteWipe; + /** + * Holds the App names and URLs available on the server + */ + AccountAppList _apps; + }; + +class AccountApp : public QObject +{ + Q_OBJECT +public: + AccountApp(const QString &name, const QUrl &url, + const QString &id, const QUrl &iconUrl, + QObject* parent = 0); + + QString name() const; + QUrl url() const; + QString id() const; + QUrl iconUrl() const; + +private: + QString _name; + QUrl _url; + + QString _id; + QUrl _iconUrl; +}; + } Q_DECLARE_METATYPE(OCC::AccountState *) diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp index b62122395b89..f4f37ba1ae74 100644 --- a/src/gui/owncloudgui.cpp +++ b/src/gui/owncloudgui.cpp @@ -14,7 +14,6 @@ #include "application.h" #include "owncloudgui.h" -#include "ocsnavigationappsjob.h" #include "theme.h" #include "folderman.h" #include "progressdispatcher.h" @@ -46,10 +45,6 @@ #include #endif -#include -#include -#include - #include #include #include @@ -68,7 +63,6 @@ ownCloudGui::ownCloudGui(Application *parent) #ifdef WITH_LIBCLOUDPROVIDERS , _bus(QDBusConnection::sessionBus()) #endif - , _recentActionsMenu(nullptr) , _app(parent) { _tray = Systray::instance(); @@ -369,7 +363,6 @@ void ownCloudGui::slotShowOptionalTrayMessage(const QString &title, const QStrin slotShowTrayMessage(title, msg); } - /* * open the folder with the given Alias */ @@ -393,93 +386,6 @@ void ownCloudGui::slotFolderOpenAction(const QString &alias) } } -void ownCloudGui::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){ - if(statusCode == 200){ - qCDebug(lcApplication) << "New navigation apps ETag Response Header received " << value; - auto account = qvariant_cast(sender()->property(propertyAccountC)); - account->setNavigationAppsEtagResponseHeader(value); - } -} - -void ownCloudGui::fetchNavigationApps(AccountStatePtr account){ - OcsNavigationAppsJob *job = new OcsNavigationAppsJob(account->account()); - job->setProperty(propertyAccountC, QVariant::fromValue(account)); - job->addRawHeader("If-None-Match", account->navigationAppsEtagResponseHeader()); - connect(job, &OcsNavigationAppsJob::appsJobFinished, this, &ownCloudGui::slotNavigationAppsFetched); - connect(job, &OcsNavigationAppsJob::etagResponseHeaderReceived, this, &ownCloudGui::slotEtagResponseHeaderReceived); - connect(job, &OcsNavigationAppsJob::ocsError, this, &ownCloudGui::slotOcsError); - job->getNavigationApps(); -} - -void ownCloudGui::buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu){ - auto navLinks = _navApps.value(account); - - _navLinksMenu->clear(); - _navLinksMenu->setEnabled(navLinks.size() > 0); - - if(navLinks.size() > 0){ - // when there is only one account add the nav links above the settings - QAction *actionBefore = _actionSettings; - - // when there is more than one account add the nav links above pause/unpause folder or logout action - if(AccountManager::instance()->accounts().size() > 1){ - foreach(QAction *action, accountMenu->actions()){ - - // pause/unpause folder and logout actions have propertyAccountC - if(auto actionAccount = qvariant_cast(action->property(propertyAccountC))){ - if(actionAccount == account){ - actionBefore = action; - break; - } - } - } - } - - // Create submenu with links - foreach (const QJsonValue &value, navLinks) { - auto navLink = value.toObject(); - QAction *action = new QAction(navLink.value("name").toString(), this); - QUrl href(navLink.value("href").toString()); - connect(action, &QAction::triggered, this, [href] { QDesktopServices::openUrl(href); }); - _navLinksMenu->addAction(action); - } - } -} - -void ownCloudGui::slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode) -{ - if(auto account = qvariant_cast(sender()->property(propertyAccountC))){ - if (statusCode == 304) { - qCWarning(lcApplication) << "Status code " << statusCode << " Not Modified - No new navigation apps."; - } else { - if(!reply.isEmpty()){ - auto element = reply.object().value("ocs").toObject().value("data"); - auto navLinks = element.toArray(); - _navApps.insert(account, navLinks); - } - } - - // TODO see pull #523 - auto accountList = AccountManager::instance()->accounts(); - if(accountList.size() > 1){ - // the list of apps will be displayed under the account that it belongs to - foreach (QMenu *accountMenu, _accountMenus) { - if(accountMenu->title() == account->account()->displayName()){ - buildNavigationAppsMenu(account, accountMenu); - break; - } - } - } else if(accountList.size() == 1){ - //buildNavigationAppsMenu(account, _contextMenu.data()); - } - } -} - -void ownCloudGui::slotOcsError(int statusCode, const QString &message) -{ - emit serverError(statusCode, message); -} - void ownCloudGui::slotUpdateProgress(const QString &folder, const ProgressInfo &progress) { Q_UNUSED(folder); diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h index 9efd34e8b9be..928a0fbd0b97 100644 --- a/src/gui/owncloudgui.h +++ b/src/gui/owncloudgui.h @@ -91,8 +91,6 @@ public slots: void slotOpenPath(const QString &path); void slotAccountStateChanged(); void slotTrayMessageIfServerUnsupported(Account *account); - void slotNavigationAppsFetched(const QJsonDocument &reply, int statusCode); - void slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode); /** @@ -106,9 +104,6 @@ public slots: void slotRemoveDestroyedShareDialogs(); -protected slots: - void slotOcsError(int statusCode, const QString &message); - private slots: void slotLogin(); void slotLogout(); @@ -118,8 +113,6 @@ private slots: private: void setPauseOnAllFoldersHelper(bool pause); - void fetchNavigationApps(AccountStatePtr account); - void buildNavigationAppsMenu(AccountStatePtr account, QMenu *accountMenu); QPointer _tray; QPointer _settingsDialog; @@ -129,8 +122,6 @@ private slots: QDBusConnection _bus; #endif - QMenu *_recentActionsMenu; - QVector _accountMenus; QMap> _shareDialogs; QAction *_actionNewAccountWizard; @@ -138,9 +129,6 @@ private slots: QAction *_actionEstimate; - QMenu *_navLinksMenu; - QMap _navApps; - QList _recentItemsActions; Application *_app; }; From 7b740f5e9a91fb6c814fae6946b3f8ca4260855e Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Wed, 15 Jan 2020 16:30:05 +0100 Subject: [PATCH 085/120] Remove setTalkCapability() from AccountState and filter for Talk upon App list building Since the per Account App list is now being built in AccountState's slotNavigationAppsFetched it's easy to filter for the availability of the Talk app on the server by its ID property upon App list building, thus eliminating the need for an extra fetch job. Signed-off-by: Michael Schuster --- src/gui/accountstate.cpp | 22 ++++------------------ src/gui/accountstate.h | 1 - 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index b9b42eca548d..5651e623f823 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -45,6 +45,7 @@ AccountState::AccountState(AccountPtr account) , _notificationsEtagResponseHeader("*") , _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay , _remoteWipe(new RemoteWipe(_account)) + , _hasTalk(false) { qRegisterMetaType("AccountState*"); @@ -96,23 +97,6 @@ AccountState::State AccountState::state() const return _state; } -void AccountState::setTalkCapability() -{ - QNetworkAccessManager *manager = new QNetworkAccessManager(this); - connect(manager, &QNetworkAccessManager::finished, - this, [=](QNetworkReply *reply) { - if (reply->error()) { - return; - } - //TODO: This is **** dirty, but atm I don't see a capability-way to determine talk integration - _hasTalk = (reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 303) ? true : false; - }); - QNetworkRequest request; - QString url = this->account()->url().toString() + "/apps/spreed"; - request.setUrl(QUrl(url)); - manager->get(request); -} - void AccountState::setState(State state) { if (_state != state) { @@ -320,7 +304,6 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta case ConnectionValidator::Connected: if (_state != Connected) { setState(Connected); - setTalkCapability(); // Get the Apps available on the server. fetchNavigationApps(); @@ -479,6 +462,9 @@ void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int sta navLink.value("id").toString(), QUrl(navLink.value("icon").toString())); _apps << app; + + if(app->id() == QLatin1String("spreed")) + _hasTalk = true; } } } diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index 8bacdcd9c7db..c89c664b0d68 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -167,7 +167,6 @@ public slots: private: void setState(State state); - void setTalkCapability(); void fetchNavigationApps(); signals: From b4b6366ba8aa6eaa19aa37c2e1f00fb2c5ff0178 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Wed, 15 Jan 2020 16:42:06 +0100 Subject: [PATCH 086/120] Add App list menu to Tray UI - Modify Window.qml and Systray to show the App menu upon clicking the App button on the top-right. Fall back to opening the general URL in case no apps are found. - Introduce a new UserAppsModel in UserModel.cpp to access the fetched server Apps from AccountState (propagated down from the User class). Signed-off-by: Michael Schuster --- src/gui/systray.cpp | 9 +++- src/gui/systray.h | 2 +- src/gui/tray/UserModel.cpp | 91 ++++++++++++++++++++++++++++++++++++++ src/gui/tray/UserModel.h | 35 +++++++++++++++ src/gui/tray/Window.qml | 39 ++++++++++++++++ 5 files changed, 173 insertions(+), 3 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index b817054d133a..6becf1662c4c 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -57,12 +57,13 @@ Systray::Systray() _trayEngine = new QQmlEngine; _trayEngine->addImageProvider("avatars", new ImageProvider); _trayEngine->rootContext()->setContextProperty("userModelBackend", UserModel::instance()); + _trayEngine->rootContext()->setContextProperty("appsMenuModelBackend", UserAppsModel::instance()); _trayEngine->rootContext()->setContextProperty("systrayBackend", this); _trayComponent = new QQmlComponent(_trayEngine, QUrl(QStringLiteral("qrc:/qml/src/gui/tray/Window.qml"))); connect(UserModel::instance(), &UserModel::newUserSelected, - this, &Systray::slotChangeActivityModel); + this, &Systray::slotNewUserSelected); connect(AccountManager::instance(), &AccountManager::accountAdded, this, &Systray::showWindow); @@ -77,9 +78,13 @@ void Systray::create() } } -void Systray::slotChangeActivityModel() +void Systray::slotNewUserSelected() { + // Change ActivityModel _trayEngine->rootContext()->setContextProperty("activityModel", UserModel::instance()->currentActivityModel()); + + // Rebuild App list + UserAppsModel::instance()->buildAppList(); } bool Systray::isOpen() diff --git a/src/gui/systray.h b/src/gui/systray.h index 7125c1090216..b491b749fc28 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -70,7 +70,7 @@ class Systray Q_INVOKABLE void showWindow(); public slots: - void slotChangeActivityModel(); + void slotNewUserSelected(); private: static Systray *_instance; diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 2d2da8ec61bb..f367a8e56b60 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -40,6 +40,8 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent) connect(_account.data(), &AccountState::stateChanged, [=]() { if (isConnected()) {slotRefresh();} }); + connect(_account.data(), &AccountState::hasFetchedNavigationApps, + this, &User::slotRebuildNavigationAppList); } void User::slotBuildNotificationDisplay(const ActivityList &list) @@ -138,6 +140,12 @@ void User::slotRefreshNotifications() } } +void User::slotRebuildNavigationAppList() +{ + // Rebuild App list + UserAppsModel::instance()->buildAppList(); +} + void User::slotNotificationRequestFinished(int statusCode) { int row = sender()->property("activityRow").toInt(); @@ -445,6 +453,11 @@ bool User::hasActivities() const return _account->account()->capabilities().hasActivities(); } +AccountAppList User::appList() const +{ + return _account->appList(); +} + bool User::isCurrentUser() const { return _isCurrentUser; @@ -603,6 +616,10 @@ Q_INVOKABLE void UserModel::openCurrentAccountTalk() Q_INVOKABLE void UserModel::openCurrentAccountServer() { + // Don't open this URL when the QML appMenu pops up on click (see Window.qml) + if(appList().count() > 0) + return; + QString url = _users[_currentUserId]->server(false); if (!(url.contains("http://") || url.contains("https://"))) { url = "https://" + _users[_currentUserId]->server(false); @@ -717,6 +734,15 @@ void UserModel::fetchCurrentActivityModel() _users[currentUserId()]->slotRefresh(); } +AccountAppList UserModel::appList() const +{ + if (_users.count() >= 1) { + return _users[_currentUserId]->appList(); + } else { + return AccountAppList(); + } +} + /*-------------------------------------------------------------------------------------*/ ImageProvider::ImageProvider() @@ -734,4 +760,69 @@ QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize & } } +/*-------------------------------------------------------------------------------------*/ + +UserAppsModel *UserAppsModel::_instance = nullptr; + +UserAppsModel *UserAppsModel::instance() +{ + if (_instance == nullptr) { + _instance = new UserAppsModel(); + } + return _instance; +} + +UserAppsModel::UserAppsModel(QObject *parent) + : QAbstractListModel(parent) +{ +} + +void UserAppsModel::buildAppList() +{ + beginRemoveRows(QModelIndex(), 0, rowCount()); + _apps.clear(); + endRemoveRows(); + + if(UserModel::instance()->appList().count() > 0) { + foreach(AccountApp *app, UserModel::instance()->appList()) { + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + _apps << app; + endInsertRows(); + } + } +} + +void UserAppsModel::openAppUrl(const QUrl &url) +{ + QDesktopServices::openUrl(url); +} + +int UserAppsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return _apps.count(); +} + +QVariant UserAppsModel::data(const QModelIndex &index, int role) const +{ + if (index.row() < 0 || index.row() >= _apps.count()) { + return QVariant(); + } + + if (role == NameRole) { + return _apps[index.row()]->name(); + } else if (role == UrlRole) { + return _apps[index.row()]->url(); + } + return QVariant(); +} + +QHash UserAppsModel::roleNames() const +{ + QHash roles; + roles[NameRole] = "appName"; + roles[UrlRole] = "appUrl"; + return roles; +} + } diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index f7d5092f8b9d..14b2b0c231ae 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -32,6 +32,7 @@ class User : public QObject QString server(bool shortened = true) const; bool serverHasTalk() const; bool hasActivities() const; + AccountAppList appList() const; QImage avatar(bool whiteBg = false) const; QString id() const; void login() const; @@ -55,6 +56,7 @@ public slots: void slotRefreshActivities(); void slotRefresh(); void setNotificationRefreshInterval(std::chrono::milliseconds interval); + void slotRebuildNavigationAppList(); private: AccountStatePtr _account; @@ -117,6 +119,8 @@ class UserModel : public QAbstractListModel IdRole }; + AccountAppList appList() const; + signals: Q_INVOKABLE void addAccount(); Q_INVOKABLE void refreshCurrentUserGui(); @@ -142,5 +146,36 @@ class ImageProvider : public QQuickImageProvider QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; }; +class UserAppsModel : public QAbstractListModel +{ + Q_OBJECT +public: + static UserAppsModel *instance(); + virtual ~UserAppsModel() {}; + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + + enum UserAppsRoles { + NameRole = Qt::UserRole + 1, + UrlRole + }; + + void buildAppList(); + +public slots: + void openAppUrl(const QUrl &url); + +protected: + QHash roleNames() const; + +private: + static UserAppsModel *_instance; + UserAppsModel(QObject *parent = 0); + + AccountAppList _apps; +}; + } #endif // USERMODEL_H diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 92f2a848a8f3..1cf9576efab5 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -380,8 +380,47 @@ Window { hoverEnabled: true onClicked: { + /* + // The count() property was introduced in QtQuick.Controls 2.3 (Qt 5.10) + // so we handle this with userModelBackend.openCurrentAccountServer() + // + // See UserModel::openCurrentAccountServer() to disable this workaround + // in the future for Qt >= 5.10 + + if(appsMenu.count() > 0) { + appsMenu.popup(); + } else { + userModelBackend.openCurrentAccountServer(); + } + */ + + appsMenu.open(); userModelBackend.openCurrentAccountServer(); } + + Menu { + id: appsMenu + x: (trayWindowAppsButton.x + 2) + y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2) + width: (trayWindowAppsButton.width - 2) + closePolicy: "CloseOnPressOutside" + + background: Rectangle { + border.color: "#0082c9" + radius: 2 + } + + Instantiator { + id: appsMenuInstantiator + model: appsMenuModelBackend + onObjectAdded: appsMenu.insertItem(index, object) + onObjectRemoved: appsMenu.removeItem(object) + delegate: MenuItem { + text: appName + onTriggered: appsMenuModelBackend.openAppUrl(appUrl) + } + } + } } background: From 42ebc7e9952a50a9f41131c0226e964b26d7388a Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Wed, 15 Jan 2020 18:09:33 +0100 Subject: [PATCH 087/120] Remove debug output Signed-off-by: Michael Schuster --- src/gui/accountstate.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 5651e623f823..3346c18613f2 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -450,7 +450,6 @@ void AccountState::slotNavigationAppsFetched(const QJsonDocument &reply, int sta _hasTalk = false; if(!reply.isEmpty()){ - printf("%s\n", reply.toJson().toStdString().c_str()); auto element = reply.object().value("ocs").toObject().value("data"); auto navLinks = element.toArray(); From 026bf02c85babd59c731fa1b3b867e10376cfaf3 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Wed, 15 Jan 2020 18:14:20 +0100 Subject: [PATCH 088/120] App menu: Filter out Talk because we have a dedicated button for it Signed-off-by: Michael Schuster --- src/gui/tray/UserModel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index f367a8e56b60..08b679e51b67 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -785,6 +785,10 @@ void UserAppsModel::buildAppList() if(UserModel::instance()->appList().count() > 0) { foreach(AccountApp *app, UserModel::instance()->appList()) { + // Filter out Talk because we have a dedicated button for it + if(app->id() == QLatin1String("spreed")) + continue; + beginInsertRows(QModelIndex(), rowCount(), rowCount()); _apps << app; endInsertRows(); From 0ec2291bd747ed02980d40e0d610aa402152468d Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 15 Jan 2020 18:44:33 +0100 Subject: [PATCH 089/120] Layout adjustments & also show remote path in activitylist Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/ActivityListModel.cpp | 6 +++++- src/gui/tray/Window.qml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 964b1f7acd1d..6baf302d0932 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -76,7 +76,11 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); if (list.count() > 0) { - return list.at(0); + if (relPath.startsWith('/') || relPath.startsWith('\\')) { + return relPath.remove(0,1); + } else { + return relPath; + } } } return QString(); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 1cf9576efab5..d16faaddaef1 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -496,6 +496,7 @@ Window { Image { id: activityIcon Layout.leftMargin: 8 + Layout.rightMargin: 8 Layout.preferredWidth: activityButton1.icon.width Layout.preferredHeight: activityButton1.icon.height verticalAlignment: Qt.AlignCenter @@ -506,7 +507,6 @@ Window { } Column { id: activityTextColumn - Layout.leftMargin: 4 spacing: 4 Layout.alignment: Qt.AlignLeft Text { From 9b32ebdae47ad65ce1f6cdfcae395373d29f8785 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 15 Jan 2020 19:15:25 +0100 Subject: [PATCH 090/120] Allow text to expand further if one or both buttons aren't visible Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index d16faaddaef1..9a240398222e 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -512,7 +512,7 @@ Window { Text { id: activityTextTitle text: (type === "Activity") ? subject : message - width: 236 + width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: 12 } @@ -521,7 +521,7 @@ Window { id: activityTextInfo text: displaypath height: (displaypath === "") ? 0 : activityTextTitle.height - width: 236 + width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: 10 } @@ -532,12 +532,12 @@ Window { } Button { id: activityButton1 - Layout.preferredWidth: activityItem.height + Layout.preferredWidth: (path === "") ? 0 : activityItem.height Layout.preferredHeight: activityItem.height Layout.alignment: Qt.AlignRight flat: true hoverEnabled: false - visible: (path !== "") ? true : false + visible: (path === "") ? false : true display: AbstractButton.IconOnly icon.source: "qrc:///client/resources/files.svg" icon.color: "transparent" @@ -547,12 +547,13 @@ Window { } } Button { - Layout.preferredWidth: activityItem.height - Layout.preferredHeight: activityItem.height + id: activityButton2 + Layout.preferredWidth: (link === "") ? 0 : activityItem.height + Layout.preferredHeight: activityItem.height Layout.alignment: Qt.AlignRight flat: true hoverEnabled: false - visible: (link !== "") ? true : false + visible: (link === "") ? false : true display: AbstractButton.IconOnly icon.source: "qrc:///client/resources/public.svg" icon.color: "transparent" From ddf67ff948eba29de176cffec4a195ad9568b9df Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 15 Jan 2020 19:28:40 +0100 Subject: [PATCH 091/120] Preserve a 2px distance from tray border (streamlining with acount menu) Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 9a240398222e..d09a2709b31f 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -400,7 +400,6 @@ Window { Menu { id: appsMenu - x: (trayWindowAppsButton.x + 2) y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2) width: (trayWindowAppsButton.width - 2) closePolicy: "CloseOnPressOutside" From 00d73a938949f1dd9a26518948502cff8eda995b Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Wed, 15 Jan 2020 20:11:50 +0100 Subject: [PATCH 092/120] Add visual separation between accountStateIndicator and avatar Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 17 +++++++++++++++-- src/gui/tray/Window.qml | 16 ++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index 0c2025032e80..aa7d4a4228bf 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -20,6 +20,10 @@ MenuItem { display: AbstractButton.IconOnly flat: true + onHoveredChanged: { + accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white") + } + background: Rectangle { color: "transparent" } @@ -45,12 +49,21 @@ MenuItem { source: ("image://avatars/" + id) Layout.preferredHeight: (userLineLayout.height -16) Layout.preferredWidth: (userLineLayout.height -16) + Rectangle { + id: accountStateIndicatorBackground + width: accountStateIndicator.sourceSize.width + 2 + height: width + anchors.bottom: accountAvatar.bottom + anchors.right: accountAvatar.right + color: "white" + radius: width*0.5 + } Image { id: accountStateIndicator source: isConnected ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" cache: false - anchors.bottom: accountAvatar.bottom - anchors.right: accountAvatar.right + x: accountStateIndicatorBackground.x + 1 + y: accountStateIndicatorBackground.y + 1 sourceSize.width: 16 sourceSize.height: 16 } diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index d09a2709b31f..53b813343c6a 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -107,6 +107,9 @@ Window { id: accountBtnMouseArea anchors.fill: parent hoverEnabled: true + onContainsMouseChanged: { + currentAccountStateIndicatorBackground.color = (containsMouse ? "#009dd9" : "#0082c9") + } onClicked: { syncPauseButton.text = systrayBackend.syncIsPaused() ? qsTr("Resume sync for all") : qsTr("Pause sync for all") @@ -255,12 +258,21 @@ Window { source: "image://avatars/currentUser" Layout.preferredHeight: (trayWindowHeaderBackground.height -16) Layout.preferredWidth: (trayWindowHeaderBackground.height -16) + Rectangle { + id: currentAccountStateIndicatorBackground + width: currentAccountStateIndicator.sourceSize.width + 2 + height: width + anchors.bottom: currentAccountAvatar.bottom + anchors.right: currentAccountAvatar.right + color: "#0082c9" + radius: width*0.5 + } Image { id: currentAccountStateIndicator source: userModelBackend.isUserConnected(userModelBackend.currentUserId()) ? "qrc:///client/theme/colored/state-ok.svg" : "qrc:///client/theme/colored/state-offline.svg" cache: false - anchors.bottom: currentAccountAvatar.bottom - anchors.right: currentAccountAvatar.right + x: currentAccountStateIndicatorBackground.x + 1 + y: currentAccountStateIndicatorBackground.y + 1 sourceSize.width: 16 sourceSize.height: 16 } From 3488fd7c7fb63845676b84bf62da5dba1ddcc005 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Thu, 16 Jan 2020 00:45:31 +0100 Subject: [PATCH 093/120] Fix font sizes for "Log out" and "Remove account" and the Apps menu Signed-off-by: Michael Schuster --- src/gui/tray/UserLine.qml | 2 ++ src/gui/tray/Window.qml | 1 + 2 files changed, 3 insertions(+) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index aa7d4a4228bf..afa0a28f60fe 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -132,6 +132,7 @@ MenuItem { MenuItem { text: isConnected ? qsTr("Log out") : qsTr("Log in") + font.pixelSize: 12 onClicked: { isConnected ? userModelBackend.logout(index) : userModelBackend.login(index) accountMenu.close() @@ -140,6 +141,7 @@ MenuItem { MenuItem { text: qsTr("Remove Account") + font.pixelSize: 12 onClicked: { userModelBackend.removeAccount(index) accountMenu.close() diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 53b813343c6a..db4c4427d2e1 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -428,6 +428,7 @@ Window { onObjectRemoved: appsMenu.removeItem(object) delegate: MenuItem { text: appName + font.pixelSize: 12 onTriggered: appsMenuModelBackend.openAppUrl(appUrl) } } From 7fe138330e11a5c8a8e684eb46631749a5fc8123 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Thu, 16 Jan 2020 00:57:49 +0100 Subject: [PATCH 094/120] Double the width of the Apps menu (x starts at the Talk button) Signed-off-by: Michael Schuster --- src/gui/tray/Window.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index db4c4427d2e1..45f1c52aaa72 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -413,7 +413,7 @@ Window { Menu { id: appsMenu y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2) - width: (trayWindowAppsButton.width - 2) + width: (trayWindowAppsButton.width * 2) closePolicy: "CloseOnPressOutside" background: Rectangle { From 246f7e93955159e87fdcb585f3947251580350cf Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 08:01:09 +0100 Subject: [PATCH 095/120] 1px border #0082c9 around tray window Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 45f1c52aaa72..a7a16742c590 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -73,6 +73,7 @@ Window { id: trayWindowBackground anchors.fill: parent radius: 10 + border.color: "#0082c9" Rectangle { id: trayWindowHeaderBackground From db92d5bfffc93f58cf3b35f1198cb43c5cd91dc0 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 10:39:56 +0100 Subject: [PATCH 096/120] append the correct folderwatcher (platform specific) Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 55e5327e6887..39bd4dcfc03e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -80,7 +80,7 @@ list(APPEND RemoteWipe_SRC ../src/gui/accountstate.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/socketapi.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/folder.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/syncrunfilelog.cpp ) -list(APPEND RemoteWipe_SRC ../src/gui/folderwatcher_linux.cpp ) +list(APPEND RemoteWipe_SRC ${FolderWatcher_SRC} ) list(APPEND RemoteWipe_SRC ../src/gui/folderwatcher.cpp ) list(APPEND RemoteWipe_SRC ${RemoteWipe_SRC}) list(APPEND RemoteWipe_SRC stubremotewipe.cpp ) From d7a9940973c0c7d03430ff365e9ef17dcbd4e701 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 10:53:43 +0100 Subject: [PATCH 097/120] Fix failing of several tests by including ocsjob source (after its inclusion in accountstate in a previous commit) Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- test/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 39bd4dcfc03e..501ec9039af5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -66,6 +66,8 @@ list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp ) list(APPEND FolderMan_SRC ../src/gui/navigationpanehelper.cpp ) list(APPEND FolderMan_SRC ../src/gui/connectionvalidator.cpp ) list(APPEND FolderMan_SRC ../src/gui/clientproxy.cpp ) +list(APPEND FolderMan_SRC ../src/gui/ocsjob.cpp ) +list(APPEND FolderMan_SRC ../src/gui/ocsnavigationappsjob.cpp ) list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp ) list(APPEND FolderMan_SRC ../src/gui/remotewipe.cpp ) list(APPEND FolderMan_SRC ${FolderWatcher_SRC}) @@ -76,6 +78,8 @@ SET(RemoteWipe_SRC ../src/gui/remotewipe.cpp) list(APPEND RemoteWipe_SRC ../src/gui/clientproxy.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/guiutility.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/connectionvalidator.cpp ) +list(APPEND RemoteWipe_SRC ../src/gui/ocsjob.cpp ) +list(APPEND RemoteWipe_SRC ../src/gui/ocsnavigationappsjob.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/accountstate.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/socketapi.cpp ) list(APPEND RemoteWipe_SRC ../src/gui/folder.cpp ) From 9f259bda2734d73ae6d5c4f9b34b47fcf590266b Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 11:30:51 +0100 Subject: [PATCH 098/120] Add check if applist rowCount() already empty to prevent assert exception Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 08b679e51b67..ae9b42653f58 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -779,9 +779,11 @@ UserAppsModel::UserAppsModel(QObject *parent) void UserAppsModel::buildAppList() { - beginRemoveRows(QModelIndex(), 0, rowCount()); - _apps.clear(); - endRemoveRows(); + if (rowCount() > 0) { + beginRemoveRows(QModelIndex(), 0, rowCount() - 1); + _apps.clear(); + endRemoveRows(); + } if(UserModel::instance()->appList().count() > 0) { foreach(AccountApp *app, UserModel::instance()->appList()) { From 06fdde8f1e0851aa0d57aaa5367ba370632b5d07 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 12:43:04 +0100 Subject: [PATCH 099/120] Upgrade of NC server API usage + corresponding activity queries and properties including icon ref Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/ActivityData.h | 2 ++ src/gui/tray/ActivityListModel.cpp | 14 +++++++++----- src/gui/tray/NotificationHandler.cpp | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/gui/tray/ActivityData.h b/src/gui/tray/ActivityData.h index d2f3c864f43e..d912a7e3c661 100644 --- a/src/gui/tray/ActivityData.h +++ b/src/gui/tray/ActivityData.h @@ -56,6 +56,7 @@ class Activity Type _type; qlonglong _id; + QString _fileAction; QString _objectType; QString _subject; QString _message; @@ -64,6 +65,7 @@ class Activity QUrl _link; QDateTime _dateTime; QString _accName; + QString _icon; // Stores information about the error int _status; diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 6baf302d0932..701c543eed90 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -203,13 +203,14 @@ void ActivityListModel::startFetchJob() if (!_accountState->isConnected()) { return; } - JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/cloud/activity"), this); + //JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/cloud/activity"), this); + JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this); QObject::connect(job, &JsonApiJob::jsonReceived, this, &ActivityListModel::slotActivitiesReceived); QUrlQuery params; - params.addQueryItem(QLatin1String("start"), QString::number(_currentItem)); - params.addQueryItem(QLatin1String("count"), QString::number(100)); + params.addQueryItem(QLatin1String("since"), QString::number(_currentItem)); + params.addQueryItem(QLatin1String("limit"), QString::number(50)); job->addQueryParams(params); _currentlyFetching = true; @@ -239,13 +240,16 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st Activity a; a._type = Activity::ActivityType; + a._objectType = json.value("object_type").toString(); a._accName = ast->account()->displayName(); a._id = json.value("id").toInt(); + a._fileAction = json.value("type").toString(); a._subject = json.value("subject").toString(); a._message = json.value("message").toString(); - a._file = json.value("file").toString(); + a._file = json.value("object_name").toString(); a._link = QUrl(json.value("link").toString()); - a._dateTime = QDateTime::fromString(json.value("date").toString(), Qt::ISODate); + a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate); + a._icon = json.value("icon").toString(); list.append(a); } diff --git a/src/gui/tray/NotificationHandler.cpp b/src/gui/tray/NotificationHandler.cpp index f1594b4dd683..ff218f9258b4 100644 --- a/src/gui/tray/NotificationHandler.cpp +++ b/src/gui/tray/NotificationHandler.cpp @@ -102,6 +102,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j a._subject = json.value("subject").toString(); a._message = json.value("message").toString(); + a._icon = json.value("icon").toString(); if (!json.value("icon").toString().isEmpty()) { IconJob *iconJob = new IconJob(QUrl(json.value("icon").toString())); From cd80c749d66596ab13d9c4ffb74509b1ab042eda Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 14:01:54 +0100 Subject: [PATCH 100/120] Retreive and svg data from icon property url for activity entries Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/iconjob.cpp | 1 + src/gui/tray/ActivityData.h | 1 + src/gui/tray/ActivityListModel.cpp | 26 ++++++++++++++++++++++++-- src/gui/tray/ActivityListModel.h | 1 + src/gui/tray/NotificationHandler.cpp | 12 +++++------- src/gui/tray/NotificationHandler.h | 2 +- 6 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/gui/iconjob.cpp b/src/gui/iconjob.cpp index c77a925b18a4..69f84d4bd791 100644 --- a/src/gui/iconjob.cpp +++ b/src/gui/iconjob.cpp @@ -23,6 +23,7 @@ IconJob::IconJob(const QUrl &url, QObject *parent) : this, &IconJob::finished); QNetworkRequest request(url); + request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); _accessManager.get(request); } diff --git a/src/gui/tray/ActivityData.h b/src/gui/tray/ActivityData.h index d912a7e3c661..f2fc0e345676 100644 --- a/src/gui/tray/ActivityData.h +++ b/src/gui/tray/ActivityData.h @@ -66,6 +66,7 @@ class Activity QDateTime _dateTime; QString _accName; QString _icon; + QString _iconData; // Stores information about the error int _status; diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 701c543eed90..88f967f6df8e 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -22,6 +22,7 @@ #include "accountstate.h" #include "accountmanager.h" #include "folderman.h" +#include "iconjob.h" #include "accessmanager.h" #include "ActivityData.h" @@ -77,7 +78,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const list = FolderMan::instance()->findFileInLocalFolders(relPath, ast->account()); if (list.count() > 0) { if (relPath.startsWith('/') || relPath.startsWith('\\')) { - return relPath.remove(0,1); + return relPath.remove(0, 1); } else { return relPath; } @@ -135,6 +136,11 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const return "qrc:///client/theme/black/state-sync.svg"; } } else { + // We have an activity + if (!a._iconData.isEmpty()) { + QString svgData = "data:image/svg+xml;utf8," + a._iconData; + return svgData; + } return "qrc:///client/theme/black/activity.svg"; } } @@ -242,7 +248,7 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st a._type = Activity::ActivityType; a._objectType = json.value("object_type").toString(); a._accName = ast->account()->displayName(); - a._id = json.value("id").toInt(); + a._id = json.value("activity_id").toInt(); a._fileAction = json.value("type").toString(); a._subject = json.value("subject").toString(); a._message = json.value("message").toString(); @@ -250,6 +256,13 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st a._link = QUrl(json.value("link").toString()); a._dateTime = QDateTime::fromString(json.value("datetime").toString(), Qt::ISODate); a._icon = json.value("icon").toString(); + + if (!a._icon.isEmpty()) { + IconJob *iconJob = new IconJob(QUrl(a._icon)); + iconJob->setProperty("activityId", a._id); + connect(iconJob, &IconJob::jobFinished, this, &ActivityListModel::slotIconDownloaded); + } + list.append(a); } @@ -260,6 +273,15 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st combineActivityLists(); } +void ActivityListModel::slotIconDownloaded(QByteArray iconData) +{ + for (size_t i = 0; i < _activityLists.count(); i++) { + if (_activityLists[i]._id == sender()->property("activityId").toLongLong()) { + _activityLists[i]._iconData = iconData; + } + } +} + void ActivityListModel::addErrorToActivityList(Activity activity) { qCInfo(lcActivity) << "Error successfully added to the notification list: " << activity._subject; diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index f073504a0034..c55797d7024b 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -78,6 +78,7 @@ public slots: private slots: void slotActivitiesReceived(const QJsonDocument &json, int statusCode); + void slotIconDownloaded(QByteArray iconData); signals: void activityJobStatusCode(int statusCode); diff --git a/src/gui/tray/NotificationHandler.cpp b/src/gui/tray/NotificationHandler.cpp index ff218f9258b4..4571f9a9dfe1 100644 --- a/src/gui/tray/NotificationHandler.cpp +++ b/src/gui/tray/NotificationHandler.cpp @@ -17,7 +17,7 @@ const QString notificationsPath = QLatin1String("ocs/v2.php/apps/notifications/a const char propertyAccountStateC[] = "oc_account_state"; const int successStatusCode = 200; const int notModifiedStatusCode = 304; -QMap ServerNotificationHandler::iconCache; +QMap ServerNotificationHandler::iconCache; ServerNotificationHandler::ServerNotificationHandler(AccountState *accountState, QObject *parent) : QObject(parent) @@ -64,9 +64,7 @@ void ServerNotificationHandler::slotEtagResponseHeaderReceived(const QByteArray void ServerNotificationHandler::slotIconDownloaded(QByteArray iconData) { - QPixmap pixmap; - pixmap.loadFromData(iconData); - iconCache.insert(sender()->property("activityId").toInt(), QIcon(pixmap)); + iconCache.insert(sender()->property("activityId").toInt(),iconData); } void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &json, int statusCode) @@ -94,7 +92,7 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j auto json = element.toObject(); a._type = Activity::NotificationType; a._accName = ai->account()->displayName(); - a._id = json.value("notification_id").toInt(); + a._id = json.value("activity_id").toInt(); //need to know, specially for remote_share a._objectType = json.value("object_type").toString(); @@ -104,8 +102,8 @@ void ServerNotificationHandler::slotNotificationsReceived(const QJsonDocument &j a._message = json.value("message").toString(); a._icon = json.value("icon").toString(); - if (!json.value("icon").toString().isEmpty()) { - IconJob *iconJob = new IconJob(QUrl(json.value("icon").toString())); + if (!a._icon.isEmpty()) { + IconJob *iconJob = new IconJob(QUrl(a._icon)); iconJob->setProperty("activityId", a._id); connect(iconJob, &IconJob::jobFinished, this, &ServerNotificationHandler::slotIconDownloaded); } diff --git a/src/gui/tray/NotificationHandler.h b/src/gui/tray/NotificationHandler.h index 01caef428e1d..69e286e783e3 100644 --- a/src/gui/tray/NotificationHandler.h +++ b/src/gui/tray/NotificationHandler.h @@ -14,7 +14,7 @@ class ServerNotificationHandler : public QObject Q_OBJECT public: explicit ServerNotificationHandler(AccountState *accountState, QObject *parent = nullptr); - static QMap iconCache; + static QMap iconCache; signals: void newNotificationList(ActivityList); From b9a350e201e4d69e7ae832158977f1c260465b64 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 15:19:17 +0100 Subject: [PATCH 101/120] Minor layout adjustments Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/systray.cpp | 4 ++-- src/gui/tray/ActivityListModel.h | 2 +- src/gui/tray/Window.qml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index 6becf1662c4c..be0253ddba64 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -194,11 +194,11 @@ int Systray::calcTrayWindowY() return (screenHeight - availableHeight) + 6; } else { // on bottom - return screenHeight - 500 - (screenHeight - availableHeight) - 6; + return screenHeight - 510 - (screenHeight - availableHeight) - 6; } } else { // on the left or right - return (trayIconTopCenterY - 500 + 12); + return (trayIconTopCenterY - 510 + 12); } #endif } diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index c55797d7024b..e6e68cba4091 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -52,7 +52,7 @@ class ActivityListModel : public QAbstractListModel LinkRole, PointInTimeRole, AccountConnectedRole, - SyncFileStatusRole }; + SyncFileStatusRole}; explicit ActivityListModel(AccountState *accountState, QObject* parent = 0); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index a7a16742c590..6b922e2c18d2 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -11,7 +11,7 @@ Window { id: trayWindow visible: true width: 400 - height: 500 + height: 510 color: "transparent" flags: Qt.FramelessWindowHint From 1bc8ec59aa3ab129bbd6ec010db78bb003b8ab5b Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 17:09:34 +0100 Subject: [PATCH 102/120] Streamlining local sync notification/activity icons with server ones. Added fileAction logic for local sync activities Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- client.qrc | 3 +++ resources/add-color.svg | 1 + resources/change.svg | 1 + resources/delete-color.svg | 1 + src/gui/tray/ActivityListModel.cpp | 10 ++++++++-- src/gui/tray/UserModel.cpp | 25 +++++++++++++++++++++++-- 6 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 resources/add-color.svg create mode 100644 resources/change.svg create mode 100644 resources/delete-color.svg diff --git a/client.qrc b/client.qrc index de385dfb20ac..bb284644a91f 100644 --- a/client.qrc +++ b/client.qrc @@ -30,7 +30,10 @@ resources/copy.svg resources/state-sync.svg resources/add.png + resources/add-color.svg resources/state-info.svg + resources/change.svg + resources/delete-color.svg diff --git a/resources/add-color.svg b/resources/add-color.svg new file mode 100644 index 000000000000..cb4596ac1e56 --- /dev/null +++ b/resources/add-color.svg @@ -0,0 +1 @@ + diff --git a/resources/change.svg b/resources/change.svg new file mode 100644 index 000000000000..12071422b7f9 --- /dev/null +++ b/resources/change.svg @@ -0,0 +1 @@ + diff --git a/resources/delete-color.svg b/resources/delete-color.svg new file mode 100644 index 000000000000..72aed6b937da --- /dev/null +++ b/resources/delete-color.svg @@ -0,0 +1 @@ + diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 88f967f6df8e..dd724acb0ed6 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -133,7 +133,14 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } else if (a._status == SyncFileItem::FileIgnored) { return "qrc:///client/theme/black/state-info.svg"; } else { - return "qrc:///client/theme/black/state-sync.svg"; + // File sync successful + if (a._fileAction == "file_created") { + return "qrc:///client/resources/add-color.svg"; + } else if (a._fileAction == "file_deleted") { + return "qrc:///client/resources/delete-color.svg"; + } else { + return "qrc:///client/resources/change.svg"; + } } } else { // We have an activity @@ -209,7 +216,6 @@ void ActivityListModel::startFetchJob() if (!_accountState->isConnected()) { return; } - //JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/cloud/activity"), this); JsonApiJob *job = new JsonApiJob(_accountState->account(), QLatin1String("ocs/v2.php/apps/activity/api/v2/activity"), this); QObject::connect(job, &JsonApiJob::jsonReceived, this, &ActivityListModel::slotActivitiesReceived); diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index ae9b42653f58..c0163876ce61 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -341,11 +341,32 @@ void User::slotItemCompleted(const QString &folder, const SyncFileItemPtr &item) activity._accName = folderInstance->accountState()->account()->displayName(); activity._file = item->_file; activity._folder = folder; + activity._fileAction = ""; + + if (item->_instruction == CSYNC_INSTRUCTION_REMOVE) { + activity._fileAction = "file_deleted"; + } else if (item->_instruction == CSYNC_INSTRUCTION_NEW) { + activity._fileAction = "file_created"; + } else if (item->_instruction == CSYNC_INSTRUCTION_RENAME) { + activity._fileAction = "file_renamed"; + } else { + activity._fileAction = "file_changed"; + } + if (item->_status == SyncFileItem::NoStatus || item->_status == SyncFileItem::Success) { qCWarning(lcActivity) << "Item " << item->_file << " retrieved successfully."; - activity._message.prepend(" "); - activity._message.prepend(tr("Synced")); + + if (activity._fileAction == "file_renamed") { + activity._message.prepend(tr("You renamed") + " "); + } else if (activity._fileAction == "file_deleted") { + activity._message.prepend(tr("You deleted") + " "); + } else if (activity._fileAction == "file_created") { + activity._message.prepend(tr("You created") + " "); + } else { + activity._message.prepend(tr("You changed") + " "); + } + _activityModel->addSyncFileItemToActivityList(activity); } else { qCWarning(lcActivity) << "Item " << item->_file << " retrieved resulted in error " << item->_errorString; From 4b79c597dc5f5bdf28ea042e4d3b64d135af65ec Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 17:11:08 +0100 Subject: [PATCH 103/120] Disabled animations until population glitches are fixed Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 6b922e2c18d2..86a6ee39d0b8 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -577,11 +577,7 @@ Window { } } - populate: Transition { - // prevent animations on initial list population - } - - add: Transition { + /*add: Transition { NumberAnimation { properties: "y"; from: -60; duration: 100; easing.type: Easing.Linear } } @@ -598,7 +594,7 @@ Window { displaced: Transition { NumberAnimation { properties: "y"; duration: 100; easing.type: Easing.Linear } - } + }*/ } } // Rectangle trayWindowBackground From 585094a71957f852be8cbc9d976e6a54efdfbbef Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 17:40:47 +0100 Subject: [PATCH 104/120] Minor property hierarchy fix Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 86a6ee39d0b8..a13d0b385770 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -499,12 +499,13 @@ Window { model: activityModel + visible: (model.rowCount() > 0) + delegate: RowLayout { id: activityItem width: activityListView.width height: trayWindowHeaderLayout.height spacing: 0 - visible: (activityListView.model.rowCount() > 0) Image { id: activityIcon From 94325359d7eefd7457b56bd905e21eca90f25192 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Thu, 16 Jan 2020 18:01:18 +0100 Subject: [PATCH 105/120] Fixed bug regarding additional but empty delegate at end of activitylist Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/ActivityListModel.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index dd724acb0ed6..543fd188bbaf 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -404,9 +404,11 @@ void ActivityListModel::combineActivityLists() _finalList.clear(); endResetModel(); - beginInsertRows(QModelIndex(), 0, resultList.count()); - _finalList = resultList; - endInsertRows(); + if (resultList.count() > 0) { + beginInsertRows(QModelIndex(), 0, resultList.count() - 1); + _finalList = resultList; + endInsertRows(); + } } bool ActivityListModel::canFetchActivities() const From 9abc1eb92170888a260bf899b52c267b5faa5153 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Thu, 16 Jan 2020 18:43:48 +0100 Subject: [PATCH 106/120] Add initial support for Apps menu icon display (show remote SVGs) Caching and showing them from local data-URLs leaded to blurry rendering in Qt. Signed-off-by: Michael Schuster --- src/gui/tray/UserModel.cpp | 3 +++ src/gui/tray/UserModel.h | 3 ++- src/gui/tray/Window.qml | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index c0163876ce61..7fee90aa7838 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -840,6 +840,8 @@ QVariant UserAppsModel::data(const QModelIndex &index, int role) const return _apps[index.row()]->name(); } else if (role == UrlRole) { return _apps[index.row()]->url(); + } else if (role == IconUrlRole) { + return _apps[index.row()]->iconUrl().toString(); } return QVariant(); } @@ -849,6 +851,7 @@ QHash UserAppsModel::roleNames() const QHash roles; roles[NameRole] = "appName"; roles[UrlRole] = "appUrl"; + roles[IconUrlRole] = "appIconUrl"; return roles; } diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 14b2b0c231ae..db5807043713 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -159,7 +159,8 @@ class UserAppsModel : public QAbstractListModel enum UserAppsRoles { NameRole = Qt::UserRole + 1, - UrlRole + UrlRole, + IconUrlRole }; void buildAppList(); diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index a13d0b385770..9f4b8f5d3fd4 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -414,7 +414,7 @@ Window { Menu { id: appsMenu y: (trayWindowAppsButton.y + trayWindowAppsButton.height + 2) - width: (trayWindowAppsButton.width * 2) + width: (trayWindowAppsButton.width * 3) closePolicy: "CloseOnPressOutside" background: Rectangle { @@ -430,6 +430,7 @@ Window { delegate: MenuItem { text: appName font.pixelSize: 12 + icon.source: appIconUrl onTriggered: appsMenuModelBackend.openAppUrl(appUrl) } } From 1c22070a7420ddf7a730256ebf4eb3499e38a571 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 17 Jan 2020 09:04:24 +0100 Subject: [PATCH 107/120] Refresh immediately on showWindow, regardless of the timer Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserModel.cpp | 7 +++++++ src/gui/tray/UserModel.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 7fee90aa7838..9a6c9e312161 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -98,6 +98,13 @@ void User::setNotificationRefreshInterval(std::chrono::milliseconds interval) _notificationCheckTimer.start(interval.count()); } +void User::slotRefreshImmediately() { + if (_account.data() && _account.data()->isConnected()) { + this->slotRefreshActivities(); + } + this->slotRefreshNotifications(); +} + void User::slotRefresh() { // QElapsedTimer isn't actually constructed as invalid. diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index db5807043713..01ebb4ffda38 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -55,6 +55,7 @@ public slots: void slotRefreshNotifications(); void slotRefreshActivities(); void slotRefresh(); + void slotRefreshImmediately(); void setNotificationRefreshInterval(std::chrono::milliseconds interval); void slotRebuildNavigationAppList(); From 7ea2f3fa59cc03fbc69a74d40aa0ef46706cf580 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 17 Jan 2020 09:09:42 +0100 Subject: [PATCH 108/120] Refresh visibility of ListView based on presence of items on showWindow Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 9f4b8f5d3fd4..fb7c20cd351b 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -62,6 +62,7 @@ Window { trayWindow.setY( systrayBackend.calcTrayWindowY()); systrayBackend.setOpened(); userModelBackend.fetchCurrentActivityModel(); + activityListView.visible = (activityListView.model.rowCount() > 0) } onHideWindow: { trayWindow.hide(); From f425dfdfea7b3672f7afb280d0f1c00725c5551a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 17 Jan 2020 10:38:21 +0100 Subject: [PATCH 109/120] Set sourceSize to prevent blurry rerendering of svg source in activityIcon Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index fb7c20cd351b..6730f0c9025d 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -518,8 +518,8 @@ Window { verticalAlignment: Qt.AlignCenter cache: true source: icon - sourceSize.height: activityButton1.icon.height - sourceSize.width: activityButton1.icon.width + sourceSize.height: 64 + sourceSize.width: 64 } Column { id: activityTextColumn From 0156edf320fddccd75c4ad38d9fbd9e9379d55e7 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 17 Jan 2020 11:21:43 +0100 Subject: [PATCH 110/120] Removed visibility workaround Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 6730f0c9025d..6423e1bc5d48 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -62,7 +62,6 @@ Window { trayWindow.setY( systrayBackend.calcTrayWindowY()); systrayBackend.setOpened(); userModelBackend.fetchCurrentActivityModel(); - activityListView.visible = (activityListView.model.rowCount() > 0) } onHideWindow: { trayWindow.hide(); @@ -501,8 +500,6 @@ Window { model: activityModel - visible: (model.rowCount() > 0) - delegate: RowLayout { id: activityItem width: activityListView.width From 1c5cdc4d36833297a6d9b9b7bc2af7b3ea2116c0 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 17 Jan 2020 14:29:54 +0100 Subject: [PATCH 111/120] Adapted currentId logic for activity fetching to new API (should fix endless fetch bug) Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/ActivityListModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 543fd188bbaf..75200a9395db 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -245,7 +245,6 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st } _currentlyFetching = false; - _currentItem += activities.size(); foreach (auto activ, activities) { auto json = activ.toObject(); @@ -270,6 +269,7 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st } list.append(a); + _currentItem = list.last()._id; } _activityLists.append(list); From 599dae0486704c57e601f9a5456b22795aae793a Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 17 Jan 2020 14:50:05 +0100 Subject: [PATCH 112/120] Inclusion of possible 'notification' type descriptions in activity Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/Window.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 6423e1bc5d48..f3fed155a960 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -524,7 +524,7 @@ Window { Layout.alignment: Qt.AlignLeft Text { id: activityTextTitle - text: (type === "Activity") ? subject : message + text: (type === "Activity" || type === "Notification") ? subject : message width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: 12 @@ -532,8 +532,8 @@ Window { Text { id: activityTextInfo - text: displaypath - height: (displaypath === "") ? 0 : activityTextTitle.height + text: (type === "Activity" || type === "File" || type === "Sync") ? displaypath : message + height: (text === "") ? 0 : activityTextTitle.height width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: 10 From c9719f44dc913dd362a93d11b80c2b2dcc672dc2 Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Fri, 17 Jan 2020 16:30:27 +0100 Subject: [PATCH 113/120] Fix hover property wrongly placed in button, not MouseArea Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/tray/UserLine.qml | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/gui/tray/UserLine.qml b/src/gui/tray/UserLine.qml index afa0a28f60fe..357be6005246 100644 --- a/src/gui/tray/UserLine.qml +++ b/src/gui/tray/UserLine.qml @@ -20,22 +20,25 @@ MenuItem { display: AbstractButton.IconOnly flat: true - onHoveredChanged: { - accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white") + MouseArea { + anchors.fill: parent + hoverEnabled: true + onContainsMouseChanged: { + accountStateIndicatorBackground.color = (containsMouse ? "#f6f6f6" : "white") + } + onClicked: { + if (!isCurrentUser) { + userModelBackend.switchCurrentUser(id) + } else { + accountMenu.close() + } + } } background: Rectangle { color: "transparent" } - onClicked: { - if (!isCurrentUser) { - userModelBackend.switchCurrentUser(id) - } else { - accountMenu.close() - } - } - RowLayout { id: accountControlRowLayout height: accountButton.height From 889cb636d38badcdbe17a25aa4a689bfb3929c2f Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Fri, 17 Jan 2020 20:16:20 +0100 Subject: [PATCH 114/120] Add Activity date and time Signed-off-by: Michael Schuster --- src/gui/tray/ActivityListModel.cpp | 3 ++- src/gui/tray/Window.qml | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 75200a9395db..dc1b109f6721 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -51,6 +51,7 @@ QHash ActivityListModel::roleNames() const roles[ActionIconRole] = "icon"; roles[ActionTextRole] = "subject"; roles[ObjectTypeRole] = "objectType"; + roles[PointInTimeRole] = "dateTime"; return roles; } @@ -184,7 +185,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const case AccountRole: return a._accName; case PointInTimeRole: - return QString("%1 (%2)").arg(a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate), Utility::timeAgoInWords(a._dateTime.toLocalTime())); + return QString("%1 - %2").arg(Utility::timeAgoInWords(a._dateTime.toLocalTime()), a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate)); case AccountConnectedRole: return (ast && ast->isConnected()); default: diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index f3fed155a960..9fd16c82f0f7 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -538,6 +538,16 @@ Window { elide: Text.ElideRight font.pixelSize: 10 } + + Text { + id: activityTextDateTime + text: dateTime + height: (text === "") ? 0 : activityTextTitle.height + width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 + elide: Text.ElideRight + font.pixelSize: 10 + color: "#808080" + } } Item { id: activityItemFiller From b47adb7aebea838d4b4106cfb211e9c447db9d7e Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Fri, 17 Jan 2020 20:20:37 +0100 Subject: [PATCH 115/120] Notifications bug fix: Don't init ETag response header with * Signed-off-by: Michael Schuster --- src/gui/accountstate.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 3346c18613f2..899a7728cc16 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -42,7 +42,6 @@ AccountState::AccountState(AccountPtr account) , _state(AccountState::Disconnected) , _connectionStatus(ConnectionValidator::Undefined) , _waitingForNewCredentials(false) - , _notificationsEtagResponseHeader("*") , _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay , _remoteWipe(new RemoteWipe(_account)) , _hasTalk(false) From 3ae55c2555fcbf1633a9b8f74b19c92f4c414624 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Fri, 17 Jan 2020 20:21:42 +0100 Subject: [PATCH 116/120] AccountState: Add helper to find navigation App Signed-off-by: Michael Schuster --- src/gui/accountstate.cpp | 12 ++++++++++++ src/gui/accountstate.h | 1 + 2 files changed, 13 insertions(+) diff --git a/src/gui/accountstate.cpp b/src/gui/accountstate.cpp index 899a7728cc16..1491db714eed 100644 --- a/src/gui/accountstate.cpp +++ b/src/gui/accountstate.cpp @@ -477,6 +477,18 @@ AccountAppList AccountState::appList() const return _apps; } +AccountApp* AccountState::findApp(const QString &appId) const +{ + if(!appId.isEmpty()) { + foreach(AccountApp *app, appList()) { + if(app->id() == appId) + return app; + } + } + + return nullptr; +} + /*-------------------------------------------------------------------------------------*/ AccountApp::AccountApp(const QString &name, const QUrl &url, diff --git a/src/gui/accountstate.h b/src/gui/accountstate.h index c89c664b0d68..433fb51161b7 100644 --- a/src/gui/accountstate.h +++ b/src/gui/accountstate.h @@ -106,6 +106,7 @@ class AccountState : public QObject, public QSharedData bool hasTalk() const; AccountAppList appList() const; + AccountApp* findApp(const QString &appId) const; /** A user-triggered sign out which disconnects, stops syncs * for the account and forgets the password. */ From e2188b109a38ba68590956b83b0c338d578bb00c Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Fri, 17 Jan 2020 20:25:41 +0100 Subject: [PATCH 117/120] Limit fetching Activities to 100 or max. 30 days old Signed-off-by: Michael Schuster --- src/gui/tray/ActivityListModel.cpp | 13 +++++++++++++ src/gui/tray/ActivityListModel.h | 4 ++++ 2 files changed, 17 insertions(+) diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index dc1b109f6721..8be9c174d025 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -247,6 +247,9 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st _currentlyFetching = false; + QDateTime oldestDate = QDateTime::currentDateTime(); + oldestDate = oldestDate.addDays(_maxActivitiesDays * -1); + foreach (auto activ, activities) { auto json = activ.toObject(); @@ -271,6 +274,14 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st list.append(a); _currentItem = list.last()._id; + + _totalActivitiesFetched++; + if(_totalActivitiesFetched == _maxActivities || + a._dateTime < oldestDate) { + + _doneFetching = true; + break; + } } _activityLists.append(list); @@ -432,6 +443,7 @@ void ActivityListModel::slotRefreshActivity() _activityLists.clear(); _doneFetching = false; _currentItem = 0; + _totalActivitiesFetched = 0; if (canFetchActivities()) { startFetchJob(); @@ -448,5 +460,6 @@ void ActivityListModel::slotRemoveAccount() _currentlyFetching = false; _doneFetching = false; _currentItem = 0; + _totalActivitiesFetched = 0; } } diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index e6e68cba4091..96b0785eea00 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -102,6 +102,10 @@ private slots: bool _currentlyFetching = false; bool _doneFetching = false; int _currentItem = 0; + + int _totalActivitiesFetched = 0; + int _maxActivities = 100; + int _maxActivitiesDays = 30; }; } From de74057cc8bc0b71238c4c41705c4a7e3c4cea1b Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Fri, 17 Jan 2020 20:26:48 +0100 Subject: [PATCH 118/120] Add grey hint Activity entry if limit is reached Signed-off-by: Michael Schuster --- src/gui/tray/ActivityListModel.cpp | 24 +++++++++++++++++++++++- src/gui/tray/ActivityListModel.h | 2 ++ src/gui/tray/Window.qml | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/gui/tray/ActivityListModel.cpp b/src/gui/tray/ActivityListModel.cpp index 8be9c174d025..a9b2efc64381 100644 --- a/src/gui/tray/ActivityListModel.cpp +++ b/src/gui/tray/ActivityListModel.cpp @@ -50,6 +50,7 @@ QHash ActivityListModel::roleNames() const roles[ActionRole] = "type"; roles[ActionIconRole] = "icon"; roles[ActionTextRole] = "subject"; + roles[ActionTextColorRole] = "activityTextTitleColor"; roles[ObjectTypeRole] = "objectType"; roles[PointInTimeRole] = "dateTime"; return roles; @@ -170,6 +171,8 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const } case ActionTextRole: return a._subject; + case ActionTextColorRole: + return a._id == -1 ? QLatin1String("#808080") : QLatin1String("#222"); // FIXME: This is a temporary workaround for _showMoreActivitiesAvailableEntry case MessageRole: if (a._message.isEmpty()) { return QString("No description available."); @@ -185,7 +188,7 @@ QVariant ActivityListModel::data(const QModelIndex &index, int role) const case AccountRole: return a._accName; case PointInTimeRole: - return QString("%1 - %2").arg(Utility::timeAgoInWords(a._dateTime.toLocalTime()), a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate)); + return a._id == -1 ? "" : QString("%1 - %2").arg(Utility::timeAgoInWords(a._dateTime.toLocalTime()), a._dateTime.toLocalTime().toString(Qt::DefaultLocaleShortDate)); case AccountConnectedRole: return (ast && ast->isConnected()); default: @@ -279,6 +282,7 @@ void ActivityListModel::slotActivitiesReceived(const QJsonDocument &json, int st if(_totalActivitiesFetched == _maxActivities || a._dateTime < oldestDate) { + _showMoreActivitiesAvailableEntry = true; _doneFetching = true; break; } @@ -410,6 +414,22 @@ void ActivityListModel::combineActivityLists() if (_activityLists.count() > 0) { std::sort(_activityLists.begin(), _activityLists.end()); resultList.append(_activityLists); + + if(_showMoreActivitiesAvailableEntry) { + Activity a; + a._type = Activity::ActivityType; + a._accName = _accountState->account()->displayName(); + a._id = -1; + a._subject = tr("For more activities please open the Activity app."); + a._dateTime = QDateTime::currentDateTime(); + + AccountApp *app = _accountState->findApp(QLatin1String("activity")); + if(app) { + a._link = app->url(); + } + + resultList.append(a); + } } beginResetModel(); @@ -444,6 +464,7 @@ void ActivityListModel::slotRefreshActivity() _doneFetching = false; _currentItem = 0; _totalActivitiesFetched = 0; + _showMoreActivitiesAvailableEntry = false; if (canFetchActivities()) { startFetchJob(); @@ -461,5 +482,6 @@ void ActivityListModel::slotRemoveAccount() _doneFetching = false; _currentItem = 0; _totalActivitiesFetched = 0; + _showMoreActivitiesAvailableEntry = false; } } diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index 96b0785eea00..1c8ae6ee24e9 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -45,6 +45,7 @@ class ActivityListModel : public QAbstractListModel ObjectTypeRole, ActionsLinksRole, ActionTextRole, + ActionTextColorRole, ActionRole, MessageRole, DisplayPathRole, @@ -106,6 +107,7 @@ private slots: int _totalActivitiesFetched = 0; int _maxActivities = 100; int _maxActivitiesDays = 30; + bool _showMoreActivitiesAvailableEntry = false; }; } diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml index 9fd16c82f0f7..5a040c970989 100644 --- a/src/gui/tray/Window.qml +++ b/src/gui/tray/Window.qml @@ -528,6 +528,7 @@ Window { width: 240 + ((path === "") ? activityItem.height : 0) + ((link === "") ? activityItem.height : 0) - 8 elide: Text.ElideRight font.pixelSize: 12 + color: activityTextTitleColor } Text { From 9b5bb1365cc05cd3293ce97fc235dd72e650663f Mon Sep 17 00:00:00 2001 From: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> Date: Sat, 18 Jan 2020 10:30:34 +0100 Subject: [PATCH 119/120] Code maintenance - correct some syntax, fix some warnings. Signed-off-by: Dominique Fuchs <32204802+DominiqueFuchs@users.noreply.github.com> --- src/gui/folder.cpp | 2 ++ src/gui/tray/ActivityListModel.h | 2 +- src/gui/tray/UserModel.cpp | 3 +++ src/libsync/owncloudpropagator.cpp | 4 ++-- src/libsync/syncengine.cpp | 4 ++-- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/gui/folder.cpp b/src/gui/folder.cpp index 0e906a4203c6..f9cb8cfe00f6 100644 --- a/src/gui/folder.cpp +++ b/src/gui/folder.cpp @@ -914,6 +914,8 @@ void Folder::slotItemCompleted(const SyncFileItemPtr &item) _folderWatcher->removePath(path() + item->_file); _folderWatcher->addPath(path() + item->destination()); break; + default: + break; } } diff --git a/src/gui/tray/ActivityListModel.h b/src/gui/tray/ActivityListModel.h index 1c8ae6ee24e9..37a766907fb2 100644 --- a/src/gui/tray/ActivityListModel.h +++ b/src/gui/tray/ActivityListModel.h @@ -85,7 +85,7 @@ private slots: void activityJobStatusCode(int statusCode); protected: - QHash roleNames() const; + QHash roleNames() const override; private: void startFetchJob(); diff --git a/src/gui/tray/UserModel.cpp b/src/gui/tray/UserModel.cpp index 9a6c9e312161..2c66894e63ef 100644 --- a/src/gui/tray/UserModel.cpp +++ b/src/gui/tray/UserModel.cpp @@ -780,6 +780,9 @@ ImageProvider::ImageProvider() QImage ImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + Q_UNUSED(size) + Q_UNUSED(requestedSize) + if (id == "currentUser") { return UserModel::instance()->currentUserAvatar(); } else { diff --git a/src/libsync/owncloudpropagator.cpp b/src/libsync/owncloudpropagator.cpp index c728172e6a8a..03d1dbf7244a 100644 --- a/src/libsync/owncloudpropagator.cpp +++ b/src/libsync/owncloudpropagator.cpp @@ -376,14 +376,14 @@ void OwncloudPropagator::start(const SyncFileItemVector &items, Q_ASSERT(std::is_sorted(items.begin(), items.end())); } else if (hasChange) { Q_ASSERT(std::is_sorted(items.begin(), items.end(), - [](const SyncFileItemVector::const_reference &a, const SyncFileItemVector::const_reference &b) -> bool { + [](SyncFileItemVector::const_reference &a, SyncFileItemVector::const_reference &b) -> bool { return ((a->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) && (b->_instruction != CSYNC_INSTRUCTION_TYPE_CHANGE)); })); Q_ASSERT(std::is_sorted(items.begin(), items.begin() + lastChangeInstruction)); if (hasDelete) { Q_ASSERT(std::is_sorted(items.begin() + (lastChangeInstruction + 1), items.end(), - [](const SyncFileItemVector::const_reference &a, const SyncFileItemVector::const_reference &b) -> bool { + [](SyncFileItemVector::const_reference &a, SyncFileItemVector::const_reference &b) -> bool { return ((a->_instruction == CSYNC_INSTRUCTION_REMOVE) && (b->_instruction != CSYNC_INSTRUCTION_REMOVE)); })); Q_ASSERT(std::is_sorted(items.begin() + (lastChangeInstruction + 1), items.begin() + lastDeleteInstruction)); diff --git a/src/libsync/syncengine.cpp b/src/libsync/syncengine.cpp index 24732d9753b0..42e45b5c8596 100644 --- a/src/libsync/syncengine.cpp +++ b/src/libsync/syncengine.cpp @@ -1078,7 +1078,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) // Get CHANGE instructions to the top first if (syncItems.count() > 0) { std::sort(syncItems.begin(), syncItems.end(), - [](const SyncFileItemVector::const_reference &a, const SyncFileItemVector::const_reference &b) -> bool { + [](SyncFileItemVector::const_reference &a, SyncFileItemVector::const_reference &b) -> bool { return ((a->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) && (b->_instruction != CSYNC_INSTRUCTION_TYPE_CHANGE)); }); if (syncItems.at(0)->_instruction == CSYNC_INSTRUCTION_TYPE_CHANGE) { @@ -1089,7 +1089,7 @@ void SyncEngine::slotDiscoveryJobFinished(int discoveryResult) std::sort(syncItems.begin(), syncItems.begin() + lastChangeInstruction); if (syncItems.count() > lastChangeInstruction) { std::sort(syncItems.begin() + (lastChangeInstruction + 1), syncItems.end(), - [](const SyncFileItemVector::const_reference &a, const SyncFileItemVector::const_reference &b) -> bool { + [](SyncFileItemVector::const_reference &a, SyncFileItemVector::const_reference &b) -> bool { return ((a->_instruction == CSYNC_INSTRUCTION_REMOVE) && (b->_instruction != CSYNC_INSTRUCTION_REMOVE)); }); if (syncItems.at(lastChangeInstruction + 1)->_instruction == CSYNC_INSTRUCTION_REMOVE) { From 016a90d7b0a258643cc62eea10540b5154e90759 Mon Sep 17 00:00:00 2001 From: Michael Schuster Date: Sat, 18 Jan 2020 12:56:26 +0100 Subject: [PATCH 120/120] More code maintenance Signed-off-by: Michael Schuster --- src/gui/tray/UserModel.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/tray/UserModel.h b/src/gui/tray/UserModel.h index 01ebb4ffda38..36f5af4b3942 100644 --- a/src/gui/tray/UserModel.h +++ b/src/gui/tray/UserModel.h @@ -128,7 +128,7 @@ class UserModel : public QAbstractListModel Q_INVOKABLE void newUserSelected(); protected: - QHash roleNames() const; + QHash roleNames() const override; private: static UserModel *_instance; @@ -170,7 +170,7 @@ public slots: void openAppUrl(const QUrl &url); protected: - QHash roleNames() const; + QHash roleNames() const override; private: static UserAppsModel *_instance;