diff --git a/src/kuiserverjobtracker.cpp b/src/kuiserverjobtracker.cpp index eaca6af..d3b1f39 100644 --- a/src/kuiserverjobtracker.cpp +++ b/src/kuiserverjobtracker.cpp @@ -1,387 +1,393 @@ /* This file is part of the KDE project Copyright (C) 2008 Rafael Fernández López Copyright (C) 2007 Kevin Ottens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kuiserverjobtracker.h" #include "kuiserverjobtracker_p.h" #include "jobviewiface.h" #include "debug.h" #include #include #include #include Q_GLOBAL_STATIC(KSharedUiServerProxy, serverProxy) class Q_DECL_HIDDEN KUiServerJobTracker::Private { public: Private(KUiServerJobTracker *parent) : q(parent) { } KUiServerJobTracker *const q; void _k_killJob(); static void updateDestUrl(KJob *job, org::kde::JobViewV2 *jobView); QHash progressJobView; QMetaObject::Connection serverRegisteredConnection; }; void KUiServerJobTracker::Private::_k_killJob() { org::kde::JobViewV2 *jobView = qobject_cast(q->sender()); if (jobView) { KJob *job = progressJobView.key(jobView); if (job) { job->kill(KJob::EmitResult); } } } void KUiServerJobTracker::Private::updateDestUrl(KJob *job, org::kde::JobViewV2 *jobView) { const QVariant destUrl = job->property("destUrl"); if (destUrl.isValid()) { jobView->setDestUrl(QDBusVariant(destUrl)); } } KUiServerJobTracker::KUiServerJobTracker(QObject *parent) : KJobTrackerInterface(parent), d(new Private(this)) { } KUiServerJobTracker::~KUiServerJobTracker() { if (!d->progressJobView.isEmpty()) { qWarning() << "A KUiServerJobTracker instance contains" << d->progressJobView.size() << "stalled jobs"; } qDeleteAll(d->progressJobView); delete d; } void KUiServerJobTracker::registerJob(KJob *job) { // Already registered job? if (d->progressJobView.contains(job)) { return; } // Watch the server registering/unregistering and re-register the jobs as needed if (!d->serverRegisteredConnection) { d->serverRegisteredConnection = connect(serverProxy(), &KSharedUiServerProxy::serverRegistered, this, [this]() { // Remember the list of jobs to re-register and then delete the old ones const QList staleJobs = d->progressJobView.keys(); qDeleteAll(d->progressJobView); d->progressJobView.clear(); for (KJob *job : staleJobs) { registerJob(job); } }); } const QString appName = QCoreApplication::applicationName(); // This will only work if main() used QIcon::fromTheme. QString programIconName = QApplication::windowIcon().name(); if (programIconName.isEmpty()) { programIconName = appName; } QPointer jobWatch = job; QDBusReply reply = serverProxy()->uiserver()->requestView(appName, programIconName, job->capabilities()); // If we got a valid reply, register the interface for later usage. if (reply.isValid()) { org::kde::JobViewV2 *jobView = new org::kde::JobViewV2(QStringLiteral("org.kde.JobViewServer"), reply.value().path(), QDBusConnection::sessionBus()); if (!jobWatch) { //qCDebug(KJOBWIDGETS) << "deleted out from under us when asking the server proxy for the view"; jobView->terminate(QString()); delete jobView; return; } QObject::connect(jobView, SIGNAL(cancelRequested()), this, SLOT(_k_killJob())); QObject::connect(jobView, &org::kde::JobViewV2::suspendRequested, job, &KJob::suspend); QObject::connect(jobView, &org::kde::JobViewV2::resumeRequested, job, &KJob::resume); d->updateDestUrl(job, jobView); if (!jobWatch) { //qCDebug(KJOBWIDGETS) << "deleted out from under us when creating the dbus interface"; jobView->terminate(QString()); delete jobView; return; } d->progressJobView.insert(job, jobView); } else if (!jobWatch) { qWarning() << "Uh-oh...KUiServerJobTracker was trying to forward a job, but it was deleted from under us." << "kuiserver *may* have a stranded job. we can't do anything about it because the returned objectPath is invalid."; return; } KJobTrackerInterface::registerJob(job); } void KUiServerJobTracker::unregisterJob(KJob *job) { KJobTrackerInterface::unregisterJob(job); if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView.take(job); d->updateDestUrl(job, jobView); jobView->setError(job->error()); if (job->error()) { jobView->terminate(job->errorText()); } else { jobView->terminate(QString()); } delete jobView; } void KUiServerJobTracker::finished(KJob *job) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView.take(job); d->updateDestUrl(job, jobView); jobView->setError(job->error()); if (job->error()) { jobView->terminate(job->errorText()); } else { jobView->terminate(QString()); } } void KUiServerJobTracker::suspended(KJob *job) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; jobView->setSuspended(true); } void KUiServerJobTracker::resumed(KJob *job) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; jobView->setSuspended(false); } void KUiServerJobTracker::description(KJob *job, const QString &title, const QPair &field1, const QPair &field2) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; jobView->setInfoMessage(title); if (field1.first.isNull() || field1.second.isNull()) { jobView->clearDescriptionField(0); } else { jobView->setDescriptionField(0, field1.first, field1.second); } if (field2.first.isNull() || field2.second.isNull()) { jobView->clearDescriptionField(1); } else { jobView->setDescriptionField(1, field2.first, field2.second); } } void KUiServerJobTracker::infoMessage(KJob *job, const QString &plain, const QString &rich) { Q_UNUSED(rich) if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; jobView->setInfoMessage(plain); } void KUiServerJobTracker::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; switch (unit) { case KJob::Bytes: jobView->setTotalAmount(amount, QStringLiteral("bytes")); break; case KJob::Files: jobView->setTotalAmount(amount, QStringLiteral("files")); break; case KJob::Directories: jobView->setTotalAmount(amount, QStringLiteral("dirs")); break; + case KJob::Items: + jobView->setTotalAmount(amount, QStringLiteral("items")); + break; } } void KUiServerJobTracker::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; switch (unit) { case KJob::Bytes: jobView->setProcessedAmount(amount, QStringLiteral("bytes")); break; case KJob::Files: jobView->setProcessedAmount(amount, QStringLiteral("files")); break; case KJob::Directories: jobView->setProcessedAmount(amount, QStringLiteral("dirs")); break; + case KJob::Items: + jobView->setProcessedAmount(amount, QStringLiteral("items")); + break; } } void KUiServerJobTracker::percent(KJob *job, unsigned long percent) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; jobView->setPercent(percent); } void KUiServerJobTracker::speed(KJob *job, unsigned long value) { if (!d->progressJobView.contains(job)) { return; } org::kde::JobViewV2 *jobView = d->progressJobView[job]; jobView->setSpeed(value); } KSharedUiServerProxy::KSharedUiServerProxy() : m_uiserver(new org::kde::JobViewServer(QStringLiteral("org.kde.JobViewServer"), QStringLiteral("/JobViewServer"), QDBusConnection::sessionBus())) , m_watcher(new QDBusServiceWatcher(QStringLiteral("org.kde.JobViewServer"), QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForOwnerChange)) { QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface(); if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer"))) { QDBusReply reply = bus->startService(QStringLiteral("org.kde.kuiserver")); if (!reply.isValid()) { qCritical() << "Couldn't start kuiserver from org.kde.kuiserver.service:" << reply.error(); return; } if (!bus->isServiceRegistered(QStringLiteral("org.kde.JobViewServer"))) { qCDebug(KJOBWIDGETS) << "The dbus name org.kde.JobViewServer is STILL NOT REGISTERED, even after starting kuiserver. Should not happen."; return; } qCDebug(KJOBWIDGETS) << "kuiserver registered"; } else { qCDebug(KJOBWIDGETS) << "kuiserver found"; } connect(m_watcher.get(), &QDBusServiceWatcher::serviceOwnerChanged, this, &KSharedUiServerProxy::uiserverOwnerChanged); // cleanup early enough to avoid issues with dbus at application exit // see e.g. https://phabricator.kde.org/D2545 qAddPostRoutine([]() { serverProxy->m_uiserver.reset(); serverProxy->m_watcher.reset(); }); } KSharedUiServerProxy::~KSharedUiServerProxy() { } org::kde::JobViewServer *KSharedUiServerProxy::uiserver() { return m_uiserver.get(); } void KSharedUiServerProxy::uiserverOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(serviceName); Q_UNUSED(oldOwner); if (!newOwner.isEmpty()) { // registered emit serverRegistered(); } else if (newOwner.isEmpty()) { // unregistered emit serverUnregistered(); } } #include "moc_kuiserverjobtracker.cpp" #include "moc_kuiserverjobtracker_p.cpp" diff --git a/src/kwidgetjobtracker.cpp b/src/kwidgetjobtracker.cpp index 0dbdee6..768052c 100644 --- a/src/kwidgetjobtracker.cpp +++ b/src/kwidgetjobtracker.cpp @@ -1,698 +1,715 @@ /* This file is part of the KDE project Copyright (C) 2000 Matej Koss Copyright (C) 2007 Kevin Ottens Copyright (C) 2007 Rafael Fernández López Copyright (C) 2009 Shaun Reich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "kwidgetjobtracker.h" #include "kwidgetjobtracker_p.h" #include "kjobtrackerformatters_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include void KWidgetJobTracker::Private::_k_showProgressWidget() { if (progressWidgetsToBeShown.isEmpty()) { return; } KJob *job = progressWidgetsToBeShown.dequeue(); // If the job has been unregistered before reaching this point, widget will // return 0. QWidget *widget = q->widget(job); if (widget) { // Don't steal the focus from the current widget (e. g. Kate) widget->setAttribute(Qt::WA_ShowWithoutActivating); widget->show(); } } KWidgetJobTracker::KWidgetJobTracker(QWidget *parent) : KAbstractWidgetJobTracker(parent), d(new Private(parent, this)) { } KWidgetJobTracker::~KWidgetJobTracker() { delete d; } QWidget *KWidgetJobTracker::widget(KJob *job) { return d->progressWidget.value(job, nullptr); } void KWidgetJobTracker::registerJob(KJob *job) { Private::ProgressWidget *vi = new Private::ProgressWidget(job, this, d->parent); vi->jobRegistered = true; vi->setAttribute(Qt::WA_DeleteOnClose); d->progressWidget.insert(job, vi); d->progressWidgetsToBeShown.enqueue(job); KAbstractWidgetJobTracker::registerJob(job); QTimer::singleShot(500, this, SLOT(_k_showProgressWidget())); } void KWidgetJobTracker::unregisterJob(KJob *job) { KAbstractWidgetJobTracker::unregisterJob(job); d->progressWidgetsToBeShown.removeAll(job); KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->jobRegistered = false; pWidget->deref(); } bool KWidgetJobTracker::keepOpen(KJob *job) const { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return false; } return pWidget->keepOpenCheck->isChecked(); } void KWidgetJobTracker::infoMessage(KJob *job, const QString &plain, const QString &rich) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->infoMessage(plain, rich); } void KWidgetJobTracker::description(KJob *job, const QString &title, const QPair &field1, const QPair &field2) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->description(title, field1, field2); } void KWidgetJobTracker::totalAmount(KJob *job, KJob::Unit unit, qulonglong amount) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->totalAmount(unit, amount); } void KWidgetJobTracker::processedAmount(KJob *job, KJob::Unit unit, qulonglong amount) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->processedAmount(unit, amount); } void KWidgetJobTracker::percent(KJob *job, unsigned long percent) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->percent(percent); } void KWidgetJobTracker::speed(KJob *job, unsigned long value) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->speed(value); } void KWidgetJobTracker::slotClean(KJob *job) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->slotClean(); } void KWidgetJobTracker::suspended(KJob *job) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->suspended(); } void KWidgetJobTracker::resumed(KJob *job) { KWidgetJobTracker::Private::ProgressWidget *pWidget = d->progressWidget.value(job, nullptr); if (!pWidget) { return; } pWidget->resumed(); } void KWidgetJobTracker::Private::ProgressWidget::ref() { ++refCount; } void KWidgetJobTracker::Private::ProgressWidget::deref() { if (refCount) { --refCount; } if (!refCount) { if (!keepOpenCheck->isChecked()) { closeNow(); } else { slotClean(); } } } void KWidgetJobTracker::Private::ProgressWidget::closeNow() { close(); // It might happen the next scenario: // - Start a job which opens a progress widget. Keep it open. Address job is 0xdeadbeef // - Start a new job, which is given address 0xdeadbeef. A new window is opened. // This one will take much longer to complete. The key 0xdeadbeef on the widget map now // stores the new widget address. // - Close the first progress widget that was opened (and has already finished) while the // last one is still running. We remove its reference on the map. Wrong. // For that reason we have to check if the map stores the widget as the current one. // ereslibre if (tracker->d->progressWidget[job] == this) { tracker->d->progressWidget.remove(job); tracker->d->progressWidgetsToBeShown.removeAll(job); } } bool KWidgetJobTracker::Private::ProgressWidget::eventFilter(QObject *watched, QEvent *event) { // Handle context menu events for the source/dest labels here, so that we are ref()ed while the // menu is exec()ed, to avoid a crash if the job finishes meanwhile. #159621. if ((watched == sourceEdit || watched == destEdit) && event->type() == QEvent::ContextMenu) { ref(); watched->event(event); deref(); return true; } return QWidget::eventFilter(watched, event); } void KWidgetJobTracker::Private::ProgressWidget::infoMessage(const QString &plain, const QString &/*rich*/) { speedLabel->setText(plain); speedLabel->setAlignment(speedLabel->alignment() & ~Qt::TextWordWrap); } void KWidgetJobTracker::Private::ProgressWidget::description(const QString &title, const QPair &field1, const QPair &field2) { setWindowTitle(title); caption = title; sourceInvite->setText( QCoreApplication::translate("KWidgetJobTracker", "%1:", "%1 is the label, we add a ':' to it" ).arg(field1.first)); sourceEdit->setText(field1.second); if (field2.first.isEmpty()) { setDestVisible(false); } else { setDestVisible(true); checkDestination(QUrl::fromUserInput(field2.second)); // path or URL destInvite->setText( QCoreApplication::translate("KWidgetJobTracker", "%1:", "%1 is the label, we add a ':' to it" ).arg(field2.first)); destEdit->setText(field2.second); } } void KWidgetJobTracker::Private::ProgressWidget::totalAmount(KJob::Unit unit, qulonglong amount) { switch (unit) { case KJob::Bytes: totalSizeKnown = true; // size is measured in bytes if (totalSize == amount) { return; } totalSize = amount; if (!startTime.isValid()) { startTime.start(); } break; case KJob::Files: if (totalFiles == amount) { return; } totalFiles = amount; showTotals(); break; case KJob::Directories: if (totalDirs == amount) { return; } totalDirs = amount; showTotals(); break; + + case KJob::Items: + if (totalItems == amount) { + return; + } + totalItems = amount; + showTotals(); + break; } } void KWidgetJobTracker::Private::ProgressWidget::processedAmount(KJob::Unit unit, qulonglong amount) { QString tmp; switch (unit) { case KJob::Bytes: if (processedSize == amount) { return; } processedSize = amount; if (totalSizeKnown) { //~ singular %1 of %2 complete //~ plural %1 of %2 complete tmp = QCoreApplication::translate("KWidgetJobTracker", "%1 of %2 complete", "", amount) .arg(KJobTrackerFormatters::byteSize(amount), KJobTrackerFormatters::byteSize(totalSize)); } else { tmp = KJobTrackerFormatters::byteSize(amount); } sizeLabel->setText(tmp); if (!totalSizeKnown) { // update jumping progressbar progressBar->setValue(amount); } break; case KJob::Directories: if (processedDirs == amount) { return; } processedDirs = amount; //~ singular %1 / %n folder //~ plural %1 / %n folders tmp = QCoreApplication::translate("KWidgetJobTracker", "%1 / %n folder(s)", "", totalDirs).arg(processedDirs); tmp += QLatin1String(" "); //~ singular %1 / %n file //~ plural %1 / %n files tmp += QCoreApplication::translate("KWidgetJobTracker", "%1 / %n file(s)", "", totalFiles).arg(processedFiles); progressLabel->setText(tmp); break; case KJob::Files: if (processedFiles == amount) { return; } processedFiles = amount; if (totalDirs > 1) { //~ singular %1 / %n folder //~ plural %1 / %n folders tmp = QCoreApplication::translate("KWidgetJobTracker", "%1 / %n folder(s)", "", totalDirs).arg(processedDirs); tmp += QLatin1String(" "); } //~ singular %1 / %n file //~ plural %1 / %n files tmp += QCoreApplication::translate("KWidgetJobTracker", "%1 / %n file(s)", "", totalFiles).arg(processedFiles); progressLabel->setText(tmp); + break; + + case KJob::Items: + if (processedItems == amount) { + return; + } + processedItems = amount; + tmp = QCoreApplication::translate("KWidgetJobTracker", "%1 / %n item(s)", "", totalItems).arg(processedItems); + progressLabel->setText(tmp); + break; } } void KWidgetJobTracker::Private::ProgressWidget::percent(unsigned long percent) { QString title = caption + QLatin1String(" ("); if (totalSizeKnown) { title += QCoreApplication::translate("KWidgetJobTracker", "%1% of %2").arg(percent).arg( KJobTrackerFormatters::byteSize(totalSize)); } else if (totalFiles) { //~ singular %1% of %n file //~ plural %1% of %n files title += QCoreApplication::translate("KWidgetJobTracker", "%1% of %n file(s)", "", totalFiles).arg(percent); } else { title += QCoreApplication::translate("KWidgetJobTracker", "%1%").arg(percent); } title += QLatin1Char(')'); progressBar->setMaximum(100); progressBar->setValue(percent); setWindowTitle(title); } void KWidgetJobTracker::Private::ProgressWidget::speed(unsigned long value) { if (value == 0) { speedLabel->setText(QCoreApplication::translate("KWidgetJobTracker", "Stalled")); } else { const QString speedStr = KJobTrackerFormatters::byteSize(value); if (totalSizeKnown) { const int remaining = 1000 * (totalSize - processedSize) / value; //~ singular %1/s (%2 remaining) //~ plural %1/s (%2 remaining) speedLabel->setText(QCoreApplication::translate("KWidgetJobTracker", "%1/s (%2 remaining)", "", remaining).arg(speedStr, KJobTrackerFormatters::duration(remaining))); } else { // total size is not known (#24228) speedLabel->setText(QCoreApplication::translate("KWidgetJobTracker", "%1/s", "speed in bytes per second").arg(speedStr)); } } } void KWidgetJobTracker::Private::ProgressWidget::slotClean() { percent(100); cancelClose->setText(QCoreApplication::translate("KWidgetJobTracker", "&Close")); cancelClose->setIcon(QIcon::fromTheme(QStringLiteral("window-close"))); cancelClose->setToolTip(QCoreApplication::translate("KWidgetJobTracker", "Close the current window or document")); openFile->setEnabled(true); if (!totalSizeKnown || totalSize < processedSize) { totalSize = processedSize; } processedAmount(KJob::Bytes, totalSize); keepOpenCheck->setEnabled(false); pauseButton->setEnabled(false); if (startTime.isValid()) { int s = startTime.elapsed(); if (!s) { s = 1; } speedLabel->setText(QCoreApplication::translate("KWidgetJobTracker", "%1/s (done)").arg( KJobTrackerFormatters::byteSize(1000 * totalSize / s))); } } void KWidgetJobTracker::Private::ProgressWidget::suspended() { pauseButton->setText(QCoreApplication::translate("KWidgetJobTracker", "&Resume")); suspendedProperty = true; } void KWidgetJobTracker::Private::ProgressWidget::resumed() { pauseButton->setText(QCoreApplication::translate("KWidgetJobTracker", "&Pause")); suspendedProperty = false; } void KWidgetJobTracker::Private::ProgressWidget::closeEvent(QCloseEvent *event) { if (jobRegistered && tracker->stopOnClose(job)) { tracker->slotStop(job); } QWidget::closeEvent(event); } void KWidgetJobTracker::Private::ProgressWidget::init() { setWindowIcon(QIcon::fromTheme(QStringLiteral("document-save"), windowIcon())); QVBoxLayout *topLayout = new QVBoxLayout(this); QGridLayout *grid = new QGridLayout(); topLayout->addLayout(grid); const int spacingHint = style()->pixelMetric(QStyle::PM_DefaultLayoutSpacing); grid->addItem(new QSpacerItem(spacingHint, 0), 0, 1); // filenames or action name sourceInvite = new QLabel(QCoreApplication::translate("KWidgetJobTracker", "Source:", "The source url of a job"), this); grid->addWidget(sourceInvite, 0, 0); sourceEdit = new KSqueezedTextLabel(this); sourceEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); sourceEdit->installEventFilter(this); grid->addWidget(sourceEdit, 0, 2); destInvite = new QLabel(QCoreApplication::translate("KWidgetJobTracker", "Destination:", "The destination url of a job"), this); grid->addWidget(destInvite, 1, 0); destEdit = new KSqueezedTextLabel(this); destEdit->setTextInteractionFlags(Qt::TextSelectableByMouse); destEdit->installEventFilter(this); grid->addWidget(destEdit, 1, 2); QHBoxLayout *progressHBox = new QHBoxLayout(); topLayout->addLayout(progressHBox); progressBar = new QProgressBar(this); progressBar->setMaximum(0); // want a jumping progress bar if percent is not emitted progressHBox->addWidget(progressBar); suspendedProperty = false; // processed info QHBoxLayout *hBox = new QHBoxLayout(); topLayout->addLayout(hBox); arrowButton = new QPushButton(this); arrowButton->setMaximumSize(QSize(32, 25)); arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); arrowButton->setToolTip(QCoreApplication::translate("KWidgetJobTracker", "Click this to expand the dialog, to show details")); arrowState = Qt::DownArrow; connect(arrowButton, &QPushButton::clicked, this, &KWidgetJobTracker::Private::ProgressWidget::arrowClicked); hBox->addWidget(arrowButton); hBox->addStretch(1); KSeparator *separator1 = new KSeparator(Qt::Horizontal, this); topLayout->addWidget(separator1); sizeLabel = new QLabel(this); hBox->addWidget(sizeLabel, 0, Qt::AlignLeft); resumeLabel = new QLabel(this); hBox->addWidget(resumeLabel); pauseButton = new QPushButton(QCoreApplication::translate("KWidgetJobTracker", "&Pause"), this); pauseButton->setVisible(job && (job->capabilities() & KJob::Suspendable)); connect(pauseButton, &QPushButton::clicked, this, &KWidgetJobTracker::Private::ProgressWidget::pauseResumeClicked); hBox->addWidget(pauseButton); hBox = new QHBoxLayout(); topLayout->addLayout(hBox); speedLabel = new QLabel(this); hBox->addWidget(speedLabel, 1); speedLabel->hide(); hBox = new QHBoxLayout(); topLayout->addLayout(hBox); progressLabel = new QLabel(this); progressLabel->setAlignment(Qt::AlignLeft); hBox->addWidget(progressLabel); progressLabel->hide(); keepOpenCheck = new QCheckBox(QCoreApplication::translate("KWidgetJobTracker", "&Keep this window open after transfer is complete"), this); connect(keepOpenCheck, &QCheckBox::toggled, this, &KWidgetJobTracker::Private::ProgressWidget::keepOpenToggled); topLayout->addWidget(keepOpenCheck); keepOpenCheck->hide(); hBox = new QHBoxLayout(); topLayout->addLayout(hBox); openFile = new QPushButton(QCoreApplication::translate("KWidgetJobTracker", "Open &File"), this); connect(openFile, &QPushButton::clicked, this, &KWidgetJobTracker::Private::ProgressWidget::openFileClicked); hBox->addWidget(openFile); openFile->setEnabled(false); openFile->hide(); openLocation = new QPushButton(QCoreApplication::translate("KWidgetJobTracker", "Open &Destination"), this); connect(openLocation, &QPushButton::clicked, this, &KWidgetJobTracker::Private::ProgressWidget::openLocationClicked); hBox->addWidget(openLocation); openLocation->hide(); hBox->addStretch(1); cancelClose = new QPushButton(this); cancelClose->setText(QCoreApplication::translate("KWidgetJobTracker", "&Cancel")); cancelClose->setIcon(QIcon::fromTheme(QStringLiteral("dialog-cancel"))); connect(cancelClose, &QPushButton::clicked, this, &KWidgetJobTracker::Private::ProgressWidget::cancelClicked); hBox->addWidget(cancelClose); resize(sizeHint()); setMaximumHeight(sizeHint().height()); setWindowTitle(QCoreApplication::translate("KWidgetJobTracker", "Progress Dialog")); // show something better than kuiserver } void KWidgetJobTracker::Private::ProgressWidget::showTotals() { // Show the totals in the progress label, if we still haven't // processed anything. This is useful when the stat'ing phase // of CopyJob takes a long time (e.g. over networks). - if (processedFiles == 0 && processedDirs == 0) { - QString tmps; - if (totalDirs > 1) - // that we have a singular to translate looks weired but is only logical - { - //~ singular %n folder - //~ plural %n folders - tmps = QCoreApplication::translate("KWidgetJobTracker", "%n folder(s)", "", totalDirs) + QLatin1String(" "); + if (processedFiles == 0 && processedDirs == 0 && processedItems == 0) { + QString total; + if (totalItems > 1) { + total = QCoreApplication::translate("KWidgetJobTracker", "%n item(s)", "", totalItems); + progressLabel->setText(total); + } else { + if (totalDirs > 1) { + total = QCoreApplication::translate("KWidgetJobTracker", "%n folder(s)", "", totalDirs) + QLatin1String(" "); + } + total += QCoreApplication::translate("KWidgetJobTracker", "%n file(s)", "", totalFiles); + progressLabel->setText(total); } - //~ singular %n file - //~ plural %n files - tmps += QCoreApplication::translate("KWidgetJobTracker", "%n file(s)", "", totalFiles); - progressLabel->setText(tmps); } } void KWidgetJobTracker::Private::ProgressWidget::setDestVisible(bool visible) { // We can't hide the destInvite/destEdit labels, // because it screws up the QGridLayout. if (visible) { destInvite->show(); destEdit->show(); } else { destInvite->hide(); destEdit->hide(); destInvite->setText(QString()); destEdit->setText(QString()); } setMaximumHeight(sizeHint().height()); } void KWidgetJobTracker::Private::ProgressWidget::checkDestination(const QUrl &dest) { bool ok = true; if (dest.isLocalFile()) { const QString path = dest.toLocalFile(); if (path.contains(QDir::tempPath())) { ok = false; // it's in the tmp directory } } if (ok) { openFile->show(); openLocation->show(); keepOpenCheck->show(); setMaximumHeight(sizeHint().height()); location = dest; } } void KWidgetJobTracker::Private::ProgressWidget::keepOpenToggled(bool keepOpen) { if (keepOpen) { Q_ASSERT(!tracker->d->eventLoopLocker); tracker->d->eventLoopLocker = new QEventLoopLocker; } else { delete tracker->d->eventLoopLocker; tracker->d->eventLoopLocker = nullptr; } } void KWidgetJobTracker::Private::ProgressWidget::openFileClicked() { QProcess::startDetached(QStringLiteral("kde-open"), QStringList() << location.toDisplayString()); } void KWidgetJobTracker::Private::ProgressWidget::openLocationClicked() { QProcess::startDetached(QStringLiteral("kde-open"), QStringList() << location.adjusted(QUrl::RemoveFilename).toString()); } void KWidgetJobTracker::Private::ProgressWidget::pauseResumeClicked() { if (jobRegistered && !suspendedProperty) { tracker->slotSuspend(job); } else if (jobRegistered) { tracker->slotResume(job); } } void KWidgetJobTracker::Private::ProgressWidget::cancelClicked() { if (jobRegistered) { tracker->slotStop(job); } closeNow(); } void KWidgetJobTracker::Private::ProgressWidget::arrowClicked() { if (arrowState == Qt::DownArrow) { //The arrow is in the down position, dialog is collapsed, expand it and change icon. progressLabel->show(); speedLabel->show(); arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-up"))); arrowButton->setToolTip(QCoreApplication::translate("KWidgetJobTracker", "Click this to collapse the dialog, to hide details")); arrowState = Qt::UpArrow; } else { //Collapse the dialog progressLabel->hide(); speedLabel->hide(); arrowButton->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); arrowButton->setToolTip(QCoreApplication::translate("KWidgetJobTracker", "Click this to expand the dialog, to show details")); arrowState = Qt::DownArrow; } setMaximumHeight(sizeHint().height()); } #include "moc_kwidgetjobtracker.cpp" #include "moc_kwidgetjobtracker_p.cpp" diff --git a/src/kwidgetjobtracker_p.h b/src/kwidgetjobtracker_p.h index 6e24bcd..038dd3a 100644 --- a/src/kwidgetjobtracker_p.h +++ b/src/kwidgetjobtracker_p.h @@ -1,209 +1,211 @@ /* This file is part of the KDE project Copyright (C) 2007 Rafael Fernández López Copyright (C) 2007 Kevin Ottens Copyright (C) 2009 Shaun Reich This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KWIDGETJOBTRACKER_P_H #define KWIDGETJOBTRACKER_P_H #include "kabstractwidgetjobtracker_p.h" #include "kwidgetjobtracker.h" #include #include #include #include #include #include #include #include class QPushButton; class KSqueezedTextLabel; class QLabel; class QProgressBar; class Q_DECL_HIDDEN KWidgetJobTracker::Private : public KAbstractWidgetJobTracker::Private { public: Private(QWidget *parent, KWidgetJobTracker *tracker) : KAbstractWidgetJobTracker::Private(tracker) , parent(parent) , eventLoopLocker(nullptr) { } virtual ~Private() { delete eventLoopLocker; } void setStopOnClose(KJob *job, bool stopOnClose) override; bool stopOnClose(KJob *job) const override; void setAutoDelete(KJob *job, bool autoDelete) override; bool autoDelete(KJob *job) const override; void _k_showProgressWidget(); class ProgressWidget; QWidget *parent; QEventLoopLocker *eventLoopLocker; QMap progressWidget; QQueue progressWidgetsToBeShown; }; class KWidgetJobTracker::Private::ProgressWidget : public QWidget { Q_OBJECT public: ProgressWidget(KJob *job, KWidgetJobTracker *object, QWidget *parent) - : QWidget(parent), tracker(object), job(job), totalSize(0), totalFiles(0), totalDirs(0), - processedSize(0), processedDirs(0), processedFiles(0), totalSizeKnown(false), + : QWidget(parent), tracker(object), job(job), totalSize(0), totalFiles(0), totalDirs(0), totalItems(0), + processedSize(0), processedDirs(0), processedFiles(0), processedItems(0), totalSizeKnown(false), stopOnClose(true), jobRegistered(false), cancelClose(nullptr), openFile(nullptr), openLocation(nullptr), keepOpenCheck(nullptr), pauseButton(nullptr), sourceEdit(nullptr), destEdit(nullptr), progressLabel(nullptr), destInvite(nullptr), speedLabel(nullptr), sizeLabel(nullptr), resumeLabel(nullptr), progressBar(nullptr), suspendedProperty(false), refCount(1) { if (!parent) { setWindowFlags(windowFlags() | Qt::Dialog); } init(); } ~ProgressWidget() { delete tracker->d->eventLoopLocker; tracker->d->eventLoopLocker = nullptr; } KWidgetJobTracker *const tracker; KJob *const job; qulonglong totalSize; qulonglong totalFiles; qulonglong totalDirs; + qulonglong totalItems; qulonglong processedSize; qulonglong processedDirs; qulonglong processedFiles; + qulonglong processedItems; bool totalSizeKnown; bool stopOnClose; bool jobRegistered; QString caption; QPushButton *cancelClose; QPushButton *openFile; QPushButton *openLocation; QCheckBox *keepOpenCheck; QUrl location; QElapsedTimer startTime; QPushButton *pauseButton; KSqueezedTextLabel *sourceEdit; KSqueezedTextLabel *destEdit; QLabel *progressLabel; QLabel *sourceInvite; QLabel *destInvite; QLabel *speedLabel; QLabel *sizeLabel; QLabel *resumeLabel; QProgressBar *progressBar; QPushButton *arrowButton; Qt::ArrowType arrowState; bool suspendedProperty; int refCount; // will not close the dialog if a modal menu is shown void init(); void showTotals(); void setDestVisible(bool visible); void checkDestination(const QUrl &dest); void ref(); void deref(); void closeNow(); bool eventFilter(QObject *watched, QEvent *event) override; public Q_SLOTS: virtual void infoMessage(const QString &plain, const QString &rich); virtual void description(const QString &title, const QPair &field1, const QPair &field2); virtual void totalAmount(KJob::Unit unit, qulonglong amount); virtual void processedAmount(KJob::Unit unit, qulonglong amount); virtual void percent(unsigned long percent); virtual void speed(unsigned long value); virtual void slotClean(); virtual void suspended(); virtual void resumed(); //TODO: Misses canResume() protected: void closeEvent(QCloseEvent *event) override; private Q_SLOTS: void keepOpenToggled(bool); void openFileClicked(); void openLocationClicked(); void pauseResumeClicked(); void cancelClicked(); void arrowClicked(); }; void KWidgetJobTracker::Private::setStopOnClose(KJob *job, bool stopOnClose) { if (!progressWidget.contains(job)) { return; } progressWidget[job]->stopOnClose = stopOnClose; } bool KWidgetJobTracker::Private::stopOnClose(KJob *job) const { if (!progressWidget.contains(job)) { qWarning() << "no widget found for job" << job; return true; } return progressWidget[job]->stopOnClose; } void KWidgetJobTracker::Private::setAutoDelete(KJob *job, bool autoDelete) { if (!progressWidget.contains(job)) { return; } progressWidget[job]->setAttribute(Qt::WA_DeleteOnClose, autoDelete); } bool KWidgetJobTracker::Private::autoDelete(KJob *job) const { if (!progressWidget.contains(job)) { qWarning() << "no widget found for job" << job; return true; } return progressWidget[job]->testAttribute(Qt::WA_DeleteOnClose); } #endif // KWIDGETJOBTRACKER_P_H