アドベントカレンダーだるい

これはアドベントカレンダーだるい Advent Calendar 2016の2日目のエントリーです

1日目はschemelispさんによるアドベントカレンダーはだるいです


プログラマー[]ので魚屋始めました

デザインとロジックを分離したかった・・・

これは 【その1】ドリコム Advent Calendar 2015 - Adventar の8日目です。

7日目は すべてがKになる (@ka_nipan) | Twitter さんによる ドリコムを支えるデータ分析基盤がTD+AWSに移行した話 - かにぱんのなく頃に です。

【その2】ドリコム Advent Calendar 2015 - Adventar もあります。

f:id:sazae657:20151206210029p:plain

自己紹介

Fz (@sazae657) | Twitter

配線工や商業作曲家等を経て、現在はドリコムにてクロスプラットフォーム開発向けのミドルウエアやデータプロテクション等、低レイヤーの研究開発をしています。

ドリコム釣り部CFO(Chief Fishing Officer)

本日の話

私が手元で使うツールの爆速開発を支えているのがご存じMotifですが、その開発効率化に取り組んでみた話しをゆる〜くお話しします

Motifと採用の経緯に関しては去年のエントリーを参照して頂ければと思います。
ドリコムの俺を支えるUIツールキット - sazae657

MrmとUIL

GUIツールを作る際、コードでベタベタとUIを書くと各種生成、属性や座標の調整、各種解放やらのコードがどんどん増えるのはWin32 APIやCarbonで書いた事がある方ならわかって頂けると思います(おもいたい!!)
そこで、生成や外観の処理を隠蔽してしてロジックの実装に集中させてくれるDCCツールや各種フレームワークが数多くリリースされています。 いくらAPIがシンプルかつ洗練されているMotifとてUIの要素が増えてくるとその傾向は看過できない量になって、爆速開発とモチベーションに暗い影を落とし始めます。

そこで Mrm の登場です。

Mrm (Motif Resource Manager) と UIL (User Interface Language) がMotif アプリケーションにデザインとロジックの分離をもたらしてくれます

Mrmの歴史はかなり長く、1992年にリリースされた OSF/Motif 1.2に[digital]によって実装されたという所までは掴めましたが、資料がほとんど無い上にその頃は小学生だったのでリリース当時の状況等は把握出来ておりません

Mrm+UIL では UIL に外観を定義してCソースにイベント処理等のロジックを書きます。

ウイジェットの生成、各種登録処理はUILを基にMrmが面倒を見てくれます。

猛烈に大雑把に書くとこのような感じになります。

f:id:sazae657:20151208113650p:plain

UILの記法はDCEを書いたことがあればお馴染みの記法です

module モジュール名

 セクション 名前 : 型  {
      (セクション内記述)
 };

end module;

文字列付きラベルウイジェット(XmLabel)を定義するとこのような記述になります

object my_label : XmLabel  {
    arguments {
         XmNlabelString = compound_string("2015年11月26日 (木)発売!!!");
    };
};

これをCで書くとこのようになります

Widget my_label;
Arg arg[1];
XmString str;

str = XmStringCreateLtoR(
             "2015年11月26日 (木)発売!!!", XmSTRING_DEFAULT_CHARSET);
XtSetArg(arg[0], XmNlabelString, str);

my_label = XmCreateLabel(parent ,"my_label", arg, 1);
XtManageChild(my_label);

UIL 素敵 !!!

作ってみる

手元の環境は以下です

ウインドウマネージャーはtwmを愛用しています。
f:id:sazae657:20151208120058p:plain 右端のタマネギ模様のリサイズコーナーとドラッグ時に安心感のある滑り止付きタイトルバーが最高です。

では、このようなアプリケーションをUILで書いてみます

ウインドウにテキストラベルが貼り付いていて、そこに文字が出るだけです

f:id:sazae657:20151207210612p:plain

まずは UIL エンコーディングEUC-JP厳守!!※

halo.uil

module halo

object
    halo_world : XmRowColumn {
        controls {
            label : XmLabel {
                arguments {
                    XmNlabelString =
                        compound_string('右隣の部屋にはテレビが2台',separate=true) &
                        compound_string('左隣の部屋にはスライスたくあんが1kgあるとする',separate=true) &
                        compound_string('顧問がPCを1台着服したと仮定して、現在の情報処理部のPC台数を求めよ');
                };
            };
        };
    };
end module;

次に対応するCソースを書きます エンコーディングEUC-JP厳守!!※

chief.c

#include <stdlib.h>
#include <Xm/XmAll.h>
#include <Mrm/MrmPublic.h>

static char *fallback_resource[] = {
    "*fontList:  -misc-fixed-medium-r-normal--14-*-*-*-*-*-*-*:",
    "*title: HALO WORLD!!",
    NULL
};

int
main(argc, argv)
    int argc;
    char **argv;
{
    XtAppContext app_context;
    Widget topLevel, app;
    Arg al[20];
    int ac;
    /*uilコンパイラーでコンパイルしたファイルを列挙 */
    static String uid_files[] = { "halo.uid" };
    MrmHierarchy  hierarchy_id;
    MrmCode        class ;

    /* お約束(1) */
    XtSetLanguageProc( NULL, (XtLanguageProc)NULL, NULL);

    /* Mrmの初期化 */
    MrmInitialize();

    /* お約束(2) */
    ac = 0;
    topLevel = XtAppInitialize(&app_context, "halo", NULL, 0,
            &argc, argv, fallback_resource , al, ac);

    /* UIDから MrmHierarchyを作る */
    MrmOpenHierarchyPerDisplay(XtDisplay(topLevel),
            (MrmCount) XtNumber(uid_files),
            uid_files,
            (MrmOsOpenParamPtr*)NULL,
            &hierarchy_id);

    /* halo_world を取得 */
    MrmFetchWidget (hierarchy_id,
            "halo_world", topLevel, &app, &class);

    /*  halo_world を表示 */
    XtManageChild(app);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);

    return 0;
}

uil をコンパイルして uid ファイルを生成

% uil halo.uil -o halo.uid

C ソースをコンパイル & リンク

% cc -g  -I/usr/dt/include -c  chief.c -o chief.o
% cc -g  -o svchost.exe chief.o -L/usr/dt/lib64 -lXm -lMrm -lXt

実行すると EUC-JP環境でない場合はロケールEUC-JPに!!※

% env LANG=ja_JP.eucJP ./svchost.exe

f:id:sazae657:20151206213641p:plain

あら簡単!

UTF-8で使ってみたかった・・・

このuilをUTF-8で保存してコンパイルすると無慈悲なメッセージを吐いてエラーに終わります。
コメント行も許されません。

f:id:sazae657:20151207095743p:plain

UILパーサーのコメントを見ればわかりますが、最終更新は[digital]による1997年です。
UTF-8に対応してたらオーパーツです。

/* 
 * HISTORY
*/ 
#ifdef REV_INFO
#ifndef lint
static char rcsid[] = "$TOG: UilLexAna.c /main/14 1997/03/12 15:10:52 dbl $"
#endif
#endif

/*
*  (c) Copyright 1989, 1990, DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASS. */
/*

という事でUIにマルチバイト文字を使いたい場合、以下のような選択肢があります

  1. EUCで書いてEUCで使う
  2. UILでUIの定義だけして文字列はXのリソースに追い出す
  3. UILを使わないでCソースにベッタベッタとXmCreateXX関数を書く

という感じになります・・・

でも、やっぱり・・・

ですが!

折角Motifのソースが公開されているのに妥協するのは面白くないので、自分で使うツールを手早く手間無く見た目は格好良く(Motif)爆速開発する目的の為にゴリ押しでUILを何とかしてみます。

神聖なMotifのソースに手を入れるのは非常に気が引けますが、液晶を指でみょんみょんする人の気持ちになって思い切ってやってみましょう。

まず、OpenMotifのUILコンパイラーのソースを読んでみます

問題の箇所が1145行目のココです

 switch (z_cell.backup)    /* backup holds special processing code */
 {
case control_char: /* encountered a control char in a string or
        * comment - issue a diagnotic and continue */
    issue_control_char_diagnostic( c_char );
break;

Motif / Code / [9f0120] /clients/uil/UilLexAna.c

ここで d_control_char が発生して無慈悲なエラーを吐いています

ココをコメントアウトしたバイナリーを作って、冒頭のUILをコンパイルしてみます

f:id:sazae657:20151208084903p:plain

あ・・・
予想通り、ゴッソリ削れてしまいました。

次にエラー診断をしている箇所に飛びます

severity = diag_rz_msg_table[ message_number ].l_severity;

http://sourceforge.net/p/motif/code/ci/master/tree/clients/uil/UilDiags.c#l227

diag_rz_msg_table[d_control_char] の l_severityでエラーレベルが定義されています d_control_char = 9ですので、この定義を見ると

typedef struct
{
  XmConst int  l_severity;
  char XmConst *ac_text;
} diag_rz_msg_entry;

XmConst diag_rz_msg_entry diag_rz_msg_table[81] =
(略)
 { 3, msg9 },

Motif / Code / [9f0120] /clients/uil/UilMessTab.h

d_control_char の l_severity は 3 となっているので、展開すると以下のようになります

 { 3,  "unprintable character \\%d\\ ignored" }

l_severity = 3の定数を見てみます

#define Uil_k_error_status    3

Motif / Code / [9f0120] /clients/uil/Uil.h

d_control_char の severity は Uil_k_error_status という事になっていそうです
UilDiags.c の診断ルーチン では Uil_message_count[Uil_k_error_status] の件数をインクリメントしています
もうダメそうな匂いが漂ってきました・・・

Uil_message_count[ severity ]++;

流れ流れてエントリーポイントに戻ってきました Uil_message_count[Uil_k_error_status] はUilDiags.cでインクリメント済みで明らかにゼロではないのでチェックメイトです。

    /* If there are any error/severe messages, then don't return */
    /* a symbol table for the callable compiler - clean up here */
    if ( Uil_message_count[Uil_k_error_status]>0 ||
     Uil_message_count[Uil_k_severe_status]>0 ) {

Motif / Code / [9f0120] /clients/uil/UilMain.c

という事で・・・
最初に上げた switch 文で z_cell.backup が control_char になった状態を見てみます

わかりやすいように、UILの文字列は compound_string('ゆずこしょう'); に変更しました

(gdb) p/x z_cell
$2 = {action = 0x4, next_state = 0x8, backup = 0x1, unused = 0x0}

z_cellを作っている場所を探します

l_class = class_table[ c_char ];     /* determine its class */
z_cell = token_table[ l_state][l_class ];   /* load state cell */

Motif / Code / [9f0120] /clients/uil/UilLexAna.c

関連変数を見ていきます

(gdb) p/x c_char
$3 = 0x82
(gdb) p/x l_state
$4 = 0x8
(gdb) p/x l_class
$5 = 0x10

class_table[c_char = 130] とtoken_table[l_state = 8][l_class = 16]を見てみます

static cell XmConst token_table[ max_state+1][ max_class+1] = {
(中略)
/* class_illegal */ { special,      state_str_1,    control_char },

static char class_table[ 256 ] = {
(中略)
/* 80 */    class_illegal,  class_illegal,  class_illegal,  class_illegal,

Motif / Code / [9f0120] /clients/uil/UilLexAna.c

つまるところ
「ゆ」(0xE3 0x82 0x86) は0x82から文字コードとして class_illegal 扱いになって、制御文字(control_char)として片付けられていました。

ここまで来たら・・・

事象は掴めたので即行動してみました。

Motif は UTF-8に対応しているので、UIL コンパイラーに対して文字判定で class_illegal+control_charを喰らったらUTF-8判定を掛けて、処理を強行する簡単な改造を実施してみました

(デカいのでgistに貼りました)
UILにUTF-8を無理矢理通すゴリ押し · GitHub

手元で使えりゃいーじゃんレベルですので厳密な検証はしていません、ご了承ください

f:id:sazae657:20151207141007p:plain

ちゃんと表示された!!!

UILを使いまくってデザインとロジックを分離しよう!

ゴリ押し改造の結果、遂に念願のUTF-8のソースがコンパイルできるようになったので使いまくってみます!

折角ですので、Motifで超絶面倒くさいと評判のメニューバーとプルダウンメニューを扱ってみます。
C側のコードだけで書くとXmCreateXXの嵐で大変な事になるのですが、UILで書けば定義するだけでコードの変更は必要ありません

冒頭のコードにメニューバーの定義を追加して実行してみます

object
    menu_bar : XmMenuBar {
        controls {
            XmCascadeButton main_menu;
        };
    };

object
    main_menu : XmCascadeButton {
        controls {
            XmPulldownMenu {
                controls {
                    member1 : XmPushButton {
                        arguments {
                            XmNlabelString = compound_string("紫");
                        };
                    };
                    member2 : XmPushButton {
                        arguments {
                            XmNlabelString = compound_string("ピンク");
                        };
                    };
                    member3 : XmPushButton {
                        arguments {
                            XmNlabelString = compound_string("明るい茶色");
                        };
                    };
                };
            };
        };
        arguments {
            XmNlabelString = compound_string("情報処理部");
        };
    };

f:id:sazae657:20151207213836p:plain

あら簡単!

次はメニューが選択された時の処理を書いてみます

イベント駆動ですのでコールバックを登録します。

まず、UILにイベントと呼んで欲しい関数を宣言します。

procedure
    member_select();

次に呼んで欲しいウイジェットの呼んで欲しいイベントに定義したコールバックを設定します 今回はメニュー項目が選択されたら member_select() というコールバックを呼んでもらいます

member1 : XmPushButton {
    arguments {
            XmNlabelString = compound_string("紫");
        };
        callbacks {
            XmNactivateCallback = procedure member_select();
        };
};

あとはSystem.Reflectionで・・・という訳には行かないのでCのコードに定義を追加します。

呼び出し元のラベルの文字列を取得してコンソールに出力するコードを書いてみます

/* member_select コールバック */
static void
member_select(widget, call_data, client_data)
    Widget    widget;
    XtPointer client_data;
    XtPointer call_data;
{
    XmString str;
    char* cstr;
    Arg ar[1];

    /* 呼び出し元のラベルの文字列を取得してみる */
    XtSetArg(ar[0], XmNlabelString, (XtArgVal)&str);
    XtGetValues(widget, ar, 1);

    /* C文字列へ変換 */
    cstr = (char *)XmStringUnparse(str, NULL, XmCHARSET_TEXT, XmCHARSET_TEXT, NULL, 0, XmOUTPUT_ALL);

    /* コンソールに出してみる */
    fprintf(stderr, "SELECT: %s\n", cstr);

    /* 後始末 */
    XtFree(cstr);
    XmStringFree(str);
}

つぎに MRMに登録する為のリストを定義します。
key - valueで列挙していく形です (スクリプト言語の拡張モジュールを書いたことがあればお馴染みですね)

static MrmRegisterArg  register_args[] = {
    {"member_select", (XtPointer) member_select }
};

これで member_select() を member_select という名前でマッピングしました

最後に MRMへコールバックを登録します。

Hierarchyへ登録するタイプとグローバルへ登録する2タイプがありますが、今回はHierarchyへ登録します。

    /* Hierarchy へコールバックの登録 */
    MrmRegisterNamesInHierarchy (hierarchy_id,
          register_args, XtNumber(register_args));

あとは実行するだけです

f:id:sazae657:20151207222821p:plain

あら簡単!

後ろのxtermはUTF-8ですので、ちゃんとウイジェットからUTF-8で取れています。

お前すごいよMotif!現代でも全然通用するよ!めっちゃ古いのに!

参考資料など

20年ほど前は解説書籍やベンダー製のリファレンスなどが出版されていたようですが、絶版です。

OpenMotifのソースコードとman位しか現存していません。。。。

まとめ

Motif はかっこいい
Motif はモダン
Motif 最高
ありがとう[digital]

この記事は CDE 環境を構築した openSUSE にて Visual Studio Codeで書きました

f:id:sazae657:20151208193945p:plain The Common Desktop Environment


9日目は hang chang - Adventarさんによる 今さら聞けない、はじめてのOGP設定 - Funny panic!です

サンパク

f:id:sazae657:20150701004825j:plain

〆てから2日位置いて食べるとおいしいですよ!

f:id:sazae657:20150701005108j:plain

広告を非表示にする

ドリコムの俺を支えるUIツールキット

これは ドリコム Advent Calendar 2014 - Adventar の8日目です。

7日目は おーはら (@ohrdev) | Twitter さんによる 写経 - 般若心経F*ck、コピペで徳を高める話 - Qiita です。

f:id:sazae657:20141207134418p:plain

自己紹介

@sazae657

配線工や商業作曲家等を経て、現在はドリコムにてクロスプラットフォーム開発向けのミドルウエアやデータプロテクション等の研究開発をしています。

ドリコム釣り部CFO(Chief Fishing Officer)

本日の話

クライアント/サーバー両用、クロスプラットフォームのライブラリを作っているので、データ生成ツールや逆アセンブラーなどの個人的に使うツールをたくさん作ります。

そのツール類の爆速開発を支える Motif についてゆる〜くお話しします。

背景

ライブラリをC/C++たまにアセンブラで作ります。
この時にデータ生成等のツールを作るのですが、コレがどんどん複雑化していきコマンドラインオプションが MagaCLI 並に大変な事になるようなゆゆ式事態が発生。
という事でGUIでマルチスレッドでポインティングデバイスでポチポチ出来る素敵ツールを作ろうと思ったのですが、iOSを扱うので開発端末はMacMacといえばXQuartz、X11といえばMotifという事でMotif を選択しました。

Motif って何よ

最近はGTKやQtは知ってるのに、Motifを聞いた事すら無いという人が増えてきて寂しい限りですが、X Window System 上で動く信頼と伝統のUIツールキットです。
超絶格好いいクールなLook&Feel とシンプルで洗練されたAPIが特徴です。

昔、布教しようと頑張りましたが・・・無理でした

以前はライセンスを買う必要があったので、Lesstif のような互換ライブラリを使いましたが、現在は 非商用に限りOpenMotif として本家の Motif が使えます。ライブラリのビルドは簡単なので割愛します。

ちょっとアラートダイアログを出してみました。

f:id:sazae657:20141207153905p:plain

かっけえ!!!!

Windows Phone に端を発し、Nimdaも顔負けの大流行中っぷりを見せるフラットデザインとは真逆を行くエッジの立ったボタン、フォーカスの当たっているボタンは周りが大きく陥没し、これでもかと言うほどの存在感を放っています。
ビットマップフォントも美しいですね、やっぱ -misc-fixed-medium-r-normal--14- はいつ見ても美しいです。 シャープでエッジの効いた Motif にはアンチエイリアスボケボケフォントより -misc-fixed-medium-r-normal--14- ですね。

もう一個

f:id:sazae657:20141207161629p:plain

プルダウンメニューです。
とにかく立体的です。選択項目がボコボコと膨らむので非常にわかりやすい上に格好いいです。実際に触った全人類の90%はこの格好良さと独特のフィーリングの前に論議など無意味と知る事になるでしょう。


ツールといえばダイアログ、ダイアログの華といえばグループボックスに収まったラジオボタンチェックボックスですよね!

f:id:sazae657:20141207163640p:plain

Motifのソレはこれまた超絶格好いいのです。
ダイヤ型のラジオボタン、四角いチェックボックス
そう、シンセサイザーなんかのトグルスイッチがそうですね。UIのパーツ表現として非常非常ひじょーーーーー優秀です。

ボタンと同じようにフォーカスが当たっているウイジェットには黒い枠線が引かれ、キーボードでのフォーカス切り替え時の視認性は抜群です。細かい所まで気が利いてます。

GTKで実装されたGnomeやQtで実装されたKDEのようにMotifで実装されたデスクトップ環境のCDEという物もあります。
自宅で使っているSun Ultra45ワークステーションはずっとCDEです。メチャクチャ格好いいです。やる気がみなぎってきます。
現在はオープンソースになっているので是非、お試しください(超絶格好いいです)

The Common Desktop Environment

その昔、大枚叩いて買ったLinux向けのDeXtop CDEという物もありました。

以外とモダンだぜ Motif

古くさいように見えますが、結構モダンな造りなのが Motifの良い所
XmPushButton (超格好いいプッシュボタン)の定義はこんな感じになっています。

f:id:sazae657:20141207171638p:plain

そう、継承関係があるんですよ!!

Button は・・・
TextView を継承してるから setText が出来る
TextView は View を継承しているからsetXが出来る

どこぞの AWT のような感じというか同じです。モダンでしょ?

コレの定義がある Xm/PushB.h を見てみると・・・

/*
 * HISTORY
*/
/*   $XConsortium: PushB.h /main/12 1995/07/13 17:43:06 drk $ */
/*
*  (c) Copyright 1989, DIGITAL EQUIPMENT CORPORATION, MAYNARD, MASS. */
/*
*  (c) Copyright 1987, 1988, 1989, 1990, 1991, 1992 HEWLETT-PACKARD COMPANY */
/*
*  (c) Copyright 1988 MASSACHUSETTS INSTITUTE OF TECHNOLOGY  */
/*
*  (c) Copyright 1988 MICROSOFT CORPORATION */
/***********************************************************************
 *
 * PushButton Widget
 *
 ***********************************************************************/

Testing

古!!!!

カルチャー・クラブがアーハでテイク~オン~ミーな時代です

私は小学生になった直後くらいです

でも良いのです、良い物は古くても良いのです(みんな大好きCocoaだってNeXTSTEPからほとんど変わってません)

以外と簡単だぜ Motif

冒頭の方で出たダイアログのテキストラベルの部分の全ソースです
文字列をハードコードするのはダメですが、ここではハードコードしてしまいます
(リソースの外出しについては XToolkit intrinsics が面倒を見てくれます。Xtサイコー)

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <Xm/XmAll.h>

static char *fallback_resource[] = {
    "*fontList:  -misc-fixed-medium-r-normal--14-*-*-*-*-*-*-*:",
    "*title:   svchost",
    NULL
};


int
main(argc, argv)
    int argc;
    char **argv;
{
    XtAppContext app_context;
    Widget app, panel, label;
    Arg al[20];
    int ac;


    XtSetLanguageProc( NULL, NULL, NULL);

    ac = 0;
    app = XtAppInitialize(&app_context, "test", NULL, 0,
            &argc, argv, fallback_resource , al, ac);
    ac = 0;
    panel = XmCreateRowColumn(app, "panel", al, ac);
    XtManageChild(panel);

    ac = 0;
    XtSetArg(al[ac], XmNlabelString,
                XmStringCreateLtoR("右隣の部屋にはテレビが2台\n"
                                    "左隣の部屋にはスライスたくあんが1kgあるとする\n"
                                    "顧問がPCを1台着服したと仮定して、現在の情報処理部のPC台数を求めよ",
                XmSTRING_DEFAULT_CHARSET)); ac++;

    label = XmCreateLabel(panel,"label", al, ac);
    XtManageChild(label);

    XtRealizeWidget(app);
    XtAppMainLoop(app_context);
    return 0;
}

これだけです!

ボタンやメニューを置いてコールバックを付けても、隣の席の人がXcodeでテンプレートを生成してIBActionを線で結んでる間にコーディングが終わります。
さすがに爆速っぷりでは WindowsForms には勝てませんが、C/C++で書いたライブラリールーチンをそのまま呼べるので、変なラッパーを噛まさなくても実際にゲーム開発開発で使われているor使われる予定のルーチンを使ったデータ生成や読み込みが出来るのが非常に強いです。
変なライブラリをリンクしない&エンディアンに気をつければSolarisだろうがHP-UXだろうがソース互換です
あと、自動的に格好良くなります。

まとめ

Motif はかっこいい
Motif はモダン
Motif 最高

第9回は ひらしー (@y05_net) | Twitter さんです。