LECTURE 8 ウィンドウのコントロール

○ はじめに

ウィンドウは様々な属性情報(背景色、サイズ、etc.)を持っていますが、そのうち、表示位置、サイズ、ボーダの幅のことを一般にジオメトリ情報といいます。これらのジオメトリ情報をプログラム中から変更することによって、ウィンドウのサイズや表示位置を動的に変更することができます。

ここでは、ウィンドウのジオメトリ情報を操作する方法について説明していきます。

○ サンプルプログラム(移動、サイズ変更)

ウィンドウ上に移動ボタン( MOVE )とサイズ変更ボタン( RESIZE )の 2 つのボタンを表示して、移動ボタンをクリックすると現在の表示位置より少し右下にウィンドウを移動し、サイズ変更ボタンをクリックするとウィンドウの拡大( 2 倍)/縮小( 1/2 倍)を交互に行うサンプルプログラム xctrl.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。

今までのサンプルプログラムより処理が繁雑になるため、プログラムの構造を変えています。イベントループである xctrl_event_loop() と各ボタンが押された時の処理( xctrl_proc_moveb()、xctrl_proc_resizeb() )に注意して読んでみてください。

実行すると下図のようなウィンドウが表示されます。ボタンを押すと前述の動作をします。また、Esc ボタンを押すと終了します。

008control.jpg

--- xctrl.c ---

    1	#include <X11/Xlib.h>
    2	#include <string.h>
    3	
    4	#define WINDOW_W   400      /* ウィンドウの幅 */
    5	#define WINDOW_H   300      /* ウィンドウの高さ */
    6	#define BORDER     2        /* ボーダの幅 */
    7	#define MOVEB_W    70       /* 移動ボタンの幅 */
    8	#define MOVEB_H    30       /* 移動ボタンの高さ */
    9	#define RESIZEB_W  MOVEB_W  /* サイズ変更ボタンの幅 */
   10	#define RESIZEB_H  MOVEB_H  /* サイズ変更ボタンの高さ */
   11	
   12	
   13	static Display*       dpy;              /* ディスプレイ */
   14	static int            screen;           /* スクリーン */
   15	static Window         root;             /* ルートウィンドウ */
   16	static unsigned long  black, white;     /* 黒、白のピクセル値 */
   17	static GC             gc;               /* グラフィックスコンテキスト */
   18	static Window         win;              /* 表示するウィンドウ */
   19	static Window         move_b, resize_b; /* ボタン用ウィンドウ */
   20	static int            x = 100, y = 100; /* win の位置座標 */
   21	
   22	
   23	static void xctrl_init( void );
   24	static void xctrl_create_window( void );
   25	static void xctrl_event_loop( void );
   26	static void xctrl_proc_moveb( void );
   27	static void xctrl_proc_resizeb( void );
   28	
   29	
   30	int 
   31	main( void )
   32	{
   33		/* 初期処理 */
   34		xctrl_init();
   35	
   36		/* ウィンドウ作成 */
   37		xctrl_create_window();
   38	
   39		/* 僕の環境では XCreateSimpleWindow() の座標指定では指定通りの位置
   40		   に表示されないので、マップした後に一度移動させている */
   41		XMapWindow( dpy, win );
   42		XMoveWindow( dpy, win, x, y );
   43	
   44		/* winを親ウィンドウとする全ての子ウィンドウをマップする */
   45		XMapSubwindows( dpy, win );
   46	
   47		/* イベントループ */
   48		xctrl_event_loop();
   49	
   50		return 0;
   51	}
   52	
   53	
   54	static void 
   55	xctrl_init( void )
   56	{
   57		dpy = XOpenDisplay( "" );
   58	
   59		root   = DefaultRootWindow( dpy );
   60		screen = DefaultScreen( dpy );
   61	
   62		white = WhitePixel( dpy, screen );
   63		black = BlackPixel( dpy, screen );
   64	}
   65	
   66	
   67	static void 
   68	xctrl_create_window( void )
   69	{
   70		win = XCreateSimpleWindow( dpy, root, 100, 100, 
   71			   WINDOW_W, WINDOW_H, BORDER, black, white );
   72	
   73		move_b = XCreateSimpleWindow( dpy, win, 10, 10, 
   74			      MOVEB_W, MOVEB_H, BORDER, black, white );
   75	
   76		resize_b = XCreateSimpleWindow( dpy, win, 10 + MOVEB_W + 5, 10, 
   77			RESIZEB_W, RESIZEB_H, BORDER, black, white );
   78	
   79		gc = XCreateGC( dpy, win, 0, NULL );
   80		XSetBackground( dpy, gc, white );
   81		XSetForeground( dpy, gc, black );
   82	
   83		XSelectInput( dpy, win,      ExposureMask    | KeyPressMask );
   84		XSelectInput( dpy, move_b,   ButtonPressMask | ExposureMask );
   85		XSelectInput( dpy, resize_b, ButtonPressMask | ExposureMask );
   86	}
   87	
   88	
   89	static void
   90	xctrl_event_loop( void )
   91	{
   92		XEvent event;
   93	
   94		while( 1 ) {
   95			XNextEvent( dpy, &event );
   96			switch( event.type ) {
   97				case Expose:
   98					if ( event.xexpose.count == 0 ) {
   99						/* ボタンのラベルを描画 */
  100						XDrawString( dpy, move_b, gc, 
  101						    15, MOVEB_H / 2, "MOVE", 
  102						    strlen( "MOVE" ) );
  103	
  104						XDrawString( dpy, resize_b, gc, 
  105						    15, RESIZEB_H / 2, "RESIZE", 
  106						    strlen( "RESIZE" ) );
  107					}
  108					break;
  109	
  110				case ButtonPress:
  111					/* 移動ボタン */
  112					if ( event.xbutton.window == move_b  ) {
  113						xctrl_proc_moveb();
  114					}
  115	
  116					/* サイズ変更ボタン */
  117					else if ( event.xbutton.window == resize_b ) {
  118						xctrl_proc_resizeb();
  119					}	    
  120	
  121					break;
  122	
  123				case KeyPress:
  124					XFreeGC( dpy, gc );
  125					XDestroyWindow( dpy, win );
  126					XCloseDisplay( dpy );
  127					return;
  128			}
  129		}
  130	}
  131	
  132	
  133	static void 
  134	xctrl_proc_moveb()
  135	{
  136		/* ウィンドウマネージャが間にいるためか、XGetGeometry() では正しく
  137		   位置を取得できなかったので現在座標は別に保存しておくことにして、
  138		   それを引数としてウィンドウを移動させています。 */
  139	
  140		x += 10;
  141		y += 10;
  142		
  143		XMoveWindow( dpy, win, x, y );
  144	}
  145	
  146	
  147	static void 
  148	xctrl_proc_resizeb( void )
  149	{
  150		static int stat = 0;
  151	
  152		unsigned int w, h;
  153		unsigned int dummy;
  154	
  155	
  156		/* 現在の幅と高さを取得 */
  157		XGetGeometry( dpy, win, (Window*)&dummy, (int*)&dummy, (int*)&dummy, 
  158			      &w, &h, &dummy, &dummy );
  159	
  160		if ( ! stat ) {
  161			/* 拡大 */
  162			w    *= 2;
  163			h    *= 2;
  164			stat  = 1;
  165		}
  166		else {
  167			/* 縮小 */
  168			w    /= 2;
  169			h    /= 2;
  170			stat  = 0;
  171		}
  172	
  173		XResizeWindow( dpy, win, w, h );
  174	}
  175	
  176	/* End of xctrl.c */

○ サンプルプログラムの説明

サンプルプログラムについて説明していきます。

  • 4 〜 10 行目

    各種定数定義を行っています。今までのメインウィンドウ用の定数だけでなく、移動ボタンやサ イズ変更ボタン用の定数についても定義しています。

  • 13 〜 20 行目

    いろいろな処理を関数として切り分けたため、関数間で共有する変数を static 変数として宣言 しています。xctrl.c は説明しやすいように 1 ファイルにしているため static 変数が多くなっ ていますが、ちゃんと作るのであれば メインウィンドウ / 移動ボタン / サイズ変更ボタン そ れぞれについて、別ファイルに切り分ける方が良い作り方だと思います。

    18、19 行目で、移動ボタン / サイズ変更ボタンとして使用する Window 型変数を宣言しています。 これらは、メインウィンドウの子ウィンドウとして実現します。親子関係にあるウィンドウ間に は、次のような性質があります。

    • 親ウィンドウと子ウィンドウは一緒に動きますが、親ウィンドウをリサイズしても子ウィンド ウの大きさは変わりません。親ウィンドウの大きさに合わせて子ウィンドウの大きさを変えた い場合には、親ウィンドウのリサイズを ConfigureNotify イベントによって検知して、自分で 子ウィンドウの大きさを変えなければいけません。

    • 子ウィンドウの親ウィンドウからはみだした部分は、親ウィンドウのボーダでクリッピングさ れるため表示されません。

    前者の性質により、xctrl.c で RESIZE ボタンをクリックしてウィンドウを拡大/縮小しても、 ボタンのサイズは変わりません。

  • 23 〜 27 行目

    xctrl.c では、ここでプロトタイプ宣言されている関数単位に処理を切り分けています。各関数 については、以降で説明します。

  • 30 〜51 行目 ( main() )

    xctrl.c の main 関数です。おおまかな処理は、

    • 初期処理 ( xctrl_init() )
    • ウィンドウ作成 ( xctrl_create_window() )
    • ウィンドウのマップ ( XMapWindow() )
    • 初期表示座標へのウィンドウの移動 ( XMoveWindow() )
    • 子ウィンドウのマップ ( XMapSubwindows() )
    • イベントループ ( xctrl_event_loop() )

    となります。XCreateSimpleWindow() の座標指定で希望通りの位置にウィンドウが表示される場合 は、XMoveWindow() を実行する必要はありません。

  • 54 〜 64 行目 ( xctrl_init() )

    ここでは、

    • X サーバとの接続
    • ルートウィンドウ、スクリーン情報の取得
    • 白、黒色のピクセル値を取得

    という処理を初期処理として行っています。

  • 67 〜 86 行目 ( xctrl_create_window() )

    メインウィンドウ、移動ボタン、サイズ変更ボタンの 3 つのウィンドウを作成し、その後描画に 使用する GC を作成しています。この GC はメインウィンドウを使って作成していますが、3 つ のウィンドウは同じ深さなので、ここで作成した GC を使ってボタンへの描画も行えます。

    その後、各ウィンドウに必要なイベントを選択しています。メインウィンドウでは Esc キーの押 下を検出するために KeyPressMask を、ボタンではマウスクリックを検出するために ButtonPressMask を指定しています。

  • 89 〜 130 行目 ( xctrl_event_loop() )

    主処理を行うイベントループです。while ループ内で、Expose イベント、ButtonPress イベント、 KeyPress イベントを拾って処理を振り分けています。

    • Expose イベント時

      XDrawString() を使用してボタンに表示されるラベルを描画しています。ButtonPress イベン トのように、イベント発生元ウィンドウを判定していないため、3 つのウィンドウで Expose イベントが発生した場合全てでこの処理が行われます。この無駄さが嫌な場合は、移動ボタン の Expose イベント時には移動ボタンのラベル描画を、サイズ変更ボタンの Expose イベント 時にはサイズ変更ボタンのラベル描画を行うように書き換えるといいと思います。

    • ButtonPress イベント時

      まず event.xbutton.window メンバ(イベントの発生元ウィンドウ ID )と各ボタンのウィンド ウ ID を比較して、移動ボタンのウィンドウ ID と一致した場合は xctrl_proc_moveb() を、 サイズ変更ボタンのウィンドウ ID と一致した場合は xctrl_proc_resizeb() を実行していま す。

    • KeyPress イベント時

      まず、XLookupString() を使って KeyPressEvent で通知されたキーコード番号を ASCII 文字 列および KeySym 名に変換しています。これについては、LECTURE 10 で詳しく説明します。

      変換によって得られた KeySym 名と Esc キーの KeySym 名( XK_Escape ) を比較して、一致す る場合にはリソースを解放してプログラムを終了しています。リソースの解放において、メイ ンウィンドウに対してのみ XDestroyWindow() が実行されていますが、これによって自動的に メインウィンドウの全ての子ウィンドウも破棄されます。

  • 133 〜 144 行目 ( xctrl_proc_moveb() )

    移動ボタンがクリックされた場合に実行される処理です。メインウィンドウの現在の位置座標の x と y にそれぞれ 10 を足した後に XMoveWindow() を使ってウィンドウを移動させています。 ウィンドウの現在位置を取得するには、本来は後程説明する XGetGeometry() を使います。しか し、XCreateSimpleWindow() の座標指定がうまくいかないのと同じ理由からか僕の環境では正し い位置座標が取得できなかったため、ファイルスコープを持つ static 変数に現在位置を保存す るようにしています。

  • 147 〜 174 行目 ( xctrl_proc_resizeb() )

    サイズ変更ボタンがクリックされた場合に実行される処理です。まず、メインウィンドウの現在 の幅と高さを取得するために、XGetGeometry() を使っています(これについては正常に動作しま す)。

      Status XGetGeometry( Display*      display, 
                           Drawable      d, 
                           Window*       root, 
                           int*          x, 
                           int*          y, 
                           unsigned int* width, 
                           unsigned int* height, 
                           unsigned int* border, 
                           unsigned int* depth 
                         );
    
      root          : d のルートウィンドウの ID が格納される
      x, y          : d の親ウィンドウ内での座標が格納される
      width, height : d の幅と高さが格納される
      border, depth : d のボーダの幅と深さが格納される
    
    

    XGetGeometry() を使うと、指定した Drawable の、親ウィンドウ内での位置と大きさに関する情 報が取得できます。Drawable がピクスマップの場合は x、y、border は存在しないのでつねに 0 が格納されます。XGetGeometry() は、成功時には 0 以外の値、失敗時には 0 を返します。

    変数 stat に現在の状態( 0 ならば通常状態、1 ならば拡大状態としている)が格納されているの で、それに応じて新たな幅と高さを計算し、XResizeWindow() を使ってウィンドウのサイズを変 更しています。

      XResizeWindow( Display*     display, 
                     Window       w, 
                     unsigned int width, 
                     unsigned int height 
                   );
    
      width  : 変更後のウィンドウの幅
      height : 変更後のウィンドウの高さ
    
    

    ちなみに、XResizeWindow() と XMoveWindow() の処理を同時に行う XMoveResizeWindow() とい う関数があります。

      XMoveResizeWindow( Display*     display, 
                         Window       w, 
                         int          x, 
                         int          y, 
                         unsigned int width, 
                         unsigned int height 
                       );
    
    

以上で xctrl.c の説明は終りです。なお、xctrl.c では使わなかったウィンドウのボーダ幅を変更する関数、およびウィンドウを一番上 / 下に表示しなおす関数を以下に紹介しておきます。

○ ウィンドウのボーダ幅を変更する

ウィンドウのボーダ幅を変更するには XSetWindowBorderWidth() を使います。

XSetWindowBorderWidth( Display* display, Window w, unsigned int width );

width : 新しいボーダ幅

○ ウィンドウを一番上/下に表示する

ジオメトリ操作ではありませんが、ウィンドウを一番上に表示するには XRaiseWindow() を使います。その逆に一番下に表示するには XLowerWindow() を使います。

XRaiseWindow( Display* display, Window w );
XLowerWindow( Display* display, Window w );