diff --git a/applets/notifications/package/contents/ui/JobDelegate.qml b/applets/notifications/package/contents/ui/JobDelegate.qml index 7e4dd8fc7..9034c9e99 100644 --- a/applets/notifications/package/contents/ui/JobDelegate.qml +++ b/applets/notifications/package/contents/ui/JobDelegate.qml @@ -1,195 +1,196 @@ /* * Copyright 2011 Marco Martin * Copyright 2014 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program 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 program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.0 import QtQuick.Layouts 1.1 import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 Column { id: jobItem width: parent.width spacing: jobItem.layoutSpacing readonly property int layoutSpacing: units.largeSpacing / 4 readonly property int animationDuration: units.shortDuration * 2 readonly property string infoMessage: getData(jobsSource.data, "infoMessage", '') readonly property string labelName0: getData(jobsSource.data, "labelName0", '') readonly property string labelName1: getData(jobsSource.data, "labelName1", '') + readonly property string labelFileName0: getData(jobsSource.data, "labelFileName0", '') readonly property string label0: getData(jobsSource.data, "label0", '') readonly property string label1: getData(jobsSource.data, "label1", '') readonly property bool isSuspended: getData(jobsSource.data, "state", '') === "suspended" property alias infoMessageVisible: infoLabel.visible function getData(data, name, defaultValue) { var source = model.name return data[source] ? (data[source][name] ? data[source][name] : defaultValue) : defaultValue; } PlasmaExtras.Heading { id: infoLabel width: parent.width opacity: 0.6 level: 3 text: jobItem.isSuspended ? i18nc("Placeholder is job name, eg. 'Copying'", "%1 (Paused)", infoMessage) : infoMessage textFormat: Text.PlainText } RowLayout { width: parent.width PlasmaComponents.Label { id: summary Layout.fillWidth: true elide: Text.ElideMiddle text: { - var label = label0; + var label = labelFileName0; var lastSlashIdx = label.lastIndexOf("/"); return label.slice(lastSlashIdx + 1); } textFormat: Text.PlainText } PlasmaComponents.ToolButton { id: expandButton iconSource: checked ? "arrow-down" : (LayoutMirroring.enabled ? "arrow-left" : "arrow-right") tooltip: checked ? i18nc("A button tooltip; hides item details", "Hide Details") : i18nc("A button tooltip; expands the item to show details", "Show Details") checkable: true onCheckedChanged: { if (checked) { // Need to force the Loader active here, otherwise the transition // doesn't fire because the height is still 0 without a loaded item detailsLoader.active = true } } } } Loader { id: detailsLoader width: parent.width height: 0 //visible: false // this breaks the opening transition but given the loaded item is released anyway... source: "JobDetailsItem.qml" active: false opacity: state === "expanded" ? 0.6 : 0 Behavior on opacity { NumberAnimation { duration: jobItem.animationDuration } } states: [ State { name: "expanded" when: expandButton.checked && detailsLoader.status === Loader.Ready PropertyChanges { target: detailsLoader height: detailsLoader.item.implicitHeight } } ] transitions : [ Transition { from: "" // default state - collapsed to: "expanded" SequentialAnimation { ScriptAction { script: detailsLoader.clip = true } NumberAnimation { duration: jobItem.animationDuration properties: "height" easing.type: Easing.InOutQuad } ScriptAction { script: detailsLoader.clip = false } } }, Transition { from: "expanded" to: "" // default state - collapsed SequentialAnimation { ScriptAction { script: detailsLoader.clip = true } NumberAnimation { duration: jobItem.animationDuration properties: "height" easing.type: Easing.InOutQuad } ScriptAction { script: { detailsLoader.clip = false detailsLoader.active = false } } } } ] } RowLayout { width: parent.width height: pauseButton.height spacing: jobItem.layoutSpacing PlasmaComponents.ProgressBar { id: progressBar Layout.fillWidth: true //height: units.gridUnit minimumValue: 0 maximumValue: 100 //percentage doesn't always exist, so doesn't get in the model value: getData(jobsSource.data, "percentage", 0) indeterminate: plasmoid.expanded && jobsSource.data[model.name] && typeof jobsSource.data[model.name]["percentage"] === "undefined" && !jobItem.isSuspended } PlasmaComponents.ToolButton { id: pauseButton iconSource: jobItem.isSuspended ? "media-playback-start" : "media-playback-pause" visible: getData(jobsSource.data, "suspendable", 0) onClicked: { var operationName = "suspend" if (jobItem.isSuspended) { operationName = "resume" } var service = jobsSource.serviceForSource(model.name) var operation = service.operationDescription(operationName) service.startOperationCall(operation) } } PlasmaComponents.ToolButton { id: stopButton iconSource: "media-playback-stop" visible: getData(jobsSource.data, "killable", 0) onClicked: { var service = jobsSource.serviceForSource(model.name) var operation = service.operationDescription("stop") service.startOperationCall(operation) } } } } diff --git a/dataengines/applicationjobs/kuiserverengine.cpp b/dataengines/applicationjobs/kuiserverengine.cpp index a9be3e69f..3e985403e 100644 --- a/dataengines/applicationjobs/kuiserverengine.cpp +++ b/dataengines/applicationjobs/kuiserverengine.cpp @@ -1,393 +1,399 @@ /* * Copyright © 2008 Rob Scheepmaker * * This program 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 program 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 General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "jobviewadaptor.h" #include "jobviewserveradaptor.h" #include "kuiserverengine.h" #include "jobcontrol.h" #include +#include #include #include #include #include uint JobView::s_jobId = 0; static const int UPDATE_INTERVAL = 100; JobView::JobView(QObject* parent) : Plasma::DataContainer(parent), m_capabilities(-1), m_percent(0), m_speed(0), m_totalBytes(0), m_processedBytes(0), m_state(UnknownState), m_bytesUnitId(-1), m_unitId(0) { m_jobId = ++s_jobId; setObjectName(QStringLiteral("Job %1").arg(s_jobId)); new JobViewV2Adaptor(this); m_objectPath.setPath(QStringLiteral("/DataEngine/applicationjobs/JobView_%1").arg(m_jobId)); QDBusConnection::sessionBus().registerObject(m_objectPath.path(), this); setSuspended(false); } JobView::~JobView() { QDBusConnection::sessionBus().unregisterObject(m_objectPath.path(), QDBusConnection::UnregisterTree); } uint JobView::jobId() const { return m_jobId; } void JobView::scheduleUpdate() { if (!m_updateTimer.isActive()) { m_updateTimer.start(UPDATE_INTERVAL, this); } } void JobView::timerEvent(QTimerEvent *event) { if (event->timerId() == m_updateTimer.timerId()) { m_updateTimer.stop(); checkForUpdate(); if (m_state == Stopped) { emit becameUnused(objectName()); } } else { Plasma::DataContainer::timerEvent(event); } } void JobView::terminate(const QString &errorMessage) { setData(QStringLiteral("errorText"), errorMessage); QTimer::singleShot(0, this, &JobView::finished); } void JobView::finished() { if (m_state != Stopped) { m_state = Stopped; setData(QStringLiteral("state"), "stopped"); setData(QStringLiteral("speed"), QVariant()); setData(QStringLiteral("numericSpeed"), QVariant()); scheduleUpdate(); } } JobView::State JobView::state() { return m_state; } void JobView::setSuspended(bool suspended) { if (suspended) { if (m_state != Suspended) { m_state = Suspended; setData(QStringLiteral("state"), "suspended"); setData(QStringLiteral("speed"), QVariant()); setData(QStringLiteral("numericSpeed"), QVariant()); scheduleUpdate(); } } else if (m_state != Running) { m_state = Running; setData(QStringLiteral("state"), "running"); setData(QStringLiteral("speed"), speedString()); setData(QStringLiteral("numericSpeed"), m_speed); scheduleUpdate(); } } void JobView::setError(uint errorCode) { setData(QStringLiteral("error"), errorCode); } int JobView::unitId(const QString &unit) { int id = 0; if (m_unitMap.contains(unit)) { id = m_unitMap.value(unit); } else { id = m_unitId; setData(QStringLiteral("totalUnit%1").arg(id), unit); setData(QStringLiteral("totalAmount%1").arg(id), 0); setData(QStringLiteral("processedUnit%1").arg(id), unit); setData(QStringLiteral("processedAmount%1").arg(id), 0); m_unitMap.insert(unit, m_unitId); if (unit == QLatin1String("bytes")) { m_bytesUnitId = id; } ++m_unitId; scheduleUpdate(); } return id; } void JobView::updateEta() { if (m_speed < 1) { setData(QStringLiteral("eta"), 0); return; } if (m_totalBytes < 1) { setData(QStringLiteral("eta"), 0); return; } const qlonglong remaining = 1000 * (m_totalBytes - m_processedBytes); setData(QStringLiteral("eta"), remaining / m_speed); } void JobView::setTotalAmount(qlonglong amount, const QString &unit) { const int id = unitId(unit); const QString amountString = QStringLiteral("totalAmount%1").arg(id); const qlonglong prevTotal = data().value(amountString).toLongLong(); if (prevTotal != amount) { if (id == m_bytesUnitId) { m_totalBytes = amount; updateEta(); } setData(amountString, amount); scheduleUpdate(); } } void JobView::setProcessedAmount(qlonglong amount, const QString &unit) { const int id = unitId(unit); const QString processedString = QStringLiteral("processedAmount%1").arg(id); const qlonglong prevTotal = data().value(processedString).toLongLong(); if (prevTotal != amount) { if (id == m_bytesUnitId) { m_processedBytes = amount; if (!m_totalBytes && m_processedBytes && (m_percent != 0)) { m_totalBytes = m_processedBytes / m_percent * 100; const QString totalAmountString = QStringLiteral("totalAmount%1").arg(id); setData(totalAmountString, m_totalBytes); } updateEta(); } setData(processedString, amount); scheduleUpdate(); } } void JobView::setDestUrl(const QDBusVariant & destUrl) { setData(QStringLiteral("destUrl"), destUrl.variant().toUrl()); } void JobView::setPercent(uint percent) { if (m_percent != percent) { m_percent = percent; setData(QStringLiteral("percentage"), m_percent); scheduleUpdate(); } } void JobView::setSpeed(qlonglong bytesPerSecond) { if (m_speed != bytesPerSecond) { m_speed = bytesPerSecond; setData(QStringLiteral("speed"), speedString()); setData(QStringLiteral("numericSpeed"), m_speed); if (m_bytesUnitId > -1) { updateEta(); } scheduleUpdate(); } } QString JobView::speedString() const { return i18nc("Bytes per second", "%1/s", KFormat().formatByteSize(m_speed)); } void JobView::setInfoMessage(const QString &infoMessage) { if (data().value(QStringLiteral("infoMessage")) != infoMessage) { setData(QStringLiteral("infoMessage"), infoMessage); scheduleUpdate(); } } bool JobView::setDescriptionField(uint number, const QString &name, const QString &value) { const QString labelString = QStringLiteral("label%1").arg(number); const QString labelNameString = QStringLiteral("labelName%1").arg(number); + const QString labelFileNameString = QStringLiteral("labelFileName%1").arg(number); if (!data().contains(labelNameString) || data().value(labelString) != value) { setData(labelNameString, name); setData(labelString, value); + QUrl url = QUrl::fromUserInput(value, QString(), QUrl::AssumeLocalFile); + setData(labelFileNameString, url.toString(QUrl::PreferLocalFile | QUrl::RemoveFragment | QUrl::RemoveQuery)); scheduleUpdate(); } return true; } void JobView::clearDescriptionField(uint number) { const QString labelString = QStringLiteral("label%1").arg(number); const QString labelNameString = QStringLiteral("labelName%1").arg(number); + const QString labelFileNameString = QStringLiteral("labelFileName%1").arg(number); setData(labelNameString, QVariant()); setData(labelString, QVariant()); + setData(labelFileNameString, QVariant()); scheduleUpdate(); } void JobView::setAppName(const QString &appName) { // don't need to update, this is only set once at creation setData(QStringLiteral("appName"), appName); } void JobView::setAppIconName(const QString &appIconName) { // don't need to update, this is only set once at creation setData(QStringLiteral("appIconName"), appIconName); } void JobView::setCapabilities(int capabilities) { if (m_capabilities != uint(capabilities)) { m_capabilities = capabilities; setData(QStringLiteral("suspendable"), m_capabilities & KJob::Suspendable); setData(QStringLiteral("killable"), m_capabilities & KJob::Killable); scheduleUpdate(); } } QDBusObjectPath JobView::objectPath() const { return m_objectPath; } void JobView::requestStateChange(State state) { switch (state) { case Running: emit resumeRequested(); break; case Suspended: emit suspendRequested(); break; case Stopped: emit cancelRequested(); break; default: break; } } KuiserverEngine::KuiserverEngine(QObject* parent, const QVariantList& args) : Plasma::DataEngine(parent, args) { new JobViewServerAdaptor(this); QDBusConnection bus = QDBusConnection::sessionBus(); bus.registerObject(QLatin1String("/DataEngine/applicationjobs/JobWatcher"), this); setMinimumPollingInterval(500); m_pendingJobsTimer.setSingleShot(true); m_pendingJobsTimer.setInterval(500); connect(&m_pendingJobsTimer, &QTimer::timeout, this, &KuiserverEngine::processPendingJobs); init(); } KuiserverEngine::~KuiserverEngine() { QDBusConnection::sessionBus() .unregisterObject(QLatin1String("/DataEngine/applicationjobs/JobWatcher"), QDBusConnection::UnregisterTree); qDeleteAll(m_pendingJobs); } QDBusObjectPath KuiserverEngine::requestView(const QString &appName, const QString &appIconName, int capabilities) { JobView *jobView = new JobView(this); jobView->setAppName(appName); jobView->setAppIconName(appIconName); jobView->setCapabilities(capabilities); connect(jobView, &Plasma::DataContainer::becameUnused, this, &KuiserverEngine::removeSource); m_pendingJobs << jobView; m_pendingJobsTimer.start(); return jobView->objectPath(); } void KuiserverEngine::processPendingJobs() { foreach (JobView *jobView, m_pendingJobs) { if (jobView->state() == JobView::Stopped) { delete jobView; } else { addSource(jobView); } } m_pendingJobs.clear(); } Plasma::Service* KuiserverEngine::serviceForSource(const QString& source) { JobView *jobView = qobject_cast(containerForSource(source)); if (jobView) { return new JobControl(this, jobView); } else { return DataEngine::serviceForSource(source); } } void KuiserverEngine::init() { // register with the Job UI Serer to receive notifications of jobs becoming available QDBusInterface interface(QStringLiteral("org.kde.kuiserver"), QStringLiteral("/JobViewServer")/* object to connect to */, QLatin1String("")/* use the default interface */, QDBusConnection::sessionBus(), this); interface.asyncCall(QLatin1String("registerService"), QDBusConnection::sessionBus().baseService(), "/DataEngine/applicationjobs/JobWatcher"); } K_EXPORT_PLASMA_DATAENGINE_WITH_JSON(kuiserver, KuiserverEngine, "plasma-dataengine-applicationjobs.json") #include "kuiserverengine.moc"