Thursday 9 January 2014

Load data from C++ module in extensionplugin and show it on Qt ListView

This time the loaded data will be from C++ dynamic library instead of another qml file. It's more complex than previous article:
http://csfreebird.blogspot.com/2014/01/load-data-from-another-qml-file-and.html

My project folder tree looks as following:
/listview2$ tree
.
├── imports
│   └── mylist
│       ├── libmylist.so
│       └── qmldir
├── list2.pro
├── plugin.cpp
├── run.sh
└── test.qml
 The list2.pro file content is:
TEMPLATE = lib
CONFIG += plugin
QT += qml
DESTDIR = imports/mylist
TARGET  = mylist
SOURCES += plugin.cpp
qml.files = test.qml
qml.path += ./
pluginfiles.files += imports/mylist/qmldir
pluginfiles.path += imports/mylist
target.path += imports/mylist
INSTALLS += target qml pluginfiles
qmldir in module folder mylist is changed:
module mylist
plugin mylist
As a matter of convenience, I implement all C++ codes in one cpp file called plugin.cpp.
#include <QtQml/QQmlExtensionPlugin>
#include <QtQml/qqml.h>
#include <qdebug.h>
#include <qdatetime.h>
#include <qbasictimer.h>
#include <qcoreapplication.h>
#include <QAbstractItemModel>
#include <QStringList>
class People {
public:
  People(QString const & name, QString const & number)
    : name_(name), number_(number) {
  }
  QString name() const {
    return name_;
  }
  QString number() const {
    return number_;
  }
private:
  QString name_;
  QString number_;
};
class PeopleListModel : public QAbstractListModel {
  Q_OBJECT
public:
  enum PeopleRoles {
    NameRole = Qt::UserRole + 1,
    NumberRole
  };
  PeopleListModel(QObject * parent = 0)
    : QAbstractListModel(parent) {
    People p1("Dean", "186***");
    addPeople(p1);
    People p2("Crystal", "186***");
    addPeople(p2);
  }
  void addPeople(People const & p) {
    beginInsertRows(QModelIndex(), rowCount(), rowCount());
    values_ << p;
    endInsertRows();
  }
  int rowCount(QModelIndex const & parent = QModelIndex()) const {
    return values_.count();
  }
  QVariant data(QModelIndex const & index, int role = Qt::DisplayRole) const {
    if (index.row() < 0 || index.row() >= values_.count())
      return QVariant();
    People const & p = values_[index.row()];
    if (role == NameRole)
      return p.name();
    else if (role == NumberRole)
      return p.number();
    return QVariant();
  }
protected:
  QHash<int, QByteArray> roleNames() const {
    QHash<int, QByteArray> roles;
    roles[NameRole] = "name";
    roles[NumberRole] = "number";
    return roles;
  }
private:
  QList<People> values_;
};
class QExampleQmlPlugin : public QQmlExtensionPlugin {
  Q_OBJECT
  Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtension.PeopleListModel")
  public:
  void registerTypes(char const * uri) {
    qmlRegisterType<PeopleListModel>(uri, 1, 0, "PeopleListModel");
  }
};
#include "plugin.moc"
The People class is simple, just hold two properties: name and number.
The PeopleListModel class inherits from QAbstractListModel, it provides the data needed by ListView on Qml view.
The QExampleQmlPlugin is the plugin class, registers the exposed PeopleListModle class in it.
Pay attention to three methods from PeopleListModel: rowCount, data and roleNames. They are required by ListView.

To understand the Model-View architecture, please start with the following article:
http://doc-snapshot.qt-project.org/qdoc/model-view-programming.html#introduction-to-model-view-programming


Load data from another qml file and show it in ListView

From below doc, you will see some code snippet which shows how to create a simple ListView.
http://doc-snapshot.qt-project.org/qdoc/qml-qtquick-listview.html#model-prop

I assemble them and put them in one demo app. In my project, the folder tree is:
listview1$ tree
.
├── imports
│   └── model
│       ├── ContactModel.qml
│       └── qmldir
├── run.sh
└── test.qml
This demo app contains one test.qml and another plugin which only contains one ContactModel.qml file without C++ dynamic library.
qmldir describes the module exposed from plugin
module model
ContactModel 1.0 ContactModel.qml
The ContactModel.qml is a model for ListView to provide data.
import QtQuick 2.0
ListModel {
    ListElement {
        name: "Bill Smith"
        number: "555 3264"
    }
    ListElement {
        name: "John Brown"
        number: "555 8426"
    }
    ListElement {
        name: "Sam Wise"
        number: "555 0473"
    }
}
In test.qml file, apply ContactModel to model attribute. The delegate is a way to iterate all ListElements provided by model and show each element as one Text block on Window.
import QtQuick 2.0
import model 1.0
ListView {
    width: 180; height: 200
    model: ContactModel {}
    delegate: Text {
        text: name + ": " + number
    }
}
 To build this, just call following commands:
qmake
make
To run it:
~/Qt5.2.0/5.2.0/gcc_64/bin/qmlscene -I ./imports ./test.qml

Pass Qt imports folder path as one argument

I used to set imports folder path using environment variable, after look inside the code of qmlscene, I can pass the imports folder as an argument to my app, below is my full code:
#include <QtQml>
#include <QtQuick/QQuickView>
#include <QtCore/QString>
#include <algorithm>
#ifdef QT_WIDGETS_LIB
#include <QtWidgets/QApplication>
#else
#include <QtGui/QGuiApplication>
#endif

#ifdef QT_WIDGETS_LIB
#define Application QApplication
#else
#define Application QGuiApplication
#endif
QCoreApplication* createApplication(int & argc, char * argv[]) {
for (int i = 1; i < argc; ++i) {
if (!qstrcmp(argv[i], "-no-gui")) {
return new QCoreApplication(argc, argv);
}
}
return new QApplication(argc, argv);
}
int main(int argc, char *argv[])
{
if ((argc != 3) || qstrcmp(argv[1], "-I")) {
qWarning("Error: You must pass the location of plugins as one argument, e.g. -I ..\imports");
return -1;
}
QDir folder(argv[2]);
QString imports_dir_path;
if (!folder.exists()) {
QFileInfo info(argv[0]); QDir dir(info.absoluteDir().absolutePath() + QDir::separator() + argv[2]);
imports_dir_path = dir.absolutePath(); if (!dir.exists()) { QString err = "Error: The passed imports path is not a real folder, -I " + folder.absolutePath();
qWarning(err.toStdString().c_str());
return -1;
}
} else {
imports_dir_path = folder.absolutePath();
}

QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
if (qobject_cast<QApplication*>(app.data())) {
QQmlApplicationEngine engine;
engine.addImportPath(imports_dir_path); engine.load(QUrl("qrc:/qmls/main.qml"));
QObject * topLevel = engine.rootObjects().value(0);
QQuickWindow * window = qobject_cast<QQuickWindow*>(topLevel);
if (!window) {
qWarning("Error: Your root item has to be a Window.");
return -1;
}
window->showMaximized();
return app->exec();
} else {
}
}
The key is to use QQmlApplicationEngine::addImportPath method. 
For supporting the relative path, I test the passed path first, if it does not exit, I concatenate the current process folder and relative path. 


Tuesday 7 January 2014

QUrl and QFile

Recently I design one Qml file dialog to create a new file, then pass the file path to C++ extensionplugin to write a local file.
But the Qml file dialog makes a QUrl for me, format looks like so:
file:///C:/uuuu.a3

What I want is just C:/uuuu.a3, a local file path.
In C++ code, QUrl class can help to convert this.
Construct a QUrl object with url path, then call toLocalFile method to get a real local file path.
Example code is below:
void Test::setPath(QString const& path) {
  QUrl url(path);
  projectFilePath_ = url.toLocalFile();
  QFile file(projectFilePath_);
  if (!file.exists()) {
 if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
 QString error_msg = file.errorString();
 return;
 }
 QTextStream out(&file);
 out << "test" << "\n";
 file.close();
  }
}

Also, the above snippet shows how to create and write a text file on local disk using QFile & QIODevice & QTextStream.

Thursday 2 January 2014

Develop Qml Plugin 3

Have a look at hello.pro file now.
TEMPLATE = lib
CONFIG += plugin
QT += qml
DESTDIR = imports/demo
TARGET  = hello
SOURCES += plugin.cpp
qml.files = test.qml
qml.path += ./
pluginfiles.files += \
    imports/demo/qmldir \
    imports/demo/hello.qml \
pluginfiles.path += imports/demo
target.path += imports/demo
INSTALLS += target qml pluginfiles
 It's a qmake project file. The official docs are located at:
http://qt-project.org/doc/qt-5/qmake-project-files.html
http://qt-project.org/doc/qt-5/qmake-language.html

qmake reads this pro file and generates Makefile, before running qmake command, let's see how to implement an exposed C++ class in plugin.cpp file.
#include <QtQml/QQmlExtensionPlugin>
#include <QtQml/qqml.h>
#include <qdebug.h>
#include <qdatetime.h>
#include <qbasictimer.h>
#include <qcoreapplication.h>
//![0]
class ClickHandler : public QObject
{
  Q_OBJECT
  Q_PROPERTY(QString value READ value)
  //![0]
public:
  ClickHandler(QObject *parent=0) : QObject(parent)
  {
  }
  ~ClickHandler()
  {
  }
  QString value() const {
    return "hello, world";
  }
};
//![plugin]
class QExampleQmlPlugin : public QQmlExtensionPlugin
{
  Q_OBJECT
  Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
  public:
  void registerTypes(const char *uri)
  {
    qmlRegisterType<ClickHandler>(uri, 1, 0, "MyClick");
  }
};
//![plugin]
#include "plugin.moc"
To implement this, need the following steps:
1. Define a class which inherits from QQmlExtensionPlugin and use above Q_ macros
2. Define a class that you want to expose, here I have ClickHandler class, then register its type in the above class's registerTypes method, also with the version and short name.
3. Do not forget the plugin.moc
The above code is not long, but still contains many detail information. Please refer to official doc to get more information.
http://doc-snapshot.qt-project.org/qdoc/qtqml-cppintegration-definetypes.html
http://doc-snapshot.qt-project.org/qdoc/qml-extending-tutorial-index.html
http://doc-snapshot.qt-project.org/qdoc/qtqml-cppintegration-exposecppattributes.html
http://doc-snapshot.qt-project.org/qdoc/qqmlextensionplugin.html

Now, run commands to build this project.

dean@sloop2:~/test/plugin$ qmake
dean@sloop2:~/test/plugin$ make
/home/dean/Qt5.2.0/5.2.0/gcc_64/bin/moc -DQT_NO_DEBUG -DQT_PLUGIN -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I../../Qt5.2.0/5.2.0/gcc_64/mkspecs/linux-g++ -I. -I../../Qt5.2.0/5.2.0/gcc_64/include -I../../Qt5.2.0/5.2.0/gcc_64/include/QtQml -I../../Qt5.2.0/5.2.0/gcc_64/include/QtNetwork -I../../Qt5.2.0/5.2.0/gcc_64/include/QtGui -I../../Qt5.2.0/5.2.0/gcc_64/include/QtCore -I. plugin.cpp -o plugin.moc
g++ -c -pipe -O2 -Wall -W -D_REENTRANT -fPIC -DQT_NO_DEBUG -DQT_PLUGIN -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I../../Qt5.2.0/5.2.0/gcc_64/mkspecs/linux-g++ -I. -I../../Qt5.2.0/5.2.0/gcc_64/include -I../../Qt5.2.0/5.2.0/gcc_64/include/QtQml -I../../Qt5.2.0/5.2.0/gcc_64/include/QtNetwork -I../../Qt5.2.0/5.2.0/gcc_64/include/QtGui -I../../Qt5.2.0/5.2.0/gcc_64/include/QtCore -I. -o plugin.o plugin.cpp
rm -f libhello.so
g++ -Wl,-O1 -Wl,-rpath,/home/dean/Qt5.2.0/5.2.0/gcc_64 -Wl,-rpath,/home/dean/Qt5.2.0/5.2.0/gcc_64/lib -shared -o libhello.so plugin.o  -L/home/dean/Qt5.2.0/5.2.0/gcc_64/lib -lQt5Qml -lQt5Network -lQt5Gui -lQt5Core -lGL -lpthread
mv -f libhello.so imports/demo/ 
From above output, you will see qmake use moc coming from Qt5.2.0 to create plugin.moc from plugin.cpp file. To know moc, you need to learn something about Qt Meta-Object System.
http://qt-project.org/doc/qt-5/metaobjects.html

Develop Qml Plugin 2

Let's see my plugin project folder.
/plugin$ tree
.
├── hello.pro
├── imports
│   └── demo
│       ├── hello.qml
│       └── qmldir
├── plugin.cpp
└── test.qml
The plugin project name is called hello, hello.pro is its project file. All files are under plugin folder, you are free to change the plugin folder to any name you like.
I have introduced test.qml file in previous article, it's just a test qml file, not the part of plugin.
plugin.cpp file is a C++ implementation file, it contains a exposed C++ class.
Look inside the file imports/demo/hello.qml, all qml objects in this file is used as Hello class in test.qml. Actually, there is no name defined in hello.qml file, it is defined in imports/demo/qmldir file, I will talk about qmldir file later.
The content of hello.qml is shown below:
import QtQuick 2.0
Rectangle {
    width: 200; height: 200; color: "gray"
    Text {
        font.bold: true;
        font.pixelSize: 14;
        y:200;
        color: "white"
        anchors.horizontalCenter: parent.horizontalCenter
    }
}
It's just a normal qml file that includes a rectangle.

qmldir file is very important, see its content now:
module demo
Hello 1.0 hello.qml
plugin hello
 The first line defines module name called demo. Note, the module name can be separated by dot operation. Rule is the combination of import path and domain name will point to the qmldir file's location.
In my case, the import file path is relative path ./imports under plugin folder. Plus the demo path, the real path would be ./imports/demo/, it is right, so qmldir file can be located correctly.
To get more information please refer to official manual:
http://doc-snapshot.qt-project.org/qdoc/qtqml-modules-qmldir.html
The second line define a Hello class which comes from hello.qml. In test.qml file, I used this name.
In the third line, hello means the C++ plugin lib file name, e.g. on Ubuntu, it should be libhello.so, on Windows, it would be hello.dll. Do not treat it as the exposed C++ class name from plugin lib, because we can expose more than one C++ classes from one plugin lib file.

As a summary, I draw a diagram for plugin architecture.







Develop Qml Plugin 1

In this series of articles, I will show how to develop a Qml plugin and use it.
I will use a test.qml to load and use my demo plugin called hello.
Below is the screenshot of result of running test.qml with plugin.
The window get the "hello, world" string from hello plugin and show it.
The code in test.qml looks like so:
import demo 1.0 // import types from the plugin
import QtQuick 2.0
// this class is defined in QML (imports/demo/hello.qml)
Hello {
    // this class is defined in C++ (plugin.cpp)
    MyClick {
        id: click
    }
   
    Text {
        text: click.value
    }
}
From above comments, we know our hello plugin has a module called demo, version is 1.0. It uses the Qml object from hello.qml as a Hello class. Also there a exposed C++ class named MyClick defined in plugin.cpp from dynamic library. 

We can run the test.qml using qmlscene as following:
~/Qt5.2.0/5.2.0/gcc_64/bin/qmlscene -I imports ./test.qml
-I imports indicates that there is a imports sub folder in current folder.
Now we know how to use a plugin from qml file. I will talke about the development of  hello plugin in next article.

Followers

Contributors