diff --git a/autotests/coronatest.cpp b/autotests/coronatest.cpp index 9504c43c9..d52caabfa 100644 --- a/autotests/coronatest.cpp +++ b/autotests/coronatest.cpp @@ -1,256 +1,251 @@ /******************************************************************************** * Copyright 2014 Marco Martin * * * * This library 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 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public License * * along with this library; see the file COPYING.LIB. If not, write to * * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301, USA. * *********************************************************************************/ #include "coronatest.h" #include #include #include #include #include #include +#include #include Plasma::Applet *SimpleLoader::internalLoadApplet(const QString &name, uint appletId, const QVariantList &args) { Q_UNUSED(args) if (name == QLatin1String("simpleapplet")) { return new SimpleApplet(nullptr, QString(), appletId); } else if (name == QLatin1String("simplecontainment")) { return new SimpleContainment(nullptr, QString(), appletId); } else if (name == QLatin1String("simplenoscreencontainment")) { return new SimpleNoScreenContainment(nullptr, QString(), appletId); } else { return nullptr; } } SimpleCorona::SimpleCorona(QObject *parent) : Plasma::Corona(parent) { Plasma::PluginLoader::setPluginLoader(new SimpleLoader); } SimpleCorona::~SimpleCorona() {} QRect SimpleCorona::screenGeometry(int screen) const { //completely arbitrary, still not tested return QRect(100*screen, 100, 100, 100); } int SimpleCorona::screenForContainment(const Plasma::Containment *c) const { if (qobject_cast(c)) { return -1; } return 0; } SimpleApplet::SimpleApplet(QObject *parent , const QString &serviceId, uint appletId) : Plasma::Applet(parent, serviceId, appletId) { - QTime time = QTime::currentTime(); - qsrand((uint)time.msec()); - //updateConstraints(Plasma::Types::UiReadyConstraint); m_timer.setSingleShot(true); - m_timer.setInterval(qrand() % ((500 + 1) - 100) + 100); + m_timer.setInterval(QRandomGenerator::global()->bounded((500 + 1) - 100) + 100); m_timer.start(); connect(&m_timer, &QTimer::timeout, [=]() { updateConstraints(Plasma::Types::UiReadyConstraint); }); } SimpleContainment::SimpleContainment(QObject *parent , const QString &serviceId, uint appletId) : Plasma::Containment(parent, serviceId, appletId) { - QTime time = QTime::currentTime(); - qsrand((uint)time.msec()); - //updateConstraints(Plasma::Types::UiReadyConstraint); m_timer.setSingleShot(true); - m_timer.setInterval(qrand() % ((500 + 1) - 100) + 100); + m_timer.setInterval(QRandomGenerator::global()->bounded((500 + 1) - 100) + 100); m_timer.start(); connect(&m_timer, &QTimer::timeout, [=]() { updateConstraints(Plasma::Types::UiReadyConstraint); }); } SimpleNoScreenContainment::SimpleNoScreenContainment(QObject *parent , const QString &serviceId, uint appletId) : Plasma::Containment(parent, serviceId, appletId) { //This containment will *never* be isUiReady() } static void runKBuildSycoca() { QProcess proc; const QString kbuildsycoca = QStandardPaths::findExecutable(QStringLiteral(KBUILDSYCOCA_EXENAME)); QVERIFY(!kbuildsycoca.isEmpty()); QStringList args; args << QStringLiteral("--testmode"); proc.setProcessChannelMode(QProcess::MergedChannels); // silence kbuildsycoca output proc.start(kbuildsycoca, args); QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList))); QVERIFY(spy.wait(10000)); proc.waitForFinished(); QCOMPARE(proc.exitStatus(), QProcess::NormalExit); } void CoronaTest::initTestCase() { if (!KSycoca::isAvailable()) { runKBuildSycoca(); } QStandardPaths::setTestModeEnabled(true); m_corona = new SimpleCorona; m_configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)); m_configDir.removeRecursively(); QVERIFY(m_configDir.mkpath(QStringLiteral("."))); QVERIFY(QFile::copy(QStringLiteral(":/plasma-test-appletsrc"), m_configDir.filePath(QStringLiteral("plasma-test-appletsrc")))); } void CoronaTest::cleanupTestCase() { m_configDir.removeRecursively(); delete m_corona; } void CoronaTest::restore() { m_corona->loadLayout(QStringLiteral("plasma-test-appletsrc")); QCOMPARE(m_corona->containments().count(), 3); foreach (auto cont, m_corona->containments()) { switch (cont->id()) { case 1: QCOMPARE(cont->applets().count(), 2); break; default: QCOMPARE(cont->applets().count(), 0); break; } } } void CoronaTest::checkOrder() { QCOMPARE(m_corona->containments().count(), 3); //check containments order QCOMPARE(m_corona->containments().at(0)->id(), (uint)1); QCOMPARE(m_corona->containments().at(1)->id(), (uint)4); QCOMPARE(m_corona->containments().at(2)->id(), (uint)5); //check applets order QCOMPARE(m_corona->containments().at(0)->applets().count(), 2); QCOMPARE(m_corona->containments().at(0)->applets().at(0)->id(), (uint)2); QCOMPARE(m_corona->containments().at(0)->applets().at(1)->id(), (uint)3); } void CoronaTest::startupCompletion() { QVERIFY(!m_corona->isStartupCompleted()); QVERIFY(!m_corona->containments().at(0)->isUiReady()); QSignalSpy spy(m_corona, SIGNAL(startupCompleted())); QVERIFY(spy.wait(1000)); QVERIFY(m_corona->isStartupCompleted()); QVERIFY(m_corona->containments().at(0)->isUiReady()); } void CoronaTest::addRemoveApplets() { m_corona->containments().at(0)->createApplet(QStringLiteral("invalid")); QCOMPARE(m_corona->containments().at(0)->applets().count(), 3); //remove action present QVERIFY(m_corona->containments().at(0)->applets().at(0)->actions()->action(QStringLiteral("remove"))); //kill an applet m_corona->containments().at(0)->applets().at(0)->destroy(); QSignalSpy spy(m_corona->containments().at(0)->applets().at(0), SIGNAL(destroyed())); QVERIFY(spy.wait(1000)); QCOMPARE(m_corona->containments().at(0)->applets().count(), 2); } //this test has to be the last, since systemimmutability //can't be programmatically unlocked void CoronaTest::immutability() { //immutability QCOMPARE(m_corona->immutability(), Plasma::Types::Mutable); m_corona->setImmutability(Plasma::Types::UserImmutable); QCOMPARE(m_corona->immutability(), Plasma::Types::UserImmutable); foreach (Plasma::Containment *cont, m_corona->containments()) { QCOMPARE(cont->immutability(), Plasma::Types::UserImmutable); foreach (Plasma::Applet *app, cont->applets()) { QCOMPARE(app->immutability(), Plasma::Types::UserImmutable); } } m_corona->setImmutability(Plasma::Types::Mutable); QCOMPARE(m_corona->immutability(), Plasma::Types::Mutable); foreach (Plasma::Containment *cont, m_corona->containments()) { QCOMPARE(cont->immutability(), Plasma::Types::Mutable); foreach (Plasma::Applet *app, cont->applets()) { QCOMPARE(app->immutability(), Plasma::Types::Mutable); } } m_corona->setImmutability(Plasma::Types::SystemImmutable); QCOMPARE(m_corona->immutability(), Plasma::Types::SystemImmutable); foreach (Plasma::Containment *cont, m_corona->containments()) { QCOMPARE(cont->immutability(), Plasma::Types::SystemImmutable); foreach (Plasma::Applet *app, cont->applets()) { QCOMPARE(app->immutability(), Plasma::Types::SystemImmutable); } } //can't unlock systemimmutable m_corona->setImmutability(Plasma::Types::Mutable); QCOMPARE(m_corona->immutability(), Plasma::Types::SystemImmutable); foreach (Plasma::Containment *cont, m_corona->containments()) { QCOMPARE(cont->immutability(), Plasma::Types::SystemImmutable); foreach (Plasma::Applet *app, cont->applets()) { QCOMPARE(app->immutability(), Plasma::Types::SystemImmutable); } } } QTEST_MAIN(CoronaTest) diff --git a/src/plasma/datacontainer.cpp b/src/plasma/datacontainer.cpp index 0c083cebf..85a96f0a2 100644 --- a/src/plasma/datacontainer.cpp +++ b/src/plasma/datacontainer.cpp @@ -1,423 +1,422 @@ /* * Copyright 2006-2007 Aaron Seigo * * 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 "datacontainer.h" #include "private/datacontainer_p.h" #include "private/storage_p.h" #include #include +#include #include "plasma.h" #include "debug_p.h" namespace Plasma { DataContainer::DataContainer(QObject *parent) : QObject(parent), d(new DataContainerPrivate(this)) { } DataContainer::~DataContainer() { delete d; } const DataEngine::Data DataContainer::data() const { return d->data; } void DataContainer::setData(const QString &key, const QVariant &value) { if (!value.isValid()) { d->data.remove(key); } else { d->data.insert(key, value); } d->dirty = true; d->updateTimer.start(); //check if storage is enabled and if storage is needed. //If it is not set to be stored,then this is the first //setData() since the last time it was stored. This //gives us only one singleShot timer. if (isStorageEnabled() || !needsToBeStored()) { d->storageTimer.start(180000, this); } setNeedsToBeStored(true); } void DataContainer::setModel(QAbstractItemModel *model) { if (d->model.data() == model) { return; } if (d->model) { d->model.data()->deleteLater(); } d->model = model; model->setParent(this); emit modelChanged(objectName(), model); } QAbstractItemModel *DataContainer::model() { return d->model.data(); } void DataContainer::removeAllData() { if (d->data.isEmpty()) { // avoid an update if we don't have any data anyways return; } d->data.clear(); d->dirty = true; d->updateTimer.start(); } bool DataContainer::visualizationIsConnected(QObject *visualization) const { return d->relayObjects.contains(visualization); } void DataContainer::connectVisualization(QObject *visualization, uint pollingInterval, Plasma::Types::IntervalAlignment alignment) { //qCDebug(LOG_PLASMA) << "connecting visualization" <::iterator objIt = d->relayObjects.find(visualization); bool connected = objIt != d->relayObjects.end(); if (connected) { // this visualization is already connected. just adjust the update // frequency if necessary SignalRelay *relay = objIt.value(); if (relay) { // connected to a relay //qCDebug(LOG_PLASMA) << " already connected, but to a relay"; if (relay->m_interval == pollingInterval && relay->m_align == alignment) { //qCDebug(LOG_PLASMA) << " already connected to a relay of the same interval of" // << pollingInterval << ", nothing to do"; return; } if (relay->receiverCount() == 1) { //qCDebug(LOG_PLASMA) << " removing relay, as it is now unused"; d->relays.remove(relay->m_interval); delete relay; } else { if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } //modelChanged is always emitted by the dataSource since there is no polling there if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } //relay->isUnused(); } } else if (pollingInterval < 1) { // the visualization was connected already, but not to a relay // and it still doesn't want to connect to a relay, so we have // nothing to do! //qCDebug(LOG_PLASMA) << " already connected, nothing to do"; return; } else { if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } } else { connect(visualization, &QObject::destroyed, this, &DataContainer::disconnectVisualization); //, Qt::QueuedConnection); } if (pollingInterval < 1) { //qCDebug(LOG_PLASMA) << " connecting directly"; d->relayObjects[visualization] = nullptr; if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { connect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } else { //qCDebug(LOG_PLASMA) << " connecting to a relay"; // we only want to do an immediate update if this is not the first object to connect to us // if it is the first visualization, then the source will already have been populated // engine's sourceRequested method bool immediateUpdate = connected || d->relayObjects.count() > 1; SignalRelay *relay = d->signalRelay(this, visualization, pollingInterval, alignment, immediateUpdate); if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { connect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } //modelChanged is always emitted by the dataSource since there is no polling there if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { connect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } } void DataContainer::setStorageEnabled(bool store) { - QTime time = QTime::currentTime(); - qsrand((uint)time.msec()); d->enableStorage = store; if (store) { - QTimer::singleShot(qrand() % (2000 + 1), this, SLOT(retrieve())); + QTimer::singleShot(QRandomGenerator::global()->bounded(2000 + 1), this, SLOT(retrieve())); } } bool DataContainer::isStorageEnabled() const { return d->enableStorage; } bool DataContainer::needsToBeStored() const { return !d->isStored; } void DataContainer::setNeedsToBeStored(bool store) { d->isStored = !store; } DataEngine *DataContainer::getDataEngine() { QObject *o = this; DataEngine *de = nullptr; while (de == nullptr) { o = dynamic_cast(o->parent()); if (o == nullptr) { return nullptr; } de = dynamic_cast(o); } return de; } void DataContainerPrivate::store() { if (!q->needsToBeStored() || !q->isStorageEnabled()) { return; } DataEngine *de = q->getDataEngine(); if (!de) { return; } q->setNeedsToBeStored(false); if (!storage) { storage = new Storage(q); } QVariantMap op = storage->operationDescription(QStringLiteral("save")); op[QStringLiteral("group")] = q->objectName(); StorageJob *job = static_cast(storage->startOperationCall(op)); job->setData(data); storageCount++; QObject::connect(job, SIGNAL(finished(KJob*)), q, SLOT(storeJobFinished(KJob*))); } void DataContainerPrivate::storeJobFinished(KJob *) { --storageCount; if (storageCount < 1) { storage->deleteLater(); storage = nullptr; } } void DataContainerPrivate::retrieve() { DataEngine *de = q->getDataEngine(); if (de == nullptr) { return; } if (!storage) { storage = new Storage(q); } QVariantMap retrieveGroup = storage->operationDescription(QStringLiteral("retrieve")); retrieveGroup[QStringLiteral("group")] = q->objectName(); ServiceJob *retrieveJob = storage->startOperationCall(retrieveGroup); QObject::connect(retrieveJob, SIGNAL(result(KJob*)), q, SLOT(populateFromStoredData(KJob*))); } void DataContainerPrivate::populateFromStoredData(KJob *job) { if (job->error()) { return; } StorageJob *ret = dynamic_cast(job); if (!ret) { return; } // Only fill the source with old stored // data if it is not already populated with new data. if (data.isEmpty() && !ret->data().isEmpty()) { data = ret->data(); dirty = true; q->forceImmediateUpdate(); } QVariantMap expireGroup = storage->operationDescription(QStringLiteral("expire")); //expire things older than 4 days expireGroup[QStringLiteral("age")] = 345600; storage->startOperationCall(expireGroup); } void DataContainer::disconnectVisualization(QObject *visualization) { QMap::iterator objIt = d->relayObjects.find(visualization); disconnect(visualization, &QObject::destroyed, this, &DataContainer::disconnectVisualization); //, Qt::QueuedConnection); if (objIt == d->relayObjects.end() || !objIt.value()) { // it is connected directly to the DataContainer itself if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } else { SignalRelay *relay = objIt.value(); if (relay->receiverCount() == 1) { d->relays.remove(relay->m_interval); delete relay; } else { if (visualization->metaObject()->indexOfSlot("dataUpdated(QString,Plasma::DataEngine::Data)") >= 0) { disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); } //modelChanged is always emitted by the dataSource since there is no polling there if (visualization->metaObject()->indexOfSlot("modelChanged(QString,QAbstractItemModel*)") >= 0) { disconnect(this, SIGNAL(modelChanged(QString,QAbstractItemModel*)), visualization, SLOT(modelChanged(QString,QAbstractItemModel*))); } } } d->relayObjects.erase(objIt); d->checkUsage(); } void DataContainer::checkForUpdate() { //qCDebug(LOG_PLASMA) << objectName() << d->dirty; if (d->dirty) { emit dataUpdated(objectName(), d->data); foreach (SignalRelay *relay, d->relays) { relay->checkQueueing(); } d->dirty = false; } } void DataContainer::forceImmediateUpdate() { if (d->dirty) { d->dirty = false; emit dataUpdated(objectName(), d->data); } foreach (SignalRelay *relay, d->relays) { relay->forceImmediateUpdate(); } } uint DataContainer::timeSinceLastUpdate() const { return d->updateTimer.elapsed(); } void DataContainer::setNeedsUpdate(bool update) { d->cached = update; } bool DataContainer::isUsed() const { return !d->relays.isEmpty() || receivers(SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data))) > 0; } void DataContainerPrivate::checkUsage() { if (!checkUsageTimer.isActive()) { checkUsageTimer.start(10, q); } } void DataContainer::timerEvent(QTimerEvent *event) { if (event->timerId() == d->checkUsageTimer.timerId()) { if (!isUsed()) { // DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED! //qCDebug(LOG_PLASMA) << objectName() << "is unused"; //NOTE: Notifying visualization of the model destruction before actual deletion avoids crashes in some edge cases if (d->model) { d->model.clear(); emit modelChanged(objectName(), nullptr); } emit becameUnused(objectName()); } d->checkUsageTimer.stop(); } else if (event->timerId() == d->storageTimer.timerId()) { d->store(); d->storageTimer.stop(); } } } // Plasma namespace #include "moc_datacontainer.cpp" diff --git a/src/plasmaquick/appletquickitem.cpp b/src/plasmaquick/appletquickitem.cpp index 7e2c68ada..a4304534e 100644 --- a/src/plasmaquick/appletquickitem.cpp +++ b/src/plasmaquick/appletquickitem.cpp @@ -1,930 +1,931 @@ /* * Copyright 2014 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 "appletquickitem.h" #include "private/appletquickitem_p.h" #include "debug_p.h" #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include namespace PlasmaQuick { QHash AppletQuickItemPrivate::s_rootObjects = QHash(); AppletQuickItemPrivate::PreloadPolicy AppletQuickItemPrivate::s_preloadPolicy = AppletQuickItemPrivate::Uninitialized; AppletQuickItemPrivate::AppletQuickItemPrivate(Plasma::Applet *a, AppletQuickItem *item) : q(item), switchWidth(-1), switchHeight(-1), initComplete(false), applet(a), expanded(false), activationTogglesExpanded(false) { if (s_preloadPolicy == Uninitialized) { //default as Adaptive s_preloadPolicy = Adaptive; if (qEnvironmentVariableIsSet("PLASMA_PRELOAD_POLICY")) { const QString policy = QString::fromUtf8(qgetenv("PLASMA_PRELOAD_POLICY")).toLower(); if (policy == QLatin1String("aggressive")) { s_preloadPolicy = Aggressive; } else if (policy == QLatin1String("none")) { s_preloadPolicy = None; } } qCInfo(LOG_PLASMAQUICK) << "Applet preload policy set to" << s_preloadPolicy; } } void AppletQuickItemPrivate::init() { if (!applet->pluginMetaData().isValid()) { // This `qmlObject` is used in other parts of the code qmlObject = new KDeclarative::QmlObject(q); return; } qmlObject = new KDeclarative::QmlObjectSharedEngine(q); if (!qmlObject->engine()->urlInterceptor()) { PackageUrlInterceptor *interceptor = new PackageUrlInterceptor(qmlObject->engine(), KPackage::Package()); interceptor->setForcePlasmaStyle(true); qmlObject->engine()->setUrlInterceptor(interceptor); } } int AppletQuickItemPrivate::preloadWeight() const { int defaultWeight; const QStringList provides(KPluginMetaData::readStringList(applet->pluginMetaData().rawData(), QStringLiteral("X-Plasma-Provides"))); //some applet types we want a bigger weight if (provides.contains(QLatin1String("org.kde.plasma.launchermenu"))) { defaultWeight = DefaultLauncherPreloadWeight; } else { defaultWeight = DefaultPreloadWeight; } //default widgets to be barely preloaded return qBound(0, applet->config().readEntry(QStringLiteral("PreloadWeight"), qMax(defaultWeight, applet->pluginMetaData().rawData().value(QStringLiteral("X-Plasma-PreloadWeight")).toInt())), 100); } void AppletQuickItemPrivate::connectLayoutAttached(QObject *item) { QObject *layout = nullptr; //Extract the representation's Layout, if any //No Item? if (!item) { return; } //Search a child that has the needed Layout properties //HACK: here we are not type safe, but is the only way to access to a pointer of Layout foreach (QObject *child, item->children()) { //find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid() ) { layout = child; break; } } //if the compact repr doesn't export a Layout.* attached property, //reset our own with default values if (!layout) { if (ownLayout) { ownLayout->setProperty("minimumWidth", 0); ownLayout->setProperty("minimumHeight", 0); ownLayout->setProperty("preferredWidth", -1); ownLayout->setProperty("preferredHeight", -1); ownLayout->setProperty("maximumWidth", std::numeric_limits::infinity()); ownLayout->setProperty("maximumHeight", std::numeric_limits::infinity()); ownLayout->setProperty("fillWidth", false); ownLayout->setProperty("fillHeight", false); } return; } //propagate all the size hints propagateSizeHint("minimumWidth"); propagateSizeHint("minimumHeight"); propagateSizeHint("preferredWidth"); propagateSizeHint("preferredHeight"); propagateSizeHint("maximumWidth"); propagateSizeHint("maximumHeight"); propagateSizeHint("fillWidth"); propagateSizeHint("fillHeight"); QObject *ownLayout = nullptr; foreach (QObject *child, q->children()) { //find for the needed property of Layout: minimum/maximum/preferred sizes and fillWidth/fillHeight if (child->property("minimumWidth").isValid() && child->property("minimumHeight").isValid() && child->property("preferredWidth").isValid() && child->property("preferredHeight").isValid() && child->property("maximumWidth").isValid() && child->property("maximumHeight").isValid() && child->property("fillWidth").isValid() && child->property("fillHeight").isValid() ) { ownLayout = child; break; } } //this should never happen, since we ask to create it if doesn't exists if (!ownLayout) { return; } //if the representation didn't change, don't do anything if (representationLayout == layout) { return; } if (representationLayout) { QObject::disconnect(representationLayout, nullptr, q, nullptr); } //Here we can't use the new connect syntax because we can't link against QtQuick layouts QObject::connect(layout, SIGNAL(minimumWidthChanged()), q, SLOT(minimumWidthChanged())); QObject::connect(layout, SIGNAL(minimumHeightChanged()), q, SLOT(minimumHeightChanged())); QObject::connect(layout, SIGNAL(preferredWidthChanged()), q, SLOT(preferredWidthChanged())); QObject::connect(layout, SIGNAL(preferredHeightChanged()), q, SLOT(preferredHeightChanged())); QObject::connect(layout, SIGNAL(maximumWidthChanged()), q, SLOT(maximumWidthChanged())); QObject::connect(layout, SIGNAL(maximumHeightChanged()), q, SLOT(maximumHeightChanged())); QObject::connect(layout, SIGNAL(fillWidthChanged()), q, SLOT(fillWidthChanged())); QObject::connect(layout, SIGNAL(fillHeightChanged()), q, SLOT(fillHeightChanged())); representationLayout = layout; AppletQuickItemPrivate::ownLayout = ownLayout; propagateSizeHint("minimumWidth"); propagateSizeHint("minimumHeight"); propagateSizeHint("preferredWidth"); propagateSizeHint("preferredHeight"); propagateSizeHint("maximumWidth"); propagateSizeHint("maximumHeight"); propagateSizeHint("fillWidth"); propagateSizeHint("fillHeight"); } void AppletQuickItemPrivate::propagateSizeHint(const QByteArray &layoutProperty) { if (ownLayout && representationLayout) { ownLayout->setProperty(layoutProperty.constData(), representationLayout->property(layoutProperty.constData())); } } QQuickItem *AppletQuickItemPrivate::createCompactRepresentationItem() { if (!compactRepresentation) { return nullptr; } if (compactRepresentationItem) { return compactRepresentationItem; } QVariantHash initialProperties; initialProperties[QStringLiteral("parent")] = QVariant::fromValue(q); compactRepresentationItem = qobject_cast(qmlObject->createObjectFromComponent(compactRepresentation, QtQml::qmlContext(qmlObject->rootObject()), initialProperties)); emit q->compactRepresentationItemChanged(compactRepresentationItem); return compactRepresentationItem; } QQuickItem *AppletQuickItemPrivate::createFullRepresentationItem() { if (fullRepresentationItem) { return fullRepresentationItem; } if (fullRepresentation && fullRepresentation != qmlObject->mainComponent()) { QVariantHash initialProperties; initialProperties[QStringLiteral("parent")] = QVariant(); fullRepresentationItem = qobject_cast(qmlObject->createObjectFromComponent(fullRepresentation, QtQml::qmlContext(qmlObject->rootObject()), initialProperties)); } else { fullRepresentation = qmlObject->mainComponent(); fullRepresentationItem = qobject_cast(qmlObject->rootObject()); emit q->fullRepresentationChanged(fullRepresentation); } if (!fullRepresentationItem) { return nullptr; } emit q->fullRepresentationItemChanged(fullRepresentationItem); return fullRepresentationItem; } QQuickItem *AppletQuickItemPrivate::createCompactRepresentationExpanderItem() { if (!compactRepresentationExpander) { return nullptr; } if (compactRepresentationExpanderItem) { return compactRepresentationExpanderItem; } compactRepresentationExpanderItem = qobject_cast(qmlObject->createObjectFromComponent(compactRepresentationExpander, QtQml::qmlContext(qmlObject->rootObject()))); if (!compactRepresentationExpanderItem) { return nullptr; } compactRepresentationExpanderItem->setProperty("compactRepresentation", QVariant::fromValue(createCompactRepresentationItem())); return compactRepresentationExpanderItem; } bool AppletQuickItemPrivate::appletShouldBeExpanded() const { if (applet->isContainment()) { return true; } else { if (switchWidth > 0 && switchHeight > 0) { return q->width() > switchWidth && q->height() > switchHeight; //if a size to switch wasn't set, determine what representation to always chose } else { //preferred representation set? if (preferredRepresentation) { return preferredRepresentation == fullRepresentation; //Otherwise, base on FormFactor } else { return (applet->formFactor() != Plasma::Types::Horizontal && applet->formFactor() != Plasma::Types::Vertical); } } } } void AppletQuickItemPrivate::preloadForExpansion() { qint64 time = 0; if (QLoggingCategory::defaultCategory()->isInfoEnabled()) { time = QDateTime::currentMSecsSinceEpoch(); } createFullRepresentationItem(); // When not already expanded, also preload the expander if (!expanded && !applet->isContainment() && (!preferredRepresentation || preferredRepresentation != fullRepresentation)) { createCompactRepresentationExpanderItem(); } if (!appletShouldBeExpanded() && compactRepresentationExpanderItem) { compactRepresentationExpanderItem->setProperty("fullRepresentation", QVariant::fromValue(createFullRepresentationItem())); } else if (fullRepresentationItem) { fullRepresentationItem->setProperty("parent", QVariant::fromValue(q)); } //preallocate nodes if (fullRepresentationItem && fullRepresentationItem->window()) { fullRepresentationItem->window()->create(); } qCDebug(LOG_PLASMAQUICK) << "Applet" << applet->title() << "loaded after" << ( QDateTime::currentMSecsSinceEpoch() - time) << "msec"; } void AppletQuickItemPrivate::compactRepresentationCheck() { if (!initComplete) { return; } if (!qmlObject->rootObject()) { return; } //ignore 0 sizes; if (q->width() <= 0 || q->height() <= 0) { return; } bool full = appletShouldBeExpanded(); if ((full && fullRepresentationItem && fullRepresentationItem == currentRepresentationItem) || (!full && compactRepresentationItem && compactRepresentationItem == currentRepresentationItem) ) { return; } //Expanded if (full) { QQuickItem *item = createFullRepresentationItem(); if (item) { //unwire with the expander if (compactRepresentationExpanderItem) { compactRepresentationExpanderItem->setProperty("fullRepresentation", QVariant()); compactRepresentationExpanderItem->setProperty("compactRepresentation", QVariant()); compactRepresentationExpanderItem->setVisible(false); } item->setParentItem(q); { //set anchors QQmlExpression expr(QtQml::qmlContext(qmlObject->rootObject()), item, QStringLiteral("parent")); QQmlProperty prop(item, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } if (compactRepresentationItem) { compactRepresentationItem->setVisible(false); } currentRepresentationItem = item; connectLayoutAttached(item); expanded = true; emit q->expandedChanged(true); } //Icon } else { QQuickItem *compactItem = createCompactRepresentationItem(); QQuickItem *compactExpanderItem = createCompactRepresentationExpanderItem(); if (compactItem && compactExpanderItem) { //set the root item as the main visible item compactItem->setVisible(true); compactExpanderItem->setParentItem(q); compactExpanderItem->setVisible(true); { //set anchors QQmlExpression expr(QtQml::qmlContext(qmlObject->rootObject()), compactExpanderItem, QStringLiteral("parent")); QQmlProperty prop(compactExpanderItem, QStringLiteral("anchors.fill")); prop.write(expr.evaluate()); } if (fullRepresentationItem) { fullRepresentationItem->setProperty("parent", QVariant()); } compactExpanderItem->setProperty("compactRepresentation", QVariant::fromValue(compactItem)); //The actual full representation will be connected when created compactExpanderItem->setProperty("fullRepresentation", QVariant()); currentRepresentationItem = compactItem; connectLayoutAttached(compactItem); expanded = false; emit q->expandedChanged(false); } } } void AppletQuickItemPrivate::minimumWidthChanged() { propagateSizeHint("minimumWidth"); } void AppletQuickItemPrivate::minimumHeightChanged() { propagateSizeHint("minimumHeight"); } void AppletQuickItemPrivate::preferredWidthChanged() { propagateSizeHint("preferredWidth"); } void AppletQuickItemPrivate::preferredHeightChanged() { propagateSizeHint("preferredHeight"); } void AppletQuickItemPrivate::maximumWidthChanged() { propagateSizeHint("maximumWidth"); } void AppletQuickItemPrivate::maximumHeightChanged() { propagateSizeHint("maximumHeight"); } void AppletQuickItemPrivate::fillWidthChanged() { propagateSizeHint("fillWidth"); } void AppletQuickItemPrivate::fillHeightChanged() { propagateSizeHint("fillHeight"); } AppletQuickItem::AppletQuickItem(Plasma::Applet *applet, QQuickItem *parent) : QQuickItem(parent), d(new AppletQuickItemPrivate(applet, this)) { d->init(); if (d->applet) { d->appletPackage = d->applet->kPackage(); if (d->applet->containment()) { if (d->applet->containment()->corona()) { d->coronaPackage = d->applet->containment()->corona()->kPackage(); } d->containmentPackage = d->applet->containment()->kPackage(); } if (d->applet->pluginMetaData().isValid()) { const QString rootPath = d->applet->pluginMetaData().value(QStringLiteral("X-Plasma-RootPath")); if (!rootPath.isEmpty()) { d->qmlObject->setTranslationDomain(QLatin1String("plasma_applet_") + rootPath); } else { d->qmlObject->setTranslationDomain(QLatin1String("plasma_applet_") + d->applet->pluginMetaData().pluginId()); } } // set the graphicObject dynamic property on applet d->applet->setProperty("_plasma_graphicObject", QVariant::fromValue(this)); } d->qmlObject->setInitializationDelayed(true); setProperty("_plasma_applet", QVariant::fromValue(d->applet)); } AppletQuickItem::~AppletQuickItem() { //decrease weight if (d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive) { d->applet->config().writeEntry(QStringLiteral("PreloadWeight"), qMax(0, d->preloadWeight() - AppletQuickItemPrivate::PreloadWeightDecrement)); } //Here the order is important delete d->compactRepresentationItem; delete d->fullRepresentationItem; delete d->compactRepresentationExpanderItem; AppletQuickItemPrivate::s_rootObjects.remove(d->qmlObject->rootContext()); delete d; } AppletQuickItem *AppletQuickItem::qmlAttachedProperties(QObject *object) { QQmlContext *context; //is it using shared engine mode? if (!QtQml::qmlEngine(object)->parent()) { context = QtQml::qmlContext(object); //search the root context of the applet in which the object is in while (context) { //the rootcontext of an applet is a child of the engine root context if (context->parentContext() == QtQml::qmlEngine(object)->rootContext()) { break; } context = context->parentContext(); } //otherwise index by root context } else { context = QtQml::qmlEngine(object)->rootContext(); } //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 if (!object->parent() && AppletQuickItemPrivate::s_rootObjects.contains(context)) { return AppletQuickItemPrivate::s_rootObjects.value(context); } else { return nullptr; } } Plasma::Applet *AppletQuickItem::applet() const { return d->applet; } void AppletQuickItem::init() { //FIXME: Plasmoid attached property should be fixed since can't be indexed by engine anymore if (AppletQuickItemPrivate::s_rootObjects.contains(d->qmlObject->rootContext())) { return; } AppletQuickItemPrivate::s_rootObjects[d->qmlObject->rootContext()] = this; Q_ASSERT(d->applet); //Initialize the main QML file QQmlEngine *engine = d->qmlObject->engine(); //if the engine of the qmlObject is different from the static one, then we //are using an old version of the api in which every applet had one engine //so initialize a private url interceptor if (d->applet->kPackage().isValid() && !qobject_cast(d->qmlObject)) { PackageUrlInterceptor *interceptor = new PackageUrlInterceptor(engine, d->applet->kPackage()); interceptor->addAllowedPath(d->coronaPackage.path()); engine->setUrlInterceptor(interceptor); } //Force QtQuickControls to use the "Plasma" style for this engine. //this way is possible to mix QtQuickControls and plasma components in applets //while still having the desktop style in configuration dialogs if (!engine->property("_plasma_qqc_style_set").toBool()) { QQmlComponent c(engine); c.setData(QByteArrayLiteral("import QtQuick 2.1\n\ import QtQuick.Controls 1.0\n\ import QtQuick.Controls.Private 1.0\n \ QtObject {\ Component.onCompleted: {\ Settings.styleName = \"Plasma\";\ }\ }"), QUrl()); QObject *o = c.create(); o->deleteLater(); engine->setProperty(("_plasma_qqc_style_set"), true); } d->qmlObject->setSource(d->applet->kPackage().fileUrl("mainscript")); if (!engine || !engine->rootContext() || !engine->rootContext()->isValid() || !d->qmlObject->mainComponent() || d->qmlObject->mainComponent()->isError() || d->applet->failedToLaunch()) { QString reason; if (d->applet->failedToLaunch()) { reason = d->applet->launchErrorMessage(); } else if (d->applet->kPackage().isValid()) { foreach (QQmlError error, d->qmlObject->mainComponent()->errors()) { reason += error.toString() + QLatin1Char('\n'); } reason = i18n("Error loading QML file: %1", reason); } else { reason = i18n("Error loading Applet: package inexistent. %1", applet()->launchErrorMessage()); } d->qmlObject->setSource(d->coronaPackage.fileUrl("appleterror")); d->qmlObject->completeInitialization(); //even the error message QML may fail if (d->qmlObject->mainComponent()->isError()) { return; } else { d->qmlObject->rootObject()->setProperty("reason", reason); } d->applet->setLaunchErrorMessage(reason); } d->qmlObject->rootContext()->setContextProperty(QStringLiteral("plasmoid"), this); //initialize size, so an useless resize less QVariantHash initialProperties; //initialize with our size only if valid if (width() > 0 && height() > 0) { const qreal w = parentItem() ? std::min(parentItem()->width(), width()) : width(); const qreal h = parentItem() ? std::min(parentItem()->height(), height()) : height(); initialProperties[QStringLiteral("width")] = w; initialProperties[QStringLiteral("height")] = h; } d->qmlObject->setInitializationDelayed(false); d->qmlObject->completeInitialization(initialProperties); //otherwise, initialize our size to root object's size if (d->qmlObject->rootObject() && (width() <= 0 || height() <= 0)) { const qreal w = d->qmlObject->rootObject()->property("width").value(); const qreal h = d->qmlObject->rootObject()->property("height").value(); setSize(parentItem() ? QSizeF(std::min(parentItem()->width(), w), std::min(parentItem()->height(), h)) : QSizeF(w, h)); } //default fullrepresentation is our root main component, if none specified if (!d->fullRepresentation) { d->fullRepresentation = d->qmlObject->mainComponent(); d->fullRepresentationItem = qobject_cast(d->qmlObject->rootObject()); emit fullRepresentationChanged(d->fullRepresentation); } //default compactRepresentation is a simple icon provided by the shell package if (!d->compactRepresentation) { d->compactRepresentation = new QQmlComponent(engine, this); d->compactRepresentation->loadUrl(d->coronaPackage.fileUrl("defaultcompactrepresentation")); emit compactRepresentationChanged(d->compactRepresentation); } //default compactRepresentationExpander is the popup in which fullRepresentation goes if (!d->compactRepresentationExpander) { d->compactRepresentationExpander = new QQmlComponent(engine, this); QUrl compactExpanderUrl = d->containmentPackage.fileUrl("compactapplet"); if (compactExpanderUrl.isEmpty()) { compactExpanderUrl = d->coronaPackage.fileUrl("compactapplet"); } d->compactRepresentationExpander->loadUrl(compactExpanderUrl); } d->initComplete = true; d->compactRepresentationCheck(); qmlObject()->engine()->rootContext()->setBaseUrl(qmlObject()->source()); qmlObject()->engine()->setContextForObject(this, qmlObject()->engine()->rootContext()); //if we're expanded we don't care about preloading because it will already be the case //as well as for containments if (d->applet->isContainment() || d->expanded || d->preferredRepresentation == d->fullRepresentation) { return; } if (!d->applet->isContainment() && d->applet->containment()) { connect(d->applet->containment(), &Plasma::Containment::uiReadyChanged, this, [this](bool uiReady) { if (uiReady && d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive) { const int preloadWeight = d->preloadWeight(); qCDebug(LOG_PLASMAQUICK) << "New Applet " << d->applet->title() << "with a weight of" << preloadWeight; //don't preload applets less then a certain weight if (d->s_preloadPolicy >= AppletQuickItemPrivate::Aggressive || preloadWeight >= AppletQuickItemPrivate::DelayedPreloadWeight) { //spread the creation over a random delay to make it look //plasma started already, and load the popup in the background //without big noticeable freezes, the bigger the weight the smaller is likely //to be the delay, smaller minimum walue, smaller spread const int min = (100 - preloadWeight) * 20; const int max = (100 - preloadWeight) * 100; - const int delay = qrand() % ((max + 1) - min) + min; + const int delay = QRandomGenerator::global()->bounded((max + 1) - min) + min; QTimer::singleShot(delay, this, [this, delay]() { qCDebug(LOG_PLASMAQUICK) << "Delayed preload of " << d->applet->title() << "after" << (qreal)delay/1000 << "seconds"; d->preloadForExpansion(); }); } } }); } } Plasma::Package AppletQuickItem::appletPackage() const { return Plasma::Package(d->appletPackage); } void AppletQuickItem::setAppletPackage(const Plasma::Package &package) { d->appletPackage = package.kPackage(); } Plasma::Package AppletQuickItem::coronaPackage() const { return Plasma::Package(d->coronaPackage); } void AppletQuickItem::setCoronaPackage(const Plasma::Package &package) { d->coronaPackage = package.kPackage(); } int AppletQuickItem::switchWidth() const { return d->switchWidth; } void AppletQuickItem::setSwitchWidth(int width) { if (d->switchWidth == width) { return; } d->switchWidth = width; d->compactRepresentationCheck(); emit switchWidthChanged(width); } int AppletQuickItem::switchHeight() const { return d->switchHeight; } void AppletQuickItem::setSwitchHeight(int height) { if (d->switchHeight == height) { return; } d->switchHeight = height; d->compactRepresentationCheck(); emit switchHeightChanged(height); } QQmlComponent *AppletQuickItem::compactRepresentation() { return d->compactRepresentation; } void AppletQuickItem::setCompactRepresentation(QQmlComponent *component) { if (d->compactRepresentation == component) { return; } d->compactRepresentation = component; emit compactRepresentationChanged(component); } QQmlComponent *AppletQuickItem::fullRepresentation() { return d->fullRepresentation; } QObject *AppletQuickItem::testItem() { if (!d->testItem) { const QUrl url(d->appletPackage.fileUrl("test")); if (url.isEmpty()) { return nullptr; } d->testItem = d->qmlObject->createObjectFromSource(url, QtQml::qmlContext(rootItem())); if (d->testItem) { d->testItem->setProperty("plasmoidItem", QVariant::fromValue(this)); } } return d->testItem; } void AppletQuickItem::setFullRepresentation(QQmlComponent *component) { if (d->fullRepresentation == component) { return; } d->fullRepresentation = component; emit fullRepresentationChanged(component); } QQmlComponent *AppletQuickItem::preferredRepresentation() { return d->preferredRepresentation; } void AppletQuickItem::setPreferredRepresentation(QQmlComponent *component) { if (d->preferredRepresentation == component) { return; } d->preferredRepresentation = component; emit preferredRepresentationChanged(component); d->compactRepresentationCheck(); } bool AppletQuickItem::isExpanded() const { return d->expanded; } void AppletQuickItem::setExpanded(bool expanded) { if (d->expanded == expanded) { return; } if (expanded) { d->preloadForExpansion(); //increase on open, ignore containments if (d->s_preloadPolicy >= AppletQuickItemPrivate::Adaptive && !d->applet->isContainment()) { const int newWeight = qMin(d->preloadWeight() + AppletQuickItemPrivate::PreloadWeightIncrement, 100); d->applet->config().writeEntry(QStringLiteral("PreloadWeight"), newWeight); qCDebug(LOG_PLASMAQUICK) << "Increasing score for" << d->applet->title() << "to" << newWeight; } } d->expanded = expanded; emit expandedChanged(expanded); } bool AppletQuickItem::isActivationTogglesExpanded() const { return d->activationTogglesExpanded; } void AppletQuickItem::setActivationTogglesExpanded(bool activationTogglesExpanded) { if (d->activationTogglesExpanded == activationTogglesExpanded) { return; } d->activationTogglesExpanded = activationTogglesExpanded; emit activationTogglesExpandedChanged(activationTogglesExpanded); } ////////////Internals KDeclarative::QmlObject *AppletQuickItem::qmlObject() { return d->qmlObject; } QQuickItem *AppletQuickItem::compactRepresentationItem() { return d->compactRepresentationItem; } QQuickItem *AppletQuickItem::fullRepresentationItem() { return d->fullRepresentationItem; } QObject *AppletQuickItem::rootItem() { return d->qmlObject->rootObject(); } void AppletQuickItem::childEvent(QChildEvent *event) { // Added child may be QQuickLayoutAttached if (event->added() && !d->ownLayout && d->currentRepresentationItem) { // Child has not yet finished initialization at this point QTimer::singleShot(0, this, [this]() { if (!d->ownLayout) { d->connectLayoutAttached(d->currentRepresentationItem); } }); } QQuickItem::childEvent(event); } void AppletQuickItem::geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry) { Q_UNUSED(oldGeometry) QQuickItem::geometryChanged(newGeometry, oldGeometry); d->compactRepresentationCheck(); } void AppletQuickItem::itemChange(ItemChange change, const ItemChangeData &value) { if (change == QQuickItem::ItemSceneChange) { //we have a window: create the representations if needed if (value.window) { init(); } } QQuickItem::itemChange(change, value); } } #include "moc_appletquickitem.cpp"