1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2000 Stephan Kulow <coolo@kde.org>
4 SPDX-FileCopyrightText: 1999-2008 David Faure <faure@kde.org>
5 SPDX-FileCopyrightText: 2001, 2006 Holger Freyther <freyther@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.0-or-later
8*/
9
10#include "kio/renamedialog.h"
11#include "../utils_p.h"
12#include "kio_widgets_debug.h"
13#include "kshell.h"
14
15#include <QApplication>
16#include <QCheckBox>
17#include <QDate>
18#include <QLabel>
19#include <QLayout>
20#include <QLineEdit>
21#include <QMenu>
22#include <QMimeDatabase>
23#include <QPixmap>
24#include <QPushButton>
25#include <QScreen>
26#include <QScrollArea>
27#include <QScrollBar>
28#include <QToolButton>
29
30#include <KFileUtils>
31#include <KGuiItem>
32#include <KIconLoader>
33#include <KLocalizedString>
34#include <KMessageBox>
35#include <KSeparator>
36#include <KSqueezedTextLabel>
37#include <KStandardGuiItem>
38#include <KStringHandler>
39#include <kfileitem.h>
40#include <kio/udsentry.h>
41#include <previewjob.h>
42
43using namespace KIO;
44
45static QLabel *createLabel(QWidget *parent, const QString &text, bool containerTitle = false)
46{
47 auto *label = new QLabel(parent);
48
49 if (containerTitle) {
50 QFont font = label->font();
51 font.setBold(true);
52 label->setFont(font);
53 }
54
55 label->setAlignment(Qt::AlignHCenter);
56 label->setSizePolicy(hor: QSizePolicy::MinimumExpanding, ver: QSizePolicy::Fixed);
57 label->setText(text);
58 return label;
59}
60
61static QLabel *createDateLabel(QWidget *parent, const KFileItem &item)
62{
63 const bool hasDate = item.entry().contains(field: KIO::UDSEntry::UDS_MODIFICATION_TIME);
64 const QString text = hasDate ? i18n("Date: %1", item.timeString(KFileItem::ModificationTime)) : QString();
65 QLabel *dateLabel = createLabel(parent, text);
66 dateLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
67 return dateLabel;
68}
69
70static QLabel *createSizeLabel(QWidget *parent, const KFileItem &item)
71{
72 const bool hasSize = item.entry().contains(field: KIO::UDSEntry::UDS_SIZE);
73 const QString text = hasSize ? i18n("Size: %1", KIO::convertSize(item.size())) : QString();
74 QLabel *sizeLabel = createLabel(parent, text);
75 sizeLabel->setAlignment(Qt::AlignLeft | Qt::AlignBottom);
76 return sizeLabel;
77}
78
79static KSqueezedTextLabel *createSqueezedLabel(QWidget *parent, const QString &text)
80{
81 auto *label = new KSqueezedTextLabel(text, parent);
82 label->setAlignment(Qt::AlignHCenter);
83 label->setSizePolicy(hor: QSizePolicy::Ignored, ver: QSizePolicy::Fixed);
84 return label;
85}
86
87enum CompareFilesResult {
88 Identical,
89 PartiallyIdentical,
90 Different,
91};
92static CompareFilesResult compareFiles(const QString &filepath, const QString &secondFilePath)
93{
94 const qint64 bufferSize = 4096; // 4kb
95 QFile f(filepath);
96 QFile f2(secondFilePath);
97 const auto fileSize = f.size();
98
99 if (fileSize != f2.size()) {
100 return CompareFilesResult::Different;
101 }
102 if (!f.open(flags: QFile::ReadOnly)) {
103 qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f.fileName();
104 return CompareFilesResult::Different;
105 }
106 if (!f2.open(flags: QFile::ReadOnly)) {
107 f.close();
108 qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f2.fileName();
109 return CompareFilesResult::Different;
110 }
111
112 QByteArray buffer(bufferSize, 0);
113 QByteArray buffer2(bufferSize, 0);
114
115 auto seekFillBuffer = [bufferSize](qint64 pos, QFile &f, QByteArray &buffer) {
116 auto ioresult = f.seek(offset: pos);
117 if (ioresult) {
118 const int bytesRead = f.read(data: buffer.data(), maxlen: bufferSize);
119 ioresult = bytesRead != -1;
120 }
121 if (!ioresult) {
122 qCWarning(KIO_WIDGETS) << "Could not read file for comparison:" << f.fileName();
123 return false;
124 }
125 return true;
126 };
127
128 // compare at the beginning of the files
129 bool result = seekFillBuffer(0, f, buffer);
130 result = result && seekFillBuffer(0, f2, buffer2);
131 result = result && buffer == buffer2;
132
133 if (result && fileSize > 2 * bufferSize) {
134 // compare the contents in the middle of the files
135 result = seekFillBuffer(fileSize / 2 - bufferSize / 2, f, buffer);
136 result = result && seekFillBuffer(fileSize / 2 - bufferSize / 2, f2, buffer2);
137 result = result && buffer == buffer2;
138 }
139
140 if (result && fileSize > bufferSize) {
141 // compare the contents at the end of the files
142 result = seekFillBuffer(fileSize - bufferSize, f, buffer);
143 result = result && seekFillBuffer(fileSize - bufferSize, f2, buffer2);
144 result = result && buffer == buffer2;
145 }
146
147 if (!result) {
148 return CompareFilesResult::Different;
149 }
150
151 if (fileSize <= bufferSize * 3) {
152 // for files smaller than bufferSize * 3, we in fact compared fully the files
153 return CompareFilesResult::Identical;
154 } else {
155 return CompareFilesResult::PartiallyIdentical;
156 }
157}
158
159/** @internal */
160class Q_DECL_HIDDEN RenameDialog::RenameDialogPrivate
161{
162public:
163 RenameDialogPrivate()
164 {
165 }
166
167 void setRenameBoxText(const QString &fileName)
168 {
169 // sets the text in file name line edit box, selecting the filename (but not the extension if there is one).
170 QMimeDatabase db;
171 const QString extension = db.suffixForFileName(fileName);
172 m_pLineEdit->setText(fileName);
173
174 if (!extension.isEmpty()) {
175 const int selectionLength = fileName.length() - extension.length() - 1;
176 m_pLineEdit->setSelection(0, selectionLength);
177 } else {
178 m_pLineEdit->selectAll();
179 }
180 }
181
182 QPushButton *bCancel = nullptr;
183 QPushButton *bRename = nullptr;
184 QPushButton *bSkip = nullptr;
185 QToolButton *bOverwrite = nullptr;
186 QAction *bOverwriteWhenOlder = nullptr;
187 QPushButton *bResume = nullptr;
188 QPushButton *bSuggestNewName = nullptr;
189 QCheckBox *bApplyAll = nullptr;
190 QLineEdit *m_pLineEdit = nullptr;
191 QUrl src;
192 QUrl dest;
193 bool m_srcPendingPreview = false;
194 bool m_destPendingPreview = false;
195 QLabel *m_srcPreview = nullptr;
196 QLabel *m_destPreview = nullptr;
197 QLabel *m_srcDateLabel = nullptr;
198 QLabel *m_destDateLabel = nullptr;
199 KFileItem srcItem;
200 KFileItem destItem;
201};
202
203RenameDialog::RenameDialog(QWidget *parent,
204 const QString &title,
205 const QUrl &_src,
206 const QUrl &_dest,
207 RenameDialog_Options _options,
208 KIO::filesize_t sizeSrc,
209 KIO::filesize_t sizeDest,
210 const QDateTime &ctimeSrc,
211 const QDateTime &ctimeDest,
212 const QDateTime &mtimeSrc,
213 const QDateTime &mtimeDest)
214 : QDialog(parent)
215 , d(new RenameDialogPrivate)
216{
217 setObjectName(QStringLiteral("KIO::RenameDialog"));
218
219 d->src = _src;
220 d->dest = _dest;
221
222 setWindowTitle(title);
223
224 d->bCancel = new QPushButton(this);
225 KGuiItem::assign(button: d->bCancel, item: KStandardGuiItem::cancel());
226 connect(sender: d->bCancel, signal: &QAbstractButton::clicked, context: this, slot: &RenameDialog::cancelPressed);
227
228 if (_options & RenameDialog_MultipleItems) {
229 d->bApplyAll = new QCheckBox(i18n("Appl&y to All"), this);
230 d->bApplyAll->setToolTip((_options & RenameDialog_DestIsDirectory) ? i18n("When this is checked the button pressed will be applied to all "
231 "subsequent folder conflicts for the remainder of the current job.\n"
232 "Unless you press Skip you will still be prompted in case of a "
233 "conflict with an existing file in the directory.")
234 : i18n("When this is checked the button pressed will be applied to "
235 "all subsequent conflicts for the remainder of the current job."));
236 connect(sender: d->bApplyAll, signal: &QAbstractButton::clicked, context: this, slot: &RenameDialog::applyAllPressed);
237 }
238
239 if (!(_options & RenameDialog_NoRename)) {
240 d->bRename = new QPushButton(i18n("&Rename"), this);
241 d->bRename->setEnabled(false);
242 d->bSuggestNewName = new QPushButton(i18n("Suggest New &Name"), this);
243 connect(sender: d->bSuggestNewName, signal: &QAbstractButton::clicked, context: this, slot: &RenameDialog::suggestNewNamePressed);
244 connect(sender: d->bRename, signal: &QAbstractButton::clicked, context: this, slot: &RenameDialog::renamePressed);
245 }
246
247 if ((_options & RenameDialog_MultipleItems) && (_options & RenameDialog_Skip)) {
248 d->bSkip = new QPushButton(i18n("&Skip"), this);
249 d->bSkip->setToolTip((_options & RenameDialog_DestIsDirectory) ? i18n("Do not copy or move this folder, skip to the next item instead")
250 : i18n("Do not copy or move this file, skip to the next item instead"));
251 connect(sender: d->bSkip, signal: &QAbstractButton::clicked, context: this, slot: &RenameDialog::skipPressed);
252 }
253
254 if (_options & RenameDialog_Overwrite) {
255 d->bOverwrite = new QToolButton(this);
256 d->bOverwrite->setText(KStandardGuiItem::overwrite().text());
257 d->bOverwrite->setIcon(QIcon::fromTheme(name: KStandardGuiItem::overwrite().iconName()));
258 d->bOverwrite->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
259
260 if (_options & RenameDialog_DestIsDirectory) {
261 d->bOverwrite->setText(i18nc("Write files into an existing folder", "&Write Into"));
262 d->bOverwrite->setIcon(QIcon());
263 d->bOverwrite->setToolTip(
264 i18n("Files and folders will be copied into the existing directory, alongside its existing contents.\nYou will be prompted again in case of a "
265 "conflict with an existing file in the directory."));
266
267 } else if ((_options & RenameDialog_MultipleItems) && mtimeSrc.isValid() && mtimeDest.isValid()) {
268 d->bOverwriteWhenOlder = new QAction(QIcon::fromTheme(name: KStandardGuiItem::overwrite().iconName()),
269 i18nc("Overwrite files into an existing folder when files are older", "&Overwrite older files"),
270 this);
271 d->bOverwriteWhenOlder->setEnabled(false);
272 d->bOverwriteWhenOlder->setToolTip(
273 i18n("Destination files which have older modification times will be overwritten by the source, skipped otherwise."));
274 connect(sender: d->bOverwriteWhenOlder, signal: &QAction::triggered, context: this, slot: &RenameDialog::overwriteWhenOlderPressed);
275
276 auto *overwriteMenu = new QMenu();
277 overwriteMenu->addAction(action: d->bOverwriteWhenOlder);
278 d->bOverwrite->setMenu(overwriteMenu);
279 d->bOverwrite->setPopupMode(QToolButton::MenuButtonPopup);
280 }
281 connect(sender: d->bOverwrite, signal: &QAbstractButton::clicked, context: this, slot: &RenameDialog::overwritePressed);
282 }
283
284 if (_options & RenameDialog_Resume) {
285 d->bResume = new QPushButton(i18n("&Resume"), this);
286 connect(sender: d->bResume, signal: &QAbstractButton::clicked, context: this, slot: &RenameDialog::resumePressed);
287 }
288
289 auto *pLayout = new QVBoxLayout(this);
290 pLayout->addStrut(400); // makes dlg at least that wide
291
292 // User tries to overwrite a file with itself ?
293 if (_options & RenameDialog_OverwriteItself) {
294 auto *lb = new QLabel(i18n("This action would overwrite '%1' with itself.\n"
295 "Please enter a new file name:",
296 KStringHandler::csqueeze(d->src.toDisplayString(QUrl::PreferLocalFile), 100)),
297 this);
298 lb->setTextFormat(Qt::PlainText);
299
300 d->bRename->setText(i18n("C&ontinue"));
301 pLayout->addWidget(lb);
302 } else if (_options & RenameDialog_Overwrite) {
303 if (d->src.isLocalFile()) {
304 d->srcItem = KFileItem(d->src);
305 } else {
306 UDSEntry srcUds;
307
308 srcUds.reserve(size: 4);
309 srcUds.fastInsert(field: UDSEntry::UDS_NAME, value: d->src.fileName());
310 if (mtimeSrc.isValid()) {
311 srcUds.fastInsert(field: UDSEntry::UDS_MODIFICATION_TIME, l: mtimeSrc.toMSecsSinceEpoch() / 1000);
312 }
313 if (ctimeSrc.isValid()) {
314 srcUds.fastInsert(field: UDSEntry::UDS_CREATION_TIME, l: ctimeSrc.toMSecsSinceEpoch() / 1000);
315 }
316 if (sizeSrc != KIO::filesize_t(-1)) {
317 srcUds.fastInsert(field: UDSEntry::UDS_SIZE, l: sizeSrc);
318 }
319
320 d->srcItem = KFileItem(srcUds, d->src);
321 }
322
323 if (d->dest.isLocalFile()) {
324 d->destItem = KFileItem(d->dest);
325 } else {
326 UDSEntry destUds;
327
328 destUds.reserve(size: 4);
329 destUds.fastInsert(field: UDSEntry::UDS_NAME, value: d->dest.fileName());
330 if (mtimeDest.isValid()) {
331 destUds.fastInsert(field: UDSEntry::UDS_MODIFICATION_TIME, l: mtimeDest.toMSecsSinceEpoch() / 1000);
332 }
333 if (ctimeDest.isValid()) {
334 destUds.fastInsert(field: UDSEntry::UDS_CREATION_TIME, l: ctimeDest.toMSecsSinceEpoch() / 1000);
335 }
336 if (sizeDest != KIO::filesize_t(-1)) {
337 destUds.fastInsert(field: UDSEntry::UDS_SIZE, l: sizeDest);
338 }
339
340 d->destItem = KFileItem(destUds, d->dest);
341 }
342
343 d->m_srcPreview = createLabel(parent: this, text: QString());
344 d->m_destPreview = createLabel(parent: this, text: QString());
345
346 d->m_srcPreview->setMinimumHeight(KIconLoader::SizeHuge);
347 d->m_srcPreview->setMinimumWidth(KIconLoader::SizeHuge);
348 d->m_destPreview->setMinimumHeight(KIconLoader::SizeHuge);
349 d->m_destPreview->setMinimumWidth(KIconLoader::SizeHuge);
350
351 d->m_srcPreview->setAlignment(Qt::AlignCenter);
352 d->m_destPreview->setAlignment(Qt::AlignCenter);
353
354 d->m_srcPendingPreview = true;
355 d->m_destPendingPreview = true;
356
357 // create layout
358 auto *gridLayout = new QGridLayout();
359 pLayout->addLayout(layout: gridLayout);
360
361 int gridRow = 0;
362 auto question = i18n("Would you like to overwrite the destination?");
363 if (d->srcItem.isDir() && d->destItem.isDir()) {
364 question = i18n("Would you like to merge the contents of '%1' into '%2'?",
365 KShell::tildeCollapse(d->src.toDisplayString(QUrl::PreferLocalFile)),
366 KShell::tildeCollapse(d->dest.toDisplayString(QUrl::PreferLocalFile)));
367 }
368 auto *questionLabel = new QLabel(question, this);
369 questionLabel->setAlignment(Qt::AlignHCenter);
370 gridLayout->addWidget(questionLabel, row: gridRow, column: 0, rowSpan: 1, columnSpan: 4); // takes the complete first line
371
372 QLabel *srcTitle = createLabel(parent: this, i18n("Source"), containerTitle: true);
373 gridLayout->addWidget(srcTitle, row: ++gridRow, column: 0, rowSpan: 1, columnSpan: 2);
374 QLabel *destTitle = createLabel(parent: this, i18n("Destination"), containerTitle: true);
375 gridLayout->addWidget(destTitle, row: gridRow, column: 2, rowSpan: 1, columnSpan: 2);
376
377 // The labels containing src and dest path
378 QLabel *srcUrlLabel = createSqueezedLabel(parent: this, text: d->src.toDisplayString(options: QUrl::PreferLocalFile));
379 srcUrlLabel->setTextFormat(Qt::PlainText);
380 gridLayout->addWidget(srcUrlLabel, row: ++gridRow, column: 0, rowSpan: 1, columnSpan: 2);
381 QLabel *destUrlLabel = createSqueezedLabel(parent: this, text: d->dest.toDisplayString(options: QUrl::PreferLocalFile));
382 destUrlLabel->setTextFormat(Qt::PlainText);
383 gridLayout->addWidget(destUrlLabel, row: gridRow, column: 2, rowSpan: 1, columnSpan: 2);
384
385 gridRow++;
386
387 // src container (preview, size, date)
388 QLabel *srcSizeLabel = createSizeLabel(parent: this, item: d->srcItem);
389 d->m_srcDateLabel = createDateLabel(parent: this, item: d->srcItem);
390 QWidget *srcContainer = createContainerWidget(preview: d->m_srcPreview, SizeLabel: srcSizeLabel, DateLabel: d->m_srcDateLabel);
391 gridLayout->addWidget(srcContainer, row: gridRow, column: 0, rowSpan: 1, columnSpan: 2);
392
393 // dest container (preview, size, date)
394 QLabel *destSizeLabel = createSizeLabel(parent: this, item: d->destItem);
395 d->m_destDateLabel = createDateLabel(parent: this, item: d->destItem);
396 QWidget *destContainer = createContainerWidget(preview: d->m_destPreview, SizeLabel: destSizeLabel, DateLabel: d->m_destDateLabel);
397 gridLayout->addWidget(destContainer, row: gridRow, column: 2, rowSpan: 1, columnSpan: 2);
398
399 // Verdicts
400 auto *hbox_verdicts = new QHBoxLayout();
401 pLayout->addLayout(layout: hbox_verdicts);
402 hbox_verdicts->addStretch(stretch: 1);
403
404 if (mtimeSrc > mtimeDest) {
405 hbox_verdicts->addWidget(createLabel(parent: this, i18n("The source is <b>more recent</b>.")));
406 } else if (mtimeDest > mtimeSrc) {
407 hbox_verdicts->addWidget(createLabel(parent: this, i18n("The source is <b>older</b>.")));
408 };
409
410 if (d->srcItem.entry().contains(field: KIO::UDSEntry::UDS_SIZE) && d->destItem.entry().contains(field: KIO::UDSEntry::UDS_SIZE)
411 && d->srcItem.size() != d->destItem.size()) {
412 QString text;
413 KIO::filesize_t diff = 0;
414 if (d->destItem.size() > d->srcItem.size()) {
415 diff = d->destItem.size() - d->srcItem.size();
416 text = i18n("The source is <b>smaller by %1</b>.", KIO::convertSize(diff));
417 } else {
418 diff = d->srcItem.size() - d->destItem.size();
419 text = i18n("The source is <b>bigger by %1</b>.", KIO::convertSize(diff));
420 }
421 hbox_verdicts->addWidget(createLabel(parent: this, text));
422 }
423
424 // check files contents for local files
425 if ((d->dest.isLocalFile() && !(_options & RenameDialog_DestIsDirectory)) && (d->src.isLocalFile() && !(_options & RenameDialog_SourceIsDirectory))
426 && (d->srcItem.size() == d->destItem.size())) {
427 const CompareFilesResult CompareFilesResult = compareFiles(filepath: d->src.toLocalFile(), secondFilePath: d->dest.toLocalFile());
428
429 QString text;
430 switch (CompareFilesResult) {
431 case CompareFilesResult::Identical:
432 text = i18n("The files are <b>identical</b>.");
433 break;
434 case CompareFilesResult::PartiallyIdentical:
435 text = i18n("The files <b>seem identical</b>.");
436 break;
437 case CompareFilesResult::Different:
438 text = i18n("The files are <b>different</b>.");
439 break;
440 }
441 QLabel *filesIdenticalLabel = createLabel(parent: this, text);
442 if (CompareFilesResult == CompareFilesResult::PartiallyIdentical) {
443 auto *pixmapLabel = new QLabel(this);
444 pixmapLabel->setPixmap(QIcon::fromTheme(QStringLiteral("help-about")).pixmap(size: QSize(16, 16)));
445 pixmapLabel->setToolTip(
446 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."));
447 pixmapLabel->setCursor(Qt::WhatsThisCursor);
448
449 auto *hbox = new QHBoxLayout();
450 hbox->addWidget(filesIdenticalLabel);
451 hbox->addWidget(pixmapLabel);
452 hbox_verdicts->addLayout(layout: hbox);
453 } else {
454 hbox_verdicts->addWidget(filesIdenticalLabel);
455 }
456 }
457 hbox_verdicts->addStretch(stretch: 1);
458
459 } else {
460 // This is the case where we don't want to allow overwriting, the existing
461 // file must be preserved (e.g. when renaming).
462 QString sentence1;
463
464 if (mtimeDest < mtimeSrc) {
465 sentence1 = i18n("An older item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile));
466 } else if (mtimeDest == mtimeSrc) {
467 sentence1 = i18n("A similar file named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile));
468 } else {
469 sentence1 = i18n("A more recent item named '%1' already exists.", d->dest.toDisplayString(QUrl::PreferLocalFile));
470 }
471
472 QLabel *lb = new KSqueezedTextLabel(sentence1, this);
473 lb->setTextFormat(Qt::PlainText);
474 pLayout->addWidget(lb);
475 }
476
477 if (!(_options & RenameDialog_OverwriteItself) && !(_options & RenameDialog_NoRename)) {
478 if (_options & RenameDialog_Overwrite) {
479 pLayout->addSpacing(size: 15); // spacer
480 }
481
482 auto *lb2 = new QLabel(i18n("Rename:"), this);
483 pLayout->addWidget(lb2);
484 }
485
486 auto *layout2 = new QHBoxLayout();
487 pLayout->addLayout(layout: layout2);
488
489 d->m_pLineEdit = new QLineEdit(this);
490 layout2->addWidget(d->m_pLineEdit);
491
492 if (d->bRename) {
493 const QString fileName = d->dest.fileName();
494 d->setRenameBoxText(KIO::decodeFileName(str: fileName));
495
496 connect(sender: d->m_pLineEdit, signal: &QLineEdit::textChanged, context: this, slot: &RenameDialog::enableRenameButton);
497
498 d->m_pLineEdit->setFocus();
499 } else {
500 d->m_pLineEdit->hide();
501 }
502
503 if (d->bSuggestNewName) {
504 layout2->addWidget(d->bSuggestNewName);
505 setTabOrder(d->m_pLineEdit, d->bSuggestNewName);
506 }
507
508 auto *layout = new QHBoxLayout();
509 pLayout->addLayout(layout);
510
511 layout->setContentsMargins(left: 0, top: 10, right: 0, bottom: 0); // add some space above the bottom row with buttons
512 layout->addStretch(stretch: 1);
513
514 if (d->bApplyAll) {
515 layout->addWidget(d->bApplyAll);
516 setTabOrder(d->bApplyAll, d->bCancel);
517 }
518
519 if (d->bSkip) {
520 layout->addWidget(d->bSkip);
521 setTabOrder(d->bSkip, d->bCancel);
522 }
523
524 if (d->bRename) {
525 layout->addWidget(d->bRename);
526 setTabOrder(d->bRename, d->bCancel);
527 }
528
529 if (d->bOverwrite) {
530 layout->addWidget(d->bOverwrite);
531 setTabOrder(d->bOverwrite, d->bCancel);
532 }
533
534 if (d->bResume) {
535 layout->addWidget(d->bResume);
536 setTabOrder(d->bResume, d->bCancel);
537 }
538
539 d->bCancel->setDefault(true);
540 layout->addWidget(d->bCancel);
541
542 resize(sizeHint());
543
544#if 1 // without kfilemetadata
545 // don't wait for kfilemetadata, but wait until the layouting is done
546 if (_options & RenameDialog_Overwrite) {
547 QMetaObject::invokeMethod(object: this, function: &KIO::RenameDialog::resizePanels, type: Qt::QueuedConnection);
548 }
549#endif
550}
551
552RenameDialog::~RenameDialog() = default;
553
554void RenameDialog::enableRenameButton(const QString &newDest)
555{
556 if (newDest != KIO::decodeFileName(str: d->dest.fileName()) && !newDest.isEmpty()) {
557 d->bRename->setEnabled(true);
558 d->bRename->setDefault(true);
559
560 if (d->bOverwrite) {
561 d->bOverwrite->setEnabled(false); // prevent confusion (#83114)
562 }
563 } else {
564 d->bRename->setEnabled(false);
565
566 if (d->bOverwrite) {
567 d->bOverwrite->setEnabled(true);
568 }
569 }
570}
571
572QUrl RenameDialog::newDestUrl()
573{
574 const QString fileName = d->m_pLineEdit->text();
575 QUrl newDest = d->dest.adjusted(options: QUrl::RemoveFilename); // keeps trailing slash
576 newDest.setPath(path: newDest.path() + KIO::encodeFileName(str: fileName));
577 return newDest;
578}
579
580QUrl RenameDialog::autoDestUrl() const
581{
582 const QUrl destDirectory = d->dest.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
583 const QString newName = KFileUtils::suggestName(baseURL: destDirectory, oldName: d->dest.fileName());
584 QUrl newDest(destDirectory);
585 newDest.setPath(path: Utils::concatPaths(path1: newDest.path(), path2: newName));
586 return newDest;
587}
588
589void RenameDialog::cancelPressed()
590{
591 done(Result_Cancel);
592}
593
594// Rename
595void RenameDialog::renamePressed()
596{
597 if (d->m_pLineEdit->text().isEmpty()) {
598 return;
599 }
600
601 if (d->bApplyAll && d->bApplyAll->isChecked()) {
602 done(Result_AutoRename);
603 } else {
604 const QUrl u = newDestUrl();
605 if (!u.isValid()) {
606 KMessageBox::error(parent: this, i18n("Malformed URL\n%1", u.errorString()));
607 qCWarning(KIO_WIDGETS) << u.errorString();
608 return;
609 }
610
611 done(Result_Rename);
612 }
613}
614
615// Propose button clicked
616void RenameDialog::suggestNewNamePressed()
617{
618 /* no name to play with */
619 if (d->m_pLineEdit->text().isEmpty()) {
620 return;
621 }
622
623 QUrl destDirectory = d->dest.adjusted(options: QUrl::RemoveFilename | QUrl::StripTrailingSlash);
624 d->setRenameBoxText(KFileUtils::suggestName(baseURL: destDirectory, oldName: d->m_pLineEdit->text()));
625}
626
627void RenameDialog::skipPressed()
628{
629 if (d->bApplyAll && d->bApplyAll->isChecked()) {
630 done(Result_AutoSkip);
631 } else {
632 done(Result_Skip);
633 }
634}
635
636void RenameDialog::overwritePressed()
637{
638 if (d->bApplyAll && d->bApplyAll->isChecked()) {
639 done(Result_OverwriteAll);
640 } else {
641 done(Result_Overwrite);
642 }
643}
644
645void RenameDialog::overwriteWhenOlderPressed()
646{
647 if (d->bApplyAll && d->bApplyAll->isChecked()) {
648 done(Result_OverwriteWhenOlder);
649 }
650}
651
652void RenameDialog::overwriteAllPressed()
653{
654 done(Result_OverwriteAll);
655}
656
657void RenameDialog::resumePressed()
658{
659 if (d->bApplyAll && d->bApplyAll->isChecked()) {
660 done(Result_ResumeAll);
661 } else {
662 done(Result_Resume);
663 }
664}
665
666void RenameDialog::resumeAllPressed()
667{
668 done(Result_ResumeAll);
669}
670
671void RenameDialog::applyAllPressed()
672{
673 const bool applyAll = d->bApplyAll && d->bApplyAll->isChecked();
674
675 if (applyAll) {
676 d->m_pLineEdit->setText(KIO::decodeFileName(str: d->dest.fileName()));
677 d->m_pLineEdit->setEnabled(false);
678 } else {
679 d->m_pLineEdit->setEnabled(true);
680 }
681
682 if (d->bRename) {
683 d->bRename->setEnabled(applyAll);
684 }
685
686 if (d->bSuggestNewName) {
687 d->bSuggestNewName->setEnabled(!applyAll);
688 }
689
690 if (d->bOverwriteWhenOlder) {
691 d->bOverwriteWhenOlder->setEnabled(applyAll);
692 }
693}
694
695void RenameDialog::showSrcIcon(const KFileItem &fileitem)
696{
697 // The preview job failed, show a standard file icon.
698 d->m_srcPendingPreview = false;
699
700 const int size = d->m_srcPreview->height();
701 const QPixmap pix = QIcon::fromTheme(name: fileitem.iconName(), fallback: QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(extent: size);
702 d->m_srcPreview->setPixmap(pix);
703}
704
705void RenameDialog::showDestIcon(const KFileItem &fileitem)
706{
707 // The preview job failed, show a standard file icon.
708 d->m_destPendingPreview = false;
709
710 const int size = d->m_destPreview->height();
711 const QPixmap pix = QIcon::fromTheme(name: fileitem.iconName(), fallback: QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(extent: size);
712 d->m_destPreview->setPixmap(pix);
713}
714
715void RenameDialog::showSrcPreview(const KFileItem &fileitem, const QPixmap &pixmap)
716{
717 Q_UNUSED(fileitem);
718
719 if (d->m_srcPendingPreview) {
720 d->m_srcPreview->setPixmap(pixmap);
721 d->m_srcPendingPreview = false;
722 }
723}
724
725void RenameDialog::showDestPreview(const KFileItem &fileitem, const QPixmap &pixmap)
726{
727 Q_UNUSED(fileitem);
728
729 if (d->m_destPendingPreview) {
730 d->m_destPreview->setPixmap(pixmap);
731 d->m_destPendingPreview = false;
732 }
733}
734
735void RenameDialog::resizePanels()
736{
737 Q_ASSERT(d->m_srcPreview != nullptr);
738 Q_ASSERT(d->m_destPreview != nullptr);
739
740 // Force keep the same (max) width of date width for src and dest
741 int destDateWidth = d->m_destDateLabel->width();
742 int srcDateWidth = d->m_srcDateLabel->width();
743 int minDateWidth = std::max(a: destDateWidth, b: srcDateWidth);
744 d->m_srcDateLabel->setMinimumWidth(minDateWidth);
745 d->m_destDateLabel->setMinimumWidth(minDateWidth);
746
747 KIO::PreviewJob *srcJob = KIO::filePreview(items: KFileItemList{d->srcItem}, size: QSize(d->m_srcPreview->width() * qreal(0.9), d->m_srcPreview->height()));
748 srcJob->setScaleType(KIO::PreviewJob::Unscaled);
749
750 KIO::PreviewJob *destJob = KIO::filePreview(items: KFileItemList{d->destItem}, size: QSize(d->m_destPreview->width() * qreal(0.9), d->m_destPreview->height()));
751 destJob->setScaleType(KIO::PreviewJob::Unscaled);
752
753 connect(sender: srcJob, signal: &PreviewJob::gotPreview, context: this, slot: &RenameDialog::showSrcPreview);
754 connect(sender: destJob, signal: &PreviewJob::gotPreview, context: this, slot: &RenameDialog::showDestPreview);
755 connect(sender: srcJob, signal: &PreviewJob::failed, context: this, slot: &RenameDialog::showSrcIcon);
756 connect(sender: destJob, signal: &PreviewJob::failed, context: this, slot: &RenameDialog::showDestIcon);
757}
758
759QWidget *RenameDialog::createContainerWidget(QLabel *preview, QLabel *SizeLabel, QLabel *DateLabel)
760{
761 auto *widgetContainer = new QWidget();
762 auto *containerLayout = new QHBoxLayout(widgetContainer);
763
764 containerLayout->addStretch(stretch: 1);
765 containerLayout->addWidget(preview);
766
767 auto *detailsLayout = new QVBoxLayout(widgetContainer);
768 detailsLayout->addStretch(stretch: 1);
769 detailsLayout->addWidget(SizeLabel);
770 detailsLayout->addWidget(DateLabel);
771 detailsLayout->addStretch(stretch: 1);
772
773 containerLayout->addLayout(layout: detailsLayout);
774 containerLayout->addStretch(stretch: 1);
775
776 return widgetContainer;
777}
778
779#include "moc_renamedialog.cpp"
780

source code of kio/src/widgets/renamedialog.cpp