|
○ はじめに
ウィンドウは様々な属性情報(背景色、サイズ、etc.)を持っていますが、そのうち、表示位置、サイズ、ボーダの幅のことを一般にジオメトリ情報といいます。これらのジオメトリ情報をプログラム中から変更することによって、ウィンドウのサイズや表示位置を動的に変更することができます。
ここでは、ウィンドウのジオメトリ情報を操作する方法について説明していきます。
○ サンプルプログラム(移動、サイズ変更)
ウィンドウ上に移動ボタン( MOVE )とサイズ変更ボタン( RESIZE )の 2 つのボタンを表示して、移動ボタンをクリックすると現在の表示位置より少し右下にウィンドウを移動し、サイズ変更ボタンをクリックするとウィンドウの拡大( 2 倍)/縮小( 1/2 倍)を交互に行うサンプルプログラム xctrl.c です。はじめに のページから、全てのサンプルプログラムを固めた tar ボールをダウンロードすることもできます。
今までのサンプルプログラムより処理が繁雑になるため、プログラムの構造を変えています。イベントループである xctrl_event_loop() と各ボタンが押された時の処理( xctrl_proc_moveb()、xctrl_proc_resizeb() )に注意して読んでみてください。
実行すると下図のようなウィンドウが表示されます。ボタンを押すと前述の動作をします。また、Esc ボタンを押すと終了します。
--- 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() が実行されていますが、これによって自動的に メインウィンドウの全ての子ウィンドウも破棄されます。
- Expose イベント時
- 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 );