| 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 | |