[farman] FolderModel 再実装

ようやく体調が落ち着いてきたので、プログラミング再開。

QDirModel と QFileSystemModel (+ QSortFilterProxyModel)

今まではソート条件によっては、親ディレクトリを示す ".." が一番上に表示されない場合があり、ファイラとしてはちょっと使いづらいと感じていた。
そこでどうにかして、いかなるソート条件であっても ".." を一番上に表示しようと思って調べていたが、従来の QDirModel を使っている限り、それを実現するのは難しそうだった。
というか、調べていて今更ながら気づいたのだが、QDirModel はとうの昔に obsolete になっており、その代替手段として QFileSystemModel を使うらしい。
ところが QFileSystemModel にはフィルタはあるもののソート機能はない。
どうするのかというと、QSortFilterProxyModel と組み合わせることで、ソート機能を実現するようだ。
QSortFilterProxyModel では、ソートの際の比較処理である lessThan() というメソッドをオーバーライドできるようになっている。
なので lessThan() を自分の都合のいいように実装すれば、どういうソート条件であれ(理論上は)実現可能というわけだ。

ということで、元々 QDirModel の派生クラスとして実装していた FolderModel を、QFileSystemModel と QSortFilterProxyModel を組み合わせたものに置き換えることにした。

テストアプリ

まずは FolderModel のテスト実装として、↓こんなアプリを作った。
スクリーンショット 2018-04-30 17.26.32.png
ソースコードは https://github.com/haraki/FolderModelTest で公開している。

QFileSystemModel と QSortFilterProxyModel を組み合わせた FolderModel を定義し、QTableView で表示している。

フォルダをダブルクリックすると移動できる。
またヘッダの各項目をクリックすると、その項目の昇順・降順でソートできる。
アプリ下部にある設定だが、左側は上から
  • 大文字・小文字を区別する / しない
  • ".." を一番上に表示する / しない
  • 隠しファイルを表示する / しない
  • フィルタ(ワイルドカードが使える)
右側はフォルダの表示方法だが、上から順に
  • 順不同
  • リストの最初に表示
  • リストの最後に表示
と切り替えられるようになっている。

実装

QFileSytemModel と QSortFilterProxyModel をどう持たせるかで悩んだが、最終的に QSortFilterProxyModel の派生クラスとして FolderModel を定義し、QFileSystemModel のインスタンスを FolderModel のメンバ変数として持ち、QSortFilterProxyModel::setSourceModel() に渡すのが一番手っ取り早く実装できそうだったので、そのようにした。

また、QFileSystemModel の持つ public なメソッドをそのまま FolderModel のメソッドとして使えるようにした。
この時、引数として QModelIndex がある場合に一つ注意しなければならないことがあった。
引数の QModelIndex は、QSortFilterProxyModel 管理下にある、外部向け(?)の index であり、QFileSystemModel で使用するためには index をそれ用に変換してやらなければならない。
そのためのメソッドが QSortFilterProxyModel::mapToSource() である。
要は index を SourceModel である QFileSystemModel 用にマップし直す、ということだろうか。
また、QFileSystemModel のメソッドから返された QModelIndex を戻り値とする場合にも、QSortFilterProxyModel::mapFromSource() を使って、外部向けの index に変換する必要がある。

それと、QSortFilterProxyModel::lessThan() をオーバーライドした。
"." 及び ".." を必ず先頭に持ってくるフラグ dotFirst_ と、sortFlags_ のうちフォルダを先頭もしくは後方に移動するフラグ QDir::DirsFirst / DirsLast をチェックするようにしている。
この後の処理については、このテストアプリではソート用のフラグを QSortFilterProxyModel のを使用せず自前で持っているため、全て自前で実装しているが、そうでなければ QSortFilterProxyModel::lessThan() を呼び出して、そちらに委ねてもいいかも知れない。

何だか思った以上に長くなってきたので、続きはまた別記事で。

この記事へのコメント