From: Matthieu Gallien Date: Fri, 28 Apr 2023 10:43:04 +0000 (+0200) Subject: basic implementation of a dialog to resolve conflicts as a batch X-Git-Tag: archive/raspbian/3.16.7-1_deb13u1+rpi1~1^2~12^2~10^2~44^2~26 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=1491c134c3fd9326e401c31fd68db9c2328182ba;p=nextcloud-desktop.git basic implementation of a dialog to resolve conflicts as a batch will allow solving all conflicts at once FIX #2786 Signed-off-by: Matthieu Gallien --- diff --git a/resources.qrc b/resources.qrc index 2d7f22748..12ed9ae1d 100644 --- a/resources.qrc +++ b/resources.qrc @@ -56,5 +56,7 @@ src/gui/tray/ListItemLineAndSubline.qml src/gui/tray/TrayFoldersMenuButton.qml src/gui/tray/TrayFolderListItem.qml + src/gui/ResolveConflictsDialog.qml + src/gui/ConflictDelegate.qml diff --git a/src/gui/ConflictDelegate.qml b/src/gui/ConflictDelegate.qml new file mode 100644 index 000000000..aa2a138f3 --- /dev/null +++ b/src/gui/ConflictDelegate.qml @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 by Matthieu Gallien + * + * 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 QtQml 2.15 +import QtQuick 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import Style 1.0 +import com.nextcloud.desktopclient 1.0 +import "./tray" + +Item { + id: root + + required property string existingFileName + required property string conflictFileName + required property string existingSize + required property string conflictSize + required property string existingDate + required property string conflictDate + required property bool existingSelected + required property bool conflictSelected + + EnforcedPlainTextLabel { + id: existingFileNameLabel + + anchors.top: parent.top + anchors.left: parent.left + + text: root.existingFileName + + font.weight: Font.Light + font.pixelSize: 15 + } + + RowLayout { + anchors.top: existingFileNameLabel.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + Image { + id: existingPreview + + anchors.top: parent.top + anchors.left: parent.left + + source: 'https://nextcloud.local/index.php/apps/theming/img/core/filetypes/text.svg?v=b9feb2d6' + width: 64 + height: 64 + sourceSize.width: 64 + sourceSize.height: 64 + } + + ColumnLayout { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: existingPreview.right + anchors.right: parent.right + + CheckBox { + id: selectExisting + + Layout.alignment: Layout.TopLeft + + checked: root.existingSelected + } + + EnforcedPlainTextLabel { + Layout.fillWidth: true + + text: root.existingDate + + font.pixelSize: 15 + } + + EnforcedPlainTextLabel { + Layout.fillWidth: true + + text: existingSize + + font.pixelSize: 15 + } + } + } + + Item { + Layout.fillWidth: true + Layout.fillHeight: true + + Image { + id: conflictPreview + + anchors.top: parent.top + anchors.left: parent.left + + source: 'https://nextcloud.local/index.php/apps/theming/img/core/filetypes/text.svg?v=b9feb2d6' + width: 64 + height: 64 + sourceSize.width: 64 + sourceSize.height: 64 + } + + ColumnLayout { + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: conflictPreview.right + anchors.right: parent.right + + CheckBox { + id: selectConflict + + Layout.alignment: Layout.TopLeft + + checked: root.conflictSelected + } + + EnforcedPlainTextLabel { + Layout.fillWidth: true + + text: root.conflictDate + + font.pixelSize: 15 + } + + EnforcedPlainTextLabel { + Layout.fillWidth: true + + text: conflictSize + + font.pixelSize: 15 + } + } + } + } +} diff --git a/src/gui/ResolveConflictsDialog.qml b/src/gui/ResolveConflictsDialog.qml new file mode 100644 index 000000000..251d4d2ad --- /dev/null +++ b/src/gui/ResolveConflictsDialog.qml @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2023 by Matthieu Gallien + * + * 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 QtQml 2.15 +import QtQuick 2.15 +import QtQuick.Window 2.15 +import QtQuick.Layouts 1.15 +import QtQuick.Controls 2.15 +import QtQml.Models 2.15 +import Style 1.0 +import com.nextcloud.desktopclient 1.0 +import "./tray" + +Window { + id: root + + flags: Qt.Dialog + visible: true + + width: 600 + height: 800 + minimumWidth: 600 + minimumHeight: 800 + + onClosing: function() { + Systray.destroyDialog(root); + } + + Component.onCompleted: { + Systray.forceWindowInit(root); + Systray.positionNotificationWindow(root); + + root.show(); + root.raise(); + root.requestActivate(); + } + + ColumnLayout { + anchors.fill: parent + anchors.leftMargin: 20 + anchors.rightMargin: 20 + anchors.bottomMargin: 20 + anchors.topMargin: 20 + spacing: 15 + z: 2 + + EnforcedPlainTextLabel { + text: qsTr("%1 files in conflict").arg(12) + font.bold: true + font.pixelSize: 20 + Layout.fillWidth: true + } + + EnforcedPlainTextLabel { + text: qsTr("Which files do you want to keep?") + font.pixelSize: 15 + Layout.fillWidth: true + } + + EnforcedPlainTextLabel { + text: qsTr("If you select both versions, the local file will have a number added to its name.") + font.pixelSize: 15 + Layout.fillWidth: true + Layout.topMargin: -15 + } + + RowLayout { + Layout.fillWidth: true + Layout.topMargin: 15 + + CheckBox { + id: selectExisting + + Layout.fillWidth: true + Layout.alignment: Layout.TopLeft + + text: qsTr('Local version') + + font.pixelSize: 15 + } + + CheckBox { + id: selectConflict + + Layout.fillWidth: true + Layout.alignment: Layout.TopLeft + + text: qsTr('Server version') + + font.pixelSize: 15 + } + } + + Rectangle { + Layout.fillWidth: true + Layout.leftMargin: 5 + Layout.rightMargin: 5 + color: Style.menuBorder + height: 1 + } + + ScrollView { + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + + ListView { + id: conflictListView + + model: DelegateModel { + model: ListModel { + ListElement { + existingFileName: 'Text File.txt' + conflictFileName: 'Text File.txt' + existingSize: '2 B' + conflictSize: '15 B' + existingDate: '28 avril 2023 09:53' + conflictDate: '28 avril 2023 09:53' + existingSelected: false + conflictSelected: false + } + + ListElement { + existingFileName: 'Text File.txt' + conflictFileName: 'Text File.txt' + existingSize: '2 B' + conflictSize: '15 B' + existingDate: '28 avril 2023 09:53' + conflictDate: '28 avril 2023 09:53' + existingSelected: false + conflictSelected: false + } + + ListElement { + existingFileName: 'Text File.txt' + conflictFileName: 'Text File.txt' + existingSize: '2 B' + conflictSize: '15 B' + existingDate: '28 avril 2023 09:53' + conflictDate: '28 avril 2023 09:53' + existingSelected: false + conflictSelected: false + } + } + + delegate: ConflictDelegate { + width: conflictListView.contentItem.width + height: 100 + } + } + } + } + + DialogButtonBox { + Layout.fillWidth: true + + standardButtons: DialogButtonBox.Ok | DialogButtonBox.Cancel + + onAccepted: function() { + console.log("Ok clicked") + Systray.destroyDialog(root) + } + + onRejected: function() { + console.log("Cancel clicked") + Systray.destroyDialog(root) + } + } + } + + Rectangle { + color: Theme.systemPalette.window + anchors.fill: parent + z: 1 + } +} diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 40af80ac0..a53aa160b 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -66,7 +66,7 @@ int main(int argc, char **argv) // the platformtheme plugin won't try to force qqc2-desktops-style // anymore. // Can be removed once the bug in qqc2-desktop-style is gone. - QQuickStyle::setStyle("Default"); + QQuickStyle::setStyle("Fusion"); // OpenSSL 1.1.0: No explicit initialisation or de-initialisation is necessary. diff --git a/src/gui/systray.cpp b/src/gui/systray.cpp index c45b870b0..f2e1d12d0 100644 --- a/src/gui/systray.cpp +++ b/src/gui/systray.cpp @@ -285,6 +285,21 @@ void Systray::destroyEditFileLocallyLoadingDialog() _editFileLocallyLoadingDialog = nullptr; } +void Systray::createResolveConflictsDialog() +{ + const auto callDialog = new QQmlComponent(_trayEngine, QStringLiteral("qrc:/qml/src/gui/ResolveConflictsDialog.qml")); + const QVariantMap initialProperties{}; + + if(callDialog->isError()) { + qCWarning(lcSystray) << callDialog->errorString(); + return; + } + + // This call dialog gets deallocated on close conditions + // by a call from the QML side to the destroyDialog slot + callDialog->createWithInitialProperties(initialProperties); +} + bool Systray::raiseDialogs() { return raiseFileDetailDialogs(); diff --git a/src/gui/systray.h b/src/gui/systray.h index fdc986175..69c34a65f 100644 --- a/src/gui/systray.h +++ b/src/gui/systray.h @@ -121,6 +121,7 @@ public slots: void createCallDialog(const OCC::Activity &callNotification, const OCC::AccountStatePtr accountState); void createEditFileLocallyLoadingDialog(const QString &fileName); void destroyEditFileLocallyLoadingDialog(); + void createResolveConflictsDialog(); void slotCurrentUserChanged(); diff --git a/src/gui/tray/activitylistmodel.cpp b/src/gui/tray/activitylistmodel.cpp index 434074cf0..f3cb3838f 100644 --- a/src/gui/tray/activitylistmodel.cpp +++ b/src/gui/tray/activitylistmodel.cpp @@ -640,34 +640,8 @@ void ActivityListModel::slotTriggerDefaultAction(const int activityIndex) const auto activity = _finalList.at(activityIndex); if (activity._syncFileItemStatus == SyncFileItem::Conflict) { - Q_ASSERT(!activity._file.isEmpty()); - Q_ASSERT(!activity._folder.isEmpty()); - Q_ASSERT(Utility::isConflictFile(activity._file)); + displaySingleConflictDialog(activity); - const auto folder = FolderMan::instance()->folder(activity._folder); - - const auto conflictedRelativePath = activity._file; - const auto baseRelativePath = folder->journalDb()->conflictFileBaseName(conflictedRelativePath.toUtf8()); - - const auto dir = QDir(folder->path()); - const auto conflictedPath = dir.filePath(conflictedRelativePath); - const auto basePath = dir.filePath(baseRelativePath); - - const auto baseName = QFileInfo(basePath).fileName(); - - if (!_currentConflictDialog.isNull()) { - _currentConflictDialog->close(); - } - _currentConflictDialog = new ConflictDialog; - _currentConflictDialog->setBaseFilename(baseName); - _currentConflictDialog->setLocalVersionFilename(conflictedPath); - _currentConflictDialog->setRemoteVersionFilename(basePath); - _currentConflictDialog->setAttribute(Qt::WA_DeleteOnClose); - connect(_currentConflictDialog, &ConflictDialog::accepted, folder, [folder]() { - folder->scheduleThisFolderSoon(); - }); - _currentConflictDialog->open(); - ownCloudGui::raiseDialog(_currentConflictDialog); return; } else if (activity._syncFileItemStatus == SyncFileItem::FileNameClash) { triggerCaseClashAction(activity); @@ -730,6 +704,40 @@ void ActivityListModel::triggerCaseClashAction(Activity activity) ownCloudGui::raiseDialog(_currentCaseClashFilenameDialog); } +void ActivityListModel::displaySingleConflictDialog(const Activity &activity) +{ + Q_ASSERT(!activity._file.isEmpty()); + Q_ASSERT(!activity._folder.isEmpty()); + Q_ASSERT(Utility::isConflictFile(activity._file)); + + const auto folder = FolderMan::instance()->folder(activity._folder); + + const auto conflictedRelativePath = activity._file; + const auto baseRelativePath = folder->journalDb()->conflictFileBaseName(conflictedRelativePath.toUtf8()); + + const auto dir = QDir(folder->path()); + const auto conflictedPath = dir.filePath(conflictedRelativePath); + const auto basePath = dir.filePath(baseRelativePath); + + const auto baseName = QFileInfo(basePath).fileName(); + + if (!_currentConflictDialog.isNull()) { + _currentConflictDialog->close(); + } + _currentConflictDialog = new ConflictDialog; + _currentConflictDialog->setBaseFilename(baseName); + _currentConflictDialog->setLocalVersionFilename(conflictedPath); + _currentConflictDialog->setRemoteVersionFilename(basePath); + _currentConflictDialog->setAttribute(Qt::WA_DeleteOnClose); + connect(_currentConflictDialog, &ConflictDialog::accepted, folder, [folder]() { + folder->scheduleThisFolderSoon(); + }); + _currentConflictDialog->open(); + ownCloudGui::raiseDialog(_currentConflictDialog); + + Systray::instance()->createResolveConflictsDialog(); +} + void ActivityListModel::slotTriggerAction(const int activityIndex, const int actionIndex) { if (activityIndex < 0 || activityIndex >= _finalList.size()) { diff --git a/src/gui/tray/activitylistmodel.h b/src/gui/tray/activitylistmodel.h index 3e21ebd11..eae46ea78 100644 --- a/src/gui/tray/activitylistmodel.h +++ b/src/gui/tray/activitylistmodel.h @@ -162,6 +162,8 @@ private: void insertOrRemoveDummyFetchingActivity(); void triggerCaseClashAction(Activity activity); + void displaySingleConflictDialog(const Activity &activity); + Activity _notificationIgnoredFiles; Activity _dummyFetchingActivities;