File indexing completed on 2024-04-21 03:55:53

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"