User Interface Language

Xtを使っていると、コード中に似たような記述が含まれることに気づきます。 普通それは関数にまとめて再利用する形で使うのですが、 Motifについては ウィジェットの生成部分について余りにもよく使うというので 専用の言語が用意されています。

それがUIL(User Interface Language)です。

UILは次のXt関数の組み合わせを簡単にしてくれます。

UILでサポートするXt機能
API役割
XtCreateWidget
XtVaCreateWidget
XtCreateManagedWidget
ウィジェットの生成
XtSetValues
XtVaSetValues
リソースの設定
XtAddCallback
XtAddCallbacks
コールバックの登録
XtCreatePopupShell
XtVaCreatePopupShell
ポップアップシェルの生成 †
XtPopup ポップアップシェルの表示 †

さらにMotif関係についても利点があります。

  1. リソース ファイルよりも若干高度なコンパウンド ストリングの扱いが可能
  2. ウィジェットの組み合わせで実現できないものが事前にわかる
  3. ウィジェットで扱えるリソースの正当性が事前にわかる
  4. リテラルの演算結果をリソースで指示できる

リソース ファイルと機能的に重なりますが、次の事もできます。

  1. アイコン画像の埋め込み
  2. カラーテーブルの定義(アイコンでも使える)
  3. 色定義(モノクロ版と両方に適応できるもの)
  4. フォント リストの定義
  5. レンダー テーブル
  6. 文字集合情報を含むテキスト

バランス問題と着目点の違いだとおもいますが、 次の性質は知っていたほうがでしょう。

  1. UILで定義したリソースはリソース ファイルでは変更できない
  2. ワイルドカード指定が使えないので、 基本的にそれぞれでリソースを指定しなくてはならない
  3. 仕訳をきちんと考えてUILソースを作らないとI18Nに対応できない。‡
  4. ダイナミックにUI構成を変える場合はコールバックで実現する

† これはMotifウィジェットの性質によるものなので、ここに含めるか迷いました。 ここで意識しているのはXmDialogをXtManageすることにより表示されることです。 ‡ それぞれのロケールに応じたUILを準備するか、 テキスト関係はリソース ファイルに記述するかして対応します。

UILを使ったプログラミングの流れ

箇条書にまとめてみました。

  1. UILをテキストエディタで記述
  2. uil(1)コンパイラを利用してuidファイルを生成
  3. MRMを使ってuidを取り込む
  4. とりあえずUIの動作確認
  5. リソースファイルの記述
  6. アプリケーションの作り込み(コールバック関数)
  7. 様々な調整

MRM(Motif Resource Manager)は、UILをCで扱うためのAPI群です。 命名規則により、それがMRMのAPIかどうかが すぐにわかります。

UIの動作確認は次の手順になります。 簡単なアプリケーションでは正式な実装もあまり違いはありません。

  1. XtやMRMの初期化
  2. シェルの生成
  3. MrmOpenHierarchyPerDisplayの呼び出しでUIDファイルの読み込み
  4. MrmRegisterNamesInHierarchyの呼び出しで名前の登録
  5. シェルを親にして MrmFetchWidgetの呼び出し
  6. マネージ
  7. リアライズ
  8. Xtメインループ

UILの文法

UILの文法は、附属マニュアルのUIL(5)にて参照できます。 ほとんどの人は英語版しか入手できないと思いますが、 一度は一通り目を通した方がよいと思います。 同様の内容が「OSF/Motif Programmer's Reference」でも参照できます。

もちろん、このメモ書きを頼りにするだけでも それなりのコーディングはできることでしょう。

モジュールの定義

UILのコーディングの単位の一つにモジュールがあります。 モジュールはコンパイル単位でもあり、名前空間の管理の単位でもあります。

UILのモジュールの宣言は次のようになります。

module module_name
! オプションの指定
names = case_sensitive

!ここにモジュールごとの定義を記述する
end module;

module 〜 end module 間に必要な記述を含めます。

コメントはCの形式と同様の/* 〜 */ 形式か、「!」から改行まで という形式が使えます。

uilコンパイラの引数で渡すファイルには、必ずこのmodule文が含まれるようにしなくてはなりません。 include fileの対象となるUIL定義ファイル(後述)は存在しなくても構いません。

CからはMrmOpenHierarchyPerDisplayを使って、関連するモジュールをまとめて取り込みます。 モジュールの操作は MrmHierarchy 型の hierarchy_id経由で行ないます。

モジュール定義にはいくつかオプション指定があります。

当面は name = case_sensitive; を付けておけばよいでしょう。 この意味は「識別子の文字列のケースの違いを考慮する」ということです。 この指定がなければ(あるいは case_insensitive であれば)、 全て大文字に変換された上で扱われます。

オブジェクト定義

オブジェクトの定義が、よく利用され、かつ重要な部分です。 ウィジェッとのリソースと親子関係とコールバックを定義します。

名前とウィジェットの種類と属性を、ブレスで囲みながら、入れ子構造に定義していきます。

object
  foo_main_window : XmMainWindow {
    arguments {
      XmNworkWindow = foo_canvas;
    };

    controls {
      XmMenuBar foo_menu_bar;

      foo_canvas : XmDrawingArea {
	arguments {
	  XmNwidth = foo_width;
	  XmNheight = foo_height;
	};
      };
    }; !controls for main_window
  };

object
  foo_menu_bar : XmMenuBar {
    arguments {
      XmNmenuHelpWidget = foo_help_item;
    };

    controls {
      foo_canvas_item : XmCascadeButtonGadget {
        arguments {
          XmNsubMenuId = foo_canvas_pulldown_menu;
        };
      };

      foo_help_item : XmCascadeButtonGadget {
        arguments {
          XmNsubMenuId = foo_help_pulldown_menu;
        };
      };
    }; !controls for menu_bar
  };

object
  foo_canvas_pulldown_menu : XmPulldownMenu {
    controls {
      foo_close : XmPushButtonGadget {
        callbacks {
          XmNactivateCallback = procedure foo_canvas_close(foo_closure);
        };
      };

      foo_refresh : XmPushButtonGadget {
        callbacks {
          XmNactivateCallback = procedure foo_canvas_refresh(foo_closure);
        };
      };
    }; 
  };

  /* help_pulldown_menu については
  canvas_pulldown_menu 類似のため略 */

それぞれのウィジェットの定義は

object ウィジェット名 : ウィジェットの種類 { パラメータ定義リスト };
となります。

当然ながら、(標準では)Motifでサポートしているウィジェットしか指示できません。

パラメータの定義には

arguments
ウィジェットに設定するリソース
controls
子ウィジェット
callbacks
ウィジェットに設定するコールバック
を含まることができますが、必要ないものは省略できます。

そのウィジェットがサポートしていないものについてargumentsやcallbacksを指定すると エラーや警告扱いになります。

リソースとパラメータの型の組み合わせが違っていても知らせてくれます。 例えば数値を必要とするところに文字列を指定したり、 フォントを扱うところに計算式を指定したりするとエラーになります。

親によって指示できるコンストレント リソースは変化します。 例えば親がXmPanedWindowであれば、そのウィジェットで XmNskipAdjustが指示できるようになります。

ウィジェットを指示するリソースには、 uilファイル中で定義してるウィジェット名を与える事ができます。

ウィジェットがコンポサイトを継承したものである場合に限って controlsを指定できます。 controlsに含める子の定義は入れ子になるように記述してもよいですし、

[unmanaged] ウィジェットの種類 ウィジェット名;
あるいは
[unmanaged] ウィジェットの種類 { パラメータ定義リスト };
このような形式で指定しても構いません。

ウィジェット名を省略すると、適当な名前が振られます。

(上の例では使っていませんが)ウィジェットの種類の前に unmanaged を添えると MrmFetchWidgetの際にXtManageWidgetが呼ばれない状態で生成されます。 unmanagedは、ダイアログを定義する時に利用します。

親子の組み合わせによっては、うまく動作しないものがあるのですが、 明らかに想定外の使い方をしている部分については エラーや警告を出してくれる事もあります。

リストの定義

コールバック関数の定義

IDの定義

プログラムの動作時に値を決定する必要のあるパラメータについては identifierについてその識別子を宣言しておき、UIL中で利用することができます。 一般にはコールバックのclosureパラメータとして渡す 値をこれで指定することになると思います。

! IDの宣言
identifier
  hierarchy_id;
  foo_closure;

Cでこのidentifierに対応する値の設定は、用途に応じて MrmRegisterNames あるいは MrmRegisterNamesInHierarchy を利用します。

定数値の定義

UILの記述で繰り返し利用される文字列や数値は、 あらかじめ宣言しておいて、マクロ定義のように繰り返し利用できます。

この段階で利用するか、Xtリソース ファイルで利用するかは プログラム作成者によって判断がわかれるところでしょう。

(個人的にはメニューの見出しに使われるテキストは UILではなく、リソース ファイルの仕掛けを利用したほうがよいと思っています。)

! IDに対応する定数の宣言
value
  foo_width : exported 500;
  foo_height : 400;

式にexportedを沿えると、別モジュールから参照できるようになります。

逆に別モジュールの値を扱う時には、importedにその型を沿えて宣言します。

! 別モジュールからの取り込み
value
  foo_width : imported integer;

他の記述ファイルの取り込み

複数人数で開発したり、ファイルが大きくなってきたり、 特定の内容を使い回したりする時には、複数のファイルに分割しておいて、 適宜取り込むという手法が使えます。

UILでは include file 文で、それを実現します。

! 別ファイルの定義を読み込む
include file 'target-file-name.uil';

依存関係はMakefileにでも定義しておくとよいでしょう。

対処となるファイルは uilコマンドの -I オプションで検索パスを指定します。