LECTURE 7 Expose イベント

○ はじめに

LECTURE 5 グラフィックスの描画では、Expose イベントというイベントに対応した処理としてグラフィックスの描画を行いました。Expose イベントとは、簡単にいうとウィンドウの隠れていた部分が表に出た時に生じるイベントです。最初にウィンドウが表示される際にも通知されるし、他のウィンドウの下にあったウィンドウが上になった時にも通知されます。ウィンドウにとっては一番重要なイベントと言えるでしょう。

ここでは Expose イベントの詳細について説明していきます。

○ Expose イベントとは

あるウィンドウの隠れていた部分が表に表示された場合、Expose されたといいます。 Expose された場合には Expose イベントが X サーバから通知されるので、そのイベントに対応した処理として新たに表示された部分を描画しなければいけません(例外はありますが、いずれ)。Expose イベント時の描画処理を描かなかった場合は、X サーバがウィンドウのバックグラウンドカラーで塗りつぶします。

Expose イベントが発生するのは

  • ウィンドウがマップされたとき
  • ウィンドウの隠れていた部分が表に出たとき
  • ウィンドウの内容をクリアしたとき

です。LECTURE 5 で Expose イベントに対する処理として描画を行った理由ですが、ウィンドウを初めてマップする場合、マップされる前にウィンドウに描画しても画面には何も表示されません。よって、マップされて画面に表示されたことを表す Expose イベントを受け取ってからグラフィックスを描画するようにしているのです。

Expose イベントを受け取ったら、クライアントは Expose された領域を調べて再描画します。Expose された領域に以前描画されていた内容は失われているので、再描画するための情報はクライアント側で保存しておかなければいけません。

○ Expose イベント構造体

Expose イベント情報を格納するための Expose イベント構造体は次のように定義されています。

typedef struct
{
  int           type;          /* イベントの種類 */
  unsigned long serial;        /* サーバが処理した最後のリクエスト番号 */
  Bool          send_event;    /* クライアントから送られたイベントならTrue */
  Display*      display;       /* イベントが読み込まれたdisplay */
  Window        window;        /* イベントが起こった windowのID */
  int           x, y;          /* Exposeされた領域の左上の座標 */
  int           width, height; /* Exposeされた領域の幅と高さ */
  int           count;         /* 後いくつExposeイベントが来るか */
} XExposeEvent;

最初の type はイベントの種類を表しており、Expose イベントの場合には 定数 Expose が入ります。x、y、width、height によって Exposeされた長方形領域が表されます。Expose イベントは複数個同時に送られて来ることがありますが、毎回再描画していては無駄なので count が 0 の場合(一連の Expose イベントの最後)を待って描画するのが普通です。

serialには、クライアントが X サーバに送ったリクエストの通し番号が入ります。send_event はこのイベントが X サーバからではなく他のクライアントから XSendEvent() を使って送られたことを示すフラグです。

Status XSendEvent( Display* display,
                   Window   w, 
                   Bool     propagate, 
                   long     event_mask, 
                   XEvent*  evt );

○ クリッピング

描画という処理には時間がかかるので、なるべく無駄な描画を省くようにしないといけません。描画する領域を指定してその領域だけを描画するためにクリッピング(切り抜き)という処理を行うことがあります。クリッピングは GC の属性であるクリップマスクを用いて行います。

GC には clip_mask というメンバがあります。ここにはピクスマップを設定することができます。このピクスマップを設定した GC で描画を行うと、ピクスマップ中の 0 に対応する Drawable の位置には何も描画されません。このような働きをするピクスマップをクリップマスクと呼びます。

クリップマスクの設定には XSetClipMask() を使います。

XSetClipMask( Display* display, GC gc, Pixmap clip_mask );

clip_mask : クリップマスク

デフォルトでは、クリップマスクの左上の座標が描画対象の Drawable の原点に一致します。Drawable に対するクリップマスクの原点位置を変えたい場合は XSetClipOrigin() を使います。

XSetClipOrigin( Display* display, GC gc, int clip_x_origin, int clip_y_origin );

clip_x_origin : ↓
clip_y_origin : Drawable に対する、クリップマスクの原点位置座標。(0, 0)とすると
                Drawable の原点と一致

クリッピングをする場合の Expose イベント時の処理は次のように書けます。

  1. Expose イベントを受け取る
  2. Expose された領域に対応するクリップマスクを作る
  3. クリップマスクを GC にセットする
  4. その GC を用いて再描画する

Expose イベントで送られて来る領域は全て長方形なので、XSetClipRectangles() を使って簡単にクリップマスクを作成できます。

XSetClipRectangels( Display*    display, 
                    GC          gc, 
                    int         clip_x_origin, 
                    int         clip_y_origin,
                    XRectangle* rectangles, 
                    int         n, 
                    int         ordering 
                  );

clip_x_origin : ↓
clip_y_origin : クリップマスクの原点位置座標
rectangles;   : 長方形の配列
n             : 長方形の個数
ordering      : 長方形の順序関係

複数の連続した Expose イベントが送られて来た場合に、各 Expose イベントの長方形領域を rectangles(および n) に指定して実行すると、その長方形領域を論理和した領域がクリップマスクとして gc に設定されます。

ordering には rectangles の順序関係を設定します。次の 4 つのいずれかを指定します。

定数名 説明
Unsorted 特に順序関係はない(分からない場合はこれを使う)
YSorted 個々の長方形の原点のy座標が昇順である
YXSorted YSortedでy座標が同じ長方形の原点のx座標が昇順である
YXBounded YXSortedで同じy座標と高さを持つ