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 | |
32 | void 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 | |
53 | KWidgetJobTracker::KWidgetJobTracker(QWidget *parent) |
54 | : KAbstractWidgetJobTracker(*new KWidgetJobTrackerPrivate(parent, this), parent) |
55 | { |
56 | } |
57 | |
58 | KWidgetJobTracker::~KWidgetJobTracker() = default; |
59 | |
60 | QWidget *KWidgetJobTracker::widget(KJob *job) |
61 | { |
62 | Q_D(KWidgetJobTracker); |
63 | |
64 | return d->progressWidget.value(key: job, defaultValue: nullptr); |
65 | } |
66 | |
67 | void 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 | |
82 | void 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 | |
98 | bool 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 | |
110 | void 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 | |
122 | void 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 | |
134 | void 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 | |
146 | void 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 | |
158 | void 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 | |
170 | void 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 | |
182 | void 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 | |
194 | void 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 | |
206 | void 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 | |
218 | void KWidgetJobTrackerPrivate::ProgressWidget::ref() |
219 | { |
220 | ++refCount; |
221 | } |
222 | |
223 | void 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 | |
238 | void 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 | |
257 | bool 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 | |
271 | void KWidgetJobTrackerPrivate::ProgressWidget::infoMessage(const QString &message) |
272 | { |
273 | speedLabel->setText(message); |
274 | speedLabel->setAlignment(speedLabel->alignment() & ~Qt::TextWordWrap); |
275 | } |
276 | |
277 | void 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 | |
294 | void 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 | |
339 | void 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 | |
415 | void 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 | |
436 | void 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 | |
454 | void 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 | |
476 | void KWidgetJobTrackerPrivate::ProgressWidget::suspended() |
477 | { |
478 | pauseButton->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Resume" )); |
479 | suspendedProperty = true; |
480 | } |
481 | |
482 | void KWidgetJobTrackerPrivate::ProgressWidget::resumed() |
483 | { |
484 | pauseButton->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Pause" )); |
485 | suspendedProperty = false; |
486 | } |
487 | |
488 | void KWidgetJobTrackerPrivate::ProgressWidget::closeEvent(QCloseEvent *event) |
489 | { |
490 | if (jobRegistered && tracker->stopOnClose(job)) { |
491 | tracker->slotStop(job); |
492 | } |
493 | |
494 | QWidget::closeEvent(event); |
495 | } |
496 | |
497 | void 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 | |
609 | void 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 | |
635 | void 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 | |
651 | void 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 | |
671 | void 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 | |
682 | static 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 | |
691 | void 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 | |
699 | void 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 | |
707 | void KWidgetJobTrackerPrivate::ProgressWidget::pauseResumeClicked() |
708 | { |
709 | if (jobRegistered && !suspendedProperty) { |
710 | tracker->slotSuspend(job); |
711 | } else if (jobRegistered) { |
712 | tracker->slotResume(job); |
713 | } |
714 | } |
715 | |
716 | void KWidgetJobTrackerPrivate::ProgressWidget::cancelClicked() |
717 | { |
718 | if (jobRegistered) { |
719 | tracker->slotStop(job); |
720 | } |
721 | closeNow(); |
722 | } |
723 | |
724 | void 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 | |