伝説のツール「NotesPeek」をQtでリメイクする(その8・更新日時)
日時を扱うクラスを設計してから少し時間がたちましたが、NSFinderで実際に活用してみたいと思います。
Mac版
ntlxライブラリのソースコードリポジトリはこちらです。タグ「v0.1.3」をチェックアウトしてください。
https://tkondoh2@bitbucket.org/tkondoh2/ntlx.git
NSFinder本体のソースコードリポジトリはこちらです。タグ「v0.9.2」をチェックアウトしてください。
https://tkondoh2@bitbucket.org/tkondoh2/nsfinder.git
今回のリリースでは、日時を扱うクラスTimeDateを使って、データベースの更新日時、文書の更新日時を取得してみます。
TimeDateクラス
TimeDateクラスはntlxライブラリの中で定義しています。ソースコードはリポジトリを参考にしてください。
#ifndef NTLX_TIMEDATE_H #define NTLX_TIMEDATE_H #include "ntlx_global.h" #include "ntlx/status.h" #if defined(NT) #pragma pack(push, 1) #endif #include <misc.h> #include <ostime.h> #if defined(NT) #pragma pack(pop) #endif class QDateTime; class QDate; class QTime; namespace ntlx { class Lmbcs; class NTLXSHARED_EXPORT TimeDate { public: /** * @brief デフォルトコンストラクタ */ TimeDate(); /** * @brief TIMEDATE構造体を元にしたコンストラクタ * @param value TIMEDATE型の日時値 */ TimeDate(const TIMEDATE& value); /** * @brief コピーコンストラクタ * @param other コピー元 */ TimeDate(const TimeDate& other); /** * @brief 代入演算子 * @param other 代入元 * @return 自身への参照 */ TimeDate& operator=(const TimeDate& other); /** * @brief 現在の日時を取得する * @return 現在の日時データ */ static TimeDate current(); /** * @brief 最小値(特殊値)を取得する * @return 最小値(特殊値) */ static TimeDate minimum(); /** * @brief 最大値(特殊値)を取得する * @return 最大値(特殊値) */ static TimeDate maximum(); /** * @brief ワイルドカード(特殊値)を取得する * @return ワイルドカード(特殊値) */ static TimeDate wildcard(); /** * @brief 値をQDateTime型に変換して取得する * @return QDateTime型値 */ QDateTime toQDateTime() const; /** * @brief 値をLMBCSテキストに変換する * @param intlFormat 国際フォーマット * @param textFormat テキストフォーマット * @param status 結果ステータス * @return Lmbcsオブジェクト */ Lmbcs toLmbcs( void* intlFormat = nullptr , TFMT* textFormat = nullptr , Status* status = nullptr ) const; /** * @brief QDateTime型の値をTimeDateに変換して取得する * @param from QDateTime型値 * @return TimeDate型値 */ static TimeDate fromQDateTime(const QDateTime& from); /** * @brief LMBCSテキストからTimeDate型値を取得する * @param lmbcs Lmbcsオブジェクト * @param intlFormat 国際フォーマット * @param textFormat テキストフォーマット * @param status 結果ステータス * @return TimeDate型値 */ static TimeDate fromLmbcs( Lmbcs& lmbcs , void* intlFormat = nullptr , TFMT* textFormat = nullptr , Status* status = nullptr ); TIMEDATE value_; protected: template<WORD T> static TimeDate getConstant() { TimeDate td; TimeConstant(T, &td.value_); return td; } }; } // namespace ntlx #endif // NTLX_TIMEDATE_H
・・・不要な前方宣言(class QDate, class QTime)が残っていました。すみません。
メソッドについては、コメントを参考にしてください。メンバ変数value_は、プライベートにしていません。これは、Notes APIの利便性を優先させました。TIMEDATE構造体を引数に取るAPIは多く、それらを毎回、TIMEDATE型変数定義、APIコール、TimeDateオブジェクトに変換、としていては効率がよくありません。パブリックであれば、TimeDateオブジェクトを定義して、そのままメンバをAPIの引数にすればいいことになります。今回は、この方針で行きます。
これである程度、日時型変数が扱いやすくなったので、このTimeDateクラスを用いて、更新日時を取得し、UIに表示してみます。
データベースの更新日時
まずはデータベースの更新日時です。データベースの更新日時は、NSFDbModifiedTime関数を呼び出します。
#include <nsfdb.h> STATUS LNPUBLIC NSFDbModifiedTime (DBHANDLE hDB, TIMEDATE far *retDataModified, TIMEDATE far *retNonDataModified);
この関数では、データベースハンドルを引数にして、文書の最終更新日時(retDataModified)、設計の最終更新日時(retNonDataModified)を取得することができます。
これを今回は、DatabaseクラスにgetModifiedPairメソッドとして定義します。
#include <ntlx/database.h> /** * @brief 更新日付を取得する * @return 文書(1st)と設計(2nd)の更新日時 */ QPair<ntlx::TimeDate, ntlx::TimeDate> ntlx::Database::getModifiedPair();
戻り値はQPairコンテナを使っています。QPairはC++のstd::pairのQt版で、1対のデータをまとめて扱えるテンプレートクラスです。コメントにもあるように、1つめを文書、2つめを設計の更新日時として取得できます。例えば、以下のように書くことができます。
QPair<TimeDate, TimeDate> modified = db.getModifiedPair(); cout << "Document: " << modified.first.toLmbcs().data() << ", Design: " << modified.second.toLmbcs().data() << std:endl;
firstやsecondは、QPair<T1,T2>として宣言した場合、firstがT1型、secondがT2型となるパブリック変数です。
文書の更新日時
文書の更新日時は、NSFNoteGetInfo関数を用いて取得します。この関数は、更新日時以外に作成日時、アクセス日時、ID情報などが取得できます。今回、この説明については後日にしたいので、この関数は用いず、フィールドからの取得にしたいと思います。
今までのところ、文書から取得できるデータは、256バイト以下(LMBCS換算)の文字列のみで、これを文書のフォーム名取得に使用しました。文書から取得できるデータの種類を少し広げて、日時、日時リスト、数値(小数点数、整数)まで扱えるようにします。数値に関しては今回、活用する場面はありませんが、あまり難しくないので、さくっと実装してしまいます。
#include <ntlx/note.h> /** * @brief アイテムの日時データを取得する * @param itemName アイテム名 * @return 日時データ */ ntlx::TimeDate ntlx::Note::getTimeDate(const QString& itemName); /** * @brief アイテムの日時リストデータを取得する * @param itemName アイテム名 * @return 日時リストデータ */ QList<ntlx::TimeDate> ntlx::Note::getTimeDateList(const QString& itemName); /** * @brief アイテムの浮動小数点数データを取得する * @param itemName アイテム名 * @param defaultValue データが取得できない時のデフォルト値 * @return 浮動小数点数データ(倍精度) */ double ntlx::Note::getFloat(const QString& itemName, const double defaultValue = 0.); /** * @brief アイテムの整数データを取得する * @param itemName アイテム名 * @param defaultValue データが取得できない時のデフォルト値 * @return 整数データ(long幅) */ long ntlx::Note::getLongInt(const QString& itemName, const long defaultValue = 0);
数値の実装についてはリポジトリを参照してください。ここでは、日時データと日時リストデータの取得について説明します。
単一の日時データを取得するには、NSFItemGetTime関数を使います。
TimeDate Note::getTimeDate(const QString &itemName) { TIMEDATE td; open(); if (!NSFItemGetTime( handle_ , Lmbcs::fromQString(itemName).constData() , &td)) TimeConstant(TIMEDATE_WILDCARD, &td); return TimeDate(td); }
リポジトリ更新のタイミングで間に合っていませんが、後日、以下のように変更する予定です。
TimeDate Note::getTimeDate(const QString &itemName) { TimeDate td; open(); if (!NSFItemGetTime( handle_ , Lmbcs::fromQString(itemName).constData() , &td.value_)) TimeConstant(TIMEDATE_WILDCARD, &td.value_); return td; }
itemNameフィールドの日時データを取得します。取得に失敗した場合は、ワイルドカードを返します。
日時のリストデータを取得する場合、汎用フィールド取得関数NSFItemInfoを使ってブロックIDを取得し、日時データリストの構造を解析しながら取得していきます。
QList<TimeDate> Note::getTimeDateList(const QString &itemName) { QList<TimeDate> list; if (open().lastStatus().failure()) return list; Lmbcs lmbcsItem = Lmbcs::fromQString(itemName); BLOCKID bidItem, bidValue; WORD type; DWORD valueLen; lastStatus_ = NSFItemInfo( handle_ , lmbcsItem.constData() , lmbcsItem.size() , &bidItem , &type , &bidValue , &valueLen ); switch (lastStatus_.error()) { case NOERROR: { switch (type) { case TYPE_TIME_RANGE: { char* pValue = OSLockBlock(char, bidValue) + sizeof(WORD); RANGE* pRange = reinterpret_cast<RANGE*>(pValue); USHORT count = pRange->ListEntries; pValue += sizeof(RANGE); for (USHORT i = 0; i < count; ++i) { TimeDate td(*reinterpret_cast<TIMEDATE*>(pValue)); list.append(td); pValue += sizeof(TIMEDATE); } OSUnlockBlock(bidValue); } break; } } break; } return list; }
フィールドを取得する基本的なプロセスは、Note::getFormulaと同じです。NSFItemInfo関数でブロックIDを取得し、フィールドの種類を判別し、種類に応じた形式でポインタを操作しながら、目的のデータを取得していきます。
日時のリストは少々特殊な形をしています。日時リストはTYPE_TIME_RANGEという値で判別します。アイテムデータの先頭にはRANGE型という構造体が存在し、2つの配列の格納数が示されます。
#include <global.h> typedef struct { USHORT ListEntries; /* list entries following */ USHORT RangeEntries; /* range entries following */ /* now come the list entries */ /* now come the range entries */ } RANGE;
ListEntriesは日時(TIMEDATE型)の配列のエントリー数です。一方のRangeEntriesは、Rangeと書かれていますが、実際にはTIMEDATE_PAIR型の配列のエントリー数です。
#include <global.h> typedef struct { /* a timedate range entry */ TIMEDATE Lower; TIMEDATE Upper; } TIMEDATE_PAIR;
TIMEDATE_PAIRは、「日時範囲」という位置づけのデータで、「何時から何時まで」や「何日から何日まで」というデータをひとくくりとして扱います。
これらをまとめると、日時リストフィールドのデータ構造は、例えば以下のようになります。
この構造に沿って、目的のデータを取得します。今回の日時リストでは、日時範囲リストは扱わないので、いったん無視することにします。
NSFinderへの実装
ここまで仕様を拡張したDatabaseクラス、Noteクラスを使って、実際にNSFinderに実装してみます。
現在、NSFinderに表示しているデータはツリー構造と、データベースのパス、タイトル、文書のIDとウィンドウタイトルです。列が足りないので、TreeModel::initializeメソッドで列を増やし、汎用的なヘッダを付けます。
// ヘッダーラベルを設定する setHorizontalHeaderLabels( QStringList() << tr("Items") << tr("Parameter 1") << tr("Parameter 2") << tr("Parameter 3") );
あとは、TreeItem::collectFiles、TreeItem::collectNotesに以下のようなメソッドを追加していきます。
void TreeItem::collectFiles() { ... // 取得したファイル情報を子アイテムに展開する for (auto it = files.begin(); it != files.end(); ++it) { QList<QStandardItem*> items; ntlx::FileMap& data = *it; ... ntlx::Database db(ntlx::PathSet(newPath, server, port)); QPair<ntlx::TimeDate, ntlx::TimeDate> modTimes = db.getModifiedPair(); items << new TreeItem( DataType::None , modTimes.first.toLmbcs().toQString() ) << new TreeItem( DataType::None , modTimes.second.toLmbcs().toQString() ); appendRow(items); } } void TreeItem::collectNotes() { ... for (auto it = idTable.begin(); it != idTable.end(); ++it) { QList<QStandardItem*> items; ... QList<ntlx::TimeDate> revisions = note.getTimeDateList(ITEM_NAME_REVISIONS); if (note.lastStatus().success() && !revisions.isEmpty()) items << new TreeItem( DataType::None , revisions.last().toLmbcs().toQString() ) << new TreeItem( DataType::None , revisions.first().toLmbcs().toQString() ); appendRow(items); } }
collectNotesメソッドでは、NSFNoteGetInfo関数を使う代わりに、ITEM_NAME_REVISIONSアイテムから更新日時を取得しています。実体は"$Revisions"という名前で、Notesが自動で更新日時をリストとして追記していくフィールド名です。このフィールドから日時リストが取得できたら、最後の値を更新日時、最初の値を作成日時として列アイテムに追加していきます。
最後に
NSFinderプロジェクトを始めて、扱えるデータ型を増やしてきました。このまま列を増やしていくこともできますが、列のみの対応ではいずれ限界が来ます。また、データベース、文書、フィールドと階層を落とすごとに列の整合性も取りづらくなってきます。
次回はいったんNotes APIをはなれ、NotesPeekのようなツリー構造ペインと、データ表示のためのプロパティペインとに分けたUIに変更していこうと思います。