diff --git a/src/core/copyjob.cpp b/src/core/copyjob.cpp --- a/src/core/copyjob.cpp +++ b/src/core/copyjob.cpp @@ -148,6 +148,10 @@ , m_bAutoSkipDirs(false) , m_bOverwriteAllFiles(false) , m_bOverwriteAllDirs(false) + , m_bOverwriteAllOldFiles(false) + , m_bOverwriteAllOldDirs(false) + , m_bOverwriteAllNewFiles(false) + , m_bOverwriteAllNewDirs(false) , m_conflictError(0) , m_reportTimer(nullptr) { @@ -202,6 +206,10 @@ bool m_bAutoSkipDirs; bool m_bOverwriteAllFiles; bool m_bOverwriteAllDirs; + bool m_bOverwriteAllOldFiles; + bool m_bOverwriteAllOldDirs; + bool m_bOverwriteAllNewFiles; + bool m_bOverwriteAllNewDirs; int m_conflictError; QTimer *m_reportTimer; @@ -1161,20 +1169,36 @@ const QString existingDest = (*it).uDest.path(); QString newPath; + RenameDialog_Result res; if (m_reportTimer) { m_reportTimer->stop(); } - RenameDialog_Result r = q->uiDelegateExtension()->askFileRename(q, i18n("Folder Already Exists"), - (*it).uSource, - (*it).uDest, - options, newPath, - (*it).size, destsize, - (*it).ctime, destctime, - (*it).mtime, destmtime); + + if (!m_bOverwriteAllOldDirs && !m_bOverwriteAllNewDirs) { + res = q->uiDelegateExtension()->askFileRename(q, i18n("Folder Already Exists"), + (*it).uSource, + (*it).uDest, + options, newPath, + (*it).size, destsize, + (*it).ctime, destctime, + (*it).mtime, destmtime); + + m_bOverwriteAllNewDirs = res == Result_OverwriteNewDest; + m_bOverwriteAllOldDirs = res == Result_OverwriteOldDest; + } + + if (m_bOverwriteAllOldDirs || m_bOverwriteAllNewDirs) { + if (destmtime > (*it).mtime) { + res = (m_bOverwriteAllNewDirs) ? Result_Overwrite : Result_Skip; + } else { + res = (m_bOverwriteAllOldDirs) ? Result_Overwrite : Result_Skip; + } + } + if (m_reportTimer) { m_reportTimer->start(REPORT_TIMEOUT); } - switch (r) { + switch (res) { case Result_Cancel: q->setError(ERR_USER_CANCELED); q->emitResult(); @@ -1397,6 +1421,7 @@ RenameDialog_Result res; QString newPath; + QDateTime destmtime; if (m_reportTimer) { m_reportTimer->stop(); @@ -1408,7 +1433,7 @@ // Its modification time: const UDSEntry entry = static_cast(job)->statResult(); - QDateTime destmtime, destctime; + QDateTime destctime; const KIO::filesize_t destsize = entry.numberValue(KIO::UDSEntry::UDS_SIZE); const QString linkDest = entry.stringValue(KIO::UDSEntry::UDS_LINK_DEST); @@ -1437,15 +1462,27 @@ options = RenameDialog_Options(options | RenameDialog_MultipleItems | RenameDialog_Skip); } - res = q->uiDelegateExtension()->askFileRename(q, !isDir ? - i18n("File Already Exists") : i18n("Already Exists as Folder"), - (*it).uSource, - (*it).uDest, - options, newPath, - (*it).size, destsize, - (*it).ctime, destctime, - (*it).mtime, destmtime); + if (!m_bOverwriteAllOldFiles && !m_bOverwriteAllNewFiles) { + res = q->uiDelegateExtension()->askFileRename(q, !isDir ? + i18n("File Already Exists") : i18n("Already Exists as Folder"), + (*it).uSource, + (*it).uDest, + options, newPath, + (*it).size, destsize, + (*it).ctime, destctime, + (*it).mtime, destmtime); + + m_bOverwriteAllNewFiles = res == Result_OverwriteNewDest; + m_bOverwriteAllOldFiles = res == Result_OverwriteOldDest; + } + if (m_bOverwriteAllOldFiles || m_bOverwriteAllNewFiles) { + if (destmtime > (*it).mtime) { + res = (m_bOverwriteAllNewFiles) ? Result_Overwrite : Result_Skip; + } else { + res = (m_bOverwriteAllOldFiles) ? Result_Overwrite : Result_Skip; + } + } } else { if (job->error() == ERR_USER_CANCELED) { res = Result_Cancel; diff --git a/src/core/jobuidelegateextension.h b/src/core/jobuidelegateextension.h --- a/src/core/jobuidelegateextension.h +++ b/src/core/jobuidelegateextension.h @@ -92,6 +92,8 @@ Result_ResumeAll = 7, Result_AutoRename = 8, Result_Retry = 9, + Result_OverwriteOldDest = 10, ///< Overwrite if destination is older than source. Skip the rest. + Result_OverwriteNewDest = 11, ///< Overwrite if destination is newer than source. Skip the rest. // @deprecated since 5.0, use the RenameDialog_Option enum values R_CANCEL = Result_Cancel, diff --git a/src/widgets/renamedialog.h b/src/widgets/renamedialog.h --- a/src/widgets/renamedialog.h +++ b/src/widgets/renamedialog.h @@ -115,7 +115,7 @@ protected Q_SLOTS: void enableRenameButton(const QString &); private Q_SLOTS: - void applyAllPressed(); + void applyOptionChanged(int); void showSrcIcon(const KFileItem &); void showDestIcon(const KFileItem &); void showSrcPreview(const KFileItem &, const QPixmap &); diff --git a/src/widgets/renamedialog.cpp b/src/widgets/renamedialog.cpp --- a/src/widgets/renamedialog.cpp +++ b/src/widgets/renamedialog.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -101,7 +102,7 @@ bCancel = nullptr; bRename = bSkip = bOverwrite = nullptr; bResume = bSuggestNewName = nullptr; - bApplyAll = nullptr; + m_applyOptions = nullptr; m_pLineEdit = nullptr; m_srcPendingPreview = false; m_destPendingPreview = false; @@ -126,13 +127,34 @@ } } + enum ApplyOptions { + ApplyCurrent, + ApplyAll, + ApplyOldDest, + ApplyNewDest + }; + + ApplyOptions indexToOption(int index) + { + switch (index) { + case 1: + return ApplyAll; + case 2: + return ApplyOldDest; + case 3: + return ApplyNewDest; + default: + return ApplyCurrent; + } + } + QPushButton *bCancel; QPushButton *bRename; QPushButton *bSkip; QPushButton *bOverwrite; QPushButton *bResume; QPushButton *bSuggestNewName; - QCheckBox *bApplyAll; + QComboBox *m_applyOptions; QLineEdit *m_pLineEdit; QUrl src; QUrl dest; @@ -169,10 +191,30 @@ connect(d->bCancel, &QAbstractButton::clicked, this, &RenameDialog::cancelPressed); if (_options & RenameDialog_MultipleItems) { - d->bApplyAll = new QCheckBox(i18n("Appl&y to All"), this); - d->bApplyAll->setToolTip((_options & RenameDialog_IsDirectory) ? i18n("When this is checked the button pressed will be applied to all subsequent folder conflicts for the remainder of the current job.\nUnless you press Skip you will still be prompted in case of a conflict with an existing file in the directory.") - : i18n("When this is checked the button pressed will be applied to all subsequent conflicts for the remainder of the current job.")); - connect(d->bApplyAll, &QAbstractButton::clicked, this, &RenameDialog::applyAllPressed); + d->m_applyOptions = new QComboBox(this); + d->m_applyOptions->addItem(i18n("Apply to Current")); + d->m_applyOptions->addItem(i18n("Apply to All")); + d->m_applyOptions->addItem(i18n("Apply to Older Dest")); + d->m_applyOptions->addItem(i18n("Apply to Newer Dest")); + + auto applyOptionToolTipSuffix = [](int index) -> QString { + switch (index) { + case 1: + return i18n("all subsequent conflicts"); + case 2: + return i18n("all subsequent conflicts with an older version of destination"); + case 3: + return i18n("all subsequent conflicts with a newer version of destination"); + default: + return i18n("current conflict"); + } + }; + for (int i=0; i<=3; i++) { + d->m_applyOptions->setItemData(i, i18n("When this is selected the button pressed will be applied to %1 for the remainder of the current job.", applyOptionToolTipSuffix(i)), Qt::ToolTipRole); + } + + connect(d->m_applyOptions, QOverload::of(&QComboBox::currentIndexChanged), this, &RenameDialog::applyOptionChanged); + d->m_applyOptions->setCurrentIndex(RenameDialogPrivate::ApplyCurrent); } if (!(_options & RenameDialog_NoRename)) { @@ -361,9 +403,9 @@ layout->addStretch(1); - if (d->bApplyAll) { - layout->addWidget(d->bApplyAll); - setTabOrder(d->bApplyAll, d->bCancel); + if (d->m_applyOptions) { + layout->addWidget(d->m_applyOptions); + setTabOrder(d->m_applyOptions, d->bCancel); } if (d->bRename) { @@ -451,7 +493,7 @@ return; } - if (d->bApplyAll && d->bApplyAll->isChecked()) { + if (d->m_applyOptions && d->m_applyOptions->currentIndex() == RenameDialogPrivate::ApplyAll) { done(Result_AutoRename); } else { const QUrl u = newDestUrl(); @@ -486,7 +528,7 @@ void RenameDialog::skipPressed() { - if (d->bApplyAll && d->bApplyAll->isChecked()) { + if (d->m_applyOptions && d->m_applyOptions->currentIndex() == RenameDialogPrivate::ApplyAll) { done(Result_AutoSkip); } else { done(Result_Skip); @@ -500,8 +542,15 @@ void RenameDialog::overwritePressed() { - if (d->bApplyAll && d->bApplyAll->isChecked()) { - done(Result_OverwriteAll); + if (d->m_applyOptions) { + RenameDialogPrivate::ApplyOptions opt = d->indexToOption(d->m_applyOptions->currentIndex()); + if (opt == RenameDialogPrivate::ApplyAll) { + done(Result_OverwriteAll); + } else if (opt == RenameDialogPrivate::ApplyOldDest) { + done(Result_OverwriteOldDest); + } else if (opt == RenameDialogPrivate::ApplyNewDest) { + done(Result_OverwriteNewDest); + } } else { done(Result_Overwrite); } @@ -514,7 +563,7 @@ void RenameDialog::resumePressed() { - if (d->bApplyAll && d->bApplyAll->isChecked()) { + if (d->m_applyOptions && d->m_applyOptions->currentIndex() == RenameDialogPrivate::ApplyAll) { done(Result_ResumeAll); } else { done(Result_Resume); @@ -526,18 +575,24 @@ done(Result_ResumeAll); } -void RenameDialog::applyAllPressed() +void RenameDialog::applyOptionChanged(int index) { - if (d->bApplyAll && d->bApplyAll->isChecked()) { + RenameDialogPrivate::ApplyOptions opt = d->indexToOption(index); + if (opt != RenameDialogPrivate::ApplyCurrent) { d->m_pLineEdit->setText(KIO::decodeFileName(d->dest.fileName())); d->m_pLineEdit->setEnabled(false); + const bool applyAll = opt == RenameDialogPrivate::ApplyAll; if (d->bRename) { - d->bRename->setEnabled(true); + d->bRename->setEnabled(applyAll); } if (d->bSuggestNewName) { - d->bSuggestNewName->setEnabled(false); + d->bSuggestNewName->setEnabled(applyAll); + } + + if (d->bSkip) { + d->bSkip->setEnabled(applyAll); } } else { d->m_pLineEdit->setEnabled(true); @@ -549,6 +604,10 @@ if (d->bSuggestNewName) { d->bSuggestNewName->setEnabled(true); } + + if (d->bSkip) { + d->bSkip->setEnabled(true); + } } }