Add Duplicate feature

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
This commit is contained in:
Nathaniel Graham 2019-12-20 10:07:25 -07:00 committed by Nate Graham
parent 9c3f9c4846
commit 158d12ac37
7 changed files with 76 additions and 1 deletions

View 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();

View 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());
}
}

View 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" />

View 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();

View 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
*/

View 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;

View 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