diff --git a/src/scriptengines/qml/plasmoid/appletinterface.cpp b/src/scriptengines/qml/plasmoid/appletinterface.cpp index 41c8d423c..afb6f69bf 100644 --- a/src/scriptengines/qml/plasmoid/appletinterface.cpp +++ b/src/scriptengines/qml/plasmoid/appletinterface.cpp @@ -1,855 +1,868 @@ /* * Copyright 2008-2013 Aaron Seigo * Copyright 2010-2013 Marco Martin * * 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 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 "appletinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "containmentinterface.h" #include "wallpaperinterface.h" #include #include Q_DECLARE_METATYPE(AppletInterface *) AppletInterface::AppletInterface(DeclarativeAppletScript *script, const QVariantList &args, QQuickItem *parent) : AppletQuickItem(script->applet(), parent), m_configuration(nullptr), m_appletScriptEngine(script), m_toolTipTextFormat(0), m_toolTipItem(nullptr), m_args(args), m_backgroundHints(Plasma::Types::StandardBackground), m_hideOnDeactivate(true), m_oldKeyboardShortcut(0), m_dummyNativeInterface(nullptr), m_positionBeforeRemoval(QPointF(-1, -1)) { qmlRegisterType(); connect(this, &AppletInterface::configNeedsSaving, applet(), &Plasma::Applet::configNeedsSaving); connect(applet(), &Plasma::Applet::immutabilityChanged, this, &AppletInterface::immutabilityChanged); connect(applet(), &Plasma::Applet::userConfiguringChanged, this, &AppletInterface::userConfiguringChanged); connect(applet(), &Plasma::Applet::contextualActionsAboutToShow, this, &AppletInterface::contextualActionsAboutToShow); connect(applet(), &Plasma::Applet::statusChanged, this, &AppletInterface::statusChanged); connect(applet(), &Plasma::Applet::destroyedChanged, this, &AppletInterface::destroyedChanged); connect(applet(), &Plasma::Applet::titleChanged, this, &AppletInterface::titleChanged); connect(applet(), &Plasma::Applet::titleChanged, this, [this]() { if (m_toolTipMainText.isNull()) { emit toolTipMainTextChanged(); } }); connect(applet(), &Plasma::Applet::iconChanged, this, &AppletInterface::iconChanged); connect(applet(), &Plasma::Applet::busyChanged, this, &AppletInterface::busyChanged); connect(applet(), &Plasma::Applet::configurationRequiredChanged, this, [this](bool configurationRequired, const QString &reason) { Q_UNUSED(configurationRequired); Q_UNUSED(reason); emit configurationRequiredChanged(); emit configurationRequiredReasonChanged(); }); connect(applet(), &Plasma::Applet::activated, this, &AppletInterface::activated); connect(appletScript(), &DeclarativeAppletScript::formFactorChanged, this, &AppletInterface::formFactorChanged); connect(appletScript(), &DeclarativeAppletScript::locationChanged, this, &AppletInterface::locationChanged); connect(appletScript(), &DeclarativeAppletScript::contextChanged, this, &AppletInterface::contextChanged); if (applet()->containment()) { connect(applet()->containment(), &Plasma::Containment::screenChanged, this, &AppletInterface::screenChanged); // Screen change implies geo change for good measure. connect(applet()->containment(), &Plasma::Containment::screenChanged, this, &AppletInterface::screenGeometryChanged); connect(applet()->containment()->corona(), &Plasma::Corona::screenGeometryChanged, this, [this](int id) { if (id == applet()->containment()->screen()) { emit screenGeometryChanged(); } }); connect(applet()->containment()->corona(), &Plasma::Corona::availableScreenRegionChanged, this, &ContainmentInterface::availableScreenRegionChanged); connect(applet()->containment()->corona(), &Plasma::Corona::availableScreenRectChanged, this, &ContainmentInterface::availableScreenRectChanged); } connect(this, &AppletInterface::expandedChanged, [=](bool expanded) { //if both compactRepresentationItem and fullRepresentationItem exist, //the applet is in a popup if (expanded) { if (compactRepresentationItem() && fullRepresentationItem() && fullRepresentationItem()->window() && compactRepresentationItem()->window() && fullRepresentationItem()->window() != compactRepresentationItem()->window() && fullRepresentationItem()->parentItem()) { fullRepresentationItem()->parentItem()->installEventFilter(this); } else if (fullRepresentationItem() && fullRepresentationItem()->parentItem()) { fullRepresentationItem()->parentItem()->removeEventFilter(this); } } }); } AppletInterface::~AppletInterface() { } DeclarativeAppletScript *AppletInterface::appletScript() const { return m_appletScriptEngine; } void AppletInterface::init() { if (qmlObject()->rootObject() && m_configuration) { return; } m_configuration = new KDeclarative::ConfigPropertyMap(applet()->configScheme(), this); AppletQuickItem::init(); geometryChanged(QRectF(), QRectF(x(), y(), width(), height())); emit busyChanged(); - applet()->updateConstraints(Plasma::Types::UiReadyConstraint); + updateUiReadyConstraint(); + + connect(this, &AppletInterface::isLoadingChanged, this, &AppletInterface::updateUiReadyConstraint); connect(applet(), &Plasma::Applet::activated, this, [ = ]() { // in case the applet doesn't want to get shrinked on reactivation, // we always expand it again (only in order to conform with legacy behaviour) bool activate = !( isExpanded() && isActivationTogglesExpanded() ); setExpanded(activate); if (activate) { if (QQuickItem *i = qobject_cast(fullRepresentationItem())) { // Bug 372476: never pull focus away from it, only setFocus(true) i->setFocus(true, Qt::ShortcutFocusReason); } } }); if (m_args.count() == 1) { emit externalData(QString(), m_args.first()); } else if (!m_args.isEmpty()) { emit externalData(QString(), m_args); } } void AppletInterface::destroyedChanged(bool destroyed) { //if an item loses its scene before losing the focus, will never //be able to gain focus again if (destroyed && window() && window()->activeFocusItem()) { QQuickItem *focus = window()->activeFocusItem(); QQuickItem *candidate = focus; bool isAncestor = false; //search if the current focus item is a child or grandchild of the applet while (candidate) { if (candidate == this) { isAncestor = true; break; } candidate = candidate->parentItem(); } if (isAncestor) { //Found? remove focus for the whole hierarchy candidate = focus; while (candidate && candidate != this) { candidate->setFocus(false); candidate = candidate->parentItem(); } } } setVisible(!destroyed); } Plasma::Types::FormFactor AppletInterface::formFactor() const { return applet()->formFactor(); } Plasma::Types::Location AppletInterface::location() const { return applet()->location(); } QString AppletInterface::currentActivity() const { if (applet()->containment()) { return applet()->containment()->activity(); } else { return QString(); } } QObject *AppletInterface::configuration() const { return m_configuration; } uint AppletInterface::id() const { return applet()->id(); } QString AppletInterface::pluginName() const { return applet()->pluginMetaData().isValid() ? applet()->pluginMetaData().pluginId() : QString(); } QString AppletInterface::icon() const { return applet()->icon(); } void AppletInterface::setIcon(const QString &icon) { if (applet()->icon() == icon) { return; } applet()->setIcon(icon); } QString AppletInterface::title() const { return applet()->title(); } void AppletInterface::setTitle(const QString &title) { if (applet()->title() == title) { return; } applet()->setTitle(title); } QString AppletInterface::toolTipMainText() const { if (m_toolTipMainText.isNull()) { return title(); } else { return m_toolTipMainText; } } void AppletInterface::setToolTipMainText(const QString &text) { //Here we are abusing the difference between a null and an empty string. //by default is null so falls back to the name //the fist time it gets set, an empty non null one is set, and won't fallback anymore if (!m_toolTipMainText.isNull() && m_toolTipMainText == text) { return; } if (text.isEmpty()) { m_toolTipMainText = QStringLiteral("");//this "" makes it non-null } else { m_toolTipMainText = text; } emit toolTipMainTextChanged(); } QString AppletInterface::toolTipSubText() const { if (m_toolTipSubText.isNull() && applet()->pluginMetaData().isValid()) { return applet()->pluginMetaData().description(); } else { return m_toolTipSubText; } } void AppletInterface::setToolTipSubText(const QString &text) { //Also there the difference between null and empty gets exploited if (!m_toolTipSubText.isNull() && m_toolTipSubText == text) { return; } if (text.isEmpty()) { m_toolTipSubText = QStringLiteral("");//this "" makes it non-null } else { m_toolTipSubText = text; } emit toolTipSubTextChanged(); } int AppletInterface::toolTipTextFormat() const { return m_toolTipTextFormat; } void AppletInterface::setToolTipTextFormat(int format) { if (m_toolTipTextFormat == format) { return; } m_toolTipTextFormat = format; emit toolTipTextFormatChanged(); } QQuickItem *AppletInterface::toolTipItem() const { return m_toolTipItem.data(); } void AppletInterface::setToolTipItem(QQuickItem *toolTipItem) { if (m_toolTipItem.data() == toolTipItem) { return; } m_toolTipItem = toolTipItem; connect(m_toolTipItem.data(), &QObject::destroyed, this, &AppletInterface::toolTipItemChanged); emit toolTipItemChanged(); } bool AppletInterface::isBusy() const { return applet()->isBusy(); } void AppletInterface::setBusy(bool busy) { applet()->setBusy(busy); } Plasma::Types::BackgroundHints AppletInterface::backgroundHints() const { return m_backgroundHints; } void AppletInterface::setBackgroundHints(Plasma::Types::BackgroundHints hint) { if (m_backgroundHints == hint) { return; } m_backgroundHints = hint; emit backgroundHintsChanged(); } void AppletInterface::setConfigurationRequired(bool needsConfiguring, const QString &reason) { appletScript()->setConfigurationRequired(needsConfiguring, reason); } QString AppletInterface::file(const QString &fileType) { return appletScript()->filePath(fileType, QString()); } QString AppletInterface::file(const QString &fileType, const QString &filePath) { return appletScript()->filePath(fileType, filePath); } QList AppletInterface::contextualActions() const { QList actions; Plasma::Applet *a = applet(); if (a->failedToLaunch()) { return actions; } foreach (const QString &name, m_actions) { QAction *action = a->actions()->action(name); if (action) { actions << action; } } return actions; } void AppletInterface::setActionSeparator(const QString &name) { Plasma::Applet *a = applet(); QAction *action = a->actions()->action(name); if (action) { action->setSeparator(true); } else { action = new QAction(this); action->setSeparator(true); a->actions()->addAction(name, action); m_actions.append(name); } } void AppletInterface::setAction(const QString &name, const QString &text, const QString &icon, const QString &shortcut) { Plasma::Applet *a = applet(); QAction *action = a->actions()->action(name); if (action) { action->setText(text); } else { action = new QAction(text, this); a->actions()->addAction(name, action); Q_ASSERT(!m_actions.contains(name)); m_actions.append(name); connect(action, &QAction::triggered, this, [this, name] { executeAction(name); }); } if (!icon.isEmpty()) { action->setIcon(QIcon::fromTheme(icon)); } if (!shortcut.isEmpty()) { action->setShortcut(shortcut); } action->setObjectName(name); } void AppletInterface::removeAction(const QString &name) { Plasma::Applet *a = applet(); QAction *action = a->actions()->action(name); delete action; m_actions.removeAll(name); } void AppletInterface::clearActions() { Q_FOREACH (const QString &action, m_actions) { removeAction(action); } } QAction *AppletInterface::action(QString name) const { return applet()->actions()->action(name); } bool AppletInterface::immutable() const { return applet()->immutability() != Plasma::Types::Mutable; } Plasma::Types::ImmutabilityType AppletInterface::immutability() const { return applet()->immutability(); } bool AppletInterface::userConfiguring() const { return applet()->isUserConfiguring(); } int AppletInterface::apiVersion() const { // Look for C++ plugins first auto filter = [](const KPluginMetaData &md) -> bool { return md.value(QStringLiteral("X-Plasma-API")) == QLatin1String("declarativeappletscript") && md.value(QStringLiteral("X-Plasma-ComponentTypes")).contains(QLatin1String("Applet")); }; QVector plugins = KPluginLoader::findPlugins(QStringLiteral("plasma/scriptengines"), filter); if (plugins.isEmpty()) { return -1; } return plugins.first().value(QStringLiteral("X-KDE-PluginInfo-Version")).toInt(); } void AppletInterface::setAssociatedApplication(const QString &string) { if (applet()->associatedApplication() == string) { return; } applet()->setAssociatedApplication(string); emit associatedApplicationChanged(); } QString AppletInterface::associatedApplication() const { return applet()->associatedApplication(); } void AppletInterface::setAssociatedApplicationUrls(const QList &urls) { if (applet()->associatedApplicationUrls() == urls) { return; } applet()->setAssociatedApplicationUrls(urls); emit associatedApplicationUrlsChanged(); } QList AppletInterface::associatedApplicationUrls() const { return applet()->associatedApplicationUrls(); } void AppletInterface::setStatus(const Plasma::Types::ItemStatus &status) { applet()->setStatus(status); } Plasma::Types::ItemStatus AppletInterface::status() const { return applet()->status(); } int AppletInterface::screen() const { if (Plasma::Containment* c = applet()->containment()) { return c->screen(); } return -1; } QRect AppletInterface::screenGeometry() const { if (!applet() || !applet()->containment() || !applet()->containment()->corona()) { return QRect(); } return applet()->containment()->corona()->screenGeometry(applet()->containment()->screen()); } void AppletInterface::setHideOnWindowDeactivate(bool hide) { if (m_hideOnDeactivate != hide) { m_hideOnDeactivate = hide; emit hideOnWindowDeactivateChanged(); } } bool AppletInterface::hideOnWindowDeactivate() const { return m_hideOnDeactivate; } QKeySequence AppletInterface::globalShortcut() const { return applet()->globalShortcut(); } void AppletInterface::setGlobalShortcut(const QKeySequence &sequence) { applet()->setGlobalShortcut(sequence); } QObject *AppletInterface::nativeInterface() { if (qstrcmp(applet()->metaObject()->className(),"Plasma::Applet") != 0) { return applet(); } else { if (!m_dummyNativeInterface) { m_dummyNativeInterface = new QObject(this); } return m_dummyNativeInterface; } } bool AppletInterface::configurationRequired() const { return applet()->configurationRequired(); } void AppletInterface::setConfigurationRequiredProperty(bool needsConfiguring) { appletScript()->setConfigurationRequired(needsConfiguring, applet()->configurationRequiredReason()); } QString AppletInterface::configurationRequiredReason() const { return applet()->configurationRequiredReason(); } void AppletInterface::setConfigurationRequiredReason(const QString &reason) { appletScript()->setConfigurationRequired(applet()->configurationRequired(), reason); } QString AppletInterface::downloadPath(const QString &file) { Q_UNUSED(file); return downloadPath(); } QString AppletInterface::downloadPath() const { const QString downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + QStringLiteral("/Plasma/") + applet()->pluginMetaData().pluginId() + QLatin1Char('/'); if (!QFile::exists(downloadDir)) { QDir dir({ QLatin1Char('/') }); dir.mkpath(downloadDir); } return downloadDir; } QStringList AppletInterface::downloadedFiles() const { const QString downloadDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + QStringLiteral("/Plasma/") + applet()->pluginMetaData().pluginId() + QLatin1Char('/'); QDir dir(downloadDir); return dir.entryList(QDir::Files | QDir::NoSymLinks | QDir::Readable); } void AppletInterface::executeAction(const QString &name) { if (qmlObject()->rootObject()) { const QMetaObject *metaObj = qmlObject()->rootObject()->metaObject(); const QByteArray actionMethodName = "action_" + name.toUtf8(); const QByteArray actionFunctionName = actionMethodName + QByteArray("()"); if (metaObj->indexOfMethod(QMetaObject::normalizedSignature(actionFunctionName.constData()).constData()) != -1) { QMetaObject::invokeMethod(qmlObject()->rootObject(), actionMethodName.constData(), Qt::DirectConnection); } else { QMetaObject::invokeMethod(qmlObject()->rootObject(), "actionTriggered", Qt::DirectConnection, Q_ARG(QVariant, name)); } } } QVariantList AppletInterface::availableScreenRegion() const { QVariantList regVal; if (!applet()->containment() || !applet()->containment()->corona()) { return regVal; } QRegion reg = QRect(0, 0, width(), height()); int screenId = screen(); if (screenId > -1) { reg = applet()->containment()->corona()->availableScreenRegion(screenId); } auto it = reg.begin(); const auto itEnd = reg.end(); for (; it != itEnd; ++it) { QRect rect = *it; //make it relative QRect geometry = applet()->containment()->corona()->screenGeometry(screenId); rect.moveTo(rect.topLeft() - geometry.topLeft()); regVal << QVariant::fromValue(QRectF(rect)); } return regVal; } QRect AppletInterface::availableScreenRect() const { if (!applet()->containment() || !applet()->containment()->corona()) { return QRect(); } QRect rect(0, 0, width(), height()); int screenId = screen(); if (screenId > -1) { rect = applet()->containment()->corona()->availableScreenRect(screenId); //make it relative QRect geometry = applet()->containment()->corona()->screenGeometry(screenId); rect.moveTo(rect.topLeft() - geometry.topLeft()); } return rect; } bool AppletInterface::event(QEvent *event) { // QAction keyboard shortcuts cannot work with QML2 (and probably newver will // since in Qt qtquick and qwidgets cannot depend from each other in any way) // so do a simple keyboard shortcut matching here if (event->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(event); QKeySequence seq(ke->key()|ke->modifiers()); QList actions = applet()->actions()->actions(); //find the wallpaper action if we are a containment ContainmentInterface *ci = qobject_cast(this); if (ci) { WallpaperInterface *wi = ci->wallpaperInterface(); if (wi) { actions << wi->contextualActions(); } } //add any actions of the corona if (applet()->containment() && applet()->containment()->corona()) { actions << applet()->containment()->corona()->actions()->actions(); } bool keySequenceUsed = false; foreach (auto a, actions) { if (a->shortcut().isEmpty()) { continue; } //this will happen on a normal, non emacs shortcut if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) { event->accept(); a->trigger(); m_oldKeyboardShortcut = 0; return true; //first part of an emacs style shortcut? } else if (seq.matches(a->shortcut()) == QKeySequence::PartialMatch) { keySequenceUsed = true; m_oldKeyboardShortcut = ke->key()|ke->modifiers(); //no match at all, but it can be the second part of an emacs style shortcut } else { QKeySequence seq(m_oldKeyboardShortcut, ke->key()|ke->modifiers()); if (seq.matches(a->shortcut()) == QKeySequence::ExactMatch) { event->accept(); a->trigger(); return true; } } } if (!keySequenceUsed) { m_oldKeyboardShortcut = 0; } } return AppletQuickItem::event(event); } void AppletInterface::prepareContextualActions() { emit applet()->contextualActionsAboutToShow(); } bool AppletInterface::eventFilter(QObject *watched, QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { QMouseEvent *e = static_cast(event); //pass it up to the applet //well, actually we have to pass it to the *containment* //because all the code for showing an applet's contextmenu is actually in Containment. Plasma::Containment *c = applet()->containment(); if (c) { const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = c->containmentActions().value(trigger); if (!plugin) { return false; } ContainmentInterface *ci = c->property("_plasma_graphicObject").value(); if (!ci) { return false; } //the plugin can be a single action or a context menu //Don't have an action list? execute as single action //and set the event position as action data if (plugin->contextualActions().length() == 1) { // but first check whether we are not a popup // we don't want to randomly creates applets without confirmation if (static_cast(watched)->window() != ci->window()) { return true; } QAction *action = plugin->contextualActions().at(0); action->setData(e->globalPos()); action->trigger(); return true; } QMenu *desktopMenu = new QMenu; if (desktopMenu->winId()) { desktopMenu->windowHandle()->setTransientParent(window()); } prepareContextualActions(); ci->addAppletActions(desktopMenu, applet(), event); if (!desktopMenu->isEmpty()) { desktopMenu->setAttribute(Qt::WA_DeleteOnClose); desktopMenu->popup(e->globalPos()); return true; } delete desktopMenu; return false; } } return AppletQuickItem::eventFilter(watched, event); } +void AppletInterface::updateUiReadyConstraint() +{ + if (!isLoading()) { + applet()->updateConstraints(Plasma::Types::UiReadyConstraint); + } +} + +bool AppletInterface::isLoading() const +{ + return m_loading; +} #include "moc_appletinterface.cpp" diff --git a/src/scriptengines/qml/plasmoid/appletinterface.h b/src/scriptengines/qml/plasmoid/appletinterface.h index f09f99f48..9f75b5e47 100644 --- a/src/scriptengines/qml/plasmoid/appletinterface.h +++ b/src/scriptengines/qml/plasmoid/appletinterface.h @@ -1,488 +1,502 @@ /* * Copyright 2008 Chani Armitage * Copyright 2008, 2009 Aaron Seigo * Copyright 2010 Marco Martin * * 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 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. */ #ifndef APPLETINTERFACE_H #define APPLETINTERFACE_H #include #include #include #include #include #include "declarativeappletscript.h" class QAction; class QmlAppletScript; class QSizeF; namespace KDeclarative { class ConfigPropertyMap; class QmlObject; } namespace Plasma { class ConfigLoader; } // namespace Plasma /** * @class AppletInterface * * @short This class is exposed to applets in QML as the attached property Plasmoid * * \@import org.kde.plasma.Plasmoid */ class AppletInterface : public PlasmaQuick::AppletQuickItem { Q_OBJECT /** * The QML root object defined in the applet main.qml will be direct child of an AppletInterface instance */ /** * Version of the QML2 script engine */ Q_PROPERTY(int apiVersion READ apiVersion CONSTANT) /** * Plugin name of the plasmoid */ Q_PROPERTY(QString pluginName READ pluginName CONSTANT) /** * User friendly title for the plasmoid: it's the localized applet name by default */ Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) /** * Main title for the plasmoid tooltip or other means of quick information: * it's the same as the title property by default, but it can be personalized */ Q_PROPERTY(QString toolTipMainText READ toolTipMainText WRITE setToolTipMainText NOTIFY toolTipMainTextChanged) /** * Description for the plasmoid tooltip or other means of quick information: * it comes from the pluginifo comment by default, but it can be personalized */ Q_PROPERTY(QString toolTipSubText READ toolTipSubText WRITE setToolTipSubText NOTIFY toolTipSubTextChanged) /** * how to handle the text format of the tooltip subtext: * * Text.AutoText (default) * * Text.PlainText * * Text.StyledText * * Text.RichText * Note: in the default implementation the main text is always plain text */ Q_PROPERTY(int toolTipTextFormat READ toolTipTextFormat WRITE setToolTipTextFormat NOTIFY toolTipTextFormatChanged) /** * This allows to set custom full QML items as the tooltip. * It will ignore all texts set by setToolTipMainText or setToolTipSubText * * @since: 5.19 */ Q_PROPERTY(QQuickItem *toolTipItem READ toolTipItem WRITE setToolTipItem NOTIFY toolTipItemChanged) /** * Icon to represent the plasmoid */ Q_PROPERTY(QString icon READ icon WRITE setIcon NOTIFY iconChanged) /** * Applet id: is unique in the whole Plasma session and will never change across restarts */ Q_PROPERTY(uint id READ id CONSTANT) /** * FormFactor for the plasmoid */ Q_PROPERTY(Plasma::Types::FormFactor formFactor READ formFactor NOTIFY formFactorChanged) /** * Location for the plasmoid */ Q_PROPERTY(Plasma::Types::Location location READ location NOTIFY locationChanged) /** * Current activity name the plasmoid is in */ Q_PROPERTY(QString currentActivity READ currentActivity NOTIFY contextChanged) /** * Configuration object: each config key will be a writable property of this object. property bindings work. */ Q_PROPERTY(QObject *configuration READ configuration CONSTANT) /** * When true the plasmoid is busy. The containment may graphically indicate that drawing for instance a spinner busy widget over it */ Q_PROPERTY(bool busy WRITE setBusy READ isBusy NOTIFY busyChanged) /** * How the applet wants its background to be drawn. The containment may chose to ignore this hint. */ Q_PROPERTY(Plasma::Types::BackgroundHints backgroundHints WRITE setBackgroundHints READ backgroundHints NOTIFY backgroundHintsChanged) /** * Whether the Corona is immutable. The plasmoid implementation should avoid allowing "dangerous" modifications from the user when in an immutable mode * * This is true when immutability is not Mutable */ Q_PROPERTY(bool immutable READ immutable NOTIFY immutabilityChanged) /** * The immutability of the Corona. * * Use this if you need more granular control than just using the immutable property * * @see immutable * @since 5.23 */ Q_PROPERTY(Plasma::Types::ImmutabilityType immutability READ immutability NOTIFY immutabilityChanged) /** * True when the user is configuring, for instance when the configuration dialog is open. */ Q_PROPERTY(bool userConfiguring READ userConfiguring NOTIFY userConfiguringChanged) /** * Status of the plasmoid: useful to instruct the shell if this plasmoid is requesting attention, if is accepting input, or if is in an idle, inactive state */ Q_PROPERTY(Plasma::Types::ItemStatus status READ status WRITE setStatus NOTIFY statusChanged) /** * Sets the associated application of this plasmoid, if the plasmoid is representing the "compact" view for some application or for some document type. */ Q_PROPERTY(QString associatedApplication WRITE setAssociatedApplication READ associatedApplication NOTIFY associatedApplicationChanged) /** * Sets the associated application of this plasmoid, if the plasmoid is representing the "compact" view for some application or for some document type. */ Q_PROPERTY(QList associatedApplicationUrls WRITE setAssociatedApplicationUrls READ associatedApplicationUrls NOTIFY associatedApplicationUrlsChanged) // TODO: This was moved up from ContainmentInterface because it is required by the // Task Manager applet (for "Show only tasks from this screen") and no Qt API exposes // screen numbering. An alternate solution that doesn't extend the applet interface // would be preferable if found. Q_PROPERTY(int screen READ screen NOTIFY screenChanged) /** * Provides access to the geometry of the applet is in. * Can be useful to figure out what's the absolute position of the applet. */ Q_PROPERTY(QRect screenGeometry READ screenGeometry NOTIFY screenGeometryChanged) /** * Whether the dialog should be hidden when the dialog loses focus. * * The default value is @c false. **/ Q_PROPERTY(bool hideOnWindowDeactivate READ hideOnWindowDeactivate WRITE setHideOnWindowDeactivate NOTIFY hideOnWindowDeactivateChanged) /** * The global shortcut to activate the plasmoid * * This is typically only used by the default configuration module * */ Q_PROPERTY(QKeySequence globalShortcut READ globalShortcut WRITE setGlobalShortcut NOTIFY globalShortcutChanged) /** * An interface to the native C++ plasmoid, if implemented */ Q_PROPERTY(QObject *nativeInterface READ nativeInterface CONSTANT) /** * If true the applet requires manual configuration from the user */ Q_PROPERTY(bool configurationRequired READ configurationRequired WRITE setConfigurationRequiredProperty NOTIFY configurationRequiredChanged) /** * Reason why the manual user configuration is required */ Q_PROPERTY(QString configurationRequiredReason READ configurationRequiredReason WRITE setConfigurationRequiredReason NOTIFY configurationRequiredReasonChanged) /** * screen area free of panels: the coordinates are relative to the containment, * it's independent from the screen position * For more precise available geometry use availableScreenRegion() */ Q_PROPERTY(QRect availableScreenRect READ availableScreenRect NOTIFY availableScreenRectChanged) /** * The available region of this screen, panels excluded. It's a list of rectangles */ Q_PROPERTY(QVariantList availableScreenRegion READ availableScreenRegion NOTIFY availableScreenRegionChanged) + Q_PROPERTY(bool loading MEMBER m_loading NOTIFY isLoadingChanged) + public: AppletInterface(DeclarativeAppletScript *script, const QVariantList &args = QVariantList(), QQuickItem *parent = nullptr); ~AppletInterface() override; //API not intended for the QML part DeclarativeAppletScript *appletScript() const; QList contextualActions() const; void executeAction(const QString &name); //QML API------------------------------------------------------------------- /** * Set this to true if the plasmoid needs to be configured in order to work. The containment will display reason as a message to ask the user to configure. * @param needsConfiguring If the plasmoid needs configuration * @param reason The user readable (and localized) reason the plasmoid needs */ Q_INVOKABLE void setConfigurationRequired(bool needsConfiguring, const QString &reason = QString()); Q_INVOKABLE void setActionSeparator(const QString &name); /** * Add an action to the Plasmoid contextual menu. * When the action is triggered a function called action_ will be called, if there is no function with that name actionTriggered(name) will be called instead. * @param: action name * @text: user visible displayed text * @icon: user visible optional displayed icon * @shortcut: shortcut to trigger this action */ Q_INVOKABLE void setAction(const QString &name, const QString &text, const QString &icon = QString(), const QString &shortcut = QString()); Q_INVOKABLE void removeAction(const QString &name); Q_INVOKABLE void clearActions(); Q_INVOKABLE QAction *action(QString name) const; /** * Should be called before retrieving any action * to ensure contents are up to date * @see contextualActionsAboutToShow * @since 5.58 */ Q_INVOKABLE void prepareContextualActions(); /** * FIXME: remove? * Retrieve the path of a file from the Plasmoid package * @param fileName the package-recognized name, such as "mainscript" * @returns the full absolute path of the file, if found, an empty string if not */ Q_INVOKABLE QString file(const QString &fileName); /** * FIXME: remove? * Retrieve the path of a file from the Plasmoid package * @param fileType the type supported from the package, such as "ui", "config" or "image" * @param filePath the name of the file, such as "foo.qml" or "bar.png" * @returns the full absolute path of the file, if found, an empty string if not */ Q_INVOKABLE QString file(const QString &fileType, const QString &filePath); /** * @returns A path where it is safe to write on disk downloaded files. * @since 5.23 */ Q_INVOKABLE QString downloadPath() const; /** * @returns A path where it is safe to write on disk downloaded files. * @param file that name of the file to download (unused). * @deprecated Use downloadPath() instead. */ Q_INVOKABLE PLASMA_DEPRECATED QString downloadPath(const QString &file); /** * @returns The list of files that have been downloaded */ Q_INVOKABLE QStringList downloadedFiles() const; QVariantList availableScreenRegion() const; QRect availableScreenRect() const; static AppletInterface *qmlAttachedProperties(QObject *object) { return qobject_cast(AppletQuickItem::qmlAttachedProperties(object)); } //PROPERTY ACCESSORS------------------------------------------------------------------- QString pluginName() const; QString icon() const; void setIcon(const QString &icon); QString title() const; void setTitle(const QString &title); QString toolTipMainText() const; void setToolTipMainText(const QString &text); QString toolTipSubText() const; void setToolTipSubText(const QString &text); int toolTipTextFormat() const; void setToolTipTextFormat(int format); QQuickItem *toolTipItem() const; void setToolTipItem(QQuickItem *toolTipItem); uint id() const; Plasma::Types::FormFactor formFactor() const; Plasma::Types::Location location() const; QString currentActivity() const; QObject *configuration() const; bool isBusy() const; void setBusy(bool busy); Plasma::Types::BackgroundHints backgroundHints() const; void setBackgroundHints(Plasma::Types::BackgroundHints hint); void setAssociatedApplication(const QString &string); QString associatedApplication() const; void setAssociatedApplicationUrls(const QList &urls); QList associatedApplicationUrls() const; void setStatus(const Plasma::Types::ItemStatus &status); Plasma::Types::ItemStatus status() const; int screen() const; QRect screenGeometry() const; bool immutable() const; Plasma::Types::ImmutabilityType immutability() const; bool userConfiguring() const; int apiVersion() const; bool hideOnWindowDeactivate() const; void setHideOnWindowDeactivate(bool hide); QKeySequence globalShortcut() const; void setGlobalShortcut(const QKeySequence &keySequence); QObject *nativeInterface(); //NOTE: setConfigurationRequiredProperty because ambiguous with the // setConfigurationRequired invokable bool configurationRequired() const; void setConfigurationRequiredProperty(bool required); QString configurationRequiredReason() const; void setConfigurationRequiredReason(const QString &reason); Q_SIGNALS: /** * somebody else, usually the containment sent some data to the applet * @param mimetype the mime type of the data such as text/plain * @param data either the actual data or an URL representing it */ void externalData(const QString &mimetype, const QVariant &data); void configNeedsSaving(); /** * Emitted when the applet's activation action is triggered */ void activated(); /** * Emitted just before the contextual actions are about to show * For instance just before the context menu containing the actions * added with setAction() is shown */ void contextualActionsAboutToShow(); //PROPERTY change notifiers-------------- void iconChanged(); void titleChanged(); void toolTipMainTextChanged(); void toolTipSubTextChanged(); void toolTipTextFormatChanged(); void toolTipItemChanged(); void formFactorChanged(); void locationChanged(); void contextChanged(); void immutabilityChanged(); void statusChanged(); void backgroundHintsChanged(); void busyChanged(); void screenChanged(); void screenGeometryChanged(); void hideOnWindowDeactivateChanged(); void associatedApplicationChanged(); void associatedApplicationUrlsChanged(); void availableScreenRegionChanged(); void availableScreenRectChanged(); void userConfiguringChanged(); void globalShortcutChanged(); void configurationRequiredChanged(); void configurationRequiredReasonChanged(); + void isLoadingChanged(); + protected Q_SLOTS: void init() override; protected: bool event(QEvent *event) override; bool eventFilter(QObject *watched, QEvent *event) override; + /* + * Returns true if this plasmoid or a dependent feature (i.e wallpaper) is loading + */ + virtual bool isLoading() const; + /* + * Set UIReadyConstraint if we're not currently loading + */ + void updateUiReadyConstraint(); + private Q_SLOTS: void destroyedChanged(bool destroyed); private: QStringList m_actions; KDeclarative::ConfigPropertyMap *m_configuration; DeclarativeAppletScript *m_appletScriptEngine; //UI-specific members ------------------ QString m_toolTipMainText; QString m_toolTipSubText; int m_toolTipTextFormat; QPointer m_toolTipItem; QVariantList m_args; Plasma::Types::BackgroundHints m_backgroundHints; bool m_hideOnDeactivate : 1; + bool m_loading = false; //this is used to build an emacs style shortcut int m_oldKeyboardShortcut; QObject *m_dummyNativeInterface; friend class ContainmentInterface; //This is used by ContainmentInterface QPointF m_positionBeforeRemoval; }; QML_DECLARE_TYPEINFO(AppletInterface, QML_HAS_ATTACHED_PROPERTIES) #endif diff --git a/src/scriptengines/qml/plasmoid/containmentinterface.cpp b/src/scriptengines/qml/plasmoid/containmentinterface.cpp index cf750bd8d..333fffe37 100644 --- a/src/scriptengines/qml/plasmoid/containmentinterface.cpp +++ b/src/scriptengines/qml/plasmoid/containmentinterface.cpp @@ -1,1220 +1,1232 @@ /* * Copyright 2008 Chani Armitage * Copyright 2008, 2009 Aaron Seigo * Copyright 2010 Marco Martin * * 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 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 "containmentinterface.h" #include "wallpaperinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdeclarative/configpropertymap.h" #include ContainmentInterface::ContainmentInterface(DeclarativeAppletScript *parent, const QVariantList &args) : AppletInterface(parent, args), m_wallpaperInterface(nullptr), m_activityInfo(nullptr), m_wheelDelta(0), m_editMode(false) { m_containment = static_cast(appletScript()->applet()->containment()); setAcceptedMouseButtons(Qt::AllButtons); connect(m_containment.data(), &Plasma::Containment::appletRemoved, this, &ContainmentInterface::appletRemovedForward); connect(m_containment.data(), &Plasma::Containment::appletAdded, this, &ContainmentInterface::appletAddedForward); if (!m_appletInterfaces.isEmpty()) { emit appletsChanged(); } } void ContainmentInterface::init() { if (qmlObject()->rootObject()) { return; } m_activityInfo = new KActivities::Info(m_containment->activity(), this); connect(m_activityInfo, &KActivities::Info::nameChanged, this, &ContainmentInterface::activityNameChanged); emit activityNameChanged(); + if (!m_containment->wallpaper().isEmpty()) { + loadWallpaper(); + } + AppletInterface::init(); //Create the ToolBox if (m_containment) { KConfigGroup defaults; if (m_containment->containmentType() == Plasma::Types::DesktopContainment) { defaults = KConfigGroup(KSharedConfig::openConfig(m_containment->corona()->kPackage().filePath("defaults")), "Desktop"); } else if (m_containment->containmentType() == Plasma::Types::PanelContainment) { defaults = KConfigGroup(KSharedConfig::openConfig(m_containment->corona()->kPackage().filePath("defaults")), "Panel"); } if (defaults.isValid()) { KPackage::Package pkg = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("KPackage/GenericQML")); pkg.setDefaultPackageRoot(QStringLiteral("plasma/packages")); if (defaults.isValid()) { pkg.setPath(defaults.readEntry("ToolBox", "org.kde.desktoptoolbox")); } else { pkg.setPath(QStringLiteral("org.kde.desktoptoolbox")); } PlasmaQuick::PackageUrlInterceptor *interceptor = dynamic_cast(qmlObject()->engine()->urlInterceptor()); if (interceptor) { interceptor->addAllowedPath(pkg.path()); } if (pkg.metadata().isValid() && !pkg.metadata().isHidden()) { if (pkg.isValid()) { QObject *containmentGraphicObject = qmlObject()->rootObject(); QVariantHash toolboxProperties; toolboxProperties[QStringLiteral("parent")] = QVariant::fromValue(this); QObject *toolBoxObject = qmlObject()->createObjectFromSource(pkg.fileUrl("mainscript"), nullptr, toolboxProperties); if (toolBoxObject && containmentGraphicObject) { containmentGraphicObject->setProperty("toolBox", QVariant::fromValue(toolBoxObject)); } } else { qWarning() << "Could not load toolbox package." << pkg.path(); } } else { qWarning() << "Toolbox not loading, toolbox package is either invalid or disabled."; } } } //set parent, both as object hierarchically and visually //do this only for containments, applets will do it in compactrepresentationcheck if (qmlObject()->rootObject()) { qmlObject()->rootObject()->setProperty("parent", QVariant::fromValue(this)); //set anchors QQmlExpression expr(qmlObject()->engine()->rootContext(), qmlObject()->rootObject(), QStringLiteral("parent")); QQmlProperty prop(qmlObject()->rootObject(), QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } - if (!m_containment->wallpaper().isEmpty()) { - loadWallpaper(); - } - connect(m_containment.data(), &Plasma::Containment::activityChanged, this, &ContainmentInterface::activityChanged); connect(m_containment.data(), &Plasma::Containment::activityChanged, this, [ = ]() { delete m_activityInfo; m_activityInfo = new KActivities::Info(m_containment->activity(), this); connect(m_activityInfo, &KActivities::Info::nameChanged, this, &ContainmentInterface::activityNameChanged); emit activityNameChanged(); }); connect(m_containment.data(), &Plasma::Containment::wallpaperChanged, this, &ContainmentInterface::loadWallpaper); connect(m_containment.data(), &Plasma::Containment::containmentTypeChanged, this, &ContainmentInterface::containmentTypeChanged); connect(m_containment.data()->actions(), &KActionCollection::inserted, this, &ContainmentInterface::actionsChanged); connect(m_containment.data()->actions(), &KActionCollection::removed, this, &ContainmentInterface::actionsChanged); } QList ContainmentInterface::applets() { return m_appletInterfaces; } Plasma::Types::ContainmentType ContainmentInterface::containmentType() const { return appletScript()->containmentType(); } void ContainmentInterface::setContainmentType(Plasma::Types::ContainmentType type) { appletScript()->setContainmentType(type); } Plasma::Applet *ContainmentInterface::createApplet(const QString &plugin, const QVariantList &args, const QPoint &pos) { return createApplet(plugin, args, QRectF(pos, QSize())); } Plasma::Applet *ContainmentInterface::createApplet(const QString &plugin, const QVariantList &args, const QRectF &geom) { //HACK //This is necessary to delay the appletAdded signal (of containmentInterface) AFTER the applet graphics object has been created blockSignals(true); Plasma::Applet *applet = m_containment->createApplet(plugin, args); if (applet) { QQuickItem *appletGraphicObject = applet->property("_plasma_graphicObject").value(); //invalid applet? if (!appletGraphicObject) { blockSignals(false); return applet; } if (geom.width() > 0 && geom.height() > 0) { appletGraphicObject->setSize(geom.size()); } blockSignals(false); emit appletAdded(appletGraphicObject, geom.x(), geom.y()); emit appletsChanged(); } else { blockSignals(false); } return applet; } void ContainmentInterface::setAppletArgs(Plasma::Applet *applet, const QString &mimetype, const QString &data) { if (!applet) { return; } AppletInterface *appletInterface = applet->property("_plasma_graphicObject").value(); if (appletInterface) { emit appletInterface->externalData(mimetype, data); } } QObject *ContainmentInterface::containmentAt(int x, int y) { QObject *desktop = nullptr; foreach (Plasma::Containment *c, m_containment->corona()->containments()) { ContainmentInterface *contInterface = c->property("_plasma_graphicObject").value(); if (contInterface && contInterface->isVisible()) { QWindow *w = contInterface->window(); if (w && w->geometry().contains(QPoint(window()->x(), window()->y()) + QPoint(x, y))) { if (c->containmentType() == Plasma::Types::CustomEmbeddedContainment) { continue; } if (c->containmentType() == Plasma::Types::DesktopContainment) { desktop = contInterface; } else { return contInterface; } } } } return desktop; } void ContainmentInterface::addApplet(AppletInterface *applet, int x, int y) { if (!applet || applet->applet()->containment() == m_containment) { return; } blockSignals(true); m_containment->addApplet(applet->applet()); blockSignals(false); emit appletAdded(applet, x, y); } QPointF ContainmentInterface::mapFromApplet(AppletInterface *applet, int x, int y) { if (!applet->window() || !window()) { return QPointF(); } //x,y in absolute screen coordinates of current view QPointF pos = applet->mapToScene(QPointF(x, y)); pos = QPointF(pos + applet->window()->geometry().topLeft()); //return the coordinate in the relative view's coords return pos - window()->geometry().topLeft(); } QPointF ContainmentInterface::mapToApplet(AppletInterface *applet, int x, int y) { if (!applet->window() || !window()) { return QPointF(); } //x,y in absolute screen coordinates of current view QPointF pos(x, y); pos = QPointF(pos + window()->geometry().topLeft()); //the coordinate in the relative view's coords pos = pos - applet->window()->geometry().topLeft(); //make it relative to applet coords return pos - applet->mapToScene(QPointF(0, 0)); } QPointF ContainmentInterface::adjustToAvailableScreenRegion(int x, int y, int w, int h) const { QRegion reg; int screenId = screen(); if (screenId > -1 && m_containment->corona()) { reg = m_containment->corona()->availableScreenRegion(screenId); } if (!reg.isEmpty()) { //make it relative QRect geometry = m_containment->corona()->screenGeometry(screenId); reg.translate(- geometry.topLeft()); } else { reg = QRect(0, 0, width(), height()); } const QRect rect(qBound(reg.boundingRect().left(), x, reg.boundingRect().right() + 1 - w), qBound(reg.boundingRect().top(), y, reg.boundingRect().bottom() + 1 - h), w, h); const QRectF ar = availableScreenRect(); QRect tempRect(rect); // in the case we are in the topleft quadrant // * see if the passed rect is completely in the region, if yes, return // * otherwise, try to move it horizontally to the screenrect x // * if now fits, return // * if fail, move vertically // * as last resort, move horizontally and vertically // top left corner if (rect.center().x() <= ar.center().x() && rect.center().y() <= ar.center().y()) { //QRegion::contains doesn't do what it would suggest, so do reg.intersected(rect) != rect instead if (reg.intersected(rect) != rect) { tempRect = QRect(qMax(rect.left(), (int)ar.left()), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMax(rect.top(), (int)ar.top()), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMax(rect.left(), (int)ar.left()), qMax(rect.top(), (int)ar.top()), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } //bottom left corner } else if (rect.center().x() <= ar.center().x() && rect.center().y() > ar.center().y()) { if (reg.intersected(rect) != rect) { tempRect = QRect(qMax(rect.left(), (int)ar.left()), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMax(rect.left(), (int)ar.left()), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } //top right corner } else if (rect.center().x() > ar.center().x() && rect.center().y() <= ar.center().y()) { if (reg.intersected(rect) != rect) { tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMax(rect.top(), (int)ar.top()), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), qMax(rect.top(), (int)ar.top()), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } //bottom right corner } else if (rect.center().x() > ar.center().x() && rect.center().y() > ar.center().y()) { if (reg.intersected(rect) != rect) { tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), rect.top(), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(rect.left(), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); if (reg.intersected(tempRect) == tempRect) { return tempRect.topLeft(); } tempRect = QRect(qMin(rect.left(), (int)(ar.right() + 1 - w)), qMin(rect.top(), (int)(ar.bottom() + 1 - h)), w, h); return tempRect.topLeft(); } else { return rect.topLeft(); } } return rect.topLeft(); } bool ContainmentInterface::isEditMode() const { return m_editMode; } void ContainmentInterface::setEditMode(bool edit) { if (edit == m_editMode) { return; } if (m_containment->immutability() != Plasma::Types::Mutable) { return; } m_editMode = edit; emit editModeChanged(); } void ContainmentInterface::processMimeData(QObject *mimeDataProxy, int x, int y, KIO::DropJob *dropJob) { QMimeData* mime = qobject_cast(mimeDataProxy); if (mime) { processMimeData(mime, x, y, dropJob); } else { processMimeData(mimeDataProxy->property("mimeData").value(), x, y, dropJob); } } void ContainmentInterface::processMimeData(QMimeData *mimeData, int x, int y, KIO::DropJob *dropJob) { if (!mimeData) { return; } //const QMimeData *mimeData = data; qDebug() << "Arrived mimeData" << mimeData->urls() << mimeData->formats() << "at" << x << ", " << y; // Catch drops from a Task Manager and convert to usable URL. if (!mimeData->hasUrls() && mimeData->hasFormat(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))) { QList urls = {QUrl(QString::fromUtf8(mimeData->data(QStringLiteral("text/x-orgkdeplasmataskmanager_taskurl"))))}; mimeData->setUrls(urls); } if (mimeData->hasFormat(QStringLiteral("text/x-plasmoidservicename"))) { QString data = QString::fromUtf8( mimeData->data(QStringLiteral("text/x-plasmoidservicename")) ); const QStringList appletNames = data.split(QLatin1Char('\n'), QString::SkipEmptyParts); foreach (const QString &appletName, appletNames) { qDebug() << "adding" << appletName; metaObject()->invokeMethod(this, "createApplet", Qt::QueuedConnection, Q_ARG(QString, appletName), Q_ARG(QVariantList, QVariantList()), Q_ARG(QRectF, QRectF(x, y, -1, -1))); } } else if (mimeData->hasUrls()) { //TODO: collect the mimetypes of available script engines and offer // to create widgets out of the matching URLs, if any const QList urls = KUrlMimeData::urlsFromMimeData(mimeData); foreach (const QUrl &url, urls) { QMimeDatabase db; const QMimeType &mime = db.mimeTypeForUrl(url); QString mimeName = mime.name(); QVariantList args; args << url.url(); qDebug() << "can decode" << mimeName << args; // It may be a directory or a file, let's stat KIO::JobFlags flags = KIO::HideProgressInfo; KIO::MimetypeJob *job = KIO::mimetype(url, flags); m_dropPoints[job] = QPoint(x, y); QObject::connect(job, SIGNAL(result(KJob*)), this, SLOT(dropJobResult(KJob*))); QObject::connect(job, SIGNAL(mimetype(KIO::Job*,QString)), this, SLOT(mimeTypeRetrieved(KIO::Job*,QString))); if (dropJob) { m_dropJobs[job] = dropJob; } else { QMenu *choices = new QMenu(i18n("Content dropped")); if (choices->winId()) { choices->windowHandle()->setTransientParent(window()); } choices->addAction(QIcon::fromTheme(QStringLiteral("process-working")), i18n("Fetching file type...")); choices->popup(window() ? window()->mapToGlobal(QPoint(x, y)) : QPoint(x, y)); m_dropMenus[job] = choices; } } } else { QStringList formats = mimeData->formats(); QHash seenPlugins; QHash pluginFormats; foreach (const QString &format, formats) { const auto plugins = Plasma::PluginLoader::self()->listAppletMetaDataForMimeType(format); foreach (const auto &plugin, plugins) { if (seenPlugins.contains(plugin.pluginId())) { continue; } seenPlugins.insert(plugin.pluginId(), plugin); pluginFormats.insert(plugin.pluginId(), format); } } //qDebug() << "Mimetype ..." << formats << seenPlugins.keys() << pluginFormats.values(); QString selectedPlugin; if (seenPlugins.isEmpty()) { // do nothing //directly create if only one offer only if the containment didn't pass an existing plugin } else if (seenPlugins.count() == 1) { selectedPlugin = seenPlugins.constBegin().key(); Plasma::Applet *applet = createApplet(selectedPlugin, QVariantList(), QRect(x, y, -1, -1)); setAppletArgs(applet, pluginFormats[selectedPlugin], QString::fromUtf8(mimeData->data(pluginFormats[selectedPlugin]))); } else { QMenu *choices = nullptr; if (!dropJob) { choices = new QMenu(); if (choices->winId()) { choices->windowHandle()->setTransientParent(window()); } } QList extraActions; QHash actionsToPlugins; foreach (const auto &info, seenPlugins) { QAction *action; if (!info.iconName().isEmpty()) { action = new QAction(QIcon::fromTheme(info.iconName()), info.name(), nullptr); } else { action = new QAction(info.name(), nullptr); } extraActions << action; if (choices) { choices->addAction(action); } action->setData(info.pluginId()); connect(action, &QAction::triggered, this, [this, x, y, mimeData, action]() { const QString selectedPlugin = action->data().toString(); Plasma::Applet *applet = createApplet(selectedPlugin, QVariantList(), QRect(x, y, -1, -1)); setAppletArgs(applet, selectedPlugin, QString::fromUtf8(mimeData->data(selectedPlugin))); }); actionsToPlugins.insert(action, info.pluginId()); } //if the menu was created by ourselves, delete it if (choices) { QAction *choice = choices->exec(window() ? window()->mapToGlobal(QPoint(x, y)) : QPoint(x, y)); delete choices; } else { Q_ASSERT(dropJob); dropJob->setApplicationActions(extraActions); } } } } void ContainmentInterface::clearDataForMimeJob(KIO::Job *job) { QObject::disconnect(job, nullptr, this, nullptr); m_dropPoints.remove(job); QMenu *choices = m_dropMenus.take(job); m_dropJobs.remove(job); job->kill(); } void ContainmentInterface::dropJobResult(KJob *job) { if (job->error()) { qDebug() << "ERROR" << job->error() << ' ' << job->errorString(); } } void ContainmentInterface::mimeTypeRetrieved(KIO::Job *job, const QString &mimetype) { qDebug() << "Mimetype Job returns." << mimetype; KIO::TransferJob *tjob = dynamic_cast(job); if (!tjob) { qDebug() << "job should be a TransferJob, but isn't"; clearDataForMimeJob(job); return; } QList appletList = Plasma::PluginLoader::self()->listAppletMetaDataForUrl(tjob->url()); if (mimetype.isEmpty() && appletList.isEmpty()) { clearDataForMimeJob(job); qDebug() << "No applets found matching the url (" << tjob->url() << ") or the mimetype (" << mimetype << ")"; return; } else { QPoint posi; // will be overwritten with the event's position if (m_dropPoints.contains(tjob)) { posi = m_dropPoints.value(tjob); qDebug() << "Received a suitable dropEvent at" << posi; } else { qDebug() << "Bailing out. Cannot find associated dropEvent related to the TransferJob"; clearDataForMimeJob(job); return; } QMenu *choices = m_dropMenus.value(tjob); QList dropActions; KIO::DropJob *dropJob = m_dropJobs.value(tjob); if (!choices && !dropJob) { qDebug() << "Bailing out. No QMenu or drop job found for this job."; clearDataForMimeJob(job); return; } qDebug() << "Creating menu for:" << mimetype << posi; appletList << Plasma::PluginLoader::self()->listAppletMetaDataForMimeType(mimetype); QList wallpaperList; if (m_containment->containmentType() != Plasma::Types::PanelContainment && m_containment->containmentType() != Plasma::Types::CustomPanelContainment) { if (m_wallpaperInterface && m_wallpaperInterface->supportsMimetype(mimetype)) { wallpaperList << m_wallpaperInterface->kPackage().metadata(); } else { wallpaperList = WallpaperInterface::listWallpaperMetadataForMimetype(mimetype); } } const bool isPlasmaPackage = (mimetype == QLatin1String("application/x-plasma")); if (!appletList.isEmpty() || !wallpaperList.isEmpty() || isPlasmaPackage) { QAction *installPlasmaPackageAction = nullptr; if (isPlasmaPackage) { if (choices) { choices->addSection(i18n("Plasma Package")); installPlasmaPackageAction = choices->addAction(QIcon::fromTheme(QStringLiteral("application-x-plasma")), i18n("Install")); } else { QAction *action = new QAction(i18n("Plasma Package"), nullptr); action->setSeparator(true); dropActions << action; installPlasmaPackageAction = new QAction(QIcon::fromTheme(QStringLiteral("application-x-plasma")), i18n("Install"), nullptr); Q_ASSERT(dropJob); dropActions << installPlasmaPackageAction; dropJob->setApplicationActions(dropActions); } const QString &packagePath = tjob->url().toLocalFile(); connect(installPlasmaPackageAction, &QAction::triggered, this, [this, posi, packagePath]() { using namespace KPackage; PackageStructure *structure = PackageLoader::self()->loadPackageStructure(QStringLiteral("Plasma/Applet")); Package package(structure); KJob *installJob = package.update(packagePath); connect(installJob, &KJob::result, this, [this, packagePath, structure, posi](KJob *job) { auto fail = [](const QString &text) { KNotification::event(QStringLiteral("plasmoidInstallationFailed"), i18n("Package Installation Failed"), text, QStringLiteral("dialog-error"), nullptr, KNotification::CloseOnTimeout, QStringLiteral("plasma_workspace")); }; // if the applet is already installed, just add it to the containment if (job->error() != KJob::NoError && job->error() != Package::PackageAlreadyInstalledError && job->error() != Package::NewerVersionAlreadyInstalledError) { fail(job->errorText()); return; } using namespace KPackage; Package package(structure); // TODO how can I get the path of the actual package? package.setPath(packagePath); // TODO how can I get the plugin id? Package::metadata() is deprecated if (!package.isValid() || !package.metadata().isValid()) { fail(i18n("The package you just dropped is invalid.")); return; } createApplet(package.metadata().pluginId(), QVariantList(), QRect(posi, QSize(-1,-1))); }); }); } if (choices) { choices->addSection(i18n("Widgets")); } else { QAction *action = new QAction(i18n("Widgets"), nullptr); action->setSeparator(true); dropActions << action; } foreach (const auto &info, appletList) { const QString actionText = i18nc("Add widget", "Add %1", info.name()); QAction *action = new QAction(actionText, nullptr); if (!info.iconName().isEmpty()) { action->setIcon(QIcon::fromTheme(info.iconName())); } if (choices) { choices->addAction(action); } dropActions << action; action->setData(info.pluginId()); const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, action, posi, mimetype, url]() { Plasma::Applet *applet = createApplet(action->data().toString(), QVariantList(), QRect(posi, QSize(-1,-1))); setAppletArgs(applet, mimetype, url.toString()); }); } { QAction *action = new QAction(i18nc("Add icon widget", "Add Icon"), nullptr); if (choices) { choices->addAction(action); } dropActions << action; action->setData(QStringLiteral("org.kde.plasma.icon")); const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, action, posi, mimetype, url](){ Plasma::Applet *applet = createApplet(action->data().toString(), QVariantList(), QRect(posi, QSize(-1,-1))); setAppletArgs(applet, mimetype, url.toString()); }); } QHash actionsToWallpapers; if (!wallpaperList.isEmpty()) { if (choices) { choices->addSection(i18n("Wallpaper")); } else { QAction *action = new QAction(i18n("Wallpaper"), nullptr); action->setSeparator(true); dropActions << action; } QMap sorted; foreach (const auto &info, appletList) { sorted.insert(info.name(), info); } foreach (const KPluginMetaData &info, wallpaperList) { const QString actionText = i18nc("Set wallpaper", "Set %1", info.name()); QAction *action = new QAction(actionText, nullptr); if (!info.iconName().isEmpty()) { action->setIcon(QIcon::fromTheme(info.iconName())); } if (choices) { choices->addAction(action); } dropActions << action; actionsToWallpapers.insert(action, info.pluginId()); const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, url]() { //set wallpapery stuff if (m_wallpaperInterface && url.isValid()) { m_wallpaperInterface->setUrl(url); } }); } } } else { //case in which we created the menu ourselves, just the "fetching type entry, directly create the icon applet if (choices) { Plasma::Applet *applet = createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList(), QRect(posi, QSize(-1,-1))); setAppletArgs(applet, mimetype, tjob->url().toString()); } else { QAction *action; if (choices) { choices->addSection(i18n("Widgets")); action = choices->addAction(i18nc("Add icon widget", "Add Icon")); } else { QAction *sep = new QAction(i18n("Widgets"), nullptr); sep->setSeparator(true); dropActions << sep; // we can at least create an icon as a link to the URL action = new QAction(i18nc("Add icon widget", "Add Icon"), nullptr); dropActions << action; } const QUrl url = tjob->url(); connect(action, &QAction::triggered, this, [this, posi, mimetype, url](){ Plasma::Applet *applet = createApplet(QStringLiteral("org.kde.plasma.icon"), QVariantList(), QRect(posi, QSize(-1,-1))); setAppletArgs(applet, mimetype, url.toString()); }); } } if (choices) { // HACK If the QMenu becomes empty at any point after the "determining mimetype" // popup was shown, it self-destructs, does not matter if we call clear() or remove // the action manually, hence we remove the aforementioned item after we populated the menu choices->removeAction(choices->actions().at(0)); choices->exec(); } else { dropJob->setApplicationActions(dropActions); } clearDataForMimeJob(tjob); } } void ContainmentInterface::appletAddedForward(Plasma::Applet *applet) { if (!applet) { return; } AppletInterface *appletGraphicObject = applet->property("_plasma_graphicObject").value(); AppletInterface *contGraphicObject = m_containment->property("_plasma_graphicObject").value(); // qDebug() << "Applet added on containment:" << m_containment->title() << contGraphicObject // << "Applet: " << applet << applet->title() << appletGraphicObject; //applets can not have a graphic object if they don't have a script engine loaded //this can happen if they were loaded with an invalid metadata if (!appletGraphicObject) { return; } if (contGraphicObject) { appletGraphicObject->setProperty("visible", false); appletGraphicObject->setProperty("parent", QVariant::fromValue(contGraphicObject)); } m_appletInterfaces << appletGraphicObject; connect(appletGraphicObject, &QObject::destroyed, this, [this](QObject *obj) { m_appletInterfaces.removeAll(obj); }); emit appletAdded(appletGraphicObject, appletGraphicObject->m_positionBeforeRemoval.x(), appletGraphicObject->m_positionBeforeRemoval.y()); emit appletsChanged(); } void ContainmentInterface::appletRemovedForward(Plasma::Applet *applet) { AppletInterface *appletGraphicObject = applet->property("_plasma_graphicObject").value(); if (appletGraphicObject) { m_appletInterfaces.removeAll(appletGraphicObject); appletGraphicObject->m_positionBeforeRemoval = appletGraphicObject->mapToItem(this, QPointF()); } emit appletRemoved(appletGraphicObject); emit appletsChanged(); } void ContainmentInterface::loadWallpaper() { if (m_containment->containmentType() != Plasma::Types::DesktopContainment && m_containment->containmentType() != Plasma::Types::CustomContainment) { return; } if (!m_containment->wallpaper().isEmpty()) { delete m_wallpaperInterface; m_wallpaperInterface = new WallpaperInterface(this); m_wallpaperInterface->setZ(-1000); //Qml seems happier if the parent gets set in this way m_wallpaperInterface->setProperty("parent", QVariant::fromValue(this)); + connect(m_wallpaperInterface, &WallpaperInterface::isLoadingChanged, this, &AppletInterface::updateUiReadyConstraint); + //set anchors QQmlExpression expr(qmlObject()->engine()->rootContext(), m_wallpaperInterface, QStringLiteral("parent")); QQmlProperty prop(m_wallpaperInterface, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); m_containment->setProperty("wallpaperGraphicsObject", QVariant::fromValue(m_wallpaperInterface)); } else { if (m_wallpaperInterface) { m_wallpaperInterface->deleteLater(); m_wallpaperInterface = nullptr; } } } QString ContainmentInterface::activity() const { return m_containment->activity(); } QString ContainmentInterface::activityName() const { if (!m_activityInfo) { return QString(); } return m_activityInfo->name(); } QList ContainmentInterface::actions() const { //FIXME: giving directly a QList crashes QStringList actionOrder; actionOrder << QStringLiteral("add widgets") << QStringLiteral("manage activities") << QStringLiteral("remove") << QStringLiteral("lock widgets") << QStringLiteral("run associated application") << QStringLiteral("configure"); QHash orderedActions; //use a multimap to sort by action type QMultiMap actions; int i = 0; foreach (QAction *a, m_containment->actions()->actions()) { if (!actionOrder.contains(a->objectName())) { //FIXME QML visualizations don't support menus for now, *and* there is no way to //distinguish them on QML side if (!a->menu()) { actions.insert(a->data().toInt()*100 + i, a); ++i; } } else { orderedActions[a->objectName()] = a; } } i = 0; foreach (QAction *a, m_containment->corona()->actions()->actions()) { if (a->objectName() == QStringLiteral("lock widgets") || a->menu()) { //It is up to the Containment to decide if the user is allowed or not //to lock/unluck the widgets, so corona should not add one when there is none //(user is not allow) and it shouldn't add another one when there is already //one continue; } if (!actionOrder.contains(a->objectName())) { actions.insert(a->data().toInt()*100 + i, a); } else { orderedActions[a->objectName()] = a; } ++i; } QList actionList = actions.values(); foreach (const QString &name, actionOrder) { QAction *a = orderedActions.value(name); if (a && !a->menu()) { actionList << a; } ++i; } return actionList; } //PROTECTED-------------------- void ContainmentInterface::mouseReleaseEvent(QMouseEvent *event) { event->setAccepted(m_containment->containmentActions().contains(Plasma::ContainmentActions::eventToString(event))); } void ContainmentInterface::mousePressEvent(QMouseEvent *event) { //even if the menu is executed synchronously, other events may be processed //by the qml incubator when plasma is loading, so we need to guard there if (m_contextMenu) { m_contextMenu.data()->close(); return; } const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = m_containment->containmentActions().value(trigger); if (!plugin || plugin->contextualActions().isEmpty()) { event->setAccepted(false); return; } //the plugin can be a single action or a context menu //Don't have an action list? execute as single action //and set the event position as action data if (plugin->contextualActions().length() == 1) { QAction *action = plugin->contextualActions().at(0); action->setData(event->pos()); action->trigger(); event->accept(); return; } //FIXME: very inefficient appletAt() implementation Plasma::Applet *applet = nullptr; foreach (QObject *appletObject, m_appletInterfaces) { if (AppletInterface *ai = qobject_cast(appletObject)) { if (ai->isVisible() && ai->contains(ai->mapFromItem(this, event->localPos()))) { applet = ai->applet(); break; } else { ai = nullptr; } } } //qDebug() << "Invoking menu for applet" << applet; QMenu *desktopMenu = new QMenu; //this is a workaround where Qt now creates the menu widget //in .exec before oxygen can polish it and set the following attribute desktopMenu->setAttribute(Qt::WA_TranslucentBackground); //end workaround if (desktopMenu->winId()) { desktopMenu->windowHandle()->setTransientParent(window()); } desktopMenu->setAttribute(Qt::WA_DeleteOnClose); m_contextMenu = desktopMenu; emit m_containment->contextualActionsAboutToShow(); if (applet) { emit applet->contextualActionsAboutToShow(); addAppletActions(desktopMenu, applet, event); } else { addContainmentActions(desktopMenu, event); } //this is a workaround where Qt will fail to realize a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [this]() { if (window() && window()->mouseGrabberItem()) { window()->mouseGrabberItem()->ungrabMouse(); } }; //pre 5.8.0 QQuickWindow code is "item->grabMouse(); sendEvent(item, mouseEvent)" //post 5.8.0 QQuickWindow code is sendEvent(item, mouseEvent); item->grabMouse() if (QVersionNumber::fromString(QLatin1String(qVersion())) > QVersionNumber(5, 8, 0)) { QTimer::singleShot(0, this, ungrabMouseHack); } else { ungrabMouseHack(); } //end workaround QPoint pos = event->globalPos(); if (window() && m_containment->containmentType() == Plasma::Types::PanelContainment) { desktopMenu->adjustSize(); if (QScreen *screen = window()->screen()) { const QRect geo = screen->availableGeometry(); pos = QPoint(qBound(geo.left(), pos.x(), geo.right() + 1 - desktopMenu->width()), qBound(geo.top(), pos.y(), geo.bottom() + 1 - desktopMenu->height())); } } if (desktopMenu->isEmpty()) { delete desktopMenu; event->accept(); return; } // Bug 344205 keep panel visible while menu is open const auto oldStatus = m_containment->status(); m_containment->setStatus(Plasma::Types::RequiresAttentionStatus); connect(desktopMenu, &QMenu::aboutToHide, m_containment, [this, oldStatus] { m_containment->setStatus(oldStatus); }); KAcceleratorManager::manage(desktopMenu); desktopMenu->popup(pos); event->setAccepted(true); } void ContainmentInterface::wheelEvent(QWheelEvent *event) { const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = m_containment->containmentActions().value(trigger); if (!plugin) { event->setAccepted(false); return; } m_wheelDelta += event->delta(); // Angle delta 120 for common "one click" // See: https://doc.qt.io/qt-5/qml-qtquick-wheelevent.html#angleDelta-prop while (m_wheelDelta >= 120) { m_wheelDelta -= 120; plugin->performPreviousAction(); } while (m_wheelDelta <= -120) { m_wheelDelta += 120; plugin->performNextAction(); } } void ContainmentInterface::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Menu) { QMouseEvent me(QEvent::MouseButtonRelease, QPoint(), Qt::RightButton, Qt::RightButton, event->modifiers()); mousePressEvent(&me); event->accept(); } AppletInterface::keyPressEvent(event); } void ContainmentInterface::addAppletActions(QMenu *desktopMenu, Plasma::Applet *applet, QEvent *event) { foreach (QAction *action, applet->contextualActions()) { if (action) { desktopMenu->addAction(action); } } if (!applet->failedToLaunch()) { QAction *runAssociatedApplication = applet->actions()->action(QStringLiteral("run associated application")); if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { desktopMenu->addAction(runAssociatedApplication); } QAction *configureApplet = applet->actions()->action(QStringLiteral("configure")); if (configureApplet && configureApplet->isEnabled()) { desktopMenu->addAction(configureApplet); } QAction *appletAlternatives = applet->actions()->action(QStringLiteral("alternatives")); if (appletAlternatives && appletAlternatives->isEnabled()) { desktopMenu->addAction(appletAlternatives); } } QMenu *containmentMenu = new QMenu(i18nc("%1 is the name of the containment", "%1 Options", m_containment->title()), desktopMenu); if (m_containment->containmentType() != Plasma::Types::DesktopContainment) { addContainmentActions(containmentMenu, event); } if (!containmentMenu->isEmpty()) { int enabled = 0; //count number of real actions QListIterator actionsIt(containmentMenu->actions()); while (enabled < 3 && actionsIt.hasNext()) { QAction *action = actionsIt.next(); if (action->isVisible() && !action->isSeparator()) { ++enabled; } } if (enabled) { //if there is only one, don't create a submenu if (enabled < 2) { foreach (QAction *action, containmentMenu->actions()) { if (action->isVisible() && !action->isSeparator()) { desktopMenu->addAction(action); } } } else { desktopMenu->addMenu(containmentMenu); } } } if (m_containment->immutability() == Plasma::Types::Mutable && (m_containment->containmentType() != Plasma::Types::PanelContainment || m_containment->isUserConfiguring())) { QAction *closeApplet = applet->actions()->action(QStringLiteral("remove")); //qDebug() << "checking for removal" << closeApplet; if (closeApplet) { if (!desktopMenu->isEmpty()) { desktopMenu->addSeparator(); } //qDebug() << "adding close action" << closeApplet->isEnabled() << closeApplet->isVisible(); desktopMenu->addAction(closeApplet); } } } void ContainmentInterface::addContainmentActions(QMenu *desktopMenu, QEvent *event) { if (m_containment->corona()->immutability() != Plasma::Types::Mutable && !KAuthorized::authorizeKAction(QStringLiteral("plasma/containment_actions"))) { //qDebug() << "immutability"; return; } //this is what ContainmentPrivate::prepareContainmentActions was const QString trigger = Plasma::ContainmentActions::eventToString(event); Plasma::ContainmentActions *plugin = m_containment->containmentActions().value(trigger); if (!plugin) { return; } if (plugin->containment() != m_containment) { plugin->setContainment(m_containment); // now configure it KConfigGroup cfg(m_containment->corona()->config(), "ActionPlugins"); cfg = KConfigGroup(&cfg, QString::number(m_containment->containmentType())); KConfigGroup pluginConfig = KConfigGroup(&cfg, trigger); plugin->restore(pluginConfig); } QList actions = plugin->contextualActions(); if (actions.isEmpty()) { //it probably didn't bother implementing the function. give the user a chance to set //a better plugin. note that if the user sets no-plugin this won't happen... if ((m_containment->containmentType() != Plasma::Types::PanelContainment && m_containment->containmentType() != Plasma::Types::CustomPanelContainment) && m_containment->actions()->action(QStringLiteral("configure"))) { desktopMenu->addAction(m_containment->actions()->action(QStringLiteral("configure"))); } } else { desktopMenu->addActions(actions); } return; } +bool ContainmentInterface::isLoading() const +{ + bool loading = AppletInterface::isLoading(); + if (m_wallpaperInterface) { + loading |= m_wallpaperInterface->isLoading(); + } + return loading; +} + + #include "moc_containmentinterface.cpp" diff --git a/src/scriptengines/qml/plasmoid/containmentinterface.h b/src/scriptengines/qml/plasmoid/containmentinterface.h index 34a3f2f8c..ef4e54c6e 100644 --- a/src/scriptengines/qml/plasmoid/containmentinterface.h +++ b/src/scriptengines/qml/plasmoid/containmentinterface.h @@ -1,227 +1,229 @@ /* * Copyright 2008-2013 Aaron Seigo * Copyright 2010-2013 Marco Martin * * 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 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. */ #ifndef CONTAINMENTINTERFACE_H #define CONTAINMENTINTERFACE_H #include #include #include "appletinterface.h" class WallpaperInterface; namespace KIO { class Job; class DropJob; } namespace KActivities { class Info; } /** * @class ContainmentInterface * * @brief This class is exposed to containments QML as the attached property Plasmoid * * */ class ContainmentInterface : public AppletInterface { Q_OBJECT /** * List of applets this containment has: the containments * KF6: this should be AppletQuickItem * */ Q_PROPERTY(QList applets READ applets NOTIFY appletsChanged) /** * Type of this containment TODO: notify */ Q_PROPERTY(Plasma::Types::ContainmentType containmentType READ containmentType WRITE setContainmentType NOTIFY containmentTypeChanged) /** * Activity name of this containment */ Q_PROPERTY(QString activity READ activity NOTIFY activityChanged) /** * Activity name of this containment */ Q_PROPERTY(QString activityName READ activityName NOTIFY activityNameChanged) /** * Actions associated to this containment or corona */ Q_PROPERTY(QList actions READ actions NOTIFY actionsChanged) /** * True when the containment is in an edit mode that allows to move * things around: it's different from userConfiguring as it's about * editing plasmoids inside the containment, rather than the containment * settings dialog itself */ Q_PROPERTY(bool editMode READ isEditMode WRITE setEditMode NOTIFY editModeChanged) public: ContainmentInterface(DeclarativeAppletScript *parent, const QVariantList &args = QVariantList()); //Not for QML Plasma::Containment *containment() const { return m_containment.data(); } inline WallpaperInterface *wallpaperInterface() const { return m_wallpaperInterface; } //For QML use QList applets(); Plasma::Types::ContainmentType containmentType() const; void setContainmentType(Plasma::Types::ContainmentType type); QString activity() const; QString activityName() const; QList actions() const; /** * Process the mime data arrived to a particular coordinate, either with a drag and drop or paste with middle mouse button */ Q_INVOKABLE void processMimeData(QMimeData *data, int x, int y, KIO::DropJob *dropJob = nullptr); /** * Process the mime data arrived to a particular coordinate, either with a drag and drop or paste with middle mouse button */ Q_INVOKABLE void processMimeData(QObject *data, int x, int y, KIO::DropJob *dropJob = nullptr); /** * Search for a containment at those coordinates. * the coordinates are passed as local coordinates of *this* containment */ Q_INVOKABLE QObject *containmentAt(int x, int y); /** * Add an existing applet to this containment. * The coordinates are passed as local coordinates of this containment */ Q_INVOKABLE void addApplet(AppletInterface *applet, int x, int y); /** * Map coordinates from relative to the given applet to relative to this containment */ Q_INVOKABLE QPointF mapFromApplet(AppletInterface *applet, int x, int y); /** *Map coordinates from relative to this containment to relative to the given applet */ Q_INVOKABLE QPointF mapToApplet(AppletInterface *applet, int x, int y); /** * Given a geometry, it adjusts it moving it completely inside of the boundaries * of availableScreenRegion * @return the toLeft point of the rectangle */ Q_INVOKABLE QPointF adjustToAvailableScreenRegion(int x, int y, int w, int h) const; bool isEditMode() const; void setEditMode(bool edit); static ContainmentInterface *qmlAttachedProperties(QObject *object) { return qobject_cast(AppletQuickItem::qmlAttachedProperties(object)); } protected: void init() override; void mousePressEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; void addAppletActions(QMenu *desktopMenu, Plasma::Applet *applet, QEvent *event); void addContainmentActions(QMenu *desktopMenu, QEvent *event); + virtual bool isLoading() const override; + Q_SIGNALS: /** * Emitted when an applet is added * @param applet the applet object: it's a qml graphical object and an instance of AppletInterface * @param x coordinate containment relative * @param y coordinate containment relative */ void appletAdded(QObject *applet, int x, int y); /** * Emitted when an applet is removed * @param applet the applet object: it's a qml graphical object and an instance of AppletInterface. * It's still valid, even if it will be deleted shortly */ void appletRemoved(QObject *applet); //Property notifiers void activityChanged(); void activityNameChanged(); void appletsChanged(); void drawWallpaperChanged(); void containmentTypeChanged(); void actionsChanged(); void editModeChanged(); protected Q_SLOTS: void appletAddedForward(Plasma::Applet *applet); void appletRemovedForward(Plasma::Applet *applet); void loadWallpaper(); void dropJobResult(KJob *job); void mimeTypeRetrieved(KIO::Job *job, const QString &mimetype); private Q_SLOTS: Plasma::Applet *createApplet(const QString &plugin, const QVariantList &args, const QPoint &pos); Plasma::Applet *createApplet(const QString &plugin, const QVariantList &args, const QRectF &geom); private: void clearDataForMimeJob(KIO::Job *job); void setAppletArgs(Plasma::Applet *applet, const QString &mimetype, const QString &data); WallpaperInterface *m_wallpaperInterface; QList m_appletInterfaces; QHash m_dropPoints; QHash m_dropMenus; QHash m_dropCallbacks; QHash m_dropJobs; KActivities::Info *m_activityInfo; QPointer m_containment; QPointer m_contextMenu; int m_wheelDelta; bool m_editMode : 1; friend class AppletInterface; }; QML_DECLARE_TYPEINFO(ContainmentInterface, QML_HAS_ATTACHED_PROPERTIES) #endif diff --git a/src/scriptengines/qml/plasmoid/wallpaperinterface.cpp b/src/scriptengines/qml/plasmoid/wallpaperinterface.cpp index 2269fee3b..d1988b566 100644 --- a/src/scriptengines/qml/plasmoid/wallpaperinterface.cpp +++ b/src/scriptengines/qml/plasmoid/wallpaperinterface.cpp @@ -1,269 +1,275 @@ /* * Copyright 2013 Marco Martin * * 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 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 "wallpaperinterface.h" #include "containmentinterface.h" #include #include #include #include #include #include #include #include #include #include #include QHash WallpaperInterface::s_rootObjects = QHash(); WallpaperInterface::WallpaperInterface(ContainmentInterface *parent) : QQuickItem(parent), m_containmentInterface(parent), m_qmlObject(nullptr), m_configuration(nullptr), m_configLoader(nullptr) { m_actions = new KActionCollection(this); //resize at the beginning to avoid as much resize events as possible if (parent) { setSize(QSizeF(parent->width(), parent->height())); } if (!m_containmentInterface->containment()->wallpaper().isEmpty()) { syncWallpaperPackage(); } connect(m_containmentInterface->containment(), &Plasma::Containment::wallpaperChanged, this, &WallpaperInterface::syncWallpaperPackage); } WallpaperInterface::~WallpaperInterface() { if (m_qmlObject) { s_rootObjects.remove(m_qmlObject->engine()); } } QList WallpaperInterface::listWallpaperMetadataForMimetype(const QString &mimetype, const QString &formFactor) { auto filter = [&mimetype, &formFactor](const KPluginMetaData &md) -> bool { if (!formFactor.isEmpty() && !md.value(QStringLiteral("X-Plasma-FormFactors")).contains(formFactor)) { return false; } return KPluginMetaData::readStringList(md.rawData(), QStringLiteral("X-Plasma-DropMimeTypes")).contains(mimetype); }; return KPackage::PackageLoader::self()->findPackages(QStringLiteral("Plasma/Wallpaper"), QString(), filter); } KPackage::Package WallpaperInterface::kPackage() const { return m_pkg; } QString WallpaperInterface::pluginName() const { return m_wallpaperPlugin; } KDeclarative::ConfigPropertyMap *WallpaperInterface::configuration() const { return m_configuration; } KConfigLoader *WallpaperInterface::configScheme() { if (!m_configLoader) { //FIXME: do we need "mainconfigxml" in wallpaper packagestructures? const QString xmlPath = m_pkg.filePath("config", QStringLiteral("main.xml")); KConfigGroup cfg = m_containmentInterface->containment()->config(); cfg = KConfigGroup(&cfg, "Wallpaper"); cfg = KConfigGroup(&cfg, m_wallpaperPlugin); if (xmlPath.isEmpty()) { m_configLoader = new KConfigLoader(cfg, nullptr, this); } else { QFile file(xmlPath); m_configLoader = new KConfigLoader(cfg, &file, this); } } return m_configLoader; } void WallpaperInterface::syncWallpaperPackage() { if (m_wallpaperPlugin == m_containmentInterface->containment()->wallpaper() && m_qmlObject->rootObject()) { return; } m_wallpaperPlugin = m_containmentInterface->containment()->wallpaper(); if (!m_qmlObject) { m_qmlObject = new KDeclarative::QmlObjectSharedEngine(this); s_rootObjects[m_qmlObject->engine()] = this; m_qmlObject->setInitializationDelayed(true); connect(m_qmlObject, &KDeclarative::QmlObject::finished, this, &WallpaperInterface::loadFinished); } m_actions->clear(); m_pkg = Plasma::PluginLoader::self()->loadPackage(QStringLiteral("Plasma/Wallpaper")).kPackage(); m_pkg.setPath(m_wallpaperPlugin); if (!m_pkg.isValid()) { qWarning() << "Error loading the wallpaper, no valid package loaded"; return; } if (m_configLoader) m_configLoader->deleteLater(); if (m_configuration) m_configuration->deleteLater(); m_configLoader = nullptr; m_configuration = nullptr; if (configScheme()) { m_configuration = new KDeclarative::ConfigPropertyMap(configScheme(), this); } m_qmlObject->rootContext()->setContextProperty(QStringLiteral("wallpaper"), this); m_qmlObject->setSource(m_pkg.fileUrl("mainscript")); const QString rootPath = m_pkg.metadata().value(QStringLiteral("X-Plasma-RootPath")); if (!rootPath.isEmpty()) { m_qmlObject->setTranslationDomain(QLatin1String("plasma_wallpaper_") + rootPath); } else { m_qmlObject->setTranslationDomain(QLatin1String("plasma_wallpaper_") + m_pkg.metadata().pluginId()); } //initialize with our size to avoid as much resize events as possible QVariantHash props; props[QStringLiteral("width")] = width(); props[QStringLiteral("height")] = height(); m_qmlObject->completeInitialization(props); } void WallpaperInterface::loadFinished() { if (m_qmlObject->mainComponent() && m_qmlObject->rootObject() && !m_qmlObject->mainComponent()->isError()) { m_qmlObject->rootObject()->setProperty("z", -1000); m_qmlObject->rootObject()->setProperty("parent", QVariant::fromValue(this)); //set anchors QQmlExpression expr(m_qmlObject->engine()->rootContext(), m_qmlObject->rootObject(), QStringLiteral("parent")); QQmlProperty prop(m_qmlObject->rootObject(), QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } else if (m_qmlObject->mainComponent()) { qWarning() << "Error loading the wallpaper" << m_qmlObject->mainComponent()->errors(); s_rootObjects.remove(m_qmlObject->engine()); m_qmlObject->deleteLater(); m_qmlObject = nullptr; } else { qWarning() << "Error loading the wallpaper, package not found"; } emit packageChanged(); emit configurationChanged(); } QList WallpaperInterface::contextualActions() const { return m_actions->actions(); } bool WallpaperInterface::supportsMimetype(const QString &mimetype) const { return KPluginMetaData::readStringList(m_pkg.metadata().rawData(), QStringLiteral("X-Plasma-DropMimeTypes")).contains(mimetype); } void WallpaperInterface::setUrl(const QUrl &url) { if (m_qmlObject->rootObject()) { QMetaObject::invokeMethod(m_qmlObject->rootObject(), "setUrl", Qt::DirectConnection, Q_ARG(QVariant, QVariant::fromValue(url))); } } void WallpaperInterface::setAction(const QString &name, const QString &text, const QString &icon, const QString &shortcut) { QAction *action = m_actions->action(name); if (action) { action->setText(text); } else { Q_ASSERT(!m_actions->action(name)); action = new QAction(text, this); m_actions->addAction(name, action); connect(action, &QAction::triggered, this, [this, name] { executeAction(name); }); } if (!icon.isEmpty()) { action->setIcon(QIcon::fromTheme(icon)); } if (!shortcut.isEmpty()) { action->setShortcut(shortcut); } action->setObjectName(name); setProperty("contextualActions", QVariant::fromValue(contextualActions())); } void WallpaperInterface::removeAction(const QString &name) { QAction *action = m_actions->action(name); if (action) { m_actions->removeAction(action); delete action; } setProperty("contextualActions", QVariant::fromValue(contextualActions())); } QAction *WallpaperInterface::action(QString name) const { return m_actions->action(name); } void WallpaperInterface::executeAction(const QString &name) { if (m_qmlObject->rootObject()) { const QByteArray actionName("action_" + name.toUtf8()); QMetaObject::invokeMethod(m_qmlObject->rootObject(), actionName.constData(), Qt::DirectConnection); } } WallpaperInterface * WallpaperInterface::qmlAttachedProperties(QObject* object) { //at the moment of the attached object creation, the root item is the only one that hasn't a parent //only way to avoid creation of this attached for everybody but the root item return object->parent() ? nullptr : s_rootObjects.value(QtQml::qmlEngine(object)); } +bool WallpaperInterface::isLoading() const +{ + return m_loading; +} + + #include "moc_wallpaperinterface.cpp" diff --git a/src/scriptengines/qml/plasmoid/wallpaperinterface.h b/src/scriptengines/qml/plasmoid/wallpaperinterface.h index b315f519d..daea6a80a 100644 --- a/src/scriptengines/qml/plasmoid/wallpaperinterface.h +++ b/src/scriptengines/qml/plasmoid/wallpaperinterface.h @@ -1,112 +1,117 @@ /* * Copyright 2013 Marco Martin * * 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 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. */ #ifndef WALLPAPERINTERFACE_H #define WALLPAPERINTERFACE_H #include #include #include class KConfigLoader; class KActionCollection; class ContainmentInterface; namespace KDeclarative { class ConfigPropertyMap; class QmlObject; } /** * @class WallpaperInterface * * @brief This class is exposed to wallpapers as the attached property "wallpaper" * * */ class WallpaperInterface : public QQuickItem { Q_OBJECT Q_PROPERTY(QString pluginName READ pluginName NOTIFY packageChanged) Q_PROPERTY(KDeclarative::ConfigPropertyMap *configuration READ configuration NOTIFY configurationChanged) + Q_PROPERTY(bool loading MEMBER m_loading NOTIFY isLoadingChanged) public: explicit WallpaperInterface(ContainmentInterface *parent = nullptr); ~WallpaperInterface(); /** * Returns a list of all known wallpapers that can accept the given mimetype * @param mimetype the mimetype to search for * @param formFactor the format of the wallpaper being search for (e.g. desktop) * @return list of wallpapers */ static QList listWallpaperMetadataForMimetype(const QString &mimetype, const QString &formFactor = QString()); KPackage::Package kPackage() const; QString pluginName() const; KDeclarative::ConfigPropertyMap *configuration() const; KConfigLoader *configScheme(); QList contextualActions() const; bool supportsMimetype(const QString &mimetype) const; void setUrl(const QUrl &urls); Q_INVOKABLE void setAction(const QString &name, const QString &text, const QString &icon = QString(), const QString &shortcut = QString()); Q_INVOKABLE void removeAction(const QString &name); Q_INVOKABLE QAction *action(QString name) const; static WallpaperInterface *qmlAttachedProperties(QObject *object); + bool isLoading() const; + Q_SIGNALS: void packageChanged(); void configurationChanged(); + void isLoadingChanged(); private Q_SLOTS: void syncWallpaperPackage(); void executeAction(const QString &name); void loadFinished(); private: QString m_wallpaperPlugin; ContainmentInterface *m_containmentInterface; KDeclarative::QmlObject *m_qmlObject; KPackage::Package m_pkg; KDeclarative::ConfigPropertyMap *m_configuration; KConfigLoader *m_configLoader; KActionCollection *m_actions; + bool m_loading = false; static QHash s_rootObjects; }; QML_DECLARE_TYPEINFO(WallpaperInterface, QML_HAS_ATTACHED_PROPERTIES) #endif