mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-21 20:45:51 +03:00
Add a file details window/page, QMLify file sharing
Signed-off-by: Claudio Cambra <claudio.cambra@nextcloud.com>
This commit is contained in:
parent
1dbdd8853f
commit
7971789112
52 changed files with 4040 additions and 4012 deletions
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
setAccountState(accountState);
|
||||
|
||||
_objectId = objectId;
|
||||
const auto folder = FolderMan::instance()->folderForPath(_localPath);
|
||||
|
||||
if (!folder) {
|
||||
qCWarning(lcFileActivityListModel) << "Invalid folder for localPath:" << _localPath << "will not load activity list model.";
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
47
src/gui/filedetails/FileActivityView.qml
Normal file
47
src/gui/filedetails/FileActivityView.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
189
src/gui/filedetails/FileDetailsPage.qml
Normal file
189
src/gui/filedetails/FileDetailsPage.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
40
src/gui/filedetails/FileDetailsWindow.qml
Normal file
40
src/gui/filedetails/FileDetailsWindow.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
70
src/gui/filedetails/NCInputTextEdit.qml
Normal file
70
src/gui/filedetails/NCInputTextEdit.qml
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
65
src/gui/filedetails/NCInputTextField.qml
Normal file
65
src/gui/filedetails/NCInputTextField.qml
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
84
src/gui/filedetails/NCTabButton.qml
Normal file
84
src/gui/filedetails/NCTabButton.qml
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
762
src/gui/filedetails/ShareDelegate.qml
Normal file
762
src/gui/filedetails/ShareDelegate.qml
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
309
src/gui/filedetails/ShareView.qml
Normal file
309
src/gui/filedetails/ShareView.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
src/gui/filedetails/ShareeDelegate.qml
Normal file
27
src/gui/filedetails/ShareeDelegate.qml
Normal file
|
@ -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
|
||||
}
|
247
src/gui/filedetails/ShareeSearchField.qml
Normal file
247
src/gui/filedetails/ShareeSearchField.qml
Normal file
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
150
src/gui/filedetails/filedetails.cpp
Normal file
150
src/gui/filedetails/filedetails.cpp
Normal file
|
@ -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
|
75
src/gui/filedetails/filedetails.h
Normal file
75
src/gui/filedetails/filedetails.h
Normal file
|
@ -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
|
221
src/gui/filedetails/shareemodel.cpp
Normal file
221
src/gui/filedetails/shareemodel.cpp
Normal file
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
100
src/gui/filedetails/shareemodel.h
Normal file
100
src/gui/filedetails/shareemodel.h
Normal file
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
973
src/gui/filedetails/sharemodel.cpp
Normal file
973
src/gui/filedetails/sharemodel.cpp
Normal file
|
@ -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 ¤t, 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 ¬e) 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 ¬e) 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
|
205
src/gui/filedetails/sharemodel.h
Normal file
205
src/gui/filedetails/sharemodel.h
Normal file
|
@ -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 ¬e) const;
|
||||
void setShareNoteFromQml(const QVariant &share, const QString ¬e) 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
|
114
src/gui/filedetails/sortedsharemodel.cpp
Normal file
114
src/gui/filedetails/sortedsharemodel.cpp
Normal file
|
@ -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
|
45
src/gui/filedetails/sortedsharemodel.h
Normal file
45
src/gui/filedetails/sortedsharemodel.h
Normal file
|
@ -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
|
|
@ -24,6 +24,7 @@
|
|||
#include "filesystem.h"
|
||||
#include "lockwatcher.h"
|
||||
#include "common/asserts.h"
|
||||
#include "gui/systray.h"
|
||||
#include <pushnotifications.h>
|
||||
#include <syncengine.h>
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 ¤t, 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
|
|
@ -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
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
|
@ -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>
|
|
@ -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 ¬e)
|
||||
: 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(),
|
||||
|
|
|
@ -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,
|
||||
|
@ -221,6 +245,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.
|
||||
*
|
||||
|
@ -233,16 +274,6 @@ public:
|
|||
*/
|
||||
void setNote(const QString ¬e);
|
||||
|
||||
/*
|
||||
* Returns the token of the link share.
|
||||
*/
|
||||
[[nodiscard]] QString getToken() const;
|
||||
|
||||
/*
|
||||
* Get the expiration date
|
||||
*/
|
||||
[[nodiscard]] QDate getExpireDate() const;
|
||||
|
||||
/*
|
||||
* Set the expiration date
|
||||
*
|
||||
|
@ -250,19 +281,12 @@ public:
|
|||
* 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 ¬e);
|
||||
|
||||
void setNote(const QString ¬e);
|
||||
|
||||
[[nodiscard]] QString getNote() const;
|
||||
|
||||
void slotNoteSet(const QJsonDocument &, const QVariant ¬e);
|
||||
|
||||
void setExpireDate(const QDate &date);
|
||||
|
||||
[[nodiscard]] QDate getExpireDate() const;
|
||||
|
||||
void slotExpireDateSet(const QJsonDocument &reply, const QVariant &value);
|
||||
public slots:
|
||||
void setNote(const QString ¬e);
|
||||
void setExpireDate(const QDate &date);
|
||||
|
||||
signals:
|
||||
void noteSet();
|
||||
void noteSetError();
|
||||
void expireDateSet();
|
||||
|
||||
private slots:
|
||||
void slotNoteSet(const QJsonDocument &json, const QVariant ¬e);
|
||||
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
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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 ¬e);
|
||||
|
||||
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
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
const auto currentUser = OCC::UserModel::instance()->currentUser();
|
||||
if (currentUser && currentUser->account()) {
|
||||
OCC::AccountPtr accountInRequestedServer;
|
||||
|
||||
for (const auto &account : OCC::AccountManager::instance()->accounts()) {
|
||||
if (account && account->account() && imagePath.startsWith(account->account()->url().toString())) {
|
||||
accountInRequestedServer = account->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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue