diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -94,14 +94,17 @@ settings/viewmodes/viewmodesettings.cpp settings/viewpropertiesdialog.cpp settings/viewpropsprogressinfo.cpp + utils/filenameutils.cpp views/dolphinfileitemlistwidget.cpp views/dolphinitemlistview.cpp views/dolphinnewfilemenuobserver.cpp views/dolphinremoteencoding.cpp views/dolphinview.cpp views/dolphinviewactionhandler.cpp views/draganddrophelper.cpp - views/renamedialog.cpp + views/rename/renamedialog.cpp + views/rename/renamedialogmodel.cpp + views/rename/renametypes.cpp views/tooltips/dolphinfilemetadatawidget.cpp views/tooltips/tooltipmanager.cpp views/versioncontrol/updateitemstatesthread.cpp diff --git a/src/panels/folders/folderspanel.cpp b/src/panels/folders/folderspanel.cpp --- a/src/panels/folders/folderspanel.cpp +++ b/src/panels/folders/folderspanel.cpp @@ -30,7 +30,7 @@ #include "kitemviews/kitemlistselectionmanager.h" #include "treeviewcontextmenu.h" #include "views/draganddrophelper.h" -#include "views/renamedialog.h" +#include "views/rename/renamedialog.h" #include #include diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -59,6 +59,8 @@ LINK_LIBRARIES dolphinprivate dolphinstatic Qt5::Test) ecm_add_test(draganddrophelpertest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test) +ecm_add_test(filenameutilstest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test) +ecm_add_test(renametypestest.cpp LINK_LIBRARIES dolphinprivate Qt5::Test) # PlacesItemModelTest ecm_add_test(placesitemmodeltest.cpp diff --git a/src/tests/filenameutilstest.cpp b/src/tests/filenameutilstest.cpp new file mode 100644 --- /dev/null +++ b/src/tests/filenameutilstest.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emirald Mateli * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include "utils/filenameutils.h" + +class FileNameUtilsTest : public QObject +{ + Q_OBJECT + +private slots: + void testGetSuffix_data(); + void testGetSuffix(); + + void testGetName_data(); + void testGetName(); +}; + +void FileNameUtilsTest::testGetSuffix_data() +{ + QTest::addColumn("filename"); + QTest::addColumn("expected"); + + QTest::newRow("Known file type") + << "file.mp3" + << ".mp3"; + + QTest::newRow("Known file type many dots") + << "archive.2018.03.29.tar.gz" + << ".tar.gz"; + + QTest::newRow("Unknown file with many dots") + << "file.custom.ext1" + << ".ext1"; + + QTest::newRow("Empty filename") + << "" + << ""; + + QTest::newRow("dotfile") + << ".dotfile" + << ".dotfile"; +} + +void FileNameUtilsTest::testGetSuffix() +{ + QFETCH(QString, filename); + QFETCH(QString, expected); + + QCOMPARE(FileNameUtils::getSuffix(filename), expected); +} + +void FileNameUtilsTest::testGetName_data() +{ + QTest::addColumn("filename"); + QTest::addColumn("expected"); + + QTest::newRow("Known file type") + << "file.mp3" + << "file"; + + QTest::newRow("Known file type many dots") + << "archive.2018.03.29.tar.gz" + << "archive.2018.03.29"; + + QTest::newRow("Unknown file with many dots") + << "file.custom.ext1" + << "file.custom"; + + QTest::newRow("Empty filename") + << "" + << ""; + + QTest::newRow("dotfile") + << ".dotfile" + << ""; +} + +void FileNameUtilsTest::testGetName() +{ + QFETCH(QString, filename); + QFETCH(QString, expected); + + QCOMPARE(FileNameUtils::getName(filename), expected); +} + +QTEST_MAIN(FileNameUtilsTest) + +#include "filenameutilstest.moc" diff --git a/src/tests/renametypestest.cpp b/src/tests/renametypestest.cpp new file mode 100644 --- /dev/null +++ b/src/tests/renametypestest.cpp @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emirald Mateli * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include +#include +#include + +typedef QHash RenameTypeInput; +Q_DECLARE_METATYPE(RenameTypeInput) + +class RenameTypesTest : public QObject +{ + Q_OBJECT + +private slots: + void testMacroReplace_data(); + void testMacroReplace(); + + void testFindReplace_data(); + void testFindReplace(); + + void testCaptureGroups_data(); + void testCaptureGroups(); +}; + +void RenameTypesTest::testMacroReplace_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + RenameTypeInput params; + params["macroReplace.sequence"] = 15; + params["macroReplace.padding"] = 3; + params["timestamp"] = QDateTime::fromTime_t(1522504691); // 2018-03-31 15:58:11 CET + params["name"] = "file.ext"; + params["baseName"] = "file"; + params["extension"] = ".ext"; + + params["macroReplace.text"] = "NewName # - (%y) %Y%M%D (Renamed from #name)"; + QTest::newRow("Macro replace with sequence, date and name") + << params << "NewName 015 - (18) 20180331 (Renamed from file.ext)"; + + params["macroReplace.text"] = "#bname_%H%m%S#ext"; + QTest::newRow("Macro replace with time, basename and extension") + << params << "file_155811.ext"; + + params["macroReplace.text"] = "hello"; + QTest::newRow("Macro replace with no placeholders") + << params << "hello"; +} + +void RenameTypesTest::testMacroReplace() +{ + QFETCH(RenameTypeInput, input); + QFETCH(QString, expected); + + QCOMPARE(RenameTypes::macroRenamer(input), expected); +} + +void RenameTypesTest::testFindReplace_data() +{ + QTest::addColumn("input"); + QTest::addColumn("expected"); + + RenameTypeInput params; + params["name"] = "Filename_for_find_and_replace.tar.gz"; + params["findReplace.find"] = "find_"; + params["findReplace.replace"] = ""; + params["findReplace.useRegex"] = false; + params["findReplace.caseInsensitive"] = false; + + QTest::newRow("Find & Replace no regex case sensitive") + << params << "Filename_for_and_replace.tar.gz"; + + params["findReplace.find"] = "FIND_"; + QTest::newRow("Find & Replace no regex case sensitive, should not replace anything") + << params << "Filename_for_find_and_replace.tar.gz"; + + params["findReplace.find"] = "FIND_"; + params["findReplace.caseInsensitive"] = true; + QTest::newRow("Find & Replace no regex case insensitive") + << params << "Filename_for_and_replace.tar.gz"; + + params["findReplace.useRegex"] = true; + params["findReplace.caseInsensitive"] = false; + params["findReplace.find"] = "[^a-z]"; + QTest::newRow("Find & Replace regex, case sensitive. Remove all chars except a-z") + << params << "ilenameforfindandreplacetargz"; + + params["findReplace.useRegex"] = true; + params["findReplace.caseInsensitive"] = true; + params["findReplace.find"] = "[^a-z]"; + QTest::newRow("Find & Replace regex, case insensitive. Remove all non alpha chars") + << params << "Filenameforfindandreplacetargz"; +} + +void RenameTypesTest::testFindReplace() +{ + QFETCH(RenameTypeInput, input); + QFETCH(QString, expected); + + QCOMPARE(RenameTypes::findReplaceRenamer(input), expected); +} + +void RenameTypesTest::testCaptureGroups_data() +{ + QTest::addColumn>("input"); + QTest::addColumn("expected"); + + RenameTypeInput params; + params["name"] = "myfavpodcast1x03-Episode Title(192kbps).mp3"; + params["newName.find"] = R"(.+(\d)x(\d+)-(.+?)\()"; + params["newName.replace"] = R"(Favourite Podcast S0\1E\2 - \3.mp3)"; + + QTest::addRow("Capture groups") << params << "Favourite Podcast S01E03 - Episode Title.mp3"; +} + +void RenameTypesTest::testCaptureGroups() +{ + QFETCH(RenameTypeInput, input); + QFETCH(QString, expected); + + QCOMPARE(RenameTypes::captureRegexGroups(input), expected); +} + +QTEST_MAIN(RenameTypesTest) + +#include "renametypestest.moc" diff --git a/src/views/renamedialog.h b/src/utils/filenameutils.h rename from src/views/renamedialog.h rename to src/utils/filenameutils.h --- a/src/views/renamedialog.h +++ b/src/utils/filenameutils.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2006-2010 by Peter Penz (peter.penz@gmx.at) * + * Copyright (C) 2018 by Emirald Mateli * * * * 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 * @@ -14,55 +14,24 @@ * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ -#ifndef RENAMEDIALOG_H -#define RENAMEDIALOG_H +#ifndef FILEUTILS_H +#define FILEUTILS_H #include "dolphin_export.h" - -#include - -#include #include -class QLineEdit; -class QSpinBox; -class QPushButton; -class KJob; -/** - * @brief Dialog for renaming a variable number of files. - */ -class DOLPHIN_EXPORT RenameDialog : public QDialog -{ - Q_OBJECT - +class DOLPHIN_EXPORT FileNameUtils { public: - explicit RenameDialog(QWidget* parent, const KFileItemList& items); - ~RenameDialog() override; - -signals: - void renamingFinished(const QList& urls); - -private slots: - void slotAccepted(); - void slotTextChanged(const QString& newName); - void slotFileRenamed(const QUrl& oldUrl, const QUrl& newUrl); - void slotResult(KJob* job); - -protected: - void showEvent(QShowEvent* event) override; + /** + * Returns the extension of a file. If file is not a known type, it will return only the last suffix. + * (file.unknown.ext will return .ext) + */ + static QString getSuffix(const QString &file); -private: - bool m_renameOneItem; - QList m_renamedItems; - QString m_newName; - QLineEdit* m_lineEdit; - KFileItemList m_items; - bool m_allExtensionsDifferent; - QSpinBox* m_spinBox; - QPushButton* m_okButton; + static QString getName(const QString &file); }; #endif diff --git a/src/views/renamedialog.h b/src/utils/filenameutils.cpp copy from src/views/renamedialog.h copy to src/utils/filenameutils.cpp --- a/src/views/renamedialog.h +++ b/src/utils/filenameutils.cpp @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2006-2010 by Peter Penz (peter.penz@gmx.at) * + * Copyright (C) 2018 by Emirald Mateli * * * * 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 * @@ -14,55 +14,30 @@ * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ -#ifndef RENAMEDIALOG_H -#define RENAMEDIALOG_H - -#include "dolphin_export.h" - -#include - -#include #include +#include +#include +#include "filenameutils.h" -class QLineEdit; -class QSpinBox; -class QPushButton; -class KJob; -/** - * @brief Dialog for renaming a variable number of files. - */ -class DOLPHIN_EXPORT RenameDialog : public QDialog -{ - Q_OBJECT - -public: - explicit RenameDialog(QWidget* parent, const KFileItemList& items); - ~RenameDialog() override; +QString FileNameUtils::getSuffix(const QString &file) { + QString extension = QMimeDatabase().suffixForFileName(file); -signals: - void renamingFinished(const QList& urls); + if (!extension.isEmpty()) { + return extension.prepend(QLatin1Char('.')); + } -private slots: - void slotAccepted(); - void slotTextChanged(const QString& newName); - void slotFileRenamed(const QUrl& oldUrl, const QUrl& newUrl); - void slotResult(KJob* job); + QString suffix = QFileInfo(file).suffix(); -protected: - void showEvent(QShowEvent* event) override; + if (!suffix.isEmpty()) { + suffix.prepend(QLatin1Char('.')); + } -private: - bool m_renameOneItem; - QList m_renamedItems; - QString m_newName; - QLineEdit* m_lineEdit; - KFileItemList m_items; - bool m_allExtensionsDifferent; - QSpinBox* m_spinBox; - QPushButton* m_okButton; -}; + return suffix; +} -#endif +QString FileNameUtils::getName(const QString &file) { + return file.left(file.length() - getSuffix(file).length()); +} diff --git a/src/views/dolphinview.cpp b/src/views/dolphinview.cpp --- a/src/views/dolphinview.cpp +++ b/src/views/dolphinview.cpp @@ -31,7 +31,7 @@ #include "kitemviews/kitemlistcontroller.h" #include "kitemviews/kitemlistheader.h" #include "kitemviews/kitemlistselectionmanager.h" -#include "renamedialog.h" +#include "rename/renamedialog.h" #include "versioncontrol/versioncontrolobserver.h" #include "viewproperties.h" #include "views/tooltips/tooltipmanager.h" diff --git a/src/views/rename/renamedialog.h b/src/views/rename/renamedialog.h new file mode 100644 --- /dev/null +++ b/src/views/rename/renamedialog.h @@ -0,0 +1,88 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emirald Mateli * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef RENAMEDIALOG_H +#define RENAMEDIALOG_H + +#include "dolphin_export.h" +#include "renamedialogmodel.h" +#include "renametypes.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +class DOLPHIN_EXPORT RenameDialog : public QDialog +{ +Q_OBJECT + +public: + RenameDialog(QWidget *parent, const KFileItemList &items); + +signals: + void renamingFinished(const QList &urls); + +private: + void onEditValueChanged(const QString &value); + void btnOkClicked(bool checked = false); + void updateStatus(const QString &message, KMessageWidget::MessageType messageType = KMessageWidget::Information); + void performPreviewRename(RenamerFunction *callback); + void performItemsRename(); + void renameItem(const KFileItem &item, const QString &newName); + void updatePreview(); + void createInsertTokenMenu(); + void updateCapturedGroupsLabel(); + + QAction *createInsertTokenAction(const QString &text, const QString &token); + QLineEdit *createLineEdit(); + + QList> m_itemsToBeRenamed; + QList m_renamedItems; + + KMessageWidget *m_messageWidget; + QTimer *m_timer; + QPushButton *m_btnOk; + RenameDialogModel *model; + QTableView *m_previewTable; + QTabWidget *m_tabs; + + QMenu *m_insertTokenMenu; + QLineEdit *m_macroReplaceEdit; + QSpinBox *m_IdStartFrom; + QSpinBox *m_IdPaddingLength; + + QLineEdit *m_findEdit; + QLineEdit *m_replaceEdit; + QCheckBox *m_useRegex; + QCheckBox *m_caseInsensitive; + + QLineEdit *m_patternFind; + QLineEdit *m_newNameFromMatches; + QLabel *m_capturedGroupsText; +}; + +#endif diff --git a/src/views/rename/renamedialog.cpp b/src/views/rename/renamedialog.cpp new file mode 100644 --- /dev/null +++ b/src/views/rename/renamedialog.cpp @@ -0,0 +1,368 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emirald Mateli * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "renamedialog.h" +#include "utils/filenameutils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +RenameDialog::RenameDialog(QWidget *parent, const KFileItemList &items) : QDialog(parent) +{ + auto *mainLayout = new QVBoxLayout(); + setWindowTitle(i18nc("@title:window", "Rename Items")); + setMinimumWidth(500); + setLayout(mainLayout); + model = new RenameDialogModel(this, items); + + m_timer = new QTimer(this); + m_timer->setSingleShot(true); + m_timer->setInterval(500); + connect(m_timer, &QTimer::timeout, this, &RenameDialog::updatePreview); + + m_previewTable = new QTableView(); + m_previewTable->setModel(model); + m_previewTable->horizontalHeader()->setStretchLastSection(true); + m_previewTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + m_previewTable->verticalHeader()->hide(); + m_previewTable->setColumnHidden(RenameDialogModel::Valid, true); + m_previewTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mainLayout->addWidget(m_previewTable); + + const auto valueChangedIntCallback = [this](int) { onEditValueChanged(QString()); }; + + // Create tab widget + m_tabs = new QTabWidget(this); + + QWidget *tabRenameSequential = new QWidget(m_tabs); + auto layoutRenameSequential = new QVBoxLayout; + layoutRenameSequential->setAlignment(Qt::AlignTop); + tabRenameSequential->setLayout(layoutRenameSequential); + m_tabs->addTab(tabRenameSequential, i18nc("@label", "Rename")); + + QWidget *tabRenameFindReplace = new QWidget(m_tabs); + m_tabs->addTab(tabRenameFindReplace, i18nc("@label", "Find && Replace")); + auto layoutRenameFindReplace = new QVBoxLayout; + layoutRenameFindReplace->setAlignment(Qt::AlignTop); + tabRenameFindReplace->setLayout(layoutRenameFindReplace); + + QWidget *tabNewNameFromMatches = new QWidget(m_tabs); + m_tabs->addTab(tabNewNameFromMatches, i18nc("@label", "Capture groups")); + auto layoutNewNameFromMatches = new QVBoxLayout; + layoutNewNameFromMatches->setAlignment(Qt::AlignTop); + tabNewNameFromMatches->setLayout(layoutNewNameFromMatches); + + mainLayout->addWidget(m_tabs); + + // Rename Sequential + m_macroReplaceEdit = createLineEdit(); + m_macroReplaceEdit->setText(i18nc("@label:button", "New Name #")); + + createInsertTokenMenu(); + auto btnInsertToken = new QPushButton(this); + btnInsertToken->setText(i18nc("@label", "Insert token")); + btnInsertToken->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); + connect(btnInsertToken, &QPushButton::clicked, [this]() { m_insertTokenMenu->exec(QCursor::pos()); }); + + auto layoutEditInsertToken = new QHBoxLayout(this); + layoutEditInsertToken->addWidget(m_macroReplaceEdit); + layoutEditInsertToken->addWidget(btnInsertToken); + + layoutRenameSequential->addWidget(new QLabel(i18ncp("@label:textbox", "Rename the %1 selected item to:", + "Rename the %1 selected items to:", items.count()), this)); + layoutRenameSequential->addLayout(layoutEditInsertToken); + + + m_IdStartFrom = new QSpinBox(this); + m_IdStartFrom->setKeyboardTracking(true); + connect(m_IdStartFrom, static_cast(&QSpinBox::valueChanged), valueChangedIntCallback); + m_IdStartFrom->setRange(1, 1000000); + + layoutRenameSequential->addWidget( + new QLabel(i18nc("@info", "# will be replaced by ascending numbers starting with:"), this)); + layoutRenameSequential->addWidget(m_IdStartFrom); + + m_IdPaddingLength = new QSpinBox(this); + m_IdPaddingLength->setKeyboardTracking(true); + connect(m_IdPaddingLength, static_cast(&QSpinBox::valueChanged), valueChangedIntCallback); + m_IdPaddingLength->setRange(1, 1000000); + + layoutRenameSequential->addWidget(new QLabel(i18nc("@info", "Pad numbers using a length of:"), this)); + layoutRenameSequential->addWidget(m_IdPaddingLength); + + // Find & Replace tab + m_findEdit = createLineEdit(); + m_replaceEdit = createLineEdit(); + m_useRegex = new QCheckBox(i18nc("@label", "Use Regular Expressions"), this); + connect(m_useRegex, &QCheckBox::stateChanged, valueChangedIntCallback); + m_caseInsensitive = new QCheckBox(i18nc("@label", "Case insensitive"), this); + connect(m_caseInsensitive, &QCheckBox::stateChanged, valueChangedIntCallback); + + layoutRenameFindReplace->addWidget(new QLabel(i18nc("@label", "Find:"), this)); + layoutRenameFindReplace->addWidget(m_findEdit); + + layoutRenameFindReplace->addWidget(new QLabel(i18nc("@label", "Replace:"), this)); + layoutRenameFindReplace->addWidget(m_replaceEdit); + + layoutRenameFindReplace->addItem(new QSpacerItem(0, 10)); + layoutRenameFindReplace->addWidget(m_useRegex); + layoutRenameFindReplace->addWidget(m_caseInsensitive); + + // New name from matches + m_patternFind = createLineEdit(); + m_newNameFromMatches = createLineEdit(); + + layoutNewNameFromMatches->addWidget(new QLabel(i18nc("@label", "Find patterns:"), this)); + layoutNewNameFromMatches->addWidget(m_patternFind); + + layoutNewNameFromMatches->addWidget(new QLabel(i18nc("@label", "New name:"), this)); + layoutNewNameFromMatches->addWidget(m_newNameFromMatches); + + m_capturedGroupsText = new QLabel(this); + m_capturedGroupsText->setWordWrap(true); + layoutNewNameFromMatches->addWidget(m_capturedGroupsText); + + m_messageWidget = new KMessageWidget(this); + m_messageWidget->setCloseButtonVisible(false); // hiding,showing of the message will be handled by this form. + m_messageWidget->hide(); + mainLayout->addWidget(m_messageWidget); + + // Button Bar + auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Close | QDialogButtonBox::Help); + m_btnOk = buttonBox->button(QDialogButtonBox::Ok); + + KGuiItem::assign(m_btnOk, KGuiItem(i18nc("@action:button", "&Rename"), QStringLiteral("dialog-ok-apply"))); + + m_btnOk->setEnabled(false); + connect(m_btnOk, &QPushButton::clicked, this, &RenameDialog::btnOkClicked); + connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameDialog::reject); + + mainLayout->addWidget(buttonBox); + + m_macroReplaceEdit->setFocus(); + updatePreview(); + connect(m_tabs, &QTabWidget::currentChanged, valueChangedIntCallback); +} + +void RenameDialog::performPreviewRename(RenamerFunction *callback) +{ + QStringList generatedNames; + QHash params; + bool allItemsOk = true; + m_itemsToBeRenamed.clear(); + + params[QStringLiteral("timestamp")] = QDateTime::currentDateTime(); + + params[QStringLiteral("macroReplace.text")] = m_macroReplaceEdit->text(); + params[QStringLiteral("macroReplace.padding")] = m_IdPaddingLength->value(); + + params[QStringLiteral("findReplace.find")] = m_findEdit->text(); + params[QStringLiteral("findReplace.replace")] = m_replaceEdit->text(); + params[QStringLiteral("findReplace.useRegex")] = m_useRegex->isChecked(); + params[QStringLiteral("findReplace.caseInsensitive")] = m_caseInsensitive->isChecked(); + + params[QStringLiteral("newName.find")] = m_patternFind->text(); + params[QStringLiteral("newName.replace")] = m_newNameFromMatches->text(); + + + for (int i = 0; i < model->rowCount(); i++) { + const QString itemName = model->data(model->index(i, RenameDialogModel::Name), Qt::DisplayRole).toString(); + const KFileItem itemModel = model->fileItem(i); + const QDir itemDir(itemModel.url().adjusted(QUrl::RemoveFilename).adjusted(QUrl::RemoveScheme).toString()); + + params[QStringLiteral("name")] = itemName; + params[QStringLiteral("baseName")] = FileNameUtils::getName(itemName); + params[QStringLiteral("extension")] = FileNameUtils::getSuffix(itemName); + params[QStringLiteral("macroReplace.sequence")] = i + m_IdStartFrom->value(); + + const QString newName = KIO::encodeFileName(callback(params)); + + bool exists = (itemDir.exists(newName) || generatedNames.contains(newName)); + bool valid = (newName == itemName) || !(newName.isEmpty() || exists); + allItemsOk &= valid; + generatedNames.append(newName); + + if (newName != itemName && !exists) { + m_itemsToBeRenamed.append(QPair(itemModel, newName)); + } + + model->setData(model->index(i, RenameDialogModel::Valid), valid, Qt::DisplayRole); + model->setData(model->index(i, RenameDialogModel::NewName), newName, Qt::DisplayRole); + } + + const int itemsToBeRenamed = m_itemsToBeRenamed.length(); + m_btnOk->setEnabled(allItemsOk && itemsToBeRenamed > 0); + m_previewTable->repaint(); + + if (!allItemsOk) { + updateStatus(i18nc("@info:status", "Renaming cannot continue while there are file name clashes"), + KMessageWidget::Error); + } + + if (itemsToBeRenamed == 0) { + updateStatus(i18nc("@info:status", "No items to be renamed"), KMessageWidget::Information); + } + + if (allItemsOk && itemsToBeRenamed > 0) { + updateStatus(QString()); + } +} + +void RenameDialog::renameItem(const KFileItem &item, const QString &newName) +{ + const QUrl newUrl = QUrl::fromLocalFile(item.url().adjusted(QUrl::RemoveFilename).path() + newName); + + auto *job = KIO::moveAs(item.url(), newUrl, KIO::HideProgressInfo); + KIO::FileUndoManager::self()->recordJob(KIO::FileUndoManager::Rename, {item.url()}, newUrl, job); + + if (!job->error()) { + m_renamedItems << newUrl; + } +} + +void RenameDialog::performItemsRename() +{ + for (const auto &pair: m_itemsToBeRenamed) { + renameItem(pair.first, pair.second); + } +} + +void RenameDialog::updatePreview() +{ + switch (m_tabs->currentIndex()) { + case 1: + performPreviewRename(RenameTypes::findReplaceRenamer); + break; + case 2: + performPreviewRename(RenameTypes::captureRegexGroups); + updateCapturedGroupsLabel(); + break; + + default: + performPreviewRename(RenameTypes::macroRenamer); + } +} + +void RenameDialog::btnOkClicked(bool checked) +{ + Q_UNUSED(checked); + + performItemsRename(); + emit renamingFinished(m_renamedItems); + accept(); +} + +void RenameDialog::onEditValueChanged(const QString &value) +{ + Q_UNUSED(value); + + m_timer->start(); +} + +QLineEdit *RenameDialog::createLineEdit() +{ + auto edit = new QLineEdit(this); + connect(edit, &QLineEdit::textChanged, this, &RenameDialog::onEditValueChanged); + + return edit; +} + +void RenameDialog::updateStatus(const QString &message, KMessageWidget::MessageType messageType) +{ + // no actual status update. + if (m_messageWidget->isVisible() && m_messageWidget->text() == message) { + return; + } + + // if the message is empty, hide the widget + if (message.isEmpty()) { + m_messageWidget->animatedHide(); + + return; + } + + m_messageWidget->setMessageType(messageType); + m_messageWidget->setText(message); + m_messageWidget->animatedShow(); +} + +QAction *RenameDialog::createInsertTokenAction(const QString &text, const QString &token) +{ + auto result = new QAction(text, this); + connect(result, &QAction::triggered, [this, token]() { + m_macroReplaceEdit->setText(m_macroReplaceEdit->text() + token); + }); + + return result; +} + +void RenameDialog::createInsertTokenMenu() +{ + m_insertTokenMenu = new QMenu(this); + + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Sequence Number"), QStringLiteral("#"))); + m_insertTokenMenu->addSeparator(); + + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "FullName"), QStringLiteral("#name"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Base name"), QStringLiteral("#bname"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Extension"), QStringLiteral("#ext"))); + m_insertTokenMenu->addSeparator(); + + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Year (4 digits)"), QStringLiteral("%Y"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Year (2 digits)"), QStringLiteral("%y"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Month"), QStringLiteral("%M"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Day"), QStringLiteral("%D"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Hour"), QStringLiteral("%H"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Minute"), QStringLiteral("%m"))); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Second"), QStringLiteral("%S"))); +} + +void RenameDialog::updateCapturedGroupsLabel() +{ + QString groups; + const auto capturedGroups = RenameTypes::firstCapturedGroups(); + + for (int i = 0; i < capturedGroups.length(); i++) { + if (capturedGroups[i].length() == 0) { + continue; + } + + groups.append(QStringLiteral("\\%1: %2
").arg(i).arg(capturedGroups[i])); + } + + m_capturedGroupsText->setText(QStringLiteral("%1:

%2") + .arg(i18nc("@label", "Captured groups from the first row")).arg(groups)); + RenameTypes::clearCapturedGroups(); +} diff --git a/src/views/rename/renamedialogmodel.h b/src/views/rename/renamedialogmodel.h new file mode 100644 --- /dev/null +++ b/src/views/rename/renamedialogmodel.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emirald Mateli * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#ifndef DOLPHIN_RENAMEDIALOGMODEL_H +#define DOLPHIN_RENAMEDIALOGMODEL_H + +#include +#include "KFileItem" + +struct RenameDialogModelData +{ + KFileItem item; + QString newName; + bool valid; + + RenameDialogModelData(const KFileItem &item, QString newName, bool valid) + { + this->item = item; + this->newName = newName; + this->valid = valid; + } +}; + + +class RenameDialogModel : public QAbstractTableModel +{ +Q_OBJECT +protected: + QList *itemData; + +public: + enum DataColumn + { + Name = 0, + NewName = 1, + Valid = 2, + }; + + explicit RenameDialogModel(QObject *parent, const KFileItemList &items); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + KFileItem fileItem(int index) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; +}; + +#endif //DOLPHIN_RENAMEDIALOGMODEL_H diff --git a/src/views/rename/renamedialogmodel.cpp b/src/views/rename/renamedialogmodel.cpp new file mode 100644 --- /dev/null +++ b/src/views/rename/renamedialogmodel.cpp @@ -0,0 +1,107 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emirald Mateli * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include +#include +#include "renamedialogmodel.h" + +int RenameDialogModel::rowCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + return itemData->count(); +} + +int RenameDialogModel::columnCount(const QModelIndex &parent) const { + Q_UNUSED(parent); + return 3; +} + +QVariant RenameDialogModel::headerData(int section, Qt::Orientation orientation, int role) const { + if (!(role == Qt::DisplayRole && orientation == Qt::Horizontal)) { + return QVariant(); + } + + switch (section) { + case 0: + return i18nc("@label", "Name"); + case 1: + return i18nc("@label", "New name"); + + default: + return QVariant(); + } +} + +QVariant RenameDialogModel::data(const QModelIndex &index, int role) const { + const auto item = itemData->at(index.row()); + + if (role == Qt::DecorationRole && index.column() == RenameDialogModel::NewName) { + return QIcon::fromTheme(item.valid ? QStringLiteral("dialog-ok-apply") : QStringLiteral("dialog-close")); + } + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case RenameDialogModel::Name: + return item.item.url().fileName(); + + case RenameDialogModel::NewName: + return item.newName.isEmpty() ? i18nc("@label", "(Empty name)") : item.newName; + + case RenameDialogModel::Valid: + return item.valid; + + default: + return QVariant(); + } + } + + return QVariant(); +} + +RenameDialogModel::RenameDialogModel(QObject *parent, const KFileItemList &items) : QAbstractTableModel(parent) { + itemData = new QList; + + for (const KFileItem &item: items) { + itemData->append(RenameDialogModelData(item, QString(), true)); + } +} + +bool RenameDialogModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role != Qt::DisplayRole) { + return false; + } + + switch (index.column()) { + case 1: + (*itemData)[index.row()].newName = value.toString(); + break; + case 2: + (*itemData)[index.row()].valid = value.toBool(); + break; + + default: + return false; + } + + emit dataChanged(index, index); + return true; +} + +KFileItem RenameDialogModel::fileItem(int index) const { + return itemData->at(index).item; +} diff --git a/src/views/renamedialog.h b/src/views/rename/renametypes.h rename from src/views/renamedialog.h rename to src/views/rename/renametypes.h --- a/src/views/renamedialog.h +++ b/src/views/rename/renametypes.h @@ -1,5 +1,5 @@ /*************************************************************************** - * Copyright (C) 2006-2010 by Peter Penz (peter.penz@gmx.at) * + * Copyright (C) 2018 by Emirald Mateli * * * * 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 * @@ -14,55 +14,30 @@ * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * ***************************************************************************/ -#ifndef RENAMEDIALOG_H -#define RENAMEDIALOG_H +#ifndef DOLPHIN_RENAMETYPES +#define DOLPHIN_RENAMETYPES #include "dolphin_export.h" - -#include - -#include #include +#include -class QLineEdit; -class QSpinBox; -class QPushButton; -class KJob; -/** - * @brief Dialog for renaming a variable number of files. - */ -class DOLPHIN_EXPORT RenameDialog : public QDialog -{ - Q_OBJECT +typedef QString RenamerFunction(const QHash &); +class DOLPHIN_EXPORT RenameTypes +{ +private: + static QStringList capturedGroups; public: - explicit RenameDialog(QWidget* parent, const KFileItemList& items); - ~RenameDialog() override; + static QStringList firstCapturedGroups(); + static void clearCapturedGroups(); -signals: - void renamingFinished(const QList& urls); - -private slots: - void slotAccepted(); - void slotTextChanged(const QString& newName); - void slotFileRenamed(const QUrl& oldUrl, const QUrl& newUrl); - void slotResult(KJob* job); - -protected: - void showEvent(QShowEvent* event) override; - -private: - bool m_renameOneItem; - QList m_renamedItems; - QString m_newName; - QLineEdit* m_lineEdit; - KFileItemList m_items; - bool m_allExtensionsDifferent; - QSpinBox* m_spinBox; - QPushButton* m_okButton; + static QString macroRenamer(const QHash ¶ms); + static QString findReplaceRenamer(const QHash ¶ms); + static QString captureRegexGroups(const QHash ¶ms); }; + #endif diff --git a/src/views/rename/renametypes.cpp b/src/views/rename/renametypes.cpp new file mode 100644 --- /dev/null +++ b/src/views/rename/renametypes.cpp @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2018 by Emirald Mateli * + * * + * 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * + ***************************************************************************/ + +#include "renametypes.h" + +#include +#include +#include +#include +#include +#include +#include + + +QString RenameTypes::macroRenamer(const QHash ¶ms) +{ + QString sequenceFormatted = params[QStringLiteral("macroReplace.sequence")] + .toString().rightJustified(params[QStringLiteral("macroReplace.padding")].toInt(), QLatin1Char('0')); + const QDateTime timestamp = params[QStringLiteral("timestamp")].toDateTime(); + + return params["macroReplace.text"].toString() + .replace(QStringLiteral("%Y"), timestamp.toString(QStringLiteral("yyyy"))) + .replace(QStringLiteral("%y"), timestamp.toString(QStringLiteral("yy"))) + .replace(QStringLiteral("%M"), timestamp.toString(QStringLiteral("MM"))) + .replace(QStringLiteral("%D"), timestamp.toString(QStringLiteral("dd"))) + .replace(QStringLiteral("%H"), timestamp.toString(QStringLiteral("hh"))) + .replace(QStringLiteral("%m"), timestamp.toString(QStringLiteral("mm"))) + .replace(QStringLiteral("%S"), timestamp.toString(QStringLiteral("ss"))) + .replace(QStringLiteral("#name"), params[QStringLiteral("name")].toString()) + .replace(QStringLiteral("#bname"), params[QStringLiteral("baseName")].toString()) + .replace(QStringLiteral("#ext"), params[QStringLiteral("extension")].toString()) + .replace(QStringLiteral("#"), sequenceFormatted); +} + +QString RenameTypes::findReplaceRenamer(const QHash ¶ms) +{ + const QString find = params[QStringLiteral("findReplace.find")].toString(); + const QString replace = params[QStringLiteral("findReplace.replace")].toString(); + const bool caseInsensitive = params[QStringLiteral("findReplace.caseInsensitive")].toBool(); + + if (!params[QStringLiteral("findReplace.useRegex")].toBool()) { + return params[QStringLiteral("name")].toString().replace( + find, replace, caseInsensitive ? Qt::CaseInsensitive : Qt::CaseSensitive); + } + + auto opt = caseInsensitive + ? QRegularExpression::CaseInsensitiveOption + : QRegularExpression::NoPatternOption; + return params[QStringLiteral("name")].toString().replace(QRegularExpression(find, opt), replace); +} + +QString RenameTypes::captureRegexGroups(const QHash ¶ms) +{ + const auto find = params[QStringLiteral("newName.find")].toString(); + auto replace = params[QStringLiteral("newName.replace")].toString(); + + QRegularExpression re(QStringLiteral("(\\\\(\\d))")); + QRegularExpressionMatchIterator match = re.globalMatch(replace); + + QRegularExpression exp(find); + QRegularExpressionMatch findMatches = exp.match(params[QStringLiteral("name")].toString()); + + if (capturedGroups.isEmpty()) { + capturedGroups = findMatches.capturedTexts(); + } + + while (match.hasNext()) { + auto m = match.next(); + auto num = m.captured(2); + + replace = replace.replace(QStringLiteral("\\") + num, findMatches.captured(num.toInt())); + } + + return replace; +} + +QStringList RenameTypes::firstCapturedGroups() +{ + return capturedGroups; +} + +void RenameTypes::clearCapturedGroups() +{ + capturedGroups.clear(); +} + +QStringList RenameTypes::capturedGroups; \ No newline at end of file diff --git a/src/views/renamedialog.cpp b/src/views/renamedialog.cpp deleted file mode 100644 --- a/src/views/renamedialog.cpp +++ /dev/null @@ -1,216 +0,0 @@ -/*************************************************************************** - * Copyright (C) 2006-2010 by Peter Penz (peter.penz@gmx.at) * - * * - * 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. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * - ***************************************************************************/ - -#include "renamedialog.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -RenameDialog::RenameDialog(QWidget *parent, const KFileItemList& items) : - QDialog(parent), - m_renameOneItem(false), - m_newName(), - m_lineEdit(nullptr), - m_items(items), - m_allExtensionsDifferent(true), - m_spinBox(nullptr) -{ - const QSize minSize = minimumSize(); - setMinimumSize(QSize(320, minSize.height())); - - const int itemCount = items.count(); - Q_ASSERT(itemCount >= 1); - m_renameOneItem = (itemCount == 1); - - setWindowTitle(m_renameOneItem ? - i18nc("@title:window", "Rename Item") : - i18nc("@title:window", "Rename Items")); - QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); - QVBoxLayout *mainLayout = new QVBoxLayout; - setLayout(mainLayout); - m_okButton = buttonBox->button(QDialogButtonBox::Ok); - m_okButton->setDefault(true); - m_okButton->setShortcut(Qt::CTRL + Qt::Key_Return); - connect(buttonBox, &QDialogButtonBox::accepted, this, &RenameDialog::slotAccepted); - connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameDialog::reject); - m_okButton->setDefault(true); - - KGuiItem::assign(m_okButton, KGuiItem(i18nc("@action:button", "&Rename"), QStringLiteral("dialog-ok-apply"))); - - QWidget* page = new QWidget(this); - mainLayout->addWidget(page); - mainLayout->addWidget(buttonBox); - - QVBoxLayout* topLayout = new QVBoxLayout(page); - - QLabel* editLabel = nullptr; - if (m_renameOneItem) { - m_newName = items.first().name(); - editLabel = new QLabel(xi18nc("@label:textbox", "Rename the item %1 to:", m_newName), - page); - editLabel->setTextFormat(Qt::PlainText); - } else { - m_newName = i18nc("@info:status", "New name #"); - editLabel = new QLabel(i18ncp("@label:textbox", - "Rename the %1 selected item to:", - "Rename the %1 selected items to:", itemCount), - page); - } - - m_lineEdit = new QLineEdit(page); - mainLayout->addWidget(m_lineEdit); - connect(m_lineEdit, &QLineEdit::textChanged, this, &RenameDialog::slotTextChanged); - - int selectionLength = m_newName.length(); - if (m_renameOneItem) { - const QString fileName = items.first().url().toDisplayString(); - QMimeDatabase db; - const QString extension = db.suffixForFileName(fileName.toLower()); - - // If the current item is a directory, select the whole file name. - if ((extension.length() > 0) && !items.first().isDir()) { - // Don't select the extension - selectionLength -= extension.length() + 1; - } - } else { - // Don't select the # character - --selectionLength; - } - - m_lineEdit->setText(m_newName); - m_lineEdit->setSelection(0, selectionLength); - - topLayout->addWidget(editLabel); - topLayout->addWidget(m_lineEdit); - - if (!m_renameOneItem) { - QSet extensions; - foreach (const KFileItem& item, m_items) { - QMimeDatabase db; - const QString extension = db.suffixForFileName(item.url().toDisplayString().toLower()); - - if (extensions.contains(extension)) { - m_allExtensionsDifferent = false; - break; - } - - extensions.insert(extension); - } - - QLabel* infoLabel = new QLabel(i18nc("@info", "# will be replaced by ascending numbers starting with:"), page); - mainLayout->addWidget(infoLabel); - m_spinBox = new QSpinBox(page); - m_spinBox->setMaximum(10000); - m_spinBox->setMinimum(0); - m_spinBox->setSingleStep(1); - m_spinBox->setValue(1); - m_spinBox->setDisplayIntegerBase(10); - - QHBoxLayout* horizontalLayout = new QHBoxLayout(page); - horizontalLayout->setMargin(0); - horizontalLayout->addWidget(infoLabel); - horizontalLayout->addWidget(m_spinBox); - - topLayout->addLayout(horizontalLayout); - } -} - -RenameDialog::~RenameDialog() -{ -} - -void RenameDialog::slotAccepted() -{ - QWidget* widget = parentWidget(); - if (!widget) { - widget = this; - } - - KIO::FileUndoManager::CommandType cmdType; - if (m_renameOneItem) { - Q_ASSERT(m_items.count() == 1); - cmdType = KIO::FileUndoManager::Rename; - } else { - cmdType = KIO::FileUndoManager::BatchRename; - } - - const QList srcList = m_items.urlList(); - KIO::BatchRenameJob* job = KIO::batchRename(srcList, m_lineEdit->text(), m_spinBox->value(), QLatin1Char('#')); - KJobWidgets::setWindow(job, widget); - const QUrl parentUrl = srcList.first().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); - KIO::FileUndoManager::self()->recordJob(cmdType, srcList, parentUrl, job); - - connect(job, &KIO::BatchRenameJob::fileRenamed, this, &RenameDialog::slotFileRenamed); - connect(job, &KIO::BatchRenameJob::result, this, &RenameDialog::slotResult); - - job->uiDelegate()->setAutoErrorHandlingEnabled(true); - - accept(); -} - -void RenameDialog::slotTextChanged(const QString& newName) -{ - bool enable = !newName.isEmpty() && (newName != QLatin1String("..")) && (newName != QLatin1String(".")); - if (enable && !m_renameOneItem) { - const int count = newName.count(QLatin1Char('#')); - if (count == 0) { - // Renaming multiple files without '#' will only work if all extensions are different. - enable = m_allExtensionsDifferent; - } else { - // Assure that the new name contains exactly one # (or a connected sequence of #'s) - const int first = newName.indexOf(QLatin1Char('#')); - const int last = newName.lastIndexOf(QLatin1Char('#')); - enable = (last - first + 1 == count); - } - } - m_okButton->setEnabled(enable); -} - -void RenameDialog::slotFileRenamed(const QUrl &oldUrl, const QUrl &newUrl) -{ - Q_UNUSED(oldUrl) - m_renamedItems << newUrl; -} - -void RenameDialog::slotResult(KJob *job) -{ - if (!job->error()) { - emit renamingFinished(m_renamedItems); - } -} - -void RenameDialog::showEvent(QShowEvent* event) -{ - m_lineEdit->setFocus(); - - QDialog::showEvent(event); -}