diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -124,6 +124,7 @@ kfileplacesmodeltest.cpp kfileplacesviewtest.cpp kurlrequestertest.cpp + batchrenametypestest.cpp NAME_PREFIX "kiofilewidgets-" LINK_LIBRARIES KF5::KIOFileWidgets KF5::KIOWidgets KF5::XmlGui KF5::Bookmarks Qt5::Test KF5::I18n ) diff --git a/autotests/batchrenametypestest.h b/autotests/batchrenametypestest.h new file mode 100644 --- /dev/null +++ b/autotests/batchrenametypestest.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * 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 KIO_BATCHRENAMETYPESTEST_H +#define KIO_BATCHRENAMETYPESTEST_H + +#include + +class BatchRenameTypesTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void testMacroReplace_data(); + void testMacroReplace(); + + void testFindReplace_data(); + void testFindReplace(); + + void testCaptureGroups_data(); + void testCaptureGroups(); +}; + +#endif diff --git a/autotests/batchrenametypestest.cpp b/autotests/batchrenametypestest.cpp new file mode 100644 --- /dev/null +++ b/autotests/batchrenametypestest.cpp @@ -0,0 +1,132 @@ +/*************************************************************************** + * 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 +#include + +typedef QHash RenameTypeInput; +Q_DECLARE_METATYPE(RenameTypeInput) + +void BatchRenameTypesTest::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 {index} - (%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"] = "{baseName}_%H%m%S{suffix}"; + 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 BatchRenameTypesTest::testMacroReplace() +{ + QFETCH(RenameTypeInput, input); + QFETCH(QString, expected); + + QCOMPARE(KIO::BatchRenameTypes::macroRenamer(input), expected); +} + +void BatchRenameTypesTest::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 BatchRenameTypesTest::testFindReplace() +{ + QFETCH(RenameTypeInput, input); + QFETCH(QString, expected); + + QCOMPARE(KIO::BatchRenameTypes::findReplaceRenamer(input), expected); +} + +void BatchRenameTypesTest::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 BatchRenameTypesTest::testCaptureGroups() +{ + QFETCH(RenameTypeInput, input); + QFETCH(QString, expected); + + QCOMPARE(KIO::BatchRenameTypes::captureRegexGroups(input), expected); +} + +QTEST_MAIN(BatchRenameTypesTest) + +#include "batchrenametypestest.moc" diff --git a/src/core/batchrenamejob.h b/src/core/batchrenamejob.h --- a/src/core/batchrenamejob.h +++ b/src/core/batchrenamejob.h @@ -23,9 +23,16 @@ #include "kiocore_export.h" #include "job_base.h" +#include + namespace KIO { +struct KioBatchRenameItem { + QUrl src; + QUrl dst; +}; + class BatchRenameJobPrivate; /** @@ -84,6 +91,8 @@ int index, QChar placeHolder, JobFlags flags = DefaultFlags); +KIOCORE_EXPORT BatchRenameJob *batchRenameFiles(const QList &items, JobFlags flags = DefaultFlags); + } #endif diff --git a/src/core/batchrenamejob.cpp b/src/core/batchrenamejob.cpp --- a/src/core/batchrenamejob.cpp +++ b/src/core/batchrenamejob.cpp @@ -1,5 +1,6 @@ /* This file is part of the KDE libraries Copyright (C) 2017 by Chinmoy Ranjan Pradhan + Copyright (C) 2018 by Emirald Mateli This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -32,83 +33,25 @@ class KIO::BatchRenameJobPrivate : public KIO::JobPrivate { public: - BatchRenameJobPrivate(const QList &src, const QString &newName, - int index, QChar placeHolder, JobFlags flags) + BatchRenameJobPrivate(const QList &items, JobFlags flags) : JobPrivate(), - m_srcList(src), - m_newName(newName), - m_index(index), - m_placeHolder(placeHolder), - m_listIterator(m_srcList.constBegin()), - m_allExtensionsDifferent(true), - m_useIndex(true), - m_appendIndex(false), + m_items(items), + m_listIterator(m_items.constBegin()), m_flags(flags) { - // There occur four cases when renaming multiple files, - // 1. All files have different extension and $newName contains a valid placeholder. - // 2. At least two files have same extension and $newName contains a valid placeholder. - // In these two cases the placeholder character will be replaced by an integer($index). - // 3. All files have different extension and new name contains an invalid placeholder - // (this means either $newName doesn't contain the placeholder or the placeholders - // are not in a connected sequence). - // In this case nothing is substituted and all files have the same $newName. - // 4. At least two files have same extension and $newName contains an invalid placeholder. - // In this case $index is appended to $newName. - - - // Check for extensions. - QSet extensions; - QMimeDatabase db; - foreach (const QUrl &url, m_srcList) { - const QString extension = db.suffixForFileName(url.toDisplayString().toLower()); - if (extensions.contains(extension)) { - m_allExtensionsDifferent = false; - break; - } - - extensions.insert(extension); - } - - // Check for exactly one placeholder character or exactly one sequence of placeholders. - int pos = newName.indexOf(placeHolder); - if (pos != -1) { - while (pos < newName.size() && newName.at(pos) == placeHolder) { - pos++; - } - } - const bool validPlaceholder = (newName.indexOf(placeHolder, pos) == -1); - - if (!validPlaceholder) { - if (!m_allExtensionsDifferent) { - m_appendIndex = true; - } else { - m_useIndex = false; - } - } } - QList m_srcList; - QString m_newName; - int m_index; - QChar m_placeHolder; - QList::const_iterator m_listIterator; - bool m_allExtensionsDifferent; - bool m_useIndex; - bool m_appendIndex; - QUrl m_newUrl; // for fileRenamed signal + QList m_items; + QList::const_iterator m_listIterator; const JobFlags m_flags; Q_DECLARE_PUBLIC(BatchRenameJob) void slotStart(); - QString indexedName(const QString& name, int index, QChar placeHolder) const; - - static inline BatchRenameJob *newJob(const QList &src, const QString &newName, - int index, QChar placeHolder, JobFlags flags) + static inline BatchRenameJob *newJob(const QList &items, JobFlags flags) { - BatchRenameJob *job = new BatchRenameJob(*new BatchRenameJobPrivate(src, newName, index, placeHolder, flags)); + BatchRenameJob *job = new BatchRenameJob(*new BatchRenameJobPrivate(items, flags)); job->setUiDelegate(KIO::createDefaultJobUiDelegate()); if (!(flags & HideProgressInfo)) { KIO::getJobTracker()->registerJob(job); @@ -132,53 +75,16 @@ { } -QString BatchRenameJobPrivate::indexedName(const QString& name, int index, QChar placeHolder) const -{ - if (!m_useIndex) { - return name; - } - - QString newName = name; - QString indexString = QString::number(index); - - if (m_appendIndex) { - newName.append(indexString); - return newName; - } - - // Insert leading zeros if necessary - const int minIndexLength = name.count(placeHolder); - indexString.prepend(QString(minIndexLength - indexString.length(), QLatin1Char('0'))); - - // Replace the index placeholders by the indexString - const int placeHolderStart = newName.indexOf(placeHolder); - newName.replace(placeHolderStart, minIndexLength, indexString); - - return newName; -} - void BatchRenameJobPrivate::slotStart() { Q_Q(BatchRenameJob); - if (m_listIterator == m_srcList.constBegin()) { // emit total - q->setTotalAmount(KJob::Files, m_srcList.count()); + if (m_listIterator == m_items.constBegin()) { // emit total + q->setTotalAmount(KJob::Files, m_items.count()); } - if (m_listIterator != m_srcList.constEnd()) { - QString newName = indexedName(m_newName, m_index, m_placeHolder); - const QUrl oldUrl = *m_listIterator; - QMimeDatabase db; - const QString extension = db.suffixForFileName(oldUrl.path().toLower()); - if (!extension.isEmpty()) { - newName.append(QLatin1Char('.')); - newName.append(extension); - } - - m_newUrl = oldUrl.adjusted(QUrl::RemoveFilename); - m_newUrl.setPath(m_newUrl.path() + KIO::encodeFileName(newName)); - - KIO::Job * job = KIO::moveAs(oldUrl, m_newUrl, KIO::HideProgressInfo); + if (m_listIterator != m_items.constEnd()) { + KIO::Job * job = KIO::moveAs(m_listIterator->src, m_listIterator->dst, KIO::HideProgressInfo); job->setParentJob(q); q->addSubjob(job); q->setProcessedAmount(KJob::Files, q->processedAmount(KJob::Files) + 1); @@ -197,17 +103,20 @@ removeSubjob(job); - emit fileRenamed(*d->m_listIterator, d->m_newUrl); + emit fileRenamed(d->m_listIterator->src, d->m_listIterator->dst); ++d->m_listIterator; - ++d->m_index; - emitPercent(d->m_listIterator - d->m_srcList.constBegin(), d->m_srcList.count()); + emitPercent(d->m_listIterator - d->m_items.constBegin(), d->m_items.count()); d->slotStart(); } BatchRenameJob * KIO::batchRename(const QList &src, const QString &newName, int index, QChar placeHolder, KIO::JobFlags flags) { - return BatchRenameJobPrivate::newJob(src, newName, index, placeHolder, flags); + return batchRenameFiles({}, flags); +} + +BatchRenameJob *KIO::batchRenameFiles(const QList &items, JobFlags flags) { + return BatchRenameJobPrivate::newJob(items, flags); } diff --git a/src/widgets/CMakeLists.txt b/src/widgets/CMakeLists.txt --- a/src/widgets/CMakeLists.txt +++ b/src/widgets/CMakeLists.txt @@ -61,7 +61,11 @@ executablefileopendialog.cpp dndpopupmenuplugin.cpp kurifiltersearchprovideractions.cpp -) + rename/batchrenamedialog.cpp + rename/batchrenamedialogmodel.cpp + rename/batchrenametypes.cpp + rename/filenameutils.cpp + rename/batchrenamevar.cpp) if (WIN32) list(APPEND kiowidgets_SRCS krun_win.cpp @@ -145,6 +149,7 @@ KShellCompletion KUrlCompletion KUriFilter + rename/BatchRenameDialog REQUIRED_HEADERS KIOWidgets_HEADERS ) diff --git a/src/widgets/rename/batchrenamedialog.h b/src/widgets/rename/batchrenamedialog.h new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenamedialog.h @@ -0,0 +1,61 @@ +/*************************************************************************** + * 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 BATCHRENAMEDIALOG_H +#define BATCHRENAMEDIALOG_H + +#include "kiowidgets_export.h" +#include + +namespace KIO +{ +class BatchRenameDialogPrivate; + +/** + * @class KIO::BatchRenameDialog batchrenamedialog.h + * + * A KIO dialog that allows the user to perform filename manipulations + * for batch renaming. + * + * @see KIO::BatchRenameJob + * + * @since 5.14 + */ +class KIOWIDGETS_EXPORT BatchRenameDialog: QObject +{ + Q_OBJECT + +public: + BatchRenameDialog(QWidget *parent, const QList &items); + ~BatchRenameDialog() override; + +Q_SIGNALS: + /** + * Emitted once the BatchRenameJob has finished. + * + * @param urls contains the list of the new file names of the files which were successfully renamed + */ + void renamingFinished(const QList &urls); +private: + BatchRenameDialogPrivate *const d; +}; +} + +#endif + diff --git a/src/widgets/rename/batchrenamedialog.cpp b/src/widgets/rename/batchrenamedialog.cpp new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenamedialog.cpp @@ -0,0 +1,462 @@ +/*************************************************************************** + * 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 "batchrenamedialog.h" +#include "batchrenamedialogmodel_p.h" +#include "filenameutils_p.h" +#include "batchrenamevar_p.h" +#include "batchrenametypes_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace KIO +{ + +class BatchRenameDialogPrivate : public QDialog +{ + Q_OBJECT + +public: + BatchRenameDialogPrivate(QWidget *parent, const QList &items); + + +private: + void onEditValueChanged(const QString &value); + void btnOkClicked(); + void updateStatus(const QString &message, KMessageWidget::MessageType messageType = KMessageWidget::Information); + void performPreviewRename(RenamerFunction *callback); + void performItemsRename(); + void updatePreview(); + void createInsertTokenMenu(); + void updateCapturedGroupsLabel(); + void onItemRenamed(const QUrl &oldUrl, const QUrl &newUrl); + void onJobResult(KJob *job); + + QAction *createInsertTokenAction(const QString &text, const QString &token); + QLineEdit *createLineEdit(); + + QList> m_itemsToBeRenamed; + QList m_renamedItems; + + KMessageWidget *m_messageWidget = nullptr; + QTimer *m_timer = nullptr; + QPushButton *m_btnOk = nullptr; + BatchRenameDialogModel *model = nullptr; + QTableView *m_previewTable = nullptr; + QTabWidget *m_tabs = nullptr; + + QMenu *m_insertTokenMenu = nullptr; + QLineEdit *m_macroReplaceEdit = nullptr; + QSpinBox *m_IdStartFrom = nullptr; + QSpinBox *m_IdPaddingLength = nullptr; + + QLineEdit *m_findEdit = nullptr; + QLineEdit *m_replaceEdit = nullptr; + QCheckBox *m_useRegex = nullptr; + QCheckBox *m_caseInsensitive = nullptr; + + QLineEdit *m_patternFind = nullptr; + QLineEdit *m_newNameFromMatches = nullptr; + QLabel *m_capturedGroupsText = nullptr; + +Q_SIGNALS: + void renamingFinished(const QList &urls); +}; + +BatchRenameDialog::BatchRenameDialog(QWidget *parent, const QList &items) + : d(new BatchRenameDialogPrivate(parent, items)) +{ + d->show(); + connect(d, &BatchRenameDialogPrivate::renamingFinished, [this](const QList &urls){ + emit renamingFinished(urls); + }); +} + +BatchRenameDialog::~BatchRenameDialog() +{ + delete d; +} + +BatchRenameDialogPrivate::BatchRenameDialogPrivate(QWidget *parent, const QList &items) + : QDialog(parent) +{ + auto *mainLayout = new QVBoxLayout(this); + setWindowTitle(i18nc("@title:window", "Rename Items")); + setMinimumWidth(500); + + model = new BatchRenameDialogModel(this, items); + + m_timer = new QTimer(this); + m_timer->setSingleShot(true); + m_timer->setInterval(500); + connect(m_timer, &QTimer::timeout, this, &BatchRenameDialogPrivate::updatePreview); + + m_previewTable = new QTableView(this); + m_previewTable->setModel(model); + m_previewTable->horizontalHeader()->setStretchLastSection(true); + m_previewTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + m_previewTable->verticalHeader()->hide(); + m_previewTable->setColumnHidden(BatchRenameDialogModel::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 {index}")); + + 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", "{index} 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::Cancel, this); + 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, &BatchRenameDialogPrivate::btnOkClicked); + connect(buttonBox, &QDialogButtonBox::rejected, this, &BatchRenameDialogPrivate::reject); + + mainLayout->addWidget(buttonBox); + + m_macroReplaceEdit->setFocus(); + updatePreview(); + connect(m_tabs, &QTabWidget::currentChanged, valueChangedIntCallback); +} + +void BatchRenameDialogPrivate::performPreviewRename(RenamerFunction *callback) +{ + QStringList generatedNames; + QHash params; + bool allItemsOk = true; + m_itemsToBeRenamed.clear(); + + params.insert(QStringLiteral("timestamp"), QDateTime::currentDateTime()); + + params.insert(QStringLiteral("macroReplace.text"), m_macroReplaceEdit->text()); + params.insert(QStringLiteral("macroReplace.padding"), m_IdPaddingLength->value()); + + params.insert(QStringLiteral("findReplace.find"), m_findEdit->text()); + params.insert(QStringLiteral("findReplace.replace"), m_replaceEdit->text()); + params.insert(QStringLiteral("findReplace.useRegex"), m_useRegex->isChecked()); + params.insert(QStringLiteral("findReplace.caseInsensitive"), m_caseInsensitive->isChecked()); + + params.insert(QStringLiteral("newName.find"), m_patternFind->text()); + params.insert(QStringLiteral("newName.replace"), m_newNameFromMatches->text()); + + + for (int i = 0; i < model->rowCount(); i++) { + const QString itemName = model->data(model->index(i, BatchRenameDialogModel::Name), Qt::DisplayRole).toString(); + const QUrl item = model->fileItem(i); + const QDir itemDir(item.adjusted(QUrl::RemoveFilename).adjusted(QUrl::RemoveScheme).toString()); + + params.insert(QStringLiteral("name"), itemName); + params.insert(QStringLiteral("baseName"), KIO::FileNameUtils::getName(itemName)); + params.insert(QStringLiteral("extension"), KIO::FileNameUtils::getSuffix(itemName)); + params.insert(QStringLiteral("macroReplace.sequence"), i + m_IdStartFrom->value()); + + const QString newName = KIO::encodeFileName(callback(params)); + + bool exists = newName.isEmpty() || generatedNames.contains(newName) || itemDir.exists(newName); + bool valid = newName == itemName || !exists; + + allItemsOk = allItemsOk && valid; + generatedNames.append(newName); + + if (newName != itemName && !exists) { + m_itemsToBeRenamed.append(QPair(item, newName)); + } + + model->setData(model->index(i, BatchRenameDialogModel::Valid), valid, Qt::DisplayRole); + model->setData(model->index(i, BatchRenameDialogModel::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 BatchRenameDialogPrivate::performItemsRename() +{ + QList list; + list.reserve(m_itemsToBeRenamed.count()); + + for (const auto &pair: qAsConst(m_itemsToBeRenamed)) { + QUrl newName = QUrl::fromLocalFile(pair.first.adjusted(QUrl::RemoveFilename).path() + pair.second); + list.append(KioBatchRenameItem{pair.first, newName}); + } + + BatchRenameJob* job = batchRenameFiles(list); + connect(job, &BatchRenameJob::fileRenamed, this, &BatchRenameDialogPrivate::onItemRenamed); + connect(job, &KJob::result, this, &BatchRenameDialogPrivate::onJobResult); + + job->exec(); +} + +void BatchRenameDialogPrivate::onItemRenamed(const QUrl &oldUrl, const QUrl &newUrl) +{ + Q_UNUSED(oldUrl); + m_renamedItems.append(newUrl); +} + +void BatchRenameDialogPrivate::onJobResult(KJob *job) +{ + Q_UNUSED(job); +// emit renamingFinished(m_renamedItems); +} + +void BatchRenameDialogPrivate::updatePreview() +{ + switch (m_tabs->currentIndex()) { + case 1: + performPreviewRename(findReplaceRenamer); + break; + case 2: + performPreviewRename(captureRegexGroups); + updateCapturedGroupsLabel(); + break; + + default: + performPreviewRename(macroRenamer); + } +} + +void BatchRenameDialogPrivate::btnOkClicked() +{ + performItemsRename(); + emit renamingFinished(m_renamedItems); + accept(); +} + +void BatchRenameDialogPrivate::onEditValueChanged(const QString &value) +{ + Q_UNUSED(value); + + m_timer->start(); +} + +QLineEdit *BatchRenameDialogPrivate::createLineEdit() +{ + auto edit = new QLineEdit(this); + connect(edit, &QLineEdit::textChanged, this, &BatchRenameDialogPrivate::onEditValueChanged); + + return edit; +} + +void BatchRenameDialogPrivate::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 *BatchRenameDialogPrivate::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 BatchRenameDialogPrivate::createInsertTokenMenu() +{ + m_insertTokenMenu = new QMenu(this); + + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Index"), BatchRenameVar::index())); + m_insertTokenMenu->addSeparator(); + + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "File name"), BatchRenameVar::fullName())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Base name"), BatchRenameVar::baseName())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Extension"), BatchRenameVar::suffix())); + m_insertTokenMenu->addSeparator(); + + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Year (4 digits)"), BatchRenameVar::dateYear4Digit())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Year (2 digits)"), BatchRenameVar::dateYear2Digit())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Month"), BatchRenameVar::dateMonth())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Day"), BatchRenameVar::dateDay())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Hour"), BatchRenameVar::dateHour())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Minute"), BatchRenameVar::dateMinute())); + m_insertTokenMenu->addAction(createInsertTokenAction(i18nc("@label", "Second"), BatchRenameVar::dateSecond())); +} + +void BatchRenameDialogPrivate::updateCapturedGroupsLabel() +{ + QString groups; + const auto capturedGroups = firstCapturedGroups(); + + // skip the first captured group. it is always the entire string + for (int i = 1; i < capturedGroups.length(); i++) { + if (capturedGroups[i].isEmpty()) { + 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)); + clearCapturedGroups(); +} +} + +#include "batchrenamedialog.moc" diff --git a/src/widgets/rename/batchrenamedialogmodel.cpp b/src/widgets/rename/batchrenamedialogmodel.cpp new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenamedialogmodel.cpp @@ -0,0 +1,130 @@ +/*************************************************************************** + * 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 "batchrenamedialogmodel_p.h" + +namespace KIO +{ + +BatchRenameDialogModel::BatchRenameDialogModel(QObject *parent, const QList &items) + : QAbstractTableModel(parent) +{ + itemData = new QList; + itemData->reserve(items.count()); + + for (const QUrl &item: items) { + itemData->append(BatchRenameDialogModelData {item, QString(), true}); + } +} + + +int BatchRenameDialogModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return itemData->count(); +} + +int BatchRenameDialogModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return 3; +} + +QVariant BatchRenameDialogModel::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 BatchRenameDialogModel::data(const QModelIndex &index, int role) const +{ + const auto item = itemData->at(index.row()); + + if (role == Qt::DecorationRole && index.column() == BatchRenameDialogModel::NewName) { + return QIcon::fromTheme(item.valid ? QStringLiteral("dialog-ok-apply") : QStringLiteral("dialog-close")); + } + + if (role == Qt::DisplayRole) { + switch (index.column()) { + case BatchRenameDialogModel::Name: + return item.item.fileName(); + + case BatchRenameDialogModel::NewName: + return item.newName.isEmpty() ? i18nc("@label", "(Empty name)") : item.newName; + + case BatchRenameDialogModel::Valid: + return item.valid; + + default: + return QVariant(); + } + } + + return QVariant(); +} + +bool BatchRenameDialogModel::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; +} + +QUrl BatchRenameDialogModel::fileItem(int index) const +{ + return itemData->at(index).item; +} + +BatchRenameDialogModel::~BatchRenameDialogModel() +{ + delete itemData; +} +} diff --git a/src/widgets/rename/batchrenamedialogmodel_p.h b/src/widgets/rename/batchrenamedialogmodel_p.h new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenamedialogmodel_p.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 KIO_BATCHRENAMEDIALOGMODEL +#define KIO_BATCHRENAMEDIALOGMODEL + +#include + +class QUrl; + +namespace KIO +{ + +struct BatchRenameDialogModelData +{ + QUrl item; + QString newName; + bool valid; +}; + + +class BatchRenameDialogModel : public QAbstractTableModel +{ + Q_OBJECT + +private: + QList *itemData; + +public: + enum DataColumn + { + Name = 0, + NewName = 1, + Valid = 2, + }; + + explicit BatchRenameDialogModel(QObject *parent, const QList &items); + ~BatchRenameDialogModel() override; + + 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; + QUrl fileItem(int index) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const override; +}; +} + +#endif diff --git a/src/widgets/rename/batchrenametypes.cpp b/src/widgets/rename/batchrenametypes.cpp new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenametypes.cpp @@ -0,0 +1,108 @@ +/*************************************************************************** + * 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 "batchrenametypes_p.h" +#include "batchrenamevar_p.h" + +#include +#include +#include +#include +#include +#include + +namespace KIO +{ + +QStringList capturedGroups; + + +QString 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[QStringLiteral("macroReplace.text")].toString() + .replace(BatchRenameVar::dateYear4Digit(), timestamp.toString(QStringLiteral("yyyy"))) + .replace(BatchRenameVar::dateYear2Digit(), timestamp.toString(QStringLiteral("yy"))) + .replace(BatchRenameVar::dateMonth(), timestamp.toString(QStringLiteral("MM"))) + .replace(BatchRenameVar::dateDay(), timestamp.toString(QStringLiteral("dd"))) + .replace(BatchRenameVar::dateHour(), timestamp.toString(QStringLiteral("hh"))) + .replace(BatchRenameVar::dateMinute(), timestamp.toString(QStringLiteral("mm"))) + .replace(BatchRenameVar::dateSecond(), timestamp.toString(QStringLiteral("ss"))) + .replace(BatchRenameVar::fullName(), params[QStringLiteral("name")].toString()) + .replace(BatchRenameVar::baseName(), params[QStringLiteral("baseName")].toString()) + .replace(BatchRenameVar::suffix(), params[QStringLiteral("extension")].toString()) + .replace(BatchRenameVar::index(), sequenceFormatted); +} + +QString 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 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 firstCapturedGroups() +{ + return capturedGroups; +} + +void clearCapturedGroups() +{ + capturedGroups.clear(); +} + +} diff --git a/src/widgets/rename/batchrenametypes_p.h b/src/widgets/rename/batchrenametypes_p.h new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenametypes_p.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * 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 KIO_BATCHRENAMETYPES_P_H +#define KIO_BATCHRENAMETYPES_P_H + +#include "kiowidgets_export.h" +#include +#include + +namespace KIO +{ + +typedef QString RenamerFunction(const QHash &); + +QStringList firstCapturedGroups(); +void clearCapturedGroups(); + +QString macroRenamer(const QHash ¶ms); +QString findReplaceRenamer(const QHash ¶ms); +QString captureRegexGroups(const QHash ¶ms); +} + +#endif diff --git a/src/widgets/rename/batchrenamevar.cpp b/src/widgets/rename/batchrenamevar.cpp new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenamevar.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** + * 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 "batchrenamevar_p.h" + +namespace BatchRenameVar +{ + +QString index() +{ + return QStringLiteral("{index}"); +} + +QString baseName() +{ + return QStringLiteral("{baseName}"); +} + + +QString fullName() +{ + return QStringLiteral("{name}"); +} + +QString suffix() +{ + return QStringLiteral("{suffix}"); +} + +QString dateYear4Digit() +{ + return QStringLiteral("%Y"); +} + +QString dateYear2Digit() +{ + return QStringLiteral("%y"); +} + +QString dateMonth() +{ + return QStringLiteral("%M"); +} + +QString dateDay() +{ + return QStringLiteral("%D"); +} + +QString dateHour() +{ + return QStringLiteral("%H"); +} + +QString dateMinute() +{ + return QStringLiteral("%m"); +} + +QString dateSecond() +{ + return QStringLiteral("%S"); +} +} diff --git a/src/widgets/rename/batchrenamevar_p.h b/src/widgets/rename/batchrenamevar_p.h new file mode 100644 --- /dev/null +++ b/src/widgets/rename/batchrenamevar_p.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * 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 KIO_BATCHRENAMEVAR_H +#define KIO_BATCHRENAMEVAR_H + + +#include + +namespace BatchRenameVar +{ + +QString index(); +QString baseName(); +QString fullName(); +QString suffix(); + +QString dateYear4Digit(); +QString dateYear2Digit(); +QString dateMonth(); +QString dateDay(); +QString dateHour(); +QString dateMinute(); +QString dateSecond(); + +} +#endif diff --git a/src/widgets/rename/filenameutils.cpp b/src/widgets/rename/filenameutils.cpp new file mode 100644 --- /dev/null +++ b/src/widgets/rename/filenameutils.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * 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 "filenameutils_p.h" + +namespace KIO +{ +namespace FileNameUtils +{ + +QString getSuffix(const QString &file) +{ + QString extension = QMimeDatabase().suffixForFileName(file); + + if (!extension.isEmpty()) { + return extension.prepend(QLatin1Char('.')); + } + + QString suffix = QFileInfo(file).suffix(); + + if (!suffix.isEmpty()) { + suffix.prepend(QLatin1Char('.')); + } + + return suffix; +} + +QString getName(const QString &file) +{ + return file.left(file.length() - getSuffix(file).length()); +} + +} +} diff --git a/src/widgets/rename/filenameutils_p.h b/src/widgets/rename/filenameutils_p.h new file mode 100644 --- /dev/null +++ b/src/widgets/rename/filenameutils_p.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * 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 KIO_FILENAMEUTILS_P_H +#define KIO_FILENAMEUTILS_P_H + +#include "kiowidgets_export.h" +#include + +namespace KIO +{ +namespace FileNameUtils +{ + +/** + * 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) + */ +QString getSuffix(const QString &file); + +QString getName(const QString &file); + +} +} +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -39,6 +39,7 @@ ksycocaupdatetest udsentrybenchmark kurlnavigatortest_gui + batchrenamedialogtest_gui kprotocolinfo_dumper kfilewidgettest_gui runapplication diff --git a/tests/batchrenamedialogtest_gui.cpp b/tests/batchrenamedialogtest_gui.cpp new file mode 100644 --- /dev/null +++ b/tests/batchrenamedialogtest_gui.cpp @@ -0,0 +1,42 @@ +/*************************************************************************** + * 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 +#include "rename/batchrenamedialog.h" + +int main(int argc, char **argv) +{ + QApplication::setApplicationName(QStringLiteral("batchrenamedialogtest")); + QApplication app(argc, argv); + + QList items = {}; + + for(int i = 1; i <= 101; i++) { + items.append(QUrl::fromLocalFile("/home/aldo/Share/Rename/New Name " + QString::number(i))); + } + + KIO::BatchRenameDialog dlg(nullptr, items); + + return QApplication::exec(); +} + diff --git a/tests/previewtest.cpp b/tests/previewtest.cpp --- a/tests/previewtest.cpp +++ b/tests/previewtest.cpp @@ -1,6 +1,7 @@ #include "previewtest.h" +#include #include #include #include