diff --git a/applets/analog-clock/contents/ui/Hand.qml b/applets/analog-clock/contents/ui/Hand.qml index 510316a61..e550dd49a 100644 --- a/applets/analog-clock/contents/ui/Hand.qml +++ b/applets/analog-clock/contents/ui/Hand.qml @@ -1,81 +1,80 @@ /* * Copyright 2012 Viranch Mehta * Copyright 2012 Marco Martin * Copyright 2013 David Edmundson * * 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 org.kde.plasma.core 2.0 as PlasmaCore PlasmaCore.SvgItem { id: handRoot property alias rotation: rotation.angle property double svgScale property double horizontalRotationOffset: 0 property double verticalRotationOffset: 0 property string rotationCenterHintId readonly property double horizontalRotationCenter: { if (svg.hasElement(rotationCenterHintId)) { var hintedCenterRect = svg.elementRect(rotationCenterHintId), handRect = svg.elementRect(elementId), hintedX = hintedCenterRect.x - handRect.x + hintedCenterRect.width/2; return Math.round(hintedX * svgScale) + Math.round(hintedX * svgScale) % 2; } return width/2; } readonly property double verticalRotationCenter: { if (svg.hasElement(rotationCenterHintId)) { var hintedCenterRect = svg.elementRect(rotationCenterHintId), handRect = svg.elementRect(elementId), hintedY = hintedCenterRect.y - handRect.y + hintedCenterRect.height/2; return Math.round(hintedY * svgScale) + width % 2; } return width/2; } width: Math.round(naturalSize.width * svgScale) + Math.round(naturalSize.width * svgScale) % 2 height: Math.round(naturalSize.height * svgScale) + width % 2 anchors { top: clock.verticalCenter topMargin: -verticalRotationCenter + verticalRotationOffset left: clock.horizontalCenter leftMargin: -horizontalRotationCenter + horizontalRotationOffset } svg: clockSvg - smooth: !anim.running transform: Rotation { id: rotation angle: 0 origin { x: handRoot.horizontalRotationCenter y: handRoot.verticalRotationCenter } Behavior on angle { RotationAnimation { id: anim duration: 200 direction: RotationAnimation.Clockwise easing.type: Easing.OutElastic easing.overshoot: 0.5 } } } } diff --git a/components/shellprivate/widgetexplorer/widgetexplorer.cpp b/components/shellprivate/widgetexplorer/widgetexplorer.cpp index a3185b05b..90bfadeea 100644 --- a/components/shellprivate/widgetexplorer/widgetexplorer.cpp +++ b/components/shellprivate/widgetexplorer/widgetexplorer.cpp @@ -1,510 +1,510 @@ /* * Copyright (C) 2007 by Ivan Cukic * Copyright (C) 2009 by Ana Cecília Martins * Copyright 2013 by Sebastian Kügler * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library/Lesser General Public License * version 2, or (at your option) any later version, 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/Lesser 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 "widgetexplorer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kcategorizeditemsviewmodels_p.h" #include "plasmaappletitemmodel_p.h" #include "openwidgetassistant_p.h" #include "config-workspace.h" using namespace KActivities; using namespace KCategorizedItemsViewModels; using namespace Plasma; WidgetAction::WidgetAction(QObject *parent) : QAction(parent) { } WidgetAction::WidgetAction(const QIcon &icon, const QString &text, QObject *parent) : QAction(icon, text, parent) { } class WidgetExplorerPrivate { public: WidgetExplorerPrivate(WidgetExplorer *w) : q(w), containment(nullptr), itemModel(w), filterModel(w), activitiesConsumer(new KActivities::Consumer()) { QObject::connect(activitiesConsumer.data(), &Consumer::currentActivityChanged, q, [this] { initRunningApplets(); }); } void initFilters(); void initRunningApplets(); void containmentDestroyed(); void addContainment(Containment *containment); /** * Tracks a new running applet */ void appletAdded(Plasma::Applet *applet); /** * A running applet is no more */ void appletRemoved(Plasma::Applet *applet); WidgetExplorer *q; QString application; Plasma::Containment *containment; QHash runningApplets; // applet name => count //extra hash so we can look up the names of deleted applets QHash appletNames; QPointer openAssistant; KPackage::Package *package; PlasmaAppletItemModel itemModel; KCategorizedItemsViewModels::DefaultFilterModel filterModel; bool showSpecialFilters = true; DefaultItemFilterProxyModel filterItemModel; QPointer newStuffDialog; QScopedPointer activitiesConsumer; }; void WidgetExplorerPrivate::initFilters() { filterModel.clear(); filterModel.addFilter(i18n("All Widgets"), KCategorizedItemsViewModels::Filter(), QIcon::fromTheme(QStringLiteral("plasma"))); if (showSpecialFilters) { // Filters: Special filterModel.addFilter(i18n("Running"), KCategorizedItemsViewModels::Filter(QStringLiteral("running"), true), QIcon::fromTheme(QStringLiteral("dialog-ok"))); filterModel.addFilter(i18n("Uninstallable"), KCategorizedItemsViewModels::Filter(QStringLiteral("local"), true), - QIcon::fromTheme(QStringLiteral("list-remove"))); + QIcon::fromTheme(QStringLiteral("edit-delete"))); filterModel.addSeparator(i18n("Categories:")); } typedef QPair catPair; QMap categories; QSet existingCategories = itemModel.categories(); //foreach (const QString &category, Plasma::Applet::listCategories(application)) { QStringList cats; const QList list = PluginLoader::self()->listAppletInfo(QString()); for (auto& info : list) { if (!info.isValid()) { continue; } if (info.property(QStringLiteral("NoDisplay")).toBool() || info.category() == QLatin1String("Containments") || info.category().isEmpty()) { // we don't want to show the hidden category continue; } const QString c = info.category(); if (-1 == cats.indexOf(c)) { cats << c; } } qWarning() << "TODO: port listCategories()"; foreach (const QString &category, cats) { const QString lowerCaseCat = category.toLower(); if (existingCategories.contains(lowerCaseCat)) { const QString trans = i18nd("libplasma5", category.toLocal8Bit()); categories.insert(trans.toLower(), qMakePair(trans, lowerCaseCat)); } } foreach (const catPair &category, categories) { filterModel.addFilter(category.first, KCategorizedItemsViewModels::Filter(QStringLiteral("category"), category.second)); } } void WidgetExplorer::classBegin() { } void WidgetExplorer::componentComplete() { d->itemModel.setStartupCompleted(true); setApplication(); d->initRunningApplets(); } QObject *WidgetExplorer::widgetsModel() const { return &d->filterItemModel; } QObject *WidgetExplorer::filterModel() const { return &d->filterModel; } bool WidgetExplorer::showSpecialFilters() const { return d->showSpecialFilters; } void WidgetExplorer::setShowSpecialFilters(bool show) { if (d->showSpecialFilters != show) { d->showSpecialFilters = show; d->initFilters(); emit showSpecialFiltersChanged(); } } QList WidgetExplorer::widgetsMenuActions() { QList actionList; WidgetAction *action = nullptr; if (KAuthorized::authorize(QStringLiteral("ghns"))) { action = new WidgetAction(QIcon::fromTheme(QStringLiteral("applications-internet")), i18n("Download New Plasma Widgets"), this); connect(action, &QAction::triggered, this, &WidgetExplorer::downloadWidgets); actionList << action; } action = new WidgetAction(this); action->setSeparator(true); actionList << action; action = new WidgetAction(QIcon::fromTheme(QStringLiteral("package-x-generic")), i18n("Install Widget From Local File..."), this); QObject::connect(action, &QAction::triggered, this, &WidgetExplorer::openWidgetFile); actionList << action; return actionList; } QList WidgetExplorer::extraActions() const { QList actionList; // foreach (QAction *action, actions()) { // FIXME: where did actions() come from? // actionList << action; // } qWarning() << "extraactions needs reimplementation"; return actionList; } void WidgetExplorerPrivate::initRunningApplets() { //get applets from corona, count them, send results to model if (!containment) { return; } Plasma::Corona *c = containment->corona(); //we've tried our best to get a corona //we don't want just one containment, we want them all if (!c) { qWarning() << "WidgetExplorer failed to find corona"; return; } appletNames.clear(); runningApplets.clear(); const QList containments = c->containments(); for (Containment *containment : containments) { if (containment->containmentType() == Plasma::Types::DesktopContainment && containment->activity() != activitiesConsumer->currentActivity()) { continue; } addContainment(containment); } //qDebug() << runningApplets; itemModel.setRunningApplets(runningApplets); } void WidgetExplorerPrivate::addContainment(Containment *containment) { QObject::connect(containment, SIGNAL(appletAdded(Plasma::Applet*)), q, SLOT(appletAdded(Plasma::Applet*))); QObject::connect(containment, SIGNAL(appletRemoved(Plasma::Applet*)), q, SLOT(appletRemoved(Plasma::Applet*))); foreach (Applet *applet, containment->applets()) { if (applet->pluginMetaData().isValid()) { Containment *childContainment = applet->property("containment").value(); if (childContainment) { addContainment(childContainment); } runningApplets[applet->pluginMetaData().pluginId()]++; } else { qDebug() << "Invalid plugin metadata. :("; } } } void WidgetExplorerPrivate::containmentDestroyed() { containment = nullptr; } void WidgetExplorerPrivate::appletAdded(Plasma::Applet *applet) { if (!applet->pluginMetaData().isValid()) { return; } QString name = applet->pluginMetaData().pluginId(); runningApplets[name]++; appletNames.insert(applet, name); itemModel.setRunningApplets(name, runningApplets[name]); } void WidgetExplorerPrivate::appletRemoved(Plasma::Applet *applet) { QString name = appletNames.take(applet); int count = 0; if (runningApplets.contains(name)) { count = runningApplets[name] - 1; if (count < 1) { runningApplets.remove(name); } else { runningApplets[name] = count; } } itemModel.setRunningApplets(name, count); } //WidgetExplorer WidgetExplorer::WidgetExplorer(QObject *parent) : QObject(parent), d(new WidgetExplorerPrivate(this)) { d->filterItemModel.setSortCaseSensitivity(Qt::CaseInsensitive); d->filterItemModel.setDynamicSortFilter(true); d->filterItemModel.setSourceModel(&d->itemModel); d->filterItemModel.sort(0); } WidgetExplorer::~WidgetExplorer() { delete d; } void WidgetExplorer::setApplication(const QString &app) { if (d->application == app && !app.isEmpty()) { return; } d->application = app; d->itemModel.setApplication(app); d->initFilters(); d->itemModel.setRunningApplets(d->runningApplets); emit applicationChanged(); } QString WidgetExplorer::application() { return d->application; } QStringList WidgetExplorer::provides() const { return d->itemModel.provides(); } void WidgetExplorer::setProvides(const QStringList &provides) { if (d->itemModel.provides() == provides) { return; } d->itemModel.setProvides(provides); emit providesChanged(); } void WidgetExplorer::setContainment(Plasma::Containment *containment) { if (d->containment != containment) { if (d->containment) { d->containment->disconnect(this); } d->containment = containment; if (d->containment) { connect(d->containment, SIGNAL(destroyed(QObject*)), this, SLOT(containmentDestroyed())); connect(d->containment, &Applet::immutabilityChanged, this, &WidgetExplorer::immutabilityChanged); } d->initRunningApplets(); emit containmentChanged(); } } Containment *WidgetExplorer::containment() const { return d->containment; } Plasma::Corona *WidgetExplorer::corona() const { if (d->containment) { return d->containment->corona(); } return nullptr; } void WidgetExplorer::addApplet(const QString &pluginName) { const QString p = PLASMA_RELATIVE_DATA_INSTALL_DIR "/plasmoids/"+pluginName; qWarning() << "--------> load applet: " << pluginName << " relpath: " << p; QStringList dirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, p, QStandardPaths::LocateDirectory); qDebug() << " .. pathes: " << dirs; if (!dirs.count()) { qWarning() << "Failed to find plasmoid path for " << pluginName; return; } if (d->containment) { d->containment->createApplet(dirs.first()); } } void WidgetExplorer::immutabilityChanged(Plasma::Types::ImmutabilityType type) { if (type != Plasma::Types::Mutable) { emit shouldClose(); } } void WidgetExplorer::downloadWidgets() { if (!d->newStuffDialog) { d->newStuffDialog = new KNS3::DownloadDialog(QLatin1String("plasmoids.knsrc")); d->newStuffDialog.data()->setWindowTitle(i18n("Download New Plasma Widgets")); d->newStuffDialog.data()->setAttribute(Qt::WA_DeleteOnClose); } d->newStuffDialog.data()->show(); emit shouldClose(); } void WidgetExplorer::openWidgetFile() { Plasma::OpenWidgetAssistant *assistant = d->openAssistant.data(); if (!assistant) { assistant = new Plasma::OpenWidgetAssistant(nullptr); d->openAssistant = assistant; } KWindowSystem::setOnDesktop(assistant->winId(), KWindowSystem::currentDesktop()); assistant->setAttribute(Qt::WA_DeleteOnClose, true); assistant->show(); assistant->raise(); assistant->setFocus(); emit shouldClose(); } void WidgetExplorer::uninstall(const QString &pluginName) { static const QString packageRoot = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + PLASMA_RELATIVE_DATA_INSTALL_DIR "/plasmoids/"; KPackage::PackageStructure *structure = KPackage::PackageLoader::self()->loadPackageStructure(QStringLiteral("Plasma/Applet")); KPackage::Package pkg(structure); pkg.uninstall(pluginName, packageRoot); //FIXME: moreefficient way rather a linear scan? for (int i = 0; i < d->itemModel.rowCount(); ++i) { QStandardItem *item = d->itemModel.item(i); if (item->data(PlasmaAppletItemModel::PluginNameRole).toString() == pluginName) { d->itemModel.takeRow(i); break; } } // now remove all instances of that applet if (corona()) { const auto &containments = corona()->containments(); foreach (Containment *c, containments) { const auto &applets = c->applets(); foreach (Applet *applet, applets) { const auto &appletInfo = applet->pluginMetaData(); if (appletInfo.isValid() && appletInfo.pluginId() == pluginName) { applet->destroy(); } } } } } #include "moc_widgetexplorer.cpp" diff --git a/dataengines/mpris2/playercontrol.cpp b/dataengines/mpris2/playercontrol.cpp index 0cfefcc95..28d1bbb30 100644 --- a/dataengines/mpris2/playercontrol.cpp +++ b/dataengines/mpris2/playercontrol.cpp @@ -1,126 +1,147 @@ /* * Copyright 2008 Alex Merry * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "playercontrol.h" #include "playeractionjob.h" #include "playercontainer.h" #include #include #include #include +#include +#include PlayerControl::PlayerControl(PlayerContainer* container, QObject* parent) : Plasma::Service(parent) , m_container(container) { setObjectName(container->objectName() + QLatin1String(" controller")); setName(QStringLiteral("mpris2")); setDestination(container->objectName()); connect(container, &Plasma::DataContainer::dataUpdated, this, &PlayerControl::updateEnabledOperations); connect(container, &QObject::destroyed, this, &PlayerControl::containerDestroyed); updateEnabledOperations(); } void PlayerControl::updateEnabledOperations() { PlayerContainer::Caps caps = PlayerContainer::NoCaps; if (m_container) caps = m_container->capabilities(); setOperationEnabled(QStringLiteral("Quit"), caps & PlayerContainer::CanQuit); setOperationEnabled(QStringLiteral("Raise"), caps & PlayerContainer::CanRaise); setOperationEnabled(QStringLiteral("SetFullscreen"), caps & PlayerContainer::CanSetFullscreen); setOperationEnabled(QStringLiteral("Play"), caps & PlayerContainer::CanPlay); setOperationEnabled(QStringLiteral("Pause"), caps & PlayerContainer::CanPause); setOperationEnabled(QStringLiteral("PlayPause"), caps & (PlayerContainer::CanPlay | PlayerContainer::CanPause)); setOperationEnabled(QStringLiteral("Stop"), caps & PlayerContainer::CanStop); setOperationEnabled(QStringLiteral("Next"), caps & PlayerContainer::CanGoNext); setOperationEnabled(QStringLiteral("Previous"), caps & PlayerContainer::CanGoPrevious); setOperationEnabled(QStringLiteral("Seek"), caps & PlayerContainer::CanSeek); setOperationEnabled(QStringLiteral("SetPosition"), caps & PlayerContainer::CanSeek); setOperationEnabled(QStringLiteral("OpenUri"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetVolume"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("ChangeVolume"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetLoopStatus"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetRate"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("SetShuffle"), caps & PlayerContainer::CanControl); setOperationEnabled(QStringLiteral("GetPosition"), true); emit enabledOperationsChanged(); } QDBusObjectPath PlayerControl::trackId() const { QVariant mprisTrackId = m_container->data().value(QStringLiteral("Metadata")).toMap().value(QStringLiteral("mpris:trackid")); if (mprisTrackId.canConvert()) { return mprisTrackId.value(); } QString mprisTrackIdString = mprisTrackId.toString(); if (!mprisTrackIdString.isEmpty()) { return QDBusObjectPath(mprisTrackIdString); } return QDBusObjectPath(); } void PlayerControl::containerDestroyed() { m_container = nullptr; } -void PlayerControl::changeVolume(double delta, bool showOSD) { - const double volume = playerInterface()->volume(); - const double newVolume = qBound(0.0, volume + delta, qMax(volume, 1.0)); - playerInterface()->setVolume(newVolume); - - if (showOSD) { - const auto& data = m_container->data(); - - QDBusMessage msg = QDBusMessage::createMethodCall( - QStringLiteral("org.kde.plasmashell"), - QStringLiteral("/org/kde/osdService"), - QStringLiteral("org.kde.osdService"), - QStringLiteral("mediaPlayerVolumeChanged") - ); +void PlayerControl::changeVolume(double delta, bool showOSD) +{ + // Not relying on property/setProperty to avoid doing blocking DBus calls - msg.setArguments({ - (int)(100 * newVolume), - data.value("Identity", ""), - data.value("Desktop Icon Name", "") - }); + const double volume = m_container->data().value(QStringLiteral("Volume")).toDouble(); + const double newVolume = qBound(0.0, volume + delta, qMax(volume, 1.0)); - QDBusConnection::sessionBus().asyncCall(msg); - } + QDBusPendingCall reply = propertiesInterface()->Set(m_container->playerInterface()->interface(), + QStringLiteral("Volume"), QDBusVariant(newVolume)); + + // Update the container value right away so when calling this method in quick succession + // (mouse wheeling the tray icon) next call already gets the new value + m_container->setData(QStringLiteral("Volume"), newVolume); + + QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this); + connect(watcher, &QDBusPendingCallWatcher::finished, [this, showOSD](QDBusPendingCallWatcher *watcher) { + watcher->deleteLater(); + + QDBusPendingReply reply = *watcher; + if (reply.isError()) { + return; + } + + if (showOSD) { + const auto& data = m_container->data(); + + QDBusMessage msg = QDBusMessage::createMethodCall( + QStringLiteral("org.kde.plasmashell"), + QStringLiteral("/org/kde/osdService"), + QStringLiteral("org.kde.osdService"), + QStringLiteral("mediaPlayerVolumeChanged") + ); + + msg.setArguments({ + qRound(data.value(QStringLiteral("Volume")).toDouble() * 100), + data.value("Identity", ""), + data.value("Desktop Icon Name", "") + }); + + QDBusConnection::sessionBus().asyncCall(msg); + } + }); } Plasma::ServiceJob* PlayerControl::createJob(const QString& operation, QMap& parameters) { if (!m_container) return nullptr; return new PlayerActionJob(operation, parameters, this); } // vim: sw=4 sts=4 et tw=100