見出し画像

Qt5再入門: FilePageサブクラスの作成

前回の記事で、FilePageというページクラスの親となるTabPageクラスを定義しました。今回は、これを元にFilePageサブクラスを作成していきます。

まずはヘッダーファイルです。

// filepage.h

#ifndef FILEPAGE_H
#define FILEPAGE_H

#include "tabpage.h"
#include <nxpp/nxpp_database.hpp>

/**
* @brief ファイルページクラス(DB、ディレクトリ用)
*/
class FilePage : public TabPage
{
 Q_OBJECT

 nxpp::Lmbcs path_;
 nxpp::Lmbcs server_;

public:
 /**
  * @brief コンストラクタ
  * @param path パス
  * @param server サーバ
  * @param parent 親ウィジェット
  */
 FilePage(
     const nxpp::Lmbcs &path,
     const nxpp::Lmbcs &server,
     QWidget *parent = nullptr
     );

 /**
  * @brief 同じファイルかどうか
  * @param path パス
  * @param server サーバ
  * @return 同じファイルパスなら真
  */
 bool equalsTo(
     const nxpp::Lmbcs &path,
     const nxpp::Lmbcs &server
     ) const;

 /**
  * @brief ページの書き込み
  */
 virtual void loadMainModel() override;

 /**
  * @brief ローダー関数オブジェクト用抽象クラス
  */
 class Loader {
 public:
   /**
    * @param pPage ページへのポインタ
    * @param dbPtr データベースポインタ
    */
   virtual void operator ()(FilePage *pPage, nxpp::DbPtr dbPtr) = 0;

   /**
    * @brief データベース/ディレクトリ共通のパス行追加
    * @param pPage ページへのポインタ
    * @param dbPtr データベースポインタ
    */
   void addPaths(FilePage *pPage, nxpp::DbPtr dbPtr);
 };

 /**
  * @brief アップデーター関数オブジェクト用抽象クラス
  */
 class Updater {
 public:
   /**
    * @param pPage ページへのポインタ
    * @param dbPtr データベースポインタ
    * @param item 更新されたアイテム(セル)
    */
   virtual void operator ()(
       FilePage *pPage,
       nxpp::DbPtr dbPtr,
       QStandardItem *item
       ) = 0;
 };

public slots:
 /**
  * @brief アイテム更新スロット
  * @param item 更新されたアイテム(セル)
  */
 void updateItem(QStandardItem *item);
};

#endif // FILEPAGE_H

FilePageクラスは、ブックマークに対応するページクラスなので、パスとサーバー名を受け取って内部に保持します。
また、一度開いたFilePageクラスは、同じ内容であれば別のオブジェクトとせず、すでに開いたページを再表示させたいので、同値性を確認できるようにequalsToというメソッドを用意しています。
loadMainModelメソッドは、TabPageクラスからのサブクラスとして実装必須の仮想関数です。

次に、FilePage::Loaderクラスはローダーとして機能する関数オブジェクトの抽象クラスです。ローダーとは、FilePageが持っているデータ(パスとサーバー名)を元に、メインモデルに対して行データを追加します。FilePage::loadMainModelが呼び出します。

さらにもう一つ、FilePage::Updaterクラスも同じく関数オブジェクト用抽象クラスですが、こちらはテーブル上でデータを更新した時に呼び出します。直後に定義されているスロットメソッド、updateItemから呼び出します。

次にソースファイルです。

// filepage.cpp

#include "filepage/directorypage.h"
#include "filepage/databasepage.h"
#include <nxpp/qt/nxpp_qt_lmbcs.hpp>

FilePage::FilePage(
   const nxpp::Lmbcs &path,
   const nxpp::Lmbcs &server,
   QWidget *parent
   )
 : TabPage(parent)
 , path_(path)
 , server_(server)
{
 // 更新シグナルをメインモデルのロードに接続
 connect(this, &TabPage::refresh,
         this, &FilePage::loadMainModel
         );

 // モデルの更新シグナルをファイルページのアイテム更新スロットに接続
 connect(
       mainModel(), &QStandardItemModel::itemChanged,
       this, &FilePage::updateItem
       );
}

bool FilePage::equalsTo(
   const nxpp::Lmbcs &path,
   const nxpp::Lmbcs &server
   ) const {
 return path == path_ && server == server_;
}

void FilePage::loadMainModel() {
 try {
   // データベース/ディレクトリを開く
   auto dbPtr = nxpp::Db::open(path_, server_);

   // ローダーポインタを作成
   QScopedPointer<Loader> loader;

   // 開いたファイルに応じてローダーを設定
   if (dbPtr->isDirectory()) {
     loader.reset(new DirectoryLoader());
   } else {
     loader.reset(new DatabaseLoader());
   }

   // メインモデルを初期化
   initMainModel();

   // ローダーを実行
   (*loader)(this, dbPtr);

   // ステータスを表示
   emit showStatus(tr("Loaded FilePage"));

   // コンソールに表示
   emit consoleLog(
         tr("Loaded file (%1:%2)")
         .arg(fromLmbcsQ(path_))
         .arg(fromLmbcsQ(server_))
         );
 }
 // Notesステータスがスローされたらそのエラーメッセージに変換して表示
 catch (nxpp::Status &status) {
   emit consoleLog(nxpp::qt::fromStatus(status.error()));
 }
 // それ以外の例外ならそのメッセージを表示
 catch (std::exception &ex) {
   std::string what(ex.what());
   emit consoleLog(QString::fromStdString(what));
 }
}

void FilePage::updateItem(QStandardItem *item) {
 try {
   // データベース/ディレクトリをオープン
   auto dbPtr = nxpp::Db::open(path_, server_);

   // アップデーターを作成
   QScopedPointer<Updater> updater;
   if (!dbPtr->isDirectory()) {
     updater.reset(new DatabaseUpdater());
   }
   if (updater.isNull()) return;

   // アップデータを実行
   (*updater)(this, dbPtr, item);

   // ステータスを表示
   emit showStatus(tr("Updated FilePage"));
 }
 // Notesステータスがスローされたらそのエラーメッセージに変換して表示
 catch (nxpp::Status &status) {
   emit consoleLog(nxpp::qt::fromStatus(status.error()));
 }
 // それ以外の例外ならそのメッセージを表示
 catch (std::exception &ex) {
   std::string what(ex.what());
   emit consoleLog(QString::fromStdString(what));
 }
}

void FilePage::Loader::addPaths(FilePage *pPage, nxpp::DbPtr dbPtr) {
 // データベース/ディレクトリのパスを取得する。
 auto paths = dbPtr->getPaths();

 // テーブルに追加するデータリストを作成する。
 QList<QPair<QString, QString>> list {
   { pPage->tr("Path/Canonical"), fromLmbcsQ(std::get<0>(paths)) },
   { pPage->tr("Path/Extended"), fromLmbcsQ(std::get<1>(paths)) }
 };

 // リストに沿ってテーブルに行を追加する。
 for (auto pair : list) {
   auto items = pPage->addRowToMainModel(pair.first, pair.second);
   items.first->setEditable(false);
   items.second->setEditable(false);
 }
}

最初に、コンストラクタではrefreshシグナルをFilePage::loadMainModelに接続しています。また、メインモデルでアイテムが更新された時は、自身のupdateItemスロットメソッドを呼び出すようにしています。
次のloadMainModelでは、まずFilePageが保持するパス、サーバー名という2つのデータを元にデータベースを開きます。開いた情報を元に、これはディレクトリか、データベースかが区別され、実装するローダーを切り替えています(DirectoryLoaderとDatabaseLoaderは次回紹介する予定です)。あとは、メインモデルを初期化し、ローダーを呼び出します。ローダーが正常に実行されればテーブルも更新されます。
次のupdateItemは、テーブル内のデータ(=モデルアイテム)が変更された時に呼び出されます。ここでもディレクトリか、データベースかがチェックされ、データベースの時はアップデータを作成して、これを実行します。
最後のLoader::addPathsは、データベース/ディレクトリ共通の、パス情報をテーブルに追加するメソッドになります。

まとめ

今回は、Notes C APIの要素も少し含まれていましたが、ページウィジェットとしてシグナル/スロットの取り回しやモデル/ビューなどがメインかなと思い、「Qt5再入門」として掲載しました。
次回は、データベース/ディレクトリに特化した情報を扱って、テーブルに情報を追加したり、変更した内容をデータベースに書き込んだりする作業をしていきます。

この記事が気に入ったらサポートをしてみませんか?