伝説のツール「NotesPeek」をQtでリメイクする(その10・リスト型/範囲型)

Notesの基本的な「型」は、テキスト(文字列)型、数値型、日時型になります。これら基本型は、いずれも複数値になることは皆さんご存じのことと思います。

  • テキストリスト型
  • 数値リスト型
  • 日時リスト型

このうちテキストリストは、LISTという構造体が使われています。

typedef struct {
  USHORT ListEntries; /* list entries following */
                      /* now come the list entries */
} LIST;

f:id:takahide-kondoh:20170613065006p:plain

この図は文字列リストのメモリ配置を示していて、LIST.ListEntries=3の場合を表しています。L1〜L3はUSHORT型で、文字列1〜3の長さを表しています。

LISTが適用できるのはテキスト型で、TYPE_TEXT_LISTという型で判別します。では、数値や日時ではLIST型でなく、何が使われているのでしょう。

実は数値と日時には、単純なリストはなく、「RANGE」という構造体で複数値を表します。型の判別にはTYPE_NUMBER_RANGE、TYPE_TIME_RANGEという値でそれぞれ判別します。それではRANGE構造体を見てみましょう。

typedef struct {
  USHORT ListEntries;   /* list entries following */
  USHORT RangeEntries;  /* range entries following */
                        /* now come the list entries */
                        /* now come the range entries */
} RANGE;

ListEntriesの他にRangeEntriesというメンバーが存在します。このRangeEntriesは、「範囲」リストの数を表します。

「範囲」という概念は、日時でいう「何日から何日」、「何時から何時」というものを表し、主にカレンダーや予定などで使用されます。Notes APIから見ると、日時型の範囲はTIMEDATE_PAIRという構造体で表されます。

typedef struct { /* a timedate range entry */
  TIMEDATE Lower;
  TIMEDATE Upper;
} TIMEDATE_PAIR;

RangeEntriesは、このTIMEDATE_PAIR型のリストのアイテム数を表していたんですね。一方で数値の方は、NUMBER_PAIRという構造体があります。

typedef struct {  /* a float range entry */
  NUMBER Lower;
  NUMBER Upper;
} NUMBER_PAIR;

このNUMBER_PAIRですが、Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミングによると「Notesクライアントからこれを入力することはできない」と書かれています。この本はNotes R5の頃に書かれていますが、現在のNotes 9でも数値範囲を入力できないのであれば、利用することはあまりないでしょう。ですが、APIでなら操作できるのであれば、扱えるようにしておこうと思います。

TYPE_NUMBER_RANGE、TYPE_TIME_RANGEのデータ構造は以下のようになります。

f:id:takahide-kondoh:20170613124053p:plain

せっかく数値のRANGEと日時のRANGEが同じ構造をしているので、C++テンプレートの機能を使って、実装してみることにしましょう。(続く)

伝説のツール「NotesPeek」をQtでリメイクする(その9・UI見直し)

今回はUIの見直しです。

前回まではツリー構造のビューにひたすら追加情報を列として表示していましたが、いろいろな情報が同じ列に表示していては必ず破綻するので、今のうちに、アイテムの情報を別のペインにしておきます。

Mac

f:id:takahide-kondoh:20170611172332p:plain f:id:takahide-kondoh:20170611172349p:plain f:id:takahide-kondoh:20170611172402p:plain

Ubuntu

f:id:takahide-kondoh:20170611172418p:plain f:id:takahide-kondoh:20170611172434p:plain f:id:takahide-kondoh:20170611172445p:plain

Windows

f:id:takahide-kondoh:20170611172455p:plain f:id:takahide-kondoh:20170611172508p:plain f:id:takahide-kondoh:20170611172521p:plain

ntlxライブラリのソースコードリポジトリこちらです。タグ「v0.1.4」をチェックアウトしてください。

https://tkondoh2@bitbucket.org/tkondoh2/ntlx.git

NSFinder本体のソースコードリポジトリこちらです。タグ「v0.9.3」をチェックアウトしてください。

https://tkondoh2@bitbucket.org/tkondoh2/nsfinder.git

今回は画面とソースコードの紹介にとどめ、次回以降に今回のUI見直しについて、ポイントをご紹介していきます。

伝説のツール「NotesPeek」をQtでリメイクする(その8・更新日時)

日時を扱うクラスを設計してから少し時間がたちましたが、NSFinderで実際に活用してみたいと思います。

Mac

f:id:takahide-kondoh:20170525074007p:plain

Ubuntu

f:id:takahide-kondoh:20170525074026p:plain

Windows

f:id:takahide-kondoh:20170525074036p:plain

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は、「日時範囲」という位置づけのデータで、「何時から何時まで」や「何日から何日まで」というデータをひとくくりとして扱います。

これらをまとめると、日時リストフィールドのデータ構造は、例えば以下のようになります。

f:id:takahide-kondoh:20170525093527p:plain

この構造に沿って、目的のデータを取得します。今回の日時リストでは、日時範囲リストは扱わないので、いったん無視することにします。

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に変更していこうと思います。

伝説のツール「NotesPeek」をQtでリメイクする(その7・日時その2)

f:id:takahide-kondoh:20170510213941p:plain

この構造体「TIME」は、Notes APIの日時型(TIMEDATE構造体)と、C/C++言語を介した他の日時型と相互に変換するための媒介の役目をします。

上図のように、この構造体には以下のようなデータが含まれます。

このほかに「GM」という値がありますが、これはTIMEDATEになります。

TIMEDATE構造体が表す日時データを他の日時に変換するためには、まずTIME::GM変数にその日時を代入します。その後、TimeGMToLocal関数を呼び出します。

#include <misc.h>

// 前提としてnow変数に現在の日時が入っているものとする。
// TIMEDATE now;
// OSCurrentTIMEDATE(&now);

TIME time;
time.GM = now;
TimeGMToLocal(&time);

すると、TIME::GMに格納された日時に基づいて、年月日などの他のフィールドにデータがパースされます。後はこのフィールドデータを元に、他の日時データを構築すればいいわけです。

TIME構造体を元に、Qtの日時型であるQDateTimeを作るには、以下のようにします(ただし、サマータイムは考慮しません)。

QDate qDate(time.year, time.month, time.day);
QTime qTime(time.hour, time.minute, time.second, time.hundredth * 10);
QDateTime qDateTime(qDate, qTime, Qt::LocalTime, time.zone * -3600);
// OUTPUT: qDateTime

まず、日付型のQDate、時間型のQTimeを作ります。TIMEに保存される1秒以下の時間単位(TIME::hundredth)は10ミリ秒なので、10倍して1ミリ秒単位に変換します。次に、QDate、QTimeを元にQDateTimeを作ります。デフォルトでTIMEDATEが表す日時はローカル時間なので、ローカル時間であることを指定します(Qt::LocalTime定数)。最後にタイムゾーンの指定ですが、TIMEDATE構造体では時間(Hour)単位である一方、Qtのタイムゾーンは秒単位です。また正負も逆なので、60分×60秒×-1=-3600を掛けた値を指定します。

次は、他の日時データをTIMEDATEに変換するプロセスを見ていきます。

先ほどと同様にTIME構造体で変数を作り、今度はGM以外のフィールドに値を埋めていきます。その後で、TimeLocalToGM関数を呼び出します。QDateTimeに変換元の日時がある場合を見ていきます。

#include <misc.h>

// INPUT: const QDateTime& qDateTime

TIME time;
QDate qDate = qDateTime.date();
QTime qTime = qDateTime.time();
time.zone = qDateTime.offsetFromUtc() / -3600;
time.dst = qDateTime.isDaylightTime() ? 1 : 0;
time.year = qDate.year();
time.month = qDate.month();
time.day = qDate.day();
time.weekday = qDate.dayOfWeek() == 7 ? 1 : (qDate.dayOfWeek() + 1);
time.hour = qTime.hour();
time.minute = qTime.minute();
time.second = qTime.second();
time.hundredth = qTime.msec() / 10;
TimeLocalToGM(&time);
// OUTPUT: time.GM

基本的にはTimeGMToLocal関数の逆のことをしています。サマータイム(TIME::dst)については、日本がサマータイムではないのでぴんときませんので推測の域を出ません。あしからず。

以上が、TIMEDATE型と他の日時型との相互変換に関する基本的なプロセスです。次回は、ここまでのAPIをTimeDateクラスにまとめ、データベースに関する日時データ、文書に関する日時データを取得して、ツール上に表示するところまでを実装したいと思います。

伝説のツール「NotesPeek」をQtでリメイクする(その7・日時その1)

f:id:takahide-kondoh:20170509070312p:plain

Notesにおいて基本的なデータ型3つを挙げるとすると、テキスト型、日時型、数値型となるでしょう。また、それらの複数形「テキストリスト型」、「日時リスト型」、「数値リスト型」も存在します。その他に、@式で紹介した「バイナリ型」、整形可能な「リッチテキスト型」などもあります。

ここでは、その中の「日時型」を扱います。テキスト型はLMBCS文字列で紹介しました。数値型の実体は「double型」なので、あまり詳しく触れなくてもいいでしょう。

日時型は、Notes APIではTIMEDATE構造体で表します。C/C++ではtime_t型、構造体tmなどで表されていますが、直接の互換性はないようです。QtではQDateTime、QDate、QTimeなどで表します。今回は、TIMEDATEとQtの日時型との相互互換を可能にします。また、文字列との相互変換も紹介します。

まず、TIMEDATE構造体について見てみます。

#include <global.h>

typedef struct tagTIMEDATE {
    DWORD Innards[2];
} TIMEDATE;

TIMEDATEは独自のバイナリ形式を持っているため直接扱えません。必ずAPIを通して使います。

現在の日時を取得するには、OSCurrentTIMEDATEを使用します。

#include <ostime.h>

void LNPUBLIC OSCurrentTIMEDATE (TIMEDATE far *retTimeDate);

// ex)
TIMEDATE timeDate;
OSCurrentTIMEDATE(&timeDate);

TIMEDATEには「特殊な値」という3つの定数があります。最小値、最大値、ワイルドカードの3つです。これらを取得するには、TimeConstant関数を使います。

#include <misc.h>

#define TIMEDATE_MINIMUM     0
#define TIMEDATE_MAXIMUM     1
#define TIMEDATE_WILDCARD 2
void LNPUBLIC TimeConstant(WORD, TIMEDATE far *);

f:id:takahide-kondoh:20170510211450p:plain

TIMEDATE構造体の内容をテキストに変換するにはConvertTIMEDATEToText関数を使います。逆に、日時を表したテキストをTIMEDATEにするにはConvertTextToTIMEDATE関数を使います。

#include <misc.h>

STATUS LNPUBLIC ConvertTIMEDATEToText (
    const void far *IntlFormat,
    const TFMT far *TextFormat,
    const TIMEDATE far *InputTime,
    char far *retTextBuffer,
    WORD TextBufferLength,
    WORD far *retTextLength);

STATUS LNPUBLIC ConvertTextToTIMEDATE (
    const void far *IntlFormat,
    const TFMT far *TextFormat,
    char far * far *Text,
    WORD MaxLength,
    TIMEDATE far *retTIMEDATE);

「IntlFormat」は国際フォーマットといって、構造体INTLFORMATで表すフォーマットです。

#include <intl.h>

typedef struct {
    WORD Flags;    /* Flags (see above) */
    BYTE CurrencyDigits;
    BYTE Length;    /* Length of this structure */
    /* THIS MUST BE SET TO THE EXACT */
    /* SIZE OF THE STRUCTURE WHEN ITS */
    /* POINTER IS PASSED AS AN ARGUMENT */
    int TimeZone;
    char AMString[ISTRMAX];
    char PMString[ISTRMAX];
    char CurrencyString[ISTRMAX];
    char ThousandString[ISTRMAX];
    char DecimalString[ISTRMAX];
    char DateString[ISTRMAX];
    char TimeString[ISTRMAX];
    char YesterdayString[YTSTRMAX];
    char TodayString[YTSTRMAX];
    char TomorrowString[YTSTRMAX];
} INTLFORMAT;

デフォルト(日本語環境)では、INTLFORMAT型には以下のような値が入っています。

f:id:takahide-kondoh:20170510125825p:plain

文字列はLMBCS形式です。

「TextFormat」は日時フォーマットで、TFMT構造体で表します。TFMTでは@関数の@Text()で日時変換に使用するような選択肢がありますが、詳細は今回割愛します。IntlFormatもTextFormatも、デフォルトでよければ0(ヌルポインタ)を渡します。

日時のテキスト化に必要なバイト数はMAXALPHATIMEDATE定数を使うことができます。

ここまでの説明で、Notes APIの日時型であるTIMEDATE構造体に関する基本的なAPIを見ました。次回は、Notes APIが用意している日時変換のためのデータ型と関数を紹介します。

伝説のツール「NotesPeek」をQtでリメイクする(その6・文書取得)

その4・IDテーブル」「その5・@式その1」「その5・@式その2」「その5・@式その3」とAPIの説明などが続いたので、このあたりでコードの紹介を挟みます。

f:id:takahide-kondoh:20170505220039p:plain

今回のゴールは、データベースアイテムの直下に、文書のアイテムを展開し、文書ID(NOTEID)とその文書のウィンドウタイトルを画面に表示することです。

操作方法はデータベースを展開する時と同じく、コンテキストメニュー(右クリックメニュー)を開きます。データベースアイテムにマウスを移動し、コンテキストメニューで「Refresh Children」を選ぶと、そのデータベースに含まれる文書がすべて展開されます。

構築環境は、以前のIntroQtデモと同じです。nsfinder本体と、ntlxライブラリのコードはBitbucketから取得できます。

Bitbucket/ntlxライブラリ

クローンをダウンロードしたあと、タグv0.1.2をチェックアウトしてください。

Bitbucket/nsfinder本体

こちらはタグv0.9.1をチェックアウトしてください。

Mac以外にWindowsLinux(Ubuntu)でも動作確認しています。Qt Creatorなどの設定は、以前の記事をご覧ください。

さて、すべてのコードの説明は割愛しますが、IDテーブル用クラスと@式用クラスの、2つのヘッダーファイルだけ簡単に紹介しておきます。

#include <ntlx/idtable.h>

#ifndef NTLX_IDTABLE_H
#define NTLX_IDTABLE_H

#include "ntlx_global.h"
#include "ntlx/status.h"

#if defined(NT)
#pragma pack(push, 1)
#endif

#include <nsfdata.h>

#if defined(NT)
#pragma pack(pop)
#endif

namespace ntlx
{

class Database;
class Note;

/**
 * @brief IDテーブルクラス
 */
class NTLXSHARED_EXPORT IDTable
    : public IStatus
{
public:

  /**
   * @brief イテレータインナークラス
   */
  class NTLXSHARED_EXPORT iterator
  {
  public:
    /**
     * @brief デフォルトコンストラクタ
     */
    iterator();

    /**
     * @brief NOTEIDを返す演算子
     * @return NOTEID
     */
    NOTEID operator*();

    /**
     * @brief 前置きインクリメント演算子
     * @return 自身への参照
     */
    iterator& operator++();

    /**
     * @brief 後置きインクリメント演算子
     * @return インクリメントする前のイテレータ
     */
    iterator operator++(int);

    /**
     * @brief NOTEIDに対応したNoteオブジェクトを取得する
     * @param db 取得元になるデータベースオブジェクト
     * @return Noteオブジェクト
     */
    Note getNote(Database& db) const;

  protected:
    /**
     * @brief コンストラクタ
     * @param pIdTable IDTableオブジェクトへのポインタ
     * @param noteId NOTEID
     * @param isLast 最後を示しているか
     */
    iterator(IDTable* pIdTable, NOTEID noteId, bool isLast = false);

    IDTable* idTable_;
    NOTEID id_;
    bool isLast_;

    /**
     * @brief 等値演算子
     * @param lhs 左辺値
     * @param rhs 右辺値
     * @return ブール値
     */
    friend NTLXSHARED_EXPORT bool operator==(
        const iterator& lhs
        , const iterator& rhs
        );

    /**
     * @brief 不等値演算子
     * @param lhs 左辺値
     * @param rhs 右辺値
     * @return ブール値
     */
    friend NTLXSHARED_EXPORT bool operator!=(
        const iterator& lhs
        , const iterator& rhs
        );

    friend class IDTable;
  };

  /**
   * @brief コンストラクタ
   */
  IDTable();

  /**
   * @brief デストラクタ
   */
  virtual ~IDTable();

  /**
   * @brief 最初のイテレータを返す
   * @return 最初のイテレータ
   */
  iterator begin();

  /**
   * @brief 最後のイテレータを返す
   * @return 最後のイテレータ
   */
  iterator end();

  /**
   * @brief NOTEIDを挿入する
   * @param id NOTEID
   * @return 重複していなければ真
   */
  bool insert(NOTEID id);

private:
  DHANDLE handle_;

  friend class iterator;
};

} // namespace ntlx

#endif // IDTABLE_H

このIDTableクラスは、ほぼ「Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミング」の定義を踏襲しています。インナークラスiteratorを用いて、STLのコンテナクラスのようなイテレーション操作を可能にしているところなどは秀逸です。

#include <ntlx/formula.h>

#ifndef NTLX_FORMULA_H
#define NTLX_FORMULA_H

#include "ntlx_global.h"
#include "ntlx/status.h"
#include "ntlx/note.h"

namespace ntlx
{

/**
 * @brief 式クラス
 */
class NTLXSHARED_EXPORT Formula
    : public IStatus
{
public:
  /**
   * @brief デフォルトコンストラクタ
   */
  Formula();

  /**
   * @brief コンストラクタ
   * @param bin コンパイル済み@式へのポインタ
   * @param size コンパイル済み@式の長さ
   */
  Formula(const char* bin, WORD size);

  /**
   * @brief コピーコンストラクタ
   * @param other コピー元Formula
   */
  Formula(const Formula& other);

  /**
   * @brief 代入演算子
   * @param other 代入元Formula
   * @return 自身への参照
   */
  Formula& operator=(const Formula& other);

  /**
   * @brief デストラクタ
   */
  virtual ~Formula();

  /**
   * @brief コンパイル済み@式のサイズを取得する
   * @return コンパイル済み@式のサイズ
   */
  WORD odsLength() const;

  /**
   * @brief 式ハンドルをロックする
   * @return コンパイル済み@式へのポインタ
   */
  const char* lock() const;

  /**
   * @brief コンパイル済み@式を割り当てる
   * @param bin コンパイル済み@式
   * @param size コンパイル済み@式の長さ
   */
  void assign(const char* bin, WORD size);

  /**
   * @brief 式を評価する
   * @param note 評価元になる文書オブジェクト
   * @return 評価した式の結果テキスト
   */
  QString evaluate(Note& note);

protected:

  /**
   * @brief 計算を開始する
   * @return 計算ハンドルを取得できれば真
   */
  bool startCompute();

  /**
   * @brief 計算を終了する
   */
  void stopCompute() const;

  /**
   * @brief 式ハンドルをアンロックする
   */
  void unlock() const;

  /**
   * @brief 式を閉じる
   */
  void close() const;

  mutable FORMULAHANDLE handle_;
  mutable char* bin_;
  mutable HCOMPUTE hCompute_;
};

} // namespace ntlx

#endif // NTLX_FORMULA_H

こちらのFormulaクラスも前出の「Notes/Domino APIプログラミング―C++とSTLによる実践的プログラミング」のコーディングを応用しています。

このFormulaとIDTableを組み合わせると、選択式を使ってIDテーブルに文書を捕捉することが可能になります。

伝説のツール「NotesPeek」をQtでリメイクする(その5・@式その3)

f:id:takahide-kondoh:20170505220039p:plain

「ウィンドウタイトル」はフォームという設計文書に含まれる、「@式型」のフィールド(アイテム)です。ここでは、NoteクラスのgetWindowTitle()というメソッドとして定義します。このメソッドの実装プロセスを、APIを中心にしてみていきます。

QString ntlx::Note::getWindowTitle();

このgetWindowTitleメソッドは、以下のように4つのプロセスで成り立ちます。

QString ntlx::Note::getWindowTitle()
{
  QString formName = getShortText(FIELD_FORM);
  Note formNote = db_->getDesignNote(formName, NOTE_CLASS_FORM);
  Formula formula = formNote.getFormula(ITEM_NAME_WINDOWTITLE);
  return formula.evaluate(*this);
}
  1. 自身(文書)が持っているFormフィールドの値(=フォーム名)を取得します。
  2. フォーム名に該当するフォーム設計文書を取得します。
  3. フォーム設計文書が持つウィンドウタイトルフィールドの値(=コンパイル済み@式、バイナリ)を取得します。
  4. 自身(文書)を基準に@ウィンドウタイトル式を評価し、その文字列を返します。

ここでは、最後の「式の評価」プロセスを順に説明しますが、その前に少しだけ、getFormula()内で起きるバイナリデータの扱いについて触れておきます。

取得したコンパイル済み@式をすぐに評価する場合は必要ないと思いますが、例えばこのgetFormula()のように、文書(Note)から取得したコンパイル済み@式をFormulaオブジェクト内で保持して戻り値とする必要がある場合、取得したコンパイル済み@式(バイナリ)と同じ状態でコピーする必要があります。手順としては以下のようになります。

  1. NSFItemInfo関数で取得した値用ブロックID(=コンパイル済み@式)を、OSLockBlockでロックし、ポインタを得る(コピー元)。
  2. OSMemAllocでメモリハンドルを取得し、OSLockObjectでロックし、ポインタを得る(格納先)。
  3. コピー元から格納先に必要な長さ分メモリコピーをする。
  4. 格納先をOSUnlockObjectでアンロックする。
  5. コピー元をOSUnlockBlockでアンロックする。

ユーザ側がOSMemAllocで割り当てたメモリは、責任を持って不要時にOSMemFreeで解放します。一方で、NSFItemInfo関数で得たブロックIDは、責務はAPI側にあるので、ユーザ側で解放してはいけません。

話を「式の評価」に戻します。

@式を評価する手順は大まかに次の通りです。

  1. コンパイル済み@式をロックし、ポインタを得る(式ポインタ)。
  2. NSFComputeStart()関数に式ポインタを与えて、計算ハンドル(HCOMPUTE型)を得る。
  3. 基準とする文書(Document)を開いて、文書ハンドル(NOTEHANDLE型)を得る。
  4. NSFComputeEvaluate()関数に計算ハンドル、文書ハンドルを与えて、結果ハンドル(DHANDLE型)を得る。
  5. 結果ハンドルをロックすると、結果がLMBCS文字列として取得できる。
  6. 結果ハンドルは不要になったらアンロックし、ユーザ側の責務で解放する。

このように@式は、式ハンドル→式ポインタ→計算ハンドル→結果ハンドルというように、いくつもハンドルが出てくるのが特徴です。 計算ハンドルは使い回すことができるので、式の内容が同じなら計算ハンドルをキープし、NSFComputeEvaluateを繰り返し呼び出した方が効率がよくなるでしょう。不要になった計算ハンドルはNSFComputeStopでリリースします。

@式を利用する重要な手順には、これらの他に「コンパイルする」という手順が加わります。今回扱っている「ウィンドウタイトル」は、Domino Designerを通してコンパイルされて、フォーム設計文書に格納されているため説明を省きました。テキストの@式をコンパイルしてバイナリにする「NSFFormulaCompile」の他に、コンパイル済みの@式をテキストにする(デコンパイルする)API「NSFFormulaDecompile」もありますが、いずれ必要に応じて説明したいと思います。