Xlib 編 (3)

前へ << Xlib 編 (2) Xlib 編 (4) >> 次へ

ウィンドウでボタンを作る

「複数のウィンドウ」と書いてあるので、画面に何個も ウィンドウが出てくるプログラムを作るのかなと思ったあなた、ハズレです。 もちろんそれも複数のウィンドウなんだけど、 今回はそうじゃなくてウィンドウ内ウィンドウを作成する。

例えばさ、Netscape Navigator を作るとしたらどうする? 画面の上部にはメニューがあり、その下にはアイコンがあり、さらに ブックマークプルダウンメニューなどもある。 そしてメインのブラウズ結果表示領域があって、一番下には ステータスバーがある。

とりあえずアイコンを考えよう。絵を出すのは置いといて、 「押せば何かがおこる領域」を作る。つまりボタンだね。 どうすればボタンを実現できる?

「ボタンウィジェットを作る」ってのはナシ。そもそも Xlib にはウィジェットなどという高級部品は存在しない。 「ButtonPress イベントを受けたら、マウスの座標が 指定の領域内にあるか調べる」。うん、確かにそれでもできる。 でも、もっといい方法があるのね。それがウィンドウ内ウィンドウ。

定番だけど、お絵書きソフトを作ろう。 キャンバス上でマウスを押すと線が引け、 画面上のボタンを押すことで、筆の太さ、色を選べるようにする。

色選択関数

まず、X での色の扱いについて説明しておこう。 showrgb コマンドを実行すると、
% showrgb
255 250 250             snow
248 248 255             ghost white
248 248 255             GhostWhite
245 245 245             white smoke
245 245 245             WhiteSmoke
220 220 220             gainsboro
255 250 240             floral white
255 250 240             FloralWhite
253 245 230             old lace
(略)
と、RGB の各値と使用可能な色の名前が表示される。 もちろん red・black・white・blue などもある。 red・black などの色の名前で指定すると、それに対応する RGB 値が使われるわけだ。 一方、RGB の値を直接指定することもできる。 "rgb:00/00/00" は RGB は 0 0 0 で、黒になる。 "rgb:FF/00/00" は "red" と等価である。

Xlib では、色は unsigned long の整数値で表される。 色の名前や RGB 値から unsigned long の整数値を取得するには

xlib-3.c

    6: unsigned long mycolor(Display *display, char *color){
    7:     Colormap cmap;
    8:     XColor c0,c1;
    9: 
   10:     cmap = DefaultColormap(display,0);
   11:     XAllocNamedColor(display,cmap,color,&c1,&c0);
   12:     return c1.pixel;
   13: }
という関数を作り、
unsigned long color;
color = mycolor(display,"red");          /* 赤 */
color = mycolor(display,"rgb:00/00/00"); /* 黒 */
color = mycolor(display,"rgb:00/ff/00"); /* 緑 */
などとすればよいわけ。

お絵書きプログラム

ちょっと長いけど、ソース全体はこんな感じ。

xlib-3.c

    1: #include <X11/Xlib.h>
    2: #include <X11/Xutil.h>
    3: 
    4: /* 色選択関数 */
    5: 
    6: unsigned long mycolor(Display *display, char *color){
    7:     Colormap cmap;
    8:     XColor c0,c1;
    9: 
   10:     cmap = DefaultColormap(display,0);
   11:     XAllocNamedColor(display,cmap,color,&c1,&c0);
   12:     return c1.pixel;
   13: }
   14: 
   15: 
   16: int main(int argc, char *argv[]){
   17:     Display *display;
   18:     Window window;
   19:     Window pen_window[5];
   20:     Window color_window[5];
   21: 
   22:     GC     gc;
   23:     char title[]      = "Paint";
   24:     char icon_title[] = "Paint";
   25:     unsigned long background;
   26:     unsigned long foreground;
   27:     int button_size=40;
   28: 
   29:     char *colors[]={
   30:         "rgb:00/00/00",
   31:         "rgb:ff/00/00",
   32:         "rgb:00/ff/00",
   33:         "rgb:00/00/ff",
   34:         "rgb:ff/ff/00",
   35:     };
   36: 
   37:     int current_pen=2;
   38:     unsigned long current_color=0;
   39:     int x0, y0, x1, y1;
   40:     int i;
   41:                      
   42:     display = XOpenDisplay(NULL);
   43: 
   44:     background = WhitePixel(display, 0);
   45:     foreground = BlackPixel(display, 0);
   46: 
   47:     window = XCreateSimpleWindow(display,
   48:                                  DefaultRootWindow(display),
   49:                                  0, 0, 500, 400,
   50:                                  0, 0, background);
   51: 
   52:     XSetStandardProperties(display, window, title, icon_title,
   53:                            None, argv, argc, NULL);
   54: 
   55:     /* GC を生成し、各種設定を行う */
   56:     gc = XCreateGC(display, window, 0, 0);
   57:     XSetBackground(display, gc, background);
   58:     XSetForeground(display, gc, current_color);
   59:     XSetLineAttributes(display, gc, current_pen,
   60:                        LineSolid, CapRound, JoinMiter);
   61: 
   62:     /* メインウィンドウのイベントマスクを設定 */
   63:     XSelectInput(display, window,
   64:                  ExposureMask |
   65:                  ButtonPressMask |
   66:                  ButtonMotionMask);
   67: 
   68:     /* ペンサイズ・色選択ウィンドウを作成 */
   69:     for ( i=0 ; i<sizeof(pen_window)/sizeof(pen_window[0]) ; i++ ){
   70:         pen_window[i] =
   71:           XCreateSimpleWindow(display, window,
   72:                               10, (button_size+10)*i+30,
   73:                               button_size, button_size,
   74:                               1, mycolor(display, "rgb:aa/aa/aa"),
   75:                               mycolor(display, "rgb:ee/ee/ee"));
   76:         color_window[i] =
   77:           XCreateSimpleWindow(display, window,
   78:                               500-10-button_size, (button_size+10)*i+30,
   79:                               button_size, button_size,
   80:                               1, mycolor(display, "rgb:aa/aa/aa"),
   81:                               mycolor(display, colors[i]));
   82:         XSelectInput(display, pen_window[i],
   83:                      ButtonPressMask |
   84:                      EnterWindowMask |
   85:                      LeaveWindowMask );
   86:         XSelectInput(display, color_window[i],
   87:                      ButtonPressMask |
   88:                      EnterWindowMask |
   89:                      LeaveWindowMask );
   90:     }
   91: 
   92:     /* 全てのウィンドウをマップ */
   93:     XMapWindow(display, window);
   94:     XMapSubwindows(display, window);
   95: 
   96:     while (1){
   97:         XEvent event;
   98:         XNextEvent(display, &event);
   99:         switch (event.type){
  100: 
  101:           case Expose:          /* 再描画 */
  102:             for ( i=0 ; i<sizeof(pen_window)/sizeof(pen_window[0]) ; i++ ){
  103:                 int pen_size = i*3+2;
  104:                                 /* ペンサイズウィンドウを再描画 */
  105:                 XSetForeground(display, gc, foreground);
  106:                 XFillArc(display, pen_window[i], gc,
  107:                          button_size/2-pen_size/2, button_size/2-pen_size/2,
  108:                          pen_size, pen_size, 0, 360*64);
  109: 
  110:             }
  111:             break;
  112: 
  113:           case EnterNotify:     /* ウィンドウにポインタが入った */
  114:             XSetWindowBorder(display, event.xany.window,
  115:                              mycolor(display, "black"));
  116:             break;
  117: 
  118:           case LeaveNotify:     /* ウィンドウからポインタが出た */
  119:             XSetWindowBorder(display, event.xany.window,
  120:                              mycolor(display, "rgb:aa/aa/aa"));
  121:             break;
  122: 
  123:           case MotionNotify:    /* ボタンを押しながらマウスが動いた */
  124:             x1 = event.xbutton.x;
  125:             y1 = event.xbutton.y;
  126:             XDrawLine(display, window, gc, x0, y0, x1, y1);
  127:             x0 = x1; y0 = y1;
  128:             break;
  129: 
  130:           case ButtonPress:     /* ボタンが押された */
  131: 
  132:                                 /* キャンバス上で押された? */
  133:             if ( event.xany.window == window ){
  134:                 x0 = event.xbutton.x;
  135:                 y0 = event.xbutton.y;
  136:                 XDrawLine(display, window, gc, x0, y0, x0, y0);
  137:                 break;
  138:             }
  139: 
  140:                                 /* ペンサイズ/色選択ウィンドウ上で押された? */
  141:             for ( i=0 ; i<sizeof(pen_window)/sizeof(pen_window[0]) ; i++ ){
  142:                                 /* ペンサイズを変更 */
  143:                 if ( event.xany.window == pen_window[i] ){
  144:                     current_pen = i*3+2;
  145:                     XSetLineAttributes(display, gc, current_pen,
  146:                                        LineSolid, CapRound, JoinMiter);
  147:                     break;
  148:                 }
  149:                                 /* 色を変更 */
  150:                 if ( event.xany.window == color_window[i] ){
  151:                     current_color = mycolor(display, colors[i]);
  152:                     XSetForeground(display, gc, current_color);
  153:                     break;
  154:                 }
  155:             }
  156:             break;
  157:         }
  158:     }
  159: }

   42:     display = XOpenDisplay(NULL);
   43: 
   44:     background = WhitePixel(display, 0);
   45:     foreground = BlackPixel(display, 0);
   46: 
   47:     window = XCreateSimpleWindow(display,
   48:                                  DefaultRootWindow(display),
   49:                                  0, 0, 500, 400,
   50:                                  0, 0, background);
   51: 
   52:     XSetStandardProperties(display, window, title, icon_title,
   53:                            None, argv, argc, NULL);
500x400 で背景が白のメインウィンドウを生成するんだけど、これまでと同じだね。
   56:     gc = XCreateGC(display, window, 0, 0);
   57:     XSetBackground(display, gc, background);
   58:     XSetForeground(display, gc, current_color);
   59:     XSetLineAttributes(display, gc, current_pen,
   60:                        LineSolid, CapRound, JoinMiter);
GC を生成し、背景色を白、前景色を黒 (current_color==0) に設定する。 また、XSetLineAttributes で線の太さを2 (current_pen の初期値) にする。 LineSolid・CapRound・JoinMiter は線の属性を表す定数である。
   63:     XSelectInput(display, window,
   64:                  ExposureMask |
   65:                  ButtonPressMask |
   66:                  ButtonMotionMask);
イベントを選択する。これまで通り Expose イベントを受け取りたいので、 ExposureMask を指定している。さらに ButtonPressMask・ButtonMotionMask を 指定することで、ButtonPress イベントと ButtonMotion イベントが通知されるようになる。 ButtonPress イベントはマウスボタンのクリック時に発生し、 ButtonMotion イベントはマウスボタンをクリックしたままマウスポインタを 動かした時、つまりドラッグ時に発生する。マウスをドラッグしようとすると、 最初のクリックで ButtonPress イベントが発生し、マウスを動かすたびに ButtonMotion イベントが発生するわけだ。
次に、左側に5個のペン選択ウィンドウ、右側に5個の色選択ウィンドウを作る。
   19:     Window pen_window[5];
   20:     Window color_window[5];
   27:     int button_size=40;
   28: 
   29:     char *colors[]={
   30:         "rgb:00/00/00",
   31:         "rgb:ff/00/00",
   32:         "rgb:00/ff/00",
   33:         "rgb:00/00/ff",
   34:         "rgb:ff/ff/00",
   35:     };
button_size はペン選択・色選択ボタンのサイズである。縦・横とも同じサイズになる。 colors は、色選択ウィンドウの色である。上から順に黒・赤・緑・青・黄色を表している。
   69:     for ( i=0 ; i<sizeof(pen_window)/sizeof(pen_window[0]) ; i++ ){
   70:         pen_window[i] =
   71:           XCreateSimpleWindow(display, window,
   72:                               10, (button_size+10)*i+30,
   73:                               button_size, button_size,
   74:                               1, mycolor(display, "rgb:aa/aa/aa"),
   75:                               mycolor(display, "rgb:ee/ee/ee"));
   76:         color_window[i] =
   77:           XCreateSimpleWindow(display, window,
   78:                               500-10-button_size, (button_size+10)*i+30,
   79:                               button_size, button_size,
   80:                               1, mycolor(display, "rgb:aa/aa/aa"),
   81:                               mycolor(display, colors[i]));
   82:         XSelectInput(display, pen_window[i],
   83:                      ButtonPressMask |
   84:                      EnterWindowMask |
   85:                      LeaveWindowMask );
   86:         XSelectInput(display, color_window[i],
   87:                      ButtonPressMask |
   88:                      EnterWindowMask |
   89:                      LeaveWindowMask );
   90:     }
左側に5個のペン選択ウィンドウ、右側に5個のペン選択ウィンドウを作る。
   69:     for ( i=0 ; i<sizeof(pen_window)/sizeof(pen_window[0]) ; i++ ){
というのは、配列の要素数を自動的に調べて、その数だけループを回している。 結構有名なテクニックだ (sizeof を計算するのはプリプロセッサなので、 実行時に動的に評価しているわけではない)。
   70:         pen_window[i] =
   71:           XCreateSimpleWindow(display, window,
   72:                               10, (button_size+10)*i+30,
   73:                               button_size, button_size,
   74:                               1, mycolor(display, "rgb:aa/aa/aa"),
   75:                               mycolor(display, "rgb:ee/ee/ee"));
ペン選択ウィンドウを作る。メインウィンドウと同じく XCreateSimpleWindow を使うが、 注目して欲しいのは第2引数の window。第2引数は、上位のウィンドウ (親ウィンドウ) を指定する。これまでは DefaultRootWindow(display)
ここまでで
[お絵書きプログラム]
という表示ができた。まだ Expose イベントを受けていないので、 左側のペン選択ウィンドウの内部は描かれていない。しかし、 右側の色選択ウィンドウは既に色が描かれている。 これは色の部分は XCreateSimpleWindow の最後の引数で指定したものである。 普通ウィンドウの中身は Expose イベントを待ってプログラム側が 描かなければいけないのだが、
[お絵書きプログラム]

ウィンドウ内ウィンドウ

「ウィンドウ内ウィンドウ」というと特別なウィンドウ作成の方法が あるのではないかと思うが、これまで見てきたように特に難しいことはない。

前へ << Xlib 編 (2) Xlib 編 (4) >> 次へ

$Id: xlib-3.html,v 1.2 2004/06/12 02:03:55 68user Exp $