File indexing completed on 2024-04-28 15:27:32

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"