Libplasma

appletquickitem.cpp
1/*
2 SPDX-FileCopyrightText: 2014 Marco Martin <mart@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "appletquickitem.h"
8#include "applet.h"
9#include "appletcontext_p.h"
10#include "appletquickitem_p.h"
11#include "configview.h"
12#include "containment.h"
13#include "debug_p.h"
14#include "plasma_version.h"
15#include "plasmoid/containmentitem.h"
16#include "plasmoid/plasmoiditem.h"
17#include "plasmoid/wallpaperitem.h"
18#include "plasmoidattached_p.h"
19#include "sharedqmlengine.h"
20
21#include <QJsonArray>
22#include <QQmlContext>
23#include <QQmlExpression>
24#include <QQmlProperty>
25#include <QQuickWindow>
26#include <QRandomGenerator>
27
28#include <QDebug>
29
30#include <KLocalizedString>
31
32#include <Plasma/Applet>
33#include <Plasma/Containment>
34#include <Plasma/Corona>
35#include <qquickitem.h>
36
37namespace PlasmaQuick
38{
39
41AppletQuickItemPrivate::PreloadPolicy AppletQuickItemPrivate::s_preloadPolicy = AppletQuickItemPrivate::Uninitialized;
42
43AppletQuickItemPrivate::AppletQuickItemPrivate(AppletQuickItem *item)
44 : q(item)
45 , switchWidth(-1)
46 , switchHeight(-1)
47 , initComplete(false)
48 , compactRepresentationCheckGuard(false)
49{
50 if (s_preloadPolicy == Uninitialized) {
51 // default as Adaptive
52 s_preloadPolicy = Adaptive;
53
54 if (qEnvironmentVariableIsSet("PLASMA_PRELOAD_POLICY")) {
55 const QString policy = qEnvironmentVariable("PLASMA_PRELOAD_POLICY");
56 if (policy.compare(QLatin1String("aggressive"), Qt::CaseInsensitive) == 0) {
57 s_preloadPolicy = Aggressive;
58 } else if (policy.compare(QLatin1String("none"), Qt::CaseInsensitive) == 0) {
59 s_preloadPolicy = None;
60 }
61 }
62
63 qCInfo(LOG_PLASMAQUICK) << "Applet preload policy set to" << s_preloadPolicy;
64 }
65}
66
67int AppletQuickItemPrivate::preloadWeight() const
68{
69 int defaultWeight;
70 const QStringList provides = applet->pluginMetaData().value(u"X-Plasma-Provides", QStringList());
71
72 // some applet types we want a bigger weight
73 if (provides.contains(QLatin1String("org.kde.plasma.launchermenu"))) {
74 defaultWeight = DefaultLauncherPreloadWeight;
75 } else {
76 defaultWeight = DefaultPreloadWeight;
77 }
78 // default widgets to be barely preloaded
79 return qBound(
80 0,
81 applet->config().readEntry(QStringLiteral("PreloadWeight"), qMax(defaultWeight, applet->pluginMetaData().value(u"X-Plasma-PreloadWeight", 0))),
82 100);
83}
84
85QObject *AppletQuickItemPrivate::searchLayoutAttached(QObject *parent) const
86{
87 QObject *layout = nullptr;
88 // Search a child that has the needed Layout properties
89 // HACK: here we are not type safe, but is the only way to access to a pointer of Layout
90 const auto lstChildren = parent->children();
91 for (QObject *child : lstChildren) {
92 // find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight
93 /* clang-format off */
94 if (child->property("minimumWidth").isValid()
95 && child->property("minimumHeight").isValid()
96 && child->property("preferredWidth").isValid()
97 && child->property("preferredHeight").isValid()
98 && child->property("maximumWidth").isValid()
99 && child->property("maximumHeight").isValid()
100 && child->property("fillWidth").isValid()
101 && child->property("fillHeight").isValid()) { /* clang-format on */
102 layout = child;
103 break;
104 }
105 }
106 return layout;
107}
108
109void AppletQuickItemPrivate::connectLayoutAttached(QObject *item)
110{
111 // Extract the representation's Layout, if any
112 if (!item) {
113 return;
114 }
115
116 QObject *layout = searchLayoutAttached(item);
117
118 // if the compact repr doesn't export a Layout.* attached property,
119 // reset our own with default values
120 if (!layout) {
121 if (ownLayout) {
122 ownLayout->setProperty("minimumWidth", 0);
123 ownLayout->setProperty("minimumHeight", 0);
124 ownLayout->setProperty("preferredWidth", -1);
125 ownLayout->setProperty("preferredHeight", -1);
126 ownLayout->setProperty("maximumWidth", std::numeric_limits<qreal>::infinity());
127 ownLayout->setProperty("maximumHeight", std::numeric_limits<qreal>::infinity());
128 ownLayout->setProperty("fillWidth", false);
129 ownLayout->setProperty("fillHeight", false);
130 }
131 return;
132 }
133
134 // propagate all the size hints
135 propagateSizeHint("minimumWidth");
136 propagateSizeHint("minimumHeight");
137 propagateSizeHint("preferredWidth");
138 propagateSizeHint("preferredHeight");
139 propagateSizeHint("maximumWidth");
140 propagateSizeHint("maximumHeight");
141 propagateSizeHint("fillWidth");
142 propagateSizeHint("fillHeight");
143
144 QObject *newOwnLayout = searchLayoutAttached(q);
145
146 // this should never happen, since we ask to create it if doesn't exists
147 if (!newOwnLayout) {
148 return;
149 }
150
151 // if the representation didn't change, don't do anything
152 if (representationLayout == layout) {
153 return;
154 }
155
156 if (representationLayout) {
157 QObject::disconnect(representationLayout, nullptr, q, nullptr);
158 }
159
160 // Here we can't use the new connect syntax because we can't link against QtQuick layouts
161 QObject::connect(layout, SIGNAL(minimumWidthChanged()), q, SLOT(minimumWidthChanged()));
162 QObject::connect(layout, SIGNAL(minimumHeightChanged()), q, SLOT(minimumHeightChanged()));
163
164 QObject::connect(layout, SIGNAL(preferredWidthChanged()), q, SLOT(preferredWidthChanged()));
165 QObject::connect(layout, SIGNAL(preferredHeightChanged()), q, SLOT(preferredHeightChanged()));
166
167 QObject::connect(layout, SIGNAL(maximumWidthChanged()), q, SLOT(maximumWidthChanged()));
168 QObject::connect(layout, SIGNAL(maximumHeightChanged()), q, SLOT(maximumHeightChanged()));
169
170 QObject::connect(layout, SIGNAL(fillWidthChanged()), q, SLOT(fillWidthChanged()));
171 QObject::connect(layout, SIGNAL(fillHeightChanged()), q, SLOT(fillHeightChanged()));
172
173 representationLayout = layout;
174 ownLayout = newOwnLayout;
175
176 propagateSizeHint("minimumWidth");
177 propagateSizeHint("minimumHeight");
178 propagateSizeHint("preferredWidth");
179 propagateSizeHint("preferredHeight");
180 propagateSizeHint("maximumWidth");
181 propagateSizeHint("maximumHeight");
182 propagateSizeHint("fillWidth");
183 propagateSizeHint("fillHeight");
184}
185
186void AppletQuickItemPrivate::propagateSizeHint(const QByteArray &layoutProperty)
187{
188 if (ownLayout && representationLayout) {
189 ownLayout->setProperty(layoutProperty.constData(), representationLayout->property(layoutProperty.constData()).toReal());
190 }
191}
192
193QQuickItem *AppletQuickItemPrivate::createCompactRepresentationItem()
194{
195 if (!compactRepresentation) {
196 return nullptr;
197 }
198
199 if (compactRepresentationItem) {
200 return compactRepresentationItem;
201 }
202
203 QVariantHash initialProperties;
204 initialProperties[QStringLiteral("parent")] = QVariant::fromValue(q);
205 initialProperties[QStringLiteral("plasmoidItem")] = QVariant::fromValue(q);
206
207 compactRepresentationItem = qobject_cast<QQuickItem *>(qmlObject->createObjectFromComponent(compactRepresentation, qmlContext(q), initialProperties));
208
209 Q_EMIT q->compactRepresentationItemChanged(compactRepresentationItem);
210
211 return compactRepresentationItem;
212}
213
214QQuickItem *AppletQuickItemPrivate::createFullRepresentationItem()
215{
216 if (fullRepresentationItem) {
217 return fullRepresentationItem;
218 }
219
220 if (fullRepresentation && fullRepresentation != qmlObject->mainComponent()) {
221 QVariantHash initialProperties;
222 initialProperties[QStringLiteral("parent")] = QVariant();
223 fullRepresentationItem = qobject_cast<QQuickItem *>(qmlObject->createObjectFromComponent(fullRepresentation, qmlContext(q), initialProperties));
224 }
225
226 if (!fullRepresentationItem) {
227 return nullptr;
228 }
229
230 Q_EMIT q->fullRepresentationItemChanged(fullRepresentationItem);
231
232 return fullRepresentationItem;
233}
234
235QQuickItem *AppletQuickItemPrivate::createCompactRepresentationExpanderItem()
236{
237 if (!compactRepresentationExpander) {
238 return nullptr;
239 }
240
241 if (compactRepresentationExpanderItem) {
242 return compactRepresentationExpanderItem;
243 }
244
245 compactRepresentationExpanderItem = qobject_cast<QQuickItem *>(qmlObject->createObjectFromComponent(compactRepresentationExpander, qmlContext(q)));
246
247 if (!compactRepresentationExpanderItem) {
248 return nullptr;
249 }
250
251 compactRepresentationExpanderItem->setProperty("compactRepresentation", QVariant::fromValue<QObject *>(createCompactRepresentationItem()));
252 compactRepresentationExpanderItem->setProperty("plasmoidItem", QVariant::fromValue(q));
253
254 return compactRepresentationExpanderItem;
255}
256
257bool AppletQuickItemPrivate::appletShouldBeExpanded() const
258{
259 if (applet->isContainment()) {
260 return true;
261
262 } else {
263 if (!fullRepresentation) {
264 // If a full representation wasn't specified, the onle and only representation of the plasmoid are our
265 // direct contents, so we consider it always expanded
266 return true;
267 }
268 if (switchWidth > 0 && switchHeight > 0) {
269 // This code checks against the following edge case:
270 // The compact representation preferred size is bigger than both the switch
271 // size, and the full representation preferred size.
272 // this can cause in the panel (when is quite big) an infinite resize loop, because
273 // the applet size is bigger than the switch size, then it switches to full
274 // representaiton that has a smaller hint. this causes a resize that will make it
275 // switch to compact representation, making it grow again and switch again
276 if (compactRepresentationItem && fullRepresentationItem) {
277 QObject *compactLayout = searchLayoutAttached(compactRepresentationItem);
278 QObject *fullLayout = searchLayoutAttached(fullRepresentationItem);
279 if (compactLayout && fullLayout) {
280 QSizeF compactPreferred = {compactLayout->property("preferredWidth").toReal(), compactLayout->property("preferredHeight").toReal()};
281 QSizeF fullPreferred = {fullLayout->property("preferredWidth").toReal(), fullLayout->property("preferredHeight").toReal()};
282
283 if ((compactPreferred.width() > fullPreferred.width() && compactPreferred.width() > switchWidth) ||
284 (compactPreferred.height() > fullPreferred.height() && compactPreferred.height() > switchHeight)) {
285 return false;
286 }
287 }
288 }
289 return q->width() > switchWidth && q->height() > switchHeight;
290
291 // if a size to switch wasn't set, determine what representation to always chose
292 } else {
293 // preferred representation set?
294 if (preferredRepresentation) {
295 return preferredRepresentation == fullRepresentation;
296 // Otherwise, base on FormFactor
297 } else {
298 return (applet->formFactor() != Plasma::Types::Horizontal && applet->formFactor() != Plasma::Types::Vertical);
299 }
300 }
301 }
302}
303
304void AppletQuickItemPrivate::preloadForExpansion()
305{
306 qint64 time = 0;
307 if (QLoggingCategory::defaultCategory()->isInfoEnabled()) {
309 }
310
311 if (!createFullRepresentationItem()) {
312 return;
313 }
314
315 // When not already expanded, also preload the expander
316 if (!appletShouldBeExpanded() && !applet->isContainment() && (!preferredRepresentation || preferredRepresentation != fullRepresentation)) {
317 createCompactRepresentationExpanderItem();
318 }
319
320 if (!appletShouldBeExpanded() && compactRepresentationExpanderItem) {
321 compactRepresentationExpanderItem->setProperty("fullRepresentation", QVariant::fromValue<QObject *>(createFullRepresentationItem()));
322 } else if (fullRepresentationItem) {
323 fullRepresentationItem->setProperty("parent", QVariant::fromValue<QObject *>(q));
324 }
325
326 // preallocate nodes
327 if (fullRepresentationItem && fullRepresentationItem->window()) {
328 fullRepresentationItem->window()->create();
329 }
330
331 qCDebug(LOG_PLASMAQUICK) << "Applet" << applet->title() << "loaded after" << (QDateTime::currentMSecsSinceEpoch() - time) << "msec";
332}
333
334void AppletQuickItemPrivate::anchorsFillParent(QQuickItem *item, QQuickItem *parent)
335{
336 if (item->parentItem() != parent) {
337 return;
338 }
339 // just set once, don't bind
340 QQmlProperty::write(item, QStringLiteral("anchors.fill"), QVariant::fromValue<QObject *>(parent));
341}
342
343void AppletQuickItemPrivate::compactRepresentationCheck()
344{
345 if (!initComplete) {
346 return;
347 }
348
349 // ignore 0 sizes;
350 if (q->width() <= 0 || q->height() <= 0) {
351 return;
352 }
353
354 // ignore if this widget is being checked somewhere above
355 if (compactRepresentationCheckGuard) {
356 return;
357 }
358
359 bool full = appletShouldBeExpanded();
360
361 if ((full && fullRepresentationItem && fullRepresentationItem == currentRepresentationItem)
362 || (!full && compactRepresentationItem && compactRepresentationItem == currentRepresentationItem)) {
363 return;
364 }
365
366 compactRepresentationCheckGuard = true;
367
368 // Expanded
369 if (full) {
370 QQuickItem *item = createFullRepresentationItem();
371
372 if (item) {
373 // unwire with the expander
374 if (compactRepresentationExpanderItem) {
375 compactRepresentationExpanderItem->setProperty("fullRepresentation", QVariant());
376 compactRepresentationExpanderItem->setProperty("compactRepresentation", QVariant());
377 compactRepresentationExpanderItem->setVisible(false);
378 }
379
380 const bool fullRepresentationWasVisible = fullRepresentationItem->parentItem() == q;
381
382 // the fullrepresentation being the complete AppletItem is actually allowed when the main ui
383 // is child of the root item (like many panel applets)
384 if (item != q) {
385 item->setParentItem(q);
386 anchorsFillParent(item, q);
387 }
388
389 if (compactRepresentationItem) {
390 compactRepresentationItem->setVisible(false);
391 }
392
393 currentRepresentationItem = item;
394 connectLayoutAttached(item);
395
396 if (!expanded && !fullRepresentationWasVisible) {
397 expanded = true;
398 Q_EMIT q->expandedChanged(true);
399 }
400 }
401
402 } else {
403 // Icon
404 QQuickItem *compactItem = createCompactRepresentationItem();
405 QQuickItem *compactExpanderItem = createCompactRepresentationExpanderItem();
406
407 if (compactItem && compactExpanderItem) {
408 // set the root item as the main visible item
409 compactItem->setVisible(true);
410 compactExpanderItem->setParentItem(q);
411 compactExpanderItem->setVisible(true);
412 anchorsFillParent(compactExpanderItem, q);
413
414 // only reparent full representation to null if it was parented to the applet
415 // if it was already in the expander, leave it where it is
416 const bool fullRepresentationWasVisible = fullRepresentationItem && fullRepresentationItem->parentItem() == q;
417 if (fullRepresentationItem && fullRepresentationWasVisible) {
418 fullRepresentationItem->setProperty("parent", QVariant());
419 }
420
421 compactExpanderItem->setProperty("compactRepresentation", QVariant::fromValue<QObject *>(compactItem));
422 // The actual full representation will be connected when created
423 compactExpanderItem->setProperty("fullRepresentation", QVariant());
424
425 currentRepresentationItem = compactItem;
426 connectLayoutAttached(compactItem);
427
428 // set Expanded to false only if the expanded cause was the full representation
429 // in the applet item, not if the full representation was in the popup and the popup was open
430 if (expanded && fullRepresentationWasVisible) {
431 expanded = false;
432 Q_EMIT q->expandedChanged(false);
433 }
434 }
435 }
436
437 compactRepresentationCheckGuard = false;
438}
439
440void AppletQuickItemPrivate::minimumWidthChanged()
441{
442 propagateSizeHint("minimumWidth");
443}
444
445void AppletQuickItemPrivate::minimumHeightChanged()
446{
447 propagateSizeHint("minimumHeight");
448}
449
450void AppletQuickItemPrivate::preferredWidthChanged()
451{
452 propagateSizeHint("preferredWidth");
453}
454
455void AppletQuickItemPrivate::preferredHeightChanged()
456{
457 propagateSizeHint("preferredHeight");
458}
459
460void AppletQuickItemPrivate::maximumWidthChanged()
461{
462 propagateSizeHint("maximumWidth");
463}
464
465void AppletQuickItemPrivate::maximumHeightChanged()
466{
467 propagateSizeHint("maximumHeight");
468}
469
470void AppletQuickItemPrivate::fillWidthChanged()
471{
472 propagateSizeHint("fillWidth");
473}
474
475void AppletQuickItemPrivate::fillHeightChanged()
476{
477 propagateSizeHint("fillHeight");
478}
479
480AppletQuickItem::AppletQuickItem(QQuickItem *parent)
481 : QQuickItem(parent)
482 , d(new AppletQuickItemPrivate(this))
483{
484}
485
486AppletQuickItem::~AppletQuickItem()
487{
488 AppletQuickItemPrivate::s_itemsForApplet.remove(d->applet);
489 // decrease weight
490 if (d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive) {
491 d->applet->config().writeEntry(QStringLiteral("PreloadWeight"), qMax(0, d->preloadWeight() - AppletQuickItemPrivate::PreloadWeightDecrement));
492 }
493
494 // Here the order is important
495 delete d->compactRepresentationItem;
496 delete d->fullRepresentationItem;
497 delete d->compactRepresentationExpanderItem;
498 delete d;
499}
500
501bool AppletQuickItem::hasItemForApplet(Plasma::Applet *applet)
502{
503 return AppletQuickItemPrivate::s_itemsForApplet.contains(applet);
504}
505
506AppletQuickItem *AppletQuickItem::itemForApplet(Plasma::Applet *applet)
507{
508 if (!applet) {
509 return nullptr;
510 }
511
512 // TODO: move somewhere else? in plasmacore import?
513 if (AppletQuickItemPrivate::s_itemsForApplet.isEmpty()) {
514 const char *uri = "org.kde.plasma.plasmoid";
515 qmlRegisterExtendedType<Plasma::Applet, PlasmoidAttached>(uri, 2, 0, "Plasmoid");
516 qmlRegisterExtendedType<Plasma::Containment, ContainmentAttached>(uri, 2, 0, "Containment");
517
518 qmlRegisterType<PlasmoidItem>(uri, 2, 0, "PlasmoidItem");
519 qmlRegisterType<ContainmentItem>(uri, 2, 0, "ContainmentItem");
520 qmlRegisterType<WallpaperItem>(uri, 2, 0, "WallpaperItem");
521 qmlRegisterAnonymousType<Plasma::Corona>("org.kde.plasma.plasmoid", 1);
522 }
523 auto it = AppletQuickItemPrivate::s_itemsForApplet.constFind(applet);
524 if (it != AppletQuickItemPrivate::s_itemsForApplet.constEnd()) {
525 return it.value();
526 }
527
528 // Don't try to create applet items when the app is closing
529 if (qApp->closingDown() || applet->destroyed()) {
530 return nullptr;
531 }
532
533 Plasma::Containment *pc = qobject_cast<Plasma::Containment *>(applet);
534 auto *qmlObject = new PlasmaQuick::SharedQmlEngine(applet, applet);
535 qmlObject->engine()->setProperty("_kirigamiTheme", QStringLiteral("KirigamiPlasmaStyle"));
536 qmlObject->setInitializationDelayed(true);
537 qmlObject->setTranslationDomain(applet->translationDomain());
538
539 AppletQuickItem *item = nullptr;
540 qmlObject->setSource(applet->mainScript());
541 if (pc && pc->isContainment()) {
542 item = qobject_cast<ContainmentItem *>(qmlObject->rootObject());
543 if (!item && qmlObject->mainComponent() && !qmlObject->mainComponent()->isError()) {
544 applet->setLaunchErrorMessage(i18n("The root item of %1 must be of type ContainmentItem", applet->mainScript().toString()));
545 }
546 } else {
547 item = qobject_cast<PlasmoidItem *>(qmlObject->rootObject());
548 if (!item && qmlObject->mainComponent() && !qmlObject->mainComponent()->isError()) {
549 applet->setLaunchErrorMessage(i18n("The root item of %1 must be of type PlasmoidItem", applet->mainScript().toString()));
550 }
551 }
552
553 if (!item || !qmlObject->mainComponent() || qmlObject->mainComponent()->isError() || applet->failedToLaunch()) {
554 QString reason;
555 QString compactReason;
556 QJsonObject errorData;
557 errorData[QStringLiteral("appletName")] = i18n("Unknown Applet");
558 errorData[QStringLiteral("isDebugMode")] = qEnvironmentVariableIntValue("PLASMA_ENABLE_QML_DEBUG") != 0;
559
560 if (applet->sourceValid()) {
561 const QString versionString = applet->pluginMetaData().value(u"X-Plasma-API-Minimum-Version");
563 if (!versionString.isEmpty()) {
564 version = QVersionNumber::fromString(versionString);
565 }
566
567 bool versionMismatch = false;
568 const int plasma_version_major = 6; // TODO: as soon PLASMA_VERSION_MAJOR is actually 6, use directly that
569 if (version.isNull()) {
570 reason = i18n(
571 "This Widget was written for an unknown older version of Plasma and is not compatible with Plasma %1. Please contact the widget's author for "
572 "an updated version.",
573 plasma_version_major);
574 compactReason = i18n("%1 is not compatible with Plasma %2", applet->pluginMetaData().name(), plasma_version_major);
575 versionMismatch = true;
576 } else if (version.majorVersion() < plasma_version_major) {
577 reason =
578 i18n("This Widget was written for Plasma %1 and is not compatible with Plasma %2. Please contact the widget's author for an updated version.",
579 version.majorVersion(),
580 plasma_version_major);
581 compactReason = i18n("%1 is not compatible with Plasma %2", applet->pluginMetaData().name(), plasma_version_major);
582 versionMismatch = true;
583 } else if (version.majorVersion() > plasma_version_major || version.minorVersion() > PLASMA_VERSION_MINOR) {
584 reason = i18n("This Widget was written for Plasma %1 and is not compatible with Plasma %2. Please update Plasma in order to use the widget.",
585 versionString,
586 plasma_version_major);
587 compactReason = i18n("%1 is not compatible with Plasma %2", applet->pluginMetaData().name(), plasma_version_major);
588 versionMismatch = true;
589 } else if (applet->failedToLaunch()) {
590 reason = applet->launchErrorMessage();
591 compactReason = reason;
592 } else {
593 compactReason = i18n("Sorry! There was an error loading %1.", applet->pluginMetaData().name());
594 }
595 errorData[QStringLiteral("errors")] = QJsonArray::fromStringList({reason});
596 if (compactReason != QString()) {
597 errorData[QStringLiteral("compactError")] = compactReason;
598 }
599
600 if (!versionMismatch) {
601 const auto errors = qmlObject->mainComponent()->errors();
603 for (const QQmlError &error : errors) {
604 reason += error.toString() + QLatin1Char('\n');
605 errorList << error.toString();
606 }
607 errorData[QStringLiteral("errors")] = QJsonArray::fromStringList(errorList);
608 }
609 errorData[QStringLiteral("appletName")] = applet->pluginMetaData().name();
610 reason += i18n("Error loading QML file: %1 %2", qmlObject->mainComponent()->url().toString(), reason);
611 } else {
612 const auto pluginId = applet->pluginMetaData().pluginId();
613 reason = i18n("Error loading Applet: package %1 does not exist.", pluginId);
614 errorData[QStringLiteral("errors")] = QJsonArray::fromStringList({reason});
615 compactReason = i18n("Sorry! There was an error loading %1.", pluginId);
616 errorData[QStringLiteral("compactError")] = compactReason;
617 }
618
619 qCWarning(LOG_PLASMAQUICK) << "error when loading applet" << applet->pluginMetaData().pluginId()
620 << errorData[QStringLiteral("errors")].toVariant().toStringList();
621
622 qmlObject->setSource(applet->containment()->corona()->kPackage().fileUrl("appleterror"));
623
624 applet->setHasConfigurationInterface(false);
625 // even the error message QML may fail
626 if (qmlObject->mainComponent()->isError()) {
627 return nullptr;
628 }
629
630 item = qobject_cast<PlasmoidItem *>(qmlObject->rootObject());
631
632 applet->setLaunchErrorMessage(reason);
633 if (item) {
634 item->setProperty("errorInformation", errorData);
635 } else {
636 // In this case the error message loaded correctly, but was not a PlasmoidItem, bail out
637 qCWarning(LOG_PLASMAQUICK) << "Applet Error message is not of type PlasmoidItem"
638 << applet->containment()->corona()->kPackage().fileUrl("appleterror");
639 return nullptr;
640 }
641 }
642
643 AppletQuickItemPrivate::s_itemsForApplet[applet] = item;
644 qmlObject->setInitializationDelayed(false);
645 qmlObject->completeInitialization();
646
647 // A normal applet has UI ready as soon as is loaded, a containment, only when also the wallpaper is loaded
648 if (!pc || !pc->isContainment()) {
651 }
652
653 item->setProperty("_plasma_applet", QVariant::fromValue(applet));
654 item->d->applet = applet;
655 item->d->qmlObject = qmlObject;
656
657 if (!qEnvironmentVariableIntValue("PLASMA_NO_CONTEXTPROPERTIES")) {
658 qmlObject->rootContext()->setContextProperty(QStringLiteral("plasmoid"), applet);
659 }
660
661 QObject::connect(applet, &Plasma::Applet::appletDeleted, item, [qmlObject](Plasma::Applet *applet) {
662 // Deleting qmlObject will also delete the instantiated plasmoidItem
663 // deleteing just the plasmoiditem will cause a double deletion when qmlObject
664 // gets deleted by applet deletion
665 if (qmlObject->parent() == applet) {
666 // appletDelete can also be emitted by a containment for one of its children
667 delete qmlObject;
668 AppletQuickItemPrivate::s_itemsForApplet.remove(applet);
669 }
670 });
671
672 applet->setProperty("_plasmoid", QVariant::fromValue(item));
673 return item;
674}
675
676Plasma::Applet *AppletQuickItem::applet() const
677{
678 return d->applet;
679}
680
681void AppletQuickItem::init()
682{
683 if (!d->applet) {
684 // This can happen only if the client QML code declares a PlasmoidItem somewhere else than the root object
685 return;
686 }
687 if (d->initComplete) {
688 return;
689 }
690
691 if (d->applet->containment()) {
692 if (d->applet->containment()->corona()) {
693 d->coronaPackage = d->applet->containment()->corona()->kPackage();
694 }
695 }
696
697 // Initialize the main QML file
698 QQmlEngine *engine = d->qmlObject->engine().get();
699
700 // If no fullRepresentation was defined, we won't create compact and expander either.
701 // The only representation available are whatever items defined directly inside PlasmoidItem {}
702 // default compactRepresentation is a simple icon provided by the shell package
703 if (!d->compactRepresentation && d->fullRepresentation) {
704 d->compactRepresentation = new QQmlComponent(engine, this);
705 d->compactRepresentation->loadUrl(d->coronaPackage.fileUrl("defaultcompactrepresentation"));
706 Q_EMIT compactRepresentationChanged(d->compactRepresentation);
707 }
708
709 // default compactRepresentationExpander is the popup in which fullRepresentation goes
710 if (!d->compactRepresentationExpander && d->fullRepresentation) {
711 d->compactRepresentationExpander = new QQmlComponent(engine, this);
712 QUrl compactExpanderUrl = d->applet->containment()->compactApplet();
713 if (compactExpanderUrl.isEmpty()) {
714 compactExpanderUrl = d->coronaPackage.fileUrl("compactapplet");
715 }
716
717 d->compactRepresentationExpander->loadUrl(compactExpanderUrl);
718 }
719
720 d->initComplete = true;
721 d->compactRepresentationCheck();
722 qmlObject()->engine()->rootContext()->setBaseUrl(qmlObject()->source());
723
724 // if we're expanded we don't care about preloading because it will already be the case
725 // as well as for containments
726 if (d->applet->isContainment() || d->expanded || d->preferredRepresentation == d->fullRepresentation) {
727 return;
728 }
729
730 if (!d->applet->isContainment() && d->applet->containment()) {
731 connect(d->applet->containment(), &Plasma::Containment::uiReadyChanged, this, [this](bool uiReady) {
732 if (uiReady && d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive) {
733 const int preloadWeight = d->preloadWeight();
734 qCDebug(LOG_PLASMAQUICK) << "New Applet " << d->applet->title() << "with a weight of" << preloadWeight;
735
736 // don't preload applets less then a certain weight
737 if (d->s_preloadPolicy >= AppletQuickItemPrivate::Aggressive || preloadWeight >= AppletQuickItemPrivate::DelayedPreloadWeight) {
738 // spread the creation over a random delay to make it look
739 // plasma started already, and load the popup in the background
740 // without big noticeable freezes, the bigger the weight the smaller is likely
741 // to be the delay, smaller minimum walue, smaller spread
742 const int min = (100 - preloadWeight) * 20;
743 const int max = (100 - preloadWeight) * 100;
744 const int delay = QRandomGenerator::global()->bounded((max + 1) - min) + min;
745 QTimer::singleShot(delay, this, [this, delay]() {
746 qCDebug(LOG_PLASMAQUICK) << "Delayed preload of " << d->applet->title() << "after" << (qreal)delay / 1000 << "seconds";
747 d->preloadForExpansion();
748 });
749 }
750 }
751 });
752 }
753}
754
755void AppletQuickItem::classBegin()
756{
758 AppletContext *ac = qobject_cast<AppletContext *>(QQmlEngine::contextForObject(this)->parentContext());
759 if (!ac) {
760 qCWarning(LOG_PLASMAQUICK) << "Detected a PlasmoidItem which is not the root QML item: this is not supported.";
761 return;
762 }
763 d->applet = ac->applet();
764 d->qmlObject = ac->sharedQmlEngine();
765}
766
767void AppletQuickItem::componentComplete()
768{
770 init();
771}
772
773int AppletQuickItem::switchWidth() const
774{
775 return d->switchWidth;
776}
777
778void AppletQuickItem::setSwitchWidth(int width)
779{
780 if (d->switchWidth == width) {
781 return;
782 }
783
784 d->switchWidth = width;
785 d->compactRepresentationCheck();
786 Q_EMIT switchWidthChanged(width);
787}
788
789int AppletQuickItem::switchHeight() const
790{
791 return d->switchHeight;
792}
793
794void AppletQuickItem::setSwitchHeight(int height)
795{
796 if (d->switchHeight == height) {
797 return;
798 }
799
800 d->switchHeight = height;
801 d->compactRepresentationCheck();
802 Q_EMIT switchHeightChanged(height);
803}
804
805QQmlComponent *AppletQuickItem::compactRepresentation()
806{
807 return d->compactRepresentation;
808}
809
810void AppletQuickItem::setCompactRepresentation(QQmlComponent *component)
811{
812 if (d->compactRepresentation == component) {
813 return;
814 }
815
816 d->compactRepresentation = component;
817 Q_EMIT compactRepresentationChanged(component);
818}
819
820QQmlComponent *AppletQuickItem::fullRepresentation()
821{
822 return d->fullRepresentation;
823}
824
825void AppletQuickItem::setFullRepresentation(QQmlComponent *component)
826{
827 if (d->fullRepresentation == component) {
828 return;
829 }
830
831 d->fullRepresentation = component;
832 Q_EMIT fullRepresentationChanged(component);
833}
834
835QQmlComponent *AppletQuickItem::preferredRepresentation()
836{
837 return d->preferredRepresentation;
838}
839
840void AppletQuickItem::setPreferredRepresentation(QQmlComponent *component)
841{
842 if (d->preferredRepresentation == component) {
843 return;
844 }
845
846 d->preferredRepresentation = component;
847 Q_EMIT preferredRepresentationChanged(component);
848 d->compactRepresentationCheck();
849}
850
851bool AppletQuickItem::isExpanded() const
852{
853 return d->applet->isContainment() || !d->fullRepresentation || d->expanded;
854}
855
856void AppletQuickItem::setExpanded(bool expanded)
857{
858 if (d->expanded == expanded) {
859 return;
860 }
861
862 if (expanded) {
863 d->preloadForExpansion();
864 // increase on open, ignore containments
865 if (d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive && !d->applet->isContainment()) {
866 const int newWeight = qMin(d->preloadWeight() + AppletQuickItemPrivate::PreloadWeightIncrement, 100);
867 d->applet->config().writeEntry(QStringLiteral("PreloadWeight"), newWeight);
868 qCDebug(LOG_PLASMAQUICK) << "Increasing score for" << d->applet->title() << "to" << newWeight;
869 }
870 }
871
872 d->expanded = expanded;
873
874 Q_EMIT expandedChanged(expanded);
875}
876
877bool AppletQuickItem::isActivationTogglesExpanded() const
878{
879 return d->activationTogglesExpanded;
880}
881
882void AppletQuickItem::setActivationTogglesExpanded(bool activationTogglesExpanded)
883{
884 if (d->activationTogglesExpanded == activationTogglesExpanded) {
885 return;
886 }
887 d->activationTogglesExpanded = activationTogglesExpanded;
888 Q_EMIT activationTogglesExpandedChanged(activationTogglesExpanded);
889}
890
891bool AppletQuickItem::hideOnWindowDeactivate() const
892{
893 return d->hideOnWindowDeactivate;
894}
895
896void AppletQuickItem::setHideOnWindowDeactivate(bool hide)
897{
898 if (d->hideOnWindowDeactivate == hide) {
899 return;
900 }
901 d->hideOnWindowDeactivate = hide;
902 Q_EMIT hideOnWindowDeactivateChanged(hide);
903}
904
905bool AppletQuickItem::preloadFullRepresentation() const
906{
907 return d->preloadFullRepresentation;
908}
909
910void AppletQuickItem::setPreloadFullRepresentation(bool preload)
911{
912 if (d->preloadFullRepresentation == preload) {
913 return;
914 }
915
916 d->preloadFullRepresentation = preload;
917 d->createFullRepresentationItem();
918
919 Q_EMIT preloadFullRepresentationChanged(preload);
920}
921
922////////////Internals
923
924PlasmaQuick::SharedQmlEngine *AppletQuickItem::qmlObject()
925{
926 return d->qmlObject;
927}
928
929QQuickItem *AppletQuickItem::compactRepresentationItem()
930{
931 return d->compactRepresentationItem;
932}
933
934QQuickItem *AppletQuickItem::fullRepresentationItem()
935{
936 return d->fullRepresentationItem;
937}
938
939void AppletQuickItem::childEvent(QChildEvent *event)
940{
941 // Added child may be QQuickLayoutAttached
942 if (event->added() && !d->ownLayout && d->currentRepresentationItem) {
943 // Child has not yet finished initialization at this point
944 QTimer::singleShot(0, this, [this]() {
945 if (!d->ownLayout) {
946 d->connectLayoutAttached(d->currentRepresentationItem);
947 }
948 });
949 }
950
952}
953
954void AppletQuickItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
955{
956 QQuickItem::geometryChange(newGeometry, oldGeometry);
957 d->compactRepresentationCheck();
958}
959}
960
961#include "moc_appletquickitem.cpp"
bool isNull() const
QUrl fileUrl(const QByteArray &key, const QString &filename=QString()) const
QString pluginId() const
bool value(QStringView key, bool defaultValue) const
QString name() const
An object that instantiates an entire QML context, with its own declarative engine.
The base Applet class.
Definition applet.h:64
void updateConstraints(Constraints constraints=AllConstraints)
Called when any of the geometry constraints have been updated.
Definition applet.cpp:266
void setHasConfigurationInterface(bool hasInterface)
Sets whether or not this applet provides a user interface for configuring the applet.
Definition applet.cpp:780
bool failedToLaunch() const
If for some reason, the applet fails to get up on its feet (the library couldn't be loaded,...
Definition applet.cpp:462
@ UiReadyConstraint
The ui has been completely loaded.
Definition applet.h:220
bool isContainment
True if this applet is a Containment and is acting as one, such as a desktop or a panel.
Definition applet.h:200
QString translationDomain() const
The translation domain for this applet.
Definition applet.cpp:877
void appletDeleted(Plasma::Applet *applet)
Emitted when the applet is deleted.
void setLaunchErrorMessage(const QString &reason=QString())
Call this method when the applet fails to launch properly.
Definition applet.cpp:169
bool destroyed() const
Definition applet.cpp:233
Plasma::Containment * containment
The Containment managing this applet.
Definition applet.h:189
void flushPendingConstraintsEvents()
Sends all pending constraints updates to the applet.
Definition applet.cpp:533
KPluginMetaData pluginMetaData() const
Definition applet.cpp:391
QString launchErrorMessage() const
If for some reason, the applet fails to get up on its feet (the library couldn't be loaded,...
Definition applet.cpp:457
The base class for plugins that provide backgrounds and applet grouping containers.
Definition containment.h:47
void uiReadyChanged(bool uiReady)
Emitted when the ui has been fully loaded and is fully working.
Plasma::Corona * corona
The corona for this contaiment.
Definition containment.h:59
@ Vertical
The applet is constrained horizontally, but can expand vertically.
Definition plasma.h:53
@ Horizontal
The applet is constrained vertically, but can expand horizontally.
Definition plasma.h:51
QString i18n(const char *text, const TYPE &arg...)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
KCOREADDONS_EXPORT QString versionString()
KDB_EXPORT KDbVersionInfo version()
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
void errorList(QWidget *parent, const QString &text, const QStringList &strlist, const QString &title=QString(), Options options=Notify)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
The EdgeEventForwarder class This class forwards edge events to be replayed within the given margin T...
Definition action.h:20
const char * constData() const const
qint64 currentMSecsSinceEpoch()
const_iterator constFind(const Key &key) const const
bool contains(const Key &key) const const
bool remove(const Key &key)
QJsonArray fromStringList(const QStringList &list)
T value(qsizetype i) const const
QLoggingCategory * defaultCategory()
virtual void childEvent(QChildEvent *event)
const QObjectList & children() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QVariant property(const char *name) const const
bool setProperty(const char *name, QVariant &&value)
void setBaseUrl(const QUrl &baseUrl)
QQmlContext * contextForObject(const QObject *object)
QQmlContext * rootContext() const const
bool write(QObject *object, const QString &name, const QVariant &value)
virtual void classBegin() override
virtual void componentComplete() override
virtual void geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
QQuickItem * parentItem() const const
void setVisible(bool)
qreal height() const const
qreal width() const const
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
bool isEmpty() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
CaseInsensitive
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool isEmpty() const const
QString toString(FormattingOptions options) const const
QVariant fromValue(T &&value)
qreal toReal(bool *ok) const const
QVersionNumber fromString(QAnyStringView string, qsizetype *suffixIndex)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 4 2024 16:34:35 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.