diff --git a/applets/icon/iconapplet.cpp b/applets/icon/iconapplet.cpp index d23c2bb9b..25e90e293 100644 --- a/applets/icon/iconapplet.cpp +++ b/applets/icon/iconapplet.cpp @@ -1,436 +1,436 @@ /* * Copyright 2016 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 General Public License * along with this program. If not, see . * */ #include "iconapplet.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include IconApplet::IconApplet(QObject *parent, const QVariantList &data) : Plasma::Applet(parent, data) { } IconApplet::~IconApplet() { // in a handler connected to IconApplet::appletDeleted m_localPath will be empty?! if (destroyed()) { QFile::remove(m_localPath); } } void IconApplet::init() { populate(); } void IconApplet::populate() { m_url = config().readEntry(QStringLiteral("url"), QUrl()); if (!m_url.isValid()) { // the old applet that used a QML plugin and stored its url // in plasmoid.configuration.url had its entries stored in [Configuration][General] // so we look here as well to provide an upgrade path m_url = config().group("General").readEntry(QStringLiteral("url"), QUrl()); } // our backing desktop file already exists? just read all the things from it const QString path = localPath(); if (QFileInfo::exists(path)) { populateFromDesktopFile(path); return; } if (!m_url.isValid()) { // invalid url, use dummy data populateFromDesktopFile(QString()); return; } const QString plasmaIconsFolderPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1String("/plasma_icons"); if (!QDir().mkpath(plasmaIconsFolderPath)) { setLaunchErrorMessage(i18n("Failed to create icon widgets folder '%1'", plasmaIconsFolderPath)); return; } QString desiredDesktopFileName = m_url.fileName(); // in doubt, just hash the URL, e.g. http://www.kde.org/ has no filename if (desiredDesktopFileName.isEmpty()) { desiredDesktopFileName = QString::fromLatin1(QCryptographicHash::hash(m_url.toDisplayString().toUtf8(), QCryptographicHash::Md5).toHex()); } // We always want it to be a .desktop file (e.g. also for the "Type=Link" at the end) if (!desiredDesktopFileName.endsWith(QLatin1String(".desktop"))) { desiredDesktopFileName.append(QLatin1String(".desktop")); } QString backingDesktopFile = plasmaIconsFolderPath + QLatin1Char('/'); // KIO::suggestName always appends a suffix, i.e. it expects that we already know the file already exists if (QFileInfo::exists(backingDesktopFile + desiredDesktopFileName)) { desiredDesktopFileName = KIO::suggestName(QUrl::fromLocalFile(plasmaIconsFolderPath), desiredDesktopFileName); } backingDesktopFile.append(desiredDesktopFileName); QString name; // ends up as "Name" in the .desktop file for "Link" files below if (m_url.isLocalFile()) { const QString localUrlString = m_url.toLocalFile(); // if desktop file just copy it over if (KDesktopFile::isDesktopFile(localUrlString)) { // if this restriction is set, KIO won't allow running desktop files from outside // registered services, applications, and so on, in this case we'll use the original // .desktop file and lose the ability to customize it if (!KAuthorized::authorize(QStringLiteral("run_desktop_files"))) { populateFromDesktopFile(localUrlString); // we don't call setLocalPath here as we don't want to store localPath to be a system-location // so that the fact that we cannot edit is re-evaluated every time return; } if (!QFile::copy(localUrlString, backingDesktopFile)) { setLaunchErrorMessage(i18n("Failed to copy icon widget desktop file from '%1' to '%2'", localUrlString, backingDesktopFile)); return; } // set executable flag on the desktop file so KIO doesn't complain about executing it QFile file(backingDesktopFile); file.setPermissions(file.permissions() | QFile::ExeOwner); populateFromDesktopFile(backingDesktopFile); setLocalPath(backingDesktopFile); return; } name = QFileInfo(localUrlString).baseName(); } // in all other cases just make it a link // TODO use kio stat job which also works for remote stuff QMimeDatabase db; const QMimeType mimeType = db.mimeTypeForUrl(m_url); KDesktopFile linkDesktopFile(backingDesktopFile); auto desktopGroup = linkDesktopFile.desktopGroup(); if (name.isEmpty()) { if (m_url.scheme().startsWith(QLatin1String("http"))) { name = m_url.host(); } else { name = m_url.fileName(); } } desktopGroup.writeEntry(QStringLiteral("Name"), name); desktopGroup.writeEntry(QStringLiteral("Type"), QStringLiteral("Link")); desktopGroup.writeEntry(QStringLiteral("URL"), m_url); desktopGroup.writeEntry(QStringLiteral("Icon"), KIO::iconNameForUrl(m_url)); // when in doubt Qt returns application/octet-stream which will show as "Unknown" usually, so don't write it down then if (mimeType.name() != QLatin1String("application/octet-stream")) { desktopGroup.writeEntry(QStringLiteral("GenericName"), mimeType.comment()); } linkDesktopFile.sync(); populateFromDesktopFile(backingDesktopFile); setLocalPath(backingDesktopFile); } void IconApplet::populateFromDesktopFile(const QString &path) { // path empty? just set icon to "unknown" and call it a day if (path.isEmpty()) { setIconName({}); return; } KDesktopFile desktopFile(path); const QString &name = desktopFile.readName(); if (m_name != name) { m_name = name; emit nameChanged(name); } const QString &genericName = desktopFile.readGenericName(); if (m_genericName != genericName) { m_genericName = genericName; emit genericNameChanged(genericName); } setIconName(desktopFile.readIcon()); delete m_openContainingFolderAction; m_openContainingFolderAction = nullptr; m_openWithActions.clear(); if (desktopFile.hasLinkType()) { const QUrl &linkUrl = QUrl(desktopFile.readUrl()); if (!m_fileItemActions) { m_fileItemActions = new KFileItemActions(this); } KFileItemListProperties itemProperties(KFileItemList({KFileItem(linkUrl)})); m_fileItemActions->setItemListProperties(itemProperties); if (!m_openWithMenu) { m_openWithMenu.reset(new QMenu()); } m_openWithMenu->clear(); m_fileItemActions->addOpenWithActionsTo(m_openWithMenu.data()); m_openWithActions = m_openWithMenu->actions(); if (KProtocolManager::supportsListing(linkUrl)) { if (!m_openContainingFolderAction) { m_openContainingFolderAction = new QAction(QIcon::fromTheme(QStringLiteral("document-open-folder")), i18n("Open Containing Folder"), this); connect(m_openContainingFolderAction, &QAction::triggered, this, [this] { KIO::highlightInFileManager({m_openContainingFolderAction->property("linkUrl").toUrl()}); }); } m_openContainingFolderAction->setProperty("linkUrl", linkUrl); } } m_jumpListActions.clear(); foreach (const QString &actionName, desktopFile.readActions()) { const KConfigGroup &actionGroup = desktopFile.actionGroup(actionName); if (!actionGroup.isValid() || !actionGroup.exists()) { continue; } const QString &name = actionGroup.readEntry(QStringLiteral("Name")); const QString &exec = actionGroup.readEntry(QStringLiteral("Exec")); if (name.isEmpty() || exec.isEmpty()) { continue; } QAction *action = new QAction(QIcon::fromTheme(actionGroup.readEntry("Icon")), name, this); connect(action, &QAction::triggered, this, [this, exec] { KRun::run(exec, {}, nullptr, m_name, m_iconName); }); m_jumpListActions << action; } m_localPath = path; } QUrl IconApplet::url() const { return m_url; } void IconApplet::setUrl(const QUrl &url) { if (m_url != url) { m_url = url; urlChanged(url); config().writeEntry(QStringLiteral("url"), url); populate(); } } void IconApplet::setIconName(const QString &iconName) { const QString newIconName = (!iconName.isEmpty() ? iconName : QStringLiteral("unknown")); if (m_iconName != newIconName) { m_iconName = newIconName; emit iconNameChanged(newIconName); } } QString IconApplet::name() const { return m_name; } QString IconApplet::iconName() const { return m_iconName; } QString IconApplet::genericName() const { return m_genericName; } QList IconApplet::contextualActions() { QList actions; actions << m_jumpListActions; if (!actions.isEmpty()) { if (!m_separatorAction) { m_separatorAction = new QAction(this); m_separatorAction->setSeparator(true); } actions << m_separatorAction; } actions << m_openWithActions; if (m_openContainingFolderAction) { actions << m_openContainingFolderAction; } return actions; } -void IconApplet::open() +void IconApplet::run() { new KRun(QUrl::fromLocalFile(m_localPath), QApplication::desktop()); } void IconApplet::processDrop(QObject *dropEvent) { Q_ASSERT(dropEvent); // DeclarativeDropEvent and co aren't public const QObject *mimeData = qvariant_cast(dropEvent->property("mimeData")); Q_ASSERT(mimeData); const QJsonArray &droppedUrls = mimeData->property("urls").toJsonArray(); QList urls; urls.reserve(droppedUrls.count()); foreach (const QJsonValue &droppedUrl, droppedUrls) { const QUrl url(droppedUrl.toString()); if (url.isValid()) { urls.append(url); } } if (urls.isEmpty()) { return; } const QString &localPath = m_url.toLocalFile(); if (KDesktopFile::isDesktopFile(localPath)) { KRun::runService(KService(localPath), urls, nullptr); return; } QMimeDatabase db; const QMimeType mimeType = db.mimeTypeForUrl(m_url); if (KAuthorized::authorize(QStringLiteral("shell_access")) && (mimeType.inherits(QStringLiteral("application/x-executable")) || mimeType.inherits(QStringLiteral("application/x-shellscript")))) { QProcess::startDetached(m_url.toLocalFile(), QUrl::toStringList(urls)); return; } if (mimeType.inherits(QStringLiteral("inode/directory"))) { QMimeData mimeData; mimeData.setUrls(urls); // DeclarativeDropEvent isn't public QDropEvent de(QPointF(dropEvent->property("x").toInt(), dropEvent->property("y").toInt()), static_cast(dropEvent->property("proposedActions").toInt()), &mimeData, static_cast(dropEvent->property("buttons").toInt()), static_cast(dropEvent->property("modifiers").toInt())); KIO::DropJob *dropJob = KIO::drop(&de, m_url); KJobWidgets::setWindow(dropJob, QApplication::desktop()); return; } } void IconApplet::configure() { KPropertiesDialog *dialog = m_configDialog.data(); if (dialog) { dialog->show(); dialog->raise(); return; } dialog = new KPropertiesDialog(QUrl::fromLocalFile(m_localPath)); m_configDialog = dialog; connect(dialog, &KPropertiesDialog::applied, this, [this] { KDesktopFile desktopFile(m_localPath); if (desktopFile.hasLinkType()) { // make sure to fully repopulate in case the user changed the Link URL QFile::remove(m_localPath); setUrl(QUrl(desktopFile.readUrl())); // calls populate() itself } else { populate(); } }); dialog->setAttribute(Qt::WA_DeleteOnClose, true); dialog->setFileNameReadOnly(true); dialog->setWindowTitle(i18n("Properties for %1", m_name)); dialog->setWindowIcon(QIcon::fromTheme(QStringLiteral("document-properties"))); dialog->show(); } QString IconApplet::localPath() const { return config().readEntry(QStringLiteral("localPath")); } void IconApplet::setLocalPath(const QString &localPath) { m_localPath = localPath; config().writeEntry(QStringLiteral("localPath"), localPath); } K_EXPORT_PLASMA_APPLET_WITH_JSON(icon, IconApplet, "metadata.json") #include "iconapplet.moc" diff --git a/applets/icon/iconapplet.h b/applets/icon/iconapplet.h index bdfbc0f5a..804cb4a7b 100644 --- a/applets/icon/iconapplet.h +++ b/applets/icon/iconapplet.h @@ -1,97 +1,97 @@ /* * Copyright 2016 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 General Public License * along with this program. If not, see . * */ #pragma once #include #include #include class KFileItemActions; class QMenu; class IconApplet : public Plasma::Applet { Q_OBJECT Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString iconName READ iconName NOTIFY iconNameChanged) Q_PROPERTY(QString genericName READ genericName NOTIFY genericNameChanged) public: explicit IconApplet(QObject *parent, const QVariantList &data); ~IconApplet() override; void init() override; QUrl url() const; void setUrl(const QUrl &url); QString name() const; QString iconName() const; QString genericName() const; QList contextualActions() override; - Q_INVOKABLE void open(); + Q_INVOKABLE void run(); Q_INVOKABLE void processDrop(QObject *dropEvent); Q_INVOKABLE void configure(); signals: void urlChanged(const QUrl &url); void nameChanged(const QString &name); void iconNameChanged(const QString &iconName); void genericNameChanged(const QString &genericName); void jumpListActionsChanged(const QVariantList &jumpListActions); private: void setIconName(const QString &iconName); void populate(); void populateFromDesktopFile(const QString &path); QString localPath() const; void setLocalPath(const QString &localPath); QUrl m_url; QString m_localPath; QString m_name; QString m_iconName; QString m_genericName; QList m_jumpListActions; QAction *m_separatorAction = nullptr; QList m_openWithActions; QAction *m_openContainingFolderAction = nullptr; KFileItemActions *m_fileItemActions = nullptr; QScopedPointer m_openWithMenu; QPointer m_configDialog; }; diff --git a/applets/icon/package/contents/ui/main.qml b/applets/icon/package/contents/ui/main.qml index 5d865fdc7..7b91ed840 100644 --- a/applets/icon/package/contents/ui/main.qml +++ b/applets/icon/package/contents/ui/main.qml @@ -1,146 +1,146 @@ /* * Copyright 2013 Bhushan Shah * Copyright 2016 Kai Uwe Broulik * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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 General Public License * along with this program. If not, see */ import QtQuick 2.0 import QtQuick.Layouts 1.1 import QtGraphicalEffects 1.0 import org.kde.plasma.plasmoid 2.0 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as Components import org.kde.kquickcontrolsaddons 2.0 import org.kde.draganddrop 2.0 as DragDrop MouseArea { id: root readonly property bool constrained: plasmoid.formFactor === PlasmaCore.Types.Vertical || plasmoid.formFactor === PlasmaCore.Types.Horizontal property bool containsAcceptableDrag: false height: Math.round(units.iconSizes.desktop + 2 * theme.mSize(theme.defaultFont).height) width: Math.round(units.iconSizes.desktop * 1.5) Layout.minimumWidth: plasmoid.formFactor === PlasmaCore.Types.Horizontal ? height : units.iconSizes.small Layout.minimumHeight: plasmoid.formFactor === PlasmaCore.Types.Vertical ? width : units.iconSizes.small hoverEnabled: true - onClicked: plasmoid.nativeInterface.open() + onClicked: plasmoid.nativeInterface.run() Plasmoid.preferredRepresentation: Plasmoid.fullRepresentation Plasmoid.icon: plasmoid.nativeInterface.iconName Plasmoid.title: plasmoid.nativeInterface.name Plasmoid.backgroundHints: PlasmaCore.Types.NoBackground - Plasmoid.onActivated: plasmoid.nativeInterface.open() + Plasmoid.onActivated: plasmoid.nativeInterface.run() Plasmoid.onContextualActionsAboutToShow: updateActions() Component.onCompleted: updateActions() function updateActions() { plasmoid.clearActions() plasmoid.removeAction("configure"); if (plasmoid.immutability !== PlasmaCore.Types.SystemImmutable) { plasmoid.setAction("configure", i18n("Properties"), "document-properties"); } } function action_configure() { plasmoid.nativeInterface.configure() } Connections { target: plasmoid onExternalData: plasmoid.nativeInterface.url = data } DragDrop.DropArea { id: dropArea anchors.fill: parent preventStealing: true onDragEnter: root.containsAcceptableDrag = event.mimeData.hasUrls onDragLeave: root.containsAcceptableDrag = false onDrop: { plasmoid.nativeInterface.processDrop(event) root.containsAcceptableDrag = false } } PlasmaCore.IconItem { id: icon anchors{ left: parent.left right: parent.right top: parent.top bottom: constrained ? parent.bottom : text.top } source: plasmoid.icon enabled: root.enabled active: root.containsMouse || root.containsAcceptableDrag usesPlasmaTheme: false } DropShadow { id: textShadow anchors.fill: text visible: !constrained horizontalOffset: units.devicePixelRatio * 2 verticalOffset: horizontalOffset radius: 9.0 samples: 18 spread: 0.15 color: "black" source: constrained ? null : text } Components.Label { id : text text : plasmoid.title anchors { left : parent.left bottom : parent.bottom right : parent.right } height: undefined // unset Label defaults horizontalAlignment : Text.AlignHCenter visible: false // rendered by DropShadow maximumLineCount: 2 color: "white" elide: Text.ElideRight wrapMode: Text.WrapAtWordBoundaryOrAnywhere } PlasmaCore.ToolTipArea { anchors.fill: parent mainText: plasmoid.title subText: plasmoid.nativeInterface.genericName !== mainText ? plasmoid.nativeInterface.genericName :"" icon: plasmoid.icon } }