<file>src/gui/tray/ListItemLineAndSubline.qml</file>
<file>src/gui/tray/TrayFoldersMenuButton.qml</file>
<file>src/gui/tray/TrayFolderListItem.qml</file>
+ <file>src/gui/ResolveConflictsDialog.qml</file>
+ <file>src/gui/ConflictDelegate.qml</file>
</qresource>
</RCC>
--- /dev/null
+/*
+ * Copyright (C) 2023 by Matthieu Gallien <matthieu.gallien@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 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
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Copyright (C) 2023 by Matthieu Gallien <matthieu.gallien@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 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
+ }
+}
// 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.
_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();
void createCallDialog(const OCC::Activity &callNotification, const OCC::AccountStatePtr accountState);
void createEditFileLocallyLoadingDialog(const QString &fileName);
void destroyEditFileLocallyLoadingDialog();
+ void createResolveConflictsDialog();
void slotCurrentUserChanged();
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);
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()) {
void insertOrRemoveDummyFetchingActivity();
void triggerCaseClashAction(Activity activity);
+ void displaySingleConflictDialog(const Activity &activity);
+
Activity _notificationIgnoredFiles;
Activity _dummyFetchingActivities;