Allow creation of new folders from the Settings Dialog.
authorallexzander <blackslayer4@gmail.com>
Fri, 29 Jan 2021 18:00:21 +0000 (20:00 +0200)
committerallexzander <blackslayer4@gmail.com>
Thu, 4 Feb 2021 08:05:18 +0000 (10:05 +0200)
Signed-off-by: allexzander <blackslayer4@gmail.com>
src/gui/CMakeLists.txt
src/gui/accountsettings.cpp
src/gui/accountsettings.h
src/gui/foldercreationdialog.cpp [new file with mode: 0644]
src/gui/foldercreationdialog.h [new file with mode: 0644]
src/gui/foldercreationdialog.ui [new file with mode: 0644]
src/gui/folderstatusmodel.cpp
src/gui/folderstatusmodel.h

index 0107a9d6c2bcc2098ad19d213a19b61affce496f..346114074f04845b235e62214c2896f9b4dcc6cf 100644 (file)
@@ -23,6 +23,7 @@ endif()
 set(client_UI_SRCS
     accountsettings.ui
     conflictdialog.ui
+    foldercreationdialog.ui
     folderwizardsourcepage.ui
     folderwizardtargetpage.ui
     generalsettings.ui
@@ -60,6 +61,7 @@ set(client_SRCS
     conflictsolver.cpp
     connectionvalidator.cpp
     folder.cpp
+    foldercreationdialog.cpp
     folderman.cpp
     folderstatusmodel.cpp
     folderstatusdelegate.cpp
index ec6254b91bcfa9a57567e71eca5bab68c782fb56..5b340de407311a1054eec15553681b487579a02c 100644 (file)
@@ -17,6 +17,7 @@
 #include "ui_accountsettings.h"
 
 #include "theme.h"
+#include "foldercreationdialog.h"
 #include "folderman.h"
 #include "folderwizard.h"
 #include "folderstatusmodel.h"
@@ -333,6 +334,46 @@ void AccountSettings::slotEditCurrentIgnoredFiles()
     openIgnoredFilesDialog(f->path());
 }
 
+void AccountSettings::slotOpenMakeFolderDialog()
+{
+    const auto &selected = _ui->_folderList->selectionModel()->currentIndex();
+
+    if (!selected.isValid()) {
+        qCWarning(lcAccountSettings) << "Selection model current folder index is not valid.";
+        return;
+    }
+
+    const auto &classification = _model->classify(selected);
+
+    if (classification != FolderStatusModel::SubFolder && classification != FolderStatusModel::RootFolder) {
+        return;
+    }
+
+    const QString fileName = [this, &selected, &classification] {
+        QString result;
+        if (classification == FolderStatusModel::RootFolder) {
+            const auto alias = _model->data(selected, FolderStatusDelegate::FolderAliasRole).toString();
+            if (const auto folder = FolderMan::instance()->folder(alias)) {
+                result = folder->path();
+            }
+        } else {
+            result = _model->data(selected, FolderStatusDelegate::FolderPathRole).toString();
+        }
+
+        if (result.endsWith('/')) {
+            result.chop(1);
+        }
+
+        return result;
+    }();
+
+    if (!fileName.isEmpty()) {
+        const auto folderCreationDialog = new FolderCreationDialog(fileName, this); 
+        folderCreationDialog->setAttribute(Qt::WA_DeleteOnClose);
+        folderCreationDialog->open();
+    }
+}
+
 void AccountSettings::slotEditCurrentLocalIgnoredFiles()
 {
     QModelIndex selected = _ui->_folderList->selectionModel()->currentIndex();
@@ -403,6 +444,10 @@ void AccountSettings::slotSubfolderContextMenuRequested(const QModelIndex& index
     ac = menu.addAction(tr("Edit Ignored Files"));
     connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentLocalIgnoredFiles);
 
+    ac = menu.addAction(tr("Create new folder"));
+    connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenMakeFolderDialog);
+    ac->setEnabled(QFile::exists(fileName));
+
     const auto folder = info->_folder;
     if (folder && folder->virtualFilesEnabled()) {
         auto availabilityMenu = menu.addMenu(tr("Availability"));
@@ -475,6 +520,10 @@ void AccountSettings::slotCustomContextMenuRequested(const QPoint &pos)
     ac = menu->addAction(tr("Edit Ignored Files"));
     connect(ac, &QAction::triggered, this, &AccountSettings::slotEditCurrentIgnoredFiles);
 
+    ac = menu->addAction(tr("Create new folder"));
+    connect(ac, &QAction::triggered, this, &AccountSettings::slotOpenMakeFolderDialog);
+    ac->setEnabled(QFile::exists(folder->path()));
+
     if (!_ui->_folderList->isExpanded(index) && folder->supportsSelectiveSync()) {
         ac = menu->addAction(tr("Choose what to sync"));
         ac->setEnabled(folderConnected);
index ccb986e74262f3173e194824bd83be538d1ef793..17e8398394b595f885aa6abaf88e3e7cfd43270e 100644 (file)
@@ -85,6 +85,7 @@ protected slots:
     void slotOpenCurrentFolder(); // sync folder
     void slotOpenCurrentLocalSubFolder(); // selected subfolder in sync folder
     void slotEditCurrentIgnoredFiles();
+    void slotOpenMakeFolderDialog();
     void slotEditCurrentLocalIgnoredFiles();
     void slotEnableVfsCurrentFolder();
     void slotDisableVfsCurrentFolder();
diff --git a/src/gui/foldercreationdialog.cpp b/src/gui/foldercreationdialog.cpp
new file mode 100644 (file)
index 0000000..3ee046d
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@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 "foldercreationdialog.h"
+#include "ui_foldercreationdialog.h"
+
+#include <limits>
+
+#include <QDir>
+#include <QMessageBox>
+
+FolderCreationDialog::FolderCreationDialog(const QString &destination, QWidget *parent)
+    : QDialog(parent)
+    , ui(new Ui::FolderCreationDialog)
+    , _destination(destination)
+{
+    ui->setupUi(this);
+
+    ui->labelErrorMessage->setVisible(false);
+
+    setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
+
+    connect(ui->newFolderNameEdit, &QLineEdit::textChanged, this, &FolderCreationDialog::slotNewFolderNameEditTextEdited);
+
+    const QString suggestedFolderNamePrefix = QObject::tr("New folder");
+
+    const auto newFolderFullPath = _destination + "/" + suggestedFolderNamePrefix;
+    if (!QDir(newFolderFullPath).exists()) {
+        ui->newFolderNameEdit->setText(suggestedFolderNamePrefix);
+    } else {
+        for (unsigned int i = 2; i < std::numeric_limits<unsigned int>::max(); ++i) {
+            const QString suggestedPostfix = QString(" (%1)").arg(i);
+
+            if (!QDir(newFolderFullPath + suggestedPostfix).exists()) {
+                ui->newFolderNameEdit->setText(suggestedFolderNamePrefix + suggestedPostfix);
+                break;
+            }
+        }
+    }
+
+    ui->newFolderNameEdit->setFocus();
+    ui->newFolderNameEdit->selectAll();
+}
+
+FolderCreationDialog::~FolderCreationDialog()
+{
+    delete ui;
+}
+
+void FolderCreationDialog::accept()
+{
+    Q_ASSERT(!_destination.endsWith('/'));
+
+    if (QDir(_destination + "/" + ui->newFolderNameEdit->text()).exists()) {
+        ui->labelErrorMessage->setVisible(true);
+        return;
+    }
+
+    if (!QDir(_destination).mkdir(ui->newFolderNameEdit->text())) {
+        QMessageBox::critical(this, tr("Error"), tr("Could not create a folder! Check your write permissions."));
+    }
+
+    QDialog::accept();
+}
+
+void FolderCreationDialog::slotNewFolderNameEditTextEdited()
+{
+    if (!ui->newFolderNameEdit->text().isEmpty() && QDir(_destination + "/" + ui->newFolderNameEdit->text()).exists()) {
+        ui->labelErrorMessage->setVisible(true);
+    } else {
+        ui->labelErrorMessage->setVisible(false);
+    }
+}
diff --git a/src/gui/foldercreationdialog.h b/src/gui/foldercreationdialog.h
new file mode 100644 (file)
index 0000000..c3d219f
--- /dev/null
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) by Oleksandr Zolotov <alex@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.
+ */
+
+#ifndef FOLDERCREATIONDIALOG_H
+#define FOLDERCREATIONDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class FolderCreationDialog;
+}
+
+class FolderCreationDialog : public QDialog
+{
+    Q_OBJECT
+
+public:
+    explicit FolderCreationDialog(const QString &destination, QWidget *parent = nullptr);
+    ~FolderCreationDialog();
+
+private slots:
+    void accept() override;
+
+    void slotNewFolderNameEditTextEdited();
+
+private:
+    Ui::FolderCreationDialog *ui;
+
+    QString _destination;
+};
+
+#endif // FOLDERCREATIONDIALOG_H
diff --git a/src/gui/foldercreationdialog.ui b/src/gui/foldercreationdialog.ui
new file mode 100644 (file)
index 0000000..87c7a87
--- /dev/null
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>FolderCreationDialog</class>
+ <widget class="QDialog" name="FolderCreationDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>355</width>
+    <height>138</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Create new folder</string>
+  </property>
+  <widget class="QDialogButtonBox" name="buttonBox">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>90</y>
+     <width>341</width>
+     <height>32</height>
+    </rect>
+   </property>
+   <property name="orientation">
+    <enum>Qt::Horizontal</enum>
+   </property>
+   <property name="standardButtons">
+    <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+   </property>
+  </widget>
+  <widget class="QLineEdit" name="newFolderNameEdit">
+   <property name="geometry">
+    <rect>
+     <x>20</x>
+     <y>30</y>
+     <width>321</width>
+     <height>22</height>
+    </rect>
+   </property>
+   <property name="placeholderText">
+    <string>Enter folder name</string>
+   </property>
+  </widget>
+  <widget class="QLabel" name="labelErrorMessage">
+   <property name="enabled">
+    <bool>true</bool>
+   </property>
+   <property name="geometry">
+    <rect>
+     <x>20</x>
+     <y>60</y>
+     <width>321</width>
+     <height>16</height>
+    </rect>
+   </property>
+   <property name="styleSheet">
+    <string notr="true">color: rgb(255, 0, 0)</string>
+   </property>
+   <property name="text">
+    <string>Folder already exists</string>
+   </property>
+  </widget>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>FolderCreationDialog</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>FolderCreationDialog</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
index d2e906c0e9c257b4a16a83fe9972ad98d3be4472..d66378a1651637de852c2f212bce49c5baf3c74a 100644 (file)
@@ -595,8 +595,12 @@ void FolderStatusModel::fetchMore(const QModelIndex &parent)
         return;
     info->resetSubs(this, parent);
     QString path = info->_folder->remotePathTrailingSlash();
-    if (info->_path != QLatin1String("/")) {
-        path += info->_path;
+
+    // info->_path always contains non-mangled name, so we need to use mangled when requesting nested folders for encrypted subfolders as required by LsColJob
+    const QString infoPath = (info->_isEncrypted && !info->_e2eMangledName.isEmpty()) ? info->_e2eMangledName : info->_path;
+
+    if (infoPath != QLatin1String("/")) {
+        path += infoPath;
     }
 
     auto *job = new LsColJob(_accountState->account(), path, this);
@@ -742,6 +746,15 @@ void FolderStatusModel::slotUpdateDirectories(const QStringList &list)
         parentInfo->_folder->journalDb()->getFileRecordByE2eMangledName(removeTrailingSlash(relativePath), &rec);
         if (rec.isValid()) {
             newInfo._name = removeTrailingSlash(rec._path).split('/').last();
+            if (rec._isE2eEncrypted && !rec._e2eMangledName.isEmpty()) {
+                // we must use local path for Settings Dialog's filesystem tree, otherwise open and create new folder actions won't work
+                // hence, we are storing _e2eMangledName separately so it can be use later for LsColJob
+                newInfo._e2eMangledName = relativePath;
+                newInfo._path = rec._path;
+            }
+            if (!newInfo._path.endsWith('/')) {
+                newInfo._path += '/';
+            }
         } else {
             newInfo._name = removeTrailingSlash(relativePath).split('/').last();
         }
index 194e8edf98aa9aa4985c13214747d34038131493..39d937f09c6e90f23732524befb8244e8612d9ce 100644 (file)
@@ -60,8 +60,9 @@ public:
     struct SubFolderInfo
     {
         Folder *_folder = nullptr;
-        QString _name;
-        QString _path;
+        QString _name; // Folder name to be displayed in the UI
+        QString _path; // Sub-folder path that should always point to a local filesystem's folder
+        QString _e2eMangledName; // Mangled name that needs to be used when making fetch requests and should not be used for displaying in the UI
         QVector<int> _pathIdx;
         QVector<SubFolderInfo> _subs;
         qint64 _size = 0;