QAbstractItemModelを実装してツリービューを作る(Read Only編)

# はじめに
この記事はQtが提供している [QFileSystemModel](http://doc.qt.io/qt-5/qfilesystemmodel.html)のように、データモデルを[QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)から自作する際に必要なことをまとめたメモです。

## QAbstractItemModelとQModelIndex
[QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)は、`QListView`、`QTableView`、`QTreeView`などのビュークラスに対するモデルのクラスです。行(row)、列(column)、親(parent)の3つの情報でアイテムを特定する柔軟な構造になっていてリスト・表・木をまとめて扱えます。

![image](https://qiita-image-store.s3.amazonaws.com/0/31974/ead8fc72-a78a-20fe-2d7c-a88b2ba1269e.png)
([Qt Documentation - Model/View Programming](http://doc.qt.io/qt-5/model-view-programming.html)から引用)

モデル中のアイテムを特定する3つの情報をまとめてインデックスと呼び、[QModelIndex](http://doc.qt.io/qt-5/qmodelindex.html)オブジェクトで表現します。デフォルトコンストラクタで作成した`QModelIndex()`は無効なインデックスを表し、特別な意味を持っています。

* ルートアイテムのインデックスを表す
    * あるアイテムの親の`QModelIndex`が無効な場合、そのアイテムがトップレベル(ルートアイテムの子)であることを表します
* 無効なアイテムを表す
    * 親以外で無効なインデックスが返った場合、そのようなアイテムが無いことを表します

[QModelIndex](http://doc.qt.io/qt-5/qmodelindex.html)は、内部表現のポインタ(`internalPointer`)またはID(`internalId`)を格納できます。

## 基本クラスを決める
表現するデータ構造がリストの場合は[QAbstractListModel](http://doc.qt.io/qt-5/qabstractlistmodel.html)、表の場合は[QAbstractTableModel](http://doc.qt.io/qt-5/qabstracttablemodel.html)が使用できます。これらは[QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)の派生クラスで、メンバー関数をある程度実装してくれています。

データ構造が木の場合は[QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)を直接拡張します。

|データ構造 |基本クラス|
|----|-----|
|リスト|[QAbstractListModel](http://doc.qt.io/qt-5/qabstractlistmodel.html) または [QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)|
|表|[QAbstractTableModel](http://doc.qt.io/qt-5/qabstracttablemodel.html) または [QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)|
|木|[QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)|

# サンプルの設計

サンプルとしてウィジェットの階層構造を表すツリーモデル`WidgetHierarchyModel`を作成します。仕様は以下の通りです。

* `QWidget*`を直接`internalPointer`に格納
* 列数は1
* 行数は子ウィジェットの数
* トップレベルのアイテム(ウィジェット)は1つ、メンバー変数`topWidget`で保持
* 各アイテムの文字列表現はウィジェットのクラス名

# 仮想関数をオーバーライドする
[QAbstractItemModel](http://doc.qt.io/qt-5/qabstractitemmodel.html)が持つ仮想関数は主に3種類に分けられます。

* アイテムデータハンドリング(必須)  
    アイテムの個数や内容を読み書きする関数
* ナビゲーションとインデックス生成(必須)  
    モデル内のインデックスを表す`QModelIndex`オブジェクトを生成する関数
* ドラッグアンドドロップサポート  
    今回は扱いません

## アイテムデータハンドリング(必須)

読み取り専用の場合、[data()](http://doc.qt.io/qt-5/qabstractitemmodel.html#data)、[rowCount()](http://doc.qt.io/qt-5/qabstractitemmodel.html#rowCount)、[columnCount()](http://doc.qt.io/qt-5/qabstractitemmodel.html#columnCount)を実装します。これらは純粋仮想関数なので実装は必須です。

ヘッダーを出せるようにするには[headerData()](http://doc.qt.io/qt-5/qabstractitemmodel.html#headerData)を実装します(基本的にはした方が良いでしょう)。

[flags()](http://doc.qt.io/qt-5/qabstractitemmodel.html#flags)は、各アイテムのフラグ(編集可能か、選択可能か、など)を返す関数です。デフォルトの実装では`Qt::ItemIsEnabled | Qt::ItemIsSelectable`を返します。

### data()
```cpp
QVariant QAbstractItemModel::data(const QModelIndex &index, int role = Qt::DisplayRole) const
```

アイテムのデータを返す関数です。

第1引数indexは要求されているデータのインデックスです。インデックスが無効なら範囲外です。

第2引数roleは返すデータの使用目的です。ツールチップ用の文字列を別に返したい場合などはこれで分岐します。最低でも`Qt::DisplayRole`の場合は値を返さなければ実用性がありません。

今回は`role`は`Qt::DisplayRole`のみ対応することにし、ウィジェットのクラス名を返します。`internalPointer()`からの`static_cast`は頻出なのでラップしました。

```cpp:WidgetHierarchyModel.cpp
QWidget* widget(const QModelIndex &index) const
{
    return static_cast<QWidget*>(index.internalPointer());
}

QVariant data(const QModelIndex &index, int role) const override
{
    if( role != Qt::DisplayRole || !index.isValid() ) return QVariant();
    return widget(index)->metaObject()->className();
}
```

### headerData()
```cpp
QVariant QAbstractItemModel::headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const
```
`orientation`は`Qt::Horizontal`または`Qt::Vertical`です。それに合わせて、`section`は列または行を表します。

今回は固定値を返します。

```cpp:WidgetHierarchyModel.cpp
QVariant WidgetHierarchyModel::headerData(int, Qt::Orientation orientation, int role) const override
{
    if( orientation != Qt::Horizontal || role != Qt::DisplayRole ) return QVariant();
    return QString("Widget Hierarchy");
}
```

### rowCount()・columnCount()
```cpp
int QAbstractItemModel::rowCount(const QModelIndex &parent = QModelIndex()) const
int QAbstractItemModel::columnCount(const QModelIndex &parent = QModelIndex()) const
```
`parent`を親に持つアイテムの行数・列数を返します。`parent`が無効なインデックスの場合、親がない(トップレベルである)ことを表します。

`WidgetHierarchyModel`では行数を子ウィジェットの数とし、列数は1とします。ただし、ルート直下の行数は1とします。

`findChildren`は頻出のためラップしました。

```cpp:WidgetHierarchyModel.cpp

QList<QWidget*> WidgetHierarchyModel::childrenOf(const QWidget* parent)
{
    return parent->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly);
}

int WidgetHierarchyModel::rowCount(const QModelIndex &parent) const override
{
    return parent.isValid() ? childrenOf(widget(parent)).size() : 1;
}

int WidgetHierarchyModel::columnCount(const QModelIndex &) const override
{
    return 1;
}
```

## ナビゲーションとインデックス生成(必須)

インデックスから親・子のインデックスを生成する関数です。便利なファクトリー関数`createIndex`を使ってインデックスを作成します。ここで第3引数`ptr`に渡したポインタが`internalPointer`になります。

```cpp
QModelIndex QAbstractItemModel::createIndex(int row, int column, void *ptr = nullptr) const
```

`WidgetHierarchyModel`では`row`は0に固定です。

### index()
```cpp
QModelIndex QAbstractItemModel::index(int row, int column, const QModelIndex &parent = QModelIndex()) const
```
`row`、`column`、`parent`で特定されるアイテムのインデックスを返します。`parent`が無効なインデックスの場合、親がない(トップレベルである)ことを表します。

`WidgetHierarchyModel`では親ウィジェットに対して`findChildren`して取得したリスト中の`row`番目を返します。

```cpp
QModelIndex WidgetHierarchyModel::index(int row, int column, const QModelIndex &parent) const override
{
    if( !parent.isValid() )
    {
        // トップレベルは0行0列目だけアイテムがある
        if( row == 0 && column == 0 )
        {
            return createIndex(0, 0, topWidget);
        }
        return QModelIndex();
    }
    // 列は1列だけ
    if( column != 0 || parent.column() != 0 )
    {
        return QModelIndex();
    }
    QList<QWidget*> children = childrenOf(widget(parent));
    return row < children.size() ? createIndex(row, 0, children.at(row)) : QModelIndex();
}
```

### parent()
```cpp
QModelIndex QAbstractItemModel::parent(const QModelIndex &index) const
```
`index`が指すアイテムの親のインデックスを返します。`index`が指すアイテムがトップレベルの場合はルートのインデックス(無効なインデックス)を返します。

`createIndex`を呼ぶには、親アイテムの中で`index`が指すアイテムが何行何列にあるのか求める必要があります。

`WidgetHierarchyModel`では`QList`の`indexOf`で求めます。

```cpp:WidgetHierarchyModel.cpp
QModelIndex WidgetHierarchyModel::parent(const QModelIndex &index) const override
{
    if( index.isValid() )
    {
        QWidget* self = widget(index);
        if( self != topWidget )
        {
            QWidget* parent = self->parentWidget();
            int row = childrenOf(parent).indexOf(self);
            if( row > -1 )
            {
                return createIndex(row, 0, parent);
            }
        }
    }
    return QModelIndex();
}
```

# 完成

モデルを`QTreeView`に設定して動作確認した結果がこちらです。
![image](https://qiita-image-store.s3.amazonaws.com/0/31974/22f60a13-257b-6ac7-c0de-a1b2a4cd706c.png)

全コードは以下の通りです。

```cpp:WidgetHierarchyModel.cpp
#include <QApplication>
#include <QVBoxLayout>
#include <QTreeView>

class WidgetHierarchyModel : public QAbstractItemModel
{
   public:
    WidgetHierarchyModel(QWidget* topWidget):topWidget(topWidget)
    {
    }

    QWidget* widget(const QModelIndex &index) const
    {
        return static_cast<QWidget*>(index.internalPointer());
    }

    QVariant data(const QModelIndex &index, int role) const override
    {
        if( role != Qt::DisplayRole || !index.isValid() ) return QVariant();
        return widget(index)->metaObject()->className();
    }

    QVariant headerData(int, Qt::Orientation orientation, int role) const override
    {
        if( orientation != Qt::Horizontal || role != Qt::DisplayRole ) return QVariant();
        return QString("Widget Hierarchy");
    }

    int rowCount(const QModelIndex &parent) const override
    {
        return parent.isValid() ? childrenOf(widget(parent)).size() : 1;
    }

    int columnCount(const QModelIndex &) const override
    {
        return 1;
    }

    QModelIndex index(int row, int column, const QModelIndex &parent) const override
    {
        if( !parent.isValid() )
        {
            if( row == 0 && column == 0 )
            {
                return createIndex(0, 0, topWidget);
            }
            return QModelIndex();
        }
        if( column != 0 || parent.column() != 0 )
        {
            return QModelIndex();
        }
        QList<QWidget*> children = childrenOf(widget(parent));
        if( row < children.size() )
        {
            return createIndex(row, 0, children.at(row));
        }
        return QModelIndex();
    }

    QModelIndex parent(const QModelIndex &index) const override
    {
        if( index.isValid() )
        {
            QWidget* const self = widget(index);
            if( self != topWidget )
            {
                QWidget* const parent = self->parentWidget();
                int row = childrenOf(parent).indexOf(self);
                if( row > -1 )
                {
                    return createIndex(row, 0, parent);
                }
            }
        }
        return QModelIndex();
    }

  private:
    static QList<QWidget*> childrenOf(const QWidget* parent)
    {
        return parent->findChildren<QWidget*>(QString(), Qt::FindDirectChildrenOnly);
    }

    QWidget* topWidget;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    auto window = new QWidget();
    auto layout = new QVBoxLayout();
    auto tree   = new QTreeView(window);

    layout->addWidget(tree);
    window->setLayout(layout);

    auto model = new WidgetHierarchyModel(tree);
    tree->setModel(model);
    tree->setHeaderHidden(false);
    window->show();

    a.exec();
    delete window;

    return 0;
}
```

# 参考文献
* [Qt Documentation - Model/View Programming](http://doc.qt.io/qt-5/model-view-programming.html)