File indexing completed on 2024-07-21 03:41:45

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2006-2010 Peter Penz <peter.penz@gmx.at>
0004     SPDX-FileCopyrightText: 2020 Méven Car <meven.car@kdemail.net>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 
0009 #include "renamefiledialog.h"
0010 
0011 #include <KGuiItem>
0012 #include <KIO/BatchRenameJob>
0013 #include <KIO/CopyJob>
0014 #include <KIO/FileUndoManager>
0015 #include <KJobUiDelegate>
0016 #include <KJobWidgets>
0017 #include <KLocalizedString>
0018 
0019 #include <QDialogButtonBox>
0020 #include <QHBoxLayout>
0021 #include <QLabel>
0022 #include <QLineEdit>
0023 #include <QMimeDatabase>
0024 #include <QPushButton>
0025 #include <QShowEvent>
0026 #include <QSpinBox>
0027 
0028 namespace KIO
0029 {
0030 class Q_DECL_HIDDEN RenameFileDialog::RenameFileDialogPrivate
0031 {
0032 public:
0033     RenameFileDialogPrivate(const KFileItemList &items)
0034         : lineEdit(nullptr)
0035         , items(items)
0036         , spinBox(nullptr)
0037         , renameOneItem(false)
0038         , allExtensionsDifferent(true)
0039     {
0040     }
0041 
0042     QList<QUrl> renamedItems;
0043     QLineEdit *lineEdit;
0044     KFileItemList items;
0045     QSpinBox *spinBox;
0046     QPushButton *okButton;
0047     bool renameOneItem;
0048     bool allExtensionsDifferent;
0049 };
0050 
0051 RenameFileDialog::RenameFileDialog(const KFileItemList &items, QWidget *parent)
0052     : QDialog(parent)
0053     , d(new RenameFileDialogPrivate(items))
0054 {
0055     setMinimumWidth(320);
0056 
0057     const int itemCount = items.count();
0058     Q_ASSERT(itemCount >= 1);
0059     d->renameOneItem = (itemCount == 1);
0060 
0061     setWindowTitle(d->renameOneItem ? i18nc("@title:window", "Rename Item") : i18nc("@title:window", "Rename Items"));
0062     QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0063     QVBoxLayout *mainLayout = new QVBoxLayout(this);
0064     d->okButton = buttonBox->button(QDialogButtonBox::Ok);
0065     d->okButton->setDefault(true);
0066     d->okButton->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Return));
0067     connect(buttonBox, &QDialogButtonBox::accepted, this, &RenameFileDialog::slotAccepted);
0068     connect(buttonBox, &QDialogButtonBox::rejected, this, &RenameFileDialog::reject);
0069     connect(buttonBox, &QDialogButtonBox::rejected, this, &QObject::deleteLater);
0070     d->okButton->setDefault(true);
0071 
0072     KGuiItem::assign(d->okButton, KGuiItem(i18nc("@action:button", "&Rename"), QStringLiteral("dialog-ok-apply")));
0073 
0074     QWidget *page = new QWidget(this);
0075     mainLayout->addWidget(page);
0076     mainLayout->addWidget(buttonBox);
0077 
0078     QVBoxLayout *topLayout = new QVBoxLayout(page);
0079 
0080     QLabel *editLabel = nullptr;
0081     QString newName;
0082     if (d->renameOneItem) {
0083         newName = items.first().name();
0084         editLabel = new QLabel(xi18nc("@label:textbox", "Rename the item <filename>%1</filename> to:", newName), page);
0085         editLabel->setTextFormat(Qt::PlainText);
0086     } else {
0087         newName = i18nc("This a template for new filenames, # is replaced by a number later, must be the end character", "New name #");
0088         editLabel = new QLabel(i18ncp("@label:textbox", "Rename the %1 selected item to:", "Rename the %1 selected items to:", itemCount), page);
0089     }
0090 
0091     d->lineEdit = new QLineEdit(page);
0092     mainLayout->addWidget(d->lineEdit);
0093     connect(d->lineEdit, &QLineEdit::textChanged, this, &RenameFileDialog::slotTextChanged);
0094 
0095     int selectionLength = newName.length();
0096     if (d->renameOneItem) {
0097         // If the current item is a directory, select the whole file name.
0098         if (!items.first().isDir()) {
0099             QMimeDatabase db;
0100             const QString extension = db.suffixForFileName(items.first().name());
0101             if (extension.length() > 0) {
0102                 // Don't select the extension
0103                 selectionLength -= extension.length() + 1;
0104             }
0105         }
0106     } else {
0107         // Don't select the # character
0108         --selectionLength;
0109     }
0110 
0111     d->lineEdit->setText(newName);
0112     d->lineEdit->setSelection(0, selectionLength);
0113 
0114     topLayout->addWidget(editLabel);
0115     topLayout->addWidget(d->lineEdit);
0116 
0117     if (!d->renameOneItem) {
0118         QMimeDatabase db;
0119         QSet<QString> extensions;
0120         for (const KFileItem &item : std::as_const(d->items)) {
0121             const QString extension = db.suffixForFileName(item.name());
0122 
0123             if (extensions.contains(extension)) {
0124                 d->allExtensionsDifferent = false;
0125                 break;
0126             }
0127 
0128             extensions.insert(extension);
0129         }
0130 
0131         QLabel *infoLabel = new QLabel(i18nc("@info", "# will be replaced by ascending numbers starting with:"), page);
0132         mainLayout->addWidget(infoLabel);
0133         d->spinBox = new QSpinBox(page);
0134         d->spinBox->setMinimum(0);
0135         d->spinBox->setMaximum(1'000'000'000);
0136         d->spinBox->setSingleStep(1);
0137         d->spinBox->setValue(1);
0138         d->spinBox->setDisplayIntegerBase(10);
0139 
0140         QHBoxLayout *horizontalLayout = new QHBoxLayout;
0141         horizontalLayout->setContentsMargins(0, 0, 0, 0);
0142         horizontalLayout->addWidget(infoLabel);
0143         horizontalLayout->addWidget(d->spinBox);
0144 
0145         topLayout->addLayout(horizontalLayout);
0146     }
0147 
0148     d->lineEdit->setFocus();
0149 }
0150 
0151 RenameFileDialog::~RenameFileDialog()
0152 {
0153 }
0154 
0155 void RenameFileDialog::slotAccepted()
0156 {
0157     QWidget *widget = parentWidget();
0158     if (!widget) {
0159         widget = this;
0160     }
0161 
0162     const QList<QUrl> srcList = d->items.urlList();
0163     const QString newName = d->lineEdit->text();
0164     KIO::FileUndoManager::CommandType cmdType;
0165     KIO::Job *job = nullptr;
0166     if (d->renameOneItem) {
0167         Q_ASSERT(d->items.count() == 1);
0168         cmdType = KIO::FileUndoManager::Rename;
0169         const QUrl oldUrl = d->items.constFirst().url();
0170         QUrl newUrl = oldUrl.adjusted(QUrl::RemoveFilename);
0171         newUrl.setPath(newUrl.path() + KIO::encodeFileName(newName));
0172         d->renamedItems << newUrl;
0173         job = KIO::moveAs(oldUrl, newUrl, KIO::HideProgressInfo);
0174     } else {
0175         d->renamedItems.reserve(d->items.count());
0176         cmdType = KIO::FileUndoManager::BatchRename;
0177         job = KIO::batchRename(srcList, newName, d->spinBox->value(), QLatin1Char('#'));
0178         connect(qobject_cast<KIO::BatchRenameJob *>(job), &KIO::BatchRenameJob::fileRenamed, this, &RenameFileDialog::slotFileRenamed);
0179     }
0180 
0181     KJobWidgets::setWindow(job, widget);
0182     const QUrl parentUrl = srcList.first().adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
0183     KIO::FileUndoManager::self()->recordJob(cmdType, srcList, parentUrl, job);
0184 
0185     connect(job, &KJob::result, this, &RenameFileDialog::slotResult);
0186     connect(job, &KJob::result, this, &QObject::deleteLater);
0187 
0188     accept();
0189 }
0190 
0191 void RenameFileDialog::slotTextChanged(const QString &newName)
0192 {
0193     bool enable = !newName.isEmpty() && (newName != QLatin1String("..")) && (newName != QLatin1String("."));
0194     if (enable && !d->renameOneItem) {
0195         const int count = newName.count(QLatin1Char('#'));
0196         if (count == 0) {
0197             // Renaming multiple files without '#' will only work if all extensions are different.
0198             enable = d->allExtensionsDifferent;
0199         } else {
0200             // Ensure that the new name contains exactly one # (or a connected sequence of #'s)
0201             const int first = newName.indexOf(QLatin1Char('#'));
0202             const int last = newName.lastIndexOf(QLatin1Char('#'));
0203             enable = (last - first + 1 == count);
0204         }
0205     }
0206     d->okButton->setEnabled(enable);
0207 }
0208 
0209 void RenameFileDialog::slotFileRenamed(const QUrl &oldUrl, const QUrl &newUrl)
0210 {
0211     Q_UNUSED(oldUrl)
0212     d->renamedItems << newUrl;
0213 }
0214 
0215 void RenameFileDialog::slotResult(KJob *job)
0216 {
0217     if (!job->error()) {
0218         Q_EMIT renamingFinished(d->renamedItems);
0219     } else {
0220         Q_EMIT error(job);
0221     }
0222 }
0223 
0224 } // namespace KIO
0225 
0226 #include "moc_renamefiledialog.cpp"