File indexing completed on 2025-02-02 03:49:40
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"