]> cloud.milkyroute.net Git - dolphin.git/commitdiff
Add Duplicate feature
authorNathaniel Graham <nate@kde.org>
Fri, 20 Dec 2019 17:07:25 +0000 (10:07 -0700)
committerNate Graham <nate@kde.org>
Sun, 15 Mar 2020 19:20:50 +0000 (13:20 -0600)
Summary: Adds a Duplicate feature to Dolphin, showing up as a menu item in the File menu that appears when one or more items are selected and the directory is writable. Duplicated items receive the names of the original files with " copy" appended before the file extension, if any.

Test Plan:

{F5201386} {F5201393}

Test cases:

- Try to duplicate when nothing is selected: **PASS**: menu item is grayed out
- Try to duplicate anything on a read-only local volume: **PASS**:  menu item is grayed out
- Try to duplicate anything on a read-only samba share: **PASS**: menu item is grayed out
- Duplicate single local file on R/W volume: **PASS**: item is duplicated and named correctly
- Duplicate multiple local files on R/W volume: **PASS**: 3 items are duplicated, named correctly, and selected
- Duplicate single local directory on  R/W volume: **PASS**: item is duplicated and named correctly, but a rename operation is not initiated
- Duplicate multiple local directories on R/W volume: **PASS**: 3 items are duplicated, named correctly, and selected
- Duplicate single file on R/W samba share: **PASS**: item is duplicated and correctly
- Duplicate multiple files on R/W samba share: **PASS**: 3 items are duplicated, named correctly, and selected
- Duplicate single directory on R/W samba share: **PASS**: item is duplicated and named correctly
- Duplicate multiple directory on R/W samba share: **PASS**: 3 items are duplicated, named correctly, and selected
- Try to undo a successful duplication: **PASS**: operation is undone

This is my first attempt at a big change like this and I'm sure it's full of issues. I will accept any and all suggestions for improvement. :)

Reviewers: #dolphin, #kde_applications, elvisangelaccio, dfaure, broulik, davidedmundson

Subscribers: kfm-devel, meven, markg, fazevedo, cfeck, #dolphin

Tags: #dolphin

Differential Revision: https://phabricator.kde.org/D8208

src/dolphincontextmenu.cpp
src/dolphinmainwindow.cpp
src/dolphinui.rc
src/views/dolphinview.cpp
src/views/dolphinview.h
src/views/dolphinviewactionhandler.cpp
src/views/dolphinviewactionhandler.h

index 9f396719971da3170005edd6cc1a5228e938f75a..e80283c58b84b57c9e3b0315938b2bad1b177d8f 100644 (file)
@@ -396,6 +396,7 @@ void DolphinContextMenu::insertDefaultItemActions(const KFileItemListProperties&
     addAction(collection->action(KStandardAction::name(KStandardAction::Cut)));
     addAction(collection->action(KStandardAction::name(KStandardAction::Copy)));
     addAction(createPasteAction());
+    addAction(m_mainWindow->actionCollection()->action(QStringLiteral("duplicate")));
 
     addSeparator();
 
index 642c24e60d30adb3cd289e482d6e25364e6707b3..399901688a81fdda2e99e338a5e198de81940ae8 100644 (file)
@@ -1905,6 +1905,7 @@ void DolphinMainWindow::updateFileAndEditActions()
         QAction* cutAction               = col->action(KStandardAction::name(KStandardAction::Cut));
         QAction* deleteWithTrashShortcut = col->action(QStringLiteral("delete_shortcut")); // see DolphinViewActionHandler
         QAction* showTarget              = col->action(QStringLiteral("show_target"));
+        QAction* duplicateAction         = col->action(QStringLiteral("duplicate")); // see DolphinViewActionHandler
 
         if (list.length() == 1 && list.first().isDir()) {
             addToPlacesAction->setEnabled(true);
@@ -1921,6 +1922,7 @@ void DolphinMainWindow::updateFileAndEditActions()
         deleteWithTrashShortcut->setEnabled(capabilities.supportsDeleting() && !enableMoveToTrash);
         cutAction->setEnabled(capabilities.supportsMoving());
         showTarget->setEnabled(list.length() == 1 && list.at(0).isLink());
+        duplicateAction->setEnabled(capabilities.supportsWriting());
     }
 }
 
index e1bb9ee58daa0ad0c8b2f556a1a7358d96a4ce6b..e717b67ae96d9f3cfd1ce48ef8062a544faa13d9 100644 (file)
@@ -1,5 +1,5 @@
 <!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
-<kpartgui name="dolphin" version="29">
+<kpartgui name="dolphin" version="30">
     <MenuBar>
         <Menu name="file">
             <Action name="new_menu" />
@@ -11,6 +11,7 @@
             <Action name="add_to_places" />
             <Separator/>
             <Action name="renamefile" />
+            <Action name="duplicate" />
             <Action name="movetotrash" />
             <Action name="deletefile" />
             <Separator/>
@@ -81,6 +82,7 @@
             <Action name="edit_cut" />
             <Action name="edit_copy" />
             <Action name="renamefile" />
+            <Action name="duplicate" />
             <Action name="movetotrash" />
             <Action name="deletefile" />
             <Action name="invert_selection" />
@@ -91,6 +93,7 @@
             <Action name="edit_cut" />
             <Action name="edit_copy" />
             <Action name="renamefile" />
+            <Action name="duplicate" />
             <Action name="movetotrash" />
             <Action name="deletefile" />
             <Action name="delete_shortcut" />
index cfece0fe6b7f1e0b17349269fe340a888ec36b01..776436032e1e0815e29866febed011ef8b770c35 100644 (file)
@@ -63,6 +63,7 @@
 #include <QDropEvent>
 #include <QGraphicsSceneDragDropEvent>
 #include <QMenu>
+#include <QMimeDatabase>
 #include <QPixmapCache>
 #include <QPointer>
 #include <QScrollBar>
@@ -704,6 +705,50 @@ void DolphinView::pasteIntoFolder()
     }
 }
 
+void DolphinView::duplicateSelectedItems()
+{
+    const KFileItemList itemList = selectedItems();
+    if (itemList.isEmpty()) {
+        return;
+    }
+
+    const QMimeDatabase db;
+
+    // Duplicate all selected items and append "copy" to the end of the file name
+    // but before the filename extension, if present
+    QList<QUrl> newSelection;
+    for (const auto &item : itemList) {
+        const QUrl originalURL  = item.url();
+        const QString originalFileName = item.name();
+        QString extension = db.suffixForFileName(originalFileName);
+
+        QUrl duplicateURL = originalURL;
+
+        // No extension; new filename is "<oldfilename> copy"
+        if (extension.isEmpty()) {
+            duplicateURL.setPath(i18nc("<file path> copy", "%1 copy", originalURL.path()));
+        // There's an extension; new filename is "<oldfilename> copy.<extension>"
+        } else {
+            // Need to add a dot since QMimeDatabase::suffixForFileName() doesn't include it
+            extension = QLatin1String(".") + extension;
+            const QString directoryPath = originalURL.adjusted(QUrl::RemoveFilename).path();
+            const QString originalFilenameWithoutExtension = originalFileName.chopped(extension.size());
+            // Preserve file's original filename extension in case the casing differs
+            // from what QMimeDatabase::suffixForFileName() returned
+            const QString originalExtension = originalFileName.right(extension.size());
+            duplicateURL.setPath(i18nc("<file path><filename> copy.<extension>", "%1%2 copy%3", directoryPath, originalFilenameWithoutExtension, originalExtension));
+        }
+
+        KIO::CopyJob* job = KIO::copyAs(originalURL, duplicateURL, KIO::HideProgressInfo);
+        KJobWidgets::setWindow(job, this);
+
+        if (job) {
+            newSelection << duplicateURL;
+            KIO::FileUndoManager::self()->recordCopyJob(job);
+        }
+    }
+}
+
 void DolphinView::stopLoading()
 {
     m_model->cancelDirectoryLoading();
index 60ecb2a9520c1ad083424a618d3adbc1e9d348aa..83c5f92a4dbdd01040ba28155bf2a8930ce5fb27 100644 (file)
@@ -374,6 +374,12 @@ public slots:
      */
     void pasteIntoFolder();
 
+    /**
+     * Creates duplicates of selected items, appending "copy"
+     * to the end.
+     */
+    void duplicateSelectedItems();
+
     /**
      * Handles a drop of @p dropEvent onto widget @p dropWidget and destination @p destUrl
      */
index ef9f317ee16d20e3399d6e46b41d1bd33d0ef4b2..c61e1aaa9f46df99ef418e4059f83cc77a4e9c4a 100644 (file)
@@ -136,6 +136,13 @@ void DolphinViewActionHandler::createActions()
     deleteWithTrashShortcut->setEnabled(false);
     connect(deleteWithTrashShortcut, &QAction::triggered, this, &DolphinViewActionHandler::slotDeleteItems);
 
+    QAction* duplicateAction = m_actionCollection->addAction(QStringLiteral("duplicate"));
+    duplicateAction->setText(i18nc("@action:inmenu File", "Duplicate Here"));
+    duplicateAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-duplicate")));
+    m_actionCollection->setDefaultShortcut(duplicateAction, Qt::CTRL | Qt::Key_D);
+    duplicateAction->setEnabled(false);
+    connect(duplicateAction, &QAction::triggered, this, &DolphinViewActionHandler::slotDuplicate);
+
     QAction *propertiesAction = m_actionCollection->addAction( QStringLiteral("properties") );
     // Well, it's the File menu in dolphinmainwindow and the Edit menu in dolphinpart... :)
     propertiesAction->setText( i18nc("@action:inmenu File", "Properties") );
@@ -680,6 +687,12 @@ void DolphinViewActionHandler::slotAdjustViewProperties()
     delete dialog;
 }
 
+void DolphinViewActionHandler::slotDuplicate()
+{
+    emit actionBeingHandled();
+    m_currentView->duplicateSelectedItems();
+}
+
 void DolphinViewActionHandler::slotProperties()
 {
     KPropertiesDialog* dialog = nullptr;
index f931b3b9c5556efd38fcba2b034611f4d4718dd5..3228e7364cdc1ff6a72b8bb22daeb1efe4f40acb 100644 (file)
@@ -206,6 +206,11 @@ private Q_SLOTS:
      */
     void slotAdjustViewProperties();
 
+    /**
+     * Begins a duplicate operation on the selected files
+     */
+    void slotDuplicate();
+
     /**
      * Connected to the "properties" action.
      * Opens the properties dialog for the selected items of the