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