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 | arrowButton->show(); |
274 | speedLabel->setText(message); |
275 | speedLabel->setAlignment(speedLabel->alignment() & ~Qt::TextWordWrap); |
276 | } |
277 | |
278 | void KWidgetJobTrackerPrivate::ProgressWidget::description(const QString &title, const QPair<QString, QString> &field1, const QPair<QString, QString> &field2) |
279 | { |
280 | setWindowTitle(title); |
281 | caption = title; |
282 | sourceInvite->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1:" , disambiguation: "%1 is the label, we add a ':' to it" ).arg(a: field1.first)); |
283 | sourceEdit->setText(field1.second); |
284 | |
285 | if (field2.first.isEmpty()) { |
286 | setDestVisible(false); |
287 | } else { |
288 | setDestVisible(true); |
289 | checkDestination(dest: QUrl::fromUserInput(userInput: field2.second)); // path or URL |
290 | destInvite->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1:" , disambiguation: "%1 is the label, we add a ':' to it" ).arg(a: field2.first)); |
291 | destEdit->setText(field2.second); |
292 | } |
293 | } |
294 | |
295 | void KWidgetJobTrackerPrivate::ProgressWidget::totalAmount(KJob::Unit unit, qulonglong amount) |
296 | { |
297 | switch (unit) { |
298 | case KJob::Bytes: |
299 | totalSizeKnown = true; |
300 | // size is measured in bytes |
301 | if (totalSize == amount) { |
302 | return; |
303 | } |
304 | totalSize = amount; |
305 | if (!startTime.isValid()) { |
306 | startTime.start(); |
307 | } |
308 | break; |
309 | |
310 | case KJob::Files: |
311 | if (totalFiles == amount) { |
312 | return; |
313 | } |
314 | totalFiles = amount; |
315 | showTotals(); |
316 | break; |
317 | |
318 | case KJob::Directories: |
319 | if (totalDirs == amount) { |
320 | return; |
321 | } |
322 | totalDirs = amount; |
323 | showTotals(); |
324 | break; |
325 | |
326 | case KJob::Items: |
327 | if (totalItems == amount) { |
328 | return; |
329 | } |
330 | totalItems = amount; |
331 | showTotals(); |
332 | break; |
333 | |
334 | case KJob::UnitsCount: |
335 | Q_UNREACHABLE(); |
336 | break; |
337 | } |
338 | } |
339 | |
340 | void KWidgetJobTrackerPrivate::ProgressWidget::processedAmount(KJob::Unit unit, qulonglong amount) |
341 | { |
342 | QString tmp; |
343 | |
344 | switch (unit) { |
345 | case KJob::Bytes: |
346 | if (processedSize == amount) { |
347 | return; |
348 | } |
349 | processedSize = amount; |
350 | |
351 | if (totalSizeKnown) { |
352 | //~ singular %1 of %2 complete |
353 | //~ plural %1 of %2 complete |
354 | tmp = QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1 of %2 complete" , disambiguation: "" , n: amount) |
355 | .arg(args: KJobTrackerFormatters::byteSize(size: amount), args: KJobTrackerFormatters::byteSize(size: totalSize)); |
356 | } else { |
357 | tmp = KJobTrackerFormatters::byteSize(size: amount); |
358 | } |
359 | sizeLabel->setText(tmp); |
360 | if (!totalSizeKnown) { // update jumping progressbar |
361 | progressBar->setValue(amount); |
362 | } |
363 | break; |
364 | |
365 | case KJob::Directories: |
366 | if (processedDirs == amount) { |
367 | return; |
368 | } |
369 | processedDirs = amount; |
370 | |
371 | //~ singular %1 / %n folder |
372 | //~ plural %1 / %n folders |
373 | tmp = QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1 / %n folder(s)" , disambiguation: "" , n: totalDirs).arg(a: processedDirs); |
374 | tmp += QLatin1String(" " ); |
375 | //~ singular %1 / %n file |
376 | //~ plural %1 / %n files |
377 | tmp += QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1 / %n file(s)" , disambiguation: "" , n: totalFiles).arg(a: processedFiles); |
378 | progressLabel->setText(tmp); |
379 | arrowButton->show(); |
380 | break; |
381 | |
382 | case KJob::Files: |
383 | if (processedFiles == amount) { |
384 | return; |
385 | } |
386 | processedFiles = amount; |
387 | |
388 | if (totalDirs > 1) { |
389 | //~ singular %1 / %n folder |
390 | //~ plural %1 / %n folders |
391 | tmp = QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1 / %n folder(s)" , disambiguation: "" , n: totalDirs).arg(a: processedDirs); |
392 | tmp += QLatin1String(" " ); |
393 | } |
394 | //~ singular %1 / %n file |
395 | //~ plural %1 / %n files |
396 | tmp += QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1 / %n file(s)" , disambiguation: "" , n: totalFiles).arg(a: processedFiles); |
397 | progressLabel->setText(tmp); |
398 | arrowButton->show(); |
399 | break; |
400 | |
401 | case KJob::Items: |
402 | if (processedItems == amount) { |
403 | return; |
404 | } |
405 | processedItems = amount; |
406 | //~ singular %1 / %n item |
407 | //~ plural %1 / %n items |
408 | tmp = QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1 / %n item(s)" , disambiguation: "" , n: totalItems).arg(a: processedItems); |
409 | progressLabel->setText(tmp); |
410 | arrowButton->show(); |
411 | break; |
412 | |
413 | case KJob::UnitsCount: |
414 | Q_UNREACHABLE(); |
415 | break; |
416 | } |
417 | } |
418 | |
419 | void KWidgetJobTrackerPrivate::ProgressWidget::percent(unsigned long percent) |
420 | { |
421 | QString title = caption + QLatin1String(" (" ); |
422 | |
423 | if (totalSizeKnown) { |
424 | title += QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1% of %2" ).arg(a: percent).arg(a: KJobTrackerFormatters::byteSize(size: totalSize)); |
425 | } else if (totalFiles) { |
426 | //~ singular %1% of %n file |
427 | //~ plural %1% of %n files |
428 | title += QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1% of %n file(s)" , disambiguation: "" , n: totalFiles).arg(a: percent); |
429 | } else { |
430 | title += QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1%" ).arg(a: percent); |
431 | } |
432 | |
433 | title += QLatin1Char(')'); |
434 | |
435 | progressBar->setMaximum(100); |
436 | progressBar->setValue(percent); |
437 | setWindowTitle(title); |
438 | } |
439 | |
440 | void KWidgetJobTrackerPrivate::ProgressWidget::speed(unsigned long value) |
441 | { |
442 | arrowButton->show(); |
443 | if (value == 0) { |
444 | speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Stalled" )); |
445 | } else { |
446 | const QString speedStr = KJobTrackerFormatters::byteSize(size: value); |
447 | if (totalSizeKnown) { |
448 | const int remaining = 1000 * (totalSize - processedSize) / value; |
449 | //~ singular %1/s (%2 remaining) |
450 | //~ plural %1/s (%2 remaining) |
451 | speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1/s (%2 remaining)" , disambiguation: "" , n: remaining) |
452 | .arg(args: speedStr, args: KJobTrackerFormatters::duration(mSec: remaining))); |
453 | } else { // total size is not known (#24228) |
454 | speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1/s" , disambiguation: "speed in bytes per second" ).arg(a: speedStr)); |
455 | } |
456 | } |
457 | } |
458 | |
459 | void KWidgetJobTrackerPrivate::ProgressWidget::slotClean() |
460 | { |
461 | percent(percent: 100); |
462 | cancelClose->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Close" )); |
463 | cancelClose->setIcon(QIcon::fromTheme(QStringLiteral("window-close" ))); |
464 | cancelClose->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Close the current window or document" )); |
465 | openFile->setEnabled(true); |
466 | if (!totalSizeKnown || totalSize < processedSize) { |
467 | totalSize = processedSize; |
468 | } |
469 | processedAmount(unit: KJob::Bytes, amount: totalSize); |
470 | keepOpenCheck->setEnabled(false); |
471 | pauseButton->setEnabled(false); |
472 | if (startTime.isValid()) { |
473 | int s = startTime.elapsed(); |
474 | if (!s) { |
475 | s = 1; |
476 | } |
477 | arrowButton->show(); |
478 | speedLabel->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%1/s (done)" ).arg(a: KJobTrackerFormatters::byteSize(size: 1000 * totalSize / s))); |
479 | } |
480 | } |
481 | |
482 | void KWidgetJobTrackerPrivate::ProgressWidget::suspended() |
483 | { |
484 | pauseButton->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Resume" )); |
485 | suspendedProperty = true; |
486 | } |
487 | |
488 | void KWidgetJobTrackerPrivate::ProgressWidget::resumed() |
489 | { |
490 | pauseButton->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Pause" )); |
491 | suspendedProperty = false; |
492 | } |
493 | |
494 | void KWidgetJobTrackerPrivate::ProgressWidget::closeEvent(QCloseEvent *event) |
495 | { |
496 | if (jobRegistered && tracker->stopOnClose(job)) { |
497 | tracker->slotStop(job); |
498 | } |
499 | |
500 | QWidget::closeEvent(event); |
501 | } |
502 | |
503 | void KWidgetJobTrackerPrivate::ProgressWidget::init() |
504 | { |
505 | setWindowIcon(QIcon::fromTheme(QStringLiteral("document-save" ), fallback: windowIcon())); |
506 | |
507 | QVBoxLayout *topLayout = new QVBoxLayout(this); |
508 | |
509 | QGridLayout *grid = new QGridLayout(); |
510 | topLayout->addLayout(layout: grid); |
511 | const int horizontalSpacing = style()->pixelMetric(metric: QStyle::PM_LayoutHorizontalSpacing); |
512 | grid->addItem(item: new QSpacerItem(horizontalSpacing, 0), row: 0, column: 1); |
513 | // filenames or action name |
514 | sourceInvite = new QLabel(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Source:" , disambiguation: "The source url of a job" ), this); |
515 | grid->addWidget(sourceInvite, row: 0, column: 0); |
516 | |
517 | sourceEdit = new KSqueezedTextLabel(this); |
518 | sourceEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); |
519 | sourceEdit->installEventFilter(filterObj: this); |
520 | grid->addWidget(sourceEdit, row: 0, column: 2); |
521 | |
522 | destInvite = new QLabel(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Destination:" , disambiguation: "The destination url of a job" ), this); |
523 | grid->addWidget(destInvite, row: 1, column: 0); |
524 | |
525 | destEdit = new KSqueezedTextLabel(this); |
526 | destEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); |
527 | destEdit->installEventFilter(filterObj: this); |
528 | grid->addWidget(destEdit, row: 1, column: 2); |
529 | |
530 | QHBoxLayout *progressHBox = new QHBoxLayout(); |
531 | topLayout->addLayout(layout: progressHBox); |
532 | |
533 | progressBar = new QProgressBar(this); |
534 | 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." )); |
535 | progressBar->setMaximum(0); // want a jumping progress bar if percent is not emitted |
536 | progressHBox->addWidget(progressBar); |
537 | |
538 | suspendedProperty = false; |
539 | |
540 | // processed info |
541 | QHBoxLayout *hBox = new QHBoxLayout(); |
542 | topLayout->addLayout(layout: hBox); |
543 | |
544 | arrowButton = new QPushButton(this); |
545 | arrowButton->setMaximumSize(QSize(32, 25)); |
546 | arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down" ))); |
547 | arrowButton->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Click this to expand the dialog, to show details" )); |
548 | arrowState = Qt::DownArrow; |
549 | connect(sender: arrowButton, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::arrowClicked); |
550 | hBox->addWidget(arrowButton); |
551 | hBox->addStretch(stretch: 1); |
552 | arrowButton->hide(); |
553 | |
554 | KSeparator *separator1 = new KSeparator(Qt::Horizontal, this); |
555 | topLayout->addWidget(separator1); |
556 | |
557 | sizeLabel = new QLabel(this); |
558 | hBox->addWidget(sizeLabel, stretch: 0, alignment: Qt::AlignLeft); |
559 | |
560 | resumeLabel = new QLabel(this); |
561 | hBox->addWidget(resumeLabel); |
562 | |
563 | pauseButton = new QPushButton(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Pause" ), this); |
564 | pauseButton->setVisible(job && (job->capabilities() & KJob::Suspendable)); |
565 | connect(sender: pauseButton, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::pauseResumeClicked); |
566 | hBox->addWidget(pauseButton); |
567 | |
568 | hBox = new QHBoxLayout(); |
569 | topLayout->addLayout(layout: hBox); |
570 | |
571 | speedLabel = new QLabel(this); |
572 | hBox->addWidget(speedLabel, stretch: 1); |
573 | speedLabel->hide(); |
574 | |
575 | hBox = new QHBoxLayout(); |
576 | topLayout->addLayout(layout: hBox); |
577 | |
578 | progressLabel = new QLabel(this); |
579 | progressLabel->setAlignment(Qt::AlignLeft); |
580 | hBox->addWidget(progressLabel); |
581 | progressLabel->hide(); |
582 | |
583 | keepOpenCheck = new QCheckBox(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Keep this window open after transfer is complete" ), this); |
584 | connect(sender: keepOpenCheck, signal: &QCheckBox::toggled, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::keepOpenToggled); |
585 | topLayout->addWidget(keepOpenCheck); |
586 | keepOpenCheck->hide(); |
587 | |
588 | hBox = new QHBoxLayout(); |
589 | topLayout->addLayout(layout: hBox); |
590 | |
591 | openFile = new QPushButton(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Open &File" ), this); |
592 | connect(sender: openFile, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::openFileClicked); |
593 | hBox->addWidget(openFile); |
594 | openFile->setEnabled(false); |
595 | openFile->hide(); |
596 | |
597 | openLocation = new QPushButton(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Open &Destination" ), this); |
598 | connect(sender: openLocation, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::openLocationClicked); |
599 | hBox->addWidget(openLocation); |
600 | openLocation->hide(); |
601 | |
602 | hBox->addStretch(stretch: 1); |
603 | |
604 | cancelClose = new QPushButton(this); |
605 | cancelClose->setText(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "&Cancel" )); |
606 | cancelClose->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel" ))); |
607 | connect(sender: cancelClose, signal: &QPushButton::clicked, context: this, slot: &KWidgetJobTrackerPrivate::ProgressWidget::cancelClicked); |
608 | hBox->addWidget(cancelClose); |
609 | |
610 | resize(sizeHint()); |
611 | setMaximumHeight(sizeHint().height()); |
612 | |
613 | setWindowTitle(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Progress Dialog" )); // show something better than kuiserver |
614 | } |
615 | |
616 | void KWidgetJobTrackerPrivate::ProgressWidget::showTotals() |
617 | { |
618 | // Show the totals in the progress label, if we still haven't |
619 | // processed anything. This is useful when the stat'ing phase |
620 | // of CopyJob takes a long time (e.g. over networks). |
621 | if (processedFiles == 0 && processedDirs == 0 && processedItems == 0) { |
622 | QString total; |
623 | if (totalItems > 1) { |
624 | //~ singular %n item |
625 | //~ plural %n items |
626 | total = QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%n item(s)" , disambiguation: "" , n: totalItems); |
627 | progressLabel->setText(total); |
628 | } else { |
629 | if (totalDirs > 1) { |
630 | //~ singular %n folder |
631 | //~ plural %n folders |
632 | total = QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%n folder(s)" , disambiguation: "" , n: totalDirs) + QLatin1String(" " ); |
633 | } |
634 | //~ singular %n file |
635 | //~ plural %n files |
636 | total += QCoreApplication::translate(context: "KWidgetJobTracker" , key: "%n file(s)" , disambiguation: "" , n: totalFiles); |
637 | progressLabel->setText(total); |
638 | } |
639 | arrowButton->show(); |
640 | } |
641 | } |
642 | |
643 | void KWidgetJobTrackerPrivate::ProgressWidget::setDestVisible(bool visible) |
644 | { |
645 | // We can't hide the destInvite/destEdit labels, |
646 | // because it screws up the QGridLayout. |
647 | if (visible) { |
648 | destInvite->show(); |
649 | destEdit->show(); |
650 | } else { |
651 | destInvite->hide(); |
652 | destEdit->hide(); |
653 | destInvite->setText(QString()); |
654 | destEdit->setText(QString()); |
655 | } |
656 | setMaximumHeight(sizeHint().height()); |
657 | } |
658 | |
659 | void KWidgetJobTrackerPrivate::ProgressWidget::checkDestination(const QUrl &dest) |
660 | { |
661 | bool ok = true; |
662 | |
663 | if (dest.isLocalFile()) { |
664 | const QString path = dest.toLocalFile(); |
665 | if (path.contains(s: QDir::tempPath())) { |
666 | ok = false; // it's in the tmp directory |
667 | } |
668 | } |
669 | |
670 | if (ok) { |
671 | openFile->show(); |
672 | openLocation->show(); |
673 | keepOpenCheck->show(); |
674 | setMaximumHeight(sizeHint().height()); |
675 | location = dest; |
676 | } |
677 | } |
678 | |
679 | void KWidgetJobTrackerPrivate::ProgressWidget::keepOpenToggled(bool keepOpen) |
680 | { |
681 | if (keepOpen) { |
682 | Q_ASSERT(!tracker->d_func()->eventLoopLocker); |
683 | tracker->d_func()->eventLoopLocker = new QEventLoopLocker; |
684 | } else { |
685 | delete tracker->d_func()->eventLoopLocker; |
686 | tracker->d_func()->eventLoopLocker = nullptr; |
687 | } |
688 | } |
689 | |
690 | static QString findKdeOpen() |
691 | { |
692 | const QString exec = QStandardPaths::findExecutable(QStringLiteral("kde-open" )); |
693 | if (exec.isEmpty()) { |
694 | qCWarning(KJOBWIDGETS) << "Could not find kde-open executable in PATH" ; |
695 | } |
696 | return exec; |
697 | } |
698 | |
699 | void KWidgetJobTrackerPrivate::ProgressWidget::openFileClicked() |
700 | { |
701 | const QString exec = findKdeOpen(); |
702 | if (!exec.isEmpty()) { |
703 | QProcess::startDetached(program: exec, arguments: QStringList() << location.toDisplayString()); |
704 | } |
705 | } |
706 | |
707 | void KWidgetJobTrackerPrivate::ProgressWidget::openLocationClicked() |
708 | { |
709 | const QString exec = findKdeOpen(); |
710 | if (!exec.isEmpty()) { |
711 | QProcess::startDetached(program: exec, arguments: QStringList() << location.adjusted(options: QUrl::RemoveFilename).toString()); |
712 | } |
713 | } |
714 | |
715 | void KWidgetJobTrackerPrivate::ProgressWidget::pauseResumeClicked() |
716 | { |
717 | if (jobRegistered && !suspendedProperty) { |
718 | tracker->slotSuspend(job); |
719 | } else if (jobRegistered) { |
720 | tracker->slotResume(job); |
721 | } |
722 | } |
723 | |
724 | void KWidgetJobTrackerPrivate::ProgressWidget::cancelClicked() |
725 | { |
726 | if (jobRegistered) { |
727 | tracker->slotStop(job); |
728 | } |
729 | closeNow(); |
730 | } |
731 | |
732 | void KWidgetJobTrackerPrivate::ProgressWidget::arrowClicked() |
733 | { |
734 | if (arrowState == Qt::DownArrow) { |
735 | // The arrow is in the down position, dialog is collapsed, expand it and change icon. |
736 | progressLabel->show(); |
737 | speedLabel->show(); |
738 | arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up" ))); |
739 | arrowButton->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Click this to collapse the dialog, to hide details" )); |
740 | arrowState = Qt::UpArrow; |
741 | } else { |
742 | // Collapse the dialog |
743 | progressLabel->hide(); |
744 | speedLabel->hide(); |
745 | arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down" ))); |
746 | arrowButton->setToolTip(QCoreApplication::translate(context: "KWidgetJobTracker" , key: "Click this to expand the dialog, to show details" )); |
747 | arrowState = Qt::DownArrow; |
748 | } |
749 | setMaximumHeight(sizeHint().height()); |
750 | } |
751 | |
752 | #include "moc_kwidgetjobtracker.cpp" |
753 | #include "moc_kwidgetjobtracker_p.cpp" |
754 | |