File indexing completed on 2024-12-01 03:41:20
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org> 0004 SPDX-FileCopyrightText: 1999-2008 David Faure <faure@kde.org> 0005 SPDX-FileCopyrightText: 2001, 2006 Holger Freyther <freyther@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "kio/renamedialog.h" 0011 #include "../utils_p.h" 0012 #include "kio_widgets_debug.h" 0013 #include "kshell.h" 0014 0015 #include <QApplication> 0016 #include <QCheckBox> 0017 #include <QDate> 0018 #include <QLabel> 0019 #include <QLayout> 0020 #include <QLineEdit> 0021 #include <QMenu> 0022 #include <QMimeDatabase> 0023 #include <QPixmap> 0024 #include <QPushButton> 0025 #include <QScreen> 0026 #include <QScrollArea> 0027 #include <QScrollBar> 0028 #include <QToolButton> 0029 0030 #include <KFileUtils> 0031 #include <KGuiItem> 0032 #include <KIconLoader> 0033 #include <KLocalizedString> 0034 #include <KMessageBox> 0035 #include <KSeparator> 0036 #include <KSqueezedTextLabel> 0037 #include <KStandardGuiItem> 0038 #include <KStringHandler> 0039 #include <kfileitem.h> 0040 #include <kio/udsentry.h> 0041 #include <previewjob.h> 0042 0043 using namespace KIO; 0044 0045 static QLabel *createLabel(QWidget *parent, const QString &text, bool containerTitle = false) 0046 { 0047 auto *label = new QLabel(parent); 0048 0049 if (containerTitle) { 0050 QFont font = label->font(); 0051 font.setBold(true); 0052 label->setFont(font); 0053 } 0054 0055 label->setAlignment(Qt::AlignHCenter); 0056 label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); 0057 label->setText(text); 0058 return label; 0059 } 0060 0061 static QLabel *createDateLabel(QWidget *parent, const KFileItem &item) 0062 { 0063 const bool hasDate = item.entry().contains(KIO::UDSEntry::UDS_MODIFICATION_TIME); 0064 const QString text = hasDate ? i18n("Date: %1", item.timeString(KFileItem::ModificationTime)) : QString(); 0065 QLabel *dateLabel = createLabel(parent, text); 0066 dateLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop); 0067 return dateLabel; 0068 } 0069 0070 static QLabel *createSizeLabel(QWidget *parent, const KFileItem &item) 0071 { 0072 const bool hasSize = item.entry().contains(KIO::UDSEntry::UDS_SIZE); 0073 const QString text = hasSize ? i18n("Size: %1", KIO::convertSize(item.size())) : QString(); 0074 QLabel *sizeLabel = createLabel(parent, text); 0075 sizeLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom); 0076 return sizeLabel; 0077 } 0078 0079 static KSqueezedTextLabel *createSqueezedLabel(QWidget *parent, const QString &text) 0080 { 0081 auto *label = new KSqueezedTextLabel(text, parent); 0082 label->setAlignment(Qt::AlignHCenter); 0083 label->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); 0084 return label; 0085 } 0086 0087 enum CompareFilesResult { 0088 Identical, 0089 PartiallyIdentical, 0090 Different, 0091 }; 0092 static CompareFilesResult compareFiles(const QString &filepath, const QString &secondFilePath) 0093 { 0094 const qint64 bufferSize = 4096; // 4kb 0095 QFile f(filepath); 0096 QFile f2(secondFilePath); 0097 const auto fileSize = f.size(); 0098 0099 if (fileSize != f2.size()) { 0100 return CompareFilesResult::Different; 0101 } 0102 if (!f.open(QFile::ReadOnly)) { 0103 qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f.fileName(); 0104 return CompareFilesResult::Different; 0105 } 0106 if (!f2.open(QFile::ReadOnly)) { 0107 f.close(); 0108 qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f2.fileName(); 0109 return CompareFilesResult::Different; 0110 } 0111 0112 QByteArray buffer(bufferSize, 0); 0113 QByteArray buffer2(bufferSize, 0); 0114 0115 auto seekFillBuffer = [bufferSize](qint64 pos, QFile &f, QByteArray &buffer) { 0116 auto ioresult = f.seek(pos); 0117 if (ioresult) { 0118 const int bytesRead = f.read(buffer.data(), bufferSize); 0119 ioresult = bytesRead != -1; 0120 } 0121 if (!ioresult) { 0122 qCWarning(KIO_WIDGETS) << "Could not read file for comparison:" << f.fileName(); 0123 return false; 0124 } 0125 return true; 0126 }; 0127 0128 // compare at the beginning of the files 0129 bool result = seekFillBuffer(0, f, buffer); 0130 result = result && seekFillBuffer(0, f2, buffer2); 0131 result = result && buffer == buffer2; 0132 0133 if (result && fileSize > 2 * bufferSize) { 0134 // compare the contents in the middle of the files 0135 result = seekFillBuffer(fileSize / 2 - bufferSize / 2, f, buffer); 0136 result = result && seekFillBuffer(fileSize / 2 - bufferSize / 2, f2, buffer2); 0137 result = result && buffer == buffer2; 0138 } 0139 0140 if (result && fileSize > bufferSize) { 0141 // compare the contents at the end of the files 0142 result = seekFillBuffer(fileSize - bufferSize, f, buffer); 0143 result = result && seekFillBuffer(fileSize - bufferSize, f2, buffer2); 0144 result = result && buffer == buffer2; 0145 } 0146 0147 if (!result) { 0148 return CompareFilesResult::Different; 0149 } 0150 0151 if (fileSize <= bufferSize * 3) { 0152 // for files smaller than bufferSize * 3, we in fact compared fully the files 0153 return CompareFilesResult::Identical; 0154 } else { 0155 return CompareFilesResult::PartiallyIdentical; 0156 } 0157 } 0158 0159 /** @internal */ 0160 class Q_DECL_HIDDEN RenameDialog::RenameDialogPrivate 0161 { 0162 public: 0163 RenameDialogPrivate() 0164 { 0165 } 0166 0167 void setRenameBoxText(const QString &fileName) 0168 { 0169 // sets the text in file name line edit box, selecting the filename (but not the extension if there is one). 0170 QMimeDatabase db; 0171 const QString extension = db.suffixForFileName(fileName); 0172 m_pLineEdit->setText(fileName); 0173 0174 if (!extension.isEmpty()) { 0175 const int selectionLength = fileName.length() - extension.length() - 1; 0176 m_pLineEdit->setSelection(0, selectionLength); 0177 } else { 0178 m_pLineEdit->selectAll(); 0179 } 0180 } 0181 0182 QPushButton *bCancel = nullptr; 0183 QPushButton *bRename = nullptr; 0184 QPushButton *bSkip = nullptr; 0185 QToolButton *bOverwrite = nullptr; 0186 QAction *bOverwriteWhenOlder = nullptr; 0187 QPushButton *bResume = nullptr; 0188 QPushButton *bSuggestNewName = nullptr; 0189 QCheckBox *bApplyAll = nullptr; 0190 QLineEdit *m_pLineEdit = nullptr; 0191 QUrl src; 0192 QUrl dest; 0193 bool m_srcPendingPreview = false; 0194 bool m_destPendingPreview = false; 0195 QLabel *m_srcPreview = nullptr; 0196 QLabel *m_destPreview = nullptr; 0197 QLabel *m_srcDateLabel = nullptr; 0198 QLabel *m_destDateLabel = nullptr; 0199 KFileItem srcItem; 0200 KFileItem destItem; 0201 }; 0202 0203 RenameDialog::RenameDialog(QWidget *parent, 0204 const QString &title, 0205 const QUrl &_src, 0206 const QUrl &_dest, 0207 RenameDialog_Options _options, 0208 KIO::filesize_t sizeSrc, 0209 KIO::filesize_t sizeDest, 0210 const QDateTime &ctimeSrc, 0211 const QDateTime &ctimeDest, 0212 const QDateTime &mtimeSrc, 0213 const QDateTime &mtimeDest) 0214 : QDialog(parent) 0215 , d(new RenameDialogPrivate) 0216 { 0217 setObjectName(QStringLiteral("KIO::RenameDialog")); 0218 0219 d->src = _src; 0220 d->dest = _dest; 0221 0222 setWindowTitle(title); 0223 0224 d->bCancel = new QPushButton(this); 0225 KGuiItem::assign(d->bCancel, KStandardGuiItem::cancel()); 0226 connect(d->bCancel, &QAbstractButton::clicked, this, &RenameDialog::cancelPressed); 0227 0228 if (_options & RenameDialog_MultipleItems) { 0229 d->bApplyAll = new QCheckBox(i18n("Appl&y to All"), this); 0230 d->bApplyAll->setToolTip((_options & RenameDialog_DestIsDirectory) ? i18n("When this is checked the button pressed will be applied to all " 0231 "subsequent folder conflicts for the remainder of the current job.\n" 0232 "Unless you press Skip you will still be prompted in case of a " 0233 "conflict with an existing file in the directory.") 0234 : i18n("When this is checked the button pressed will be applied to " 0235 "all subsequent conflicts for the remainder of the current job.")); 0236 connect(d->bApplyAll, &QAbstractButton::clicked, this, &RenameDialog::applyAllPressed); 0237 } 0238 0239 if (!(_options & RenameDialog_NoRename)) { 0240 d->bRename = new QPushButton(i18n("&Rename"), this); 0241 d->bRename->setEnabled(false); 0242 d->bSuggestNewName = new QPushButton(i18n("Suggest New &Name"), this); 0243 connect(d->bSuggestNewName, &QAbstractButton::clicked, this, &RenameDialog::suggestNewNamePressed); 0244 connect(d->bRename, &QAbstractButton::clicked, this, &RenameDialog::renamePressed); 0245 } 0246 0247 if ((_options & RenameDialog_MultipleItems) && (_options & RenameDialog_Skip)) { 0248 d->bSkip = new QPushButton(i18n("&Skip"), this); 0249 d->bSkip->setToolTip((_options & RenameDialog_DestIsDirectory) ? i18n("Do not copy or move this folder, skip to the next item instead") 0250 : i18n("Do not copy or move this file, skip to the next item instead")); 0251 connect(d->bSkip, &QAbstractButton::clicked, this, &RenameDialog::skipPressed); 0252 } 0253 0254 if (_options & RenameDialog_Overwrite) { 0255 d->bOverwrite = new QToolButton(this); 0256 d->bOverwrite->setText(KStandardGuiItem::overwrite().text()); 0257 d->bOverwrite->setIcon(QIcon::fromTheme(KStandardGuiItem::overwrite().iconName())); 0258 d->bOverwrite->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0259 0260 if (_options & RenameDialog_DestIsDirectory) { 0261 d->bOverwrite->setText(i18nc("Write files into an existing folder", "&Write Into")); 0262 d->bOverwrite->setIcon(QIcon()); 0263 d->bOverwrite->setToolTip( 0264 i18n("Files and folders will be copied into the existing directory, alongside its existing contents.\nYou will be prompted again in case of a " 0265 "conflict with an existing file in the directory.")); 0266 0267 } else if ((_options & RenameDialog_MultipleItems) && mtimeSrc.isValid() && mtimeDest.isValid()) { 0268 d->bOverwriteWhenOlder = new QAction(QIcon::fromTheme(KStandardGuiItem::overwrite().iconName()), 0269 i18nc("Overwrite files into an existing folder when files are older", "&Overwrite older files"), 0270 this); 0271 d->bOverwriteWhenOlder->setEnabled(false); 0272 d->bOverwriteWhenOlder->setToolTip( 0273 i18n("Destination files which have older modification times will be overwritten by the source, skipped otherwise.")); 0274 connect(d->bOverwriteWhenOlder, &QAction::triggered, this, &RenameDialog::overwriteWhenOlderPressed); 0275 0276 auto *overwriteMenu = new QMenu(); 0277 overwriteMenu->addAction(d->bOverwriteWhenOlder); 0278 d->bOverwrite->setMenu(overwriteMenu); 0279 d->bOverwrite->setPopupMode(QToolButton::MenuButtonPopup); 0280 } 0281 connect(d->bOverwrite, &QAbstractButton::clicked, this, &RenameDialog::overwritePressed); 0282 } 0283 0284 if (_options & RenameDialog_Resume) { 0285 d->bResume = new QPushButton(i18n("&Resume"), this); 0286 connect(d->bResume, &QAbstractButton::clicked, this, &RenameDialog::resumePressed); 0287 } 0288 0289 auto *pLayout = new QVBoxLayout(this); 0290 pLayout->addStrut(400); // makes dlg at least that wide 0291 0292 // User tries to overwrite a file with itself ? 0293 if (_options & RenameDialog_OverwriteItself) { 0294 auto *lb = new QLabel(i18n("This action would overwrite '%1' with itself.\n" 0295 "Please enter a new file name:", 0296 KStringHandler::csqueeze(d->src.toDisplayString(QUrl::PreferLocalFile), 100)), 0297 this); 0298 lb->setTextFormat(Qt::PlainText); 0299 0300 d->bRename->setText(i18n("C&ontinue")); 0301 pLayout->addWidget(lb); 0302 } else if (_options & RenameDialog_Overwrite) { 0303 if (d->src.isLocalFile()) { 0304 d->srcItem = KFileItem(d->src); 0305 } else { 0306 UDSEntry srcUds; 0307 0308 srcUds.reserve(4); 0309 srcUds.fastInsert(UDSEntry::UDS_NAME, d->src.fileName()); 0310 if (mtimeSrc.isValid()) { 0311 srcUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeSrc.toMSecsSinceEpoch() / 1000); 0312 } 0313 if (ctimeSrc.isValid()) { 0314 srcUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeSrc.toMSecsSinceEpoch() / 1000); 0315 } 0316 if (sizeSrc != KIO::filesize_t(-1)) { 0317 srcUds.fastInsert(UDSEntry::UDS_SIZE, sizeSrc); 0318 } 0319 0320 d->srcItem = KFileItem(srcUds, d->src); 0321 } 0322 0323 if (d->dest.isLocalFile()) { 0324 d->destItem = KFileItem(d->dest); 0325 } else { 0326 UDSEntry destUds; 0327 0328 destUds.reserve(4); 0329 destUds.fastInsert(UDSEntry::UDS_NAME, d->dest.fileName()); 0330 if (mtimeDest.isValid()) { 0331 destUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeDest.toMSecsSinceEpoch() / 1000); 0332 } 0333 if (ctimeDest.isValid()) { 0334 destUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeDest.toMSecsSinceEpoch() / 1000); 0335 } 0336 if (sizeDest != KIO::filesize_t(-1)) { 0337 destUds.fastInsert(UDSEntry::UDS_SIZE, sizeDest); 0338 } 0339 0340 d->destItem = KFileItem(destUds, d->dest); 0341 } 0342 0343 d->m_srcPreview = createLabel(this, QString()); 0344 d->m_destPreview = createLabel(this, QString()); 0345 0346 d->m_srcPreview->setMinimumHeight(KIconLoader::SizeHuge); 0347 d->m_srcPreview->setMinimumWidth(KIconLoader::SizeHuge); 0348 d->m_destPreview->setMinimumHeight(KIconLoader::SizeHuge); 0349 d->m_destPreview->setMinimumWidth(KIconLoader::SizeHuge); 0350 0351 d->m_srcPreview->setAlignment(Qt::AlignCenter); 0352 d->m_destPreview->setAlignment(Qt::AlignCenter); 0353 0354 d->m_srcPendingPreview = true; 0355 d->m_destPendingPreview = true; 0356 0357 // create layout 0358 auto *gridLayout = new QGridLayout(); 0359 pLayout->addLayout(gridLayout); 0360 0361 int gridRow = 0; 0362 auto question = i18n("Would you like to overwrite the destination?"); 0363 if (d->srcItem.isDir() && d->destItem.isDir()) { 0364 question = i18n("Would you like to merge the contents of '%1' into '%2'?", 0365 KShell::tildeCollapse(d->src.toDisplayString(QUrl::PreferLocalFile)), 0366 KShell::tildeCollapse(d->dest.toDisplayString(QUrl::PreferLocalFile))); 0367 } 0368 auto *questionLabel = new QLabel(question, this); 0369 questionLabel->setAlignment(Qt::AlignHCenter); 0370 gridLayout->addWidget(questionLabel, gridRow, 0, 1, 4); // takes the complete first line 0371 0372 QLabel *srcTitle = createLabel(this, i18n("Source"), true); 0373 gridLayout->addWidget(srcTitle, ++gridRow, 0, 1, 2); 0374 QLabel *destTitle = createLabel(this, i18n("Destination"), true); 0375 gridLayout->addWidget(destTitle, gridRow, 2, 1, 2); 0376 0377 // The labels containing src and dest path 0378 QLabel *srcUrlLabel = createSqueezedLabel(this, d->src.toDisplayString(QUrl::PreferLocalFile)); 0379 srcUrlLabel->setTextFormat(Qt::PlainText); 0380 gridLayout->addWidget(srcUrlLabel, ++gridRow, 0, 1, 2); 0381 QLabel *destUrlLabel = createSqueezedLabel(this, d->dest.toDisplayString(QUrl::PreferLocalFile)); 0382 destUrlLabel->setTextFormat(Qt::PlainText); 0383 gridLayout->addWidget(destUrlLabel, gridRow, 2, 1, 2); 0384 0385 gridRow++; 0386 0387 // src container (preview, size, date) 0388 QLabel *srcSizeLabel = createSizeLabel(this, d->srcItem); 0389 d->m_srcDateLabel = createDateLabel(this, d->srcItem); 0390 QWidget *srcContainer = createContainerWidget(d->m_srcPreview, srcSizeLabel, d->m_srcDateLabel); 0391 gridLayout->addWidget(srcContainer, gridRow, 0, 1, 2); 0392 0393 // dest container (preview, size, date) 0394 QLabel *destSizeLabel = createSizeLabel(this, d->destItem); 0395 d->m_destDateLabel = createDateLabel(this, d->destItem); 0396 QWidget *destContainer = createContainerWidget(d->m_destPreview, destSizeLabel, d->m_destDateLabel); 0397 gridLayout->addWidget(destContainer, gridRow, 2, 1, 2); 0398 0399 // Verdicts 0400 auto *hbox_verdicts = new QHBoxLayout(); 0401 pLayout->addLayout(hbox_verdicts); 0402 hbox_verdicts->addStretch(1); 0403 0404 if (mtimeSrc > mtimeDest) { 0405 hbox_verdicts->addWidget(createLabel(this, i18n("The source is <b>more recent</b>."))); 0406 } else if (mtimeDest > mtimeSrc) { 0407 hbox_verdicts->addWidget(createLabel(this, i18n("The source is <b>older</b>."))); 0408 }; 0409 0410 if (d->srcItem.entry().contains(KIO::UDSEntry::UDS_SIZE) && d->destItem.entry().contains(KIO::UDSEntry::UDS_SIZE) 0411 && d->srcItem.size() != d->destItem.size()) { 0412 QString text; 0413 KIO::filesize_t diff = 0; 0414 if (d->destItem.size() > d->srcItem.size()) { 0415 diff = d->destItem.size() - d->srcItem.size(); 0416 text = i18n("The source is <b>smaller by %1</b>.", KIO::convertSize(diff)); 0417 } else { 0418 diff = d->srcItem.size() - d->destItem.size(); 0419 text = i18n("The source is <b>bigger by %1</b>.", KIO::convertSize(diff)); 0420 } 0421 hbox_verdicts->addWidget(createLabel(this, text)); 0422 } 0423 0424 // check files contents for local files 0425 if ((d->dest.isLocalFile() && !(_options & RenameDialog_DestIsDirectory)) && (d->src.isLocalFile() && !(_options & RenameDialog_SourceIsDirectory)) 0426 && (d->srcItem.size() == d->destItem.size())) { 0427 const CompareFilesResult CompareFilesResult = compareFiles(d->src.toLocalFile(), d->dest.toLocalFile()); 0428 0429 QString text; 0430 switch (CompareFilesResult) { 0431 case CompareFilesResult::Identical: 0432 text = i18n("The files are <b>identical</b>."); 0433 break; 0434 case CompareFilesResult::PartiallyIdentical: 0435 text = i18n("The files <b>seem identical</b>."); 0436 break; 0437 case CompareFilesResult::Different: 0438 text = i18n("The files are <b>different</b>."); 0439 break; 0440 } 0441 QLabel *filesIdenticalLabel = createLabel(this, text); 0442 if (CompareFilesResult == CompareFilesResult::PartiallyIdentical) { 0443 auto *pixmapLabel = new QLabel(this); 0444 pixmapLabel->setPixmap(QIcon::fromTheme(QStringLiteral("help-about")).pixmap(QSize(16, 16))); 0445 pixmapLabel->setToolTip( 0446 i18n("The files are likely to be identical: they have the same size and their contents are the same at the beginning, middle and end.")); 0447 pixmapLabel->setCursor(Qt::WhatsThisCursor); 0448 0449 auto *hbox = new QHBoxLayout(); 0450 hbox->addWidget(filesIdenticalLabel); 0451 hbox->addWidget(pixmapLabel); 0452 hbox_verdicts->addLayout(hbox); 0453 } else { 0454 hbox_verdicts->addWidget(filesIdenticalLabel); 0455 } 0456 } 0457 hbox_verdicts->addStretch(1); 0458 0459 } else { 0460 // This is the case where we don't want to allow overwriting, the existing 0461 // file must be preserved (e.g. when renaming). 0462 QString sentence1; 0463 0464 if (mtimeDest < mtimeSrc) { 0465 sentence1 = i18n("An older item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); 0466 } else if (mtimeDest == mtimeSrc) { 0467 sentence1 = i18n("A similar file named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); 0468 } else { 0469 sentence1 = i18n("A more recent item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile)); 0470 } 0471 0472 QLabel *lb = new KSqueezedTextLabel(sentence1, this); 0473 lb->setTextFormat(Qt::PlainText); 0474 pLayout->addWidget(lb); 0475 } 0476 0477 if (!(_options & RenameDialog_OverwriteItself) && !(_options & RenameDialog_NoRename)) { 0478 if (_options & RenameDialog_Overwrite) { 0479 pLayout->addSpacing(15); // spacer 0480 } 0481 0482 auto *lb2 = new QLabel(i18n("Rename:"), this); 0483 pLayout->addWidget(lb2); 0484 } 0485 0486 auto *layout2 = new QHBoxLayout(); 0487 pLayout->addLayout(layout2); 0488 0489 d->m_pLineEdit = new QLineEdit(this); 0490 layout2->addWidget(d->m_pLineEdit); 0491 0492 if (d->bRename) { 0493 const QString fileName = d->dest.fileName(); 0494 d->setRenameBoxText(KIO::decodeFileName(fileName)); 0495 0496 connect(d->m_pLineEdit, &QLineEdit::textChanged, this, &RenameDialog::enableRenameButton); 0497 0498 d->m_pLineEdit->setFocus(); 0499 } else { 0500 d->m_pLineEdit->hide(); 0501 } 0502 0503 if (d->bSuggestNewName) { 0504 layout2->addWidget(d->bSuggestNewName); 0505 setTabOrder(d->m_pLineEdit, d->bSuggestNewName); 0506 } 0507 0508 auto *layout = new QHBoxLayout(); 0509 pLayout->addLayout(layout); 0510 0511 layout->setContentsMargins(0, 10, 0, 0); // add some space above the bottom row with buttons 0512 layout->addStretch(1); 0513 0514 if (d->bApplyAll) { 0515 layout->addWidget(d->bApplyAll); 0516 setTabOrder(d->bApplyAll, d->bCancel); 0517 } 0518 0519 if (d->bSkip) { 0520 layout->addWidget(d->bSkip); 0521 setTabOrder(d->bSkip, d->bCancel); 0522 } 0523 0524 if (d->bRename) { 0525 layout->addWidget(d->bRename); 0526 setTabOrder(d->bRename, d->bCancel); 0527 } 0528 0529 if (d->bOverwrite) { 0530 layout->addWidget(d->bOverwrite); 0531 setTabOrder(d->bOverwrite, d->bCancel); 0532 } 0533 0534 if (d->bResume) { 0535 layout->addWidget(d->bResume); 0536 setTabOrder(d->bResume, d->bCancel); 0537 } 0538 0539 d->bCancel->setDefault(true); 0540 layout->addWidget(d->bCancel); 0541 0542 resize(sizeHint()); 0543 0544 #if 1 // without kfilemetadata 0545 // don't wait for kfilemetadata, but wait until the layouting is done 0546 if (_options & RenameDialog_Overwrite) { 0547 QMetaObject::invokeMethod(this, &KIO::RenameDialog::resizePanels, Qt::QueuedConnection); 0548 } 0549 #endif 0550 } 0551 0552 RenameDialog::~RenameDialog() = default; 0553 0554 void RenameDialog::enableRenameButton(const QString &newDest) 0555 { 0556 if (newDest != KIO::decodeFileName(d->dest.fileName()) && !newDest.isEmpty()) { 0557 d->bRename->setEnabled(true); 0558 d->bRename->setDefault(true); 0559 0560 if (d->bOverwrite) { 0561 d->bOverwrite->setEnabled(false); // prevent confusion (#83114) 0562 } 0563 } else { 0564 d->bRename->setEnabled(false); 0565 0566 if (d->bOverwrite) { 0567 d->bOverwrite->setEnabled(true); 0568 } 0569 } 0570 } 0571 0572 QUrl RenameDialog::newDestUrl() 0573 { 0574 const QString fileName = d->m_pLineEdit->text(); 0575 QUrl newDest = d->dest.adjusted(QUrl::RemoveFilename); // keeps trailing slash 0576 newDest.setPath(newDest.path() + KIO::encodeFileName(fileName)); 0577 return newDest; 0578 } 0579 0580 QUrl RenameDialog::autoDestUrl() const 0581 { 0582 const QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0583 const QString newName = KFileUtils::suggestName(destDirectory, d->dest.fileName()); 0584 QUrl newDest(destDirectory); 0585 newDest.setPath(Utils::concatPaths(newDest.path(), newName)); 0586 return newDest; 0587 } 0588 0589 void RenameDialog::cancelPressed() 0590 { 0591 done(Result_Cancel); 0592 } 0593 0594 // Rename 0595 void RenameDialog::renamePressed() 0596 { 0597 if (d->m_pLineEdit->text().isEmpty()) { 0598 return; 0599 } 0600 0601 if (d->bApplyAll && d->bApplyAll->isChecked()) { 0602 done(Result_AutoRename); 0603 } else { 0604 const QUrl u = newDestUrl(); 0605 if (!u.isValid()) { 0606 KMessageBox::error(this, i18n("Malformed URL\n%1", u.errorString())); 0607 qCWarning(KIO_WIDGETS) << u.errorString(); 0608 return; 0609 } 0610 0611 done(Result_Rename); 0612 } 0613 } 0614 0615 // Propose button clicked 0616 void RenameDialog::suggestNewNamePressed() 0617 { 0618 /* no name to play with */ 0619 if (d->m_pLineEdit->text().isEmpty()) { 0620 return; 0621 } 0622 0623 QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash); 0624 d->setRenameBoxText(KFileUtils::suggestName(destDirectory, d->m_pLineEdit->text())); 0625 } 0626 0627 void RenameDialog::skipPressed() 0628 { 0629 if (d->bApplyAll && d->bApplyAll->isChecked()) { 0630 done(Result_AutoSkip); 0631 } else { 0632 done(Result_Skip); 0633 } 0634 } 0635 0636 void RenameDialog::overwritePressed() 0637 { 0638 if (d->bApplyAll && d->bApplyAll->isChecked()) { 0639 done(Result_OverwriteAll); 0640 } else { 0641 done(Result_Overwrite); 0642 } 0643 } 0644 0645 void RenameDialog::overwriteWhenOlderPressed() 0646 { 0647 if (d->bApplyAll && d->bApplyAll->isChecked()) { 0648 done(Result_OverwriteWhenOlder); 0649 } 0650 } 0651 0652 void RenameDialog::overwriteAllPressed() 0653 { 0654 done(Result_OverwriteAll); 0655 } 0656 0657 void RenameDialog::resumePressed() 0658 { 0659 if (d->bApplyAll && d->bApplyAll->isChecked()) { 0660 done(Result_ResumeAll); 0661 } else { 0662 done(Result_Resume); 0663 } 0664 } 0665 0666 void RenameDialog::resumeAllPressed() 0667 { 0668 done(Result_ResumeAll); 0669 } 0670 0671 void RenameDialog::applyAllPressed() 0672 { 0673 const bool applyAll = d->bApplyAll && d->bApplyAll->isChecked(); 0674 0675 if (applyAll) { 0676 d->m_pLineEdit->setText(KIO::decodeFileName(d->dest.fileName())); 0677 d->m_pLineEdit->setEnabled(false); 0678 } else { 0679 d->m_pLineEdit->setEnabled(true); 0680 } 0681 0682 if (d->bRename) { 0683 d->bRename->setEnabled(applyAll); 0684 } 0685 0686 if (d->bSuggestNewName) { 0687 d->bSuggestNewName->setEnabled(!applyAll); 0688 } 0689 0690 if (d->bOverwriteWhenOlder) { 0691 d->bOverwriteWhenOlder->setEnabled(applyAll); 0692 } 0693 } 0694 0695 void RenameDialog::showSrcIcon(const KFileItem &fileitem) 0696 { 0697 // The preview job failed, show a standard file icon. 0698 d->m_srcPendingPreview = false; 0699 0700 const int size = d->m_srcPreview->height(); 0701 const QPixmap pix = QIcon::fromTheme(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(size); 0702 d->m_srcPreview->setPixmap(pix); 0703 } 0704 0705 void RenameDialog::showDestIcon(const KFileItem &fileitem) 0706 { 0707 // The preview job failed, show a standard file icon. 0708 d->m_destPendingPreview = false; 0709 0710 const int size = d->m_destPreview->height(); 0711 const QPixmap pix = QIcon::fromTheme(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(size); 0712 d->m_destPreview->setPixmap(pix); 0713 } 0714 0715 void RenameDialog::showSrcPreview(const KFileItem &fileitem, const QPixmap &pixmap) 0716 { 0717 Q_UNUSED(fileitem); 0718 0719 if (d->m_srcPendingPreview) { 0720 d->m_srcPreview->setPixmap(pixmap); 0721 d->m_srcPendingPreview = false; 0722 } 0723 } 0724 0725 void RenameDialog::showDestPreview(const KFileItem &fileitem, const QPixmap &pixmap) 0726 { 0727 Q_UNUSED(fileitem); 0728 0729 if (d->m_destPendingPreview) { 0730 d->m_destPreview->setPixmap(pixmap); 0731 d->m_destPendingPreview = false; 0732 } 0733 } 0734 0735 void RenameDialog::resizePanels() 0736 { 0737 Q_ASSERT(d->m_srcPreview != nullptr); 0738 Q_ASSERT(d->m_destPreview != nullptr); 0739 0740 // Force keep the same (max) width of date width for src and dest 0741 int destDateWidth = d->m_destDateLabel->width(); 0742 int srcDateWidth = d->m_srcDateLabel->width(); 0743 int minDateWidth = std::max(destDateWidth, srcDateWidth); 0744 d->m_srcDateLabel->setMinimumWidth(minDateWidth); 0745 d->m_destDateLabel->setMinimumWidth(minDateWidth); 0746 0747 KIO::PreviewJob *srcJob = KIO::filePreview(KFileItemList{d->srcItem}, QSize(d->m_srcPreview->width() * qreal(0.9), d->m_srcPreview->height())); 0748 srcJob->setScaleType(KIO::PreviewJob::Unscaled); 0749 0750 KIO::PreviewJob *destJob = KIO::filePreview(KFileItemList{d->destItem}, QSize(d->m_destPreview->width() * qreal(0.9), d->m_destPreview->height())); 0751 destJob->setScaleType(KIO::PreviewJob::Unscaled); 0752 0753 connect(srcJob, &PreviewJob::gotPreview, this, &RenameDialog::showSrcPreview); 0754 connect(destJob, &PreviewJob::gotPreview, this, &RenameDialog::showDestPreview); 0755 connect(srcJob, &PreviewJob::failed, this, &RenameDialog::showSrcIcon); 0756 connect(destJob, &PreviewJob::failed, this, &RenameDialog::showDestIcon); 0757 } 0758 0759 QWidget *RenameDialog::createContainerWidget(QLabel *preview, QLabel *SizeLabel, QLabel *DateLabel) 0760 { 0761 auto *widgetContainer = new QWidget(); 0762 auto *containerLayout = new QHBoxLayout(widgetContainer); 0763 0764 containerLayout->addStretch(1); 0765 containerLayout->addWidget(preview); 0766 0767 auto *detailsLayout = new QVBoxLayout(widgetContainer); 0768 detailsLayout->addStretch(1); 0769 detailsLayout->addWidget(SizeLabel); 0770 detailsLayout->addWidget(DateLabel); 0771 detailsLayout->addStretch(1); 0772 0773 containerLayout->addLayout(detailsLayout); 0774 containerLayout->addStretch(1); 0775 0776 return widgetContainer; 0777 } 0778 0779 #include "moc_renamedialog.cpp"