1/*
2 This file is part of the KDE project
3 SPDX-FileCopyrightText: 2000 Matej Koss <koss@miesto.sk>
4 SPDX-FileCopyrightText: 2007 Kevin Ottens <ervin@kde.org>
5 SPDX-FileCopyrightText: 2007 Rafael Fernández López <ereslibre@kde.org>
6 SPDX-FileCopyrightText: 2009 Shaun Reich <shaun.reich@kdemail.net>
7
8 SPDX-License-Identifier: LGPL-2.0-only
9*/
10
11#include "kwidgetjobtracker.h"
12#include "debug.h"
13#include "kjobtrackerformatters_p.h"
14#include "kwidgetjobtracker_p.h"
15
16#include <QCoreApplication>
17#include <QDir>
18#include <QEvent>
19#include <QGridLayout>
20#include <QLabel>
21#include <QProcess>
22#include <QProgressBar>
23#include <QPushButton>
24#include <QStandardPaths>
25#include <QStyle>
26#include <QTimer>
27#include <QVBoxLayout>
28
29#include <KSeparator>
30#include <KSqueezedTextLabel>
31
32void KWidgetJobTrackerPrivate::_k_showProgressWidget()
33{
34 Q_Q(KWidgetJobTracker);
35
36 if (progressWidgetsToBeShown.isEmpty()) {
37 return;
38 }
39
40 KJob *job = progressWidgetsToBeShown.dequeue();
41
42 // If the job has been unregistered before reaching this point, widget will
43 // return 0.
44 QWidget *widget = q->widget(job);
45
46 if (widget) {
47 // Don't steal the focus from the current widget (e. g. Kate)
48 widget->setAttribute(Qt::WA_ShowWithoutActivating);
49 widget->show();
50 }
51}
52
53KWidgetJobTracker::KWidgetJobTracker(QWidget *parent)
54 : KAbstractWidgetJobTracker(*new KWidgetJobTrackerPrivate(parent, this), parent)
55{
56}
57
58KWidgetJobTracker::~KWidgetJobTracker() = default;
59
60QWidget *KWidgetJobTracker::widget(KJob *job)
61{
62 Q_D(KWidgetJobTracker);
63
64 return d->progressWidget.value(key: job, defaultValue: nullptr);
65}
66
67void KWidgetJobTracker::registerJob(KJob *job)
68{
69 Q_D(KWidgetJobTracker);
70
71 auto *vi = new KWidgetJobTrackerPrivate::ProgressWidget(job, this, d->parent);
72 vi->jobRegistered = true;
73 vi->setAttribute(Qt::WA_DeleteOnClose);
74 d->progressWidget.insert(key: job, value: vi);
75 d->progressWidgetsToBeShown.enqueue(t: job);
76
77 KAbstractWidgetJobTracker::registerJob(job);
78
79 QTimer::singleShot(msec: 500, receiver: this, SLOT(_k_showProgressWidget()));
80}
81
82void KWidgetJobTracker::unregisterJob(KJob *job)
83{
84 Q_D(KWidgetJobTracker);
85
86 KAbstractWidgetJobTracker::unregisterJob(job);
87
88 d->progressWidgetsToBeShown.removeAll(t: job);
89 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
90 if (!pWidget) {
91 return;
92 }
93
94 pWidget->jobRegistered = false;
95 pWidget->deref();
96}
97
98bool KWidgetJobTracker::keepOpen(KJob *job) const
99{
100 Q_D(const KWidgetJobTracker);
101
102 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
103 if (!pWidget) {
104 return false;
105 }
106
107 return pWidget->keepOpenCheck->isChecked();
108}
109
110void KWidgetJobTracker::infoMessage(KJob *job, const QString &message)
111{
112 Q_D(KWidgetJobTracker);
113
114 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
115 if (!pWidget) {
116 return;
117 }
118
119 pWidget->infoMessage(plain: message);
120}
121
122void KWidgetJobTracker::description(KJob *job, const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2)
123{
124 Q_D(KWidgetJobTracker);
125
126 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
127 if (!pWidget) {
128 return;
129 }
130
131 pWidget->description(title, field1, field2);
132}
133
134void KWidgetJobTracker::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount)
135{
136 Q_D(KWidgetJobTracker);
137
138 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
139 if (!pWidget) {
140 return;
141 }
142
143 pWidget->totalAmount(unit, amount);
144}
145
146void KWidgetJobTracker::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount)
147{
148 Q_D(KWidgetJobTracker);
149
150 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
151 if (!pWidget) {
152 return;
153 }
154
155 pWidget->processedAmount(unit, amount);
156}
157
158void KWidgetJobTracker::percent(KJob *job, unsigned long percent)
159{
160 Q_D(KWidgetJobTracker);
161
162 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
163 if (!pWidget) {
164 return;
165 }
166
167 pWidget->percent(percent);
168}
169
170void KWidgetJobTracker::speed(KJob *job, unsigned long value)
171{
172 Q_D(KWidgetJobTracker);
173
174 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
175 if (!pWidget) {
176 return;
177 }
178
179 pWidget->speed(value);
180}
181
182void KWidgetJobTracker::slotClean(KJob *job)
183{
184 Q_D(KWidgetJobTracker);
185
186 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
187 if (!pWidget) {
188 return;
189 }
190
191 pWidget->slotClean();
192}
193
194void KWidgetJobTracker::suspended(KJob *job)
195{
196 Q_D(KWidgetJobTracker);
197
198 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
199 if (!pWidget) {
200 return;
201 }
202
203 pWidget->suspended();
204}
205
206void KWidgetJobTracker::resumed(KJob *job)
207{
208 Q_D(KWidgetJobTracker);
209
210 KWidgetJobTrackerPrivate::ProgressWidget *pWidget = d->progressWidget.value(key: job, defaultValue: nullptr);
211 if (!pWidget) {
212 return;
213 }
214
215 pWidget->resumed();
216}
217
218void KWidgetJobTrackerPrivate::ProgressWidget::ref()
219{
220 ++refCount;
221}
222
223void KWidgetJobTrackerPrivate::ProgressWidget::deref()
224{
225 if (refCount) {
226 --refCount;
227 }
228
229 if (!refCount) {
230 if (!keepOpenCheck->isChecked()) {
231 closeNow();
232 } else {
233 slotClean();
234 }
235 }
236}
237
238void KWidgetJobTrackerPrivate::ProgressWidget::closeNow()
239{
240 close();
241
242 // It might happen the next scenario:
243 // - Start a job which opens a progress widget. Keep it open. Address job is 0xdeadbeef
244 // - Start a new job, which is given address 0xdeadbeef. A new window is opened.
245 // This one will take much longer to complete. The key 0xdeadbeef on the widget map now
246 // stores the new widget address.
247 // - Close the first progress widget that was opened (and has already finished) while the
248 // last one is still running. We remove its reference on the map. Wrong.
249 // For that reason we have to check if the map stores the widget as the current one.
250 // ereslibre
251 if (tracker->d_func()->progressWidget[job] == this) {
252 tracker->d_func()->progressWidget.remove(key: job);
253 tracker->d_func()->progressWidgetsToBeShown.removeAll(t: job);
254 }
255}
256
257bool KWidgetJobTrackerPrivate::ProgressWidget::eventFilter(QObject *watched, QEvent *event)
258{
259 // Handle context menu events for the source/dest labels here, so that we are ref()ed while the
260 // menu is exec()ed, to avoid a crash if the job finishes meanwhile. #159621.
261 if ((watched == sourceEdit || watched == destEdit) && event->type() == QEvent::ContextMenu) {
262 ref();
263 watched->event(event);
264 deref();
265 return true;
266 }
267
268 return QWidget::eventFilter(watched, event);
269}
270
271void KWidgetJobTrackerPrivate::ProgressWidget::infoMessage(const QString &message)
272{
273 speedLabel->setText(message);
274 speedLabel->setAlignment(speedLabel->alignment() & ~Qt::TextWordWrap);
275}
276
277void KWidgetJobTrackerPrivate::ProgressWidget::description(const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2)
278{
279 setWindowTitle(title);
280 caption = title;
281 sourceInvite->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1:", disambiguation: "%1 is the label, we add a ':' to it").arg(a: field1.first));
282 sourceEdit->setText(field1.second);
283
284 if (field2.first.isEmpty()) {
285 setDestVisible(false);
286 } else {
287 setDestVisible(true);
288 checkDestination(dest: QUrl::fromUserInput(userInput: field2.second)); // path or URL
289 destInvite->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1:", disambiguation: "%1 is the label, we add a ':' to it").arg(a: field2.first));
290 destEdit->setText(field2.second);
291 }
292}
293
294void KWidgetJobTrackerPrivate::ProgressWidget::totalAmount(KJob::Unit unit, qulonglong amount)
295{
296 switch (unit) {
297 case KJob::Bytes:
298 totalSizeKnown = true;
299 // size is measured in bytes
300 if (totalSize == amount) {
301 return;
302 }
303 totalSize = amount;
304 if (!startTime.isValid()) {
305 startTime.start();
306 }
307 break;
308
309 case KJob::Files:
310 if (totalFiles == amount) {
311 return;
312 }
313 totalFiles = amount;
314 showTotals();
315 break;
316
317 case KJob::Directories:
318 if (totalDirs == amount) {
319 return;
320 }
321 totalDirs = amount;
322 showTotals();
323 break;
324
325 case KJob::Items:
326 if (totalItems == amount) {
327 return;
328 }
329 totalItems = amount;
330 showTotals();
331 break;
332
333 case KJob::UnitsCount:
334 Q_UNREACHABLE();
335 break;
336 }
337}
338
339void KWidgetJobTrackerPrivate::ProgressWidget::processedAmount(KJob::Unit unit, qulonglong amount)
340{
341 QString tmp;
342
343 switch (unit) {
344 case KJob::Bytes:
345 if (processedSize == amount) {
346 return;
347 }
348 processedSize = amount;
349
350 if (totalSizeKnown) {
351 //~ singular %1 of %2 complete
352 //~ plural %1 of %2 complete
353 tmp = QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1 of %2 complete", disambiguation: "", n: amount)
354 .arg(args: KJobTrackerFormatters::byteSize(size: amount), args: KJobTrackerFormatters::byteSize(size: totalSize));
355 } else {
356 tmp = KJobTrackerFormatters::byteSize(size: amount);
357 }
358 sizeLabel->setText(tmp);
359 if (!totalSizeKnown) { // update jumping progressbar
360 progressBar->setValue(amount);
361 }
362 break;
363
364 case KJob::Directories:
365 if (processedDirs == amount) {
366 return;
367 }
368 processedDirs = amount;
369
370 //~ singular %1 / %n folder
371 //~ plural %1 / %n folders
372 tmp = QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1 / %n folder(s)", disambiguation: "", n: totalDirs).arg(a: processedDirs);
373 tmp += QLatin1String(" ");
374 //~ singular %1 / %n file
375 //~ plural %1 / %n files
376 tmp += QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1 / %n file(s)", disambiguation: "", n: totalFiles).arg(a: processedFiles);
377 progressLabel->setText(tmp);
378 break;
379
380 case KJob::Files:
381 if (processedFiles == amount) {
382 return;
383 }
384 processedFiles = amount;
385
386 if (totalDirs > 1) {
387 //~ singular %1 / %n folder
388 //~ plural %1 / %n folders
389 tmp = QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1 / %n folder(s)", disambiguation: "", n: totalDirs).arg(a: processedDirs);
390 tmp += QLatin1String(" ");
391 }
392 //~ singular %1 / %n file
393 //~ plural %1 / %n files
394 tmp += QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1 / %n file(s)", disambiguation: "", n: totalFiles).arg(a: processedFiles);
395 progressLabel->setText(tmp);
396 break;
397
398 case KJob::Items:
399 if (processedItems == amount) {
400 return;
401 }
402 processedItems = amount;
403 //~ singular %1 / %n item
404 //~ plural %1 / %n items
405 tmp = QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1 / %n item(s)", disambiguation: "", n: totalItems).arg(a: processedItems);
406 progressLabel->setText(tmp);
407 break;
408
409 case KJob::UnitsCount:
410 Q_UNREACHABLE();
411 break;
412 }
413}
414
415void KWidgetJobTrackerPrivate::ProgressWidget::percent(unsigned long percent)
416{
417 QString title = caption + QLatin1String(" (");
418
419 if (totalSizeKnown) {
420 title += QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1% of %2").arg(a: percent).arg(a: KJobTrackerFormatters::byteSize(size: totalSize));
421 } else if (totalFiles) {
422 //~ singular %1% of %n file
423 //~ plural %1% of %n files
424 title += QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1% of %n file(s)", disambiguation: "", n: totalFiles).arg(a: percent);
425 } else {
426 title += QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1%").arg(a: percent);
427 }
428
429 title += QLatin1Char(')');
430
431 progressBar->setMaximum(100);
432 progressBar->setValue(percent);
433 setWindowTitle(title);
434}
435
436void KWidgetJobTrackerPrivate::ProgressWidget::speed(unsigned long value)
437{
438 if (value == 0) {
439 speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Stalled"));
440 } else {
441 const QString speedStr = KJobTrackerFormatters::byteSize(size: value);
442 if (totalSizeKnown) {
443 const int remaining = 1000 * (totalSize - processedSize) / value;
444 //~ singular %1/s (%2 remaining)
445 //~ plural %1/s (%2 remaining)
446 speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1/s (%2 remaining)", disambiguation: "", n: remaining)
447 .arg(args: speedStr, args: KJobTrackerFormatters::duration(mSec: remaining)));
448 } else { // total size is not known (#24228)
449 speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1/s", disambiguation: "speed in bytes per second").arg(a: speedStr));
450 }
451 }
452}
453
454void KWidgetJobTrackerPrivate::ProgressWidget::slotClean()
455{
456 percent(percent: 100);
457 cancelClose->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "&Close"));
458 cancelClose->setIcon(QIcon::fromTheme(QStringLiteral("window-close")));
459 cancelClose->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Close the current window or document"));
460 openFile->setEnabled(true);
461 if (!totalSizeKnown || totalSize < processedSize) {
462 totalSize = processedSize;
463 }
464 processedAmount(unit: KJob::Bytes, amount: totalSize);
465 keepOpenCheck->setEnabled(false);
466 pauseButton->setEnabled(false);
467 if (startTime.isValid()) {
468 int s = startTime.elapsed();
469 if (!s) {
470 s = 1;
471 }
472 speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "%1/s (done)").arg(a: KJobTrackerFormatters::byteSize(size: 1000 * totalSize / s)));
473 }
474}
475
476void KWidgetJobTrackerPrivate::ProgressWidget::suspended()
477{
478 pauseButton->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "&Resume"));
479 suspendedProperty = true;
480}
481
482void KWidgetJobTrackerPrivate::ProgressWidget::resumed()
483{
484 pauseButton->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "&Pause"));
485 suspendedProperty = false;
486}
487
488void KWidgetJobTrackerPrivate::ProgressWidget::closeEvent(QCloseEvent *event)
489{
490 if (jobRegistered && tracker->stopOnClose(job)) {
491 tracker->slotStop(job);
492 }
493
494 QWidget::closeEvent(event);
495}
496
497void KWidgetJobTrackerPrivate::ProgressWidget::init()
498{
499 setWindowIcon(QIcon::fromTheme(QStringLiteral("document-save"), fallback: windowIcon()));
500
501 QVBoxLayout *topLayout = new QVBoxLayout(this);
502
503 QGridLayout *grid = new QGridLayout();
504 topLayout->addLayout(layout: grid);
505 const int horizontalSpacing = style()->pixelMetric(metric: QStyle::PM_LayoutHorizontalSpacing);
506 grid->addItem(item: new QSpacerItem(horizontalSpacing, 0), row: 0, column: 1);
507 // filenames or action name
508 sourceInvite = new QLabel(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Source:", disambiguation: "The source url of a job"), this);
509 grid->addWidget(sourceInvite, row: 0, column: 0);
510
511 sourceEdit = new KSqueezedTextLabel(this);
512 sourceEdit->setTextInteractionFlags(Qt::TextSelectableByMouse);
513 sourceEdit->installEventFilter(filterObj: this);
514 grid->addWidget(sourceEdit, row: 0, column: 2);
515
516 destInvite = new QLabel(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Destination:", disambiguation: "The destination url of a job"), this);
517 grid->addWidget(destInvite, row: 1, column: 0);
518
519 destEdit = new KSqueezedTextLabel(this);
520 destEdit->setTextInteractionFlags(Qt::TextSelectableByMouse);
521 destEdit->installEventFilter(filterObj: this);
522 grid->addWidget(destEdit, row: 1, column: 2);
523
524 QHBoxLayout *progressHBox = new QHBoxLayout();
525 topLayout->addLayout(layout: progressHBox);
526
527 progressBar = new QProgressBar(this);
528 progressBar->setFormat(QCoreApplication::translate(context: "KWidgetJobTracker", key: "%p%", disambiguation: "Progress bar percent value, %p is the value, % is the percent sign. Make sure to include %p in your translation."));
529 progressBar->setMaximum(0); // want a jumping progress bar if percent is not emitted
530 progressHBox->addWidget(progressBar);
531
532 suspendedProperty = false;
533
534 // processed info
535 QHBoxLayout *hBox = new QHBoxLayout();
536 topLayout->addLayout(layout: hBox);
537
538 arrowButton = new QPushButton(this);
539 arrowButton->setMaximumSize(QSize(32, 25));
540 arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
541 arrowButton->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Click this to expand the dialog, to show details"));
542 arrowState = Qt::DownArrow;
543 connect(sender: arrowButton, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::arrowClicked);
544 hBox->addWidget(arrowButton);
545 hBox->addStretch(stretch: 1);
546
547 KSeparator *separator1 = new KSeparator(Qt::Horizontal, this);
548 topLayout->addWidget(separator1);
549
550 sizeLabel = new QLabel(this);
551 hBox->addWidget(sizeLabel, stretch: 0, alignment: Qt::AlignLeft);
552
553 resumeLabel = new QLabel(this);
554 hBox->addWidget(resumeLabel);
555
556 pauseButton = new QPushButton(QCoreApplication::translate(context: "KWidgetJobTracker", key: "&Pause"), this);
557 pauseButton->setVisible(job && (job->capabilities() & KJob::Suspendable));
558 connect(sender: pauseButton, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::pauseResumeClicked);
559 hBox->addWidget(pauseButton);
560
561 hBox = new QHBoxLayout();
562 topLayout->addLayout(layout: hBox);
563
564 speedLabel = new QLabel(this);
565 hBox->addWidget(speedLabel, stretch: 1);
566 speedLabel->hide();
567
568 hBox = new QHBoxLayout();
569 topLayout->addLayout(layout: hBox);
570
571 progressLabel = new QLabel(this);
572 progressLabel->setAlignment(Qt::AlignLeft);
573 hBox->addWidget(progressLabel);
574 progressLabel->hide();
575
576 keepOpenCheck = new QCheckBox(QCoreApplication::translate(context: "KWidgetJobTracker", key: "&Keep this window open after transfer is complete"), this);
577 connect(sender: keepOpenCheck, signal: &QCheckBox::toggled, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::keepOpenToggled);
578 topLayout->addWidget(keepOpenCheck);
579 keepOpenCheck->hide();
580
581 hBox = new QHBoxLayout();
582 topLayout->addLayout(layout: hBox);
583
584 openFile = new QPushButton(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Open &File"), this);
585 connect(sender: openFile, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::openFileClicked);
586 hBox->addWidget(openFile);
587 openFile->setEnabled(false);
588 openFile->hide();
589
590 openLocation = new QPushButton(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Open &Destination"), this);
591 connect(sender: openLocation, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::openLocationClicked);
592 hBox->addWidget(openLocation);
593 openLocation->hide();
594
595 hBox->addStretch(stretch: 1);
596
597 cancelClose = new QPushButton(this);
598 cancelClose->setText(QCoreApplication::translate(context: "KWidgetJobTracker", key: "&Cancel"));
599 cancelClose->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel")));
600 connect(sender: cancelClose, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::cancelClicked);
601 hBox->addWidget(cancelClose);
602
603 resize(sizeHint());
604 setMaximumHeight(sizeHint().height());
605
606 setWindowTitle(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Progress Dialog")); // show something better than kuiserver
607}
608
609void KWidgetJobTrackerPrivate::ProgressWidget::showTotals()
610{
611 // Show the totals in the progress label, if we still haven't
612 // processed anything. This is useful when the stat'ing phase
613 // of CopyJob takes a long time (e.g. over networks).
614 if (processedFiles == 0 && processedDirs == 0 && processedItems == 0) {
615 QString total;
616 if (totalItems > 1) {
617 //~ singular %n item
618 //~ plural %n items
619 total = QCoreApplication::translate(context: "KWidgetJobTracker", key: "%n item(s)", disambiguation: "", n: totalItems);
620 progressLabel->setText(total);
621 } else {
622 if (totalDirs > 1) {
623 //~ singular %n folder
624 //~ plural %n folders
625 total = QCoreApplication::translate(context: "KWidgetJobTracker", key: "%n folder(s)", disambiguation: "", n: totalDirs) + QLatin1String(" ");
626 }
627 //~ singular %n file
628 //~ plural %n files
629 total += QCoreApplication::translate(context: "KWidgetJobTracker", key: "%n file(s)", disambiguation: "", n: totalFiles);
630 progressLabel->setText(total);
631 }
632 }
633}
634
635void KWidgetJobTrackerPrivate::ProgressWidget::setDestVisible(bool visible)
636{
637 // We can't hide the destInvite/destEdit labels,
638 // because it screws up the QGridLayout.
639 if (visible) {
640 destInvite->show();
641 destEdit->show();
642 } else {
643 destInvite->hide();
644 destEdit->hide();
645 destInvite->setText(QString());
646 destEdit->setText(QString());
647 }
648 setMaximumHeight(sizeHint().height());
649}
650
651void KWidgetJobTrackerPrivate::ProgressWidget::checkDestination(const QUrl &dest)
652{
653 bool ok = true;
654
655 if (dest.isLocalFile()) {
656 const QString path = dest.toLocalFile();
657 if (path.contains(s: QDir::tempPath())) {
658 ok = false; // it's in the tmp directory
659 }
660 }
661
662 if (ok) {
663 openFile->show();
664 openLocation->show();
665 keepOpenCheck->show();
666 setMaximumHeight(sizeHint().height());
667 location = dest;
668 }
669}
670
671void KWidgetJobTrackerPrivate::ProgressWidget::keepOpenToggled(bool keepOpen)
672{
673 if (keepOpen) {
674 Q_ASSERT(!tracker->d_func()->eventLoopLocker);
675 tracker->d_func()->eventLoopLocker = new QEventLoopLocker;
676 } else {
677 delete tracker->d_func()->eventLoopLocker;
678 tracker->d_func()->eventLoopLocker = nullptr;
679 }
680}
681
682static QString findKdeOpen()
683{
684 const QString exec = QStandardPaths::findExecutable(QStringLiteral("kde-open"));
685 if (exec.isEmpty()) {
686 qCWarning(KJOBWIDGETS) << "Could not find kde-open executable in PATH";
687 }
688 return exec;
689}
690
691void KWidgetJobTrackerPrivate::ProgressWidget::openFileClicked()
692{
693 const QString exec = findKdeOpen();
694 if (!exec.isEmpty()) {
695 QProcess::startDetached(program: exec, arguments: QStringList() << location.toDisplayString());
696 }
697}
698
699void KWidgetJobTrackerPrivate::ProgressWidget::openLocationClicked()
700{
701 const QString exec = findKdeOpen();
702 if (!exec.isEmpty()) {
703 QProcess::startDetached(program: exec, arguments: QStringList() << location.adjusted(options: QUrl::RemoveFilename).toString());
704 }
705}
706
707void KWidgetJobTrackerPrivate::ProgressWidget::pauseResumeClicked()
708{
709 if (jobRegistered && !suspendedProperty) {
710 tracker->slotSuspend(job);
711 } else if (jobRegistered) {
712 tracker->slotResume(job);
713 }
714}
715
716void KWidgetJobTrackerPrivate::ProgressWidget::cancelClicked()
717{
718 if (jobRegistered) {
719 tracker->slotStop(job);
720 }
721 closeNow();
722}
723
724void KWidgetJobTrackerPrivate::ProgressWidget::arrowClicked()
725{
726 if (arrowState == Qt::DownArrow) {
727 // The arrow is in the down position, dialog is collapsed, expand it and change icon.
728 progressLabel->show();
729 speedLabel->show();
730 arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up")));
731 arrowButton->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Click this to collapse the dialog, to hide details"));
732 arrowState = Qt::UpArrow;
733 } else {
734 // Collapse the dialog
735 progressLabel->hide();
736 speedLabel->hide();
737 arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down")));
738 arrowButton->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker", key: "Click this to expand the dialog, to show details"));
739 arrowState = Qt::DownArrow;
740 }
741 setMaximumHeight(sizeHint().height());
742}
743
744#include "moc_kwidgetjobtracker.cpp"
745#include "moc_kwidgetjobtracker_p.cpp"
746

source code of kjobwidgets/src/kwidgetjobtracker.cpp