Merge branch 'mart/modelsInDataEngine'

This commit is contained in:
Marco Martin 2014-01-02 11:05:20 +01:00
commit 84df832f5c
14 changed files with 259 additions and 5 deletions

View File

@ -1,5 +1,6 @@
plasma_install_package(config org.kde.example.configuration) 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(notes org.kde.example.notes)
plasma_install_package(widgetgallery org.kde.example.widgetgallery) plasma_install_package(widgetgallery org.kde.example.widgetgallery)
plasma_install_package(qmltasks org.kde.example.tasks) plasma_install_package(qmltasks org.kde.example.tasks)

View File

@ -0,0 +1,55 @@
// -*- coding: iso-8859-1 -*-
/*
* Copyright 2012 Marco Martin <mart@kde.org>
*
* 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
}
}
}
}

View File

@ -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=

View File

@ -12,5 +12,5 @@ target_link_libraries(plasma_dataengine_example_customDataContainers
KF5::Service 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} ) install(FILES plasma-dataengine-example-customDataContainers.desktop DESTINATION ${SERVICES_INSTALL_DIR} )

View File

@ -12,5 +12,5 @@ target_link_libraries(plasma_dataengine_example_simpleEngine
KF5::I18n 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} ) install(FILES plasma-dataengine-example-simpleEngine.desktop DESTINATION ${SERVICES_INSTALL_DIR} )

View File

@ -11,5 +11,5 @@ target_link_libraries(plasma_dataengine_example_sourcesOnRequest
KF5::Service 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} ) install(FILES plasma-dataengine-example-sourcesOnRequest.desktop DESTINATION ${SERVICES_INSTALL_DIR} )

View File

@ -27,9 +27,12 @@
#include <Plasma/DataContainer> #include <Plasma/DataContainer>
#include <QStandardItemModel>
/* /*
This DataEngine shows how to created sources on demand as they are requested This DataEngine shows how to created sources on demand as they are requested
and update them on visualization-requested update intervals. 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) 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: // expects. So ALWAYS key the new data by the source string as below:
setData(source, "Update Count", 0); 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 // as we successfully set up the source, return true
return true; return true;
} }
@ -81,7 +92,16 @@ bool SourcesOnRequestEngine::updateSourceEvent(const QString &source)
// sourceRequestEvent, however, this will result in expected behavior: visualizations // sourceRequestEvent, however, this will result in expected behavior: visualizations
// connected to the sources which have setData called for them will be notified // connected to the sources which have setData called for them will be notified
// of these changes. // 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<QStandardItemModel *>(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 // Since we updated the source immediately here, we need to return true so the DataEngine
// knows to continue with the update notification for visualizations. // knows to continue with the update notification for visualizations.

View File

@ -30,6 +30,7 @@ DataSource::DataSource(QObject* parent)
m_dataEngine(0), m_dataEngine(0),
m_dataEngineConsumer(0) m_dataEngineConsumer(0)
{ {
m_models = new QQmlPropertyMap(this);
setObjectName("DataSource"); 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) void DataSource::removeSource(const QString &source)
{ {
m_data.remove(source); m_data.remove(source);
m_models->clear(source);
//TODO: emit those signals as last thing //TODO: emit those signals as last thing
if (m_connectedSources.contains(source)) { if (m_connectedSources.contains(source)) {

View File

@ -89,11 +89,19 @@ public:
/** /**
* All the data fetched by this dataengine. * 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); Q_PROPERTY(QVariantMap data READ data NOTIFY dataChanged);
QVariantMap data() const {return m_data;} 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 * @returns a Plasma::Service given a source name
* @arg QString source source name we want a service of * @arg QString source source name we want a service of
@ -112,6 +120,7 @@ public:
protected Q_SLOTS: protected Q_SLOTS:
void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data); void dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data);
void modelChanged(const QString &sourceName, QAbstractItemModel *model);
void removeSource(const QString &source); void removeSource(const QString &source);
void setupData(); void setupData();
@ -132,6 +141,7 @@ private:
int m_interval; int m_interval;
QString m_engine; QString m_engine;
QVariantMap m_data; QVariantMap m_data;
QQmlPropertyMap *m_models;
Plasma::DataEngine* m_dataEngine; Plasma::DataEngine* m_dataEngine;
Plasma::DataEngineConsumer* m_dataEngineConsumer; Plasma::DataEngineConsumer* m_dataEngineConsumer;
QStringList m_connectedSources; QStringList m_connectedSources;

View File

@ -21,6 +21,7 @@
#include "private/storage_p.h" #include "private/storage_p.h"
#include <QDebug> #include <QDebug>
#include <QAbstractItemModel>
#include "plasma.h" #include "plasma.h"
@ -65,6 +66,26 @@ void DataContainer::setData(const QString &key, const QVariant &value)
setNeedsToBeStored(true); 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() void DataContainer::removeAllData()
{ {
if (d->data.isEmpty()) { if (d->data.isEmpty()) {
@ -109,6 +130,9 @@ void DataContainer::connectVisualization(QObject *visualization, uint pollingInt
} else { } else {
disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(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(); //relay->isUnused();
} }
} else if (pollingInterval < 1) { } else if (pollingInterval < 1) {
@ -120,6 +144,8 @@ void DataContainer::connectVisualization(QObject *visualization, uint pollingInt
} else { } else {
disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)),
visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
} }
} else { } else {
connect(visualization, SIGNAL(destroyed(QObject*)), connect(visualization, SIGNAL(destroyed(QObject*)),
@ -131,6 +157,8 @@ void DataContainer::connectVisualization(QObject *visualization, uint pollingInt
d->relayObjects[visualization] = 0; d->relayObjects[visualization] = 0;
connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), connect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
connect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)),
visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
} else { } else {
//qDebug() << " connecting to a relay"; //qDebug() << " connecting to a relay";
// we only want to do an imediate update if this is not the first object to connect to us // 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); alignment, immediateUpdate);
connect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), connect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(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 // it is connected directly to the DataContainer itself
disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), disconnect(this, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data))); visualization, SLOT(dataUpdated(QString,Plasma::DataEngine::Data)));
disconnect(this, SIGNAL(modelChanged(QString, QAbstractItemModel *)),
visualization, SLOT(modelChanged(QString, QAbstractItemModel *)));
} else { } else {
SignalRelay *relay = objIt.value(); SignalRelay *relay = objIt.value();
@ -280,6 +313,9 @@ void DataContainer::disconnectVisualization(QObject *visualization)
} else { } else {
disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)), disconnect(relay, SIGNAL(dataUpdated(QString,Plasma::DataEngine::Data)),
visualization, SLOT(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()) { if (!isUsed()) {
// DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED! // DO NOT CALL ANYTHING AFTER THIS LINE AS IT MAY GET DELETED!
//qDebug() << objectName() << "is unused"; //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()); emit becameUnused(objectName());
} }
d->checkUsageTimer.stop(); d->checkUsageTimer.stop();

View File

@ -28,6 +28,8 @@
#include <plasma/plasma_export.h> #include <plasma/plasma_export.h>
#include <plasma/dataengine.h> #include <plasma/dataengine.h>
class QAbstractItemModel;
namespace Plasma namespace Plasma
{ {
@ -106,6 +108,26 @@ class PLASMA_EXPORT DataContainer : public QObject
**/ **/
void removeAllData(); 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 * @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); 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. * Emitted when the last visualization is disconnected.
* *

View File

@ -21,6 +21,7 @@
#include "private/dataengine_p.h" #include "private/dataengine_p.h"
#include "private/datacontainer_p.h" #include "private/datacontainer_p.h"
#include <QAbstractItemModel>
#include <QQueue> #include <QQueue>
#include <QTimer> #include <QTimer>
#include <QTime> #include <QTime>
@ -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) void DataEngine::addSource(DataContainer *source)
{ {
if (d->sources.contains(source->objectName())) { if (d->sources.contains(source->objectName())) {
@ -521,6 +548,11 @@ void DataEnginePrivate::connectSource(DataContainer *s, QObject *visualization,
QMetaObject::invokeMethod(visualization, "dataUpdated", QMetaObject::invokeMethod(visualization, "dataUpdated",
Q_ARG(QString, s->objectName()), Q_ARG(QString, s->objectName()),
Q_ARG(Plasma::DataEngine::Data, s->data())); 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; s->d->dirty = false;
} }
} }

View File

@ -31,6 +31,8 @@
#include <plasma/plasma.h> #include <plasma/plasma.h>
#include <plasma/service.h> #include <plasma/service.h>
class QAbstractItemModel;
namespace Plasma namespace Plasma
{ {
@ -175,6 +177,12 @@ Types::NoAlignment) const;
**/ **/
Q_INVOKABLE DataContainer *containerForSource(const QString &source); 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 * 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); 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 * Adds an already constructed data source. The DataEngine takes
* ownership of the DataContainer object. The objectName of the source * ownership of the DataContainer object. The objectName of the source

View File

@ -27,6 +27,8 @@
#include <QtCore/QTime> #include <QtCore/QTime>
#include <QtCore/QBasicTimer> #include <QtCore/QBasicTimer>
#include <QAbstractItemModel>
class QTimer; class QTimer;
namespace Plasma namespace Plasma
@ -87,6 +89,7 @@ public:
Storage* storage; Storage* storage;
QBasicTimer storageTimer; QBasicTimer storageTimer;
QBasicTimer checkUsageTimer; QBasicTimer checkUsageTimer;
QWeakPointer<QAbstractItemModel> model;
int storageCount; int storageCount;
bool dirty : 1; bool dirty : 1;
bool cached : 1; bool cached : 1;