KIO

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

KDE's Doxygen guidelines are available online.