QMLの拡張プラグインを作ろうとしてみたんだけど・・・

##今日は一体何月何日だ？
この記事はQt Advent Calendarの12日目の記事になるはずだったものなんだけど、これを書いている今日は一体何月何日なんだろう・・・？

##とりあえずやりたかったこと
随分長いこと放置していた[SailfishOS向けにリリースしているアプリ](https://gitlab.com/helicalgear/harbour-qayttamaton)を今年は久しぶりにアップデートして、足そうと思ってた機能は概ね足すことができた。なんか積極的にPatch送ってくれた人もいたので結構捗った。

このアプリでWeb APIにアクセスする部分はJavaScriptで作ってある訳なんだけど、（ま、自分のコーディングがヘボいだけって話ではあるんだけど）QML側から使うにあたってなんか一貫した使い方ができない感じがするなーって思う部分があったりするので、Twitter4QMLみたいな感じの拡張プラグインにできたらいいなーと思ったりした訳ですわ。

だけどいきなりそれ作るまでのスキルは全くもって持ち合わせてないので、とりあえず手始めに昔書いた[「QMLのカスタムエレメント作りを試してみた。」](https://qiita.com/helicalgear/items/2beb08e1333854fdebda)ってな記事で作ったエレメントを独立したPluginにしてみるところから着手してみるかー、みたいな感じで作業を始めてみた。

##どーすっか
とりあえず拡張プラグインを作るためのとっかかりのドキュメントとして公開されているものに["Writing QML Extensions with C++"](https://doc.qt.io/qt-5/qtqml-tutorials-extending-qml-example.html)ってのがあるので、プラグイン用のヘッダーファイルとかproファイルとかを真似して書いてみたけど、これがなかなかうまくいかない。
まぁ、ふつーに書けるヒトはこーゆードキュメントを読んでフンフンと思って作ってしまうんだろうけど、そこはほらド素人なせいなのかなかなかうまくいかなかった。

##QtCreator最高？
結局.proとかqmldirとかの使い方をよくわかってないので、一から書き起こすのは得策じゃなさそう。
ということで、基本的な部分はQtCreatorに任せてしまうことにした。プロジェクトのテンプレートに"Qt Quick 2 Extension Plugin"ってのがあって、どうやらこれを使えばいいっぽい。
![Screenshot_20211226_104605.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57538/0a04ec0a-7c0b-b83d-685e-3c49d9ef564a.png)
![Screenshot_20211226_104634.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57538/939601ca-7a3c-78ed-0753-0a758f9c3b5d.png)
![Screenshot_20211226_104658.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57538/8a229eac-4378-b484-f408-a7102cf1044a.png)
![Screenshot_20211226_104711.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57538/5d2a4683-ad19-551e-9ae6-4a3cdf628c3d.png)
![Screenshot_20211226_104725.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57538/fb182f3d-ce03-a634-540a-5e8ccf8aee4f.png)
これで必要なソースのベースが一式揃う。
とりあえず動かすだけならあんまりいろいろいじらなくて大丈夫。jsonlistmodel.hとjsonlistmode.cppの中身について、[以前作ったカスタムエレメント](https://qiita.com/helicalgear/items/2beb08e1333854fdebda)の中身をほぼそのまま移してみた。

```cpp:jsonlistmodel.h
#ifndef JSONLISTMODEL_H
#define JSONLISTMODEL_H

#include <QQuickItem>
#include <QAbstractListModel>

class JsonListModel : public QAbstractListModel
{
    Q_OBJECT
    Q_PROPERTY(QString json READ json WRITE setJson)
    Q_PROPERTY(QString query READ query WRITE setQuery)
    Q_PROPERTY(QStringList roles READ roles WRITE setRoles)
    Q_DISABLE_COPY(JsonListModel)

public:
    //
    int rowCount(const QModelIndex &parent) const;
    QHash<int, QByteArray> roleNames() const;
    QVariant data(const QModelIndex &index, int role) const;
    //
    QString json() const;
    void setJson(const QString &json);
    QString query() const;
    void setQuery(const QString &query);
    QStringList roles() const;
    void setRoles(const QStringList &roles);
    //
    explicit JsonListModel(QObject *parent = 0);

private:
    QString m_json;
    QString m_query;
    QStringList m_roles;
    QList<QHash<int, QVariant>> m_jsonList;
};

#endif // JSONLISTMODEL_H
```
```cpp:jsonlistmodel.cpp
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include "jsonlistmodel.h"

JsonListModel::JsonListModel(QObject *parent) : QAbstractListModel(parent)
{
}

QString JsonListModel::json() const
{
    return m_json;
}

void JsonListModel::setJson(const QString &json)
{
    m_json = json;
    QJsonDocument jsonDoc = QJsonDocument::fromJson(m_json.toUtf8());
    QJsonObject jsonObj = jsonDoc.object();
    QJsonArray jsonArray = jsonObj[m_query].toArray();
    foreach (const QJsonValue & value, jsonArray) {
        QJsonObject obj = value.toObject();
        QHash<int, QVariant> jsonData;
        beginInsertRows(QModelIndex(), m_jsonList.size(), m_jsonList.size());
        for ( int i = 0; i < m_roles.size(); i++ ) {
            jsonData.insert(i, obj[m_roles[i]].toVariant());
        }
        m_jsonList.append(jsonData);
        endInsertRows();
    }
}

QString JsonListModel::query() const
{
    return m_query;
}

void JsonListModel::setQuery(const QString &query)
{
    m_query = query;
}

QStringList JsonListModel::roles() const
{
    return m_roles;
}

void JsonListModel::setRoles(const QStringList &roles)
{
    m_roles = roles;
}

int JsonListModel::rowCount(const QModelIndex &) const
{
    return m_jsonList.size();
}

QHash<int, QByteArray> JsonListModel::roleNames() const
{
    QHash<int, QByteArray> ret;
    for ( int i = 0; i < m_roles.size(); i++ ) {
        ret.insert(i, m_roles[i].toUtf8());
    }
    return ret;
}

QVariant JsonListModel::data(const QModelIndex &index, int role) const
{
    return m_jsonList[index.row()][role];
}
```
これをとりあえずビルドしてインストールしてみる。


```sh
$ mkdir build
$ cd build
$ qmake ../JsonListModel
$ make
$ sudo make install
```
とりあえずなんとなくこれで動いたりするっぽい。
以前作ったqmlのサンプルはAPIのサービスが終了した関係でそのままだと動かないので、互換のAPIに切り替えて動かしてみる。

```qml:main.qml
import QtQuick 2.15
import QtQuick.Window 2.15
import JsonListModel 1.0

Window {
    visible: true
    width: 480
    height: 240
    title: qsTr("東京の天気")

    JsonListModel {
        id: jsonListModel
        query: "forecasts"
        roles: ["dateLabel", "telop", "image", "temperature"]
    }

    ListView {
        id: listView
        anchors.fill: parent

        model: jsonListModel

        delegate: Rectangle {
            width: parent.width
            height: 72
            border.color: "#FF000000"
            border.width: 1
            radius: 5
            Row {
                anchors.fill: parent
                padding: 2
                Text {
                    width: parent.width / 3
                    height: parent.height
                    font.pixelSize: 24
                    horizontalAlignment: Text.AlignHCenter
                    verticalAlignment: Text.AlignVCenter
                    text: dateLabel
                }
                Column {
                    width: parent.width / 3
                    Image {
                        width: 48
                        height: 48
                        anchors.horizontalCenter: parent.horizontalCenter
                        fillMode: Image.PreserveAspectFit
                        source: image.url
                    }
                    Text {
                        anchors.horizontalCenter: parent.horizontalCenter
                        font.pixelSize: 16
                        horizontalAlignment: Text.AlignHCenter
                        text: telop
                    }
                }
                Column {
                    width: parent.width / 3
                    anchors.verticalCenter: parent.verticalCenter
                    Text {
                        text: "最高気温：%1℃".arg(defined(temperature.max)&&defined(temperature.max.celsius) ? temperature.max.celsius : "---")
                    }
                }
            }
        }
    }

    function getWeather() {

        var xhr = new XMLHttpRequest();
        xhr.open("GET", "https://weather.tsukumijima.net/api/forecast?city=%1".arg("130010"), true);
        xhr.onreadystatechange = function() {
            if (xhr.readyState === 4) {
                switch (xhr.status) {
                case 200:
                    jsonListModel.json = xhr.responseText;
                    break;
                default:
                    console.debug("Something wrong to get the Weather data.");
                    break;
                }
            }
        }
        xhr.send();
    }

    function defined(obj) { return typeof obj !== 'undefined' }

    Component.onCompleted: getWeather()

}
```
とりあえず結果はこんな感じ
![Screenshot_20211226_151029.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/57538/4b8a4ce8-10d7-87fa-dca1-3dff07419814.png)
便利！！
QtCreatorは、もちろん、IDEとしてもいろいろ便利ではあるけれど、ちょっぴりわかりにくい周辺ファイルとかも一式ベースを提示してくれるのでとっても楽ちん。細かいところはちゃんと手を入れないと駄目なんだと思うけど、基本的な部分を動かしたいだけならテンプレートにおまかせてそこそこ行ける。

##というわけで
来年はこの辺りをベースに少しずつ動くものを増やしていけるといいなぁ。まぁ、もうそれなりの年齢なのでボケ防止のためにも少しずつなんか書いていくことにしよう。