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

github.com/nextcloud/desktop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaudio Cambra <claudio.cambra@nextcloud.com>2022-07-25 19:57:18 +0300
committerClaudio Cambra <claudio.cambra@nextcloud.com>2022-10-31 20:06:03 +0300
commit7971789112465df32db61950a34afe22b0b9ca0a (patch)
tree5eea02381135c0f2f549c65ec8691a513903c3b1
parent1dbdd8853f033a7ae5111eb8f23ba1e24093fe78 (diff)
Add a file details window/page, QMLify file sharing
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
-rw-r--r--resources.qrc15
-rw-r--r--src/gui/CMakeLists.txt17
-rw-r--r--src/gui/application.cpp3
-rw-r--r--src/gui/fileactivitylistmodel.cpp35
-rw-r--r--src/gui/fileactivitylistmodel.h12
-rw-r--r--src/gui/filedetails/FileActivityView.qml47
-rw-r--r--src/gui/filedetails/FileDetailsPage.qml189
-rw-r--r--src/gui/filedetails/FileDetailsWindow.qml40
-rw-r--r--src/gui/filedetails/NCInputTextEdit.qml70
-rw-r--r--src/gui/filedetails/NCInputTextField.qml65
-rw-r--r--src/gui/filedetails/NCTabButton.qml84
-rw-r--r--src/gui/filedetails/ShareDelegate.qml762
-rw-r--r--src/gui/filedetails/ShareView.qml309
-rw-r--r--src/gui/filedetails/ShareeDelegate.qml27
-rw-r--r--src/gui/filedetails/ShareeSearchField.qml247
-rw-r--r--src/gui/filedetails/filedetails.cpp150
-rw-r--r--src/gui/filedetails/filedetails.h75
-rw-r--r--src/gui/filedetails/shareemodel.cpp221
-rw-r--r--src/gui/filedetails/shareemodel.h100
-rw-r--r--src/gui/filedetails/sharemodel.cpp973
-rw-r--r--src/gui/filedetails/sharemodel.h205
-rw-r--r--src/gui/filedetails/sortedsharemodel.cpp114
-rw-r--r--src/gui/filedetails/sortedsharemodel.h45
-rw-r--r--src/gui/folderman.cpp1
-rw-r--r--src/gui/owncloudgui.cpp74
-rw-r--r--src/gui/owncloudgui.h9
-rw-r--r--src/gui/sharedialog.cpp494
-rw-r--r--src/gui/sharedialog.h114
-rw-r--r--src/gui/sharedialog.ui217
-rw-r--r--src/gui/sharee.cpp156
-rw-r--r--src/gui/sharee.h42
-rw-r--r--src/gui/sharelinkwidget.cpp625
-rw-r--r--src/gui/sharelinkwidget.h157
-rw-r--r--src/gui/sharelinkwidget.ui439
-rw-r--r--src/gui/sharemanager.cpp22
-rw-r--r--src/gui/sharemanager.h139
-rw-r--r--src/gui/shareusergroupwidget.cpp1129
-rw-r--r--src/gui/shareusergroupwidget.h236
-rw-r--r--src/gui/shareusergroupwidget.ui154
-rw-r--r--src/gui/socketapi/socketapi.cpp14
-rw-r--r--src/gui/socketapi/socketapi.h7
-rw-r--r--src/gui/systray.cpp85
-rw-r--r--src/gui/systray.h14
-rw-r--r--src/gui/tray/ActivityItem.qml6
-rw-r--r--src/gui/tray/ActivityItemContent.qml6
-rw-r--r--src/gui/tray/ActivityList.qml3
-rw-r--r--src/gui/tray/FileActivityDialog.qml40
-rw-r--r--src/gui/tray/Window.qml33
-rw-r--r--src/gui/tray/activitylistmodel.cpp1
-rw-r--r--src/gui/tray/activitylistmodel.h5
-rw-r--r--src/gui/tray/asyncimageresponse.cpp23
-rw-r--r--src/gui/tray/asyncimageresponse.h2
52 files changed, 4040 insertions, 4012 deletions
diff --git a/resources.qrc b/resources.qrc
index e49b6ec48..3132330e2 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -7,17 +7,24 @@
<file>src/gui/PredefinedStatusButton.qml</file>
<file>src/gui/BasicComboBox.qml</file>
<file>src/gui/ErrorBox.qml</file>
+ <file>src/gui/filedetails/FileActivityView.qml</file>
+ <file>src/gui/filedetails/FileDetailsPage.qml</file>
+ <file>src/gui/filedetails/FileDetailsWindow.qml</file>
+ <file>src/gui/filedetails/NCInputTextEdit.qml</file>
+ <file>src/gui/filedetails/NCInputTextField.qml</file>
+ <file>src/gui/filedetails/NCTabButton.qml</file>
+ <file>src/gui/filedetails/ShareeDelegate.qml</file>
+ <file>src/gui/filedetails/ShareDelegate.qml</file>
+ <file>src/gui/filedetails/ShareeSearchField.qml</file>
+ <file>src/gui/filedetails/ShareView.qml</file>
<file>src/gui/tray/Window.qml</file>
<file>src/gui/tray/UserLine.qml</file>
<file>src/gui/tray/HeaderButton.qml</file>
<file>src/gui/tray/SyncStatus.qml</file>
- <file>theme/Style/Style.qml</file>
- <file>theme/Style/qmldir</file>
<file>src/gui/tray/ActivityActionButton.qml</file>
<file>src/gui/tray/ActivityItem.qml</file>
<file>src/gui/tray/AutoSizingMenu.qml</file>
<file>src/gui/tray/ActivityList.qml</file>
- <file>src/gui/tray/FileActivityDialog.qml</file>
<file>src/gui/tray/UnifiedSearchInputContainer.qml</file>
<file>src/gui/tray/UnifiedSearchResultFetchMoreTrigger.qml</file>
<file>src/gui/tray/UnifiedSearchResultItem.qml</file>
@@ -39,5 +46,7 @@
<file>src/gui/tray/EditFileLocallyLoadingDialog.qml</file>
<file>src/gui/tray/NCBusyIndicator.qml</file>
<file>src/gui/tray/NCToolTip.qml</file>
+ <file>theme/Style/Style.qml</file>
+ <file>theme/Style/qmldir</file>
</qresource>
</RCC>
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 3a845531e..77d875b85 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -39,9 +39,6 @@ set(client_UI_SRCS
ignorelisttablewidget.ui
networksettings.ui
settingsdialog.ui
- sharedialog.ui
- sharelinkwidget.ui
- shareusergroupwidget.ui
shareuserline.ui
sslerrordialog.ui
addcertificatedialog.ui
@@ -139,14 +136,8 @@ set(client_SRCS
selectivesyncdialog.cpp
settingsdialog.h
settingsdialog.cpp
- sharedialog.h
- sharedialog.cpp
- sharelinkwidget.h
- sharelinkwidget.cpp
sharemanager.h
sharemanager.cpp
- shareusergroupwidget.h
- shareusergroupwidget.cpp
profilepagewidget.h
profilepagewidget.cpp
sharee.h
@@ -193,6 +184,14 @@ set(client_SRCS
emojimodel.cpp
fileactivitylistmodel.h
fileactivitylistmodel.cpp
+ filedetails/filedetails.h
+ filedetails/filedetails.cpp
+ filedetails/sharemodel.h
+ filedetails/sharemodel.cpp
+ filedetails/shareemodel.h
+ filedetails/shareemodel.cpp
+ filedetails/sortedsharemodel.h
+ filedetails/sortedsharemodel.cpp
tray/svgimageprovider.h
tray/svgimageprovider.cpp
tray/syncstatussummary.h
diff --git a/src/gui/application.cpp b/src/gui/application.cpp
index fafc33a24..9dd436dc6 100644
--- a/src/gui/application.cpp
+++ b/src/gui/application.cpp
@@ -32,7 +32,6 @@
#include "sslerrordialog.h"
#include "theme.h"
#include "clientproxy.h"
-#include "sharedialog.h"
#include "accountmanager.h"
#include "creds/abstractcredentials.h"
#include "pushnotifications.h"
@@ -379,7 +378,7 @@ Application::Application(int &argc, char **argv)
_gui.data(), &ownCloudGui::slotShowShareDialog);
connect(FolderMan::instance()->socketApi(), &SocketApi::fileActivityCommandReceived,
- Systray::instance(), &Systray::showFileActivityDialog);
+ _gui.data(), &ownCloudGui::slotShowFileActivityDialog);
// startup procedure.
connect(&_checkConnectionTimer, &QTimer::timeout, this, &Application::slotCheckConnection);
diff --git a/src/gui/fileactivitylistmodel.cpp b/src/gui/fileactivitylistmodel.cpp
index 5fadec981..2dde4924f 100644
--- a/src/gui/fileactivitylistmodel.cpp
+++ b/src/gui/fileactivitylistmodel.cpp
@@ -24,23 +24,46 @@ FileActivityListModel::FileActivityListModel(QObject *parent)
: ActivityListModel(nullptr, parent)
{
setDisplayActions(false);
+ connect(this, &FileActivityListModel::accountStateChanged, this, &FileActivityListModel::load);
}
-void FileActivityListModel::load(AccountState *accountState, const int objectId)
+QString FileActivityListModel::localPath() const
{
- Q_ASSERT(accountState);
- if (!accountState || currentlyFetching()) {
+ return _localPath;
+}
+
+void FileActivityListModel::setLocalPath(const QString &localPath)
+{
+ _localPath = localPath;
+ Q_EMIT localPathChanged();
+
+ load();
+}
+
+void FileActivityListModel::load()
+{
+ if (!accountState() || _localPath.isEmpty() || currentlyFetching()) {
+ return;
+ }
+
+ const auto folder = FolderMan::instance()->folderForPath(_localPath);
+
+ if (!folder) {
+ qCWarning(lcFileActivityListModel) << "Invalid folder for localPath:" << _localPath << "will not load activity list model.";
return;
}
- setAccountState(accountState);
- _objectId = objectId;
+ const auto folderRelativePath = _localPath.mid(folder->cleanPath().length() + 1);
+ SyncJournalFileRecord record;
+ folder->journalDb()->getFileRecord(folderRelativePath, &record);
+
+ _objectId = record.numericFileId().toInt();
slotRefreshActivity();
}
void FileActivityListModel::startFetchJob()
{
- if (!accountState()->isConnected()) {
+ if (!accountState()->isConnected() || _objectId == -1) {
return;
}
setAndRefreshCurrentlyFetching(true);
diff --git a/src/gui/fileactivitylistmodel.h b/src/gui/fileactivitylistmodel.h
index 18d8d9830..4134dc0c7 100644
--- a/src/gui/fileactivitylistmodel.h
+++ b/src/gui/fileactivitylistmodel.h
@@ -22,17 +22,25 @@ namespace OCC {
class FileActivityListModel : public ActivityListModel
{
Q_OBJECT
+ Q_PROPERTY(QString localPath READ localPath WRITE setLocalPath NOTIFY localPathChanged)
public:
explicit FileActivityListModel(QObject *parent = nullptr);
+ QString localPath() const;
+
+signals:
+ void localPathChanged();
+
public slots:
- void load(AccountState *accountState, const int objectId);
+ void setLocalPath(const QString &localPath);
+ void load();
protected slots:
void startFetchJob() override;
private:
- int _objectId;
+ int _objectId = -1;
+ QString _localPath;
};
}
diff --git a/src/gui/filedetails/FileActivityView.qml b/src/gui/filedetails/FileActivityView.qml
new file mode 100644
index 000000000..47fbe35be
--- /dev/null
+++ b/src/gui/filedetails/FileActivityView.qml
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+
+Item {
+ id: root
+
+ property string localPath: ""
+ property var accountState: ({})
+ property int horizontalPadding: 0
+ property int iconSize: 32
+ property alias model: activityModel
+
+ FileActivityListModel {
+ id: activityModel
+ localPath: root.localPath
+ accountState: root.accountState
+ }
+
+ ActivityList {
+ anchors.fill: parent
+ anchors.leftMargin: root.horizontalPadding
+ anchors.rightMargin: root.horizontalPadding
+
+ iconSize: root.iconSize
+ isFileActivityList: true
+ model: root.model
+ }
+}
diff --git a/src/gui/filedetails/FileDetailsPage.qml b/src/gui/filedetails/FileDetailsPage.qml
new file mode 100644
index 000000000..3e31f102a
--- /dev/null
+++ b/src/gui/filedetails/FileDetailsPage.qml
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+Page {
+ id: root
+
+ property var accountState: ({})
+ property string localPath: ({})
+
+ // We want the SwipeView to "spill" over the edges of the window to really
+ // make it look nice. If we apply page-wide padding, however, the swipe
+ // contents only go as far as the page contents, clipped by the padding.
+ // This property reflects the padding we intend to display, but not the real
+ // padding, which we have to apply selectively to achieve our desired effect.
+ property int intendedPadding: Style.standardSpacing * 2
+ property int iconSize: 32
+
+ property FileDetails fileDetails: FileDetails {
+ id: fileDetails
+ localPath: root.localPath
+ }
+
+ Connections {
+ target: Systray
+ function onShowFileDetailsPage(fileLocalPath, page) {
+ if(fileLocalPath === root.localPath) {
+ switch(page) {
+ case Systray.FileDetailsPage.Activity:
+ swipeView.currentIndex = fileActivityView.swipeIndex;
+ break;
+ case Systray.FileDetailsPage.Sharing:
+ swipeView.currentIndex = shareView.swipeIndex;
+ break;
+ }
+ }
+ }
+ }
+
+ topPadding: intendedPadding
+ bottomPadding: intendedPadding
+
+ background: Rectangle {
+ color: Style.backgroundColor
+ }
+
+ header: ColumnLayout {
+ spacing: root.intendedPadding
+
+ GridLayout {
+ id: headerGridLayout
+
+ readonly property bool showFileLockedString: root.fileDetails.lockExpireString !== ""
+
+ Layout.fillWidth: parent
+ Layout.topMargin: root.topPadding
+
+ columns: 2
+ rows: showFileLockedString ? 3 : 2
+
+ rowSpacing: Style.standardSpacing / 2
+ columnSpacing: Style.standardSpacing
+
+ Image {
+ id: fileIcon
+
+ Layout.rowSpan: headerGridLayout.rows
+ Layout.preferredWidth: Style.trayListItemIconSize
+ Layout.leftMargin: root.intendedPadding
+ Layout.fillHeight: true
+
+ verticalAlignment: Image.AlignVCenter
+ horizontalAlignment: Image.AlignHCenter
+ source: root.fileDetails.iconUrl
+ sourceSize.width: Style.trayListItemIconSize
+ sourceSize.height: Style.trayListItemIconSize
+ fillMode: Image.PreserveAspectFit
+ }
+
+ Label {
+ id: fileNameLabel
+
+ Layout.fillWidth: true
+ Layout.rightMargin: root.intendedPadding
+
+ text: root.fileDetails.name
+ color: Style.ncTextColor
+ font.bold: true
+ wrapMode: Text.Wrap
+ }
+
+ Label {
+ id: fileDetailsLabel
+
+ Layout.fillWidth: true
+ Layout.rightMargin: root.intendedPadding
+
+ text: `${root.fileDetails.sizeString} · ${root.fileDetails.lastChangedString}`
+ color: Style.ncSecondaryTextColor
+ wrapMode: Text.Wrap
+ }
+
+ Label {
+ id: fileLockedLabel
+
+ Layout.fillWidth: true
+ Layout.rightMargin: root.intendedPadding
+
+ text: root.fileDetails.lockExpireString
+ color: Style.ncSecondaryTextColor
+ wrapMode: Text.Wrap
+ visible: headerGridLayout.showFileLockedString
+ }
+ }
+
+ TabBar {
+ id: viewBar
+
+ Layout.leftMargin: root.intendedPadding
+ Layout.rightMargin: root.intendedPadding
+
+ padding: 0
+ background: Rectangle {
+ color: Style.backgroundColor
+ }
+
+ NCTabButton {
+ svgCustomColorSource: "image://svgimage-custom-color/activity.svg"
+ text: qsTr("Activity")
+ checked: swipeView.currentIndex === fileActivityView.swipeIndex
+ onClicked: swipeView.currentIndex = fileActivityView.swipeIndex
+ }
+
+ NCTabButton {
+ svgCustomColorSource: "image://svgimage-custom-color/share.svg"
+ text: qsTr("Sharing")
+ checked: swipeView.currentIndex === shareView.swipeIndex
+ onClicked: swipeView.currentIndex = shareView.swipeIndex
+ }
+ }
+ }
+
+ SwipeView {
+ id: swipeView
+
+ anchors.fill: parent
+ clip: true
+
+ FileActivityView {
+ id: fileActivityView
+
+ property int swipeIndex: SwipeView.index
+
+ accountState: root.accountState
+ localPath: root.localPath
+ horizontalPadding: root.intendedPadding
+ iconSize: root.iconSize
+ }
+
+ ShareView {
+ id: shareView
+
+ property int swipeIndex: SwipeView.index
+
+ accountState: root.accountState
+ localPath: root.localPath
+ fileDetails: root.fileDetails
+ horizontalPadding: root.intendedPadding
+ iconSize: root.iconSize
+ }
+ }
+}
diff --git a/src/gui/filedetails/FileDetailsWindow.qml b/src/gui/filedetails/FileDetailsWindow.qml
new file mode 100644
index 000000000..a63607a56
--- /dev/null
+++ b/src/gui/filedetails/FileDetailsWindow.qml
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+ApplicationWindow {
+ id: root
+
+ property var accountState
+ property string localPath: ""
+
+ width: 400
+ height: 500
+
+ title: qsTr("File details of %1 · %2").arg(fileDetailsPage.fileDetails.name).arg(Systray.windowTitle)
+
+ FileDetailsPage {
+ id: fileDetailsPage
+ anchors.fill: parent
+ accountState: root.accountState
+ localPath: root.localPath
+ }
+}
diff --git a/src/gui/filedetails/NCInputTextEdit.qml b/src/gui/filedetails/NCInputTextEdit.qml
new file mode 100644
index 000000000..85cd39940
--- /dev/null
+++ b/src/gui/filedetails/NCInputTextEdit.qml
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+TextEdit {
+ id: root
+
+ property color accentColor: Style.ncBlue
+ property color secondaryColor: Style.menuBorder
+ property alias submitButton: submitButton
+
+ clip: true
+ color: Style.ncTextColor
+ textMargin: Style.smallSpacing
+ wrapMode: TextEdit.Wrap
+ selectByMouse: true
+ height: Math.max(Style.talkReplyTextFieldPreferredHeight, contentHeight)
+
+ Rectangle {
+ id: textFieldBorder
+ anchors.fill: parent
+ radius: Style.slightlyRoundedButtonRadius
+ border.width: Style.normalBorderWidth
+ border.color: root.activeFocus ? root.accentColor : root.secondaryColor
+ color: Style.backgroundColor
+ z: -1
+ }
+
+ Button {
+ id: submitButton
+
+ anchors.bottom: root.bottom
+ anchors.right: root.right
+ anchors.margins: 1
+
+ width: height
+ height: parent.height
+
+ background: Rectangle {
+ radius: width / 2
+ color: textFieldBorder.color
+ }
+
+ flat: true
+ icon.source: "image://svgimage-custom-color/confirm.svg" + "/" + root.secondaryColor
+ icon.color: hovered && enabled ? UserModel.currentUser.accentColor : root.secondaryColor
+
+ enabled: root.text !== ""
+
+ onClicked: root.editingFinished()
+ }
+}
+
diff --git a/src/gui/filedetails/NCInputTextField.qml b/src/gui/filedetails/NCInputTextField.qml
new file mode 100644
index 000000000..36dd42ee7
--- /dev/null
+++ b/src/gui/filedetails/NCInputTextField.qml
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+TextField {
+ id: root
+
+ property color accentColor: Style.ncBlue
+ property color secondaryColor: Style.menuBorder
+ property alias submitButton: submitButton
+
+ implicitHeight: Style.talkReplyTextFieldPreferredHeight
+ color: Style.ncTextColor
+ placeholderTextColor: secondaryColor
+
+ rightPadding: submitButton.width
+
+ selectByMouse: true
+
+ background: Rectangle {
+ id: textFieldBorder
+ radius: Style.slightlyRoundedButtonRadius
+ border.width: Style.normalBorderWidth
+ border.color: root.activeFocus ? root.accentColor : root.secondaryColor
+ color: Style.backgroundColor
+ }
+
+ Button {
+ id: submitButton
+
+ anchors.top: root.top
+ anchors.right: root.right
+ anchors.margins: 1
+
+ width: height
+ height: parent.height
+
+ background: null
+ flat: true
+ icon.source: "image://svgimage-custom-color/confirm.svg" + "/" + root.secondaryColor
+ icon.color: hovered && enabled ? UserModel.currentUser.accentColor : root.secondaryColor
+
+ enabled: root.text !== ""
+
+ onClicked: root.accepted()
+ }
+}
+
diff --git a/src/gui/filedetails/NCTabButton.qml b/src/gui/filedetails/NCTabButton.qml
new file mode 100644
index 000000000..6a5a2829a
--- /dev/null
+++ b/src/gui/filedetails/NCTabButton.qml
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+TabButton {
+ id: tabButton
+
+ property string svgCustomColorSource: ""
+
+ padding: 0
+ background: Rectangle {
+ color: tabButton.pressed ? Style.lightHover : Style.backgroundColor
+ }
+
+ contentItem: ColumnLayout {
+ id: tabButtonLayout
+
+ property var elementColors: tabButton.checked ? Style.ncTextColor : Style.ncSecondaryTextColor
+
+ // We'd like to just set the height of the Image, but this causes crashing.
+ // So we use a wrapping Item and use anchors to adjust the size.
+ Item {
+ id: iconItem
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ height: 20
+
+ Image {
+ id: iconItemImage
+ anchors.fill: parent
+ anchors.margins: tabButton.checked ? 0 : 2
+ horizontalAlignment: Image.AlignHCenter
+ verticalAlignment: Image.AlignVCenter
+ fillMode: Image.PreserveAspectFit
+ source: tabButton.svgCustomColorSource + "/" + tabButtonLayout.elementColors
+ sourceSize.width: 32
+ sourceSize.height: 32
+ }
+ }
+
+ Label {
+ id: tabButtonLabel
+ Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ color: tabButtonLayout.elementColors
+ text: tabButton.text
+ font.bold: tabButton.checked
+ }
+
+ Rectangle {
+ FontMetrics {
+ id: fontMetrics
+ font.family: tabButtonLabel.font.family
+ font.pixelSize: tabButtonLabel.font.pixelSize
+ font.bold: true
+ }
+
+ property int textWidth: fontMetrics.boundingRect(tabButtonLabel.text).width
+
+ implicitWidth: textWidth + Style.standardSpacing * 2
+ implicitHeight: 2
+ color: tabButton.checked ? Style.ncBlue : "transparent"
+ }
+ }
+}
diff --git a/src/gui/filedetails/ShareDelegate.qml b/src/gui/filedetails/ShareDelegate.qml
new file mode 100644
index 000000000..56cbe9edf
--- /dev/null
+++ b/src/gui/filedetails/ShareDelegate.qml
@@ -0,0 +1,762 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+import QtGraphicalEffects 1.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+
+GridLayout {
+ id: root
+
+ signal deleteShare
+ signal createNewLinkShare
+
+ signal toggleAllowEditing(bool enable)
+ signal toggleAllowResharing(bool enable)
+ signal togglePasswordProtect(bool enable)
+ signal toggleExpirationDate(bool enable)
+ signal toggleNoteToRecipient(bool enable)
+
+ signal setLinkShareLabel(string label)
+ signal setExpireDate(var milliseconds) // Since QML ints are only 32 bits, use a variant
+ signal setPassword(string password)
+ signal setNote(string note)
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ columns: 3
+ rows: linkDetailLabel.visible ? 1 : 2
+
+ columnSpacing: Style.standardSpacing / 2
+ rowSpacing: Style.standardSpacing / 2
+
+ property int iconSize: 32
+
+ property var share: model.share ?? ({})
+
+ property string iconUrl: model.iconUrl ?? ""
+ property string avatarUrl: model.avatarUrl ?? ""
+ property string text: model.display ?? ""
+ property string detailText: model.detailText ?? ""
+ property string link: model.link ?? ""
+ property string note: model.note ?? ""
+ property string password: model.password ?? ""
+ property string passwordPlaceholder: "●●●●●●●●●●"
+
+ property var expireDate: model.expireDate // Don't use int as we are limited
+ property var maximumExpireDate: model.enforcedMaximumExpireDate
+
+ property string linkShareLabel: model.linkShareLabel ?? ""
+
+ property bool editingAllowed: model.editingAllowed
+ property bool noteEnabled: model.noteEnabled
+ property bool expireDateEnabled: model.expireDateEnabled
+ property bool expireDateEnforced: model.expireDateEnforced
+ property bool passwordProtectEnabled: model.passwordProtectEnabled
+ property bool passwordEnforced: model.passwordEnforced
+
+ property bool isLinkShare: model.shareType === ShareModel.ShareTypeLink
+ property bool isPlaceholderLinkShare: model.shareType === ShareModel.ShareTypePlaceholderLink
+
+ property bool canCreateLinkShares: true
+
+ property bool waitingForEditingAllowedChange: false
+ property bool waitingForNoteEnabledChange: false
+ property bool waitingForExpireDateEnabledChange: false
+ property bool waitingForPasswordProtectEnabledChange: false
+ property bool waitingForExpireDateChange: false
+ property bool waitingForLinkShareLabelChange: false
+ property bool waitingForPasswordChange: false
+ property bool waitingForNoteChange: false
+
+ function resetNoteField() {
+ noteTextEdit.text = note;
+ waitingForNoteChange = false;
+ }
+
+ function resetLinkShareLabelField() {
+ linkShareLabelTextField.text = linkShareLabel;
+ waitingForLinkShareLabelChange = false;
+ }
+
+ function resetPasswordField() {
+ passwordTextField.text = password !== "" ? password : passwordPlaceholder;
+ waitingForPasswordChange = false;
+ }
+
+ function resetExpireDateField() {
+ // Expire date changing is handled by the expireDateSpinBox
+ waitingForExpireDateChange = false;
+ }
+
+ function resetEditingAllowedField() {
+ editingAllowedMenuItem.checked = editingAllowed;
+ waitingForEditingAllowedChange = false;
+ }
+
+ function resetNoteEnabledField() {
+ noteEnabledMenuItem.checked = noteEnabled;
+ waitingForNoteEnabledChange = false;
+ }
+
+ function resetExpireDateEnabledField() {
+ expireDateEnabledMenuItem.checked = expireDateEnabled;
+ waitingForExpireDateEnabledChange = false;
+ }
+
+ function resetPasswordProtectEnabledField() {
+ passwordProtectEnabledMenuItem.checked = passwordProtectEnabled;
+ waitingForPasswordProtectEnabledChange = false;
+ }
+
+ function resetMenu() {
+ moreMenu.close();
+
+ resetNoteField();
+ resetPasswordField();
+ resetLinkShareLabelField();
+ resetExpireDateField();
+
+ resetEditingAllowedField();
+ resetNoteEnabledField();
+ resetExpireDateEnabledField();
+ resetPasswordProtectEnabledField();
+ }
+
+ // Renaming a link share can lead to the model being reshuffled.
+ // This can cause a situation where this delegate is assigned to
+ // a new row and it doesn't have its properties signalled as
+ // changed by the model, leading to bugs. We therefore reset all
+ // the fields here when we detect the share has been changed
+ onShareChanged: resetMenu()
+
+ // Reset value after property binding broken by user interaction
+ onNoteChanged: resetNoteField()
+ onPasswordChanged: resetPasswordField()
+ onLinkShareLabelChanged: resetLinkShareLabelField()
+ onExpireDateChanged: resetExpireDateField()
+
+ onEditingAllowedChanged: resetEditingAllowedField()
+ onNoteEnabledChanged: resetNoteEnabledField()
+ onExpireDateEnabledChanged: resetExpireDateEnabledField()
+ onPasswordProtectEnabledChanged: resetPasswordProtectEnabledField()
+
+ Item {
+ id: imageItem
+
+ property bool isAvatar: root.avatarUrl !== ""
+
+ Layout.row: 0
+ Layout.column: 0
+ Layout.rowSpan: root.rows
+ Layout.preferredWidth: root.iconSize
+ Layout.preferredHeight: root.iconSize
+
+ Rectangle {
+ id: backgroundOrMask
+ anchors.fill: parent
+ radius: width / 2
+ color: Style.ncBlue
+ visible: !imageItem.isAvatar
+ }
+
+ Image {
+ id: shareIconOrThumbnail
+
+ anchors.centerIn: parent
+
+ verticalAlignment: Image.AlignVCenter
+ horizontalAlignment: Image.AlignHCenter
+ fillMode: Image.PreserveAspectFit
+
+ source: imageItem.isAvatar ? root.avatarUrl : root.iconUrl + "/white"
+ sourceSize.width: imageItem.isAvatar ? root.iconSize : root.iconSize / 2
+ sourceSize.height: imageItem.isAvatar ? root.iconSize : root.iconSize / 2
+
+ visible: !imageItem.isAvatar
+ }
+
+ OpacityMask {
+ anchors.fill: parent
+ source: shareIconOrThumbnail
+ maskSource: backgroundOrMask
+ visible: imageItem.isAvatar
+ }
+ }
+
+ Label {
+ id: shareTypeLabel
+
+ Layout.fillWidth: true
+ Layout.alignment: linkDetailLabel.visible ? Qt.AlignBottom : Qt.AlignVCenter
+ Layout.row: 0
+ Layout.column: 1
+ Layout.rowSpan: root.rows
+
+ text: root.text
+ color: Style.ncTextColor
+ elide: Text.ElideRight
+ }
+
+ Label {
+ id: linkDetailLabel
+
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignTop
+ Layout.row: 1
+ Layout.column: 1
+
+ text: root.detailText
+ color: Style.ncSecondaryTextColor
+ elide: Text.ElideRight
+ visible: text !== ""
+ }
+
+ RowLayout {
+ Layout.row: 0
+ Layout.column: 2
+ Layout.rowSpan: root.rows
+ Layout.fillHeight: true
+ spacing: 0
+
+ Button {
+ id: createLinkButton
+
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredWidth: icon.width + (Style.standardSpacing * 2)
+
+ flat: true
+ display: AbstractButton.IconOnly
+ icon.color: Style.ncTextColor
+ icon.source: "qrc:///client/theme/add.svg"
+ icon.width: 16
+ icon.height: 16
+
+ visible: root.isPlaceholderLinkShare && root.canCreateLinkShares
+ enabled: visible
+
+ onClicked: root.createNewLinkShare()
+ }
+
+ Button {
+ id: copyLinkButton
+
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredWidth: icon.width + (Style.standardSpacing * 2)
+
+ flat: true
+ display: AbstractButton.IconOnly
+ icon.color: Style.ncTextColor
+ icon.source: "qrc:///client/theme/copy.svg"
+ icon.width: 16
+ icon.height: 16
+
+ visible: root.isLinkShare
+ enabled: visible
+
+ onClicked: {
+ clipboardHelper.text = root.link;
+ clipboardHelper.selectAll();
+ clipboardHelper.copy();
+ clipboardHelper.clear();
+ }
+
+ TextEdit { id: clipboardHelper; visible: false}
+ }
+
+ Button {
+ id: moreButton
+
+ Layout.alignment: Qt.AlignCenter
+ Layout.preferredWidth: icon.width + (Style.standardSpacing * 2)
+
+ flat: true
+ display: AbstractButton.IconOnly
+ icon.color: Style.ncTextColor
+ icon.source: "qrc:///client/theme/more.svg"
+ icon.width: 16
+ icon.height: 16
+
+ visible: !root.isPlaceholderLinkShare
+ enabled: visible
+
+ onClicked: moreMenu.popup()
+
+ Menu {
+ id: moreMenu
+
+ property int rowIconWidth: 16
+ property int indicatorItemWidth: 20
+ property int indicatorSpacing: Style.standardSpacing
+ property int itemPadding: Style.smallSpacing
+
+ padding: Style.smallSpacing
+ // TODO: Rather than setting all these palette colours manually,
+ // create a custom style and do it for all components globally
+ palette {
+ text: Style.ncTextColor
+ windowText: Style.ncTextColor
+ buttonText: Style.ncTextColor
+ light: Style.lightHover
+ midlight: Style.lightHover
+ mid: Style.ncSecondaryTextColor
+ dark: Style.menuBorder
+ button: Style.menuBorder
+ window: Style.backgroundColor
+ base: Style.backgroundColor
+ }
+
+ RowLayout {
+ anchors.left: parent.left
+ anchors.leftMargin: moreMenu.itemPadding
+ anchors.right: parent.right
+ anchors.rightMargin: moreMenu.itemPadding
+ height: visible ? implicitHeight : 0
+ spacing: moreMenu.indicatorSpacing
+
+ visible: root.isLinkShare
+
+ Image {
+ Layout.preferredWidth: moreMenu.indicatorItemWidth
+ Layout.fillHeight: true
+
+ verticalAlignment: Image.AlignVCenter
+ horizontalAlignment: Image.AlignHCenter
+ fillMode: Image.Pad
+
+ source: "image://svgimage-custom-color/edit.svg/" + Style.menuBorder
+ sourceSize.width: moreMenu.rowIconWidth
+ sourceSize.height: moreMenu.rowIconWidth
+ }
+
+ NCInputTextField {
+ id: linkShareLabelTextField
+
+ Layout.fillWidth: true
+ height: visible ? implicitHeight : 0
+
+ text: root.linkShareLabel
+ placeholderText: qsTr("Share label")
+
+ enabled: root.isLinkShare &&
+ !root.waitingForLinkShareLabelChange
+
+ onAccepted: if(text !== root.linkShareLabel) {
+ root.setLinkShareLabel(text);
+ root.waitingForLinkShareLabelChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForLinkShareLabelChange
+ running: visible
+ z: 1
+ }
+ }
+ }
+
+ // On these checkables, the clicked() signal is called after
+ // the check state changes.
+ CheckBox {
+ id: editingAllowedMenuItem
+
+ spacing: moreMenu.indicatorSpacing
+ padding: moreMenu.itemPadding
+ indicator.width: moreMenu.indicatorItemWidth
+ indicator.height: moreMenu.indicatorItemWidth
+
+ checkable: true
+ checked: root.editingAllowed
+ text: qsTr("Allow editing")
+ enabled: !root.waitingForEditingAllowedChange
+
+ onClicked: {
+ root.toggleAllowEditing(checked);
+ root.waitingForEditingAllowedChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForEditingAllowedChange
+ running: visible
+ z: 1
+ }
+ }
+
+ CheckBox {
+ id: passwordProtectEnabledMenuItem
+
+ spacing: moreMenu.indicatorSpacing
+ padding: moreMenu.itemPadding
+ indicator.width: moreMenu.indicatorItemWidth
+ indicator.height: moreMenu.indicatorItemWidth
+
+ checkable: true
+ checked: root.passwordProtectEnabled
+ text: qsTr("Password protect")
+ enabled: !root.waitingForPasswordProtectEnabledChange && !root.passwordEnforced
+
+ onClicked: {
+ root.togglePasswordProtect(checked);
+ root.waitingForPasswordProtectEnabledChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForPasswordProtectEnabledChange
+ running: visible
+ z: 1
+ }
+ }
+
+ RowLayout {
+ anchors.left: parent.left
+ anchors.leftMargin: moreMenu.itemPadding
+ anchors.right: parent.right
+ anchors.rightMargin: moreMenu.itemPadding
+ height: visible ? implicitHeight : 0
+ spacing: moreMenu.indicatorSpacing
+
+ visible: root.passwordProtectEnabled
+
+ Image {
+ Layout.preferredWidth: moreMenu.indicatorItemWidth
+ Layout.fillHeight: true
+
+ verticalAlignment: Image.AlignVCenter
+ horizontalAlignment: Image.AlignHCenter
+ fillMode: Image.Pad
+
+ source: "image://svgimage-custom-color/lock-https.svg/" + Style.menuBorder
+ sourceSize.width: moreMenu.rowIconWidth
+ sourceSize.height: moreMenu.rowIconWidth
+ }
+
+ NCInputTextField {
+ id: passwordTextField
+
+ Layout.fillWidth: true
+ height: visible ? implicitHeight : 0
+
+ text: root.password !== "" ? root.password : root.passwordPlaceholder
+ enabled: root.passwordProtectEnabled &&
+ !root.waitingForPasswordChange &&
+ !root.waitingForPasswordProtectEnabledChange
+
+ onAccepted: if(text !== root.password && text !== root.passwordPlaceholder) {
+ root.setPassword(text);
+ root.waitingForPasswordChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForPasswordChange ||
+ root.waitingForPasswordProtectEnabledChange
+ running: visible
+ z: 1
+ }
+ }
+ }
+
+ CheckBox {
+ id: expireDateEnabledMenuItem
+
+ spacing: moreMenu.indicatorSpacing
+ padding: moreMenu.itemPadding
+ indicator.width: moreMenu.indicatorItemWidth
+ indicator.height: moreMenu.indicatorItemWidth
+
+ checkable: true
+ checked: root.expireDateEnabled
+ text: qsTr("Set expiration date")
+ enabled: !root.waitingForExpireDateEnabledChange && !root.expireDateEnforced
+
+ onClicked: {
+ root.toggleExpirationDate(checked);
+ root.waitingForExpireDateEnabledChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForExpireDateEnabledChange
+ running: visible
+ z: 1
+ }
+ }
+
+ RowLayout {
+ anchors.left: parent.left
+ anchors.leftMargin: moreMenu.itemPadding
+ anchors.right: parent.right
+ anchors.rightMargin: moreMenu.itemPadding
+ height: visible ? implicitHeight : 0
+ spacing: moreMenu.indicatorSpacing
+
+ visible: root.expireDateEnabled
+
+ Image {
+ Layout.preferredWidth: moreMenu.indicatorItemWidth
+ Layout.fillHeight: true
+
+ verticalAlignment: Image.AlignVCenter
+ horizontalAlignment: Image.AlignHCenter
+ fillMode: Image.Pad
+
+ source: "image://svgimage-custom-color/calendar.svg/" + Style.menuBorder
+ sourceSize.width: moreMenu.rowIconWidth
+ sourceSize.height: moreMenu.rowIconWidth
+ }
+
+ // QML dates are essentially JavaScript dates, which makes them very finicky and unreliable.
+ // Instead, we exclusively deal with msecs from epoch time to make things less painful when editing.
+ // We only use the QML Date when showing the nice string to the user.
+ SpinBox {
+ id: expireDateSpinBox
+
+ // Work arounds the limitations of QML's 32 bit integer when handling msecs from epoch
+ // Instead, we handle everything as days since epoch
+ readonly property int dayInMSecs: 24 * 60 * 60 * 1000
+ readonly property int expireDateReduced: Math.floor(root.expireDate / dayInMSecs)
+ // Reset the model data after binding broken on user interact
+ onExpireDateReducedChanged: value = expireDateReduced
+
+ // We can't use JS's convenient Infinity or Number.MAX_VALUE as
+ // JS Number type is 64 bits, whereas QML's int type is only 32 bits
+ readonly property IntValidator intValidator: IntValidator {}
+ readonly property int maximumExpireDateReduced: root.expireDateEnforced ?
+ Math.floor(root.maximumExpireDate / dayInMSecs) :
+ intValidator.top
+ readonly property int minimumExpireDateReduced: {
+ const currentDate = new Date();
+ const minDateUTC = new Date(Date.UTC(currentDate.getFullYear(),
+ currentDate.getMonth(),
+ currentDate.getDate() + 1));
+ return Math.floor(minDateUTC / dayInMSecs) // Start of day at 00:00:0000 UTC
+ }
+
+ // Taken from Kalendar 22.08
+ // https://invent.kde.org/pim/kalendar/-/blob/release/22.08/src/contents/ui/KalendarUtils/dateutils.js
+ function parseDateString(dateString) {
+ function defaultParse() {
+ return Date.fromLocaleDateString(Qt.locale(), dateString, Locale.NarrowFormat);
+ }
+
+ const dateStringDelimiterMatches = dateString.match(/\D/);
+ if(dateStringDelimiterMatches.length === 0) {
+ // Let the date method figure out this weirdness
+ return defaultParse();
+ }
+
+ const dateStringDelimiter = dateStringDelimiterMatches[0];
+
+ const localisedDateFormatSplit = Qt.locale().dateFormat(Locale.NarrowFormat).split(dateStringDelimiter);
+ const localisedDateDayPosition = localisedDateFormatSplit.findIndex((x) => /d/gi.test(x));
+ const localisedDateMonthPosition = localisedDateFormatSplit.findIndex((x) => /m/gi.test(x));
+ const localisedDateYearPosition = localisedDateFormatSplit.findIndex((x) => /y/gi.test(x));
+
+ let splitDateString = dateString.split(dateStringDelimiter);
+ let userProvidedYear = splitDateString[localisedDateYearPosition]
+
+ const dateNow = new Date();
+ const stringifiedCurrentYear = dateNow.getFullYear().toString();
+
+ // If we have any input weirdness, or if we have a fully-written year
+ // (e.g. 2022 instead of 22) then use default parse
+ if(splitDateString.length === 0 ||
+ splitDateString.length > 3 ||
+ userProvidedYear.length >= stringifiedCurrentYear.length) {
+ return defaultParse();
+ }
+
+ let fullyWrittenYear = userProvidedYear.split("");
+ const digitsToAdd = stringifiedCurrentYear.length - fullyWrittenYear.length;
+ for(let i = 0; i < digitsToAdd; i++) {
+ fullyWrittenYear.splice(i, 0, stringifiedCurrentYear[i])
+ }
+ fullyWrittenYear = fullyWrittenYear.join("");
+
+ const fixedYearNum = Number(fullyWrittenYear);
+ const monthIndexNum = Number(splitDateString[localisedDateMonthPosition]) - 1;
+ const dayNum = Number(splitDateString[localisedDateDayPosition]);
+
+ // Modification: return date in UTC
+ return new Date(Date.UTC(fixedYearNum, monthIndexNum, dayNum));
+ }
+
+ Layout.fillWidth: true
+ height: visible ? implicitHeight : 0
+
+ // We want all the internal benefits of the spinbox but don't actually want the
+ // buttons, so set an empty item as a dummy
+ up.indicator: Item {}
+ down.indicator: Item {}
+
+ background: Rectangle {
+ radius: Style.slightlyRoundedButtonRadius
+ border.width: Style.normalBorderWidth
+ border.color: expireDateSpinBox.activeFocus ? Style.ncBlue : Style.menuBorder
+ color: Style.backgroundColor
+ }
+
+ value: expireDateReduced
+ from: minimumExpireDateReduced
+ to: maximumExpireDateReduced
+
+ textFromValue: (value, locale) => {
+ const dateFromValue = new Date(value * dayInMSecs);
+ return dateFromValue.toLocaleDateString(Qt.locale(), Locale.NarrowFormat);
+ }
+ valueFromText: (text, locale) => {
+ const dateFromText = parseDateString(text);
+ return Math.floor(dateFromText.getTime() / dayInMSecs);
+ }
+
+ editable: true
+ inputMethodHints: Qt.ImhDate | Qt.ImhFormattedNumbersOnly
+
+ enabled: root.expireDateEnabled &&
+ !root.waitingForExpireDateChange &&
+ !root.waitingForExpireDateEnabledChange
+
+ onValueModified: {
+ root.setExpireDate(value * dayInMSecs);
+ root.waitingForExpireDateChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForExpireDateEnabledChange ||
+ root.waitingForExpireDateChange
+ running: visible
+ z: 1
+ }
+ }
+ }
+
+ CheckBox {
+ id: noteEnabledMenuItem
+
+ spacing: moreMenu.indicatorSpacing
+ padding: moreMenu.itemPadding
+ indicator.width: moreMenu.indicatorItemWidth
+ indicator.height: moreMenu.indicatorItemWidth
+
+ checkable: true
+ checked: root.noteEnabled
+ text: qsTr("Note to recipient")
+ enabled: !root.waitingForNoteEnabledChange
+
+ onClicked: {
+ root.toggleNoteToRecipient(checked);
+ root.waitingForNoteEnabledChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForNoteEnabledChange
+ running: visible
+ z: 1
+ }
+ }
+
+ RowLayout {
+ anchors.left: parent.left
+ anchors.leftMargin: moreMenu.itemPadding
+ anchors.right: parent.right
+ anchors.rightMargin: moreMenu.itemPadding
+ height: visible ? implicitHeight : 0
+ spacing: moreMenu.indicatorSpacing
+
+ visible: root.noteEnabled
+
+ Image {
+ Layout.preferredWidth: moreMenu.indicatorItemWidth
+ Layout.fillHeight: true
+
+ verticalAlignment: Image.AlignVCenter
+ horizontalAlignment: Image.AlignHCenter
+ fillMode: Image.Pad
+
+ source: "image://svgimage-custom-color/edit.svg/" + Style.menuBorder
+ sourceSize.width: moreMenu.rowIconWidth
+ sourceSize.height: moreMenu.rowIconWidth
+ }
+
+ NCInputTextEdit {
+ id: noteTextEdit
+
+ Layout.fillWidth: true
+ height: visible ? Math.max(Style.talkReplyTextFieldPreferredHeight, contentHeight) : 0
+ submitButton.height: Math.min(Style.talkReplyTextFieldPreferredHeight, height - 2)
+
+ text: root.note
+ enabled: root.noteEnabled &&
+ !root.waitingForNoteChange &&
+ !root.waitingForNoteEnabledChange
+
+ onEditingFinished: if(text !== root.note) {
+ root.setNote(text);
+ root.waitingForNoteChange = true;
+ }
+
+ NCBusyIndicator {
+ anchors.fill: parent
+ visible: root.waitingForNoteChange ||
+ root.waitingForNoteEnabledChange
+ running: visible
+ z: 1
+ }
+ }
+ }
+
+ MenuItem {
+ spacing: moreMenu.indicatorSpacing
+ padding: moreMenu.itemPadding
+
+ icon.width: moreMenu.indicatorItemWidth
+ icon.height: moreMenu.indicatorItemWidth
+ icon.color: Style.ncTextColor
+ icon.source: "qrc:///client/theme/close.svg"
+ text: qsTr("Unshare")
+
+ onTriggered: root.deleteShare()
+ }
+
+ MenuItem {
+ height: visible ? implicitHeight : 0
+ spacing: moreMenu.indicatorSpacing
+ padding: moreMenu.itemPadding
+
+ icon.width: moreMenu.indicatorItemWidth
+ icon.height: moreMenu.indicatorItemWidth
+ icon.color: Style.ncTextColor
+ icon.source: "qrc:///client/theme/add.svg"
+ text: qsTr("Add another link")
+
+ visible: root.isLinkShare && root.canCreateLinkShares
+ enabled: visible
+
+ onTriggered: root.createNewLinkShare()
+ }
+ }
+ }
+ }
+}
diff --git a/src/gui/filedetails/ShareView.qml b/src/gui/filedetails/ShareView.qml
new file mode 100644
index 000000000..eb8da6f9e
--- /dev/null
+++ b/src/gui/filedetails/ShareView.qml
@@ -0,0 +1,309 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+import "../"
+
+ColumnLayout {
+ id: root
+
+ property string localPath: ""
+ property var accountState: ({})
+ property FileDetails fileDetails: FileDetails {}
+ property int horizontalPadding: 0
+ property int iconSize: 32
+
+ readonly property bool sharingPossible: shareModel && shareModel.canShare && shareModel.sharingEnabled
+ readonly property bool userGroupSharingPossible: sharingPossible && shareModel.userGroupSharingEnabled
+ readonly property bool publicLinkSharingPossible: sharingPossible && shareModel.publicLinkSharesEnabled
+
+ readonly property bool loading: sharingPossible && (!shareModel ||
+ shareModel.fetchOngoing ||
+ !shareModel.hasInitialShareFetchCompleted ||
+ waitingForSharesToChange)
+ property bool waitingForSharesToChange: true // Gets changed to false when listview count changes
+ property bool stopWaitingForSharesToChangeOnPasswordError: false
+
+ readonly property ShareModel shareModel: ShareModel {
+ accountState: root.accountState
+ localPath: root.localPath
+
+ onSharesChanged: root.waitingForSharesToChange = false
+
+ onServerError: {
+ if(errorBox.text === "") {
+ errorBox.text = message;
+ } else {
+ errorBox.text += "\n\n" + message
+ }
+
+ errorBox.visible = true;
+ }
+
+ onPasswordSetError: if(root.stopWaitingForSharesToChangeOnPasswordError) {
+ root.waitingForSharesToChange = false;
+ root.stopWaitingForSharesToChangeOnPasswordError = false;
+ }
+
+ onRequestPasswordForLinkShare: shareRequiresPasswordDialog.open()
+ onRequestPasswordForEmailSharee: {
+ shareRequiresPasswordDialog.sharee = sharee;
+ shareRequiresPasswordDialog.open();
+ }
+ }
+
+ Dialog {
+ id: shareRequiresPasswordDialog
+
+ property var sharee
+
+ function discardDialog() {
+ sharee = undefined;
+ root.waitingForSharesToChange = false;
+ close();
+ }
+
+ anchors.centerIn: parent
+ width: parent.width * 0.8
+
+ title: qsTr("Password required for new share")
+ standardButtons: Dialog.Ok | Dialog.Cancel
+ modal: true
+ closePolicy: Popup.NoAutoClose
+
+ // TODO: Rather than setting all these palette colours manually,
+ // create a custom style and do it for all components globally
+ palette {
+ text: Style.ncTextColor
+ windowText: Style.ncTextColor
+ buttonText: Style.ncTextColor
+ light: Style.lightHover
+ midlight: Style.lightHover
+ mid: Style.ncSecondaryTextColor
+ dark: Style.menuBorder
+ button: Style.menuBorder
+ window: Style.backgroundColor
+ base: Style.backgroundColor
+ }
+
+ visible: false
+
+ onAccepted: {
+ if(sharee) {
+ root.shareModel.createNewUserGroupShareWithPasswordFromQml(sharee, dialogPasswordField.text);
+ sharee = undefined;
+ } else {
+ root.shareModel.createNewLinkShareWithPassword(dialogPasswordField.text);
+ }
+
+ root.stopWaitingForSharesToChangeOnPasswordError = true;
+ dialogPasswordField.text = "";
+ }
+ onDiscarded: discardDialog()
+ onRejected: discardDialog()
+
+ NCInputTextField {
+ id: dialogPasswordField
+
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ placeholderText: qsTr("Share password")
+ onAccepted: shareRequiresPasswordDialog.accept()
+ }
+ }
+
+ ErrorBox {
+ id: errorBox
+
+ Layout.fillWidth: true
+ Layout.leftMargin: root.horizontalPadding
+ Layout.rightMargin: root.horizontalPadding
+
+ showCloseButton: true
+ visible: false
+
+ onCloseButtonClicked: {
+ text = "";
+ visible = false;
+ }
+ }
+
+ ShareeSearchField {
+ Layout.fillWidth: true
+ Layout.leftMargin: root.horizontalPadding
+ Layout.rightMargin: root.horizontalPadding
+
+ visible: root.userGroupSharingPossible
+ enabled: visible && !root.loading
+
+ accountState: root.accountState
+ shareItemIsFolder: root.fileDetails && root.fileDetails.isFolder
+
+ onShareeSelected: {
+ root.waitingForSharesToChange = true;
+ root.shareModel.createNewUserGroupShareFromQml(sharee)
+ }
+ }
+
+ Loader {
+ id: sharesViewLoader
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.leftMargin: root.horizontalPadding
+ Layout.rightMargin: root.horizontalPadding
+
+ active: root.sharingPossible
+
+ sourceComponent: ScrollView {
+ id: scrollView
+ anchors.fill: parent
+
+ contentWidth: availableWidth
+ clip: true
+ enabled: root.sharingPossible
+
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+ ListView {
+ id: shareLinksListView
+
+ enabled: !root.loading
+ model: SortedShareModel {
+ shareModel: root.shareModel
+ }
+
+ delegate: ShareDelegate {
+ id: shareDelegate
+
+ Connections {
+ target: root.shareModel
+ // Though we try to handle this internally by listening to onPasswordChanged,
+ // with passwords we will get the same value from the model data when a
+ // password set has failed, meaning we won't be able to easily tell when we
+ // have had a response from the server in QML. So we listen to this signal
+ // directly from the model and do the reset of the password field manually.
+ function onPasswordSetError(shareId) {
+ if(shareId !== model.shareId) {
+ return;
+ }
+ shareDelegate.resetPasswordField();
+ }
+
+ function onServerError() {
+ if(shareId !== model.shareId) {
+ return;
+ }
+
+ shareDelegate.resetMenu();
+ }
+ }
+
+ iconSize: root.iconSize
+ canCreateLinkShares: root.publicLinkSharingPossible
+
+ onCreateNewLinkShare: {
+ root.waitingForSharesToChange = true;
+ shareModel.createNewLinkShare();
+ }
+ onDeleteShare: {
+ root.waitingForSharesToChange = true;
+ shareModel.deleteShareFromQml(model.share);
+ }
+
+ onToggleAllowEditing: shareModel.toggleShareAllowEditingFromQml(model.share, enable)
+ onToggleAllowResharing: shareModel.toggleShareAllowResharingFromQml(model.share, enable)
+ onTogglePasswordProtect: shareModel.toggleSharePasswordProtectFromQml(model.share, enable)
+ onToggleExpirationDate: shareModel.toggleShareExpirationDateFromQml(model.share, enable)
+ onToggleNoteToRecipient: shareModel.toggleShareNoteToRecipientFromQml(model.share, enable)
+
+ onSetLinkShareLabel: shareModel.setLinkShareLabelFromQml(model.share, label)
+ onSetExpireDate: shareModel.setShareExpireDateFromQml(model.share, milliseconds)
+ onSetPassword: shareModel.setSharePasswordFromQml(model.share, password)
+ onSetNote: shareModel.setShareNoteFromQml(model.share, note)
+ }
+
+ Loader {
+ id: sharesFetchingLoader
+ anchors.fill: parent
+ active: root.loading
+ z: Infinity
+
+ sourceComponent: Rectangle {
+ color: Style.backgroundColor
+ opacity: 0.5
+
+ NCBusyIndicator {
+ anchors.centerIn: parent
+ color: Style.ncSecondaryTextColor
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Loader {
+ id: sharingNotPossibleView
+
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ Layout.leftMargin: root.horizontalPadding
+ Layout.rightMargin: root.horizontalPadding
+
+ active: !root.sharingPossible
+
+ sourceComponent: Column {
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.verticalCenter: parent.verticalCenter
+
+ Label {
+ id: sharingDisabledLabel
+ width: parent.width
+ text: qsTr("Sharing is disabled")
+ color: Style.ncSecondaryTextColor
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+ Label {
+ width: parent.width
+ text: qsTr("This item cannot be shared.")
+ color: Style.ncSecondaryTextColor
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ visible: !root.shareModel.canShare
+ }
+ Label {
+ width: parent.width
+ text: qsTr("Sharing is disabled.")
+ color: Style.ncSecondaryTextColor
+ wrapMode: Text.Wrap
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ visible: !root.shareModel.sharingEnabled
+ }
+ }
+ }
+}
diff --git a/src/gui/filedetails/ShareeDelegate.qml b/src/gui/filedetails/ShareeDelegate.qml
new file mode 100644
index 000000000..a9128cb4c
--- /dev/null
+++ b/src/gui/filedetails/ShareeDelegate.qml
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+
+ItemDelegate {
+ id: root
+
+ text: model.display
+}
diff --git a/src/gui/filedetails/ShareeSearchField.qml b/src/gui/filedetails/ShareeSearchField.qml
new file mode 100644
index 000000000..eedf20daa
--- /dev/null
+++ b/src/gui/filedetails/ShareeSearchField.qml
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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.
+ */
+
+import QtQuick 2.15
+import QtQuick.Window 2.15
+import QtQuick.Layouts 1.2
+import QtQuick.Controls 2.15
+
+import com.nextcloud.desktopclient 1.0
+import Style 1.0
+import "../tray"
+
+TextField {
+ id: root
+
+ signal shareeSelected(var sharee)
+
+ property var accountState: ({})
+ property bool shareItemIsFolder: false
+ property ShareeModel shareeModel: ShareeModel {
+ accountState: root.accountState
+ shareItemIsFolder: root.shareItemIsFolder
+ searchString: root.text
+ }
+
+ readonly property int horizontalPaddingOffset: Style.trayHorizontalMargin
+ readonly property color placeholderColor: Style.menuBorder
+ readonly property double iconsScaleFactor: 0.6
+
+ function triggerSuggestionsVisibility() {
+ shareeListView.count > 0 && text !== "" ? suggestionsPopup.open() : suggestionsPopup.close();
+ }
+
+ placeholderText: qsTr("Search for users or groups…")
+ placeholderTextColor: placeholderColor
+ color: Style.ncTextColor
+ enabled: !shareeModel.fetchOngoing
+
+ onActiveFocusChanged: triggerSuggestionsVisibility()
+ onTextChanged: triggerSuggestionsVisibility()
+ Keys.onPressed: {
+ if(suggestionsPopup.visible) {
+ switch(event.key) {
+ case Qt.Key_Escape:
+ suggestionsPopup.close();
+ shareeListView.currentIndex = -1;
+ event.accepted = true;
+ break;
+
+ case Qt.Key_Up:
+ shareeListView.decrementCurrentIndex();
+ event.accepted = true;
+ break;
+
+ case Qt.Key_Down:
+ shareeListView.incrementCurrentIndex();
+ event.accepted = true;
+ break;
+
+ case Qt.Key_Enter:
+ case Qt.Key_Return:
+ if(shareeListView.currentIndex > -1) {
+ shareeListView.itemAtIndex(shareeListView.currentIndex).selectSharee();
+ event.accepted = true;
+ break;
+ }
+ }
+ } else {
+ switch(event.key) {
+ case Qt.Key_Down:
+ triggerSuggestionsVisibility();
+ event.accepted = true;
+ break;
+ }
+ }
+ }
+
+ leftPadding: searchIcon.width + searchIcon.anchors.leftMargin + horizontalPaddingOffset
+ rightPadding: clearTextButton.width + clearTextButton.anchors.rightMargin + horizontalPaddingOffset
+
+ background: Rectangle {
+ radius: 5
+ border.color: parent.activeFocus ? UserModel.currentUser.accentColor : Style.menuBorder
+ border.width: 1
+ color: Style.backgroundColor
+ }
+
+ Image {
+ id: searchIcon
+ anchors {
+ top: parent.top
+ left: parent.left
+ bottom: parent.bottom
+ margins: 4
+ }
+
+ width: height
+
+ smooth: true
+ antialiasing: true
+ mipmap: true
+ fillMode: Image.PreserveAspectFit
+ horizontalAlignment: Image.AlignLeft
+
+ source: "image://svgimage-custom-color/search.svg" + "/" + root.placeholderColor
+ sourceSize: Qt.size(parent.height * root.iconsScaleFactor, parent.height * root.iconsScaleFactor)
+
+ visible: !root.shareeModel.fetchOngoing
+ }
+
+ NCBusyIndicator {
+ id: busyIndicator
+
+ anchors {
+ top: parent.top
+ left: parent.left
+ bottom: parent.bottom
+ }
+
+ width: height
+ color: root.placeholderColor
+ visible: root.shareeModel.fetchOngoing
+ running: visible
+ }
+
+ Image {
+ id: clearTextButton
+
+ anchors {
+ top: parent.top
+ right: parent.right
+ bottom: parent.bottom
+ margins: 4
+ }
+
+ width: height
+
+ smooth: true
+ antialiasing: true
+ mipmap: true
+ fillMode: Image.PreserveAspectFit
+
+ source: "image://svgimage-custom-color/clear.svg" + "/" + root.placeholderColor
+ sourceSize: Qt.size(parent.height * root.iconsScaleFactor, parent.height * root.iconsScaleFactor)
+
+ visible: root.text
+
+ MouseArea {
+ id: clearTextButtonMouseArea
+ anchors.fill: parent
+ onClicked: root.clear()
+ }
+ }
+
+ Popup {
+ id: suggestionsPopup
+
+ width: root.width
+ height: 100
+ y: root.height
+
+ // TODO: Rather than setting all these palette colours manually,
+ // create a custom style and do it for all components globally
+ palette {
+ text: Style.ncTextColor
+ windowText: Style.ncTextColor
+ buttonText: Style.ncTextColor
+ light: Style.lightHover
+ midlight: Style.lightHover
+ mid: Style.ncSecondaryTextColor
+ dark: Style.menuBorder
+ button: Style.menuBorder
+ window: Style.backgroundColor
+ base: Style.backgroundColor
+ }
+
+ contentItem: ScrollView {
+ id: suggestionsScrollView
+
+ clip: true
+ ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+
+ ListView {
+ id: shareeListView
+
+ spacing: 0
+ currentIndex: -1
+ interactive: true
+
+ highlight: Rectangle {
+ width: shareeListView.currentItem.width
+ height: shareeListView.currentItem.height
+ color: Style.lightHover
+ }
+ highlightFollowsCurrentItem: true
+ highlightMoveDuration: 0
+ highlightResizeDuration: 0
+ highlightRangeMode: ListView.ApplyRange
+ preferredHighlightBegin: 0
+ preferredHighlightEnd: suggestionsScrollView.height
+
+ onCountChanged: root.triggerSuggestionsVisibility()
+
+ model: root.shareeModel
+ delegate: ShareeDelegate {
+ anchors.left: parent.left
+ anchors.right: parent.right
+
+ function selectSharee() {
+ root.shareeSelected(model.sharee);
+ suggestionsPopup.close();
+
+ root.clear();
+ }
+
+ onHoveredChanged: if (hovered) {
+ // When we set the currentIndex the list view will scroll...
+ // unless we tamper with the preferred highlight points to stop this.
+ const savedPreferredHighlightBegin = shareeListView.preferredHighlightBegin;
+ const savedPreferredHighlightEnd = shareeListView.preferredHighlightEnd;
+ // Set overkill values to make sure no scroll happens when we hover with mouse
+ shareeListView.preferredHighlightBegin = -suggestionsScrollView.height;
+ shareeListView.preferredHighlightEnd = suggestionsScrollView.height * 2;
+
+ shareeListView.currentIndex = index
+
+ // Reset original values so keyboard navigation makes list view scroll
+ shareeListView.preferredHighlightBegin = savedPreferredHighlightBegin;
+ shareeListView.preferredHighlightEnd = savedPreferredHighlightEnd;
+ }
+ onClicked: selectSharee()
+ }
+ }
+ }
+ }
+}
diff --git a/src/gui/filedetails/filedetails.cpp b/src/gui/filedetails/filedetails.cpp
new file mode 100644
index 000000000..b7fa88a18
--- /dev/null
+++ b/src/gui/filedetails/filedetails.cpp
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 <QDateTime>
+
+#include "filedetails.h"
+#include "folderman.h"
+
+namespace OCC {
+
+FileDetails::FileDetails(QObject *parent)
+ : QObject(parent)
+{
+ _filelockStateUpdateTimer.setInterval(6000);
+ _filelockStateUpdateTimer.setSingleShot(false);
+ connect(&_filelockStateUpdateTimer, &QTimer::timeout, this, &FileDetails::updateLockExpireString);
+}
+
+void FileDetails::refreshFileDetails()
+{
+ _fileInfo.refresh();
+ Q_EMIT fileChanged();
+}
+
+QString FileDetails::localPath() const
+{
+ return _localPath;
+}
+
+void FileDetails::setLocalPath(const QString &localPath)
+{
+ if(localPath.isEmpty()) {
+ return;
+ }
+
+ if(!_localPath.isEmpty()) {
+ _fileWatcher.removePath(_localPath);
+ }
+
+ if(_fileInfo.exists()) {
+ disconnect(&_fileWatcher, &QFileSystemWatcher::fileChanged, this, &FileDetails::refreshFileDetails);
+ }
+
+ _localPath = localPath;
+ _fileInfo = QFileInfo(localPath);
+
+ _fileWatcher.addPath(localPath);
+ connect(&_fileWatcher, &QFileSystemWatcher::fileChanged, this, &FileDetails::refreshFileDetails);
+
+ const auto folder = FolderMan::instance()->folderForPath(_localPath);
+ const auto file = _localPath.mid(folder->cleanPath().length() + 1);
+
+ folder->journalDb()->getFileRecord(file, &_fileRecord);
+
+ _filelockState = _fileRecord._lockstate;
+ updateLockExpireString();
+
+ Q_EMIT fileChanged();
+}
+
+QString FileDetails::name() const
+{
+ return _fileInfo.fileName();
+}
+
+QString FileDetails::sizeString() const
+{
+ return _locale.formattedDataSize(_fileInfo.size());
+}
+
+QString FileDetails::lastChangedString() const
+{
+ static constexpr auto secsInMinute = 60;
+ static constexpr auto secsInHour = secsInMinute * 60;
+ static constexpr auto secsInDay = secsInHour * 24;
+ static constexpr auto secsInMonth = secsInDay * 30;
+ static constexpr auto secsInYear = secsInMonth * 12;
+
+ const auto elapsedSecs = _fileInfo.lastModified().secsTo(QDateTime::currentDateTime());
+
+ if(elapsedSecs < 60) {
+ const auto elapsedSecsAsInt = static_cast<int>(elapsedSecs);
+ return tr("%1 second(s) ago", "seconds elapsed since file last modified", elapsedSecsAsInt).arg(elapsedSecsAsInt);
+ } else if (elapsedSecs < secsInHour) {
+ const auto elapsedMinutes = static_cast<int>(elapsedSecs / secsInMinute);
+ return tr("%1 minute(s) ago", "minutes elapsed since file last modified", elapsedMinutes).arg(elapsedMinutes);
+ } else if (elapsedSecs < secsInDay) {
+ const auto elapsedHours = static_cast<int>(elapsedSecs / secsInHour);
+ return tr("%1 hour(s) ago", "hours elapsed since file last modified", elapsedHours).arg(elapsedHours);
+ } else if (elapsedSecs < secsInMonth) {
+ const auto elapsedDays = static_cast<int>(elapsedSecs / secsInDay);
+ return tr("%1 day(s) ago", "days elapsed since file last modified", elapsedDays).arg(elapsedDays);
+ } else if (elapsedSecs < secsInYear) {
+ const auto elapsedMonths = static_cast<int>(elapsedSecs / secsInMonth);
+ return tr("%1 month(s) ago", "months elapsed since file last modified", elapsedMonths).arg(elapsedMonths);
+ } else {
+ const auto elapsedYears = static_cast<int>(elapsedSecs / secsInYear);
+ return tr("%1 year(s) ago", "years elapsed since file last modified", elapsedYears).arg(elapsedYears);
+ }
+}
+
+QString FileDetails::iconUrl() const
+{
+ return QStringLiteral("image://tray-image-provider/:/fileicon") + _localPath;
+}
+
+QString FileDetails::lockExpireString() const
+{
+ return _lockExpireString;
+}
+
+void FileDetails::updateLockExpireString()
+{
+ if(!_filelockState._locked) {
+ _filelockStateUpdateTimer.stop();
+ _lockExpireString = QString();
+ Q_EMIT lockExpireStringChanged();
+ return;
+ }
+
+ if(!_filelockStateUpdateTimer.isActive()) {
+ _filelockStateUpdateTimer.start();
+ }
+
+ static constexpr auto SECONDS_PER_MINUTE = 60;
+ const auto lockExpirationTime = _filelockState._lockTime + _filelockState._lockTimeout;
+ const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
+ const auto remainingTimeInMinutes = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
+
+ _lockExpireString = tr("Locked by %1 - Expires in %2 minute(s)", "remaining time before lock expires", remainingTimeInMinutes).arg(_filelockState._lockOwnerDisplayName).arg(remainingTimeInMinutes);
+ Q_EMIT lockExpireStringChanged();
+}
+
+bool FileDetails::isFolder() const
+{
+ return _fileInfo.isDir();
+}
+
+} // namespace OCC
diff --git a/src/gui/filedetails/filedetails.h b/src/gui/filedetails/filedetails.h
new file mode 100644
index 000000000..7b3384709
--- /dev/null
+++ b/src/gui/filedetails/filedetails.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 <QFileInfo>
+#include <QFileSystemWatcher>
+#include <QLocale>
+#include <QTimer>
+
+#include "common/syncjournalfilerecord.h"
+
+namespace OCC {
+
+class FileDetails : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QString localPath READ localPath WRITE setLocalPath NOTIFY localPathChanged)
+ Q_PROPERTY(QString name READ name NOTIFY fileChanged)
+ Q_PROPERTY(QString sizeString READ sizeString NOTIFY fileChanged)
+ Q_PROPERTY(QString lastChangedString READ lastChangedString NOTIFY fileChanged)
+ Q_PROPERTY(QString iconUrl READ iconUrl NOTIFY fileChanged)
+ Q_PROPERTY(QString lockExpireString READ lockExpireString NOTIFY lockExpireStringChanged)
+ Q_PROPERTY(bool isFolder READ isFolder NOTIFY isFolderChanged)
+
+public:
+ explicit FileDetails(QObject *parent = nullptr);
+
+ QString localPath() const;
+ QString name() const;
+ QString sizeString() const;
+ QString lastChangedString() const;
+ QString iconUrl() const;
+ QString lockExpireString() const;
+ bool isFolder() const;
+
+public slots:
+ void setLocalPath(const QString &localPath);
+
+signals:
+ void localPathChanged();
+ void fileChanged();
+ void lockExpireStringChanged();
+ void isFolderChanged();
+
+private slots:
+ void refreshFileDetails();
+ void updateLockExpireString();
+
+private:
+ QString _localPath;
+
+ QFileInfo _fileInfo;
+ QFileSystemWatcher _fileWatcher;
+ SyncJournalFileRecord _fileRecord;
+ SyncJournalFileLockInfo _filelockState;
+ QByteArray _numericFileId;
+ QString _lockExpireString;
+ QTimer _filelockStateUpdateTimer;
+
+ QLocale _locale;
+};
+
+} // namespace OCC
diff --git a/src/gui/filedetails/shareemodel.cpp b/src/gui/filedetails/shareemodel.cpp
new file mode 100644
index 000000000..ddc9d7ac8
--- /dev/null
+++ b/src/gui/filedetails/shareemodel.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 "shareemodel.h"
+
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QJsonArray>
+
+#include "ocsshareejob.h"
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcShareeModel, "com.nextcloud.shareemodel")
+
+ShareeModel::ShareeModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+ _userStoppedTypingTimer.setSingleShot(true);
+ _userStoppedTypingTimer.setInterval(500);
+ connect(&_userStoppedTypingTimer, &QTimer::timeout, this, &ShareeModel::fetch);
+}
+
+// ---------------------- QAbstractListModel methods ---------------------- //
+
+int ShareeModel::rowCount(const QModelIndex &parent) const
+{
+ if(parent.isValid() || !_accountState) {
+ return 0;
+ }
+
+ return _sharees.count();
+}
+
+QHash<int, QByteArray> ShareeModel::roleNames() const
+{
+ auto roles = QAbstractListModel::roleNames();
+ roles[ShareeRole] = "sharee";
+ roles[AutoCompleterStringMatchRole] = "autoCompleterStringMatch";
+
+ return roles;
+}
+
+QVariant ShareeModel::data(const QModelIndex &index, const int role) const
+{
+ if (index.row() < 0 || index.row() > _sharees.size()) {
+ return {};
+ }
+
+ const auto sharee = _sharees.at(index.row());
+
+ if(sharee.isNull()) {
+ return {};
+ }
+
+ switch(role) {
+ case Qt::DisplayRole:
+ return sharee->format();
+ case AutoCompleterStringMatchRole:
+ // Don't show this to the user
+ return QString(sharee->displayName() + " (" + sharee->shareWith() + ")");
+ case ShareeRole:
+ return QVariant::fromValue(sharee);
+ default:
+ qCWarning(lcShareeModel) << "Got unknown role -- returning null value.";
+ return {};
+ }
+}
+
+// --------------------------- QPROPERTY methods --------------------------- //
+
+AccountState *ShareeModel::accountState() const
+{
+ return _accountState.data();
+}
+
+void ShareeModel::setAccountState(AccountState *accountState)
+{
+ _accountState = accountState;
+ Q_EMIT accountStateChanged();
+}
+
+bool ShareeModel::shareItemIsFolder() const
+{
+ return _shareItemIsFolder;
+}
+
+void ShareeModel::setShareItemIsFolder(const bool shareItemIsFolder)
+{
+ _shareItemIsFolder = shareItemIsFolder;
+ Q_EMIT shareItemIsFolderChanged();
+}
+
+QString ShareeModel::searchString() const
+{
+ return _searchString;
+}
+
+void ShareeModel::setSearchString(const QString &searchString)
+{
+ _searchString = searchString;
+ Q_EMIT searchStringChanged();
+
+ _userStoppedTypingTimer.start();
+}
+
+bool ShareeModel::fetchOngoing() const
+{
+ return _fetchOngoing;
+}
+
+ShareeModel::LookupMode ShareeModel::lookupMode() const
+{
+ return _lookupMode;
+}
+
+void ShareeModel::setLookupMode(const ShareeModel::LookupMode lookupMode)
+{
+ _lookupMode = lookupMode;
+ Q_EMIT lookupModeChanged();
+}
+
+// ------------------------- Internal data methods ------------------------- //
+
+void ShareeModel::fetch()
+{
+ if(!_accountState || !_accountState->account() || _searchString.isEmpty()) {
+ qCInfo(lcShareeModel) << "Not fetching sharees for searchString: " << _searchString;
+ return;
+ }
+
+ _fetchOngoing = true;
+ Q_EMIT fetchOngoingChanged();
+
+ const auto shareItemTypeString = _shareItemIsFolder ? QStringLiteral("folder") : QStringLiteral("file");
+
+ auto *job = new OcsShareeJob(_accountState->account());
+
+ connect(job, &OcsShareeJob::shareeJobFinished, this, &ShareeModel::shareesFetched);
+ connect(job, &OcsJob::ocsError, this, [&](const int statusCode, const QString &message) {
+ _fetchOngoing = false;
+ Q_EMIT fetchOngoingChanged();
+ Q_EMIT ShareeModel::displayErrorMessage(statusCode, message);
+ });
+
+ job->getSharees(_searchString, shareItemTypeString, 1, 50, _lookupMode == LookupMode::GlobalSearch ? true : false);
+}
+
+void ShareeModel::shareesFetched(const QJsonDocument &reply)
+{
+ _fetchOngoing = false;
+ Q_EMIT fetchOngoingChanged();
+
+ qCInfo(lcShareeModel) << "SearchString: " << _searchString << "resulted in reply: " << reply;
+
+ QVector<ShareePtr> newSharees;
+
+ const QStringList shareeTypes {"users", "groups", "emails", "remotes", "circles", "rooms"};
+
+ const auto appendSharees = [this, &shareeTypes, &newSharees](const QJsonObject &data) {
+ for (const auto &shareeType : shareeTypes) {
+ const auto category = data.value(shareeType).toArray();
+
+ for (const auto &sharee : category) {
+ const auto shareeJsonObject = sharee.toObject();
+ const auto parsedSharee = parseSharee(shareeJsonObject);
+
+ // Filter sharees that we have already shared with
+ const auto shareeInBlacklistIt = std::find_if(_shareeBlacklist.cbegin(),
+ _shareeBlacklist.cend(),
+ [&parsedSharee](const ShareePtr &blacklistSharee) {
+ return parsedSharee->type() == blacklistSharee->type() &&
+ parsedSharee->shareWith() == blacklistSharee->shareWith();
+ });
+
+ if (shareeInBlacklistIt != _shareeBlacklist.cend()) {
+ continue;
+ }
+
+ newSharees.append(parsedSharee);
+ }
+ }
+ };
+ const auto replyDataObject = reply.object().value("ocs").toObject().value("data").toObject();
+ const auto replyDataExactMatchObject = replyDataObject.value("exact").toObject();
+
+ appendSharees(replyDataObject);
+ appendSharees(replyDataExactMatchObject);
+
+ Q_EMIT beginResetModel();
+ _sharees = newSharees;
+ Q_EMIT endResetModel();
+
+ Q_EMIT shareesReady();
+}
+
+ShareePtr ShareeModel::parseSharee(const QJsonObject &data) const
+{
+ auto displayName = data.value("label").toString();
+ const auto shareWith = data.value("value").toObject().value("shareWith").toString();
+ const auto type = (Sharee::Type)data.value("value").toObject().value("shareType").toInt();
+ const auto additionalInfo = data.value("value").toObject().value("shareWithAdditionalInfo").toString();
+ if (!additionalInfo.isEmpty()) {
+ displayName = tr("%1 (%2)", "sharee (shareWithAdditionalInfo)").arg(displayName, additionalInfo);
+ }
+
+ return ShareePtr(new Sharee(shareWith, displayName, type));
+}
+
+}
diff --git a/src/gui/filedetails/shareemodel.h b/src/gui/filedetails/shareemodel.h
new file mode 100644
index 000000000..954f10ad7
--- /dev/null
+++ b/src/gui/filedetails/shareemodel.h
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 <QAbstractListModel>
+#include <QTimer>
+
+#include "accountstate.h"
+#include "sharee.h"
+
+class QJsonDocument;
+class QJsonObject;
+
+namespace OCC {
+
+class ShareeModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(AccountState* accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
+ Q_PROPERTY(bool shareItemIsFolder READ shareItemIsFolder WRITE setShareItemIsFolder NOTIFY shareItemIsFolderChanged)
+ Q_PROPERTY(QString searchString READ searchString WRITE setSearchString NOTIFY searchStringChanged)
+ Q_PROPERTY(bool fetchOngoing READ fetchOngoing NOTIFY fetchOngoingChanged)
+ Q_PROPERTY(LookupMode lookupMode READ lookupMode WRITE setLookupMode NOTIFY lookupModeChanged)
+
+public:
+ enum class LookupMode {
+ LocalSearch = 0,
+ GlobalSearch = 1
+ };
+ Q_ENUM(LookupMode);
+
+ enum Roles {
+ ShareeRole = Qt::UserRole + 1,
+ AutoCompleterStringMatchRole,
+ };
+ Q_ENUM(Roles);
+
+ explicit ShareeModel(QObject *parent = nullptr);
+
+ using ShareeSet = QVector<ShareePtr>; // FIXME: make it a QSet<Sharee> when Sharee can be compared
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QHash<int, QByteArray> roleNames() const override;
+ QVariant data(const QModelIndex &index, const int role) const override;
+
+ AccountState *accountState() const;
+ bool shareItemIsFolder() const;
+ QString searchString() const;
+ bool fetchOngoing() const;
+ LookupMode lookupMode() const;
+
+signals:
+ void accountStateChanged();
+ void shareItemIsFolderChanged();
+ void searchStringChanged();
+ void fetchOngoingChanged();
+ void lookupModeChanged();
+
+ void shareesReady();
+ void displayErrorMessage(int code, const QString &);
+
+public slots:
+ void setAccountState(AccountState *accountState);
+ void setShareItemIsFolder(const bool shareItemIsFolder);
+ void setSearchString(const QString &searchString);
+ void setLookupMode(const LookupMode lookupMode);
+
+ void fetch();
+
+private slots:
+ void shareesFetched(const QJsonDocument &reply);
+
+private:
+ ShareePtr parseSharee(const QJsonObject &data) const;
+
+ QTimer _userStoppedTypingTimer;
+
+ AccountStatePtr _accountState;
+ QString _searchString;
+ bool _shareItemIsFolder = false;
+ bool _fetchOngoing = false;
+ LookupMode _lookupMode = LookupMode::LocalSearch;
+
+ QVector<ShareePtr> _sharees;
+ QVector<ShareePtr> _shareeBlacklist;
+};
+
+}
diff --git a/src/gui/filedetails/sharemodel.cpp b/src/gui/filedetails/sharemodel.cpp
new file mode 100644
index 000000000..fba85c700
--- /dev/null
+++ b/src/gui/filedetails/sharemodel.cpp
@@ -0,0 +1,973 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 "sharemodel.h"
+
+#include <QFileInfo>
+#include <QTimeZone>
+
+#include "account.h"
+#include "folderman.h"
+#include "theme.h"
+#include "wordlist.h"
+
+namespace {
+
+static const QString placeholderLinkShareId = QStringLiteral("__placeholderLinkShareId__");
+
+QString createRandomPassword()
+{
+ const auto words = OCC::WordList::getRandomWords(10);
+
+ const auto addFirstLetter = [](const QString &current, const QString &next) -> QString {
+ return current + next.at(0);
+ };
+
+ return std::accumulate(std::cbegin(words), std::cend(words), QString(), addFirstLetter);
+}
+}
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcShareModel, "com.nextcloud.sharemodel")
+
+ShareModel::ShareModel(QObject *parent)
+ : QAbstractListModel(parent)
+{
+}
+
+// ---------------------- QAbstractListModel methods ---------------------- //
+
+int ShareModel::rowCount(const QModelIndex &parent) const
+{
+ if(parent.isValid() || !_accountState || _localPath.isEmpty()) {
+ return 0;
+ }
+
+ return _shares.count();
+}
+
+QHash<int, QByteArray> ShareModel::roleNames() const
+{
+ auto roles = QAbstractListModel::roleNames();
+ roles[ShareRole] = "share";
+ roles[ShareTypeRole] = "shareType";
+ roles[ShareIdRole] = "shareId";
+ roles[IconUrlRole] = "iconUrl";
+ roles[AvatarUrlRole] = "avatarUrl";
+ roles[LinkRole] = "link";
+ roles[LinkShareNameRole] = "linkShareName";
+ roles[LinkShareLabelRole] = "linkShareLabel";
+ roles[NoteEnabledRole] = "noteEnabled";
+ roles[NoteRole] = "note";
+ roles[ExpireDateEnabledRole] = "expireDateEnabled";
+ roles[ExpireDateEnforcedRole] = "expireDateEnforced";
+ roles[ExpireDateRole] = "expireDate";
+ roles[EnforcedMaximumExpireDateRole] = "enforcedMaximumExpireDate";
+ roles[PasswordProtectEnabledRole] = "passwordProtectEnabled";
+ roles[PasswordRole] = "password";
+ roles[PasswordEnforcedRole] = "passwordEnforced";
+ roles[EditingAllowedRole] = "editingAllowed";
+
+ return roles;
+}
+
+QVariant ShareModel::data(const QModelIndex &index, const int role) const
+{
+ if (!index.isValid()) {
+ return {};
+ }
+
+ const auto share = _shares.at(index.row());
+
+ if (!share) {
+ return {};
+ }
+
+ // Some roles only provide values for the link and user/group share types
+ if(const auto linkShare = share.objectCast<LinkShare>()) {
+ switch(role) {
+ case LinkRole:
+ return linkShare->getLink();
+ case LinkShareNameRole:
+ return linkShare->getName();
+ case LinkShareLabelRole:
+ return linkShare->getLabel();
+ case NoteEnabledRole:
+ return !linkShare->getNote().isEmpty();
+ case NoteRole:
+ return linkShare->getNote();
+ case ExpireDateEnabledRole:
+ return linkShare->getExpireDate().isValid();
+ case ExpireDateRole:
+ {
+ const auto startOfExpireDayUTC = linkShare->getExpireDate().startOfDay(QTimeZone::utc());
+ return startOfExpireDayUTC.toMSecsSinceEpoch();
+ }
+ default:
+ break;
+ }
+
+ } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+ switch(role) {
+ case NoteEnabledRole:
+ return !userGroupShare->getNote().isEmpty();
+ case NoteRole:
+ return userGroupShare->getNote();
+ case ExpireDateEnabledRole:
+ return userGroupShare->getExpireDate().isValid();
+ case ExpireDateRole:
+ {
+ const auto startOfExpireDayUTC = userGroupShare->getExpireDate().startOfDay(QTimeZone::utc());
+ return startOfExpireDayUTC.toMSecsSinceEpoch();
+ }
+ default:
+ break;
+ }
+ }
+
+ switch(role) {
+ case Qt::DisplayRole:
+ return displayStringForShare(share);
+ case ShareRole:
+ return QVariant::fromValue(share);
+ case ShareTypeRole:
+ return share->getShareType();
+ case ShareIdRole:
+ return share->getId();
+ case IconUrlRole:
+ return iconUrlForShare(share);
+ case AvatarUrlRole:
+ return avatarUrlForShare(share);
+ case ExpireDateEnforcedRole:
+ return expireDateEnforcedForShare(share);
+ case EnforcedMaximumExpireDateRole:
+ return enforcedMaxExpireDateForShare(share);
+ case PasswordProtectEnabledRole:
+ return share->isPasswordSet();
+ case PasswordRole:
+ if (!share->isPasswordSet() || !_shareIdRecentlySetPasswords.contains(share->getId())) {
+ return {};
+ }
+ return _shareIdRecentlySetPasswords.value(share->getId());
+ case PasswordEnforcedRole:
+ return _accountState && _accountState->account() && _accountState->account()->capabilities().isValid() &&
+ ((share->getShareType() == Share::TypeEmail && _accountState->account()->capabilities().shareEmailPasswordEnforced()) ||
+ (share->getShareType() == Share::TypeLink && _accountState->account()->capabilities().sharePublicLinkEnforcePassword()));
+ case EditingAllowedRole:
+ return share->getPermissions().testFlag(SharePermissionUpdate);
+
+ // Deal with roles that only return certain values for link or user/group share types
+ case NoteEnabledRole:
+ case ExpireDateEnabledRole:
+ return false;
+ case LinkRole:
+ case LinkShareNameRole:
+ case LinkShareLabelRole:
+ case NoteRole:
+ case ExpireDateRole:
+ return {};
+ default:
+ qCWarning(lcShareModel) << "Got unknown role" << role
+ << "for share of type" << share->getShareType()
+ << "so returning null value.";
+ return {};
+ }
+}
+
+// ---------------------- Internal model data methods ---------------------- //
+
+void ShareModel::resetData()
+{
+ beginResetModel();
+
+ _folder = nullptr;
+ _sharePath.clear();
+ _maxSharingPermissions = {};
+ _numericFileId.clear();
+ _manager.clear();
+ _shares.clear();
+ _fetchOngoing = false;
+ _hasInitialShareFetchCompleted = false;
+
+ Q_EMIT fetchOngoingChanged();
+ Q_EMIT hasInitialShareFetchCompletedChanged();
+
+ endResetModel();
+}
+
+void ShareModel::updateData()
+{
+ resetData();
+
+ if (_localPath.isEmpty() || !_accountState || _accountState->account().isNull()) {
+ qCWarning(lcShareModel) << "Not updating share model data. Local path is:" << _localPath
+ << "Is account state null:" << !_accountState;
+ return;
+ }
+
+ if (!sharingEnabled()) {
+ qCWarning(lcShareModel) << "Server does not support sharing";
+ return;
+ }
+
+ _folder = FolderMan::instance()->folderForPath(_localPath);
+
+ if (!_folder) {
+ qCWarning(lcShareModel) << "Could not update share model data for" << _localPath << "no responsible folder found";
+ resetData();
+ return;
+ }
+
+ qCDebug(lcShareModel) << "Updating share model data now.";
+
+ const auto relPath = _localPath.mid(_folder->cleanPath().length() + 1);
+ _sharePath = _folder->remotePathTrailingSlash() + relPath;
+
+ SyncJournalFileRecord fileRecord;
+ bool resharingAllowed = true; // lets assume the good
+
+ if(_folder->journalDb()->getFileRecord(relPath, &fileRecord) && fileRecord.isValid()) {
+ if (!fileRecord._remotePerm.isNull() &&
+ !fileRecord._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
+
+ resharingAllowed = false;
+ }
+ }
+
+ _maxSharingPermissions = resharingAllowed ? SharePermissions(_accountState->account()->capabilities().shareDefaultPermissions()) : SharePermissions({});
+ Q_EMIT sharePermissionsChanged();
+
+ _numericFileId = fileRecord.numericFileId();
+
+ _placeholderLinkShare.reset(new Share(_accountState->account(),
+ placeholderLinkShareId,
+ _accountState->account()->id(),
+ _accountState->account()->davDisplayName(),
+ _sharePath,
+ Share::TypePlaceholderLink));
+ slotAddShare(_placeholderLinkShare);
+
+ auto job = new PropfindJob(_accountState->account(), _sharePath);
+ job->setProperties(
+ QList<QByteArray>()
+ << "https://open-collaboration-services.org/ns:share-permissions"
+ << "https://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
+ << "https://owncloud.org/ns:privatelink");
+ job->setTimeout(10 * 1000);
+ connect(job, &PropfindJob::result, this, &ShareModel::slotPropfindReceived);
+ connect(job, &PropfindJob::finishedWithError, this, [&]{
+ qCWarning(lcShareModel) << "Propfind for" << _sharePath << "failed";
+ _fetchOngoing = false;
+ Q_EMIT fetchOngoingChanged();
+ });
+
+ _fetchOngoing = true;
+ Q_EMIT fetchOngoingChanged();
+ job->start();
+
+ initShareManager();
+}
+
+void ShareModel::initShareManager()
+{
+ if (!_accountState || _accountState->account().isNull()) {
+ return;
+ }
+
+ bool sharingPossible = true;
+ if (!publicLinkSharesEnabled()) {
+ qCWarning(lcSharing) << "Link shares have been disabled";
+ sharingPossible = false;
+ } else if (!canShare()) {
+ qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
+ sharingPossible = false;
+ }
+
+ if (_manager.isNull() && sharingPossible) {
+ _manager.reset(new ShareManager(_accountState->account(), this));
+ connect(_manager.data(), &ShareManager::sharesFetched, this, &ShareModel::slotSharesFetched);
+ connect(_manager.data(), &ShareManager::shareCreated, this, [&]{ _manager->fetchShares(_sharePath); });
+ connect(_manager.data(), &ShareManager::linkShareCreated, this, &ShareModel::slotAddShare);
+ connect(_manager.data(), &ShareManager::linkShareRequiresPassword, this, &ShareModel::requestPasswordForLinkShare);
+
+ _manager->fetchShares(_sharePath);
+ }
+}
+
+void ShareModel::slotPropfindReceived(const QVariantMap &result)
+{
+ _fetchOngoing = false;
+ Q_EMIT fetchOngoingChanged();
+
+ const QVariant receivedPermissions = result["share-permissions"];
+ if (!receivedPermissions.toString().isEmpty()) {
+ _maxSharingPermissions = static_cast<SharePermissions>(receivedPermissions.toInt());
+ Q_EMIT sharePermissionsChanged();
+ qCInfo(lcShareModel) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions;
+ }
+
+ const auto privateLinkUrl = result["privatelink"].toString();
+ const auto numericFileId = result["fileid"].toByteArray();
+
+ if (!privateLinkUrl.isEmpty()) {
+ qCInfo(lcShareModel) << "Received private link url for" << _sharePath << privateLinkUrl;
+ _privateLinkUrl = privateLinkUrl;
+ } else if (!numericFileId.isEmpty()) {
+ qCInfo(lcShareModel) << "Received numeric file id for" << _sharePath << numericFileId;
+ _privateLinkUrl = _accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded);
+ }
+}
+
+void ShareModel::slotSharesFetched(const QList<SharePtr> &shares)
+{
+ _hasInitialShareFetchCompleted = true;
+ Q_EMIT hasInitialShareFetchCompletedChanged();
+
+ qCInfo(lcSharing) << "Fetched" << shares.count() << "shares";
+
+ for (const auto &share : shares) {
+ if (share.isNull() ||
+ share->account().isNull() ||
+ share->getUidOwner() != share->account()->davUser()) {
+
+ continue;
+ }
+
+ slotAddShare(share);
+ }
+}
+
+void ShareModel::slotAddShare(const SharePtr &share)
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ const auto shareId = share->getId();
+
+ // Remove placeholder link share if this is a link share
+ if(share->getShareType() == Share::TypeLink) {
+ slotRemoveShareWithId(placeholderLinkShareId);
+ }
+
+ QModelIndex shareModelIndex;
+
+ if (_shareIdIndexHash.contains(shareId)) {
+ const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+ const auto shareIndex = sharePersistentModelIndex.row();
+
+ _shares.replace(shareIndex, share);
+
+ shareModelIndex = index(sharePersistentModelIndex.row());
+ Q_EMIT dataChanged(shareModelIndex, shareModelIndex);
+ } else {
+ const auto shareIndex = _shares.count();
+
+ beginInsertRows({}, _shares.count(), _shares.count());
+ _shares.append(share);
+ endInsertRows();
+
+ shareModelIndex = index(shareIndex);
+ }
+
+ const QPersistentModelIndex sharePersistentIndex(shareModelIndex);
+ _shareIdIndexHash.insert(shareId, sharePersistentIndex);
+
+ connect(share.data(), &Share::serverError, this, &ShareModel::slotServerError);
+ connect(share.data(), &Share::passwordSetError, this, [this, shareId](const int code, const QString &message) {
+ _shareIdRecentlySetPasswords.remove(shareId);
+ slotServerError(code, message);
+ slotSharePasswordSet(shareId);
+ Q_EMIT passwordSetError(shareId);
+ });
+
+ // Passing shareId by reference here will cause crashing, so we pass by value
+ connect(share.data(), &Share::shareDeleted, this, [this, shareId]{ slotRemoveShareWithId(shareId); });
+ connect(share.data(), &Share::permissionsSet, this, [this, shareId]{ slotSharePermissionsSet(shareId); });
+ connect(share.data(), &Share::passwordSet, this, [this, shareId]{ slotSharePasswordSet(shareId); });
+
+ if (const auto linkShare = share.objectCast<LinkShare>()) {
+ connect(linkShare.data(), &LinkShare::noteSet, this, [this, shareId]{ slotShareNoteSet(shareId); });
+ connect(linkShare.data(), &LinkShare::nameSet, this, [this, shareId]{ slotShareNameSet(shareId); });
+ connect(linkShare.data(), &LinkShare::labelSet, this, [this, shareId]{ slotShareLabelSet(shareId); });
+ connect(linkShare.data(), &LinkShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); });
+ } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+ connect(userGroupShare.data(), &UserGroupShare::noteSet, this, [this, shareId]{ slotShareNoteSet(shareId); });
+ connect(userGroupShare.data(), &UserGroupShare::expireDateSet, this, [this, shareId]{ slotShareExpireDateSet(shareId); });
+ }
+
+ if (_manager) {
+ connect(_manager.data(), &ShareManager::serverError, this, &ShareModel::slotServerError);
+ }
+
+ Q_EMIT sharesChanged();
+}
+
+void ShareModel::slotRemoveShareWithId(const QString &shareId)
+{
+ if (_shares.empty() || shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+ return;
+ }
+
+ _shareIdRecentlySetPasswords.remove(shareId);
+ const auto shareIndex = _shareIdIndexHash.take(shareId);
+
+ if (!shareIndex.isValid()) {
+ qCWarning(lcShareModel) << "Won't remove share with id:" << shareId
+ << ", invalid share index: " << shareIndex;
+ return;
+ }
+
+ beginRemoveRows({}, shareIndex.row(), shareIndex.row());
+ _shares.removeAt(shareIndex.row());
+ endRemoveRows();
+
+ // If no link shares then re-add placeholder link share
+ if (shareIndex.data(ShareModel::ShareTypeRole).toInt() == Share::TypeLink) {
+
+ // Early return if we find another link share
+ for(const auto &share : _shares) {
+ if(share->getShareType() == Share::TypeLink) {
+ return;
+ }
+ }
+
+ slotAddShare(_placeholderLinkShare);
+ }
+
+ Q_EMIT sharesChanged();
+}
+
+void ShareModel::slotServerError(const int code, const QString &message)
+{
+ qCWarning(lcShareModel) << "Error from server" << code << message;
+ Q_EMIT serverError(code, message);
+}
+
+QString ShareModel::displayStringForShare(const SharePtr &share) const
+{
+ if (const auto linkShare = share.objectCast<LinkShare>()) {
+ const auto displayString = tr("Link share");
+
+ if (!linkShare->getLabel().isEmpty()) {
+ return QStringLiteral("%1 (%2)").arg(displayString, linkShare->getLabel());
+ }
+
+ return displayString;
+ } else if (share->getShareType() == Share::TypePlaceholderLink) {
+ return tr("Link share");
+ } else if (share->getShareWith()) {
+ return share->getShareWith()->format();
+ }
+
+ qCWarning(lcShareModel) << "Unable to provide good display string for share";
+ return QStringLiteral("Share");
+}
+
+QString ShareModel::iconUrlForShare(const SharePtr &share) const
+{
+ const auto iconsPath = QStringLiteral("image://svgimage-custom-color/");
+
+ switch(share->getShareType()) {
+ case Share::TypePlaceholderLink:
+ case Share::TypeLink:
+ return QString(iconsPath + QStringLiteral("public.svg"));
+ case Share::TypeEmail:
+ return QString(iconsPath + QStringLiteral("email.svg"));
+ case Share::TypeRoom:
+ return QString(iconsPath + QStringLiteral("wizard-talk.svg"));
+ case Share::TypeUser:
+ return QString(iconsPath + QStringLiteral("user.svg"));
+ case Share::TypeGroup:
+ return QString(iconsPath + QStringLiteral("wizard-groupware.svg"));
+ default:
+ return {};
+ }
+}
+
+QString ShareModel::avatarUrlForShare(const SharePtr &share) const
+{
+ if (share->getShareWith() && share->getShareWith()->type() == Sharee::User && _accountState && _accountState->account()) {
+ const QString provider = QStringLiteral("image://tray-image-provider/");
+ const QString userId = share->getShareWith()->shareWith();
+ const QString avatarUrl = Utility::concatUrlPath(_accountState->account()->url(),
+ QString("remote.php/dav/avatars/%1/%2.png").arg(userId, QString::number(64))).toString();
+ return QString(provider + avatarUrl);
+ }
+
+ return {};
+}
+
+long long ShareModel::enforcedMaxExpireDateForShare(const SharePtr &share) const
+{
+ if (!_accountState || !_accountState->account() || !_accountState->account()->capabilities().isValid()) {
+ return {};
+ }
+
+ auto expireDays = -1;
+
+ // Both public links and emails count as "public" shares
+ if ((share->getShareType() == Share::TypeLink || share->getShareType() == Share::TypeEmail)
+ && _accountState->account()->capabilities().sharePublicLinkEnforceExpireDate()) {
+ expireDays = _accountState->account()->capabilities().sharePublicLinkExpireDateDays();
+
+ } else if (share->getShareType() == Share::TypeRemote && _accountState->account()->capabilities().shareRemoteEnforceExpireDate()) {
+ expireDays = _accountState->account()->capabilities().shareRemoteExpireDateDays();
+
+ } else if ((share->getShareType() == Share::TypeUser ||
+ share->getShareType() == Share::TypeGroup ||
+ share->getShareType() == Share::TypeCircle ||
+ share->getShareType() == Share::TypeRoom) &&
+ _accountState->account()->capabilities().shareInternalEnforceExpireDate()) {
+ expireDays = _accountState->account()->capabilities().shareInternalExpireDateDays();
+
+ } else {
+ return {};
+ }
+
+ const auto expireDateTime = QDate::currentDate().addDays(expireDays).startOfDay(QTimeZone::utc());
+ return expireDateTime.toMSecsSinceEpoch();
+}
+
+bool ShareModel::expireDateEnforcedForShare(const SharePtr &share) const
+{
+ if(!_accountState || !_accountState->account() || !_accountState->account()->capabilities().isValid()) {
+ return false;
+ }
+
+ // Both public links and emails count as "public" shares
+ if (share->getShareType() == Share::TypeLink ||
+ share->getShareType() == Share::TypeEmail) {
+ return _accountState->account()->capabilities().sharePublicLinkEnforceExpireDate();
+
+ } else if (share->getShareType() == Share::TypeRemote) {
+ return _accountState->account()->capabilities().shareRemoteEnforceExpireDate();
+
+ } else if (share->getShareType() == Share::TypeUser ||
+ share->getShareType() == Share::TypeGroup ||
+ share->getShareType() == Share::TypeCircle ||
+ share->getShareType() == Share::TypeRoom) {
+ return _accountState->account()->capabilities().shareInternalEnforceExpireDate();
+
+ }
+
+ return false;
+}
+
+// ----------------- Shares modified signal handling slots ----------------- //
+
+void ShareModel::slotSharePermissionsSet(const QString &shareId)
+{
+ if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+ return;
+ }
+
+ const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+ const auto shareModelIndex = index(sharePersistentModelIndex.row());
+ Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { EditingAllowedRole });
+}
+
+void ShareModel::slotSharePasswordSet(const QString &shareId)
+{
+ if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+ return;
+ }
+
+ const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+ const auto shareModelIndex = index(sharePersistentModelIndex.row());
+ Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { PasswordProtectEnabledRole, PasswordRole });
+}
+
+void ShareModel::slotShareNoteSet(const QString &shareId)
+{
+ if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+ return;
+ }
+
+ const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+ const auto shareModelIndex = index(sharePersistentModelIndex.row());
+ Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { NoteEnabledRole, NoteRole });
+}
+
+void ShareModel::slotShareNameSet(const QString &shareId)
+{
+ if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+ return;
+ }
+
+ const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+ const auto shareModelIndex = index(sharePersistentModelIndex.row());
+ Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { LinkShareNameRole });
+}
+
+void ShareModel::slotShareLabelSet(const QString &shareId)
+{
+ if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+ return;
+ }
+
+ const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+ const auto shareModelIndex = index(sharePersistentModelIndex.row());
+ Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { Qt::DisplayRole, LinkShareLabelRole });
+}
+
+void ShareModel::slotShareExpireDateSet(const QString &shareId)
+{
+ if (shareId.isEmpty() || !_shareIdIndexHash.contains(shareId)) {
+ return;
+ }
+
+ const auto sharePersistentModelIndex = _shareIdIndexHash.value(shareId);
+ const auto shareModelIndex = index(sharePersistentModelIndex.row());
+ Q_EMIT dataChanged(shareModelIndex, shareModelIndex, { ExpireDateEnabledRole, ExpireDateRole });
+}
+
+// ----------------------- Shares modification slots ----------------------- //
+
+void ShareModel::toggleShareAllowEditing(const SharePtr &share, const bool enable) const
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ auto permissions = share->getPermissions();
+ enable ? permissions |= SharePermissionUpdate : permissions &= ~SharePermissionUpdate;
+
+ share->setPermissions(permissions);
+}
+
+void ShareModel::toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const
+{
+ const auto ptr = share.value<SharePtr>();
+ toggleShareAllowEditing(ptr, enable);
+}
+
+void ShareModel::toggleShareAllowResharing(const SharePtr &share, const bool enable) const
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ auto permissions = share->getPermissions();
+ enable ? permissions |= SharePermissionShare : permissions &= ~SharePermissionShare;
+
+ share->setPermissions(permissions);
+}
+
+void ShareModel::toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const
+{
+ const auto ptr = share.value<SharePtr>();
+ toggleShareAllowResharing(ptr, enable);
+}
+
+void ShareModel::toggleSharePasswordProtect(const SharePtr &share, const bool enable)
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ if(!enable) {
+ share->setPassword({});
+ return;
+ }
+
+ const auto randomPassword = createRandomPassword();
+ _shareIdRecentlySetPasswords.insert(share->getId(), randomPassword);
+ share->setPassword(randomPassword);
+}
+
+void ShareModel::toggleSharePasswordProtectFromQml(const QVariant &share, const bool enable)
+{
+ const auto ptr = share.value<SharePtr>();
+ toggleSharePasswordProtect(ptr, enable);
+}
+
+void ShareModel::toggleShareExpirationDate(const SharePtr &share, const bool enable) const
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ const auto expireDate = enable ? QDate::currentDate().addDays(1) : QDate();
+
+ if (const auto linkShare = share.objectCast<LinkShare>()) {
+ linkShare->setExpireDate(expireDate);
+ } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+ userGroupShare->setExpireDate(expireDate);
+ }
+}
+
+void ShareModel::toggleShareExpirationDateFromQml(const QVariant &share, const bool enable) const
+{
+ const auto ptr = share.value<SharePtr>();
+ toggleShareExpirationDate(ptr, enable);
+}
+
+void ShareModel::toggleShareNoteToRecipient(const SharePtr &share, const bool enable) const
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ const QString note = enable ? tr("Enter a note for the recipient") : QString();
+ if (const auto linkShare = share.objectCast<LinkShare>()) {
+ linkShare->setNote(note);
+ } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+ userGroupShare->setNote(note);
+ }
+}
+
+void ShareModel::toggleShareNoteToRecipientFromQml(const QVariant &share, const bool enable) const
+{
+ const auto ptr = share.value<SharePtr>();
+ toggleShareNoteToRecipient(ptr, enable);
+}
+
+void ShareModel::setLinkShareLabel(const QSharedPointer<LinkShare> &linkShare, const QString &label) const
+{
+ if (linkShare.isNull()) {
+ return;
+ }
+
+ linkShare->setLabel(label);
+}
+
+void ShareModel::setLinkShareLabelFromQml(const QVariant &linkShare, const QString &label) const
+{
+ // All of our internal share pointers are SharePtr, so cast to LinkShare for this method
+ const auto ptr = linkShare.value<SharePtr>().objectCast<LinkShare>();
+ setLinkShareLabel(ptr, label);
+}
+
+void ShareModel::setShareExpireDate(const SharePtr &share, const qint64 milliseconds) const
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ const auto date = QDateTime::fromMSecsSinceEpoch(milliseconds, QTimeZone::utc()).date();
+
+ if (const auto linkShare = share.objectCast<LinkShare>()) {
+ linkShare->setExpireDate(date);
+ } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+ userGroupShare->setExpireDate(date);
+ }
+}
+
+void ShareModel::setShareExpireDateFromQml(const QVariant &share, const QVariant milliseconds) const
+{
+ const auto ptr = share.value<SharePtr>();
+ const auto millisecondsLL = milliseconds.toLongLong();
+ setShareExpireDate(ptr, millisecondsLL);
+}
+
+void ShareModel::setSharePassword(const SharePtr &share, const QString &password)
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ _shareIdRecentlySetPasswords.insert(share->getId(), password);
+ share->setPassword(password);
+}
+
+void ShareModel::setSharePasswordFromQml(const QVariant &share, const QString &password)
+{
+ const auto ptr = share.value<SharePtr>();
+ setSharePassword(ptr, password);
+}
+
+void ShareModel::setShareNote(const SharePtr &share, const QString &note) const
+{
+ if (share.isNull()) {
+ return;
+ }
+
+ if (const auto linkShare = share.objectCast<LinkShare>()) {
+ linkShare->setNote(note);
+ } else if (const auto userGroupShare = share.objectCast<UserGroupShare>()) {
+ userGroupShare->setNote(note);
+ }
+}
+
+void ShareModel::setShareNoteFromQml(const QVariant &share, const QString &note) const
+{
+ const auto ptr = share.value<SharePtr>();
+ setShareNote(ptr, note);
+}
+
+// ------------------- Share creation and deletion slots ------------------- //
+
+void ShareModel::createNewLinkShare() const
+{
+ if (_manager) {
+ const auto askOptionalPassword = _accountState->account()->capabilities().sharePublicLinkAskOptionalPassword();
+ const auto password = askOptionalPassword ? createRandomPassword() : QString();
+ _manager->createLinkShare(_sharePath, QString(), password);
+ }
+}
+
+void ShareModel::createNewLinkShareWithPassword(const QString &password) const
+{
+ if (_manager) {
+ _manager->createLinkShare(_sharePath, QString(), password);
+ }
+}
+
+void ShareModel::createNewUserGroupShare(const ShareePtr &sharee)
+{
+ if (sharee.isNull()) {
+ return;
+ }
+
+ qCInfo(lcShareModel) << "Creating new user/group share for sharee: " << sharee->format();
+
+ if (sharee->type() == Sharee::Email &&
+ _accountState &&
+ !_accountState->account().isNull() &&
+ _accountState->account()->capabilities().isValid() &&
+ _accountState->account()->capabilities().shareEmailPasswordEnforced()) {
+
+ Q_EMIT requestPasswordForEmailSharee(sharee);
+ return;
+ }
+
+ _manager->createShare(_sharePath,
+ Share::ShareType(sharee->type()),
+ sharee->shareWith(),
+ _maxSharingPermissions,
+ {});
+}
+
+void ShareModel::createNewUserGroupShareWithPassword(const ShareePtr &sharee, const QString &password) const
+{
+ if (sharee.isNull()) {
+ return;
+ }
+
+ _manager->createShare(_sharePath,
+ Share::ShareType(sharee->type()),
+ sharee->shareWith(),
+ _maxSharingPermissions,
+ password);
+}
+
+void ShareModel::createNewUserGroupShareFromQml(const QVariant &sharee)
+{
+ const auto ptr = sharee.value<ShareePtr>();
+ createNewUserGroupShare(ptr);
+}
+
+void ShareModel::createNewUserGroupShareWithPasswordFromQml(const QVariant &sharee, const QString &password) const
+{
+ const auto ptr = sharee.value<ShareePtr>();
+ createNewUserGroupShareWithPassword(ptr, password);
+}
+
+void ShareModel::deleteShare(const SharePtr &share) const
+{
+ if(share.isNull()) {
+ return;
+ }
+
+ share->deleteShare();
+}
+
+void ShareModel::deleteShareFromQml(const QVariant &share) const
+{
+ const auto ptr = share.value<SharePtr>();
+ deleteShare(ptr);
+}
+
+// --------------------------- QPROPERTY methods --------------------------- //
+
+QString ShareModel::localPath() const
+{
+ return _localPath;
+}
+
+void ShareModel::setLocalPath(const QString &localPath)
+{
+ _localPath = localPath;
+ Q_EMIT localPathChanged();
+ updateData();
+}
+
+AccountState *ShareModel::accountState() const
+{
+ return _accountState;
+}
+
+void ShareModel::setAccountState(AccountState *accountState)
+{
+ _accountState = accountState;
+
+ // Change the server and account-related properties
+ connect(_accountState, &AccountState::stateChanged, this, &ShareModel::accountConnectedChanged);
+ connect(_accountState, &AccountState::stateChanged, this, &ShareModel::sharingEnabledChanged);
+ connect(_accountState, &AccountState::stateChanged, this, &ShareModel::publicLinkSharesEnabledChanged);
+ connect(_accountState, &AccountState::stateChanged, this, &ShareModel::userGroupSharingEnabledChanged);
+
+ Q_EMIT accountStateChanged();
+ Q_EMIT accountConnectedChanged();
+ Q_EMIT sharingEnabledChanged();
+ Q_EMIT publicLinkSharesEnabledChanged();
+ Q_EMIT userGroupSharingEnabledChanged();
+ updateData();
+}
+
+bool ShareModel::accountConnected() const
+{
+ return _accountState && _accountState->isConnected();
+}
+
+bool ShareModel::sharingEnabled() const
+{
+ return _accountState &&
+ _accountState->account() &&
+ _accountState->account()->capabilities().isValid() &&
+ _accountState->account()->capabilities().shareAPI();
+}
+
+bool ShareModel::publicLinkSharesEnabled() const
+{
+ return Theme::instance()->linkSharing() &&
+ _accountState &&
+ _accountState->account() &&
+ _accountState->account()->capabilities().isValid() &&
+ _accountState->account()->capabilities().sharePublicLink();
+}
+
+bool ShareModel::userGroupSharingEnabled() const
+{
+ return Theme::instance()->userGroupSharing();
+}
+
+bool ShareModel::fetchOngoing() const
+{
+ return _fetchOngoing;
+}
+
+bool ShareModel::hasInitialShareFetchCompleted() const
+{
+ return _hasInitialShareFetchCompleted;
+}
+
+bool ShareModel::canShare() const
+{
+ return _maxSharingPermissions & SharePermissionShare;
+}
+
+} // namespace OCC
diff --git a/src/gui/filedetails/sharemodel.h b/src/gui/filedetails/sharemodel.h
new file mode 100644
index 000000000..aa4989744
--- /dev/null
+++ b/src/gui/filedetails/sharemodel.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 <QAbstractListModel>
+
+#include "accountstate.h"
+#include "folder.h"
+#include "sharemanager.h"
+#include "sharepermissions.h"
+
+namespace OCC {
+
+class ShareModel : public QAbstractListModel
+{
+ Q_OBJECT
+ Q_PROPERTY(AccountState* accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
+ Q_PROPERTY(QString localPath READ localPath WRITE setLocalPath NOTIFY localPathChanged)
+ Q_PROPERTY(bool accountConnected READ accountConnected NOTIFY accountConnectedChanged)
+ Q_PROPERTY(bool sharingEnabled READ sharingEnabled NOTIFY sharingEnabledChanged)
+ Q_PROPERTY(bool publicLinkSharesEnabled READ publicLinkSharesEnabled NOTIFY publicLinkSharesEnabledChanged)
+ Q_PROPERTY(bool userGroupSharingEnabled READ userGroupSharingEnabled NOTIFY userGroupSharingEnabledChanged)
+ Q_PROPERTY(bool canShare READ canShare NOTIFY sharePermissionsChanged)
+ Q_PROPERTY(bool fetchOngoing READ fetchOngoing NOTIFY fetchOngoingChanged)
+ Q_PROPERTY(bool hasInitialShareFetchCompleted READ hasInitialShareFetchCompleted NOTIFY hasInitialShareFetchCompletedChanged)
+
+public:
+ enum Roles {
+ ShareRole = Qt::UserRole + 1,
+ ShareTypeRole,
+ ShareIdRole,
+ IconUrlRole,
+ AvatarUrlRole,
+ LinkRole,
+ LinkShareNameRole,
+ LinkShareLabelRole,
+ NoteEnabledRole,
+ NoteRole,
+ ExpireDateEnabledRole,
+ ExpireDateEnforcedRole,
+ ExpireDateRole,
+ EnforcedMaximumExpireDateRole,
+ PasswordProtectEnabledRole,
+ PasswordRole,
+ PasswordEnforcedRole,
+ EditingAllowedRole,
+ };
+ Q_ENUM(Roles)
+
+ /**
+ * Possible share types
+ * Need to be in sync with Share::ShareType.
+ * We use this in QML.
+ */
+ enum ShareType {
+ ShareTypeUser = Share::TypeUser,
+ ShareTypeGroup = Share::TypeGroup,
+ ShareTypeLink = Share::TypeLink,
+ ShareTypeEmail = Share::TypeEmail,
+ ShareTypeRemote = Share::TypeRemote,
+ ShareTypeCircle = Share::TypeCircle,
+ ShareTypeRoom = Share::TypeRoom,
+ ShareTypePlaceholderLink = Share::TypePlaceholderLink,
+ };
+ Q_ENUM(ShareType);
+
+ explicit ShareModel(QObject *parent = nullptr);
+
+ QVariant data(const QModelIndex &index, const int role) const override;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ QHash<int, QByteArray> roleNames() const override;
+
+ AccountState *accountState() const;
+ QString localPath() const;
+
+ bool accountConnected() const;
+ bool sharingEnabled() const;
+ bool publicLinkSharesEnabled() const;
+ bool userGroupSharingEnabled() const;
+ bool canShare() const;
+
+ bool fetchOngoing() const;
+ bool hasInitialShareFetchCompleted() const;
+
+signals:
+ void localPathChanged();
+ void accountStateChanged();
+ void accountConnectedChanged();
+ void sharingEnabledChanged();
+ void publicLinkSharesEnabledChanged();
+ void userGroupSharingEnabledChanged();
+ void sharePermissionsChanged();
+ void lockExpireStringChanged();
+ void fetchOngoingChanged();
+ void hasInitialShareFetchCompletedChanged();
+
+ void serverError(const int code, const QString &message);
+ void passwordSetError(const QString &shareId);
+ void requestPasswordForLinkShare();
+ void requestPasswordForEmailSharee(const ShareePtr &sharee);
+
+ void sharesChanged();
+
+public slots:
+ void setAccountState(AccountState *accountState);
+ void setLocalPath(const QString &localPath);
+
+ void createNewLinkShare() const;
+ void createNewLinkShareWithPassword(const QString &password) const;
+ void createNewUserGroupShare(const ShareePtr &sharee);
+ void createNewUserGroupShareFromQml(const QVariant &sharee);
+ void createNewUserGroupShareWithPassword(const ShareePtr &sharee, const QString &password) const;
+ void createNewUserGroupShareWithPasswordFromQml(const QVariant &sharee, const QString &password) const;
+
+ void deleteShare(const SharePtr &share) const;
+ void deleteShareFromQml(const QVariant &share) const;
+
+ void toggleShareAllowEditing(const SharePtr &share, const bool enable) const;
+ void toggleShareAllowEditingFromQml(const QVariant &share, const bool enable) const;
+ void toggleShareAllowResharing(const SharePtr &share, const bool enable) const;
+ void toggleShareAllowResharingFromQml(const QVariant &share, const bool enable) const;
+ void toggleSharePasswordProtect(const SharePtr &share, const bool enable);
+ void toggleSharePasswordProtectFromQml(const QVariant &share, const bool enable);
+ void toggleShareExpirationDate(const SharePtr &share, const bool enable) const;
+ void toggleShareExpirationDateFromQml(const QVariant &share, const bool enable) const;
+ void toggleShareNoteToRecipient(const SharePtr &share, const bool enable) const;
+ void toggleShareNoteToRecipientFromQml(const QVariant &share, const bool enable) const;
+
+ void setLinkShareLabel(const QSharedPointer<LinkShare> &linkShare, const QString &label) const;
+ void setLinkShareLabelFromQml(const QVariant &linkShare, const QString &label) const;
+ void setShareExpireDate(const SharePtr &share, const qint64 milliseconds) const;
+ // Needed as ints in QML are 32 bits so we need to use a QVariant
+ void setShareExpireDateFromQml(const QVariant &share, const QVariant milliseconds) const;
+ void setSharePassword(const SharePtr &share, const QString &password);
+ void setSharePasswordFromQml(const QVariant &share, const QString &password);
+ void setShareNote(const SharePtr &share, const QString &note) const;
+ void setShareNoteFromQml(const QVariant &share, const QString &note) const;
+
+private slots:
+ void resetData();
+ void updateData();
+ void initShareManager();
+
+ void slotPropfindReceived(const QVariantMap &result);
+ void slotServerError(const int code, const QString &message);
+ void slotAddShare(const SharePtr &share);
+ void slotRemoveShareWithId(const QString &shareId);
+ void slotSharesFetched(const QList<SharePtr> &shares);
+
+ void slotSharePermissionsSet(const QString &shareId);
+ void slotSharePasswordSet(const QString &shareId);
+ void slotShareNoteSet(const QString &shareId);
+ void slotShareNameSet(const QString &shareId);
+ void slotShareLabelSet(const QString &shareId);
+ void slotShareExpireDateSet(const QString &shareId);
+
+private:
+ QString displayStringForShare(const SharePtr &share) const;
+ QString iconUrlForShare(const SharePtr &share) const;
+ QString avatarUrlForShare(const SharePtr &share) const;
+ long long enforcedMaxExpireDateForShare(const SharePtr &share) const;
+ bool expireDateEnforcedForShare(const SharePtr &share) const;
+
+ bool _fetchOngoing = false;
+ bool _hasInitialShareFetchCompleted = false;
+ SharePtr _placeholderLinkShare;
+
+ // DO NOT USE QSHAREDPOINTERS HERE.
+ // QSharedPointers MUST NOT be used with pointers already assigned to other shared pointers.
+ // This is because they do not share reference counters, and as such are not aware of another
+ // smart pointer's use of the same object.
+ //
+ // We cannot pass objects instantiated in QML using smart pointers through the property interface
+ // so we have to pass the pointer here. If we kill the dialog using a smart pointer then
+ // these objects will be deallocated for the entire application. We do not want that!!
+ AccountState *_accountState;
+ Folder *_folder;
+
+ QString _localPath;
+ QString _sharePath;
+ SharePermissions _maxSharingPermissions;
+ QByteArray _numericFileId;
+ SyncJournalFileLockInfo _filelockState;
+ QString _privateLinkUrl;
+
+ QSharedPointer<ShareManager> _manager;
+
+ QVector<SharePtr> _shares;
+ QHash<QString, QPersistentModelIndex> _shareIdIndexHash;
+ QHash<QString, QString> _shareIdRecentlySetPasswords;
+};
+
+} // namespace OCC
diff --git a/src/gui/filedetails/sortedsharemodel.cpp b/src/gui/filedetails/sortedsharemodel.cpp
new file mode 100644
index 000000000..9906cfc57
--- /dev/null
+++ b/src/gui/filedetails/sortedsharemodel.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 "sortedsharemodel.h"
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcSortedShareModel, "com.nextcloud.sortedsharemodel")
+
+SortedShareModel::SortedShareModel(QObject *parent)
+ : QSortFilterProxyModel(parent)
+{
+}
+
+void SortedShareModel::sortModel()
+{
+ sort(0);
+}
+
+ShareModel *SortedShareModel::shareModel() const
+{
+ return qobject_cast<ShareModel*>(sourceModel());
+}
+
+void SortedShareModel::setShareModel(ShareModel *shareModel)
+{
+ const auto currentSetModel = sourceModel();
+
+ if(currentSetModel) {
+ disconnect(currentSetModel, &ShareModel::rowsInserted, this, &SortedShareModel::sortModel);
+ disconnect(currentSetModel, &ShareModel::rowsMoved, this, &SortedShareModel::sortModel);
+ disconnect(currentSetModel, &ShareModel::rowsRemoved, this, &SortedShareModel::sortModel);
+ disconnect(currentSetModel, &ShareModel::dataChanged, this, &SortedShareModel::sortModel);
+ disconnect(currentSetModel, &ShareModel::modelReset, this, &SortedShareModel::sortModel);
+ }
+
+ // Re-sort model when any changes take place
+ connect(shareModel, &ShareModel::rowsInserted, this, &SortedShareModel::sortModel);
+ connect(shareModel, &ShareModel::rowsMoved, this, &SortedShareModel::sortModel);
+ connect(shareModel, &ShareModel::rowsRemoved, this, &SortedShareModel::sortModel);
+ connect(shareModel, &ShareModel::dataChanged, this, &SortedShareModel::sortModel);
+ connect(shareModel, &ShareModel::modelReset, this, &SortedShareModel::sortModel);
+
+ setSourceModel(shareModel);
+ sortModel();
+ Q_EMIT shareModelChanged();
+}
+
+bool SortedShareModel::lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const
+{
+ if (!sourceLeft.isValid() || !sourceRight.isValid()) {
+ return false;
+ }
+
+ const auto leftShare = sourceLeft.data(ShareModel::ShareRole).value<SharePtr>();
+ const auto rightShare = sourceRight.data(ShareModel::ShareRole).value<SharePtr>();
+
+ if (leftShare.isNull() || rightShare.isNull()) {
+ return false;
+ }
+
+ const auto leftShareType = leftShare->getShareType();
+
+ // Placeholder link shares always go at top
+ if(leftShareType == Share::TypePlaceholderLink) {
+ return true;
+ }
+
+ const auto rightShareType = rightShare->getShareType();
+
+ // We want to place link shares at the top
+ if (leftShareType == Share::TypeLink && rightShareType != Share::TypeLink) {
+ return true;
+ } else if (rightShareType == Share::TypeLink && leftShareType != Share::TypeLink) {
+ return false;
+ } else if (leftShareType != rightShareType) {
+ return leftShareType < rightShareType;
+ }
+
+ if (leftShareType == Share::TypeLink) {
+ const auto leftLinkShare = leftShare.objectCast<LinkShare>();
+ const auto rightLinkShare = rightShare.objectCast<LinkShare>();
+
+ if(leftLinkShare.isNull() || rightLinkShare.isNull()) {
+ qCWarning(lcSortedShareModel) << "One of compared shares is a null pointer after conversion despite having same share type. Left link share is null:" << leftLinkShare.isNull()
+ << "Right link share is null: " << rightLinkShare.isNull();
+ return false;
+ }
+
+ return leftLinkShare->getLabel() < rightLinkShare->getLabel();
+
+ } else if (leftShare->getShareWith()) {
+ if(rightShare->getShareWith().isNull()) {
+ return true;
+ }
+
+ return leftShare->getShareWith()->format() < rightShare->getShareWith()->format();
+ }
+
+ return false;
+}
+
+} // namespace OCC
diff --git a/src/gui/filedetails/sortedsharemodel.h b/src/gui/filedetails/sortedsharemodel.h
new file mode 100644
index 000000000..c5cdbb975
--- /dev/null
+++ b/src/gui/filedetails/sortedsharemodel.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) 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 <QSortFilterProxyModel>
+#include "sharemodel.h"
+
+namespace OCC {
+
+class SortedShareModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+ Q_PROPERTY(ShareModel* shareModel READ shareModel WRITE setShareModel NOTIFY shareModelChanged)
+
+public:
+ explicit SortedShareModel(QObject *parent = nullptr);
+
+ ShareModel *shareModel() const;
+
+signals:
+ void shareModelChanged();
+
+public slots:
+ void setShareModel(ShareModel *shareModel);
+
+protected:
+ bool lessThan(const QModelIndex &sourceLeft, const QModelIndex &sourceRight) const override;
+
+private slots:
+ void sortModel();
+};
+
+} // namespace OCC
diff --git a/src/gui/folderman.cpp b/src/gui/folderman.cpp
index 8d57a72b8..b71a9593d 100644
--- a/src/gui/folderman.cpp
+++ b/src/gui/folderman.cpp
@@ -24,6 +24,7 @@
#include "filesystem.h"
#include "lockwatcher.h"
#include "common/asserts.h"
+#include "gui/systray.h"
#include <pushnotifications.h>
#include <syncengine.h>
diff --git a/src/gui/owncloudgui.cpp b/src/gui/owncloudgui.cpp
index ab96fe831..5ef4b7e4c 100644
--- a/src/gui/owncloudgui.cpp
+++ b/src/gui/owncloudgui.cpp
@@ -29,11 +29,12 @@
#include "owncloudsetupwizard.h"
#include "progressdispatcher.h"
#include "settingsdialog.h"
-#include "sharedialog.h"
#include "theme.h"
#include "wheelhandler.h"
-#include "common/syncjournalfilerecord.h"
-#include "creds/abstractcredentials.h"
+#include "filedetails/filedetails.h"
+#include "filedetails/shareemodel.h"
+#include "filedetails/sharemodel.h"
+#include "filedetails/sortedsharemodel.h"
#include "tray/sortedactivitylistmodel.h"
#include "tray/syncstatussummary.h"
#include "tray/unifiedsearchresultslistmodel.h"
@@ -97,11 +98,6 @@ ownCloudGui::ownCloudGui(Application *parent)
connect(_tray.data(), &Systray::shutdown,
this, &ownCloudGui::slotShutdown);
- connect(_tray.data(), &Systray::openShareDialog,
- this, [=](const QString &sharePath, const QString &localPath) {
- slotShowShareDialog(sharePath, localPath, ShareDialogStartPage::UsersAndGroups);
- });
-
ProgressDispatcher *pd = ProgressDispatcher::instance();
connect(pd, &ProgressDispatcher::progressInfo, this,
&ownCloudGui::slotUpdateProgress);
@@ -125,6 +121,10 @@ ownCloudGui::ownCloudGui(Application *parent)
qmlRegisterType<SortedActivityListModel>("com.nextcloud.desktopclient", 1, 0, "SortedActivityListModel");
qmlRegisterType<WheelHandler>("com.nextcloud.desktopclient", 1, 0, "WheelHandler");
qmlRegisterType<CallStateChecker>("com.nextcloud.desktopclient", 1, 0, "CallStateChecker");
+ qmlRegisterType<FileDetails>("com.nextcloud.desktopclient", 1, 0, "FileDetails");
+ qmlRegisterType<ShareModel>("com.nextcloud.desktopclient", 1, 0, "ShareModel");
+ qmlRegisterType<ShareeModel>("com.nextcloud.desktopclient", 1, 0, "ShareeModel");
+ qmlRegisterType<SortedShareModel>("com.nextcloud.desktopclient", 1, 0, "SortedShareModel");
qmlRegisterUncreatableType<UnifiedSearchResultsListModel>("com.nextcloud.desktopclient", 1, 0, "UnifiedSearchResultsListModel", "UnifiedSearchResultsListModel");
qmlRegisterUncreatableType<UserStatus>("com.nextcloud.desktopclient", 1, 0, "UserStatus", "Access to Status enum");
@@ -134,6 +134,8 @@ ownCloudGui::ownCloudGui(Application *parent)
qRegisterMetaType<ActivityListModel *>("ActivityListModel*");
qRegisterMetaType<UnifiedSearchResultsListModel *>("UnifiedSearchResultsListModel*");
qRegisterMetaType<UserStatus>("UserStatus");
+ qRegisterMetaType<SharePtr>("SharePtr");
+ qRegisterMetaType<ShareePtr>("ShareePtr");
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "UserModel", UserModel::instance());
qmlRegisterSingletonInstance("com.nextcloud.desktopclient", 1, 0, "UserAppsModel", UserAppsModel::instance());
@@ -196,12 +198,8 @@ void ownCloudGui::slotTrayClicked(QSystemTrayIcon::ActivationReason reason)
} else if (reason == QSystemTrayIcon::Trigger) {
if (OwncloudSetupWizard::bringWizardToFrontIfVisible()) {
// brought wizard to front
- } else if (_shareDialogs.size() > 0) {
- // Share dialog(s) be hidden by other apps, bring them back
- Q_FOREACH (const QPointer<ShareDialog> &shareDialog, _shareDialogs) {
- Q_ASSERT(shareDialog.data());
- raiseDialog(shareDialog);
- }
+ } else if (_tray->raiseDialogs()) {
+ // Brings dialogs hidden by other apps to front, returns true if any raised
} else if (_tray->isOpen()) {
_tray->hideWindow();
} else {
@@ -652,54 +650,14 @@ void ownCloudGui::raiseDialog(QWidget *raiseWidget)
}
-void ownCloudGui::slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage)
+void ownCloudGui::slotShowShareDialog(const QString &localPath) const
{
- const auto folder = FolderMan::instance()->folderForPath(localPath);
- if (!folder) {
- qCWarning(lcApplication) << "Could not open share dialog for" << localPath << "no responsible folder found";
- return;
- }
-
- const auto accountState = folder->accountState();
-
- const QString file = localPath.mid(folder->cleanPath().length() + 1);
- SyncJournalFileRecord fileRecord;
-
- bool resharingAllowed = true; // lets assume the good
- if (folder->journalDb()->getFileRecord(file, &fileRecord) && fileRecord.isValid()) {
- // check the permission: Is resharing allowed?
- if (!fileRecord._remotePerm.isNull() && !fileRecord._remotePerm.hasPermission(RemotePermissions::CanReshare)) {
- resharingAllowed = false;
- }
- }
-
- auto maxSharingPermissions = resharingAllowed? SharePermissions(accountState->account()->capabilities().shareDefaultPermissions()) : SharePermissions({});
-
- ShareDialog *w = nullptr;
- if (_shareDialogs.contains(localPath) && _shareDialogs[localPath]) {
- qCInfo(lcApplication) << "Raising share dialog" << sharePath << localPath;
- w = _shareDialogs[localPath];
- } else {
- qCInfo(lcApplication) << "Opening share dialog" << sharePath << localPath << maxSharingPermissions;
- w = new ShareDialog(accountState, sharePath, localPath, maxSharingPermissions, fileRecord.numericFileId(), fileRecord._lockstate, startPage);
- w->setAttribute(Qt::WA_DeleteOnClose, true);
-
- _shareDialogs[localPath] = w;
- connect(w, &QObject::destroyed, this, &ownCloudGui::slotRemoveDestroyedShareDialogs);
- }
- raiseDialog(w);
+ _tray->createShareDialog(localPath);
}
-void ownCloudGui::slotRemoveDestroyedShareDialogs()
+void ownCloudGui::slotShowFileActivityDialog(const QString &localPath) const
{
- QMutableMapIterator<QString, QPointer<ShareDialog>> it(_shareDialogs);
- while (it.hasNext()) {
- it.next();
- if (!it.value() || it.value() == sender()) {
- it.remove();
- }
- }
+ _tray->createFileActivityDialog(localPath);
}
-
} // end namespace
diff --git a/src/gui/owncloudgui.h b/src/gui/owncloudgui.h
index 3ffa57cd1..7b40d520d 100644
--- a/src/gui/owncloudgui.h
+++ b/src/gui/owncloudgui.h
@@ -100,14 +100,11 @@ public slots:
/**
* Open a share dialog for a file or folder.
*
- * sharePath is the full remote path to the item,
* localPath is the absolute local path to it (so not relative
* to the folder).
*/
- void slotShowShareDialog(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
-
- void slotRemoveDestroyedShareDialogs();
-
+ void slotShowShareDialog(const QString &localPath) const;
+ void slotShowFileActivityDialog(const QString &localPath) const;
void slotNewAccountWizard();
private slots:
@@ -123,8 +120,6 @@ private:
QDBusConnection _bus;
#endif
- QMap<QString, QPointer<ShareDialog>> _shareDialogs;
-
QAction *_actionNewAccountWizard;
QAction *_actionSettings;
QAction *_actionEstimate;
diff --git a/src/gui/sharedialog.cpp b/src/gui/sharedialog.cpp
deleted file mode 100644
index 99a0b8356..000000000
--- a/src/gui/sharedialog.cpp
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- *
- * 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 "ui_sharedialog.h"
-#include "sharedialog.h"
-#include "sharee.h"
-#include "sharelinkwidget.h"
-#include "internallinkwidget.h"
-#include "shareusergroupwidget.h"
-#include "passwordinputdialog.h"
-
-#include "sharemanager.h"
-
-#include "account.h"
-#include "accountstate.h"
-#include "configfile.h"
-#include "theme.h"
-#include "thumbnailjob.h"
-#include "wordlist.h"
-
-#include <QFileInfo>
-#include <QFileIconProvider>
-#include <QInputDialog>
-#include <QPointer>
-#include <QPushButton>
-#include <QFrame>
-#include <QScrollBar>
-
-namespace {
-QString createRandomPassword()
-{
- const auto words = OCC::WordList::getRandomWords(10);
-
- const auto addFirstLetter = [](const QString &current, const QString &next) -> QString {
- return current + next.at(0);
- };
-
- return std::accumulate(std::cbegin(words), std::cend(words), QString(), addFirstLetter);
-}
-}
-
-
-namespace OCC {
-
-static const int thumbnailSize = 40;
-
-ShareDialog::ShareDialog(QPointer<AccountState> accountState,
- const QString &sharePath,
- const QString &localPath,
- SharePermissions maxSharingPermissions,
- const QByteArray &numericFileId,
- SyncJournalFileLockInfo filelockState,
- ShareDialogStartPage startPage,
- QWidget *parent)
- : QDialog(parent)
- , _ui(new Ui::ShareDialog)
- , _accountState(accountState)
- , _sharePath(sharePath)
- , _localPath(localPath)
- , _maxSharingPermissions(maxSharingPermissions)
- , _filelockState(std::move(filelockState))
- , _privateLinkUrl(accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded))
- , _startPage(startPage)
-{
- setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
- setAttribute(Qt::WA_DeleteOnClose);
- setObjectName("SharingDialog"); // required as group for saveGeometry call
-
- _ui->setupUi(this);
-
- // We want to act on account state changes
- connect(_accountState.data(), &AccountState::stateChanged, this, &ShareDialog::slotAccountStateChanged);
-
- // Set icon
- QFileInfo f_info(_localPath);
- QFileIconProvider icon_provider;
- QIcon icon = icon_provider.icon(f_info);
- auto pixmap = icon.pixmap(thumbnailSize, thumbnailSize);
- if (pixmap.width() > 0) {
- _ui->label_icon->setPixmap(pixmap);
- }
-
- // Set filename
- QString fileName = QFileInfo(_sharePath).fileName();
- _ui->label_name->setText(tr("%1").arg(fileName));
- QFont f(_ui->label_name->font());
- f.setPointSize(qRound(f.pointSize() * 1.4));
- _ui->label_name->setFont(f);
-
- if (_filelockState._locked) {
- static constexpr auto SECONDS_PER_MINUTE = 60;
- const auto lockExpirationTime = _filelockState._lockTime + _filelockState._lockTimeout;
- const auto remainingTime = QDateTime::currentDateTime().secsTo(QDateTime::fromSecsSinceEpoch(lockExpirationTime));
- const auto remainingTimeInMinute = static_cast<int>(remainingTime > 0 ? remainingTime / SECONDS_PER_MINUTE : 0);
- _ui->label_lockinfo->setText(tr("Locked by %1 - Expires in %2 minutes", "remaining time before lock expires", remainingTimeInMinute).arg(_filelockState._lockOwnerDisplayName).arg(remainingTimeInMinute));
- } else {
- _ui->label_lockinfo->setVisible(false);
- }
-
- QString ocDir(_sharePath);
- ocDir.truncate(ocDir.length() - fileName.length());
-
- ocDir.replace(QRegularExpression("^/*"), "");
- ocDir.replace(QRegularExpression("/*$"), "");
-
- // Laying this out is complex because sharePath
- // may be in use or not.
- _ui->gridLayout->removeWidget(_ui->label_sharePath);
- _ui->gridLayout->removeWidget(_ui->label_name);
- if (ocDir.isEmpty()) {
- _ui->gridLayout->addWidget(_ui->label_name, 0, 1, 2, 1);
- _ui->label_sharePath->setText(QString());
- } else {
- _ui->gridLayout->addWidget(_ui->label_name, 0, 1, 1, 1);
- _ui->gridLayout->addWidget(_ui->label_sharePath, 1, 1, 1, 1);
- _ui->label_sharePath->setText(tr("Folder: %2").arg(ocDir));
- }
-
- this->setWindowTitle(tr("%1 Sharing").arg(Theme::instance()->appNameGUI()));
-
- if (!accountState->account()->capabilities().shareAPI()) {
- return;
- }
-
- if (QFileInfo(_localPath).isFile()) {
- auto *job = new ThumbnailJob(_sharePath, _accountState->account(), this);
- connect(job, &ThumbnailJob::jobFinished, this, &ShareDialog::slotThumbnailFetched);
- job->start();
- }
-
- auto job = new PropfindJob(accountState->account(), _sharePath);
- job->setProperties(
- QList<QByteArray>()
- << "http://open-collaboration-services.org/ns:share-permissions"
- << "http://owncloud.org/ns:fileid" // numeric file id for fallback private link generation
- << "http://owncloud.org/ns:privatelink");
- job->setTimeout(10 * 1000);
- connect(job, &PropfindJob::result, this, &ShareDialog::slotPropfindReceived);
- connect(job, &PropfindJob::finishedWithError, this, &ShareDialog::slotPropfindError);
- job->start();
-
- initShareManager();
-
- _scrollAreaViewPort = new QWidget(_ui->scrollArea);
- _scrollAreaLayout = new QVBoxLayout(_scrollAreaViewPort);
- _scrollAreaLayout->setContentsMargins(0, 0, 0, 0);
- _ui->scrollArea->setWidget(_scrollAreaViewPort);
-
- _internalLinkWidget = new InternalLinkWidget(localPath, this);
- _ui->verticalLayout->addWidget(_internalLinkWidget);
- _internalLinkWidget->setupUiOptions();
- connect(this, &ShareDialog::styleChanged, _internalLinkWidget, &InternalLinkWidget::slotStyleChanged);
-
- adjustScrollWidget();
-}
-
-ShareLinkWidget *ShareDialog::addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare)
-{
- const auto linkShareWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea);
- _linkWidgetList.append(linkShareWidget);
-
- linkShareWidget->setLinkShare(linkShare);
-
- connect(linkShare.data(), &Share::serverError, linkShareWidget, &ShareLinkWidget::slotServerError);
- connect(linkShare.data(), &Share::shareDeleted, linkShareWidget, &ShareLinkWidget::slotDeleteShareFetched);
-
- if(_manager) {
- connect(_manager, &ShareManager::serverError, linkShareWidget, &ShareLinkWidget::slotServerError);
- }
-
- // Connect all shares signals to gui slots
- connect(this, &ShareDialog::toggleShareLinkAnimation, linkShareWidget, &ShareLinkWidget::slotToggleShareLinkAnimation);
- connect(linkShareWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
- connect(linkShareWidget, &ShareLinkWidget::deleteLinkShare, this, &ShareDialog::slotDeleteShare);
- connect(linkShareWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
-
- // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
- connect(this, &ShareDialog::styleChanged, linkShareWidget, &ShareLinkWidget::slotStyleChanged);
-
- _ui->verticalLayout->insertWidget(_linkWidgetList.size() + 1, linkShareWidget);
- _scrollAreaLayout->addWidget(linkShareWidget);
-
- linkShareWidget->setupUiOptions();
- adjustScrollWidget();
-
- return linkShareWidget;
-}
-
-void ShareDialog::initLinkShareWidget()
-{
- if(_linkWidgetList.size() == 0) {
- _emptyShareLinkWidget = new ShareLinkWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _ui->scrollArea);
- _linkWidgetList.append(_emptyShareLinkWidget);
-
- _emptyShareLinkWidget->slotStyleChanged(); // Get the initial customizeStyle() to happen
-
- connect(this, &ShareDialog::toggleShareLinkAnimation, _emptyShareLinkWidget, &ShareLinkWidget::slotToggleShareLinkAnimation);
- connect(this, &ShareDialog::styleChanged, _emptyShareLinkWidget, &ShareLinkWidget::slotStyleChanged);
-
- connect(_emptyShareLinkWidget, &ShareLinkWidget::createLinkShare, this, &ShareDialog::slotCreateLinkShare);
- connect(_emptyShareLinkWidget, &ShareLinkWidget::createPassword, this, &ShareDialog::slotCreatePasswordForLinkShare);
-
- _ui->verticalLayout->insertWidget(_linkWidgetList.size()+1, _emptyShareLinkWidget);
- _scrollAreaLayout->addWidget(_emptyShareLinkWidget);
- _emptyShareLinkWidget->show();
- } else if (_emptyShareLinkWidget) {
- _emptyShareLinkWidget->hide();
- _ui->verticalLayout->removeWidget(_emptyShareLinkWidget);
- _linkWidgetList.removeAll(_emptyShareLinkWidget);
- _emptyShareLinkWidget = nullptr;
- }
-
- adjustScrollWidget();
-}
-
-void ShareDialog::slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare)
-{
- emit toggleShareLinkAnimation(true);
- const auto addedLinkShareWidget = addLinkShareWidget(linkShare);
- initLinkShareWidget();
- if (linkShare->isPasswordSet()) {
- addedLinkShareWidget->focusPasswordLineEdit();
- }
- emit toggleShareLinkAnimation(false);
-}
-
-void ShareDialog::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
-{
- emit toggleShareLinkAnimation(true);
-
- const QString versionString = _accountState->account()->serverVersion();
- qCInfo(lcSharing) << versionString << "Fetched" << shares.count() << "shares";
-
- foreach (auto share, shares) {
- if (share->getShareType() != Share::TypeLink || share->getUidOwner() != share->account()->davUser()) {
- continue;
- }
-
- QSharedPointer<LinkShare> linkShare = qSharedPointerDynamicCast<LinkShare>(share);
- addLinkShareWidget(linkShare);
- }
-
- initLinkShareWidget();
- emit toggleShareLinkAnimation(false);
-}
-
-void ShareDialog::adjustScrollWidget()
-{
- _ui->scrollArea->setVisible(_scrollAreaLayout->count() > 0);
-
- // Sometimes the contentRect returns a height of 0, so we need a backup plan
- const auto scrollAreaContentHeight = _scrollAreaLayout->contentsRect().height();
-
- auto linkWidgetHeights = 0;
-
- if(scrollAreaContentHeight == 0 && !_linkWidgetList.empty()) {
- for (const auto linkWidget : _linkWidgetList) {
- linkWidgetHeights += linkWidget->height() - 10;
- }
- }
-
- const auto overAvailableHeight = scrollAreaContentHeight > _ui->scrollArea->height() ||
- linkWidgetHeights > _ui->scrollArea->height();
-
- _ui->scrollArea->setFrameShape(overAvailableHeight ? QFrame::StyledPanel : QFrame::NoFrame);
- _ui->verticalLayout->setSpacing(overAvailableHeight ? 10 : 0);
-}
-
-ShareDialog::~ShareDialog()
-{
- _linkWidgetList.clear();
- delete _ui;
-}
-
-void ShareDialog::done(int r)
-{
- ConfigFile cfg;
- cfg.saveGeometry(this);
- QDialog::done(r);
-}
-
-void ShareDialog::slotPropfindReceived(const QVariantMap &result)
-{
- const QVariant receivedPermissions = result["share-permissions"];
- if (!receivedPermissions.toString().isEmpty()) {
- _maxSharingPermissions = static_cast<SharePermissions>(receivedPermissions.toInt());
- qCInfo(lcSharing) << "Received sharing permissions for" << _sharePath << _maxSharingPermissions;
- }
- auto privateLinkUrl = result["privatelink"].toString();
- auto numericFileId = result["fileid"].toByteArray();
- if (!privateLinkUrl.isEmpty()) {
- qCInfo(lcSharing) << "Received private link url for" << _sharePath << privateLinkUrl;
- _privateLinkUrl = privateLinkUrl;
- } else if (!numericFileId.isEmpty()) {
- qCInfo(lcSharing) << "Received numeric file id for" << _sharePath << numericFileId;
- _privateLinkUrl = _accountState->account()->deprecatedPrivateLinkUrl(numericFileId).toString(QUrl::FullyEncoded);
- }
-
- showSharingUi();
-}
-
-void ShareDialog::slotPropfindError()
-{
- // On error show the share ui anyway. The user can still see shares,
- // delete them and so on, even though adding new shares or granting
- // some of the permissions might fail.
-
- showSharingUi();
-}
-
-void ShareDialog::showSharingUi()
-{
- auto theme = Theme::instance();
-
- // There's no difference between being unable to reshare and
- // being unable to reshare with reshare permission.
- bool canReshare = _maxSharingPermissions & SharePermissionShare;
-
- if (!canReshare) {
- auto label = new QLabel(this);
- label->setText(tr("The file cannot be shared because it does not have sharing permission."));
- label->setWordWrap(true);
- _ui->verticalLayout->insertWidget(1, label);
- return;
- }
-
- if (theme->userGroupSharing()) {
- _userGroupWidget = new ShareUserGroupWidget(_accountState->account(), _sharePath, _localPath, _maxSharingPermissions, _privateLinkUrl, _ui->scrollArea);
- _userGroupWidget->getShares();
-
- // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
- connect(this, &ShareDialog::styleChanged, _userGroupWidget, &ShareUserGroupWidget::slotStyleChanged);
-
- _userGroupWidget->slotStyleChanged();
-
- _ui->verticalLayout->insertWidget(1, _userGroupWidget);
- _scrollAreaLayout->addLayout(_userGroupWidget->shareUserGroupLayout());
- }
-
- initShareManager();
-
- if (theme->linkSharing()) {
- if(_manager) {
- _manager->fetchShares(_sharePath);
- }
- }
-
- adjustScrollWidget();
-}
-
-void ShareDialog::initShareManager()
-{
- bool sharingPossible = true;
- if (!_accountState->account()->capabilities().sharePublicLink()) {
- qCWarning(lcSharing) << "Link shares have been disabled";
- sharingPossible = false;
- } else if (!(_maxSharingPermissions & SharePermissionShare)) {
- qCWarning(lcSharing) << "The file cannot be shared because it does not have sharing permission.";
- sharingPossible = false;
- }
-
- if (!_manager && sharingPossible) {
- _manager = new ShareManager(_accountState->account(), this);
- connect(_manager, &ShareManager::sharesFetched, this, &ShareDialog::slotSharesFetched);
- connect(_manager, &ShareManager::linkShareCreated, this, &ShareDialog::slotAddLinkShareWidget);
- connect(_manager, &ShareManager::linkShareRequiresPassword, this, &ShareDialog::slotLinkShareRequiresPassword);
- }
-}
-
-void ShareDialog::slotCreateLinkShare()
-{
- if(_manager) {
- const auto askOptionalPassword = _accountState->account()->capabilities().sharePublicLinkAskOptionalPassword();
- const auto password = askOptionalPassword ? createRandomPassword() : QString();
- _manager->createLinkShare(_sharePath, QString(), password);
- }
-}
-
-void ShareDialog::slotCreatePasswordForLinkShare(const QString &password)
-{
- const auto shareLinkWidget = qobject_cast<ShareLinkWidget*>(sender());
- Q_ASSERT(shareLinkWidget);
- if (shareLinkWidget) {
- connect(_manager, &ShareManager::linkShareRequiresPassword, shareLinkWidget, &ShareLinkWidget::slotCreateShareRequiresPassword);
- connect(shareLinkWidget, &ShareLinkWidget::createPasswordProcessed, this, &ShareDialog::slotCreatePasswordForLinkShareProcessed);
- shareLinkWidget->getLinkShare()->setPassword(password);
- } else {
- qCCritical(lcSharing) << "shareLinkWidget is not a sender!";
- }
-}
-
-void ShareDialog::slotCreatePasswordForLinkShareProcessed()
-{
- const auto shareLinkWidget = qobject_cast<ShareLinkWidget*>(sender());
- Q_ASSERT(shareLinkWidget);
- if (shareLinkWidget) {
- disconnect(_manager, &ShareManager::linkShareRequiresPassword, shareLinkWidget, &ShareLinkWidget::slotCreateShareRequiresPassword);
- disconnect(shareLinkWidget, &ShareLinkWidget::createPasswordProcessed, this, &ShareDialog::slotCreatePasswordForLinkShareProcessed);
- } else {
- qCCritical(lcSharing) << "shareLinkWidget is not a sender!";
- }
-}
-
-void ShareDialog::slotLinkShareRequiresPassword(const QString &message)
-{
- const auto passwordInputDialog = new PasswordInputDialog(tr("Please enter a password for your link share:"), message, this);
- passwordInputDialog->setWindowTitle(tr("Password for share required"));
- passwordInputDialog->setAttribute(Qt::WA_DeleteOnClose);
- passwordInputDialog->open();
-
- connect(passwordInputDialog, &QDialog::finished, this, [this, passwordInputDialog](const int result) {
- if (result == QDialog::Accepted && _manager) {
- // Try to create the link share again with the newly entered password
- _manager->createLinkShare(_sharePath, QString(), passwordInputDialog->password());
- return;
- }
- emit toggleShareLinkAnimation(false);
- });
-}
-
-void ShareDialog::slotDeleteShare()
-{
- auto sharelinkWidget = dynamic_cast<ShareLinkWidget*>(sender());
- sharelinkWidget->hide();
- _ui->verticalLayout->removeWidget(sharelinkWidget);
- _scrollAreaLayout->removeWidget(sharelinkWidget);
- _linkWidgetList.removeAll(sharelinkWidget);
- initLinkShareWidget();
-}
-
-void ShareDialog::slotThumbnailFetched(const int &statusCode, const QByteArray &reply)
-{
- if (statusCode != 200) {
- qCWarning(lcSharing) << "Thumbnail status code: " << statusCode;
- return;
- }
-
- QPixmap p;
- p.loadFromData(reply, "PNG");
- p = p.scaledToHeight(thumbnailSize, Qt::SmoothTransformation);
- _ui->label_icon->setPixmap(p);
- _ui->label_icon->show();
-}
-
-void ShareDialog::slotAccountStateChanged(int state)
-{
- bool enabled = (state == AccountState::State::Connected);
- qCDebug(lcSharing) << "Account connected?" << enabled;
-
- if (_userGroupWidget) {
- _userGroupWidget->setEnabled(enabled);
- }
-
- if(_linkWidgetList.size() > 0){
- foreach(ShareLinkWidget *widget, _linkWidgetList){
- widget->setEnabled(state);
- }
- }
-}
-
-void ShareDialog::changeEvent(QEvent *e)
-{
- switch (e->type()) {
- case QEvent::StyleChange:
- case QEvent::PaletteChange:
- case QEvent::ThemeChange:
- // Notify the other widgets (Dark-/Light-Mode switching)
- emit styleChanged();
- break;
- default:
- break;
- }
-
- QDialog::changeEvent(e);
-}
-
-void ShareDialog::resizeEvent(QResizeEvent *event)
-{
- adjustScrollWidget();
- QDialog::resizeEvent(event);
-}
-
-} // namespace OCC
diff --git a/src/gui/sharedialog.h b/src/gui/sharedialog.h
deleted file mode 100644
index 89bb2ef70..000000000
--- a/src/gui/sharedialog.h
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- *
- * 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 SHAREDIALOG_H
-#define SHAREDIALOG_H
-
-#include "accountstate.h"
-#include "sharepermissions.h"
-#include "owncloudgui.h"
-#include "common/syncjournalfilerecord.h"
-
-#include <QSharedPointer>
-#include <QPointer>
-#include <QString>
-#include <QDialog>
-#include <QWidget>
-
-class QProgressIndicator;
-class QVBoxLayout;
-
-namespace OCC {
-
-namespace Ui {
- class ShareDialog;
-}
-
-class ShareLinkWidget;
-class InternalLinkWidget;
-class ShareUserGroupWidget;
-class ShareManager;
-class LinkShare;
-class Share;
-
-class ShareDialog : public QDialog
-{
- Q_OBJECT
-
-public:
- explicit ShareDialog(QPointer<AccountState> accountState,
- const QString &sharePath,
- const QString &localPath,
- SharePermissions maxSharingPermissions,
- const QByteArray &numericFileId,
- SyncJournalFileLockInfo filelockState,
- ShareDialogStartPage startPage,
- QWidget *parent = nullptr);
- ~ShareDialog() override;
-
-private slots:
- void done(int r) override;
- void slotPropfindReceived(const QVariantMap &result);
- void slotPropfindError();
- void slotThumbnailFetched(const int &statusCode, const QByteArray &reply);
- void slotAccountStateChanged(int state);
-
- void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
- void slotAddLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
- void slotDeleteShare();
- void slotCreateLinkShare();
- void slotCreatePasswordForLinkShare(const QString &password);
- void slotCreatePasswordForLinkShareProcessed();
- void slotLinkShareRequiresPassword(const QString &message);
-
-signals:
- void toggleShareLinkAnimation(bool start);
- void styleChanged();
-
-protected:
- void changeEvent(QEvent *) override;
- void resizeEvent(QResizeEvent *event) override;
-
-private:
- void showSharingUi();
- void initShareManager();
- ShareLinkWidget *addLinkShareWidget(const QSharedPointer<LinkShare> &linkShare);
- void initLinkShareWidget();
- void adjustScrollWidget();
-
- Ui::ShareDialog *_ui;
-
- QPointer<AccountState> _accountState;
- QString _sharePath;
- QString _localPath;
- SharePermissions _maxSharingPermissions;
- QByteArray _numericFileId;
- SyncJournalFileLockInfo _filelockState;
- QString _privateLinkUrl;
- ShareDialogStartPage _startPage;
- ShareManager *_manager = nullptr;
-
- QList<ShareLinkWidget*> _linkWidgetList;
- ShareLinkWidget* _emptyShareLinkWidget = nullptr;
- InternalLinkWidget* _internalLinkWidget = nullptr;
- ShareUserGroupWidget *_userGroupWidget = nullptr;
- QProgressIndicator *_progressIndicator = nullptr;
-
- QWidget *_scrollAreaViewPort = nullptr;
- QVBoxLayout *_scrollAreaLayout = nullptr;
-};
-
-} // namespace OCC
-
-#endif // SHAREDIALOG_H
diff --git a/src/gui/sharedialog.ui b/src/gui/sharedialog.ui
deleted file mode 100644
index 8e4bbbfd8..000000000
--- a/src/gui/sharedialog.ui
+++ /dev/null
@@ -1,217 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>OCC::ShareDialog</class>
- <widget class="QDialog" name="OCC::ShareDialog">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>385</width>
- <height>400</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>320</width>
- <height>240</height>
- </size>
- </property>
- <layout class="QVBoxLayout" name="shareDialogVerticalLayout">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="sizeConstraint">
- <enum>QLayout::SetMinimumSize</enum>
- </property>
- <item>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="sizeConstraint">
- <enum>QLayout::SetDefaultConstraint</enum>
- </property>
- <item>
- <layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0" columnstretch="0,0">
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="spacing">
- <number>2</number>
- </property>
- <item row="0" column="1">
- <widget class="QLabel" name="label_name">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>315</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>share label</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="3" column="1">
- <widget class="QLabel" name="label_lockinfo">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>315</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string notr="true">TextLabel</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLabel" name="label_sharePath">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>315</width>
- <height>0</height>
- </size>
- </property>
- <property name="font">
- <font>
- <bold>false</bold>
- </font>
- </property>
- <property name="text">
- <string>Nextcloud Path:</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="0" rowspan="4">
- <widget class="QLabel" name="label_icon">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>40</width>
- <height>40</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>16777215</width>
- <height>16777215</height>
- </size>
- </property>
- <property name="text">
- <string>Icon</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QScrollArea" name="scrollArea">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>0</height>
- </size>
- </property>
- <property name="frameShape">
- <enum>QFrame::NoFrame</enum>
- </property>
- <property name="frameShadow">
- <enum>QFrame::Plain</enum>
- </property>
- <property name="verticalScrollBarPolicy">
- <enum>Qt::ScrollBarAsNeeded</enum>
- </property>
- <property name="horizontalScrollBarPolicy">
- <enum>Qt::ScrollBarAlwaysOff</enum>
- </property>
- <property name="sizeAdjustPolicy">
- <enum>QAbstractScrollArea::AdjustToContentsOnFirstShow</enum>
- </property>
- <property name="widgetResizable">
- <bool>true</bool>
- </property>
- <widget class="QWidget" name="scrollAreaWidgetContents">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>359</width>
- <height>320</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="MinimumExpanding">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- <resources/>
- <connections/>
-</ui>
diff --git a/src/gui/sharee.cpp b/src/gui/sharee.cpp
index ed447e2a8..206a9d0e6 100644
--- a/src/gui/sharee.cpp
+++ b/src/gui/sharee.cpp
@@ -66,160 +66,4 @@ Sharee::Type Sharee::type() const
return _type;
}
-ShareeModel::ShareeModel(const AccountPtr &account, const QString &type, QObject *parent)
- : QAbstractListModel(parent)
- , _account(account)
- , _type(type)
-{
-}
-
-void ShareeModel::fetch(const QString &search, const ShareeSet &blacklist, LookupMode lookupMode)
-{
- _search = search;
- _shareeBlacklist = blacklist;
- auto *job = new OcsShareeJob(_account);
- connect(job, &OcsShareeJob::shareeJobFinished, this, &ShareeModel::shareesFetched);
- connect(job, &OcsJob::ocsError, this, &ShareeModel::displayErrorMessage);
- job->getSharees(_search, _type, 1, 50, lookupMode == GlobalSearch ? true : false);
-}
-
-void ShareeModel::shareesFetched(const QJsonDocument &reply)
-{
- QVector<QSharedPointer<Sharee>> newSharees;
-
- {
- const QStringList shareeTypes {"users", "groups", "emails", "remotes", "circles", "rooms"};
-
- const auto appendSharees = [this, &shareeTypes](const QJsonObject &data, QVector<QSharedPointer<Sharee>>& out) {
- for (const auto &shareeType : shareeTypes) {
- const auto category = data.value(shareeType).toArray();
- for (const auto &sharee : category) {
- out.append(parseSharee(sharee.toObject()));
- }
- }
- };
-
- appendSharees(reply.object().value("ocs").toObject().value("data").toObject(), newSharees);
- appendSharees(reply.object().value("ocs").toObject().value("data").toObject().value("exact").toObject(), newSharees);
- }
-
- // Filter sharees that we have already shared with
- QVector<QSharedPointer<Sharee>> filteredSharees;
- foreach (const auto &sharee, newSharees) {
- bool found = false;
- foreach (const auto &blacklistSharee, _shareeBlacklist) {
- if (sharee->type() == blacklistSharee->type() && sharee->shareWith() == blacklistSharee->shareWith()) {
- found = true;
- break;
- }
- }
-
- if (found == false) {
- filteredSharees.append(sharee);
- }
- }
-
- setNewSharees(filteredSharees);
- shareesReady();
-}
-
-QSharedPointer<Sharee> ShareeModel::parseSharee(const QJsonObject &data)
-{
- QString displayName = data.value("label").toString();
- const QString shareWith = data.value("value").toObject().value("shareWith").toString();
- Sharee::Type type = (Sharee::Type)data.value("value").toObject().value("shareType").toInt();
- const QString additionalInfo = data.value("value").toObject().value("shareWithAdditionalInfo").toString();
- if (!additionalInfo.isEmpty()) {
- displayName = tr("%1 (%2)", "sharee (shareWithAdditionalInfo)").arg(displayName, additionalInfo);
- }
-
- return QSharedPointer<Sharee>(new Sharee(shareWith, displayName, type));
-}
-
-
-// Helper function for setNewSharees (could be a lambda when we can use them)
-static QSharedPointer<Sharee> shareeFromModelIndex(const QModelIndex &idx)
-{
- return idx.data(Qt::UserRole).value<QSharedPointer<Sharee>>();
-}
-
-struct FindShareeHelper
-{
- const QSharedPointer<Sharee> &sharee;
- bool operator()(const QSharedPointer<Sharee> &s2) const
- {
- return s2->format() == sharee->format() && s2->displayName() == sharee->format();
- }
-};
-
-/* Set the new sharee
-
- Do that while preserving the model index so the selection stays
-*/
-void ShareeModel::setNewSharees(const QVector<QSharedPointer<Sharee>> &newSharees)
-{
- layoutAboutToBeChanged();
- const auto persistent = persistentIndexList();
- QVector<QSharedPointer<Sharee>> oldPersistantSharee;
- oldPersistantSharee.reserve(persistent.size());
-
- std::transform(persistent.begin(), persistent.end(), std::back_inserter(oldPersistantSharee),
- shareeFromModelIndex);
-
- _sharees = newSharees;
-
- QModelIndexList newPersistant;
- newPersistant.reserve(persistent.size());
- foreach (const QSharedPointer<Sharee> &sharee, oldPersistantSharee) {
- FindShareeHelper helper = { sharee };
- auto it = std::find_if(_sharees.constBegin(), _sharees.constEnd(), helper);
- if (it == _sharees.constEnd()) {
- newPersistant << QModelIndex();
- } else {
- newPersistant << index(std::distance(_sharees.constBegin(), it));
- }
- }
-
- changePersistentIndexList(persistent, newPersistant);
- layoutChanged();
-}
-
-
-int ShareeModel::rowCount(const QModelIndex &) const
-{
- return _sharees.size();
-}
-
-QVariant ShareeModel::data(const QModelIndex &index, int role) const
-{
- if (index.row() < 0 || index.row() > _sharees.size()) {
- return QVariant();
- }
-
- const auto &sharee = _sharees.at(index.row());
- if (role == Qt::DisplayRole) {
- return sharee->format();
-
- } else if (role == Qt::EditRole) {
- // This role is used by the completer - it should match
- // the full name and the user name and thus we include both
- // in the output here. But we need to take care this string
- // doesn't leak to the user.
- return QString(sharee->displayName() + " (" + sharee->shareWith() + ")");
-
- } else if (role == Qt::UserRole) {
- return QVariant::fromValue(sharee);
- }
-
- return QVariant();
-}
-
-QSharedPointer<Sharee> ShareeModel::getSharee(int at)
-{
- if (at < 0 || at > _sharees.size()) {
- return QSharedPointer<Sharee>(nullptr);
- }
-
- return _sharees.at(at);
-}
}
diff --git a/src/gui/sharee.h b/src/gui/sharee.h
index b1aa8f2c2..2139a9117 100644
--- a/src/gui/sharee.h
+++ b/src/gui/sharee.h
@@ -61,47 +61,9 @@ private:
Type _type;
};
-
-class ShareeModel : public QAbstractListModel
-{
- Q_OBJECT
-public:
- enum LookupMode {
- LocalSearch = 0,
- GlobalSearch = 1
- };
-
- explicit ShareeModel(const AccountPtr &account, const QString &type, QObject *parent = nullptr);
-
- using ShareeSet = QVector<QSharedPointer<Sharee>>; // FIXME: make it a QSet<Sharee> when Sharee can be compared
- void fetch(const QString &search, const ShareeSet &blacklist, LookupMode lookupMode);
- [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
- [[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
-
- QSharedPointer<Sharee> getSharee(int at);
-
- [[nodiscard]] QString currentSearch() const { return _search; }
-
-signals:
- void shareesReady();
- void displayErrorMessage(int code, const QString &);
-
-private slots:
- void shareesFetched(const QJsonDocument &reply);
-
-private:
- QSharedPointer<Sharee> parseSharee(const QJsonObject &data);
- void setNewSharees(const QVector<QSharedPointer<Sharee>> &newSharees);
-
- AccountPtr _account;
- QString _search;
- QString _type;
-
- QVector<QSharedPointer<Sharee>> _sharees;
- QVector<QSharedPointer<Sharee>> _shareeBlacklist;
-};
+using ShareePtr = QSharedPointer<OCC::Sharee>;
}
-Q_DECLARE_METATYPE(QSharedPointer<OCC::Sharee>)
+Q_DECLARE_METATYPE(OCC::ShareePtr)
#endif //SHAREE_H
diff --git a/src/gui/sharelinkwidget.cpp b/src/gui/sharelinkwidget.cpp
deleted file mode 100644
index 3f6b437a6..000000000
--- a/src/gui/sharelinkwidget.cpp
+++ /dev/null
@@ -1,625 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- * Copyright (C) 2015 by Klaas Freitag <freitag@owncloud.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) 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 "ui_sharelinkwidget.h"
-#include "sharelinkwidget.h"
-#include "account.h"
-#include "capabilities.h"
-#include "guiutility.h"
-#include "sharemanager.h"
-#include "theme.h"
-#include "elidedlabel.h"
-
-#include "QProgressIndicator.h"
-#include <QBuffer>
-#include <QClipboard>
-#include <QFileInfo>
-#include <QDesktopServices>
-#include <QMessageBox>
-#include <QMenu>
-#include <QTextEdit>
-#include <QToolButton>
-#include <QPropertyAnimation>
-
-namespace {
- const char *passwordIsSetPlaceholder = "●●●●●●●●";
-}
-
-namespace OCC {
-
-Q_LOGGING_CATEGORY(lcShareLink, "nextcloud.gui.sharelink", QtInfoMsg)
-
-ShareLinkWidget::ShareLinkWidget(AccountPtr account,
- const QString &sharePath,
- const QString &localPath,
- SharePermissions maxSharingPermissions,
- QWidget *parent)
- : QWidget(parent)
- , _ui(new Ui::ShareLinkWidget)
- , _account(account)
- , _sharePath(sharePath)
- , _localPath(localPath)
- , _linkShare(nullptr)
- , _passwordRequired(false)
- , _expiryRequired(false)
- , _namesSupported(true)
- , _noteRequired(false)
- , _linkContextMenu(nullptr)
- , _readOnlyLinkAction(nullptr)
- , _allowEditingLinkAction(nullptr)
- , _allowUploadEditingLinkAction(nullptr)
- , _allowUploadLinkAction(nullptr)
- , _passwordProtectLinkAction(nullptr)
- , _expirationDateLinkAction(nullptr)
- , _unshareLinkAction(nullptr)
- , _noteLinkAction(nullptr)
-{
- _ui->setupUi(this);
-
- _ui->shareLinkToolButton->hide();
-
- //Is this a file or folder?
- QFileInfo fi(localPath);
- _isFile = fi.isFile();
-
- connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
- connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreatePassword);
- connect(_ui->confirmPassword, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreatePassword);
- connect(_ui->confirmNote, &QAbstractButton::clicked, this, &ShareLinkWidget::slotCreateNote);
- connect(_ui->confirmExpirationDate, &QAbstractButton::clicked, this, &ShareLinkWidget::slotSetExpireDate);
-
- _ui->errorLabel->hide();
-
- if (!_account->capabilities().sharePublicLink()) {
- qCWarning(lcShareLink) << "Link shares have been disabled";
- } else if (!(maxSharingPermissions & SharePermissionShare)) {
- qCWarning(lcShareLink) << "The file can not be shared because it was shared without sharing permission.";
- }
-
- _ui->enableShareLink->setChecked(false);
- _ui->shareLinkToolButton->setEnabled(false);
- _ui->shareLinkToolButton->hide();
-
- // Older servers don't support multiple public link shares
- if (!_account->capabilities().sharePublicLinkMultiple()) {
- _namesSupported = false;
- }
-
- togglePasswordOptions(false);
- toggleExpireDateOptions(false);
- toggleNoteOptions(false);
-
- _ui->noteProgressIndicator->setVisible(false);
- _ui->passwordProgressIndicator->setVisible(false);
- _ui->expirationDateProgressIndicator->setVisible(false);
- _ui->sharelinkProgressIndicator->setVisible(false);
-
- // check if the file is already inside of a synced folder
- if (sharePath.isEmpty()) {
- qCWarning(lcShareLink) << "Unable to share files not in a sync folder.";
- return;
- }
-}
-
-ShareLinkWidget::~ShareLinkWidget()
-{
- delete _ui;
-}
-
-void ShareLinkWidget::slotToggleShareLinkAnimation(const bool start)
-{
- _ui->sharelinkProgressIndicator->setVisible(start);
- if (start) {
- if (!_ui->sharelinkProgressIndicator->isAnimated()) {
- _ui->sharelinkProgressIndicator->startAnimation();
- }
- } else {
- _ui->sharelinkProgressIndicator->stopAnimation();
- }
-}
-
-void ShareLinkWidget::toggleButtonAnimation(QToolButton *button, QProgressIndicator *progressIndicator, const QAction *checkedAction) const
-{
- auto startAnimation = false;
- const auto actionIsChecked = checkedAction->isChecked();
- if (!progressIndicator->isAnimated() && actionIsChecked) {
- progressIndicator->startAnimation();
- startAnimation = true;
- } else {
- progressIndicator->stopAnimation();
- }
-
- button->setVisible(!startAnimation && actionIsChecked);
- progressIndicator->setVisible(startAnimation && actionIsChecked);
-}
-
-void ShareLinkWidget::setLinkShare(QSharedPointer<LinkShare> linkShare)
-{
- _linkShare = linkShare;
-}
-
-QSharedPointer<LinkShare> ShareLinkWidget::getLinkShare()
-{
- return _linkShare;
-}
-
-void ShareLinkWidget::focusPasswordLineEdit()
-{
- _ui->lineEdit_password->setFocus();
-}
-
-void ShareLinkWidget::setupUiOptions()
-{
- connect(_linkShare.data(), &LinkShare::noteSet, this, &ShareLinkWidget::slotNoteSet);
- connect(_linkShare.data(), &LinkShare::passwordSet, this, &ShareLinkWidget::slotPasswordSet);
- connect(_linkShare.data(), &LinkShare::passwordSetError, this, &ShareLinkWidget::slotPasswordSetError);
- connect(_linkShare.data(), &LinkShare::labelSet, this, &ShareLinkWidget::slotLabelSet);
-
- // Prepare permissions check and create group action
- const QDate expireDate = _linkShare.data()->getExpireDate().isValid() ? _linkShare.data()->getExpireDate() : QDate();
- const SharePermissions perm = _linkShare.data()->getPermissions();
- auto checked = false;
- auto *permissionsGroup = new QActionGroup(this);
-
- // Prepare sharing menu
- _linkContextMenu = new QMenu(this);
-
- // radio button style
- permissionsGroup->setExclusive(true);
-
- if (_isFile) {
- checked = (perm & SharePermissionRead) && (perm & SharePermissionUpdate);
- _allowEditingLinkAction = _linkContextMenu->addAction(tr("Allow editing"));
- _allowEditingLinkAction->setCheckable(true);
- _allowEditingLinkAction->setChecked(checked);
-
- } else {
- checked = (perm == SharePermissionRead);
- _readOnlyLinkAction = permissionsGroup->addAction(tr("View only"));
- _readOnlyLinkAction->setCheckable(true);
- _readOnlyLinkAction->setChecked(checked);
-
- checked = (perm & SharePermissionRead) && (perm & SharePermissionCreate)
- && (perm & SharePermissionUpdate) && (perm & SharePermissionDelete);
- _allowUploadEditingLinkAction = permissionsGroup->addAction(tr("Allow upload and editing"));
- _allowUploadEditingLinkAction->setCheckable(true);
- _allowUploadEditingLinkAction->setChecked(checked);
-
- checked = (perm == SharePermissionCreate);
- _allowUploadLinkAction = permissionsGroup->addAction(tr("File drop (upload only)"));
- _allowUploadLinkAction->setCheckable(true);
- _allowUploadLinkAction->setChecked(checked);
- }
-
- _shareLinkElidedLabel = new OCC::ElidedLabel(this);
- _shareLinkElidedLabel->setElideMode(Qt::ElideRight);
- displayShareLinkLabel();
- _ui->horizontalLayout->insertWidget(2, _shareLinkElidedLabel);
-
- _shareLinkLayout = new QHBoxLayout(this);
-
- _shareLinkLabel = new QLabel(this);
- _shareLinkLabel->setPixmap(QString(":/client/theme/black/edit.svg"));
- _shareLinkLayout->addWidget(_shareLinkLabel);
-
- _shareLinkEdit = new QLineEdit(this);
- connect(_shareLinkEdit, &QLineEdit::returnPressed, this, &ShareLinkWidget::slotCreateLabel);
- _shareLinkEdit->setPlaceholderText(tr("Link name"));
- _shareLinkEdit->setText(_linkShare.data()->getLabel());
- _shareLinkLayout->addWidget(_shareLinkEdit);
-
- _shareLinkButton = new QToolButton(this);
- connect(_shareLinkButton, &QToolButton::clicked, this, &ShareLinkWidget::slotCreateLabel);
- _shareLinkButton->setIcon(QIcon(":/client/theme/confirm.svg"));
- _shareLinkButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
- _shareLinkLayout->addWidget(_shareLinkButton);
-
- _shareLinkProgressIndicator = new QProgressIndicator(this);
- _shareLinkProgressIndicator->setVisible(false);
- _shareLinkLayout->addWidget(_shareLinkProgressIndicator);
-
- _shareLinkDefaultWidget = new QWidget(this);
- _shareLinkDefaultWidget->setLayout(_shareLinkLayout);
-
- _shareLinkWidgetAction = new QWidgetAction(this);
- _shareLinkWidgetAction->setDefaultWidget(_shareLinkDefaultWidget);
- _shareLinkWidgetAction->setCheckable(true);
- _linkContextMenu->addAction(_shareLinkWidgetAction);
-
- // Adds permissions actions (radio button style)
- if (_isFile) {
- _linkContextMenu->addAction(_allowEditingLinkAction);
- } else {
- _linkContextMenu->addAction(_readOnlyLinkAction);
- _linkContextMenu->addAction(_allowUploadEditingLinkAction);
- _linkContextMenu->addAction(_allowUploadLinkAction);
- }
-
- // Adds action to display note widget (check box)
- _noteLinkAction = _linkContextMenu->addAction(tr("Note to recipient"));
- _noteLinkAction->setCheckable(true);
-
- if (_linkShare->getNote().isSimpleText() && !_linkShare->getNote().isEmpty()) {
- _ui->textEdit_note->setText(_linkShare->getNote());
- _noteLinkAction->setChecked(true);
- toggleNoteOptions();
- }
-
- // Adds action to display password widget (check box)
- _passwordProtectLinkAction = _linkContextMenu->addAction(tr("Password protect"));
- _passwordProtectLinkAction->setCheckable(true);
-
- if (_linkShare.data()->isPasswordSet()) {
- _passwordProtectLinkAction->setChecked(true);
- _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder));
- togglePasswordOptions();
- }
-
- // If password is enforced then don't allow users to disable it
- if (_account->capabilities().sharePublicLinkEnforcePassword()) {
- if (_linkShare.data()->isPasswordSet()) {
- _passwordProtectLinkAction->setChecked(true);
- _passwordProtectLinkAction->setEnabled(false);
- }
- _passwordRequired = true;
- }
-
- // Adds action to display expiration date widget (check box)
- _expirationDateLinkAction = _linkContextMenu->addAction(tr("Set expiration date"));
- _expirationDateLinkAction->setCheckable(true);
- if (!expireDate.isNull()) {
- _ui->calendar->setDate(expireDate);
- _expirationDateLinkAction->setChecked(true);
- toggleExpireDateOptions();
- }
- connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareLinkWidget::slotSetExpireDate);
- connect(_linkShare.data(), &LinkShare::expireDateSet, this, &ShareLinkWidget::slotExpireDateSet);
-
-
- // If expiredate is enforced do not allow disable and set max days
- if (_account->capabilities().sharePublicLinkEnforceExpireDate()) {
- _ui->calendar->setMaximumDate(QDate::currentDate().addDays(
- _account->capabilities().sharePublicLinkExpireDateDays()));
- _expirationDateLinkAction->setChecked(true);
- _expirationDateLinkAction->setEnabled(false);
- _expiryRequired = true;
- }
-
- // Adds action to unshare widget (check box)
- _unshareLinkAction.reset(_linkContextMenu->addAction(QIcon(":/client/theme/delete.svg"),
- tr("Delete link")));
-
- _linkContextMenu->addSeparator();
-
- _addAnotherLinkAction.reset(_linkContextMenu->addAction(QIcon(":/client/theme/add.svg"),
- tr("Add another link")));
-
- _ui->enableShareLink->setIcon(QIcon(":/client/theme/copy.svg"));
- disconnect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCreateShareLink);
- connect(_ui->enableShareLink, &QPushButton::clicked, this, &ShareLinkWidget::slotCopyLinkShare);
-
- connect(_linkContextMenu, &QMenu::triggered,
- this, &ShareLinkWidget::slotLinkContextMenuActionTriggered);
-
- _ui->shareLinkToolButton->setMenu(_linkContextMenu);
- _ui->shareLinkToolButton->setEnabled(true);
- _ui->enableShareLink->setEnabled(true);
- _ui->enableShareLink->setChecked(true);
-
- // show sharing options
- _ui->shareLinkToolButton->show();
-
- customizeStyle();
-}
-
-void ShareLinkWidget::slotCreateNote()
-{
- const auto note = _ui->textEdit_note->toPlainText();
- if (!_linkShare || _linkShare->getNote() == note || note.isEmpty()) {
- return;
- }
-
- toggleButtonAnimation(_ui->confirmNote, _ui->noteProgressIndicator, _noteLinkAction);
- _ui->errorLabel->hide();
- _linkShare->setNote(note);
-}
-
-void ShareLinkWidget::slotNoteSet()
-{
- toggleButtonAnimation(_ui->confirmNote, _ui->noteProgressIndicator, _noteLinkAction);
-}
-
-void ShareLinkWidget::slotCopyLinkShare(const bool clicked) const
-{
- Q_UNUSED(clicked);
-
- QApplication::clipboard()->setText(_linkShare->getLink().toString());
-}
-
-void ShareLinkWidget::slotExpireDateSet()
-{
- toggleButtonAnimation(_ui->confirmExpirationDate, _ui->expirationDateProgressIndicator, _expirationDateLinkAction);
-}
-
-void ShareLinkWidget::slotSetExpireDate()
-{
- if (!_linkShare) {
- return;
- }
-
- toggleButtonAnimation(_ui->confirmExpirationDate, _ui->expirationDateProgressIndicator, _expirationDateLinkAction);
- _ui->errorLabel->hide();
- _linkShare->setExpireDate(_ui->calendar->date());
-}
-
-void ShareLinkWidget::slotCreatePassword()
-{
- if (!_linkShare || _ui->lineEdit_password->text().isEmpty()) {
- return;
- }
-
- toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction);
- _ui->errorLabel->hide();
- emit createPassword(_ui->lineEdit_password->text());
-}
-
-void ShareLinkWidget::slotCreateShareLink(const bool clicked)
-{
- Q_UNUSED(clicked);
- slotToggleShareLinkAnimation(true);
- emit createLinkShare();
-}
-
-void ShareLinkWidget::slotPasswordSet()
-{
- toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction);
-
- _ui->lineEdit_password->setText({});
-
- if (_linkShare->isPasswordSet()) {
- _ui->lineEdit_password->setEnabled(true);
- _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder));
- } else {
- _ui->lineEdit_password->setPlaceholderText({});
- }
-
- emit createPasswordProcessed();
-}
-
-void ShareLinkWidget::slotPasswordSetError(const int code, const QString &message)
-{
- toggleButtonAnimation(_ui->confirmPassword, _ui->passwordProgressIndicator, _passwordProtectLinkAction);
-
- slotServerError(code, message);
- togglePasswordOptions();
- _ui->lineEdit_password->setFocus();
- emit createPasswordProcessed();
-}
-
-void ShareLinkWidget::slotDeleteShareFetched()
-{
- slotToggleShareLinkAnimation(false);
-
- _linkShare.clear();
- togglePasswordOptions(false);
- toggleNoteOptions(false);
- toggleExpireDateOptions(false);
- emit deleteLinkShare();
-}
-
-void ShareLinkWidget::toggleNoteOptions(const bool enable)
-{
- _ui->noteLabel->setVisible(enable);
- _ui->textEdit_note->setVisible(enable);
- _ui->confirmNote->setVisible(enable);
- _ui->textEdit_note->setText(enable && _linkShare ? _linkShare->getNote() : QString());
-
- if (!enable && _linkShare && !_linkShare->getNote().isEmpty()) {
- _linkShare->setNote({});
- }
-}
-
-void ShareLinkWidget::slotCreateLabel()
-{
- const auto labelText = _shareLinkEdit->text();
- if (!_linkShare || _linkShare->getLabel() == labelText || labelText.isEmpty()) {
- return;
- }
- _shareLinkWidgetAction->setChecked(true);
- toggleButtonAnimation(_shareLinkButton, _shareLinkProgressIndicator, _shareLinkWidgetAction);
- _ui->errorLabel->hide();
- _linkShare->setLabel(_shareLinkEdit->text());
-}
-
-void ShareLinkWidget::slotLabelSet()
-{
- toggleButtonAnimation(_shareLinkButton, _shareLinkProgressIndicator, _shareLinkWidgetAction);
- displayShareLinkLabel();
-}
-
-void ShareLinkWidget::slotCreateShareRequiresPassword(const QString &message)
-{
- slotToggleShareLinkAnimation(message.isEmpty());
-
- if (!message.isEmpty()) {
- _ui->errorLabel->setText(message);
- _ui->errorLabel->show();
- }
-
- _passwordRequired = true;
-
- togglePasswordOptions();
-}
-
-void ShareLinkWidget::togglePasswordOptions(const bool enable)
-{
- _ui->passwordLabel->setVisible(enable);
- _ui->lineEdit_password->setVisible(enable);
- _ui->confirmPassword->setVisible(enable);
- _ui->lineEdit_password->setFocus();
-
- if (!enable && _linkShare && _linkShare->isPasswordSet()) {
- _linkShare->setPassword({});
- }
-}
-
-void ShareLinkWidget::toggleExpireDateOptions(const bool enable)
-{
- _ui->expirationLabel->setVisible(enable);
- _ui->calendar->setVisible(enable);
- _ui->confirmExpirationDate->setVisible(enable);
-
- const auto date = enable ? _linkShare->getExpireDate() : QDate::currentDate().addDays(1);
- _ui->calendar->setDate(date);
- _ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
-
- if(_account->capabilities().sharePublicLinkEnforceExpireDate()) {
- _ui->calendar->setMaximumDate(QDate::currentDate().addDays(_account->capabilities().sharePublicLinkExpireDateDays()));
- }
-
- _ui->calendar->setFocus();
-
- if (!enable && _linkShare && _linkShare->getExpireDate().isValid()) {
- _linkShare->setExpireDate({});
- }
-}
-
-void ShareLinkWidget::confirmAndDeleteShare()
-{
- auto messageBox = new QMessageBox(
- QMessageBox::Question,
- tr("Confirm Link Share Deletion"),
- tr("<p>Do you really want to delete the public link share <i>%1</i>?</p>"
- "<p>Note: This action cannot be undone.</p>")
- .arg(shareName()),
- QMessageBox::NoButton,
- this);
- QPushButton *yesButton =
- messageBox->addButton(tr("Delete"), QMessageBox::YesRole);
- messageBox->addButton(tr("Cancel"), QMessageBox::NoRole);
-
- connect(messageBox, &QMessageBox::finished, this,
- [messageBox, yesButton, this]() {
- if (messageBox->clickedButton() == yesButton) {
- this->slotToggleShareLinkAnimation(true);
- this->_linkShare->deleteShare();
- }
- });
- messageBox->open();
-}
-
-QString ShareLinkWidget::shareName() const
-{
- QString name = _linkShare->getName();
- if (!name.isEmpty())
- return name;
- if (!_namesSupported)
- return tr("Public link");
- return _linkShare->getToken();
-}
-
-void ShareLinkWidget::slotContextMenuButtonClicked()
-{
- _linkContextMenu->exec(QCursor::pos());
-}
-
-void ShareLinkWidget::slotLinkContextMenuActionTriggered(QAction *action)
-{
- const auto state = action->isChecked();
- SharePermissions perm = SharePermissionRead;
-
- if (action == _addAnotherLinkAction.data()) {
- emit createLinkShare();
-
- } else if (action == _readOnlyLinkAction && state) {
- _linkShare->setPermissions(perm);
-
- } else if (action == _allowEditingLinkAction && state) {
- perm |= SharePermissionUpdate;
- _linkShare->setPermissions(perm);
-
- } else if (action == _allowUploadEditingLinkAction && state) {
- perm |= SharePermissionCreate | SharePermissionUpdate | SharePermissionDelete;
- _linkShare->setPermissions(perm);
-
- } else if (action == _allowUploadLinkAction && state) {
- perm = SharePermissionCreate;
- _linkShare->setPermissions(perm);
-
- } else if (action == _passwordProtectLinkAction) {
- togglePasswordOptions(state);
-
- } else if (action == _expirationDateLinkAction) {
- toggleExpireDateOptions(state);
-
- } else if (action == _noteLinkAction) {
- toggleNoteOptions(state);
-
- } else if (action == _unshareLinkAction.data()) {
- confirmAndDeleteShare();
- }
-}
-
-void ShareLinkWidget::slotServerError(const int code, const QString &message)
-{
- slotToggleShareLinkAnimation(false);
-
- qCWarning(lcSharing) << "Error from server" << code << message;
- displayError(message);
-}
-
-void ShareLinkWidget::displayError(const QString &errMsg)
-{
- _ui->errorLabel->setText(errMsg);
- _ui->errorLabel->show();
-}
-
-void ShareLinkWidget::slotStyleChanged()
-{
- customizeStyle();
-}
-
-void ShareLinkWidget::customizeStyle()
-{
- if(_unshareLinkAction) {
- _unshareLinkAction->setIcon(Theme::createColorAwareIcon(":/client/theme/delete.svg"));
- }
-
- if(_addAnotherLinkAction) {
- _addAnotherLinkAction->setIcon(Theme::createColorAwareIcon(":/client/theme/add.svg"));
- }
-
- _ui->enableShareLink->setIcon(Theme::createColorAwareIcon(":/client/theme/copy.svg"));
-
- _ui->shareLinkIconLabel->setPixmap(Theme::createColorAwarePixmap(":/client/theme/public.svg"));
-
- _ui->shareLinkToolButton->setIcon(Theme::createColorAwareIcon(":/client/theme/more.svg"));
-
- _ui->confirmNote->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
- _ui->confirmPassword->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
- _ui->confirmExpirationDate->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
-
- _ui->passwordProgressIndicator->setColor(QGuiApplication::palette().color(QPalette::Text));
-}
-
-void ShareLinkWidget::displayShareLinkLabel()
-{
- _shareLinkElidedLabel->clear();
- if (!_linkShare->getLabel().isEmpty()) {
- _shareLinkElidedLabel->setText(QString("(%1)").arg(_linkShare->getLabel()));
- }
-}
-
-}
diff --git a/src/gui/sharelinkwidget.h b/src/gui/sharelinkwidget.h
deleted file mode 100644
index 7ecd9691e..000000000
--- a/src/gui/sharelinkwidget.h
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@famdouma.nl>
- * Copyright (C) 2015 by Klaas Freitag <freitag@owncloud.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) 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 SHARELINKWIDGET_H
-#define SHARELINKWIDGET_H
-
-#include "accountfwd.h"
-#include "sharepermissions.h"
-#include "QProgressIndicator.h"
-#include <QDialog>
-#include <QSharedPointer>
-#include <QList>
-#include <QToolButton>
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QLineEdit>
-#include <QWidgetAction>
-
-class QMenu;
-class QTableWidgetItem;
-
-namespace OCC {
-
-namespace Ui {
- class ShareLinkWidget;
-}
-
-class AbstractCredentials;
-class SyncResult;
-class LinkShare;
-class Share;
-class ElidedLabel;
-
-/**
- * @brief The ShareDialog class
- * @ingroup gui
- */
-class ShareLinkWidget : public QWidget
-{
- Q_OBJECT
-
-public:
- explicit ShareLinkWidget(AccountPtr account,
- const QString &sharePath,
- const QString &localPath,
- SharePermissions maxSharingPermissions,
- QWidget *parent = nullptr);
- ~ShareLinkWidget() override;
-
- void toggleButton(bool show);
- void setupUiOptions();
-
- void setLinkShare(QSharedPointer<LinkShare> linkShare);
- QSharedPointer<LinkShare> getLinkShare();
-
- void focusPasswordLineEdit();
-
-public slots:
- void slotDeleteShareFetched();
- void slotToggleShareLinkAnimation(const bool start);
- void slotServerError(const int code, const QString &message);
- void slotCreateShareRequiresPassword(const QString &message);
- void slotStyleChanged();
-
-private slots:
- void slotCreateShareLink(const bool clicked);
- void slotCopyLinkShare(const bool clicked) const;
-
- void slotCreatePassword();
- void slotPasswordSet();
- void slotPasswordSetError(const int code, const QString &message);
-
- void slotCreateNote();
- void slotNoteSet();
-
- void slotSetExpireDate();
- void slotExpireDateSet();
-
- void slotContextMenuButtonClicked();
- void slotLinkContextMenuActionTriggered(QAction *action);
-
- void slotCreateLabel();
- void slotLabelSet();
-
-signals:
- void createLinkShare();
- void deleteLinkShare();
- void visualDeletionDone();
- void createPassword(const QString &password);
- void createPasswordProcessed();
-
-private:
- void displayError(const QString &errMsg);
-
- void togglePasswordOptions(const bool enable = true);
- void toggleNoteOptions(const bool enable = true);
- void toggleExpireDateOptions(const bool enable = true);
- void toggleButtonAnimation(QToolButton *button, QProgressIndicator *progressIndicator, const QAction *checkedAction) const;
-
- /** Confirm with the user and then delete the share */
- void confirmAndDeleteShare();
-
- /** Retrieve a share's name, accounting for _namesSupported */
- [[nodiscard]] QString shareName() const;
-
- void customizeStyle();
-
- void displayShareLinkLabel();
-
- Ui::ShareLinkWidget *_ui;
- AccountPtr _account;
- QString _sharePath;
- QString _localPath;
- QString _shareUrl;
-
- QSharedPointer<LinkShare> _linkShare;
-
- bool _isFile;
- bool _passwordRequired;
- bool _expiryRequired;
- bool _namesSupported;
- bool _noteRequired;
-
- QMenu *_linkContextMenu;
- QAction *_readOnlyLinkAction;
- QAction *_allowEditingLinkAction;
- QAction *_allowUploadEditingLinkAction;
- QAction *_allowUploadLinkAction;
- QAction *_passwordProtectLinkAction;
- QAction *_expirationDateLinkAction;
- QScopedPointer<QAction> _unshareLinkAction;
- QScopedPointer<QAction> _addAnotherLinkAction;
- QAction *_noteLinkAction;
- QHBoxLayout *_shareLinkLayout{};
- QLabel *_shareLinkLabel{};
- ElidedLabel *_shareLinkElidedLabel{};
- QLineEdit *_shareLinkEdit{};
- QToolButton *_shareLinkButton{};
- QProgressIndicator *_shareLinkProgressIndicator{};
- QWidget *_shareLinkDefaultWidget{};
- QWidgetAction *_shareLinkWidgetAction{};
-};
-}
-
-#endif // SHARELINKWIDGET_H
diff --git a/src/gui/sharelinkwidget.ui b/src/gui/sharelinkwidget.ui
deleted file mode 100644
index a0d5f3df9..000000000
--- a/src/gui/sharelinkwidget.ui
+++ /dev/null
@@ -1,439 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>OCC::ShareLinkWidget</class>
- <widget class="QWidget" name="OCC::ShareLinkWidget">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>400</width>
- <height>238</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
- <number>0</number>
- </property>
- <property name="leftMargin">
- <number>12</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>20</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLabel" name="shareLinkIconLabel">
- <property name="text">
- <string notr="true"/>
- </property>
- <property name="pixmap">
- <pixmap resource="../../theme.qrc">:/client/theme/public.svg</pixmap>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QLabel" name="shareLinkLabel">
- <property name="text">
- <string>Share link</string>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>25</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QProgressIndicator" name="sharelinkProgressIndicator" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>28</width>
- <height>27</height>
- </size>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_2">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>25</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="enableShareLink">
- <property name="text">
- <string/>
- </property>
- <property name="icon">
- <iconset resource="../../theme.qrc">
- <normaloff>:/client/theme/add.svg</normaloff>:/client/theme/add.svg</iconset>
- </property>
- <property name="checkable">
- <bool>false</bool>
- </property>
- <property name="flat">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="shareLinkToolButton">
- <property name="enabled">
- <bool>false</bool>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="icon">
- <iconset resource="../../theme.qrc">
- <normaloff>:/client/theme/more.svg</normaloff>:/client/theme/more.svg</iconset>
- </property>
- <property name="popupMode">
- <enum>QToolButton::InstantPopup</enum>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QGridLayout" name="gridLayout">
- <property name="leftMargin">
- <number>22</number>
- </property>
- <item row="0" column="0">
- <widget class="QLabel" name="noteLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>78</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Note</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
- </property>
- <property name="indent">
- <number>0</number>
- </property>
- </widget>
- </item>
- <item row="0" column="1">
- <widget class="QTextEdit" name="textEdit_note">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>0</width>
- <height>60</height>
- </size>
- </property>
- <property name="sizeAdjustPolicy">
- <enum>QAbstractScrollArea::AdjustToContents</enum>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QToolButton" name="confirmNote">
- <property name="minimumSize">
- <size>
- <width>28</width>
- <height>27</height>
- </size>
- </property>
- <property name="icon">
- <iconset resource="../../theme.qrc">
- <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="0" column="2">
- <widget class="QProgressIndicator" name="noteProgressIndicator" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>28</width>
- <height>27</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="1" column="0">
- <widget class="QLabel" name="passwordLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>78</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Set password</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
- </property>
- <property name="indent">
- <number>0</number>
- </property>
- </widget>
- </item>
- <item row="1" column="1">
- <widget class="QLineEdit" name="lineEdit_password">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>1</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="echoMode">
- <enum>QLineEdit::Password</enum>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QToolButton" name="confirmPassword">
- <property name="minimumSize">
- <size>
- <width>28</width>
- <height>27</height>
- </size>
- </property>
- <property name="icon">
- <iconset resource="../../theme.qrc">
- <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="1" column="2">
- <widget class="QProgressIndicator" name="passwordProgressIndicator" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>28</width>
- <height>27</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="2" column="0">
- <widget class="QLabel" name="expirationLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>78</width>
- <height>0</height>
- </size>
- </property>
- <property name="text">
- <string>Expires</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
- </property>
- <property name="indent">
- <number>0</number>
- </property>
- </widget>
- </item>
- <item row="2" column="1">
- <widget class="QDateEdit" name="calendar">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
- <horstretch>1</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QToolButton" name="confirmExpirationDate">
- <property name="minimumSize">
- <size>
- <width>28</width>
- <height>27</height>
- </size>
- </property>
- <property name="icon">
- <iconset resource="../../theme.qrc">
- <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="QProgressIndicator" name="expirationDateProgressIndicator" native="true">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>28</width>
- <height>27</height>
- </size>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <layout class="QHBoxLayout" name="horizontalLayout_2">
- <item>
- <widget class="QLabel" name="errorLabel">
- <property name="palette">
- <palette>
- <active>
- <colorrole role="WindowText">
- <brush brushstyle="SolidPattern">
- <color alpha="255">
- <red>255</red>
- <green>0</green>
- <blue>0</blue>
- </color>
- </brush>
- </colorrole>
- </active>
- <inactive>
- <colorrole role="WindowText">
- <brush brushstyle="SolidPattern">
- <color alpha="255">
- <red>255</red>
- <green>0</green>
- <blue>0</blue>
- </color>
- </brush>
- </colorrole>
- </inactive>
- <disabled>
- <colorrole role="WindowText">
- <brush brushstyle="SolidPattern">
- <color alpha="255">
- <red>123</red>
- <green>121</green>
- <blue>134</blue>
- </color>
- </brush>
- </colorrole>
- </disabled>
- </palette>
- </property>
- <property name="text">
- <string notr="true">TextLabel</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- <layoutdefault spacing="6" margin="11"/>
- <customwidgets>
- <customwidget>
- <class>QProgressIndicator</class>
- <extends>QWidget</extends>
- <header>QProgressIndicator.h</header>
- <container>1</container>
- </customwidget>
- </customwidgets>
- <resources>
- <include location="../../theme.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/src/gui/sharemanager.cpp b/src/gui/sharemanager.cpp
index 154bcbec3..7a264557d 100644
--- a/src/gui/sharemanager.cpp
+++ b/src/gui/sharemanager.cpp
@@ -58,7 +58,7 @@ Share::Share(AccountPtr account,
const ShareType shareType,
bool isPasswordSet,
const Permissions permissions,
- const QSharedPointer<Sharee> shareWith)
+ const ShareePtr shareWith)
: _account(account)
, _id(id)
, _uidowner(uidowner)
@@ -101,7 +101,7 @@ Share::ShareType Share::getShareType() const
return _shareType;
}
-QSharedPointer<Sharee> Share::getShareWith() const
+ShareePtr Share::getShareWith() const
{
return _shareWith;
}
@@ -316,7 +316,7 @@ UserGroupShare::UserGroupShare(AccountPtr account,
const ShareType shareType,
bool isPasswordSet,
const Permissions permissions,
- const QSharedPointer<Sharee> shareWith,
+ const ShareePtr shareWith,
const QDate &expireDate,
const QString &note)
: Share(account, id, owner, ownerDisplayName, path, shareType, isPasswordSet, permissions, shareWith)
@@ -461,7 +461,7 @@ void ShareManager::slotShareCreated(const QJsonDocument &reply)
{
//Parse share
auto data = reply.object().value("ocs").toObject().value("data").toObject();
- QSharedPointer<Share> share(parseShare(data));
+ SharePtr share(parseShare(data));
emit shareCreated(share);
@@ -482,14 +482,14 @@ void ShareManager::slotSharesFetched(const QJsonDocument &reply)
const QString versionString = _account->serverVersion();
qCDebug(lcSharing) << versionString << "Fetched" << tmpShares.count() << "shares";
- QList<QSharedPointer<Share>> shares;
+ QList<SharePtr> shares;
foreach (const auto &share, tmpShares) {
auto data = share.toObject();
auto shareType = data.value("share_type").toInt();
- QSharedPointer<Share> newShare;
+ SharePtr newShare;
if (shareType == Share::TypeLink) {
newShare = parseLinkShare(data);
@@ -499,7 +499,7 @@ void ShareManager::slotSharesFetched(const QJsonDocument &reply)
newShare = parseShare(data);
}
- shares.append(QSharedPointer<Share>(newShare));
+ shares.append(SharePtr(newShare));
}
qCDebug(lcSharing) << "Sending " << shares.count() << "shares";
@@ -508,7 +508,7 @@ void ShareManager::slotSharesFetched(const QJsonDocument &reply)
QSharedPointer<UserGroupShare> ShareManager::parseUserGroupShare(const QJsonObject &data)
{
- QSharedPointer<Sharee> sharee(new Sharee(data.value("share_with").toString(),
+ ShareePtr sharee(new Sharee(data.value("share_with").toString(),
data.value("share_with_displayname").toString(),
static_cast<Sharee::Type>(data.value("share_type").toInt())));
@@ -577,13 +577,13 @@ QSharedPointer<LinkShare> ShareManager::parseLinkShare(const QJsonObject &data)
data.value("label").toString()));
}
-QSharedPointer<Share> ShareManager::parseShare(const QJsonObject &data)
+SharePtr ShareManager::parseShare(const QJsonObject &data) const
{
- QSharedPointer<Sharee> sharee(new Sharee(data.value("share_with").toString(),
+ ShareePtr sharee(new Sharee(data.value("share_with").toString(),
data.value("share_with_displayname").toString(),
(Sharee::Type)data.value("share_type").toInt()));
- return QSharedPointer<Share>(new Share(_account,
+ return SharePtr(new Share(_account,
data.value("id").toVariant().toString(), // "id" used to be an integer, support both
data.value("uid_owner").toVariant().toString(),
data.value("displayname_owner").toVariant().toString(),
diff --git a/src/gui/sharemanager.h b/src/gui/sharemanager.h
index 16d2e47c3..1d9918f04 100644
--- a/src/gui/sharemanager.h
+++ b/src/gui/sharemanager.h
@@ -36,6 +36,15 @@ class OcsShareJob;
class Share : public QObject
{
Q_OBJECT
+ Q_PROPERTY(AccountPtr account READ account CONSTANT)
+ Q_PROPERTY(QString path READ path CONSTANT)
+ Q_PROPERTY(QString id READ getId CONSTANT)
+ Q_PROPERTY(QString uidOwner READ getUidOwner CONSTANT)
+ Q_PROPERTY(QString ownerDisplayName READ getOwnerDisplayName CONSTANT)
+ Q_PROPERTY(ShareType shareType READ getShareType CONSTANT)
+ Q_PROPERTY(ShareePtr shareWith READ getShareWith CONSTANT)
+ Q_PROPERTY(Permissions permissions READ getPermissions WRITE setPermissions NOTIFY permissionsSet)
+ Q_PROPERTY(bool isPasswordSet READ isPasswordSet NOTIFY passwordSet)
public:
/**
@@ -43,14 +52,16 @@ public:
* Need to be in sync with Sharee::Type
*/
enum ShareType {
+ TypePlaceholderLink = -1,
TypeUser = Sharee::User,
TypeGroup = Sharee::Group,
TypeLink = 3,
TypeEmail = Sharee::Email,
TypeRemote = Sharee::Federated,
TypeCircle = Sharee::Circle,
- TypeRoom = Sharee::Room
+ TypeRoom = Sharee::Room,
};
+ Q_ENUM(ShareType);
using Permissions = SharePermissions;
@@ -65,7 +76,7 @@ public:
const ShareType shareType,
bool isPasswordSet = false,
const Permissions permissions = SharePermissionDefault,
- const QSharedPointer<Sharee> shareWith = QSharedPointer<Sharee>(nullptr));
+ const ShareePtr shareWith = ShareePtr(nullptr));
/**
* The account the share is defined on.
@@ -97,13 +108,36 @@ public:
/*
* Get the shareWith
*/
- [[nodiscard]] QSharedPointer<Sharee> getShareWith() const;
+ [[nodiscard]] ShareePtr getShareWith() const;
/*
* Get permissions
*/
[[nodiscard]] Permissions getPermissions() const;
+ [[nodiscard]] bool isPasswordSet() const;
+
+ /*
+ * Is it a share with a user or group (local or remote)
+ */
+ [[nodiscard]] static bool isShareTypeUserGroupEmailRoomOrRemote(const ShareType type);
+
+signals:
+ void permissionsSet();
+ void shareDeleted();
+ void serverError(int code, const QString &message);
+ void passwordSet();
+ void passwordSetError(int statusCode, const QString &message);
+
+public slots:
+ /*
+ * Deletes a share
+ *
+ * On success the shareDeleted signal is emitted
+ * In case of a server error the serverError signal is emitted.
+ */
+ void deleteShare();
+
/*
* Set the permissions of a share
*
@@ -120,28 +154,6 @@ public:
*/
void setPassword(const QString &password);
- [[nodiscard]] bool isPasswordSet() const;
-
- /*
- * Deletes a share
- *
- * On success the shareDeleted signal is emitted
- * In case of a server error the serverError signal is emitted.
- */
- void deleteShare();
-
- /*
- * Is it a share with a user or group (local or remote)
- */
- static bool isShareTypeUserGroupEmailRoomOrRemote(const ShareType type);
-
-signals:
- void permissionsSet();
- void shareDeleted();
- void serverError(int code, const QString &message);
- void passwordSet();
- void passwordSetError(int statusCode, const QString &message);
-
protected:
AccountPtr _account;
QString _id;
@@ -151,7 +163,7 @@ protected:
ShareType _shareType;
bool _isPasswordSet;
Permissions _permissions;
- QSharedPointer<Sharee> _shareWith;
+ ShareePtr _shareWith;
protected slots:
void slotOcsError(int statusCode, const QString &message);
@@ -163,6 +175,8 @@ private slots:
void slotPermissionsSet(const QJsonDocument &, const QVariant &value);
};
+using SharePtr = QSharedPointer<Share>;
+
/**
* A Link share is just like a regular share but then slightly different.
* There are several methods in the API that either work differently for
@@ -171,6 +185,16 @@ private slots:
class LinkShare : public Share
{
Q_OBJECT
+ Q_PROPERTY(QUrl link READ getLink CONSTANT)
+ Q_PROPERTY(QUrl directDownloadLink READ getDirectDownloadLink CONSTANT)
+ Q_PROPERTY(bool publicCanUpload READ getPublicUpload CONSTANT)
+ Q_PROPERTY(bool publicCanReadDirectory READ getShowFileListing CONSTANT)
+ Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameSet)
+ Q_PROPERTY(QString note READ getNote WRITE setNote NOTIFY noteSet)
+ Q_PROPERTY(QString label READ getLabel WRITE setLabel NOTIFY labelSet)
+ Q_PROPERTY(QDate expireDate READ getExpireDate WRITE setExpireDate NOTIFY expireDateSet)
+ Q_PROPERTY(QString token READ getToken CONSTANT)
+
public:
explicit LinkShare(AccountPtr account,
const QString &id,
@@ -222,6 +246,23 @@ public:
[[nodiscard]] QString getLabel() const;
/*
+ * Returns the token of the link share.
+ */
+ [[nodiscard]] QString getToken() const;
+
+ /*
+ * Get the expiration date
+ */
+ [[nodiscard]] QDate getExpireDate() const;
+
+ /*
+ * Create OcsShareJob and connect to signal/slots
+ */
+ template <typename LinkShareSlot>
+ OcsShareJob *createShareJob(const LinkShareSlot slotFunction);
+
+public slots:
+ /*
* Set the name of the link share.
*
* Emits either nameSet() or serverError().
@@ -234,35 +275,18 @@ public:
void setNote(const QString &note);
/*
- * Returns the token of the link share.
- */
- [[nodiscard]] QString getToken() const;
-
- /*
- * Get the expiration date
- */
- [[nodiscard]] QDate getExpireDate() const;
-
- /*
* Set the expiration date
*
* On success the expireDateSet signal is emitted
* In case of a server error the serverError signal is emitted.
*/
void setExpireDate(const QDate &expireDate);
-
+
/*
* Set the label of the share link.
*/
void setLabel(const QString &label);
- /*
- * Create OcsShareJob and connect to signal/slots
- */
- template <typename LinkShareSlot>
- OcsShareJob *createShareJob(const LinkShareSlot slotFunction);
-
-
signals:
void expireDateSet();
void noteSet();
@@ -287,6 +311,8 @@ private:
class UserGroupShare : public Share
{
Q_OBJECT
+ Q_PROPERTY(QString note READ getNote WRITE setNote NOTIFY noteSet)
+ Q_PROPERTY(QDate expireDate READ getExpireDate WRITE setExpireDate NOTIFY expireDateSet)
public:
UserGroupShare(AccountPtr account,
const QString &id,
@@ -296,27 +322,26 @@ public:
const ShareType shareType,
bool isPasswordSet,
const Permissions permissions,
- const QSharedPointer<Sharee> shareWith,
+ const ShareePtr shareWith,
const QDate &expireDate,
const QString &note);
- void setNote(const QString &note);
-
[[nodiscard]] QString getNote() const;
-
- void slotNoteSet(const QJsonDocument &, const QVariant &note);
-
- void setExpireDate(const QDate &date);
-
[[nodiscard]] QDate getExpireDate() const;
- void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
+public slots:
+ void setNote(const QString &note);
+ void setExpireDate(const QDate &date);
signals:
void noteSet();
void noteSetError();
void expireDateSet();
+private slots:
+ void slotNoteSet(const QJsonDocument &json, const QVariant &note);
+ void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
+
private:
QString _note;
QDate _expireDate;
@@ -375,9 +400,9 @@ public:
void fetchShares(const QString &path);
signals:
- void shareCreated(const QSharedPointer<Share> &share);
+ void shareCreated(const SharePtr &share);
void linkShareCreated(const QSharedPointer<LinkShare> &share);
- void sharesFetched(const QList<QSharedPointer<Share>> &shares);
+ void sharesFetched(const QList<SharePtr> &shares);
void serverError(int code, const QString &message);
/** Emitted when creating a link share with password fails.
@@ -396,10 +421,12 @@ private slots:
private:
QSharedPointer<LinkShare> parseLinkShare(const QJsonObject &data);
QSharedPointer<UserGroupShare> parseUserGroupShare(const QJsonObject &data);
- QSharedPointer<Share> parseShare(const QJsonObject &data);
+ SharePtr parseShare(const QJsonObject &data) const;
AccountPtr _account;
};
}
+Q_DECLARE_METATYPE(OCC::SharePtr);
+
#endif // SHAREMANAGER_H
diff --git a/src/gui/shareusergroupwidget.cpp b/src/gui/shareusergroupwidget.cpp
deleted file mode 100644
index 024e1266e..000000000
--- a/src/gui/shareusergroupwidget.cpp
+++ /dev/null
@@ -1,1129 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@owncloud.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) 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 "ocsprofileconnector.h"
-#include "sharee.h"
-#include "tray/usermodel.h"
-#include "ui_shareusergroupwidget.h"
-#include "ui_shareuserline.h"
-#include "shareusergroupwidget.h"
-#include "account.h"
-#include "folderman.h"
-#include "folder.h"
-#include "accountmanager.h"
-#include "theme.h"
-#include "configfile.h"
-#include "capabilities.h"
-#include "guiutility.h"
-#include "thumbnailjob.h"
-#include "sharemanager.h"
-#include "theme.h"
-#include "iconutils.h"
-
-#include "QProgressIndicator.h"
-#include <QBuffer>
-#include <QFileIconProvider>
-#include <QClipboard>
-#include <QFileInfo>
-#include <QAbstractProxyModel>
-#include <QCompleter>
-#include <QBoxLayout>
-#include <QIcon>
-#include <QLayout>
-#include <QPropertyAnimation>
-#include <QMenu>
-#include <QAction>
-#include <QDesktopServices>
-#include <QInputDialog>
-#include <QMessageBox>
-#include <QCryptographicHash>
-#include <QColor>
-#include <QPainter>
-#include <QListWidget>
-#include <QSvgRenderer>
-#include <QPushButton>
-#include <QContextMenuEvent>
-
-#include <cstring>
-
-namespace {
-const char *passwordIsSetPlaceholder = "●●●●●●●●";
-
-}
-
-namespace OCC {
-
-AvatarEventFilter::AvatarEventFilter(QObject *parent)
- : QObject(parent)
-{
-}
-
-
-bool AvatarEventFilter::eventFilter(QObject *obj, QEvent *event)
-{
- if (event->type() == QEvent::ContextMenu) {
- const auto contextMenuEvent = dynamic_cast<QContextMenuEvent *>(event);
- if (!contextMenuEvent) {
- return false;
- }
- emit contextMenu(contextMenuEvent->globalPos());
- return true;
- }
- return QObject::eventFilter(obj, event);
-}
-
-ShareUserGroupWidget::ShareUserGroupWidget(AccountPtr account,
- const QString &sharePath,
- const QString &localPath,
- SharePermissions maxSharingPermissions,
- const QString &privateLinkUrl,
- QWidget *parent)
- : QWidget(parent)
- , _ui(new Ui::ShareUserGroupWidget)
- , _account(account)
- , _sharePath(sharePath)
- , _localPath(localPath)
- , _maxSharingPermissions(maxSharingPermissions)
- , _privateLinkUrl(privateLinkUrl)
- , _disableCompleterActivated(false)
-{
- setAttribute(Qt::WA_DeleteOnClose);
- setObjectName("SharingDialogUG"); // required as group for saveGeometry call
-
- _ui->setupUi(this);
-
- //Is this a file or folder?
- _isFile = QFileInfo(localPath).isFile();
-
- _completer = new QCompleter(this);
- _completerModel = new ShareeModel(_account,
- _isFile ? QLatin1String("file") : QLatin1String("folder"),
- _completer);
- connect(_completerModel, &ShareeModel::shareesReady, this, &ShareUserGroupWidget::slotShareesReady);
- connect(_completerModel, &ShareeModel::displayErrorMessage, this, &ShareUserGroupWidget::displayError);
-
- _completer->setModel(_completerModel);
- _completer->setCaseSensitivity(Qt::CaseInsensitive);
- _completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion);
- _ui->shareeLineEdit->setCompleter(_completer);
-
- _searchGloballyAction.reset(new QAction(_ui->shareeLineEdit));
- _searchGloballyAction->setIcon(Theme::createColorAwareIcon(":/client/theme/magnifying-glass.svg"));
- _searchGloballyAction->setToolTip(tr("Search globally"));
-
- connect(_searchGloballyAction.data(), &QAction::triggered, this, [this]() {
- searchForSharees(ShareeModel::GlobalSearch);
- });
-
- _ui->shareeLineEdit->addAction(_searchGloballyAction.data(), QLineEdit::LeadingPosition);
-
- _manager = new ShareManager(_account, this);
- connect(_manager, &ShareManager::sharesFetched, this, &ShareUserGroupWidget::slotSharesFetched);
- connect(_manager, &ShareManager::shareCreated, this, &ShareUserGroupWidget::slotShareCreated);
- connect(_manager, &ShareManager::serverError, this, &ShareUserGroupWidget::displayError);
- connect(_ui->shareeLineEdit, &QLineEdit::returnPressed, this, &ShareUserGroupWidget::slotLineEditReturn);
- connect(_ui->confirmShare, &QAbstractButton::clicked, this, &ShareUserGroupWidget::slotLineEditReturn);
- //TODO connect(_ui->privateLinkText, &QLabel::linkActivated, this, &ShareUserGroupWidget::slotPrivateLinkShare);
-
- // By making the next two QueuedConnections we can override
- // the strings the completer sets on the line edit.
- connect(_completer, SIGNAL(activated(QModelIndex)), SLOT(slotCompleterActivated(QModelIndex)),
- Qt::QueuedConnection);
- connect(_completer, SIGNAL(highlighted(QModelIndex)), SLOT(slotCompleterHighlighted(QModelIndex)),
- Qt::QueuedConnection);
-
- // Queued connection so this signal is recieved after textChanged
- connect(_ui->shareeLineEdit, &QLineEdit::textEdited,
- this, &ShareUserGroupWidget::slotLineEditTextEdited, Qt::QueuedConnection);
- _ui->shareeLineEdit->installEventFilter(this);
- connect(&_completionTimer, &QTimer::timeout, this, [this]() {
- searchForSharees(ShareeModel::LocalSearch);
- });
- _completionTimer.setSingleShot(true);
- _completionTimer.setInterval(600);
-
- _ui->errorLabel->hide();
-
- _parentScrollArea = parentWidget()->findChild<QScrollArea*>("scrollArea");
- _shareUserGroup = new QVBoxLayout(_parentScrollArea);
- _shareUserGroup->setContentsMargins(0, 0, 0, 0);
- customizeStyle();
-}
-
-QVBoxLayout *ShareUserGroupWidget::shareUserGroupLayout()
-{
- return _shareUserGroup;
-}
-
-ShareUserGroupWidget::~ShareUserGroupWidget()
-{
- delete _ui;
-}
-
-void ShareUserGroupWidget::on_shareeLineEdit_textChanged(const QString &)
-{
- _completionTimer.stop();
- emit togglePublicLinkShare(false);
-}
-
-void ShareUserGroupWidget::slotLineEditTextEdited(const QString &text)
-{
- _disableCompleterActivated = false;
- // First textChanged is called first and we stopped the timer when the text is changed, programatically or not
- // Then we restart the timer here if the user touched a key
- if (!text.isEmpty()) {
- _completionTimer.start();
- emit togglePublicLinkShare(true);
- }
-}
-
-void ShareUserGroupWidget::slotLineEditReturn()
-{
- _disableCompleterActivated = false;
- // did the user type in one of the options?
- const auto text = _ui->shareeLineEdit->text();
- for (int i = 0; i < _completerModel->rowCount(); ++i) {
- const auto sharee = _completerModel->getSharee(i);
- if (sharee->format() == text
- || sharee->displayName() == text
- || sharee->shareWith() == text) {
- slotCompleterActivated(_completerModel->index(i));
- // make sure we do not send the same item twice (because return is called when we press
- // return to activate an item inthe completer)
- _disableCompleterActivated = true;
- return;
- }
- }
-
- // nothing found? try to refresh completion
- _completionTimer.start();
-}
-
-void ShareUserGroupWidget::searchForSharees(ShareeModel::LookupMode lookupMode)
-{
- if (_ui->shareeLineEdit->text().isEmpty()) {
- return;
- }
-
- _ui->shareeLineEdit->setEnabled(false);
- _completionTimer.stop();
- _pi_sharee.startAnimation();
- ShareeModel::ShareeSet blacklist;
-
- // Add the current user to _sharees since we can't share with ourself
- QSharedPointer<Sharee> currentUser(new Sharee(_account->credentials()->user(), "", Sharee::Type::User));
- blacklist << currentUser;
-
- foreach (auto sw, _parentScrollArea->findChildren<ShareUserLine *>()) {
- blacklist << sw->share()->getShareWith();
- }
- _ui->errorLabel->hide();
- _completerModel->fetch(_ui->shareeLineEdit->text(), blacklist, lookupMode);
-}
-
-void ShareUserGroupWidget::getShares()
-{
- _manager->fetchShares(_sharePath);
-}
-
-void ShareUserGroupWidget::slotShareCreated(const QSharedPointer<Share> &share)
-{
- if (share && _account->capabilities().shareEmailPasswordEnabled() && !_account->capabilities().shareEmailPasswordEnforced()) {
- // remember this share Id so we can set it's password Line Edit to focus later
- _lastCreatedShareId = share->getId();
- }
- // fetch all shares including the one we've just created
- getShares();
-}
-
-void ShareUserGroupWidget::slotSharesFetched(const QList<QSharedPointer<Share>> &shares)
-{
- int x = 0;
- QList<QString> linkOwners({});
-
- ShareUserLine *justCreatedShareThatNeedsPassword = nullptr;
-
- while (QLayoutItem *shareUserLine = _shareUserGroup->takeAt(0)) {
- delete shareUserLine->widget();
- delete shareUserLine;
- }
-
- foreach (const auto &share, shares) {
- // We don't handle link shares, only TypeUser or TypeGroup
- if (share->getShareType() == Share::TypeLink) {
- if(!share->getUidOwner().isEmpty() &&
- share->getUidOwner() != share->account()->davUser()){
- linkOwners.append(share->getOwnerDisplayName());
- }
- continue;
- }
-
- // the owner of the file that shared it first
- // leave out if it's the current user
- if(x == 0 && !share->getUidOwner().isEmpty() && !(share->getUidOwner() == _account->credentials()->user())) {
- _ui->mainOwnerLabel->setText(QString("Shared with you by ").append(share->getOwnerDisplayName()));
- }
-
-
- Q_ASSERT(Share::isShareTypeUserGroupEmailRoomOrRemote(share->getShareType()));
- auto userGroupShare = qSharedPointerDynamicCast<UserGroupShare>(share);
- auto *s = new ShareUserLine(_account, userGroupShare, _maxSharingPermissions, _isFile, _parentScrollArea);
- connect(s, &ShareUserLine::visualDeletionDone, this, &ShareUserGroupWidget::getShares);
- s->setBackgroundRole(_shareUserGroup->count() % 2 == 0 ? QPalette::Base : QPalette::AlternateBase);
-
- // Connect styleChanged events to our widget, so it can adapt (Dark-/Light-Mode switching)
- connect(this, &ShareUserGroupWidget::styleChanged, s, &ShareUserLine::slotStyleChanged);
- _shareUserGroup->addWidget(s);
-
- if (!_lastCreatedShareId.isEmpty() && share->getId() == _lastCreatedShareId) {
- _lastCreatedShareId = QString();
- if (_account->capabilities().shareEmailPasswordEnabled() && !_account->capabilities().shareEmailPasswordEnforced()) {
- justCreatedShareThatNeedsPassword = s;
- }
- }
-
- x++;
- }
-
- foreach (const QString &owner, linkOwners) {
- auto ownerLabel = new QLabel(QString(owner + " shared via link"));
- _shareUserGroup->addWidget(ownerLabel);
- ownerLabel->setVisible(true);
- }
-
- _disableCompleterActivated = false;
- activateShareeLineEdit();
-
- if (justCreatedShareThatNeedsPassword) {
- // always set focus to a password Line Edit when the new email share is created on a server with optional passwords enabled for email shares
- justCreatedShareThatNeedsPassword->focusPasswordLineEdit();
- }
-}
-
-void ShareUserGroupWidget::slotPrivateLinkShare()
-{
- auto menu = new QMenu(this);
- menu->setAttribute(Qt::WA_DeleteOnClose);
-
- // this icon is not handled by slotStyleChanged() -> customizeStyle but we can live with that
- menu->addAction(Theme::createColorAwareIcon(":/client/theme/copy.svg"),
- tr("Copy link"),
- this, SLOT(slotPrivateLinkCopy()));
-
- menu->exec(QCursor::pos());
-}
-
-void ShareUserGroupWidget::slotShareesReady()
-{
- activateShareeLineEdit();
-
- _pi_sharee.stopAnimation();
- if (_completerModel->rowCount() == 0) {
- displayError(0, tr("No results for \"%1\"").arg(_completerModel->currentSearch()));
- }
-
- // if no rows are present in the model - complete() will hide the completer
- _completer->complete();
-}
-
-void ShareUserGroupWidget::slotCompleterActivated(const QModelIndex &index)
-{
- if (_disableCompleterActivated)
- return;
- // The index is an index from the QCompletion model which is itelf a proxy
- // model proxying the _completerModel
- auto sharee = qvariant_cast<QSharedPointer<Sharee>>(index.data(Qt::UserRole));
- if (sharee.isNull()) {
- return;
- }
-
- /*
- * Don't send the reshare permissions for federated shares for servers <9.1
- * https://github.com/owncloud/core/issues/22122#issuecomment-185637344
- * https://github.com/owncloud/client/issues/4996
- */
- _lastCreatedShareId = QString();
-
- QString password;
- if (sharee->type() == Sharee::Email && _account->capabilities().shareEmailPasswordEnforced()) {
- _ui->shareeLineEdit->clear();
- // always show a dialog for password-enforced email shares
- bool ok = false;
-
- do {
- password = QInputDialog::getText(
- this,
- tr("Password for share required"),
- tr("Please enter a password for your email share:"),
- QLineEdit::Password,
- QString(),
- &ok);
- } while (password.isEmpty() && ok);
-
- if (!ok) {
- return;
- }
- }
-
- _manager->createShare(_sharePath, Share::ShareType(sharee->type()),
- sharee->shareWith(), _maxSharingPermissions, password);
-
- _ui->shareeLineEdit->setEnabled(false);
- _ui->shareeLineEdit->clear();
-}
-
-void ShareUserGroupWidget::slotCompleterHighlighted(const QModelIndex &index)
-{
- // By default the completer would set the text to EditRole,
- // override that here.
- _ui->shareeLineEdit->setText(index.data(Qt::DisplayRole).toString());
-}
-
-void ShareUserGroupWidget::displayError(int code, const QString &message)
-{
- _pi_sharee.stopAnimation();
-
- // Also remove the spinner in the widget list, if any
- foreach (auto pi, _parentScrollArea->findChildren<QProgressIndicator *>()) {
- delete pi;
- }
-
- qCWarning(lcSharing) << "Sharing error from server" << code << message;
- _ui->errorLabel->setText(message);
- _ui->errorLabel->show();
- activateShareeLineEdit();
-}
-
-void ShareUserGroupWidget::slotPrivateLinkOpenBrowser()
-{
- Utility::openBrowser(_privateLinkUrl, this);
-}
-
-void ShareUserGroupWidget::slotPrivateLinkCopy()
-{
- QApplication::clipboard()->setText(_privateLinkUrl);
-}
-
-void ShareUserGroupWidget::slotPrivateLinkEmail()
-{
- Utility::openEmailComposer(
- tr("I shared something with you"),
- _privateLinkUrl,
- this);
-}
-
-void ShareUserGroupWidget::slotStyleChanged()
-{
- customizeStyle();
-
- // Notify the other widgets (ShareUserLine in this case, Dark-/Light-Mode switching)
- emit styleChanged();
-}
-
-void ShareUserGroupWidget::customizeStyle()
-{
- _searchGloballyAction->setIcon(Theme::createColorAwareIcon(":/client/theme/magnifying-glass.svg"));
-
- _ui->confirmShare->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
-
- _pi_sharee.setColor(QGuiApplication::palette().color(QPalette::Text));
-
- foreach (auto pi, _parentScrollArea->findChildren<QProgressIndicator *>()) {
- pi->setColor(QGuiApplication::palette().color(QPalette::Text));;
- }
-}
-
-void ShareUserGroupWidget::activateShareeLineEdit()
-{
- _ui->shareeLineEdit->setEnabled(true);
- _ui->shareeLineEdit->setFocus();
-}
-
-ShareUserLine::ShareUserLine(AccountPtr account, QSharedPointer<UserGroupShare> share,
- SharePermissions maxSharingPermissions, bool isFile, QWidget *parent)
- : QWidget(parent)
- , _ui(new Ui::ShareUserLine)
- , _account(account)
- , _share(share)
- , _isFile(isFile)
- , _profilePageMenu(account, share->getShareWith()->shareWith())
-{
- Q_ASSERT(_share);
- _ui->setupUi(this);
-
- _ui->sharedWith->setElideMode(Qt::ElideRight);
- _ui->sharedWith->setText(share->getShareWith()->format());
-
- // adds permissions
- // can edit permission
- bool enabled = (maxSharingPermissions & SharePermissionUpdate);
- if(!_isFile) enabled = enabled && (maxSharingPermissions & SharePermissionCreate &&
- maxSharingPermissions & SharePermissionDelete);
- _ui->permissionsEdit->setEnabled(enabled);
- connect(_ui->permissionsEdit, &QAbstractButton::clicked, this, &ShareUserLine::slotEditPermissionsChanged);
- connect(_ui->noteConfirmButton, &QAbstractButton::clicked, this, &ShareUserLine::onNoteConfirmButtonClicked);
- connect(_ui->calendar, &QDateTimeEdit::dateChanged, this, &ShareUserLine::setExpireDate);
-
- connect(_share.data(), &UserGroupShare::noteSet, this, &ShareUserLine::disableProgessIndicatorAnimation);
- connect(_share.data(), &UserGroupShare::noteSetError, this, &ShareUserLine::disableProgessIndicatorAnimation);
- connect(_share.data(), &UserGroupShare::expireDateSet, this, &ShareUserLine::disableProgessIndicatorAnimation);
-
- connect(_ui->confirmPassword, &QToolButton::clicked, this, &ShareUserLine::slotConfirmPasswordClicked);
- connect(_ui->lineEdit_password, &QLineEdit::returnPressed, this, &ShareUserLine::slotLineEditPasswordReturnPressed);
-
- // create menu with checkable permissions
- auto *menu = new QMenu(this);
- _permissionReshare= new QAction(tr("Can reshare"), this);
- _permissionReshare->setCheckable(true);
- _permissionReshare->setEnabled(maxSharingPermissions & SharePermissionShare);
- menu->addAction(_permissionReshare);
- connect(_permissionReshare, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
-
- showNoteOptions(false);
-
- const bool isNoteSupported = _share->getShareType() != Share::ShareType::TypeEmail && _share->getShareType() != Share::ShareType::TypeRoom;
-
- if (isNoteSupported) {
- _noteLinkAction = new QAction(tr("Note to recipient"));
- _noteLinkAction->setCheckable(true);
- menu->addAction(_noteLinkAction);
- connect(_noteLinkAction, &QAction::triggered, this, &ShareUserLine::toggleNoteOptions);
- if (!_share->getNote().isEmpty()) {
- _noteLinkAction->setChecked(true);
- showNoteOptions(true);
- }
- }
-
- showExpireDateOptions(false);
-
- const bool isExpirationDateSupported = _share->getShareType() != Share::ShareType::TypeEmail;
-
- if (isExpirationDateSupported) {
- // email shares do not support expiration dates
- _expirationDateLinkAction = new QAction(tr("Set expiration date"));
- _expirationDateLinkAction->setCheckable(true);
- menu->addAction(_expirationDateLinkAction);
- connect(_expirationDateLinkAction, &QAction::triggered, this, &ShareUserLine::toggleExpireDateOptions);
- const auto expireDate = _share->getExpireDate().isValid() ? share.data()->getExpireDate() : QDate();
- if (!expireDate.isNull()) {
- _expirationDateLinkAction->setChecked(true);
- showExpireDateOptions(true, expireDate);
- }
- }
-
- menu->addSeparator();
-
- // Adds action to delete share widget
- QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),QIcon(QLatin1String(":/client/theme/delete.svg")));
- _deleteShareButton= new QAction(deleteicon,tr("Unshare"), this);
-
- menu->addAction(_deleteShareButton);
- connect(_deleteShareButton, &QAction::triggered, this, &ShareUserLine::on_deleteShareButton_clicked);
-
- /*
- * Files can't have create or delete permissions
- */
- if (!_isFile) {
- _permissionCreate = new QAction(tr("Can create"), this);
- _permissionCreate->setCheckable(true);
- _permissionCreate->setEnabled(maxSharingPermissions & SharePermissionCreate);
- menu->addAction(_permissionCreate);
- connect(_permissionCreate, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
-
- _permissionChange = new QAction(tr("Can change"), this);
- _permissionChange->setCheckable(true);
- _permissionChange->setEnabled(maxSharingPermissions & SharePermissionUpdate);
- menu->addAction(_permissionChange);
- connect(_permissionChange, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
-
- _permissionDelete = new QAction(tr("Can delete"), this);
- _permissionDelete->setCheckable(true);
- _permissionDelete->setEnabled(maxSharingPermissions & SharePermissionDelete);
- menu->addAction(_permissionDelete);
- connect(_permissionDelete, &QAction::triggered, this, &ShareUserLine::slotPermissionsChanged);
- }
-
- // Adds action to display password widget (check box)
- if (_share->getShareType() == Share::TypeEmail && (_share->isPasswordSet() || _account->capabilities().shareEmailPasswordEnabled())) {
- _passwordProtectLinkAction = new QAction(tr("Password protect"), this);
- _passwordProtectLinkAction->setCheckable(true);
- _passwordProtectLinkAction->setChecked(_share->isPasswordSet());
- // checkbox can be checked/unchedkec if the password is not yet set or if it's not enforced
- _passwordProtectLinkAction->setEnabled(!_share->isPasswordSet() || !_account->capabilities().shareEmailPasswordEnforced());
-
- menu->addAction(_passwordProtectLinkAction);
- connect(_passwordProtectLinkAction, &QAction::triggered, this, &ShareUserLine::slotPasswordCheckboxChanged);
-
- refreshPasswordLineEditPlaceholder();
-
- connect(_share.data(), &Share::passwordSet, this, &ShareUserLine::slotPasswordSet);
- connect(_share.data(), &Share::passwordSetError, this, &ShareUserLine::slotPasswordSetError);
- }
-
- refreshPasswordOptions();
-
- _ui->errorLabel->hide();
-
- _ui->permissionToolButton->setMenu(menu);
- _ui->permissionToolButton->setPopupMode(QToolButton::InstantPopup);
-
- _ui->passwordProgressIndicator->setVisible(false);
-
- // Set the permissions checkboxes
- displayPermissions();
-
- /*
- * We don't show permission share for federated shares with server <9.1
- * https://github.com/owncloud/core/issues/22122#issuecomment-185637344
- * https://github.com/owncloud/client/issues/4996
- */
- if (share->getShareType() == Share::TypeRemote
- && share->account()->serverVersionInt() < Account::makeServerVersion(9, 1, 0)) {
- _permissionReshare->setVisible(false);
- _ui->permissionToolButton->setVisible(false);
- }
-
- connect(share.data(), &Share::permissionsSet, this, &ShareUserLine::slotPermissionsSet);
- connect(share.data(), &Share::shareDeleted, this, &ShareUserLine::slotShareDeleted);
-
- if (!share->account()->capabilities().shareResharing()) {
- _permissionReshare->setVisible(false);
- }
-
- const auto avatarEventFilter = new AvatarEventFilter(_ui->avatar);
- connect(avatarEventFilter, &AvatarEventFilter::contextMenu, this, &ShareUserLine::onAvatarContextMenu);
- _ui->avatar->installEventFilter(avatarEventFilter);
-
- loadAvatar();
-
- customizeStyle();
-}
-
-void ShareUserLine::onAvatarContextMenu(const QPoint &globalPosition)
-{
- if (_share->getShareType() == Share::TypeUser) {
- _profilePageMenu.exec(globalPosition);
- }
-}
-
-void ShareUserLine::loadAvatar()
-{
- const int avatarSize = 36;
-
- // Set size of the placeholder
- _ui->avatar->setMinimumHeight(avatarSize);
- _ui->avatar->setMinimumWidth(avatarSize);
- _ui->avatar->setMaximumHeight(avatarSize);
- _ui->avatar->setMaximumWidth(avatarSize);
- _ui->avatar->setAlignment(Qt::AlignCenter);
-
- setDefaultAvatar(avatarSize);
-
- /* Start the network job to fetch the avatar data.
- *
- * Currently only regular users can have avatars.
- */
- if (_share->getShareWith()->type() == Sharee::User) {
- auto *job = new AvatarJob(_share->account(), _share->getShareWith()->shareWith(), avatarSize, this);
- connect(job, &AvatarJob::avatarPixmap, this, &ShareUserLine::slotAvatarLoaded);
- job->start();
- }
-}
-
-void ShareUserLine::setDefaultAvatar(int avatarSize)
-{
- /* Create the fallback avatar.
- *
- * This will be shown until the avatar image data arrives.
- */
-
- // See core/js/placeholder.js for details on colors and styling
- const auto backgroundColor = backgroundColorForShareeType(_share->getShareWith()->type());
- const QString style = QString(R"(* {
- color: #fff;
- background-color: %1;
- border-radius: %2px;
- text-align: center;
- line-height: %2px;
- font-size: %2px;
- })").arg(backgroundColor.name(), QString::number(avatarSize / 2));
- _ui->avatar->setStyleSheet(style);
-
- const auto pixmap = pixmapForShareeType(_share->getShareWith()->type(), backgroundColor);
-
- if (!pixmap.isNull()) {
- _ui->avatar->setPixmap(pixmap);
- } else {
- qCDebug(lcSharing) << "pixmap is null for share type: " << _share->getShareWith()->type();
-
- // The avatar label is the first character of the user name.
- const auto text = _share->getShareWith()->displayName();
- _ui->avatar->setText(text.at(0).toUpper());
- }
-}
-
-void ShareUserLine::slotAvatarLoaded(QImage avatar)
-{
- if (avatar.isNull())
- return;
-
- avatar = AvatarJob::makeCircularAvatar(avatar);
- _ui->avatar->setPixmap(QPixmap::fromImage(avatar));
-
- // Remove the stylesheet for the fallback avatar
- _ui->avatar->setStyleSheet("");
-}
-
-void ShareUserLine::on_deleteShareButton_clicked()
-{
- setEnabled(false);
- _share->deleteShare();
-}
-
-ShareUserLine::~ShareUserLine()
-{
- delete _ui;
-}
-
-void ShareUserLine::slotEditPermissionsChanged()
-{
- setEnabled(false);
-
- // Can never manually be set to "partial".
- // This works because the state cycle for clicking is
- // unchecked -> partial -> checked -> unchecked.
- if (_ui->permissionsEdit->checkState() == Qt::PartiallyChecked) {
- _ui->permissionsEdit->setCheckState(Qt::Checked);
- }
-
- Share::Permissions permissions = SharePermissionRead;
-
- // folders edit = CREATE, READ, UPDATE, DELETE
- // files edit = READ + UPDATE
- if (_ui->permissionsEdit->checkState() == Qt::Checked) {
-
- /*
- * Files can't have create or delete permisisons
- */
- if (!_isFile) {
- if (_permissionChange->isEnabled())
- permissions |= SharePermissionUpdate;
- if (_permissionCreate->isEnabled())
- permissions |= SharePermissionCreate;
- if (_permissionDelete->isEnabled())
- permissions |= SharePermissionDelete;
- } else {
- permissions |= SharePermissionUpdate;
- }
- }
-
- if(_isFile && _permissionReshare->isEnabled() && _permissionReshare->isChecked())
- permissions |= SharePermissionShare;
-
- _share->setPermissions(permissions);
-}
-
-void ShareUserLine::slotPermissionsChanged()
-{
- setEnabled(false);
-
- Share::Permissions permissions = SharePermissionRead;
-
- if (_permissionReshare->isChecked())
- permissions |= SharePermissionShare;
-
- if (!_isFile) {
- if (_permissionChange->isChecked())
- permissions |= SharePermissionUpdate;
- if (_permissionCreate->isChecked())
- permissions |= SharePermissionCreate;
- if (_permissionDelete->isChecked())
- permissions |= SharePermissionDelete;
- } else {
- if (_ui->permissionsEdit->isChecked())
- permissions |= SharePermissionUpdate;
- }
-
- _share->setPermissions(permissions);
-}
-
-void ShareUserLine::slotPasswordCheckboxChanged()
-{
- if (!_passwordProtectLinkAction->isChecked()) {
- _ui->errorLabel->hide();
- _ui->errorLabel->clear();
-
- if (!_share->isPasswordSet()) {
- _ui->lineEdit_password->clear();
- refreshPasswordOptions();
- } else {
- // do not call refreshPasswordOptions here, as it will be called after the network request is complete
- togglePasswordSetProgressAnimation(true);
- _share->setPassword(QString());
- }
- } else {
- refreshPasswordOptions();
-
- if (_ui->lineEdit_password->isVisible() && _ui->lineEdit_password->isEnabled()) {
- focusPasswordLineEdit();
- }
- }
-}
-
-void ShareUserLine::slotDeleteAnimationFinished()
-{
- emit resizeRequested();
- emit visualDeletionDone();
- deleteLater();
-
- // There is a painting bug where a small line of this widget isn't
- // properly cleared. This explicit repaint() call makes sure any trace of
- // the share widget is removed once it's destroyed. #4189
- connect(this, SIGNAL(destroyed(QObject *)), parentWidget(), SLOT(repaint()));
-}
-
-void ShareUserLine::refreshPasswordOptions()
-{
- const bool isPasswordEnabled = _share->getShareType() == Share::TypeEmail && _passwordProtectLinkAction->isChecked();
-
- _ui->passwordLabel->setVisible(isPasswordEnabled);
- _ui->lineEdit_password->setEnabled(isPasswordEnabled);
- _ui->lineEdit_password->setVisible(isPasswordEnabled);
- _ui->confirmPassword->setVisible(isPasswordEnabled);
-
- emit resizeRequested();
-}
-
-void ShareUserLine::refreshPasswordLineEditPlaceholder()
-{
- if (_share->isPasswordSet()) {
- _ui->lineEdit_password->setPlaceholderText(QString::fromUtf8(passwordIsSetPlaceholder));
- } else {
- _ui->lineEdit_password->setPlaceholderText("");
- }
-}
-
-void ShareUserLine::slotPasswordSet()
-{
- togglePasswordSetProgressAnimation(false);
- _ui->lineEdit_password->setEnabled(true);
- _ui->confirmPassword->setEnabled(true);
-
- _ui->lineEdit_password->setText("");
-
- _passwordProtectLinkAction->setEnabled(!_share->isPasswordSet() || !_account->capabilities().shareEmailPasswordEnforced());
-
- refreshPasswordLineEditPlaceholder();
-
- refreshPasswordOptions();
-}
-
-void ShareUserLine::slotPasswordSetError(int statusCode, const QString &message)
-{
- qCWarning(lcSharing) << "Error from server" << statusCode << message;
-
- togglePasswordSetProgressAnimation(false);
-
- _ui->lineEdit_password->setEnabled(true);
- _ui->confirmPassword->setEnabled(true);
-
- refreshPasswordLineEditPlaceholder();
-
- refreshPasswordOptions();
-
- focusPasswordLineEdit();
-
- _ui->errorLabel->show();
- _ui->errorLabel->setText(message);
-
- emit resizeRequested();
-}
-
-void ShareUserLine::slotShareDeleted()
-{
- auto *animation = new QPropertyAnimation(this, "maximumHeight", this);
-
- animation->setDuration(500);
- animation->setStartValue(height());
- animation->setEndValue(0);
-
- connect(animation, &QAbstractAnimation::finished, this, &ShareUserLine::slotDeleteAnimationFinished);
- connect(animation, &QVariantAnimation::valueChanged, this, &ShareUserLine::resizeRequested);
-
- animation->start();
-}
-
-void ShareUserLine::slotPermissionsSet()
-{
- displayPermissions();
- setEnabled(true);
-}
-
-QSharedPointer<Share> ShareUserLine::share() const
-{
- return _share;
-}
-
-void ShareUserLine::displayPermissions()
-{
- auto perm = _share->getPermissions();
-
-// folders edit = CREATE, READ, UPDATE, DELETE
-// files edit = READ + UPDATE
- if (perm & SharePermissionUpdate && (_isFile ||
- (perm & SharePermissionCreate && perm & SharePermissionDelete))) {
- _ui->permissionsEdit->setCheckState(Qt::Checked);
- } else if (!_isFile && perm & (SharePermissionUpdate | SharePermissionCreate | SharePermissionDelete)) {
- _ui->permissionsEdit->setCheckState(Qt::PartiallyChecked);
- } else if(perm & SharePermissionRead) {
- _ui->permissionsEdit->setCheckState(Qt::Unchecked);
- }
-
-// edit is independent of reshare
- if (perm & SharePermissionShare)
- _permissionReshare->setChecked(true);
-
- if(!_isFile){
- _permissionCreate->setChecked(perm & SharePermissionCreate);
- _permissionChange->setChecked(perm & SharePermissionUpdate);
- _permissionDelete->setChecked(perm & SharePermissionDelete);
- }
-}
-
-void ShareUserLine::slotStyleChanged()
-{
- customizeStyle();
-}
-
-void ShareUserLine::focusPasswordLineEdit()
-{
- _ui->lineEdit_password->setFocus();
-}
-
-void ShareUserLine::customizeStyle()
-{
- _ui->permissionToolButton->setIcon(Theme::createColorAwareIcon(":/client/theme/more.svg"));
-
- QIcon deleteicon = QIcon::fromTheme(QLatin1String("user-trash"),Theme::createColorAwareIcon(QLatin1String(":/client/theme/delete.svg")));
- _deleteShareButton->setIcon(deleteicon);
-
- _ui->noteConfirmButton->setIcon(Theme::createColorAwareIcon(":/client/theme/confirm.svg"));
- _ui->progressIndicator->setColor(QGuiApplication::palette().color(QPalette::WindowText));
-
- // make sure to force BackgroundRole to QPalette::WindowText for a lable, because it's parent always has a different role set that applies to children unless customized
- _ui->errorLabel->setBackgroundRole(QPalette::WindowText);
-}
-
-QPixmap ShareUserLine::pixmapForShareeType(Sharee::Type type, const QColor &backgroundColor) const
-{
- switch (type) {
- case Sharee::Room:
- return Ui::IconUtils::pixmapForBackground(QStringLiteral("talk-app.svg"), backgroundColor);
- case Sharee::Email:
- return Ui::IconUtils::pixmapForBackground(QStringLiteral("email.svg"), backgroundColor);
- case Sharee::Group:
- case Sharee::Federated:
- case Sharee::Circle:
- case Sharee::User:
- break;
- }
-
- return {};
-}
-
-QColor ShareUserLine::backgroundColorForShareeType(Sharee::Type type) const
-{
- switch (type) {
- case Sharee::Room:
- return Theme::instance()->wizardHeaderBackgroundColor();
- case Sharee::Email:
- return Theme::instance()->wizardHeaderTitleColor();
- case Sharee::Group:
- case Sharee::Federated:
- case Sharee::Circle:
- case Sharee::User:
- break;
- }
-
- const auto calculateBackgroundBasedOnText = [this]() {
- const auto hash = QCryptographicHash::hash(_ui->sharedWith->text().toUtf8(), QCryptographicHash::Md5);
- Q_ASSERT(hash.size() > 0);
- if (hash.size() == 0) {
- qCWarning(lcSharing) << "Failed to calculate hash color for share:" << _share->path();
- return QColor{};
- }
- const double hue = static_cast<quint8>(hash[0]) / 255.;
- return QColor::fromHslF(hue, 0.7, 0.68);
- };
-
- return calculateBackgroundBasedOnText();
-}
-
-void ShareUserLine::showNoteOptions(bool show)
-{
- _ui->noteLabel->setVisible(show);
- _ui->noteTextEdit->setVisible(show);
- _ui->noteConfirmButton->setVisible(show);
-
- if (show) {
- const auto note = _share->getNote();
- _ui->noteTextEdit->setText(note);
- _ui->noteTextEdit->setFocus();
- }
-
- emit resizeRequested();
-}
-
-
-void ShareUserLine::toggleNoteOptions(bool enable)
-{
- showNoteOptions(enable);
-
- if (!enable) {
- // Delete note
- _share->setNote(QString());
- }
-}
-
-void ShareUserLine::onNoteConfirmButtonClicked()
-{
- setNote(_ui->noteTextEdit->toPlainText());
-}
-
-void ShareUserLine::setNote(const QString &note)
-{
- enableProgessIndicatorAnimation(true);
- _share->setNote(note);
-}
-
-void ShareUserLine::toggleExpireDateOptions(bool enable)
-{
- showExpireDateOptions(enable);
-
- if (!enable) {
- _share->setExpireDate(QDate());
- }
-}
-
-void ShareUserLine::showExpireDateOptions(bool show, const QDate &initialDate)
-{
- _ui->expirationLabel->setVisible(show);
- _ui->calendar->setVisible(show);
-
- if (show) {
- _ui->calendar->setMinimumDate(QDate::currentDate().addDays(1));
- _ui->calendar->setDate(initialDate.isValid() ? initialDate : _ui->calendar->minimumDate());
- _ui->calendar->setFocus();
-
- if (enforceExpirationDateForShare(_share->getShareType())) {
- _ui->calendar->setMaximumDate(maxExpirationDateForShare(_share->getShareType(), _ui->calendar->maximumDate()));
- _expirationDateLinkAction->setChecked(true);
- _expirationDateLinkAction->setEnabled(false);
- }
- }
-
- emit resizeRequested();
-}
-
-void ShareUserLine::setExpireDate()
-{
- enableProgessIndicatorAnimation(true);
- _share->setExpireDate(_ui->calendar->date());
-}
-
-void ShareUserLine::enableProgessIndicatorAnimation(bool enable)
-{
- if (enable) {
- if (!_ui->progressIndicator->isAnimated()) {
- _ui->progressIndicator->startAnimation();
- }
- } else {
- _ui->progressIndicator->stopAnimation();
- }
-}
-
-void ShareUserLine::togglePasswordSetProgressAnimation(bool show)
-{
- // button and progress indicator are interchanged depending on if the network request is in progress or not
- _ui->confirmPassword->setVisible(!show && _passwordProtectLinkAction->isChecked());
- _ui->passwordProgressIndicator->setVisible(show);
- if (show) {
- if (!_ui->passwordProgressIndicator->isAnimated()) {
- _ui->passwordProgressIndicator->startAnimation();
- }
- } else {
- _ui->passwordProgressIndicator->stopAnimation();
- }
-}
-
-void ShareUserLine::disableProgessIndicatorAnimation()
-{
- enableProgessIndicatorAnimation(false);
-}
-
-QDate ShareUserLine::maxExpirationDateForShare(const Share::ShareType type, const QDate &fallbackDate) const
-{
- auto daysToExpire = 0;
- if (type == Share::ShareType::TypeRemote) {
- daysToExpire = _account->capabilities().shareRemoteExpireDateDays();
- } else if (type == Share::ShareType::TypeEmail) {
- daysToExpire = _account->capabilities().sharePublicLinkExpireDateDays();
- } else {
- daysToExpire = _account->capabilities().shareInternalExpireDateDays();
- }
-
- if (daysToExpire > 0) {
- return QDate::currentDate().addDays(daysToExpire);
- }
-
- return fallbackDate;
-}
-
-bool ShareUserLine::enforceExpirationDateForShare(const Share::ShareType type) const
-{
- if (type == Share::ShareType::TypeRemote) {
- return _account->capabilities().shareRemoteEnforceExpireDate();
- } else if (type == Share::ShareType::TypeEmail) {
- return _account->capabilities().sharePublicLinkEnforceExpireDate();
- }
-
- return _account->capabilities().shareInternalEnforceExpireDate();
-}
-
-void ShareUserLine::setPasswordConfirmed()
-{
- if (_ui->lineEdit_password->text().isEmpty()) {
- return;
- }
-
- _ui->lineEdit_password->setEnabled(false);
- _ui->confirmPassword->setEnabled(false);
-
- _ui->errorLabel->hide();
- _ui->errorLabel->clear();
-
- togglePasswordSetProgressAnimation(true);
- _share->setPassword(_ui->lineEdit_password->text());
-}
-
-void ShareUserLine::slotLineEditPasswordReturnPressed()
-{
- setPasswordConfirmed();
-}
-
-void ShareUserLine::slotConfirmPasswordClicked()
-{
- setPasswordConfirmed();
-}
-}
diff --git a/src/gui/shareusergroupwidget.h b/src/gui/shareusergroupwidget.h
deleted file mode 100644
index 96132005e..000000000
--- a/src/gui/shareusergroupwidget.h
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * Copyright (C) by Roeland Jago Douma <roeland@owncloud.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) 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 SHAREUSERGROUPWIDGET_H
-#define SHAREUSERGROUPWIDGET_H
-
-#include "accountfwd.h"
-#include "sharemanager.h"
-#include "sharepermissions.h"
-#include "sharee.h"
-#include "profilepagewidget.h"
-#include "QProgressIndicator.h"
-#include <QDialog>
-#include <QWidget>
-#include <QSharedPointer>
-#include <QList>
-#include <QVector>
-#include <QTimer>
-#include <qpushbutton.h>
-#include <qscrollarea.h>
-
-class QAction;
-class QCompleter;
-class QModelIndex;
-
-namespace OCC {
-
-namespace Ui {
- class ShareUserGroupWidget;
- class ShareUserLine;
-}
-
-class AbstractCredentials;
-class SyncResult;
-class Share;
-class ShareManager;
-
-class AvatarEventFilter : public QObject
-{
- Q_OBJECT
-
-public:
- explicit AvatarEventFilter(QObject *parent = nullptr);
-
-signals:
- void clicked();
- void contextMenu(const QPoint &globalPosition);
-
-protected:
- bool eventFilter(QObject *obj, QEvent *event) override;
-};
-
-/**
- * @brief The ShareDialog (user/group) class
- * @ingroup gui
- */
-class ShareUserGroupWidget : public QWidget
-{
- Q_OBJECT
-
-public:
- explicit ShareUserGroupWidget(AccountPtr account,
- const QString &sharePath,
- const QString &localPath,
- SharePermissions maxSharingPermissions,
- const QString &privateLinkUrl,
- QWidget *parent = nullptr);
- ~ShareUserGroupWidget() override;
-
- QVBoxLayout *shareUserGroupLayout();
-
-signals:
- void togglePublicLinkShare(bool);
- void styleChanged();
-
-public slots:
- void getShares();
- void slotShareCreated(const QSharedPointer<Share> &share);
- void slotStyleChanged();
-
-private slots:
- void slotSharesFetched(const QList<QSharedPointer<Share>> &shares);
-
- void on_shareeLineEdit_textChanged(const QString &text);
- void searchForSharees(ShareeModel::LookupMode lookupMode);
- void slotLineEditTextEdited(const QString &text);
-
- void slotLineEditReturn();
- void slotCompleterActivated(const QModelIndex &index);
- void slotCompleterHighlighted(const QModelIndex &index);
- void slotShareesReady();
- void slotPrivateLinkShare();
- void displayError(int code, const QString &message);
-
- void slotPrivateLinkOpenBrowser();
- void slotPrivateLinkCopy();
- void slotPrivateLinkEmail();
-
-private:
- void customizeStyle();
-
- void activateShareeLineEdit();
-
- Ui::ShareUserGroupWidget *_ui;
- QScopedPointer<QAction> _searchGloballyAction;
- QScrollArea *_parentScrollArea;
- QVBoxLayout *_shareUserGroup;
- AccountPtr _account;
- QString _sharePath;
- QString _localPath;
- SharePermissions _maxSharingPermissions;
- QString _privateLinkUrl;
-
- QCompleter *_completer;
- ShareeModel *_completerModel;
- QTimer _completionTimer;
-
- bool _isFile;
- bool _disableCompleterActivated; // in order to avoid that we share the contents twice
- ShareManager *_manager;
-
- QProgressIndicator _pi_sharee;
-
- QString _lastCreatedShareId;
-};
-
-/**
- * The widget displayed for each user/group share
- */
-class ShareUserLine : public QWidget
-{
- Q_OBJECT
-
-public:
- explicit ShareUserLine(AccountPtr account,
- QSharedPointer<UserGroupShare> Share,
- SharePermissions maxSharingPermissions,
- bool isFile,
- QWidget *parent = nullptr);
- ~ShareUserLine() override;
-
- [[nodiscard]] QSharedPointer<Share> share() const;
-
-signals:
- void visualDeletionDone();
- void resizeRequested();
-
-public slots:
- void slotStyleChanged();
-
- void focusPasswordLineEdit();
-
-private slots:
- void on_deleteShareButton_clicked();
- void slotPermissionsChanged();
- void slotEditPermissionsChanged();
- void slotPasswordCheckboxChanged();
- void slotDeleteAnimationFinished();
-
- void refreshPasswordOptions();
-
- void refreshPasswordLineEditPlaceholder();
-
- void slotPasswordSet();
- void slotPasswordSetError(int statusCode, const QString &message);
-
- void slotShareDeleted();
- void slotPermissionsSet();
-
- void slotAvatarLoaded(QImage avatar);
-
- void setPasswordConfirmed();
-
- void slotLineEditPasswordReturnPressed();
-
- void slotConfirmPasswordClicked();
-
- void onAvatarContextMenu(const QPoint &globalPosition);
-
-private:
- void displayPermissions();
- void loadAvatar();
- void setDefaultAvatar(int avatarSize);
- void customizeStyle();
-
- [[nodiscard]] QPixmap pixmapForShareeType(Sharee::Type type, const QColor &backgroundColor = QColor()) const;
- [[nodiscard]] QColor backgroundColorForShareeType(Sharee::Type type) const;
-
- void showNoteOptions(bool show);
- void toggleNoteOptions(bool enable);
- void onNoteConfirmButtonClicked();
- void setNote(const QString &note);
-
- void toggleExpireDateOptions(bool enable);
- void showExpireDateOptions(bool show, const QDate &initialDate = QDate());
- void setExpireDate();
-
- void togglePasswordSetProgressAnimation(bool show);
-
- void enableProgessIndicatorAnimation(bool enable);
- void disableProgessIndicatorAnimation();
-
- [[nodiscard]] QDate maxExpirationDateForShare(const Share::ShareType type, const QDate &fallbackDate) const;
- [[nodiscard]] bool enforceExpirationDateForShare(const Share::ShareType type) const;
-
- Ui::ShareUserLine *_ui;
- AccountPtr _account;
- QSharedPointer<UserGroupShare> _share;
- bool _isFile;
-
- ProfilePageMenu _profilePageMenu;
-
- // _permissionEdit is a checkbox
- QAction *_permissionReshare;
- QAction *_deleteShareButton;
- QAction *_permissionCreate;
- QAction *_permissionChange;
- QAction *_permissionDelete;
- QAction *_noteLinkAction;
- QAction *_expirationDateLinkAction;
- QAction *_passwordProtectLinkAction;
-};
-}
-
-#endif // SHAREUSERGROUPWIDGET_H
diff --git a/src/gui/shareusergroupwidget.ui b/src/gui/shareusergroupwidget.ui
deleted file mode 100644
index 38c45a23d..000000000
--- a/src/gui/shareusergroupwidget.ui
+++ /dev/null
@@ -1,154 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<ui version="4.0">
- <class>OCC::ShareUserGroupWidget</class>
- <widget class="QWidget" name="OCC::ShareUserGroupWidget">
- <property name="geometry">
- <rect>
- <x>0</x>
- <y>0</y>
- <width>350</width>
- <height>106</height>
- </rect>
- </property>
- <property name="sizePolicy">
- <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <layout class="QVBoxLayout" name="verticalLayout">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="leftMargin">
- <number>6</number>
- </property>
- <property name="topMargin">
- <number>6</number>
- </property>
- <property name="rightMargin">
- <number>6</number>
- </property>
- <property name="bottomMargin">
- <number>6</number>
- </property>
- <item>
- <widget class="QLabel" name="mainOwnerLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="text">
- <string/>
- </property>
- </widget>
- </item>
- <item>
- <layout class="QHBoxLayout" name="shareeHorizontalLayout" stretch="0,0">
- <property name="spacing">
- <number>6</number>
- </property>
- <property name="leftMargin">
- <number>0</number>
- </property>
- <property name="topMargin">
- <number>0</number>
- </property>
- <property name="rightMargin">
- <number>0</number>
- </property>
- <property name="bottomMargin">
- <number>0</number>
- </property>
- <item>
- <widget class="QLineEdit" name="shareeLineEdit">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="placeholderText">
- <string>Share with users or groups …</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QToolButton" name="confirmShare">
- <property name="icon">
- <iconset resource="../../theme.qrc">
- <normaloff>:/client/theme/confirm.svg</normaloff>:/client/theme/confirm.svg</iconset>
- </property>
- <property name="autoRaise">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- <item>
- <widget class="QLabel" name="errorLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Ignored" vsizetype="Fixed">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="palette">
- <palette>
- <active>
- <colorrole role="WindowText">
- <brush brushstyle="SolidPattern">
- <color alpha="255">
- <red>255</red>
- <green>0</green>
- <blue>0</blue>
- </color>
- </brush>
- </colorrole>
- </active>
- <inactive>
- <colorrole role="WindowText">
- <brush brushstyle="SolidPattern">
- <color alpha="255">
- <red>255</red>
- <green>0</green>
- <blue>0</blue>
- </color>
- </brush>
- </colorrole>
- </inactive>
- <disabled>
- <colorrole role="WindowText">
- <brush brushstyle="SolidPattern">
- <color alpha="255">
- <red>123</red>
- <green>121</green>
- <blue>134</blue>
- </color>
- </brush>
- </colorrole>
- </disabled>
- </palette>
- </property>
- <property name="text">
- <string notr="true">Placeholder for Error text</string>
- </property>
- <property name="textFormat">
- <enum>Qt::PlainText</enum>
- </property>
- <property name="wordWrap">
- <bool>true</bool>
- </property>
- </widget>
- </item>
- </layout>
- </widget>
- <layoutdefault spacing="6" margin="11"/>
- <resources>
- <include location="../../theme.qrc"/>
- </resources>
- <connections/>
-</ui>
diff --git a/src/gui/socketapi/socketapi.cpp b/src/gui/socketapi/socketapi.cpp
index 4de3317a2..e023dd375 100644
--- a/src/gui/socketapi/socketapi.cpp
+++ b/src/gui/socketapi/socketapi.cpp
@@ -497,10 +497,10 @@ void SocketApi::broadcastMessage(const QString &msg, bool doWait)
void SocketApi::processFileActivityRequest(const QString &localFile)
{
const auto fileData = FileData::get(localFile);
- emit fileActivityCommandReceived(fileData.serverRelativePath, fileData.journalRecord().numericFileId().toInt());
+ emit fileActivityCommandReceived(fileData.localPath);
}
-void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage)
+void SocketApi::processShareRequest(const QString &localFile, SocketListener *listener)
{
auto theme = Theme::instance();
@@ -537,7 +537,7 @@ void SocketApi::processShareRequest(const QString &localFile, SocketListener *li
const QString message = QLatin1String("SHARE:OK:") + QDir::toNativeSeparators(localFile);
listener->sendMessage(message);
- emit shareCommandReceived(remotePath, fileData.localPath, startPage);
+ emit shareCommandReceived(fileData.localPath);
}
}
@@ -581,7 +581,7 @@ void SocketApi::command_RETRIEVE_FILE_STATUS(const QString &argument, SocketList
void SocketApi::command_SHARE(const QString &localFile, SocketListener *listener)
{
- processShareRequest(localFile, listener, ShareDialogStartPage::UsersAndGroups);
+ processShareRequest(localFile, listener);
}
void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *listener)
@@ -593,7 +593,7 @@ void SocketApi::command_ACTIVITY(const QString &localFile, SocketListener *liste
void SocketApi::command_MANAGE_PUBLIC_LINKS(const QString &localFile, SocketListener *listener)
{
- processShareRequest(localFile, listener, ShareDialogStartPage::PublicLinks);
+ processShareRequest(localFile, listener);
}
void SocketApi::command_VERSION(const QString &, SocketListener *listener)
@@ -673,7 +673,7 @@ public:
}
private slots:
- void sharesFetched(const QList<QSharedPointer<Share>> &shares)
+ void sharesFetched(const QList<SharePtr> &shares)
{
auto shareName = SocketApi::tr("Context menu share");
@@ -783,7 +783,7 @@ void SocketApi::command_COPY_PUBLIC_LINK(const QString &localFile, SocketListene
connect(job, &GetOrCreatePublicLinkShare::done, this,
[](const QString &url) { copyUrlToClipboard(url); });
connect(job, &GetOrCreatePublicLinkShare::error, this,
- [=]() { emit shareCommandReceived(fileData.serverRelativePath, fileData.localPath, ShareDialogStartPage::PublicLinks); });
+ [=]() { emit shareCommandReceived(fileData.localPath); });
job->run();
}
diff --git a/src/gui/socketapi/socketapi.h b/src/gui/socketapi/socketapi.h
index f3529f870..5f16a00fc 100644
--- a/src/gui/socketapi/socketapi.h
+++ b/src/gui/socketapi/socketapi.h
@@ -17,7 +17,6 @@
#include "syncfileitem.h"
#include "common/syncfilestatus.h"
-#include "sharedialog.h" // for the ShareDialogStartPage
#include "common/syncjournalfilerecord.h"
#include "config.h"
@@ -63,8 +62,8 @@ public slots:
void broadcastStatusPushMessage(const QString &systemPath, SyncFileStatus fileStatus);
signals:
- void shareCommandReceived(const QString &sharePath, const QString &localPath, ShareDialogStartPage startPage);
- void fileActivityCommandReceived(const QString &objectName, const int objectId);
+ void shareCommandReceived(const QString &localPath);
+ void fileActivityCommandReceived(const QString &localPath);
private slots:
void slotNewConnection();
@@ -102,7 +101,7 @@ private:
void broadcastMessage(const QString &msg, bool doWait = false);
// opens share dialog, sends reply
- void processShareRequest(const QString &localFile, SocketListener *listener, ShareDialogStartPage startPage);
+ void processShareRequest(const QString &localFile, SocketListener *listener);
void processFileActivityRequest(const QString &localFile);
Q_INVOKABLE void command_RETRIEVE_FOLDER_STATUS(const QString &argument, SocketListener *listener);
diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp
index e52f62bc8..d3cb9a22c 100644
--- a/src/gui/systray.cpp
+++ b/src/gui/systray.cpp
@@ -285,6 +285,91 @@ void Systray::destroyEditFileLocallyLoadingDialog()
_editFileLocallyLoadingDialog = nullptr;
}
+bool Systray::raiseDialogs()
+{
+ if(_dialogs.empty()) {
+ return false;
+ }
+
+ QVector<QSharedPointer<QQuickWindow>> liveDialogs;
+
+ for(const auto &dialog : _dialogs) {
+ if(dialog.isNull()) {
+ continue;
+ } else if(!dialog->isVisible()) {
+ destroyDialog(dialog.data());
+ continue;
+ }
+
+ liveDialogs.append(dialog);
+
+ dialog->show();
+ dialog->raise();
+ dialog->requestActivate();
+ }
+
+ _dialogs = liveDialogs;
+
+ // If it is empty then we have raised no dialogs, so return false (and viceversa)
+ return !liveDialogs.empty();
+}
+
+void Systray::createFileDetailsDialog(const QString &localPath)
+{
+ qCDebug(lcSystray) << "Opening new file details dialog for " << localPath;
+
+ if(!_trayEngine) {
+ qCWarning(lcSystray) << "Could not open file details dialog for" << localPath << "as no tray engine was available";
+ return;
+ }
+
+ const auto folder = FolderMan::instance()->folderForPath(localPath);
+ if (!folder) {
+ qCWarning(lcSystray) << "Could not open file details dialog for" << localPath << "no responsible folder found";
+ return;
+ }
+
+ const QVariantMap initialProperties{
+ {"accountState", QVariant::fromValue(folder->accountState())},
+ {"localPath", localPath},
+ };
+
+ const auto fileDetailsDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/filedetails/FileDetailsWindow.qml"));
+
+ if (fileDetailsDialog && !fileDetailsDialog->isError()) {
+ const auto createdDialog = fileDetailsDialog->createWithInitialProperties(initialProperties);
+ const QSharedPointer<QQuickWindow> dialog(qobject_cast<QQuickWindow*>(createdDialog));
+
+ if(dialog.isNull()) {
+ qCWarning(lcSystray) << "File details dialog window resulted in creation of object that was not a window!";
+ return;
+ }
+
+ _dialogs.append(dialog);
+
+ dialog->show();
+ dialog->raise();
+ dialog->requestActivate();
+
+ } else if (fileDetailsDialog) {
+ qCWarning(lcSystray) << fileDetailsDialog->errorString();
+ } else {
+ qCWarning(lcSystray) << "Unable to open share dialog for unknown reasons...";
+ }
+}
+
+void Systray::createShareDialog(const QString &localPath)
+{
+ createFileDetailsDialog(localPath);
+ Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Sharing);
+}
+
+void Systray::createFileActivityDialog(const QString &localPath)
+{
+ createFileDetailsDialog(localPath);
+ Q_EMIT showFileDetailsPage(localPath, FileDetailsPage::Activity);
+}
+
void Systray::slotCurrentUserChanged()
{
if (_trayEngine) {
diff --git a/src/gui/systray.h b/src/gui/systray.h
index 7350011fc..d890f9fb4 100644
--- a/src/gui/systray.h
+++ b/src/gui/systray.h
@@ -82,12 +82,17 @@ public:
enum class WindowPosition { Default, Center };
Q_ENUM(WindowPosition);
+ enum class FileDetailsPage { Activity, Sharing };
+ Q_ENUM(FileDetailsPage);
+
Q_REQUIRED_RESULT QString windowTitle() const;
Q_REQUIRED_RESULT bool useNormalWindow() const;
Q_REQUIRED_RESULT bool syncIsPaused() const;
Q_REQUIRED_RESULT bool isOpen() const;
+ Q_REQUIRED_RESULT bool raiseDialogs();
+
signals:
void currentUserChanged();
void openAccountWizard();
@@ -95,8 +100,7 @@ signals:
void openHelp();
void shutdown();
- void openShareDialog(const QString &sharePath, const QString &localPath);
- void showFileActivityDialog(const QString &objectName, const int objectId);
+ void showFileDetailsPage(const QString &fileLocalPath, const FileDetailsPage page);
void sendChatMessage(const QString &token, const QString &message, const QString &replyTo);
void showErrorMessageDialog(const QString &error);
@@ -132,6 +136,9 @@ public slots:
void setSyncIsPaused(const bool syncIsPaused);
void setIsOpen(const bool isOpen);
+ void createShareDialog(const QString &localPath);
+ void createFileActivityDialog(const QString &localPath);
+
private slots:
void slotUnpauseAllFolders();
void slotPauseAllFolders();
@@ -143,6 +150,7 @@ private:
Systray();
void setupContextMenu();
+ void createFileDetailsDialog(const QString &localPath);
[[nodiscard]] QScreen *currentScreen() const;
[[nodiscard]] QRect currentScreenRect() const;
@@ -164,8 +172,8 @@ private:
AccessManagerFactory _accessManagerFactory;
QSet<qlonglong> _callsAlreadyNotified;
-
QPointer<QObject> _editFileLocallyLoadingDialog;
+ QVector<QSharedPointer<QQuickWindow>> _dialogs;
};
} // namespace OCC
diff --git a/src/gui/tray/ActivityItem.qml b/src/gui/tray/ActivityItem.qml
index 020555ed3..eb06c7213 100644
--- a/src/gui/tray/ActivityItem.qml
+++ b/src/gui/tray/ActivityItem.qml
@@ -10,6 +10,8 @@ ItemDelegate {
property Flickable flickable
+ property int iconSize: Style.trayListItemIconSize
+
property bool isFileActivityList: false
readonly property bool isChatActivity: model.objectType === "chat" || model.objectType === "room" || model.objectType === "call"
@@ -45,9 +47,11 @@ ItemDelegate {
showDismissButton: model.links.length > 0
+ iconSize: root.iconSize
+
activityData: model
- onShareButtonClicked: Systray.openShareDialog(model.displayPath, model.path)
+ onShareButtonClicked: Systray.createShareDialog(model.openablePath)
onDismissButtonClicked: activityModel.slotTriggerDismiss(model.activityIndex)
}
diff --git a/src/gui/tray/ActivityItemContent.qml b/src/gui/tray/ActivityItemContent.qml
index 51c653312..80bc931fc 100644
--- a/src/gui/tray/ActivityItemContent.qml
+++ b/src/gui/tray/ActivityItemContent.qml
@@ -17,6 +17,8 @@ RowLayout {
property bool childHovered: shareButton.hovered || dismissActionButton.hovered
+ property int iconSize: Style.trayListItemIconSize
+
signal dismissButtonClicked()
signal shareButtonClicked()
@@ -25,8 +27,8 @@ RowLayout {
Item {
id: thumbnailItem
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
- Layout.preferredWidth: Style.trayListItemIconSize
- Layout.preferredHeight: model.thumbnail && model.thumbnail.isMimeTypeIcon ? Style.trayListItemIconSize * 0.9 : Style.trayListItemIconSize
+ Layout.preferredWidth: root.iconSize
+ Layout.preferredHeight: model.thumbnail && model.thumbnail.isMimeTypeIcon ? root.iconSize * 0.9 : root.iconSize
readonly property int imageWidth: width * (1 - Style.thumbnailImageSizeReduction)
readonly property int imageHeight: height * (1 - Style.thumbnailImageSizeReduction)
readonly property int thumbnailRadius: model.thumbnail && model.thumbnail.isUserAvatar ? width / 2 : 3
diff --git a/src/gui/tray/ActivityList.qml b/src/gui/tray/ActivityList.qml
index b108f3cca..d01c566e1 100644
--- a/src/gui/tray/ActivityList.qml
+++ b/src/gui/tray/ActivityList.qml
@@ -1,6 +1,7 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
+import Style 1.0
import com.nextcloud.desktopclient 1.0 as NC
import Style 1.0
@@ -9,6 +10,7 @@ ScrollView {
property alias model: sortedActivityList.activityListModel
property bool isFileActivityList: false
+ property int iconSize: Style.trayListItemIconSize
signal openFile(string filePath)
signal activityItemClicked(int index)
@@ -55,6 +57,7 @@ ScrollView {
delegate: ActivityItem {
isFileActivityList: controlRoot.isFileActivityList
+ iconSize: controlRoot.iconSize
width: activityList.contentWidth
flickable: activityList
onHoveredChanged: if (hovered) {
diff --git a/src/gui/tray/FileActivityDialog.qml b/src/gui/tray/FileActivityDialog.qml
deleted file mode 100644
index 1d75ff222..000000000
--- a/src/gui/tray/FileActivityDialog.qml
+++ /dev/null
@@ -1,40 +0,0 @@
-import QtQml 2.15
-import QtQuick 2.15
-import QtQuick.Window 2.15
-
-import Style 1.0
-import com.nextcloud.desktopclient 1.0 as NC
-
-Window {
- id: dialog
-
- property alias model: activityModel
-
- NC.FileActivityListModel {
- id: activityModel
- }
-
- width: 500
- height: 500
-
- Rectangle {
- id: background
- anchors.fill: parent
- color: Style.backgroundColor
- }
-
- ActivityList {
- isFileActivityList: true
- anchors.fill: parent
- model: dialog.model
- }
-
- Component.onCompleted: {
- dialog.show();
- dialog.raise();
- dialog.requestActivate();
-
- Systray.forceWindowInit(dialog);
- Systray.positionWindowAtScreenCenter(dialog);
- }
-}
diff --git a/src/gui/tray/Window.qml b/src/gui/tray/Window.qml
index 94611c647..c462b305f 100644
--- a/src/gui/tray/Window.qml
+++ b/src/gui/tray/Window.qml
@@ -21,16 +21,8 @@ ApplicationWindow {
color: "transparent"
flags: Systray.useNormalWindow ? Qt.Window : Qt.Dialog | Qt.FramelessWindowHint
- property int fileActivityDialogObjectId: -1
-
readonly property int maxMenuHeight: Style.trayWindowHeight - Style.trayWindowHeaderHeight - 2 * Style.trayWindowBorderWidth
- function openFileActivityDialog(objectName, objectId) {
- fileActivityDialogLoader.objectName = objectName;
- fileActivityDialogLoader.objectId = objectId;
- fileActivityDialogLoader.refresh();
- }
-
Component.onCompleted: Systray.forceWindowInit(trayWindow)
// Close tray window when focus is lost (e.g. click somewhere else on the screen)
@@ -91,10 +83,6 @@ ApplicationWindow {
}
}
- function onShowFileActivityDialog(objectName, objectId) {
- openFileActivityDialog(objectName, objectId)
- }
-
function onShowErrorMessageDialog(error) {
var newErrorDialog = errorMessageDialog.createObject(trayWindow)
newErrorDialog.text = error
@@ -819,26 +807,5 @@ ApplicationWindow {
model.slotTriggerDefaultAction(index)
}
}
-
- Loader {
- id: fileActivityDialogLoader
-
- property string objectName: ""
- property int objectId: -1
-
- function refresh() {
- active = true
- item.model.load(activityModel.accountState, objectId)
- item.show()
- }
-
- active: false
- sourceComponent: FileActivityDialog {
- title: qsTr("%1 - File activity").arg(fileActivityDialogLoader.objectName)
- onClosing: fileActivityDialogLoader.active = false
- }
-
- onLoaded: refresh()
- }
} // Item trayWindowMainItem
}
diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp
index 8ebdcfef5..352f23a55 100644
--- a/src/gui/tray/activitylistmodel.cpp
+++ b/src/gui/tray/activitylistmodel.cpp
@@ -93,6 +93,7 @@ QHash<int, QByteArray> ActivityListModel::roleNames() const
void ActivityListModel::setAccountState(AccountState *state)
{
_accountState = state;
+ Q_EMIT accountStateChanged();
}
void ActivityListModel::setCurrentItem(const int currentItem)
diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h
index 1c070487d..e030445ed 100644
--- a/src/gui/tray/activitylistmodel.h
+++ b/src/gui/tray/activitylistmodel.h
@@ -39,9 +39,8 @@ class InvalidFilenameDialog;
class ActivityListModel : public QAbstractListModel
{
Q_OBJECT
-
Q_PROPERTY(quint32 maxActionButtons READ maxActionButtons CONSTANT)
- Q_PROPERTY(AccountState *accountState READ accountState CONSTANT)
+ Q_PROPERTY(AccountState *accountState READ accountState WRITE setAccountState NOTIFY accountStateChanged)
public:
enum DataRole {
@@ -123,6 +122,8 @@ public slots:
void setCurrentItem(const int currentItem);
signals:
+ void accountStateChanged();
+
void activityJobStatusCode(int statusCode);
void sendNotificationRequest(const QString &accountName, const QString &link, const QByteArray &verb, int row);
diff --git a/src/gui/tray/asyncimageresponse.cpp b/src/gui/tray/asyncimageresponse.cpp
index f393c837a..6bcee6778 100644
--- a/src/gui/tray/asyncimageresponse.cpp
+++ b/src/gui/tray/asyncimageresponse.cpp
@@ -63,17 +63,30 @@ void AsyncImageResponse::processNextImage()
return;
}
- if (_imagePaths.at(_index).startsWith(QStringLiteral(":/client"))) {
- setImageAndEmitFinished(QIcon(_imagePaths.at(_index)).pixmap(_requestedImageSize).toImage());
+ const auto imagePath = _imagePaths.at(_index);
+ if (imagePath.startsWith(QStringLiteral(":/client"))) {
+ setImageAndEmitFinished(QIcon(imagePath).pixmap(_requestedImageSize).toImage());
return;
+ } else if (imagePath.startsWith(QStringLiteral(":/fileicon"))) {
+ const auto filePath = imagePath.mid(10);
+ const auto fileInfo = QFileInfo(filePath);
+ setImageAndEmitFinished(_fileIconProvider.icon(fileInfo).pixmap(_requestedImageSize).toImage());
+ return;
+ }
+
+ OCC::AccountPtr accountInRequestedServer;
+
+ for (const auto &account : OCC::AccountManager::instance()->accounts()) {
+ if (account && account->account() && imagePath.startsWith(account->account()->url().toString())) {
+ accountInRequestedServer = account->account();
+ }
}
- const auto currentUser = OCC::UserModel::instance()->currentUser();
- if (currentUser && currentUser->account()) {
+ if (accountInRequestedServer) {
const QUrl iconUrl(_imagePaths.at(_index));
if (iconUrl.isValid() && !iconUrl.scheme().isEmpty()) {
// fetch the remote resource
- const auto reply = currentUser->account()->sendRawRequest(QByteArrayLiteral("GET"), iconUrl);
+ const auto reply = accountInRequestedServer->sendRawRequest(QByteArrayLiteral("GET"), iconUrl);
connect(reply, &QNetworkReply::finished, this, &AsyncImageResponse::slotProcessNetworkReply);
++_index;
return;
diff --git a/src/gui/tray/asyncimageresponse.h b/src/gui/tray/asyncimageresponse.h
index e27c7b99c..9d2d60003 100644
--- a/src/gui/tray/asyncimageresponse.h
+++ b/src/gui/tray/asyncimageresponse.h
@@ -16,6 +16,7 @@
#include <QImage>
#include <QQuickImageProvider>
+#include <QFileIconProvider>
class AsyncImageResponse : public QQuickImageResponse
{
@@ -34,5 +35,6 @@ private slots:
QStringList _imagePaths;
QSize _requestedImageSize;
QColor _svgRecolor;
+ QFileIconProvider _fileIconProvider;
int _index = 0;
};