KIO

renamedialog.cpp
1 /*
2  This file is part of the KDE libraries
3  SPDX-FileCopyrightText: 2000 Stephan Kulow <[email protected]>
4  SPDX-FileCopyrightText: 1999-2008 David Faure <[email protected]>
5  SPDX-FileCopyrightText: 2001, 2006 Holger Freyther <[email protected]>
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 
14 #include <QApplication>
15 #include <QCheckBox>
16 #include <QDate>
17 #include <QLabel>
18 #include <QLayout>
19 #include <QLineEdit>
20 #include <QMenu>
21 #include <QMimeDatabase>
22 #include <QPixmap>
23 #include <QPushButton>
24 #include <QScreen>
25 #include <QScrollArea>
26 #include <QScrollBar>
27 #include <QToolButton>
28 
29 #include <KFileUtils>
30 #include <KGuiItem>
31 #include <KIconLoader>
32 #include <KLocalizedString>
33 #include <KMessageBox>
34 #include <KSeparator>
35 #include <KSqueezedTextLabel>
36 #include <KStandardGuiItem>
37 #include <KStringHandler>
38 #include <kfileitem.h>
39 #include <kio/udsentry.h>
40 #include <previewjob.h>
41 
42 using namespace KIO;
43 
44 static QLabel *createLabel(QWidget *parent, const QString &text, bool containerTitle = false)
45 {
46  QLabel *label = new QLabel(parent);
47 
48  if (containerTitle) {
49  QFont font = label->font();
50  font.setBold(true);
51  label->setFont(font);
52  }
53 
54  label->setAlignment(Qt::AlignHCenter);
56  label->setText(text);
57  return label;
58 }
59 
60 static QLabel *createDateLabel(QWidget *parent, const KFileItem &item)
61 {
62  const bool hasDate = item.entry().contains(KIO::UDSEntry::UDS_MODIFICATION_TIME);
63  const QString text = hasDate ? i18n("Date: %1", item.timeString(KFileItem::ModificationTime)) : QString();
64  return createLabel(parent, text);
65 }
66 
67 static QLabel *createSizeLabel(QWidget *parent, const KFileItem &item)
68 {
69  const bool hasSize = item.entry().contains(KIO::UDSEntry::UDS_SIZE);
70  const QString text = hasSize ? i18n("Size: %1", KIO::convertSize(item.size())) : QString();
71  return createLabel(parent, text);
72 }
73 
74 static KSqueezedTextLabel *createSqueezedLabel(QWidget *parent, const QString &text)
75 {
76  KSqueezedTextLabel *label = new KSqueezedTextLabel(text, parent);
77  label->setAlignment(Qt::AlignHCenter);
79  return label;
80 }
81 
82 enum CompareFilesResult {
83  Identical,
84  PartiallyIdentical,
85  Different,
86 };
87 static CompareFilesResult compareFiles(const QString &filepath, const QString &secondFilePath)
88 {
89  const qint64 bufferSize = 4096; // 4kb
90  QFile f(filepath);
91  QFile f2(secondFilePath);
92  const auto fileSize = f.size();
93 
94  if (fileSize != f2.size()) {
95  return CompareFilesResult::Different;
96  }
97  if (!f.open(QFile::ReadOnly)) {
98  qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f.fileName();
99  return CompareFilesResult::Different;
100  }
101  if (!f2.open(QFile::ReadOnly)) {
102  f.close();
103  qCWarning(KIO_WIDGETS) << "Could not open file for comparison:" << f2.fileName();
104  return CompareFilesResult::Different;
105  }
106 
107  QByteArray buffer(bufferSize, 0);
108  QByteArray buffer2(bufferSize, 0);
109 
110  bool result = true;
111 
112  auto seekFillBuffer = [bufferSize](qint64 pos, QFile &f, QByteArray &buffer) {
113  auto ioresult = f.seek(pos);
114  if (ioresult) {
115  const int bytesRead = f.read(buffer.data(), bufferSize);
116  ioresult = bytesRead != -1;
117  }
118  if (!ioresult) {
119  qCWarning(KIO_WIDGETS) << "Could not read file for comparison:" << f.fileName();
120  return false;
121  }
122  return true;
123  };
124 
125  // compare at the beginning of the files
126  result = result && seekFillBuffer(0, f, buffer);
127  result = result && seekFillBuffer(0, f2, buffer2);
128  result = result && buffer == buffer2;
129 
130  if (result && fileSize > 2 * bufferSize) {
131  // compare the contents in the middle of the files
132  result = result && seekFillBuffer(fileSize / 2 - bufferSize / 2, f, buffer);
133  result = result && seekFillBuffer(fileSize / 2 - bufferSize / 2, f2, buffer2);
134  result = result && buffer == buffer2;
135  }
136 
137  if (result && fileSize > bufferSize) {
138  // compare the contents at the end of the files
139  result = result && seekFillBuffer(fileSize - bufferSize, f, buffer);
140  result = result && seekFillBuffer(fileSize - bufferSize, f2, buffer2);
141  result = result && buffer == buffer2;
142  }
143 
144  if (!result) {
145  return CompareFilesResult::Different;
146  }
147 
148  if (fileSize <= bufferSize * 3) {
149  // for files smaller than bufferSize * 3, we in fact compared fully the files
150  return CompareFilesResult::Identical;
151  } else {
152  return CompareFilesResult::PartiallyIdentical;
153  }
154 }
155 
156 /** @internal */
157 class Q_DECL_HIDDEN RenameDialog::RenameDialogPrivate
158 {
159 public:
160  RenameDialogPrivate()
161  {
162  }
163 
164  void setRenameBoxText(const QString &fileName)
165  {
166  // sets the text in file name line edit box, selecting the filename (but not the extension if there is one).
167  QMimeDatabase db;
168  const QString extension = db.suffixForFileName(fileName);
169  m_pLineEdit->setText(fileName);
170 
171  if (!extension.isEmpty()) {
172  const int selectionLength = fileName.length() - extension.length() - 1;
173  m_pLineEdit->setSelection(0, selectionLength);
174  } else {
175  m_pLineEdit->selectAll();
176  }
177  }
178 
179  QPushButton *bCancel = nullptr;
180  QPushButton *bRename = nullptr;
181  QPushButton *bSkip = nullptr;
182  QToolButton *bOverwrite = nullptr;
183  QAction *bOverwriteWhenOlder = nullptr;
184  QPushButton *bResume = nullptr;
185  QPushButton *bSuggestNewName = nullptr;
186  QCheckBox *bApplyAll = nullptr;
187  QLineEdit *m_pLineEdit = nullptr;
188  QUrl src;
189  QUrl dest;
190  bool m_srcPendingPreview = false;
191  bool m_destPendingPreview = false;
192  QLabel *m_srcPreview = nullptr;
193  QLabel *m_destPreview = nullptr;
194  QScrollArea *m_srcArea = nullptr;
195  QScrollArea *m_destArea = nullptr;
196  KFileItem srcItem;
197  KFileItem destItem;
198 };
199 
201  const QString &title,
202  const QUrl &_src,
203  const QUrl &_dest,
204  RenameDialog_Options _options,
205  KIO::filesize_t sizeSrc,
206  KIO::filesize_t sizeDest,
207  const QDateTime &ctimeSrc,
208  const QDateTime &ctimeDest,
209  const QDateTime &mtimeSrc,
210  const QDateTime &mtimeDest)
211  : QDialog(parent)
212  , d(new RenameDialogPrivate)
213 {
214  setObjectName(QStringLiteral("KIO::RenameDialog"));
215 
216  d->src = _src;
217  d->dest = _dest;
218 
219  setWindowTitle(title);
220 
221  d->bCancel = new QPushButton(this);
223  connect(d->bCancel, &QAbstractButton::clicked, this, &RenameDialog::cancelPressed);
224 
225  if (_options & RenameDialog_MultipleItems) {
226  d->bApplyAll = new QCheckBox(i18n("Appl&y to All"), this);
227  d->bApplyAll->setToolTip((_options & RenameDialog_DestIsDirectory) ? i18n("When this is checked the button pressed will be applied to all "
228  "subsequent folder conflicts for the remainder of the current job.\n"
229  "Unless you press Skip you will still be prompted in case of a "
230  "conflict with an existing file in the directory.")
231  : i18n("When this is checked the button pressed will be applied to "
232  "all subsequent conflicts for the remainder of the current job."));
233  connect(d->bApplyAll, &QAbstractButton::clicked, this, &RenameDialog::applyAllPressed);
234  }
235 
236  if (!(_options & RenameDialog_NoRename)) {
237  d->bRename = new QPushButton(i18n("&Rename"), this);
238  d->bRename->setEnabled(false);
239  d->bSuggestNewName = new QPushButton(i18n("Suggest New &Name"), this);
240  connect(d->bSuggestNewName, &QAbstractButton::clicked, this, &RenameDialog::suggestNewNamePressed);
241  connect(d->bRename, &QAbstractButton::clicked, this, &RenameDialog::renamePressed);
242  }
243 
244  if ((_options & RenameDialog_MultipleItems) && (_options & RenameDialog_Skip)) {
245  d->bSkip = new QPushButton(i18n("&Skip"), this);
246  d->bSkip->setToolTip((_options & RenameDialog_DestIsDirectory) ? i18n("Do not copy or move this folder, skip to the next item instead")
247  : i18n("Do not copy or move this file, skip to the next item instead"));
248  connect(d->bSkip, &QAbstractButton::clicked, this, &RenameDialog::skipPressed);
249  }
250 
251  if (_options & RenameDialog_Overwrite) {
252  d->bOverwrite = new QToolButton(this);
253  d->bOverwrite->setText(KStandardGuiItem::overwrite().text());
254  d->bOverwrite->setIcon(QIcon::fromTheme(KStandardGuiItem::overwrite().iconName()));
255  d->bOverwrite->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
256 
257  if (_options & RenameDialog_DestIsDirectory) {
258  d->bOverwrite->setText(i18nc("Write files into an existing folder", "&Write Into"));
259  d->bOverwrite->setIcon(QIcon());
260  d->bOverwrite->setToolTip(
261  i18n("Files and folders will be copied into the existing directory, alongside its existing contents.\nYou will be prompted again in case of a "
262  "conflict with an existing file in the directory."));
263 
264  } else if ((_options & RenameDialog_MultipleItems) && mtimeSrc.isValid() && mtimeDest.isValid()) {
265  d->bOverwriteWhenOlder = new QAction(QIcon::fromTheme(KStandardGuiItem::overwrite().iconName()),
266  i18nc("Overwrite files into an existing folder when files are older", "&Overwrite older files"),
267  this);
268  d->bOverwriteWhenOlder->setEnabled(false);
269  d->bOverwriteWhenOlder->setToolTip(
270  i18n("Destination files which have older modification times will be overwritten by the source, skipped otherwise."));
271  connect(d->bOverwriteWhenOlder, &QAction::triggered, this, &RenameDialog::overwriteWhenOlderPressed);
272 
273  QMenu *overwriteMenu = new QMenu();
274  overwriteMenu->addAction(d->bOverwriteWhenOlder);
275  d->bOverwrite->setMenu(overwriteMenu);
276  d->bOverwrite->setPopupMode(QToolButton::MenuButtonPopup);
277  }
278  connect(d->bOverwrite, &QAbstractButton::clicked, this, &RenameDialog::overwritePressed);
279  }
280 
281  if (_options & RenameDialog_Resume) {
282  d->bResume = new QPushButton(i18n("&Resume"), this);
283  connect(d->bResume, &QAbstractButton::clicked, this, &RenameDialog::resumePressed);
284  }
285 
286  QVBoxLayout *pLayout = new QVBoxLayout(this);
287  pLayout->addStrut(400); // makes dlg at least that wide
288 
289  // User tries to overwrite a file with itself ?
290  if (_options & RenameDialog_OverwriteItself) {
291  QLabel *lb = new QLabel(i18n("This action would overwrite '%1' with itself.\n"
292  "Please enter a new file name:",
293  KStringHandler::csqueeze(d->src.toDisplayString(QUrl::PreferLocalFile), 100)),
294  this);
296 
297  d->bRename->setText(i18n("C&ontinue"));
298  pLayout->addWidget(lb);
299  } else if (_options & RenameDialog_Overwrite) {
300  if (d->src.isLocalFile()) {
301  d->srcItem = KFileItem(d->src);
302  } else {
303  UDSEntry srcUds;
304 
305  srcUds.reserve(4);
306  srcUds.fastInsert(UDSEntry::UDS_NAME, d->src.fileName());
307  if (mtimeSrc.isValid()) {
309  }
310  if (ctimeSrc.isValid()) {
311  srcUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeSrc.toMSecsSinceEpoch() / 1000);
312  }
313  if (sizeSrc != KIO::filesize_t(-1)) {
314  srcUds.fastInsert(UDSEntry::UDS_SIZE, sizeSrc);
315  }
316 
317  d->srcItem = KFileItem(srcUds, d->src);
318  }
319 
320  if (d->dest.isLocalFile()) {
321  d->destItem = KFileItem(d->dest);
322  } else {
323  UDSEntry destUds;
324 
325  destUds.reserve(4);
326  destUds.fastInsert(UDSEntry::UDS_NAME, d->dest.fileName());
327  if (mtimeDest.isValid()) {
328  destUds.fastInsert(UDSEntry::UDS_MODIFICATION_TIME, mtimeDest.toMSecsSinceEpoch() / 1000);
329  }
330  if (ctimeDest.isValid()) {
331  destUds.fastInsert(UDSEntry::UDS_CREATION_TIME, ctimeDest.toMSecsSinceEpoch() / 1000);
332  }
333  if (sizeDest != KIO::filesize_t(-1)) {
334  destUds.fastInsert(UDSEntry::UDS_SIZE, sizeDest);
335  }
336 
337  d->destItem = KFileItem(destUds, d->dest);
338  }
339 
340  d->m_srcPreview = createLabel(this, QString());
341  d->m_destPreview = createLabel(this, QString());
342  QLabel *srcToDestArrow = createLabel(this, QString());
343 
344  d->m_srcPreview->setMinimumHeight(KIconLoader::SizeEnormous);
345  d->m_destPreview->setMinimumHeight(KIconLoader::SizeEnormous);
347 
348  d->m_srcPreview->setAlignment(Qt::AlignCenter);
349  d->m_destPreview->setAlignment(Qt::AlignCenter);
350  srcToDestArrow->setAlignment(Qt::AlignCenter);
351 
352  d->m_srcPendingPreview = true;
353  d->m_destPendingPreview = true;
354 
355  // widget
356  d->m_srcArea = createContainerLayout(this, d->srcItem, d->m_srcPreview);
357  d->m_destArea = createContainerLayout(this, d->destItem, d->m_destPreview);
358 
359  connect(d->m_srcArea->verticalScrollBar(), &QAbstractSlider::valueChanged, d->m_destArea->verticalScrollBar(), &QAbstractSlider::setValue);
360  connect(d->m_destArea->verticalScrollBar(), &QAbstractSlider::valueChanged, d->m_srcArea->verticalScrollBar(), &QAbstractSlider::setValue);
361  connect(d->m_srcArea->horizontalScrollBar(), &QAbstractSlider::valueChanged, d->m_destArea->horizontalScrollBar(), &QAbstractSlider::setValue);
362  connect(d->m_destArea->horizontalScrollBar(), &QAbstractSlider::valueChanged, d->m_srcArea->horizontalScrollBar(), &QAbstractSlider::setValue);
363 
364  // create layout
365  QGridLayout *gridLayout = new QGridLayout();
366  pLayout->addLayout(gridLayout);
367 
368  int gridRow = 0;
369  QLabel *titleLabel = new QLabel(i18n("This action will overwrite the destination."), this);
370  gridLayout->addWidget(titleLabel, gridRow, 0, 1, 2); // takes the complete first line
371 
372  gridLayout->setRowMinimumHeight(++gridRow, 15); // spacer
373 
374  QLabel *srcTitle = createLabel(this, i18n("Source"), true);
375  gridLayout->addWidget(srcTitle, ++gridRow, 0);
376  QLabel *destTitle = createLabel(this, i18n("Destination"), true);
377  gridLayout->addWidget(destTitle, gridRow, 2);
378 
379  QLabel *srcUrlLabel = createSqueezedLabel(this, d->src.toDisplayString(QUrl::PreferLocalFile));
380  srcUrlLabel->setTextFormat(Qt::PlainText);
381  gridLayout->addWidget(srcUrlLabel, ++gridRow, 0);
382  QLabel *destUrlLabel = createSqueezedLabel(this, d->dest.toDisplayString(QUrl::PreferLocalFile));
383  destUrlLabel->setTextFormat(Qt::PlainText);
384  gridLayout->addWidget(destUrlLabel, gridRow, 2);
385 
386  gridLayout->addWidget(d->m_srcArea, ++gridRow, 0, 2, 1);
387 
388  // The labels containing previews or icons, and an arrow icon indicating
389  // direction from src to dest
390  const QString arrowName = qApp->isRightToLeft() ? QStringLiteral("go-previous") : QStringLiteral("go-next");
391  const QPixmap pix = QIcon::fromTheme(arrowName).pixmap(d->m_srcPreview->height());
392  srcToDestArrow->setPixmap(pix);
393  gridLayout->addWidget(srcToDestArrow, gridRow, 1);
394 
395  gridLayout->addWidget(d->m_destArea, gridRow, 2);
396 
397  QLabel *diffTitle = createLabel(this, i18n("Differences"), true);
398  gridLayout->addWidget(diffTitle, ++gridRow, 1);
399 
400  QLabel *srcDateLabel = createDateLabel(this, d->srcItem);
401  gridLayout->addWidget(srcDateLabel, ++gridRow, 0);
402 
403  if (mtimeDest > mtimeSrc) {
404  gridLayout->addWidget(createLabel(this, QStringLiteral("The destination is <b>more recent</b>")), gridRow, 1);
405  }
406  QLabel *destDateLabel = createLabel(this, i18n("Date: %1", d->destItem.timeString(KFileItem::ModificationTime)));
407  gridLayout->addWidget(destDateLabel, gridRow, 2);
408 
409  QLabel *srcSizeLabel = createSizeLabel(this, d->srcItem);
410  gridLayout->addWidget(srcSizeLabel, ++gridRow, 0);
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->srcItem.size() > d->destItem.size()) {
417  diff = d->srcItem.size() - d->destItem.size();
418  text = i18n("The destination is <b>smaller by %1</b>", KIO::convertSize(diff));
419  } else {
420  diff = d->destItem.size() - d->srcItem.size();
421  text = i18n("The destination is <b>bigger by %1</b>", KIO::convertSize(diff));
422  }
423  gridLayout->addWidget(createLabel(this, text), gridRow, 1);
424  }
425  QLabel *destSizeLabel = createLabel(this, i18n("Size: %1", KIO::convertSize(d->destItem.size())));
426  gridLayout->addWidget(destSizeLabel, gridRow, 2);
427 
428  // check files contents for local files
429  if ((d->dest.isLocalFile() && !(_options & RenameDialog_DestIsDirectory)) && (d->src.isLocalFile() && !(_options & RenameDialog_SourceIsDirectory))) {
430  const CompareFilesResult CompareFilesResult = compareFiles(d->src.toLocalFile(), d->dest.toLocalFile());
431 
432  QString text;
433  switch (CompareFilesResult) {
434  case CompareFilesResult::Identical:
435  text = i18n("The files are identical.");
436  break;
437  case CompareFilesResult::PartiallyIdentical:
438  text = i18n("The files seem identical.");
439  break;
440  case CompareFilesResult::Different:
441  text = i18n("The files are different.");
442  break;
443  }
444  QLabel *filesIdenticalLabel = createLabel(this, text, true);
445  if (CompareFilesResult == CompareFilesResult::PartiallyIdentical) {
446  QLabel *pixmapLabel = new QLabel(this);
447  pixmapLabel->setPixmap(QIcon::fromTheme(QStringLiteral("help-about")).pixmap(QSize(16, 16)));
448  pixmapLabel->setToolTip(
449  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."));
450  pixmapLabel->setCursor(Qt::WhatsThisCursor);
451 
452  QHBoxLayout *hbox = new QHBoxLayout(this);
453  hbox->addWidget(filesIdenticalLabel);
454  hbox->addWidget(pixmapLabel);
455  gridLayout->addLayout(hbox, gridRow + 1, 1);
456  } else {
457  gridLayout->addWidget(filesIdenticalLabel, gridRow + 1, 1);
458  }
459  }
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  QLabel *lb2 = new QLabel(i18n("Rename:"), this);
485  pLayout->addWidget(lb2);
486  }
487 
488  QHBoxLayout *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  KSeparator *separator = new KSeparator(this);
511  pLayout->addWidget(separator);
512 
513  QHBoxLayout *layout = new QHBoxLayout();
514  pLayout->addLayout(layout);
515 
516  layout->addStretch(1);
517 
518  if (d->bApplyAll) {
519  layout->addWidget(d->bApplyAll);
520  setTabOrder(d->bApplyAll, d->bCancel);
521  }
522 
523  if (d->bRename) {
524  layout->addWidget(d->bRename);
525  setTabOrder(d->bRename, d->bCancel);
526  }
527 
528  if (d->bSkip) {
529  layout->addWidget(d->bSkip);
530  setTabOrder(d->bSkip, d->bCancel);
531  }
532 
533  if (d->bOverwrite) {
534  layout->addWidget(d->bOverwrite);
535  setTabOrder(d->bOverwrite, d->bCancel);
536  }
537 
538  if (d->bResume) {
539  layout->addWidget(d->bResume);
540  setTabOrder(d->bResume, d->bCancel);
541  }
542 
543  d->bCancel->setDefault(true);
544  layout->addWidget(d->bCancel);
545 
546  resize(sizeHint());
547 
548 #if 1 // without kfilemetadata
549  // don't wait for kfilemetadata, but wait until the layouting is done
550  if (_options & RenameDialog_Overwrite) {
551  QMetaObject::invokeMethod(this, &KIO::RenameDialog::resizePanels, Qt::QueuedConnection);
552  }
553 #endif
554 }
555 
556 RenameDialog::~RenameDialog() = default;
557 
558 void RenameDialog::enableRenameButton(const QString &newDest)
559 {
560  if (newDest != KIO::decodeFileName(d->dest.fileName()) && !newDest.isEmpty()) {
561  d->bRename->setEnabled(true);
562  d->bRename->setDefault(true);
563 
564  if (d->bOverwrite) {
565  d->bOverwrite->setEnabled(false); // prevent confusion (#83114)
566  }
567  } else {
568  d->bRename->setEnabled(false);
569 
570  if (d->bOverwrite) {
571  d->bOverwrite->setEnabled(true);
572  }
573  }
574 }
575 
577 {
578  const QString fileName = d->m_pLineEdit->text();
579  QUrl newDest = d->dest.adjusted(QUrl::RemoveFilename); // keeps trailing slash
580  newDest.setPath(newDest.path() + KIO::encodeFileName(fileName));
581  return newDest;
582 }
583 
585 {
586  const QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
587  const QString newName = KFileUtils::suggestName(destDirectory, d->dest.fileName());
588  QUrl newDest(destDirectory);
589  newDest.setPath(Utils::concatPaths(newDest.path(), newName));
590  return newDest;
591 }
592 
593 void RenameDialog::cancelPressed()
594 {
595  done(Result_Cancel);
596 }
597 
598 // Rename
599 void RenameDialog::renamePressed()
600 {
601  if (d->m_pLineEdit->text().isEmpty()) {
602  return;
603  }
604 
605  if (d->bApplyAll && d->bApplyAll->isChecked()) {
606  done(Result_AutoRename);
607  } else {
608  const QUrl u = newDestUrl();
609  if (!u.isValid()) {
610  KMessageBox::error(this, i18n("Malformed URL\n%1", u.errorString()));
611  qCWarning(KIO_WIDGETS) << u.errorString();
612  return;
613  }
614 
615  done(Result_Rename);
616  }
617 }
618 
619 #if KIOWIDGETS_BUILD_DEPRECATED_SINCE(5, 0)
620 QString RenameDialog::suggestName(const QUrl &baseURL, const QString &oldName)
621 {
622  return KFileUtils::suggestName(baseURL, oldName);
623 }
624 #endif
625 
626 // Propose button clicked
627 void RenameDialog::suggestNewNamePressed()
628 {
629  /* no name to play with */
630  if (d->m_pLineEdit->text().isEmpty()) {
631  return;
632  }
633 
634  QUrl destDirectory = d->dest.adjusted(QUrl::RemoveFilename | QUrl::StripTrailingSlash);
635  d->setRenameBoxText(KFileUtils::suggestName(destDirectory, d->m_pLineEdit->text()));
636 }
637 
638 void RenameDialog::skipPressed()
639 {
640  if (d->bApplyAll && d->bApplyAll->isChecked()) {
641  done(Result_AutoSkip);
642  } else {
643  done(Result_Skip);
644  }
645 }
646 
647 void RenameDialog::autoSkipPressed()
648 {
649  done(Result_AutoSkip);
650 }
651 
652 void RenameDialog::overwritePressed()
653 {
654  if (d->bApplyAll && d->bApplyAll->isChecked()) {
655  done(Result_OverwriteAll);
656  } else {
657  done(Result_Overwrite);
658  }
659 }
660 
661 void RenameDialog::overwriteWhenOlderPressed()
662 {
663  if (d->bApplyAll && d->bApplyAll->isChecked()) {
665  }
666 }
667 
668 void RenameDialog::overwriteAllPressed()
669 {
670  done(Result_OverwriteAll);
671 }
672 
673 void RenameDialog::resumePressed()
674 {
675  if (d->bApplyAll && d->bApplyAll->isChecked()) {
676  done(Result_ResumeAll);
677  } else {
678  done(Result_Resume);
679  }
680 }
681 
682 void RenameDialog::resumeAllPressed()
683 {
684  done(Result_ResumeAll);
685 }
686 
687 void RenameDialog::applyAllPressed()
688 {
689  const bool applyAll = d->bApplyAll && d->bApplyAll->isChecked();
690 
691  if (applyAll) {
692  d->m_pLineEdit->setText(KIO::decodeFileName(d->dest.fileName()));
693  d->m_pLineEdit->setEnabled(false);
694  } else {
695  d->m_pLineEdit->setEnabled(true);
696  }
697 
698  if (d->bRename) {
699  d->bRename->setEnabled(applyAll);
700  }
701 
702  if (d->bSuggestNewName) {
703  d->bSuggestNewName->setEnabled(!applyAll);
704  }
705 
706  if (d->bOverwriteWhenOlder) {
707  d->bOverwriteWhenOlder->setEnabled(applyAll);
708  }
709 }
710 
711 void RenameDialog::showSrcIcon(const KFileItem &fileitem)
712 {
713  // The preview job failed, show a standard file icon.
714  d->m_srcPendingPreview = false;
715 
716  const int size = d->m_srcPreview->height();
717  const QPixmap pix = QIcon::fromTheme(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(size);
718  d->m_srcPreview->setPixmap(pix);
719 }
720 
721 void RenameDialog::showDestIcon(const KFileItem &fileitem)
722 {
723  // The preview job failed, show a standard file icon.
724  d->m_destPendingPreview = false;
725 
726  const int size = d->m_destPreview->height();
727  const QPixmap pix = QIcon::fromTheme(fileitem.iconName(), QIcon::fromTheme(QStringLiteral("application-octet-stream"))).pixmap(size);
728  d->m_destPreview->setPixmap(pix);
729 }
730 
731 void RenameDialog::showSrcPreview(const KFileItem &fileitem, const QPixmap &pixmap)
732 {
733  Q_UNUSED(fileitem);
734 
735  if (d->m_srcPendingPreview) {
736  d->m_srcPreview->setPixmap(pixmap);
737  d->m_srcPendingPreview = false;
738  }
739 }
740 
741 void RenameDialog::showDestPreview(const KFileItem &fileitem, const QPixmap &pixmap)
742 {
743  Q_UNUSED(fileitem);
744 
745  if (d->m_destPendingPreview) {
746  d->m_destPreview->setPixmap(pixmap);
747  d->m_destPendingPreview = false;
748  }
749 }
750 
751 void RenameDialog::resizePanels()
752 {
753  Q_ASSERT(d->m_srcArea != nullptr);
754  Q_ASSERT(d->m_destArea != nullptr);
755  Q_ASSERT(d->m_srcPreview != nullptr);
756  Q_ASSERT(d->m_destPreview != nullptr);
757 
758  const QSize screenSize =
759  parentWidget() ? parentWidget()->screen()->availableGeometry().size() : QGuiApplication::primaryScreen()->availableGeometry().size();
760 
761  QSize halfSize = d->m_srcArea->widget()->sizeHint().expandedTo(d->m_destArea->widget()->sizeHint());
762  const QSize currentSize = d->m_srcArea->size().expandedTo(d->m_destArea->size());
763  const int maxHeightPossible = screenSize.height() - (size().height() - currentSize.height());
764  QSize maxHalfSize = QSize(screenSize.width() / qreal(2.1), maxHeightPossible * qreal(0.9));
765 
766  if (halfSize.height() > maxHalfSize.height() && halfSize.width() <= maxHalfSize.width() + d->m_srcArea->verticalScrollBar()->width()) {
767  halfSize.rwidth() += d->m_srcArea->verticalScrollBar()->width();
768  maxHalfSize.rwidth() += d->m_srcArea->verticalScrollBar()->width();
769  }
770 
771  d->m_srcArea->setMinimumSize(halfSize.boundedTo(maxHalfSize));
772  d->m_destArea->setMinimumSize(halfSize.boundedTo(maxHalfSize));
773 
774  KIO::PreviewJob *srcJob = KIO::filePreview(KFileItemList{d->srcItem}, QSize(d->m_srcPreview->width() * qreal(0.9), d->m_srcPreview->height()));
776 
777  KIO::PreviewJob *destJob = KIO::filePreview(KFileItemList{d->destItem}, QSize(d->m_destPreview->width() * qreal(0.9), d->m_destPreview->height()));
779 
780  connect(srcJob, &PreviewJob::gotPreview, this, &RenameDialog::showSrcPreview);
781  connect(destJob, &PreviewJob::gotPreview, this, &RenameDialog::showDestPreview);
782  connect(srcJob, &PreviewJob::failed, this, &RenameDialog::showSrcIcon);
783  connect(destJob, &PreviewJob::failed, this, &RenameDialog::showDestIcon);
784 }
785 
786 QScrollArea *RenameDialog::createContainerLayout(QWidget *parent, const KFileItem &item, QLabel *preview)
787 {
788  Q_UNUSED(item)
789 #if 0 // PENDING
790  KFileItemList itemList;
791  itemList << item;
792 
793  // KFileMetaDataWidget was deprecated for a Nepomuk widget, which is itself deprecated...
794  // If we still want metadata shown, we need a plugin that fetches data from KFileMetaData::ExtractorCollection
795  KFileMetaDataWidget *metaWidget = new KFileMetaDataWidget(this);
796 
797  metaWidget->setReadOnly(true);
798  metaWidget->setItems(itemList);
799  // ### This is going to call resizePanels twice! Need to split it up to do preview job only once on each side
800  connect(metaWidget, SIGNAL(metaDataRequestFinished(KFileItemList)), this, SLOT(resizePanels()));
801 #endif
802 
803  // Encapsulate the MetaDataWidgets inside a container with stretch at the bottom.
804  // This prevents that the meta data widgets get vertically stretched
805  // in the case where the height of m_metaDataArea > m_metaDataWidget.
806 
807  QWidget *widgetContainer = new QWidget(parent);
808  QVBoxLayout *containerLayout = new QVBoxLayout(widgetContainer);
809 
810  containerLayout->setContentsMargins(0, 0, 0, 0);
811  containerLayout->setSpacing(0);
812  containerLayout->addWidget(preview);
813  containerLayout->addStretch(1);
814 
815  QScrollArea *metaDataArea = new QScrollArea(parent);
816 
817  metaDataArea->setWidget(widgetContainer);
818  metaDataArea->setWidgetResizable(true);
819  metaDataArea->setFrameShape(QFrame::NoFrame);
820 
821  return metaDataArea;
822 }
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.
PlainText
AlignHCenter
void setRowMinimumHeight(int row, int minSize)
QWidget(QWidget *parent, Qt::WindowFlags f)
KIOCORE_EXPORT QString convertSize(KIO::filesize_t size)
Converts size from bytes to the string representation.
Definition: global.cpp:43
qulonglong filesize_t
64-bit file size
Definition: global.h:39
QLayout * layout() const const
void setTextFormat(Qt::TextFormat)
void clicked(bool checked)
QIcon fromTheme(const QString &name)
@ RenameDialog_NoRename
Don't offer a "Rename" button.
@ Result_OverwriteWhenOlder
Can be returned only when multiple files are passed, Option overwrite is passed And files modificatio...
QScreen * screen() const const
int width() const const
void setFrameShape(QFrame::Shape)
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:139
@ RenameDialog_SourceIsDirectory
The source is a directory, the dialog updates labels and tooltips accordingly.
void addStretch(int stretch)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString suffixForFileName(const QString &fileName) const const
@ UDS_NAME
Filename - as displayed in directory listings etc.
Definition: udsentry.h:249
void setScaleType(ScaleType type)
Sets the scale type for the generated preview.
Definition: previewjob.cpp:310
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
Q_INVOKABLE QString timeString(FileTimes which=ModificationTime) const
Requests the modification, access or creation time as a string, depending on which.
Definition: kfileitem.cpp:1447
QAction * addAction(const QString &text)
void setValue(int)
KGuiItem cancel()
bool isValid() const const
static void assign(QPushButton *button, const KGuiItem &item)
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT QString csqueeze(const QString &str, int maxlen=40)
int height() const const
KIOCORE_EXPORT QString decodeFileName(const QString &str)
Decodes (from the filename to the text displayed) This doesn't do anything anymore,...
Definition: global.cpp:146
PreferLocalFile
void setTabOrder(QWidget *first, QWidget *second)
void setAlignment(Qt::Alignment)
void addLayout(QLayout *layout, int row, int column, Qt::Alignment alignment)
@ RenameDialog_DestIsDirectory
The destination is a directory, the dialog updates labels and tooltips accordingly.
void textChanged(const QString &text)
bool isEmpty() const const
WhatsThisCursor
int length() const const
void setWindowTitle(const QString &)
@ UDS_SIZE
Size of the file.
Definition: udsentry.h:230
void reserve(int size)
Calling this function before inserting items into an empty UDSEntry may save time and memory.
Definition: udsentry.cpp:396
@ RenameDialog_Overwrite
We have an existing destination, show details about it and offer to overwrite it.
qint64 toMSecsSinceEpoch() const const
QSize boundedTo(const QSize &otherSize) const const
QueuedConnection
void setReadOnly(bool readOnly)
@ Unscaled
The original size of the preview will be returned.
Definition: previewjob.h:45
KCOREADDONS_EXPORT QString suggestName(const QUrl &baseURL, const QString &oldName)
Given a directory path and a filename (which usually exists already), this function returns a suggest...
Definition: global.cpp:280
virtual void done(int r)
@ RenameDialog_OverwriteItself
Warn that the current operation would overwrite a file with itself, which is not allowed.
QString errorString() const const
int & rwidth()
KIO::UDSEntry entry() const
Returns the UDS entry.
Definition: kfileitem.cpp:1689
void failed(const KFileItem &item)
Emitted when a thumbnail for item could not be created, either because a ThumbCreator for its MIME ty...
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
void setMinimumHeight(int minh)
QString label(StandardShortcut id)
static QString suggestName(const QUrl &baseURL, const QString &oldName)
Given a directory path and a filename (which usually exists already), this function returns a suggest...
void setWidgetResizable(bool resizable)
KIO::filesize_t size() const
Returns the size of the file, if known.
Definition: kfileitem.cpp:735
void resize(int w, int h)
void fastInsert(uint field, const QString &value)
insert field with string value, it will assert if the field is already inserted.
Definition: udsentry.cpp:401
void triggered(bool checked)
QString path(QUrl::ComponentFormattingOptions options) const const
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
void setToolTip(const QString &)
@ UDS_MODIFICATION_TIME
The last time the file was modified. Required time format: seconds since UNIX epoch.
Definition: udsentry.h:259
void setSpacing(int spacing)
KIO Job to get a thumbnail picture.
Definition: previewjob.h:30
void setObjectName(const QString &name)
void setPath(const QString &path, QUrl::ParsingMode mode)
void addWidget(QWidget *w)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
A namespace for KIO globals.
bool isValid() const const
void setContentsMargins(int left, int top, int right, int bottom)
void valueChanged(int value)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isRightToLeft() const const
ToolButtonTextBesideIcon
void setItems(const KFileItemList &items)
void addWidget(QWidget *widget, int row, int column, Qt::Alignment alignment)
void gotPreview(const KFileItem &item, const QPixmap &preview)
Emitted when a thumbnail picture for item has been successfully retrieved.
@ RenameDialog_MultipleItems
Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply t...
void addLayout(QLayout *layout, int stretch)
void setBold(bool enable)
void addSpacing(int size)
void setPixmap(const QPixmap &)
KGuiItem overwrite()
QUrl autoDestUrl() const
QWidget * parentWidget() const const
bool contains(uint field) const
check existence of a field
Definition: udsentry.cpp:452
@ RenameDialog_Skip
Offer a "Skip" button, to skip other files too. Requires RenameDialog_MultipleItems.
void addStrut(int size)
QObject * parent() const const
KIOWIDGETS_EXPORT PreviewJob * filePreview(const KFileItemList &items, int width, int height=0, int iconSize=0, int iconAlpha=70, bool scale=true, bool save=true, const QStringList *enabledPlugins=nullptr)
Creates a PreviewJob to generate or retrieve a preview image for the given URL.
void setWidget(QWidget *widget)
@ RenameDialog_Resume
Offer a "Resume" button (plus "Resume All" if RenameDialog_MultipleItems).
void setCursor(const QCursor &)
virtual QSize sizeHint() const const override
@ UDS_CREATION_TIME
The time the file was created. Required time format: seconds since UNIX epoch.
Definition: udsentry.h:263
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon May 8 2023 03:54:44 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.