diff --git a/examples/applets/CMakeLists.txt b/examples/applets/CMakeLists.txt index ff9498720..1d61e3305 100644 --- a/examples/applets/CMakeLists.txt +++ b/examples/applets/CMakeLists.txt @@ -1,5 +1,6 @@ plasma_install_package(config org.kde.example.configuration) +plasma_install_package(dataenginemodel org.kde.example.dataenginemodel) plasma_install_package(notes org.kde.example.notes) plasma_install_package(widgetgallery org.kde.example.widgetgallery) plasma_install_package(qmltasks org.kde.example.tasks) diff --git a/examples/applets/dataenginemodel/contents/ui/main.qml b/examples/applets/dataenginemodel/contents/ui/main.qml new file mode 100644 index 000000000..d64e71f48 --- /dev/null +++ b/examples/applets/dataenginemodel/contents/ui/main.qml @@ -0,0 +1,55 @@ +// -*- coding: iso-8859-1 -*- +/* + * Copyright 2012 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. + */ + +import QtQuick 2.0 +import org.kde.plasma.core 2.0 as PlasmaCore +import org.kde.plasma.components 2.0 as PlasmaComponents +import org.kde.plasma.extras 2.0 as PlasmaExtras + +Column { + width: 500 + height: 500 + property int minimumWidth: 200 + property int minimumHeight: 300 + + PlasmaCore.DataSource { + id: source + dataEngine: "org.kde.examples.sourcesOnRequest" + interval: 1000 + connectedSources: "test" + } + + PlasmaComponents.Label { + text: source.data.test["Update Count"] + } + PlasmaExtras.ScrollArea { + anchors { + left: parent.left + right: parent.right + } + height: 500 + ListView { + model: source.models.test + delegate: PlasmaComponents.Label { + text: model.display + } + } + } +} diff --git a/examples/applets/dataenginemodel/metadata.desktop b/examples/applets/dataenginemodel/metadata.desktop new file mode 100644 index 000000000..3327b26f9 --- /dev/null +++ b/examples/applets/dataenginemodel/metadata.desktop @@ -0,0 +1,20 @@ +[Desktop Entry] +Comment=Example applet that shows how to use Models embeeded in DataEngines +Encoding=UTF-8 +Keywords= +Name=Dataengine model +Type=Service +Icon= +X-KDE-ParentApp= +X-KDE-PluginInfo-Author=Marco Martin +X-KDE-PluginInfo-Category=Miscellaneous +X-KDE-PluginInfo-Email=mart@kde.org +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-Name=org.kde.example.dataenginemodel +X-KDE-PluginInfo-Version= +X-KDE-PluginInfo-Website= +X-KDE-ServiceTypes=Plasma/Applet +X-Plasma-API=declarativeappletscript + +X-Plasma-MainScript=ui/main.qml +X-Plasma-RemoteLocation= diff --git a/examples/dataengines/customDataContainers/CMakeLists.txt b/examples/dataengines/customDataContainers/CMakeLists.txt index add3c7154..c48ea081a 100644 --- a/examples/dataengines/customDataContainers/CMakeLists.txt +++ b/examples/dataengines/customDataContainers/CMakeLists.txt @@ -12,5 +12,5 @@ target_link_libraries(plasma_dataengine_example_customDataContainers KF5::Service ) -install(TARGETS plasma_dataengine_example_customDataContainers DESTINATION ${PLUGIN_INSTALL_DIR}) +install(TARGETS plasma_dataengine_example_customDataContainers DESTINATION ${PLUGIN_INSTALL_DIR}/plasma/dataengine) install(FILES plasma-dataengine-example-customDataContainers.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/examples/dataengines/simpleEngine/CMakeLists.txt b/examples/dataengines/simpleEngine/CMakeLists.txt index fb3050044..27ae5eef2 100644 --- a/examples/dataengines/simpleEngine/CMakeLists.txt +++ b/examples/dataengines/simpleEngine/CMakeLists.txt @@ -12,5 +12,5 @@ target_link_libraries(plasma_dataengine_example_simpleEngine KF5::I18n ) -install(TARGETS plasma_dataengine_example_simpleEngine DESTINATION ${PLUGIN_INSTALL_DIR}) +install(TARGETS plasma_dataengine_example_simpleEngine DESTINATION ${PLUGIN_INSTALL_DIR}/plasma/dataengine) install(FILES plasma-dataengine-example-simpleEngine.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/examples/dataengines/sourcesOnRequest/CMakeLists.txt b/examples/dataengines/sourcesOnRequest/CMakeLists.txt index 43932efed..9c0a039aa 100644 --- a/examples/dataengines/sourcesOnRequest/CMakeLists.txt +++ b/examples/dataengines/sourcesOnRequest/CMakeLists.txt @@ -11,5 +11,5 @@ target_link_libraries(plasma_dataengine_example_sourcesOnRequest KF5::Service ) -install(TARGETS plasma_dataengine_example_sourcesOnRequest DESTINATION ${PLUGIN_INSTALL_DIR}) +install(TARGETS plasma_dataengine_example_sourcesOnRequest DESTINATION ${PLUGIN_INSTALL_DIR}/plasma/dataengine) install(FILES plasma-dataengine-example-sourcesOnRequest.desktop DESTINATION ${SERVICES_INSTALL_DIR} ) diff --git a/examples/dataengines/sourcesOnRequest/sourcesOnRequest.cpp b/examples/dataengines/sourcesOnRequest/sourcesOnRequest.cpp index 912a275b8..96aa96d63 100644 --- a/examples/dataengines/sourcesOnRequest/sourcesOnRequest.cpp +++ b/examples/dataengines/sourcesOnRequest/sourcesOnRequest.cpp @@ -27,9 +27,12 @@ #include +#include + /* This DataEngine shows how to created sources on demand as they are requested and update them on visualization-requested update intervals. + It also shows how to bind and arbitrary QAbstractItemModel to a source */ SourcesOnRequestEngine::SourcesOnRequestEngine(QObject *parent, const QVariantList &args) @@ -61,6 +64,14 @@ bool SourcesOnRequestEngine::sourceRequestEvent(const QString &source) // expects. So ALWAYS key the new data by the source string as below: setData(source, "Update Count", 0); + if (!modelForSource(source)) { + QStandardItemModel *m = new QStandardItemModel; + m->appendRow(new QStandardItem("Item1, first update")); + m->appendRow(new QStandardItem("Item2, first update")); + m->appendRow(new QStandardItem("Item3, first update")); + setModel(source, m); + } + // as we successfully set up the source, return true return true; } @@ -81,7 +92,16 @@ bool SourcesOnRequestEngine::updateSourceEvent(const QString &source) // sourceRequestEvent, however, this will result in expected behavior: visualizations // connected to the sources which have setData called for them will be notified // of these changes. - setData(source, "Update Count", containerForSource(source)->data().value("Update Count").toInt() + 1); + const int updateCount = containerForSource(source)->data().value("Update Count").toInt() + 1; + setData(source, "Update Count", updateCount); + + QStandardItemModel *m = qobject_cast(modelForSource(source)); + if (m) { + m->clear(); + m->appendRow(new QStandardItem(QString("Item1, update %1").arg(updateCount))); + m->appendRow(new QStandardItem(QString("Item2, update %1").arg(updateCount))); + m->appendRow(new QStandardItem(QString("Item3, update %1").arg(updateCount))); + } // Since we updated the source immediately here, we need to return true so the DataEngine // knows to continue with the update notification for visualizations. diff --git a/src/declarativeimports/core/datasource.cpp b/src/declarativeimports/core/datasource.cpp index b44ce109f..6b0336299 100644 --- a/src/declarativeimports/core/datasource.cpp +++ b/src/declarativeimports/core/datasource.cpp @@ -30,6 +30,7 @@ DataSource::DataSource(QObject* parent) m_dataEngine(0), m_dataEngineConsumer(0) { + m_models = new QQmlPropertyMap(this); setObjectName("DataSource"); } @@ -148,9 +149,24 @@ void DataSource::dataUpdated(const QString &sourceName, const Plasma::DataEngine } } +void DataSource::modelChanged(const QString &sourceName, QAbstractItemModel *model) +{ + if (!model) { + m_models->clear(sourceName); + return; + } + + m_models->insert(sourceName, QVariant::fromValue(model)); + //FIXME: this will break in the case a second model is set + connect(model, &QObject::destroyed, [=]() { + m_models->clear(sourceName); + }); +} + void DataSource::removeSource(const QString &source) { m_data.remove(source); + m_models->clear(source); //TODO: emit those signals as last thing if (m_connectedSources.contains(source)) { diff --git a/src/declarativeimports/core/datasource.h b/src/declarativeimports/core/datasource.h index 01c613a17..868ab63b6 100644 --- a/src/declarativeimports/core/datasource.h +++ b/src/declarativeimports/core/datasource.h @@ -89,11 +89,19 @@ public: /** * All the data fetched by this dataengine. - * This is a map of maps. At the first level, there are the source names, at the secons, they keys set by the DataEngine + * This is a map of maps. At the first level, there are the source names, at the second, they keys set by the DataEngine */ Q_PROPERTY(QVariantMap data READ data NOTIFY dataChanged); QVariantMap data() const {return m_data;} + /** + * All the models associated to this DataEngine, indexed by source. + * In order for a model to be present, besides being implemented in the DataEngine, + * The user has to be connected to its source, so the source name has to be present in the connectedSources property. + */ + Q_PROPERTY(QQmlPropertyMap *models READ models CONSTANT); + QQmlPropertyMap *models() const {return m_models;} + /** * @returns a Plasma::Service given a source name * @arg QString source source name we want a service of @@ -112,6 +120,7 @@ public: protected Q_SLOTS: void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); + void modelChanged(const QString &sourceName, QAbstractItemModel *model); void removeSource(const QString &source); void setupData(); @@ -132,6 +141,7 @@ private: int m_interval; QString m_engine; QVariantMap m_data; + QQmlPropertyMap *m_models; Plasma::DataEngine* m_dataEngine; Plasma::DataEngineConsumer* m_dataEngineConsumer; QStringList m_connectedSources; diff --git a/src/plasma/datacontainer.cpp b/src/plasma/datacontainer.cpp index f24b136d4..e8463927a 100644 --- a/src/plasma/datacontainer.cpp +++ b/src/plasma/datacontainer.cpp @@ -21,6 +21,7 @@ #include "private/storage_p.h" #include +#include #include "plasma.h" @@ -65,6 +66,26 @@ void DataContainer::setData(const QString &key, const QVariant &value) 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()) { @@ -109,6 +130,9 @@ void DataContainer::connectVisualization(QObject *visualization, uint pollingInt } else { 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 + disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), + visualization, SLOT(modelChanged(QString, QAbstractItemModel *))); //relay->isUnused(); } } else if (pollingInterval < 1) { @@ -120,6 +144,8 @@ void DataContainer::connectVisualization(QObject *visualization, uint pollingInt } else { disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), + visualization, SLOT(modelChanged(QString, QAbstractItemModel *))); } } else { connect(visualization, SIGNAL(destroyed(QObject*)), @@ -131,6 +157,8 @@ void DataContainer::connectVisualization(QObject *visualization, uint pollingInt d->relayObjects[visualization] = 0; connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + connect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), + visualization, SLOT(modelChanged(QString, QAbstractItemModel *))); } else { //qDebug() << " connecting to a relay"; // we only want to do an imediate update if this is not the first object to connect to us @@ -141,6 +169,9 @@ void DataContainer::connectVisualization(QObject *visualization, uint pollingInt alignment, immediateUpdate); 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 + connect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), + visualization, SLOT(modelChanged(QString, QAbstractItemModel *))); } } @@ -271,6 +302,8 @@ void DataContainer::disconnectVisualization(QObject *visualization) // it is connected directly to the DataContainer itself disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); + disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), + visualization, SLOT(modelChanged(QString, QAbstractItemModel *))); } else { SignalRelay *relay = objIt.value(); @@ -280,6 +313,9 @@ void DataContainer::disconnectVisualization(QObject *visualization) } else { 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 + disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)), + visualization, SLOT(modelChanged(QString, QAbstractItemModel *))); } } @@ -344,6 +380,12 @@ void DataContainer::timerEvent(QTimerEvent * event) if (!isUsed()) { // DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED! //qDebug() << 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(), 0); + } emit becameUnused(objectName()); } d->checkUsageTimer.stop(); diff --git a/src/plasma/datacontainer.h b/src/plasma/datacontainer.h index f7a165980..57fac3f59 100644 --- a/src/plasma/datacontainer.h +++ b/src/plasma/datacontainer.h @@ -28,6 +28,8 @@ #include #include +class QAbstractItemModel; + namespace Plasma { @@ -106,6 +108,26 @@ class PLASMA_EXPORT DataContainer : public QObject **/ void removeAllData(); + /** + * Associates a model with this DataContainer. Use this for data + * that is intended to be a long list of items. + * + * The ownership of the model is transferred to the DataContainer, + * so the model will be deletd when a new one is set or when the + * DataContainer itself is deleted, so it will be deleted when there won't be any + * visualization associated to this source. + * + * Normally you should set the model from DataEngine::setModel instead from here. + * + * @param model the model that will be associated with this DataContainer + */ + void setModel(QAbstractItemModel *model); + + /** + * @return the model owned by this DataSource + */ + QAbstractItemModel *model(); + /** * @return true if the visualization is currently connected */ @@ -188,6 +210,16 @@ class PLASMA_EXPORT DataContainer : public QObject **/ void dataUpdated(const QString &source, const Plasma::DataEngine::Data &data); + /** + * A new model has been associated to this source, + * visualizations can safely use it as long they are connected to this source. + * + * @param source the objectName() of the DataContainer (and hence the name + * of the source) that owns the model + * @param model the QAbstractItemModel instance + */ + void modelChanged(const QString &source, QAbstractItemModel *model); + /** * Emitted when the last visualization is disconnected. * diff --git a/src/plasma/dataengine.cpp b/src/plasma/dataengine.cpp index a56448229..312802906 100644 --- a/src/plasma/dataengine.cpp +++ b/src/plasma/dataengine.cpp @@ -21,6 +21,7 @@ #include "private/dataengine_p.h" #include "private/datacontainer_p.h" +#include #include #include #include @@ -227,6 +228,32 @@ void DataEngine::removeData(const QString &source, const QString &key) } } +void DataEngine::setModel(const QString &source, QAbstractItemModel *model) +{ + if (model) { + setData(source, "HasModel", true); + } else { + removeData(source, "HasModel"); + } + + Plasma::DataContainer *s = containerForSource(source); + + if (s) { + s->setModel(model); + } +} + +QAbstractItemModel *DataEngine::modelForSource(const QString &source) +{ + Plasma::DataContainer *s = containerForSource(source); + + if (s) { + return s->model(); + } else { + return 0; + } +} + void DataEngine::addSource(DataContainer *source) { if (d->sources.contains(source->objectName())) { @@ -521,6 +548,11 @@ void DataEnginePrivate::connectSource(DataContainer *s, QObject *visualization, QMetaObject::invokeMethod(visualization, "dataUpdated", Q_ARG(QString, s->objectName()), Q_ARG(Plasma::DataEngine::Data, s->data())); + if (s->d->model) { + QMetaObject::invokeMethod(visualization, "modelChanged", + Q_ARG(QString, s->objectName()), + Q_ARG(QAbstractItemModel *, s->d->model.data())); + } s->d->dirty = false; } } diff --git a/src/plasma/dataengine.h b/src/plasma/dataengine.h index ffe6b6ead..1552d2ab3 100644 --- a/src/plasma/dataengine.h +++ b/src/plasma/dataengine.h @@ -31,6 +31,8 @@ #include #include +class QAbstractItemModel; + namespace Plasma { @@ -175,6 +177,12 @@ Types::NoAlignment) const; **/ Q_INVOKABLE DataContainer *containerForSource(const QString &source); + /** + * @return The model associated to a source if any. The ownership of the model stays with the DataContainer. + * Returns 0 if there isn't any model associated or if the source doesn't exists. + */ + QAbstractItemModel *modelForSource(const QString &source); + /** * Returns true if this engine is valid, otherwise returns false * @@ -306,6 +314,21 @@ Types::NoAlignment) const; **/ void removeData(const QString &source, const QString &key); + /** + * Associates a model to a data source. If the source + * doesn't exist then it is created. The source will have the key "HasModel" to easily indicate there is a model present. + * + * The ownership of the model is transferred to the DataContainer, + * so the model will be deletd when a new one is set or when the + * DataContainer itself is deleted. As the DataContainer, it will be + * deleted when there won't be any + * visualization associated to this source. + * + * @param source the name of the data source + * @param model the model instance + */ + void setModel(const QString &source, QAbstractItemModel *model); + /** * Adds an already constructed data source. The DataEngine takes * ownership of the DataContainer object. The objectName of the source diff --git a/src/plasma/private/datacontainer_p.h b/src/plasma/private/datacontainer_p.h index 19cd3516a..a4760dcda 100644 --- a/src/plasma/private/datacontainer_p.h +++ b/src/plasma/private/datacontainer_p.h @@ -27,6 +27,8 @@ #include #include +#include + class QTimer; namespace Plasma @@ -87,6 +89,7 @@ public: Storage* storage; QBasicTimer storageTimer; QBasicTimer checkUsageTimer; + QWeakPointer model; int storageCount; bool dirty : 1; bool cached : 1;