Index: src/CMakeLists.txt =================================================================== --- src/CMakeLists.txt +++ src/CMakeLists.txt @@ -101,7 +101,10 @@ views/dolphinview.cpp views/dolphinviewactionhandler.cpp views/draganddrophelper.cpp - views/renamedialog.cpp + views/rename/renamedialog.cpp + views/rename/renamedialogmodel.cpp + views/rename/renamedialogmodel.cpp + views/rename/renamers.cpp views/tooltips/dolphinfilemetadatawidget.cpp views/tooltips/tooltipmanager.cpp views/versioncontrol/updateitemstatesthread.cpp Index: src/panels/folders/folderspanel.cpp =================================================================== --- src/panels/folders/folderspanel.cpp +++ src/panels/folders/folderspanel.cpp @@ -24,7 +24,7 @@ #include "treeviewcontextmenu.h" #include "foldersitemlistwidget.h" -#include +#include #include #include #include Index: src/views/dolphinview.cpp =================================================================== --- src/views/dolphinview.cpp +++ src/views/dolphinview.cpp @@ -64,7 +64,7 @@ #include "dolphin_generalsettings.h" #include "dolphinitemlistview.h" #include "draganddrophelper.h" -#include "renamedialog.h" +#include "rename/renamedialog.h" #include "versioncontrol/versioncontrolobserver.h" #include "viewmodecontroller.h" #include "viewproperties.h" Index: src/views/rename/renamedialog.h =================================================================== --- /dev/null +++ src/views/rename/renamedialog.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * 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 +#include +#include +#include +#include +#include +#include "renamedialogmodel.h" +#include "renamers.h" + +class DOLPHIN_EXPORT RenameDialog : public QDialog +{ + Q_OBJECT + +public: + RenameDialog(QWidget *parent, const KFileItemList &items); + +signals: + void renamingFinished(const QList &urls); + +private: + void btnOkClicked(bool checked = false); + void btnPreviewClicked(bool checked = false); + + void performPreviewRename(RenamerFunction* callback); + void performItemsRename(); + void renameItem(const KFileItem& item, const QString& newName); + + QPushButton* m_btnOk; + + int m_itemsToBeRenamed; + QList m_renamedItems; + + RenameDialogModel *model; + + QStatusBar *m_statusBar; + QTableView *m_previewTable; + + 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; + + QTabWidget *m_tabs; +}; + +#endif \ No newline at end of file Index: src/views/rename/renamedialog.cpp =================================================================== --- /dev/null +++ src/views/rename/renamedialog.cpp @@ -0,0 +1,251 @@ +/*************************************************************************** + * 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 "renamers.h" + +#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(QStringLiteral("Rename Items")); + setMinimumWidth(500); + setLayout(mainLayout); + model = new RenameDialogModel(this, items); + + m_previewTable = new QTableView(); + m_previewTable->setModel(model); + m_previewTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + m_previewTable->verticalHeader()->hide(); + m_previewTable->setColumnHidden(RenameDialogModel::Valid, true); + m_previewTable->setSelectionBehavior(QAbstractItemView::SelectRows); + mainLayout->addWidget(m_previewTable); + + + // Create tab widget + m_tabs = new QTabWidget(this); + + QWidget *tabRenameSequential = new QWidget(m_tabs); + auto layoutRenameSequential = new QFormLayout; + tabRenameSequential->setLayout(layoutRenameSequential); + m_tabs->addTab(tabRenameSequential, QStringLiteral("Sequential")); + + QWidget *tabRenameFindReplace = new QWidget(m_tabs); + m_tabs->addTab(tabRenameFindReplace, QStringLiteral("Find && Replace")); + auto layoutRenameFindReplace = new QFormLayout; + tabRenameFindReplace->setLayout(layoutRenameFindReplace); + + QWidget *tabNewNameFromMatches = new QWidget(m_tabs); + m_tabs->addTab(tabNewNameFromMatches, QStringLiteral("New name from matches")); + auto layoutNewNameFromMatches = new QFormLayout; + tabNewNameFromMatches->setLayout(layoutNewNameFromMatches); + + mainLayout->addWidget(m_tabs); + + // Rename Sequential + m_macroReplaceEdit = new QLineEdit(this); + m_macroReplaceEdit->setText(QStringLiteral("New Name #")); + + layoutRenameSequential->addWidget(new QLabel(QStringLiteral("Rename selected items to:"), this)); + layoutRenameSequential->addWidget(m_macroReplaceEdit); + + + m_IdStartFrom = new QSpinBox(this); + m_IdStartFrom->setRange(1, 1000000); + + layoutRenameSequential->addWidget(new QLabel(QStringLiteral("# will be replaced by integers starting from:"), this)); + layoutRenameSequential->addWidget(m_IdStartFrom); + + m_IdPaddingLength = new QSpinBox(this); + m_IdPaddingLength->setRange(1, 1000000); + + layoutRenameSequential->addWidget(new QLabel(QStringLiteral("Left-pad # using a length of:"), this)); + layoutRenameSequential->addWidget(m_IdPaddingLength); + + layoutRenameSequential->addItem(new QSpacerItem(0, 10)); + layoutRenameSequential->addWidget(new QLabel(QStringLiteral("Other variables available: #name, #bname, #ext"))); + + // Find & Replace tab + m_findEdit = new QLineEdit(this); + m_replaceEdit = new QLineEdit(this); + m_useRegex = new QCheckBox(QStringLiteral("Use Regular Expressions"), this); + m_caseInsensitive = new QCheckBox(QStringLiteral("Case insensitive"), this); + + layoutRenameFindReplace->addWidget(new QLabel(QStringLiteral("Find:"), this)); + layoutRenameFindReplace->addWidget(m_findEdit); + + layoutRenameFindReplace->addWidget(new QLabel(QStringLiteral("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 = new QLineEdit(this); + m_newNameFromMatches = new QLineEdit(this); + + layoutNewNameFromMatches->addWidget(new QLabel(QStringLiteral("Find patterns:"), this)); + layoutNewNameFromMatches->addWidget(m_patternFind); + + layoutNewNameFromMatches->addWidget(new QLabel(QStringLiteral("New name:"), this)); + layoutNewNameFromMatches->addWidget(m_newNameFromMatches); + + + // Button Bar + auto *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Close); + m_btnOk = buttonBox->button(QDialogButtonBox::Ok); + auto *btPreview = buttonBox->button(QDialogButtonBox::Apply); + + KGuiItem::assign(btPreview, KGuiItem(QStringLiteral("Preview"))); + KGuiItem::assign(m_btnOk, KGuiItem(QStringLiteral("Rename"), QStringLiteral("dialog-ok-apply"))); + + m_btnOk->setEnabled(false); + connect(m_btnOk, &QPushButton::clicked, this, &RenameDialog::btnOkClicked); + connect(btPreview, &QPushButton::clicked, this, &RenameDialog::btnPreviewClicked); + connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameDialog::reject); + + mainLayout->addWidget(buttonBox); + + m_statusBar = new QStatusBar(); + mainLayout->addWidget(m_statusBar); +} + +void RenameDialog::performPreviewRename(RenamerFunction* callback) +{ + // CONSIDER: + // 1. A file and a folder can have the same name. + // 2. A file can be eventually renamed to a name that is in the queue and + // has not been renamed to something else yet, thus overwriting the file. Perhaps better handled by KIO? + + QStringList generatedNames; + QHash params; + bool allItemsOk = true; + m_itemsToBeRenamed = 0; + + m_statusBar->showMessage(QStringLiteral("")); + + 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(); + + params[QStringLiteral("name")] = itemName; + params[QStringLiteral("baseName")] = fileGetBaseName(itemName); + params[QStringLiteral("extension")] = fileGetExt(itemName); + params[QStringLiteral("macroReplace.sequence")] = i + m_IdStartFrom->value(); + + const QString newName = callback(params); + + bool exists = (QFile::exists(newName) || generatedNames.contains(newName)); + bool valid = (newName == itemName) || !(newName.isEmpty() || exists); + allItemsOk &= valid; + generatedNames.append(newName); + + if (newName != itemName) { + m_itemsToBeRenamed++; + } + + model->setData(model->index(i, RenameDialogModel::Valid), valid, Qt::DisplayRole); + model->setData(model->index(i, RenameDialogModel::NewName), newName, Qt::DisplayRole); + } + + m_btnOk->setEnabled(allItemsOk && m_itemsToBeRenamed > 0); + m_previewTable->repaint(); + + if (!allItemsOk) { + m_statusBar->showMessage(QStringLiteral("Renaming cannot continue while there are file name clashes")); + } + + if (m_itemsToBeRenamed == 0) { + m_statusBar->showMessage(QStringLiteral("No items to be renamed.")); + } +} + +void RenameDialog::renameItem(const KFileItem& item, const QString& newName) +{ + 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 (int i = 0; i < model->rowCount(); i++) { + QString name = model->data(model->index(i, RenameDialogModel::Name), Qt::DisplayRole).toString(); + QString newName = model->data(model->index(i, RenameDialogModel::NewName), Qt::DisplayRole).toString(); + + if(name != newName) { + renameItem(model->fileItem(i), newName); + } + } +} + +void RenameDialog::btnPreviewClicked(bool checked) +{ + Q_UNUSED(checked); + + switch(m_tabs->currentIndex()) { + case 1: performPreviewRename(findReplaceRenamer); break; + case 2: performPreviewRename(newNameBuilder); break; + + default: performPreviewRename(macroRenamer); + } +} + +void RenameDialog::btnOkClicked(bool checked) +{ + Q_UNUSED(checked); + + performItemsRename(); + emit renamingFinished(m_renamedItems); + accept(); +} + Index: src/views/rename/renamedialogmodel.h =================================================================== --- /dev/null +++ src/views/rename/renamedialogmodel.h @@ -0,0 +1,63 @@ +/*************************************************************************** + * 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; +}; + +#endif //DOLPHIN_RENAMEDIALOGMODEL_H Index: src/views/rename/renamedialogmodel.cpp =================================================================== --- /dev/null +++ src/views/rename/renamedialogmodel.cpp @@ -0,0 +1,90 @@ +/*************************************************************************** + * 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 "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::data(const QModelIndex &index, int role) const +{ + const auto item = itemData->at(index.row()); + + if (role == Qt::DecorationRole && index.column() == RenameDialogModel::NewName && item.newName.length() > 0) { + 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() ? QStringLiteral("(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; +} Index: src/views/rename/renamers.h =================================================================== --- /dev/null +++ src/views/rename/renamers.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * 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_RENAMERS_H +#define DOLPHIN_RENAMERS_H + +#include +#include + +typedef QString RenamerFunction(const QHash&); + +QString fileGetExt(const QString& file); +QString fileGetBaseName(const QString& file); + +QString macroRenamer(const QHash& params); +QString findReplaceRenamer(const QHash& params); +QString newNameBuilder(const QHash& params); + +#endif //DOLPHIN_RENAMERS_H Index: src/views/rename/renamers.cpp =================================================================== --- /dev/null +++ src/views/rename/renamers.cpp @@ -0,0 +1,104 @@ +/*************************************************************************** + * 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 +#include + +QString macroRenamer(const QHash& params) +{ + QString sequenceFormatted = params[QStringLiteral("macroReplace.sequence")] + .toString().rightJustified(params["macroReplace.padding"].toInt(), QLatin1Char('0')); + const QDateTime timestamp = params["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 findReplaceRenamer(const QHash& params) +{ + const QString find = params[QStringLiteral("findReplace.find")].toString(); + const QString replace = params[QStringLiteral("findReplace.replace")].toString(); + + if (!params[QStringLiteral("findReplace.useRegex")].toBool()) { + return params[QStringLiteral("name")].toString().replace(find, replace); + } + + auto opt = params[QStringLiteral("findReplace.caseInsensitive")].toBool() + ? QRegularExpression::CaseInsensitiveOption + : QRegularExpression::NoPatternOption; + return params[QStringLiteral("name")].toString().replace(QRegularExpression(find, opt), replace); +} + +QString newNameBuilder(const QHash& params) +{ + 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()); + + while (match.hasNext()) { + auto m = match.next(); + auto num = m.captured(2); + + replace = replace.replace(QStringLiteral("\\") + num, findMatches.captured(num.toInt())); + } + + return replace; +} + +QString fileGetExt(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 fileGetBaseName(const QString& file) +{ + return file.left(file.length() - fileGetExt(file).length()); +} \ No newline at end of file Index: src/views/renamedialog.h =================================================================== --- src/views/renamedialog.h +++ /dev/null @@ -1,67 +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 * - ***************************************************************************/ - -#ifndef RENAMEDIALOG_H -#define RENAMEDIALOG_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 - -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; - -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; -}; - -#endif Index: src/views/renamedialog.cpp =================================================================== --- src/views/renamedialog.cpp +++ /dev/null @@ -1,217 +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 -#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); -}