Super Technique 講座

Xプログラミング入門〜応用編:変形ウィンドウの実現

このページではXプログラミングの初歩を解説する(←このページは嘘)。だから、少しも「Super」な内容ではない(←これも嘘)。しかし、「単に書けりゃいいやん」というノリの解説ではなくて、Xプログラミングの全体像を理解することを目的として書いたのである(←これはホント)。まあ、原則的に Ahtena ツールキットによるプログラミングをベースにしているが、他のツールキットのやり方もある程度見据えている。

さて、久々の追加となる。2005年に入ってから、死ぬほど忙しかった...ああ、次はそろそろこれをしたいなあと思いながら忙殺されていたのだが、やっと手がつけられるぞ。まあ、この「変形ウィンドウの実現」は「研究」の部類のものだ。そりゃ中には変形ウィンドウ物のプログラムもあるんだが、かなり娯楽系だな、フツー。良く知られているものとしては「xeyes」とか、「EmiClock」とかがあるが、そういったプログラムがどうやってこれを実現しているか、というのが今回のテーマだ。

まあ、ここでのサンプルコード自体は、筆者が以前「変り時計」を作るという企画を立ててやりかけたんだが、結局流れたものの研究成果だ。要するに「日の出/日没を基準に動作する時計」とか「潮の満ち引きを基準にして動作する時計」とかいうのをやろうと思ったんだが、意外にこいつら面倒なので止めてしまった...まあ、企画としてはイイでしょ。

で、具体的にどうやるか..というのはやはりゼロから出来るわけじゃなし、とっかかりは他人のプログラムを研究するのである。その「とっかかり」に使ったのは EmiClock である。これは X,Windows,Mac の3種類のウィンドウシステムをすべて制覇した変形ウィンドウで有名なプログラムだ(仕様は結構オタクだが...)。で、今回これを扱うのは、要するに「変形ウィンドウを実現している」からだけじゃなくて、これが「ツールキットを使って自作ウィジットを作っている」からなのである。

ね、なかなか凄いでしょ。だから「ツールキットの正体を暴く!!」というノリでこの解説は書いていく。だから「実用性」というよりも「ツールキット自体の構造を理解する」というなかなか深いものである。そんなつもりで読んでくれたまえ。


Xlib が実現する拡張機能

まあ、変形ウィンドウの実現自体は、X の機能として見ればこれは「Xの拡張機能」を使ってやるものだ。ライブラリやヘッダは本体とは別で、/usr/X11R6/lib/libXext.so とかにあって、ヘッダは /usr/X11R6/include/X11/extensions ディレクトリにあるようなシリーズである。この X の拡張機能はイロイロのようだが、なんだか内容がはっきりしない物も多くて、今一つようわからん....が、Xvlib.h なんてのはビデオを再生するための奴のようだったりする。まあ、そのうちこれでもいじってみても面白いかもしれん。

イキナリで恐縮だが、まずは変形ウィンドウ(Xlib版)のソースでも見た方が早いや。

#include <X11/Xlib.h>
#include <X11/extensions/shape.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <memory.h>

/* 表示するサイズ */
int Rect = 80;
/* ビット変換のため */
char BitMask[8] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80 };

/* まず手始めに1pixel=1byte のバッファを作成
  半径 r の円のバッファである。 */
char *make_shape( int r )
{
   char *ret;
   int i, j;
   int radius, radius2;
   int l1, l2;

   ret = malloc( r * r );
   if( ret == NULL ){
      fprintf( stderr, "cannot malloc\n" );
      exit( 1 );
   }
   radius = r / 2; radius2 = radius * radius;
   memset( ret, 0, r * r );
   for( i = 0; i < r; i++ ){
      for( j = 0; j < r; j++ ){
         l1 = i - radius; l1 *= l1; 
         l2 = j - radius; l2 *= l2;
         if( l1 + l2 < radius2 ){
            ret[i * r + j] = 1;
         }
      }
   }
   return ret;
}

/* で作成した 1pixel=1byte のバッファを 
  1pixel=1bit のバッファに詰めてやる */
char *make_bm( int r, char *buff )
{
   char *ret, *p;
   int i, j;
   int size;

   size = (r + 7)/8; size *= r;
   ret = malloc( size );
   if( ret == NULL ){
      fprintf( stderr, "cannot malloc\n" );
      exit( 1 );
   }
   memset( ret, 0, size );
   p = ret;
   for( i = 0; i < r; i++ ){
      for( j = 0; j < r; j++ ){
         if( buff[i * r + j] ){
            *p |= BitMask[j % 8];
         }
         /* 行末は余らせる */
         if( j % 8 == 7 ){
            p++;
         }
      }
      if( j % 8 ){
         p++;
      }
   }
   return ret;
}

int main()
{
   Display *D;
   Window W;
   GC  gc;
   XEvent event;
   Pixmap bm;
   int eventBase, errorBase;
   char *rectbuff, *rectmask;

   /* まずは定型的に... */
   D = XOpenDisplay( "" );
   W = XCreateSimpleWindow( D, RootWindow(D,0), 0, 0, Rect, Rect,
                  0, BlackPixel(D,0), WhitePixel(D,0) );

   /* X拡張機能の検索で、SHAPE Extension があるか?? */
   if (!XShapeQueryExtension(D, &eventBase, &errorBase)) {
      fprintf( stderr, "not support shape extension\n" );
      exit(1);
   }

   /* さて、円形パターンを作成する。メモリ管理が良くないけど.. */
   rectbuff = make_shape( Rect );
   rectmask = make_bm( Rect, rectbuff );

   /* Shape Extension 用のビットマップを生成 */
   bm = XCreateBitmapFromData( D,
              RootWindow(D,0),
              rectmask, Rect, Rect);
   free( rectbuff ); free( rectmask );
   gc = XCreateGC( D, W, 0, 0 );

   /* 作成したビットマップを元にウィンドウの形にする */
   XShapeCombineMask( D, W,
               ShapeBounding, 0, 0,
               bm, ShapeSet);

   /* あとも定型的 */
   XMapWindow( D, W );
   while( 1 ){
      XNextEvent( D, &event );
      switch( event.type ){
          /* 何もしてない...ゴメン */
      }
   }
}

これをコンパイルするには、次のようにする。要するに拡張機能ライブラリもリンクしないといけないのである。

% gcc -o en en.c -L/usr/X11R6/lib -lXext -lX11

コンパイルが通るんなら、実行してみれば「出た出た月が...」じゃないが、まん丸で白いウィンドウが適当に表示されたと思う。特に終了とかサポートしてないので、Ctrl+C ででも殺してくれ。

要するに見慣れない関数は赤字で示した2つだけだ。

Bool XShapeQueryExtension( Display *d, int *event_base, int error_base)
これはSHAPE拡張機能をサーバがサポートしているか否かを問い合わせる。当然 TRUE ならばサポートしているので、安心して使える。
void XShapeCombineMask( Display *d, XID window, int kind, int x, int y, Pixmap src, int operation )
これが機能の実体。Pixmap src で定義された形でウィンドウを作成する。

まあ、コードを見れば 1pixel=1bit となるようにウィンドウ形状を定義したバッファを用意して、これを Pixmap に変換して、XShareCombineMask に与えているだけで、変形ウィンドウが一丁上がり、というのは一目瞭然だ。こんなモンである。

ちなみにどんな拡張機能があるのか、を検索するには次のようにすれば判る。

#include <X11/Xlib.h>
#include <stdio.h>

int main()
{
   Display *D;
   int num;
   char **ext;
   int i;

   D = XOpenDisplay( "" );
   ext = XListExtensions( D, &num );

   if( num > 0 ) {
      for( i = 0; i < num; i++ ) {
         int code, event, error;
         printf( "%s:\n", ext[i] );
         if( XQueryExtension( D, ext[i], &code, &event, &error ) ) {
            printf( "\tOpCode=%d event=%d error=%d\n", code, event, error );
         }
      }
   }
   XCloseDisplay( D );
   return 0;
}

筆者環境での出力結果はこんなもの。ふう、いろいろと拡張機能が入っているんだな(計30個も!)。

SHAPE:
        OpCode=128 event=64 error=0
MIT-SUNDRY-NONSTANDARD:
        OpCode=129 event=0 error=0
BIG-REQUESTS:
        OpCode=130 event=0 error=0
SYNC:
        OpCode=131 event=65 error=128
MIT-SCREEN-SAVER:
        OpCode=132 event=67 error=0
XC-MISC:
        OpCode=133 event=0 error=0
XFree86-VidModeExtension:
        OpCode=134 event=0 error=130
XFree86-Misc:
        OpCode=135 event=0 error=137
XFree86-DGA:
        OpCode=136 event=68 error=145
DPMS:
        OpCode=137 event=0 error=0
FontCache:
        OpCode=138 event=0 error=150
TOG-CUP:
        OpCode=139 event=0 error=0
Extended-Visual-Information:
        OpCode=140 event=0 error=0
XVideo:
        OpCode=141 event=75 error=152
X-Resource:
        OpCode=142 event=0 error=0
DOUBLE-BUFFER:
        OpCode=143 event=0 error=155
RECORD:
        OpCode=144 event=0 error=156
DEC-XTRAP:
        OpCode=145 event=77 error=157
MIT-SHM:
        OpCode=146 event=78 error=166
XInputExtension:
        OpCode=147 event=79 error=167
XTEST:
        OpCode=148 event=0 error=0
XKEYBOARD:
        OpCode=149 event=94 error=172
LBX:
        OpCode=150 event=95 error=173
XC-APPGROUP:
        OpCode=151 event=0 error=174
SECURITY:
        OpCode=152 event=97 error=175
XFree86-Bigfont:
        OpCode=153 event=0 error=0
RENDER:
        OpCode=154 event=0 error=177
RANDR:
        OpCode=155 event=98 error=0
GLX:
        OpCode=156 event=99 error=182
SGI-GLX:
        OpCode=156 event=99 error=182


ツールキットの正体

さて、今までの話なんてジャブに過ぎない。本番は今からだ。

そりゃ Xlib で頑張ってXアプリを実装してもいい(というかこういう変形ウィンドウ物はツールキットなんか使わなくてもOKのものが多いだろう)が、リソース管理とか考えるとちょっとばかり厄介だ。なので今からツールキット版を作って行くのだが、これの基本コンセプトはこうだ。

自作ウィジットが変形ウィンドウをサポートする!

そりゃそうだ...Xaw でも Motif でも shapedWidgetClass なんてものはない...だから、これは自分で作るっきゃない。なのでこれはこれで大変なのだが、その大変さとは、実際には定型的な関数しか呼び出さない XIntrinsic の機能をフル活用しなければならないことにある。

なので、こんな具合にしよう。とりあえずウィジット・クラス作成の気分を味わうために、ヘッダファイル sugclock.h、ライブラリコードの sugclocklib.c、アプリコードの clock.c と分割してやる。なんとなく Athena のウィジットみたいな気がするかな?

コンパイル&リンクは通常だが、リンクはライブラリがいくつか必要なので、こんなコマンドラインになる。共有ライブラリにしたきゃ勝手にどうぞ。

% gcc -o sugclock sugclocklib.o clock.o -L/usr/X11R6/lib -lXt -lXext -lX11

で、一応Xは「継承モデル」ということになっているので、この SugClock クラスは、Core クラスというイントリンシクスの中で定義された、ウィンドウを持つクラスのメタクラスとして定義されたクラスを継承することにする。

これは要するに、イントリンシックで定義されたデータ構造体が2つあり、「クラス」を示す「クラスデータ構造体」(たとえば labelWidgetClass で参照できるもの)と、個々のウィジットを示す「ウィジットデータ構造体」(たとえば labelWidget で参照できるもの)の2つの構造体を、自分で定義するのだが、その時に親クラスのデータ構造を先頭の入れ子構造体として「継承」する、ということなのだ。要するに次のようになる。

/* クラスデータ構造体 */
typedef struct  _SugClockCR {
   CoreClassPart  core_class;    /* Core クラスを継承 */
   struct {            /* これが新ウィジットの追加分 */
       int        dummy;   /* 特に不要 */ 
   } sugclock_class;
} SugClockCR;

/* ウィジットデータ構造体 */
typedef struct  _SugClockR {
   CorePart    core;   /* Core クラスを継承 */
   struct {            /* これが新ウィジットの追加分 */
      /* リソース */
      int radius;      /* 直径 */
      /* など。詳細はあとで。*/
   } sugclock;
} SugClockR;

/* ウィジットやクラスらしく名前をつけておく */
typedef struct _SugClockCR *SugClockWidgetClass;
typedef struct _SugClockR  *SugClockWidget;

/* アプリ側でウィジットクラスを参照できるようにする */
extern WidgetClass  sugClockWidgetClass;

赤字で強調した親クラスの定義は、/usr/X11R6/include/X11/CoreP.h にある。が、イントリンシックの内部ヘッダである X11/IntrinsicP.h をインクルードすれば、イントリンシック定義の基底クラスたちのヘッダが、もれなく付いてくるので、そうするのがよかろう。

#include <X11/IntrinsicP.h>

のように「内部用ヘッダ」(クラス名P.h)をインクルードしてやる必要がある。

で、これはCなので、「継承」と言ってもしっかりデータを継承できるわけではなくて、親クラスで定義されたリソースやコールバックも、このやり方だと自前で値を与えてやらなくてはならない。後で見るが、この構造体を初期化し(インスタンス化)する時に、特に「クラスデータ構造体」の方はさまざまなコールバック関数のエントリを与えてやらなくてはならない。どんなものを実装しなくてはならないかは後で見よう。

じゃあ実装サブクラスである SugClock クラスが扱うリソースだが、これは紙幅の都合で非常に単純化する。だってねえ、リソースが増えればそれだけ長くなるもんね、許して欲しい。XLib の例と同様に、単に丸が表示されるだけだ。それでも...

radius
円の直径。これが縦横のサイズになる。
fillcolor
中身の色。

くらいは設定できるようにしよう。これは当然、引数で渡そうとも、リソースファイルに書こうとも、ちゃんと反映するのはツールキットのメリットである。

あと、リソースにはしないが、それでもリソース風にプログラム内で扱うものとしては、次のものを用意した。これらはプログラム側で関数を呼び出して設定する。これはまあ、要するにあまりウィジットの内部構造データを見せない方が良いからね。オブジェクト指向のカプセル化という奴である。

void setInterval( SugClockWidget sug, int milisec );
コールバックの間隔を設定する。
void setCallback( SugClockWidget sug, void (*callback)(Widget w) );
タイマコールバックルーチンを設定する。一応時計を想定したクラスなので、このコールバックの中で針を動かして描画するのである。
void setShapeMask( SugClockWidget sug, char *buff );
ウィンドウの「かたち」を定義した char 配列を渡し、かたちを設定する。もう少し良いインターフェイスにしたいのだが、思い付かないのでこうした。

ツールキット版〜ヘッダ

ではヘッダファイル sugclock.h をお目にかけよう。

#include <X11/extensions/shape.h>
#include <X11/IntrinsicP.h>

/* ウィジットデータ構造体 */
typedef struct  _SugClockR {
   CorePart   core;   /* Core クラスを継承 */
   struct {           /* これが新ウィジットの追加分 */
      /* リソース */
      int radius;      /* 直径 */
      Pixel FillColor; /* 表示色 */

      /* コールバックはプログラム上からだけ設定 */
      int clockInterval;   /* コールバックの間隔 */
      void (*callback)(Widget w); /* コールバック */

      /* 内部データ */
      int intervalID;   /* 1秒ごとにコールバックをするためのID */
      Pixmap  rect;     /* SHAPE で使う形状定義 */
      Pixmap BackImage; /* バッキングストア */
      GC FillGC;        /* 描画GC */
   } sugclock;
} SugClockR;

/* クラスデータ構造体 */
typedef struct  _SugClockCR {
   CoreClassPart  core_class;   /* Core クラスを継承 */
   struct {                     /* これが新ウィジットの追加分 */
      int      dummy;           /* 特に不要 */ 
   } sugclock_class;
} SugClockCR;

#define DEFAULT_SIZE 101
#define DEFAULT_INTERVAL 1000

/* リソース用に文字列定義 */
#define XtNradius "radius"
#define XtCRadius "Radius"
#define XtNfillcolor "fillcolor"
#define XtCFillColor "FillColor"

/* ウィジットやクラスらしく名前をつけておく */
typedef struct _SugClockCR *SugClockWidgetClass;
typedef struct _SugClockR  *SugClockWidget;

/* アプリ側でウィジットクラスを参照できるようにする */
extern WidgetClass  sugClockWidgetClass;

/* ウィジット固有メソッド */
extern void setInterval( SugClockWidget sug, int milisec );
extern void setCallback( SugClockWidget sug, void (*callback)(Widget w) );
extern void setShapeMask( SugClockWidget sug, char *buff );

通常の Athena などのツールキットのウィジットクラスだと、label.h と labelP.h の2つに分かれているが、これだと分けるまでもないので一緒である。X11/Xaw/label.h などでお馴染みな記述(リソース文字列の定義なんかね)もいろいろあるな。

で、ポイントは「ウィジットデータ構造体」だ。これは実際にウィジットを生成すると、「Widget w」で返るアレなのだが、これも構造体ポインタに過ぎないのであり、その中には「リソース」とリソースを使うために内部で保持しておく「内部データ」が定義されているのである。リソースは2つ(radius, fillcolor)で、リソースに準じて固有メソッドで設定する内部リソースが2つ(clockInterval, callback)があることになる。

まあ、ヘッダファイルはそう難しいものではなかろう。


ツールキット版〜ライブラリソース

次はライブラリ風にしてある、再利用可能なソースだ。まずはデータから。

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/extensions/shape.h>
#include <X11/IntrinsicP.h>

#include "clock.h"

/* 親クラスが求める実装すべきコールバック 。
   いわゆる「イントリンシック・メソッド」*/
static void Initialize(Widget, Widget, ArgList, Cardinal *);
static void Realize(Widget, XtValueMask *, XSetWindowAttributes *);
static void Destroy(Widget);
static void expose(Widget, XEvent *, Region);
static Boolean SetValues(Widget, Widget, Widget, ArgList, Cardinal *);
static XtGeometryResult QueryGeometry(Widget, XtWidgetGeometry *,
                                      XtWidgetGeometry *);

/* クラス固有のコールバック */
static void Callback( Widget w );

/* 注意! リソースは2つ設定部分あり。クラス内部で設定する初期リソースと、
個別のアプリの側で設定するデフォルトリソースの2つである。ここはクラス定義 */
/* 初期リソース */
static XtResource resource_in_class[] = {
   {
      XtNradius, XtCRadius, XtRInt, sizeof(int),
      XtOffset(SugClockWidget, sugclock.radius), 
      XtRImmediate, (XtPointer)DEFAULT_SIZE },
   {
      XtNfillcolor, XtCFillColor, XtRPixel, sizeof(Pixel),
      XtOffset(SugClockWidget, sugclock.FillColor), 
      XtRString, "white" },
};

/* デフォルトトランスレーションは特になし */
static char    defaultTranslations[] = "";

/* さて、これがクラスの実体 */
SugClockCR     sugClockCR = {
   {  /* Core クラスの再設定 */
      /* まず全体情報 */
      &widgetClassRec,                        /* superclass */
      "SugClock",                             /* class_name */
      sizeof(SugClockR),                      /* size */
      /* 特殊なクラス・イニシャライザなのですべて NULL */
      NULL,                                   /* class_initialize */
      NULL,                                   /* class_part_initialize */
      FALSE,                                  /* class_inited */
      /* さて、通常のイントリンシクス・メソッドなどの登録 */
      (XtInitProc)Initialize,                 /* initialize */
      NULL,                                   /* initialize_hook */
      (XtRealizeProc)Realize,                 /* realize */
      /* 初期アクションテーブルはなし */
      NULL,                                   /* actions */
      0,                                      /* num_actions */
      /* 初期リソースの設定 */
      resource_in_class,                      /* resources */
      XtNumber(resource_in_class),            /* num_resources */
      /* 細かいイベントの取扱いについて... */
      NULLQUARK,                              /* xrm_class */
      TRUE,                                   /* compress_motion */
      TRUE,                                   /* compress_exposure */
      TRUE,                                   /* compress_enterleave */
      TRUE,                                   /* visible_interest */
      /* イントリンシクス・メソッド */
      (XtWidgetProc)Destroy,                  /* destroy */
      XtInheritResize,                        /* resize */
      (XtExposeProc)expose,                   /* expose */
      (XtSetValuesFunc)SetValues,             /* set_values */
      NULL,                                   /* set_values_hook */
      XtInheritSetValuesAlmost,               /* set_values_almost */
      NULL,                                   /* get_values_hook */
      NULL,                                   /* accept_focus */
      XtVersion,                              /* version */
      NULL,                                   /* callback_private */
      /* デフォルトのトランスレーション */
      defaultTranslations,                    /* tm_table */
      (XtGeometryHandler)QueryGeometry,       /* query_geometry */
      NULL,                                   /* display_accelerator */
      NULL,                                   /* extension */
   },
   {
      (int)NULL ,                             /* dummy */
   },
};

/* クラスっぽい名前で参照 */
WidgetClass  sugClockWidgetClass = (WidgetClass) &sugClockCR;

で問題は当然 sugClockCR のメンバたちである。ここで一番重要なのは、当然イントリンシックが要求するさまざまなコールバックが、このメンバになっている、ということである。実際、何種類もあるのだが、ここで設定しているのは6つだけだ。その役割りを簡単に概説しよう。

void Initialize(Widget request, Widget new, ArgList args, Cardinal * num)n
初期化コールバック。XtVaCreateWidget() から呼ばれる。ということは、未リアライズのウィジットなので、「ウィンドウ実体がすでにある」ことを前提とした処理は出来ないのは言うまでもない。ここでは少なくともウィジットの幅(new->core.width)と高さ(new->core.height)を設定することが規約上求められている。
void Realize(Widget w, XtValueMask *vm, XSetWindowAttributes *attr)
リアライズコールバック。XtRealizeWidget() から呼ばれる。要するにこの中で XtCreateWindow() を呼んで、実際にウィンドウを作成するのが仕事である。今回は当然、ここで変形ウィンドウの「かたち」を設定する。
void Destroy(Widget w)
破棄コールバック。ウィジットの内部で確保したリソースを解放する。
void expose(Widget w, XEvent *event, Region region)
再描画コールバック。名前のとおり Expose イベントが生成した時に呼ばれ、再描画をする。今回はかなり単純でOK。
Boolean SetValues(Widget old, Widget request, Widget new, ArgList args, Cardinal *num )
リソース値設定コールバック。これがあるのは、要するに依存しあうリソースがある場合に、何かのリソースを変更したら別なリソースも作り替えなきゃならないケースに対応するためである。今回は特に動いている最中に動的にリソース変更をすることは想定していないので、実際には何もしていない。
XtGeometryResult QueryGeometry(Widget w, XtWidgetGeometry *prop, XtWidgetGeometry *ans )
ジオメトリ計算コールバック。サイズ変更などに対応するのだったら、これをちゃんと実装するのだが、今回は単に固定内容(要するに radius で縦横が決まる)を ans 引数に設定して返るのが本体。戻り値は XtGeometryYes=変更しました、XtGemortryNo=変更不可、XtGeometryAlmost=変更したがかならずしも要求(prop引数)の通りではない、を返すことになる。今回は戻り値はあまり意味がない....(そもそもサイズ変更に対応してないしな...)

こんな具合のイントリンシック・メソッドを実装したコードが次だ。

/* いわゆる「イントリンシック・メソッド」。初期化をする。
   まだWindow実体がないので、大したことはできない。 */
static void Initialize(Widget request, Widget new, ArgList args, Cardinal * num)
{
   SugClockWidget sug = (SugClockWidget)new;
   int eventBase, errorBase;

   /* とにかくサイズ指定をすることを求められている */
   if (request->core.width == 0) {
      sug->core.width = DEFAULT_SIZE;
   }
   if (request->core.height == 0) {
      sug->core.height = DEFAULT_SIZE;
   }
   /* 一応ここで SHAPE Extension があるかどうかをチェック */
   if (!XShapeQueryExtension(XtDisplay(sug), &eventBase, &errorBase)) {
      XtAppError( XtWidgetToApplicationContext(new), 
               "not support shape extension" );
   }
}

/* いわゆる「イントリンシック・メソッド」。
   リアライズされたタイミングで呼ばれる。*/
static void Realize(Widget w, XtValueMask *vm, XSetWindowAttributes *attr)
{
   SugClockWidget sug = (SugClockWidget)w;
   int r = sug->sugclock.radius;
   Display *d = XtDisplay(sug);
   Window root = RootWindowOfScreen(XtScreen(sug));
 
   /* さて、これをするのが本来の仕事 */
   XtCreateWindow(w, (unsigned)InputOutput, (Visual *)CopyFromParent,
               *vm, attr);

   /* 加えてここでは「形」の設定が必要である */
   if( sug->sugclock.rect ) {
      XShapeCombineMask( d, XtWindow(XtParent(w)),
                     ShapeBounding, 0, 0,
                     sug->sugclock.rect, ShapeSet);
   }

   /* まず丸い表示画面を作成
      一応バッキングストアであらかじめ生成しておき、あとは貼るだけ */
   sug->sugclock.FillGC = XCreateGC( d, root, 0, 0 );
   XSetForeground( d, sug->sugclock.FillGC, sug->sugclock.FillColor );
   sug->sugclock.BackImage = 
      XCreatePixmap( d, root, r, r, DefaultDepth(d,DefaultScreen(d)) ); 
   XFillArc( d, sug->sugclock.BackImage, sug->sugclock.FillGC,
           0, 0, r, r, 0, 360 * 64 );


   /* ついでにバッキングストアをコピーする */
   XCopyArea(XtDisplay(w), sug->sugclock.BackImage,
           XtWindow(sug), sug->sugclock.FillGC, 0, 0,
           sug->sugclock.radius, sug->sugclock.radius, 0, 0 );

   /* タイマコールバックの最初の設定 */
   sug->sugclock.intervalID = 
      XtAppAddTimeOut( XtWidgetToApplicationContext(w), 
                   sug->sugclock.clockInterval, 
                   (XtTimerCallbackProc)Callback, w );
}

/* いわゆる「イントリンシック・メソッド」。
   ウィジット破棄のタイミングで呼び出される。リソースを解放する。*/
static void Destroy(Widget w)
{
   SugClockWidget sug = (SugClockWidget)w;
   Display *d = XtDisplay(sug);

   if( sug->sugclock.rect ) {
       XFreePixmap( d, sug->sugclock.rect );
       sug->sugclock.rect = None;
   }
   if( sug->sugclock.FillGC ) {
       XFreeGC( d, sug->sugclock.FillGC );
       sug->sugclock.FillGC = None;
   }
   if( sug->sugclock.BackImage ) { 
       XFreePixmap( d, sug->sugclock.BackImage );
       sug->sugclock.BackImage = None;
   }
   if( sug->sugclock.intervalID != 0 ){
       XtRemoveTimeOut( sug->sugclock.intervalID );
       sug->sugclock.intervalID = 0;
   }
}

/* いわゆる「イントリンシック・メソッド」。
   Expose された時に呼ばれるので再描画をする。*/
static void expose(Widget w, XEvent *event, Region region)
{
   SugClockWidget sug = (SugClockWidget)w;

   if (sug->core.visible) {
      /* 表示されていなければ何もしないでよし */
      if (event->xexpose.count == 0) {
         /* エリアの再描画 */
         XCopyArea(XtDisplay(w), sug->sugclock.BackImage,
                 XtWindow(sug), sug->sugclock.FillGC, 0, 0,
                 sug->sugclock.radius, sug->sugclock.radius, 0, 0 );

         /* タイマコールバックの再設定 */
         if( sug->sugclock.intervalID != 0 ){
            XtRemoveTimeOut( sug->sugclock.intervalID );
            sug->sugclock.intervalID = 0;
         }
         sug->sugclock.intervalID = 
            XtAppAddTimeOut( XtWidgetToApplicationContext(w), 
                         sug->sugclock.clockInterval, 
                         (XtTimerCallbackProc)Callback, w );
      }
   }
}

/* いわゆる「イントリンシック・メソッド」。
   リソース再設定で呼ばれるが、依存関係のあるリソースはないので何もしない */
static Boolean SetValues(Widget old, Widget request, Widget new, ArgList args,
                         Cardinal *num )
{
   SugClockWidget sug = (SugClockWidget)old;
   return True;
}

/* いわゆる「イントリンシック・メソッド」。
   ジオメトリ再計算で呼ばれるが、特にここでは固定内容を返せばよし */
static XtGeometryResult QueryGeometry(Widget w, XtWidgetGeometry *prop,
                                      XtWidgetGeometry *ans )
{
   SugClockWidget sug = (SugClockWidget)w;
   int r;

   r = sug->sugclock.radius;

   ans->request_mode = CWWidth | CWHeight;
   ans->width = r;
   ans->height = r;

   if (((prop->request_mode & (CWWidth | CWHeight)) ==
       (CWWidth | CWHeight)) &&
      (prop->width == ans->width) &&
      (prop->height == ans->height)) {
      return(XtGeometryYes);
   } else if ((ans->width == w->core.width) &&
            (ans->height == w->core.height)) {
      return(XtGeometryNo);
   }
   return(XtGeometryAlmost);
}

であとは sugClock 固有で用意するもの。これには時計らしい動作をさせるためのタイマコールバック関連の実装と、変形ウィンドウのかたちの設定メソッドがある。こんなん読めば判る。

/* SugClock クラス固有のコールバック
   タイマで呼ばれるコールバックである。本来時計の針を進める目的である */
static void  Callback( Widget w )
{
   SugClockWidget sug = (SugClockWidget)w;

   /* タイマはワンショットなので再設定が必要 */
   if( sug->sugclock.intervalID != 0 ){
      XtRemoveTimeOut( sug->sugclock.intervalID );
      sug->sugclock.intervalID = 0;
   }
   if( sug->sugclock.callback ) {
      sug->sugclock.intervalID = 
         XtAppAddTimeOut( XtWidgetToApplicationContext((Widget)w), 
                      sug->sugclock.clockInterval, 
                      (XtTimerCallbackProc)Callback, w );
      /* で、アプリ側で登録したコールバックを呼び出す */
      (*sug->sugclock.callback)( w );
   }
}

/* ウィジット固有メソッド */
void setInterval( SugClockWidget sug, int milisec )
{
   sug->sugclock.clockInterval = milisec;
}

void setCallback( SugClockWidget sug, void (*callback)(Widget w) )
{
   sug->sugclock.callback = callback;
}

void setShapeMask( SugClockWidget sug, char *mask )
{
   int r = sug->sugclock.radius;
   Display *d = XtDisplay(sug);
   Window root = RootWindowOfScreen(XtScreen(sug));

   Pixmap bm = XCreateBitmapFromData( d, root, mask, r, r );
   sug->sugclock.rect = bm;
}

ツールキット版〜アプリソース

さて、最後はアプリケーションのソースだ。なるべく XLib をいじるとか、ウィジットデータにアクセスしないようにうまく仕事を分割しているので、読みやすいと思う。まあ、普通の「ウィジット使いソース」みたいになってる。

まず、さまざまなリソース定義から。

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>

#include <stdio.h>
#include <stdlib.h>

#include "clock.h"

/* コールバック */
static void Quit(Widget, XEvent *, String *, Cardinal *);

/* 内部関数 */
static void prepare( Widget w );
char *make_shape(int);
char *make_bm(int,char *);
static void draw_hand( Widget w );


/* アクションテーブル */
static XtActionsRec    actionList[] = {
   { "quit",               (XtActionProc)Quit },
};

/* 作業用のリソースレコード */
typedef struct  _AppData {
   int radius;      /* 直径 */
   Pixel FillColor; /* 描画色 */
   int clockInterval; /* コールバックの間隔  */
} AppData, *AppDatap;

AppData app_data, *app_datap;

/* オプション設定 */
static XrmOptionDescRec options[] = {
   { "-radius", "*radius", XrmoptionSepArg, NULL },
   { "-fillcolor", "*fillcolor", XrmoptionSepArg, NULL },
};

/* リソース */
static XtResource resource[] = {
   {
      XtNradius, XtCRadius, XtRInt, sizeof(int),
      XtOffset(AppDatap, radius), 
      XtRImmediate, (XtPointer)DEFAULT_SIZE },
   {
      XtNfillcolor, XtCFillColor, XtRPixel, sizeof(Pixel),
      XtOffset(AppDatap, FillColor), 
      XtRString, "white" },
};

/* トランスレーションでクリックすると終了を設定 */
String  MainTrans = "#override\n\
      <Btn1Down>: quit()";

次はアプリコード。まあ、こんなものだ。ホントに読むべき内容がないな...

/* ウィジット */
Widget top, sug;

/* メイン関数 */
int main( int argc, char **argv )
{
   XtAppContext        appContext;
   char                *programName = NULL;    /* プログラム名 */

   XtSetLanguageProc(NULL, NULL, NULL);

   programName = argv[0];
   top = XtVaAppInitialize(
                     &appContext,    /* アプリケーションコンテクスト */
                     "SugClock",     /* アプリケーションのクラス名 */
                     options,        /* アプリケーション固有のコマンド行引数 */
                     XtNumber(options), /* アプリケーションコマンド行引数の数 */
                     &argc, argv,    /* コマンド行引数 */
                     NULL,           /* デフォルトのリソース設定 */
                     NULL);          /* 可変長引数の終端 */

   /* アクションテーブルの設定 */
   XtAppAddActions(appContext, actionList, XtNumber(actionList));

   /* トランスレーションの設定 */
   XtOverrideTranslations(top,
                     XtParseTranslationTable("WM_PROTOCOLS: quit()"));

   /* アプリケーションリソースの取得 */
   XtVaGetApplicationResources(top,
                        &app_data,
                        resource,
                        XtNumber(resource),
                        NULL);

   /* トップレベル Widget の幅と高さを一応設定 */
   XtVaSetValues(top,
              XtNwidth,    app_data.radius,
              XtNheight,   app_data.radius,
              NULL);

   /* さて、ウィジットを生成 */
   sug = XtVaCreateManagedWidget(
                          "sugclock",           /* Widget 名 */
                          sugClockWidgetClass,  /* Widget クラス */
                          top,          /* 親となる Widget */
                          XtNtranslations,     
                                      XtParseTranslationTable(MainTrans),
                          NULL);                /* 可変長引数の終端 */

   /* その他のリソース設定 */
   prepare( sug );

   /* GO!! */
   XtRealizeWidget(top);
   XtAppMainLoop(appContext);

   return  0;
}

/* いろいろとリソースを設定する下請け関数 */
static void prepare( Widget wig )
{
   SugClockWidget sug = (SugClockWidget)wig;
   char *rectbuff, *rectmask;

   /* ウィンドウの形の Bitmap を作成 */
   int r = app_data.radius;
   rectbuff = make_shape( r );
   rectmask = make_bm( r, rectbuff );
   free( rectbuff );
   setShapeMask( sug, rectmask ); 
   free( rectmask );

   /* 「時計」という設定なので、少しはそれっぽく.. */
   setInterval( sug, 1000 );      /* 時間間隔 */
   setCallback( sug, draw_hand );  /* コールバック */
}


/* タイマコールバック */
static void draw_hand( Widget w )
{
   /* 本来は針の動きを表示するのを実装 */
   printf( "redraw hands...\n" );
}

/* 終了のコールバック */
static void Quit(Widget w, XEvent *event, String *params, Cardinal *num)
{
   exit(0);
}


/* 後は既出 */
static char BitMask[8] = { 0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80 };

char *make_shape( int r )
{
   /* 省略 */
}

char *make_bm( int r, char *buff )
{
   /* 省略 */
}

ふう、これで「Super Technique講座」と謳いながら「全然内容がSuperじゃないぞ!!」という「Xプログラミング入門」がようやく「Super Technique」になったように思う。まあ、自前ウィジット作成なんて「Super」の極みなんで、実用性ははっきり言って知らない。けど、ここらへんの解説なんてフツーないから、オリジナル・コンテンツとしては過激なものになったんじゃないかしら。けどねえ、Athena(3Dを含め)はダサいし、Motif は重いし...で、最近は Xt ベースのツールキットの旗色は悪いので、これが今後どの程度参考にされるのか、も知らんぞ、これ。ほとんど趣味の世界だな....



copyright by K.Sugiura, 1996-2006