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 }
54
55 label->setAlignment(Qt::AlignHCenter);
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(KIO::UDSEntry::UDS_MODIFICATION_TIME);
64 const QString text = hasDate ? i18n("Date: %1", item.timeString(KFileItem::ModificationTime)) : QString();
65 QLabel *dateLabel = createLabel(parent, text);
67 return dateLabel;
68}
69
70static QLabel *createSizeLabel(QWidget *parent, const KFileItem &item)
71{
72 const bool hasSize = item.entry().contains(KIO::UDSEntry::UDS_SIZE);
73 const QString text = hasSize ? i18n("Size: %1", KIO::convertSize(item.size())) : QString();
74 QLabel *sizeLabel = createLabel(parent, text);
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);
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(QFile::ReadOnly)) {
103 qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f.fileName();
104 return CompareFilesResult::Different;
105 }
106 if (!f2.open(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(pos);
117 if (ioresult) {
118 const int bytesRead = f.read(buffer.data(), 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
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);
226 connect(d->bCancel, &QAbstractButton::clicked, this, &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(d->bApplyAll, &QAbstractButton::clicked, this, &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(d->bSuggestNewName, &QAbstractButton::clicked, this, &RenameDialog::suggestNewNamePressed);
244 connect(d->bRename, &QAbstractButton::clicked, this, &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(d->bSkip, &QAbstractButton::clicked, this, &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(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(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(d->bOverwriteWhenOlder, &QAction::triggered, this, &RenameDialog::overwriteWhenOlderPressed);
275
276 auto *overwriteMenu = new QMenu();
277 overwriteMenu->addAction(d->bOverwriteWhenOlder);
278 d->bOverwrite->setMenu(overwriteMenu);
279 d->bOverwrite->setPopupMode(QToolButton::MenuButtonPopup);
280 }
281 connect(d->bOverwrite, &QAbstractButton::clicked, this, &RenameDialog::overwritePressed);
282 }
283
284 if (_options & RenameDialog_Resume) {
285 d->bResume = new QPushButton(i18n("&Resume"), this);
286 connect(d->bResume, &QAbstractButton::clicked, this, &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(4);
309 srcUds.fastInsert(UDSEntry::UDS_NAME, d->src.fileName());
310 if (mtimeSrc.isValid()) {
312 }
313 if (ctimeSrc.isValid()) {
315 }
316 if (sizeSrc != KIO::filesize_t(-1)) {
317 srcUds.fastInsert(UDSEntry::UDS_SIZE, 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(4);
329 destUds.fastInsert(UDSEntry::UDS_NAME, d->dest.fileName());
330 if (mtimeDest.isValid()) {
332 }
333 if (ctimeDest.isValid()) {
334 destUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeDest.toMSecsSinceEpoch() / 1000);
335 }
336 if (sizeDest != KIO::filesize_t(-1)) {
337 destUds.fastInsert(UDSEntry::UDS_SIZE, sizeDest);
338 }
339
340 d->destItem = KFileItem(destUds, d->dest);
341 }
342
343 d->m_srcPreview = createLabel(this, QString());
344 d->m_destPreview = createLabel(this, 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(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, gridRow, 0, 1, 4); // takes the complete first line
371
372 QLabel *srcTitle = createLabel(this, i18n("Source"), true);
373 gridLayout->addWidget(srcTitle, ++gridRow, 0, 1, 2);
374 QLabel *destTitle = createLabel(this, i18n("Destination"), true);
375 gridLayout->addWidget(destTitle, gridRow, 2, 1, 2);
376
377 // The labels containing src and dest path
378 QLabel *srcUrlLabel = createSqueezedLabel(this, d->src.toDisplayString(QUrl::PreferLocalFile));
379 srcUrlLabel->setTextFormat(Qt::PlainText);
380 gridLayout->addWidget(srcUrlLabel, ++gridRow, 0, 1, 2);
381 QLabel *destUrlLabel = createSqueezedLabel(this, d->dest.toDisplayString(QUrl::PreferLocalFile));
382 destUrlLabel->setTextFormat(Qt::PlainText);
383 gridLayout->addWidget(destUrlLabel, gridRow, 2, 1, 2);
384
385 gridRow++;
386
387 // src container (preview, size, date)
388 QLabel *srcSizeLabel = createSizeLabel(this, d->srcItem);
389 d->m_srcDateLabel = createDateLabel(this, d->srcItem);
390 QWidget *srcContainer = createContainerWidget(d->m_srcPreview, srcSizeLabel, d->m_srcDateLabel);
391 gridLayout->addWidget(srcContainer, gridRow, 0, 1, 2);
392
393 // dest container (preview, size, date)
394 QLabel *destSizeLabel = createSizeLabel(this, d->destItem);
395 d->m_destDateLabel = createDateLabel(this, d->destItem);
396 QWidget *destContainer = createContainerWidget(d->m_destPreview, destSizeLabel, d->m_destDateLabel);
397 gridLayout->addWidget(destContainer, gridRow, 2, 1, 2);
398
399 // Verdicts
400 auto *hbox_verdicts = new QHBoxLayout();
401 pLayout->addLayout(hbox_verdicts);
402 hbox_verdicts->addStretch(1);
403
404 if (mtimeSrc > mtimeDest) {
405 hbox_verdicts->addWidget(createLabel(this, i18n("The source is <b>more recent</b>.")));
406 } else if (mtimeDest > mtimeSrc) {
407 hbox_verdicts->addWidget(createLabel(this, i18n("The source is <b>older</b>.")));
408 };
409
410 if (d->srcItem.entry().contains(KIO::UDSEntry::UDS_SIZE) && d->destItem.entry().contains(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(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(d->src.toLocalFile(), 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(this, text);
442 if (CompareFilesResult == CompareFilesResult::PartiallyIdentical) {
443 auto *pixmapLabel = new QLabel(this);
444 pixmapLabel->setPixmap(QIcon::fromTheme(QStringLiteral("help-about")).pixmap(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(hbox);
453 } else {
454 hbox_verdicts->addWidget(filesIdenticalLabel);
455 }
456 }
457 hbox_verdicts->addStretch(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);
474 pLayout->addWidget(lb);
475 }
476
477 if (!(_options & RenameDialog_OverwriteItself) && !(_options & RenameDialog_NoRename)) {
478 if (_options & RenameDialog_Overwrite) {
479 pLayout->addSpacing(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(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(fileName));
495
496 connect(d->m_pLineEdit, &QLineEdit::textChanged, this, &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(0, 10, 0, 0); // add some space above the bottom row with buttons
512 layout->addStretch(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(this, &KIO::RenameDialog::resizePanels, Qt::QueuedConnection);
548 }
549#endif
550}
551
552RenameDialog::~RenameDialog() = default;
553
554void RenameDialog::enableRenameButton(const QString &newDest)
555{
556 if (newDest != KIO::decodeFileName(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
573{
574 const QString fileName = d->m_pLineEdit->text();
575 QUrl newDest = d->dest.adjusted(QUrl::RemoveFilename); // keeps trailing slash
576 newDest.setPath(newDest.path() + KIO::encodeFileName(fileName));
577 return newDest;
578}
579
581{
582 const QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
583 const QString newName = KFileUtils::suggestName(destDirectory, d->dest.fileName());
584 QUrl newDest(destDirectory);
585 newDest.setPath(Utils::concatPaths(newDest.path(), 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(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(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
624 d->setRenameBoxText(KFileUtils::suggestName(destDirectory, 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()) {
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(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(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(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(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(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(destDateWidth, srcDateWidth);
744 d->m_srcDateLabel->setMinimumWidth(minDateWidth);
745 d->m_destDateLabel->setMinimumWidth(minDateWidth);
746
747 KIO::PreviewJob *srcJob = KIO::filePreview(KFileItemList{d->srcItem}, QSize(d->m_srcPreview->width() * qreal(0.9), d->m_srcPreview->height()));
749
750 KIO::PreviewJob *destJob = KIO::filePreview(KFileItemList{d->destItem}, QSize(d->m_destPreview->width() * qreal(0.9), d->m_destPreview->height()));
752
753 connect(srcJob, &PreviewJob::gotPreview, this, &RenameDialog::showSrcPreview);
754 connect(destJob, &PreviewJob::gotPreview, this, &RenameDialog::showDestPreview);
755 connect(srcJob, &PreviewJob::failed, this, &RenameDialog::showSrcIcon);
756 connect(destJob, &PreviewJob::failed, this, &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(1);
765 containerLayout->addWidget(preview);
766
767 auto *detailsLayout = new QVBoxLayout(widgetContainer);
768 detailsLayout->addStretch(1);
769 detailsLayout->addWidget(SizeLabel);
770 detailsLayout->addWidget(DateLabel);
771 detailsLayout->addStretch(1);
772
773 containerLayout->addLayout(detailsLayout);
774 containerLayout->addStretch(1);
775
776 return widgetContainer;
777}
778
779#include "moc_renamedialog.cpp"
List of KFileItems, which adds a few helper methods to QList<KFileItem>.
Definition kfileitem.h:632
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)
KIO Job to get a thumbnail picture.
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.
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...
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
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 addWidget(QWidget *w)
void setContentsMargins(const QMargins &margins)
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)
void setObjectName(QAnyStringView name)
bool isEmpty() const const
qsizetype length() const const
AlignHCenter
QueuedConnection
WhatsThisCursor
PlainText
ToolButtonTextBesideIcon
PreferLocalFile
QString errorString() 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-2024 The KDE developers.
Generated on Sat Dec 21 2024 16:58:45 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.