X Window System, Client Programming, No.3


X-Window Systemにおけるイベントの扱い

お絵書きツールの作成

今までの知識を基に、簡単なお絵書きツールを作ることができます。


x5.c
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
int main() {
    Display *dpy;
    int scr;
    Window win;
    GC gc;
    XGCValues gcv;
    XSetWindowAttributes xswa;
    char *xservname=NULL;
    if ((dpy = XOpenDisplay(xservname)) == NULL) {
        fprintf(stderr, "cant open display %s\n",XDisplayName(xservname));
	exit(1);
    }
    scr = DefaultScreen(dpy);
    xswa.background_pixel = WhitePixel(dpy,scr);
    xswa.border_pixel = BlackPixel(dpy,scr);
    xswa.event_mask =
	ExposureMask | ButtonPressMask | ButtonReleaseMask;
    win=XCreateWindow(dpy,RootWindow(dpy,scr),0,0,320,240,1,
		      CopyFromParent,CopyFromParent,CopyFromParent,
		      CWBackPixel|CWBorderPixel|CWEventMask,&xswa);
    gcv.background = WhitePixel(dpy,scr);
    gcv.foreground = BlackPixel(dpy,scr);
    gcv.function = GXcopy;
    gc = XCreateGC(dpy,win,(GCForeground|GCBackground|GCFunction),&gcv);
    XMapWindow(dpy,win);
    for (;;) {
        XEvent ev;
	int x0,y0,x1,y1;
        XNextEvent(dpy, &ev);
        switch (ev.type) {
          case Expose:
            while (XCheckTypedEvent(dpy,Expose,&ev));
            XClearWindow(dpy,win);
            break;
	  case ButtonPress:
	    printf("button press\n");
	    if (ev.xbutton.button == 3) exit(0);
	    x0=ev.xbutton.x;
	    y0=ev.xbutton.y;
	    break;
	  case ButtonRelease:
	    printf("button release\n");
	    x1=ev.xbutton.x;
	    y1=ev.xbutton.y;
	    XDrawLine(dpy,win,gc,x0,y0,x1,y1);
	    break;
	  default:
            break;
        }
    }
}

rubber band

図形エディタなどでは、図形の大きさを指定するときに、 臨時の(「ゴム紐」のような)直線を描画したい場合があります。 そのような場合は GXinvert や GXxor などを Functionに 指定したグラフィック・コンテクストを用います。

diff -c x5.c x6.c
*** x5.c	Mon May 22 19:50:39 2006
--- x6.c	Mon May 22 19:50:46 2006
***************
*** 6,12 ****
      Display *dpy;
      int scr;
      Window win;
!     GC gc;
      XGCValues gcv;
      XSetWindowAttributes xswa;
      char *xservname=NULL;
--- 6,12 ----
      Display *dpy;
      int scr;
      Window win;
!     GC gc, gc2;
      XGCValues gcv;
      XSetWindowAttributes xswa;
      char *xservname=NULL;
***************
*** 18,24 ****
      xswa.background_pixel = WhitePixel(dpy,scr);
      xswa.border_pixel = BlackPixel(dpy,scr);
      xswa.event_mask =
! 	ExposureMask | ButtonPressMask | ButtonReleaseMask;
      win=XCreateWindow(dpy,RootWindow(dpy,scr),0,0,320,240,1,
  		      CopyFromParent,CopyFromParent,CopyFromParent,
  		      CWBackPixel|CWBorderPixel|CWEventMask,&xswa);
--- 18,24 ----
      xswa.background_pixel = WhitePixel(dpy,scr);
      xswa.border_pixel = BlackPixel(dpy,scr);
      xswa.event_mask =
! 	ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask;
      win=XCreateWindow(dpy,RootWindow(dpy,scr),0,0,320,240,1,
  		      CopyFromParent,CopyFromParent,CopyFromParent,
  		      CWBackPixel|CWBorderPixel|CWEventMask,&xswa);
***************
*** 26,31 ****
--- 26,33 ----
      gcv.foreground = BlackPixel(dpy,scr);
      gcv.function = GXcopy;
      gc = XCreateGC(dpy,win,(GCForeground|GCBackground|GCFunction),&gcv);
+     gcv.function = GXinvert;
+     gc2 = XCreateGC(dpy,win,(GCForeground|GCBackground|GCFunction),&gcv);
      XMapWindow(dpy,win);
      for (;;) {
          XEvent ev;
***************
*** 41,53 ****
--- 43,65 ----
  	    if (ev.xbutton.button == 3) exit(0);
  	    x0=ev.xbutton.x;
  	    y0=ev.xbutton.y;
+ 	    x1=x0; y1=y0;
+ 	    XDrawLine(dpy,win,gc2,x0,y0,x1,y1);
  	    break;
  	  case ButtonRelease:
  	    printf("button release\n");
+ 	    XDrawLine(dpy,win,gc2,x0,y0,x1,y1);
  	    x1=ev.xbutton.x;
  	    y1=ev.xbutton.y;
  	    XDrawLine(dpy,win,gc,x0,y0,x1,y1);
  	    break;
+ 	  case MotionNotify:
+ 	    printf("pointer motion\n");
+ 	    XDrawLine(dpy,win,gc2,x0,y0,x1,y1);
+ 	    x1=ev.xbutton.x;
+ 	    y1=ev.xbutton.y;
+ 	    XDrawLine(dpy,win,gc2,x0,y0,x1,y1);
+ 	    break;
  	  default:
              break;
          }

複数のウィンドウ

複数のウィンドウを利用するプログラムでは, イベント構造体の中の window フィールドを見て、 どのウィンドウに起きたイベントであるかを 判断する必要があります。


x7.c
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

struct _menudata {
    int x, y, width, height;
    char *name;
    Window win;
} menu[] = {
    { 0, 0, 80, 30, "Line"},
    { 0, 30, 80, 30, "Rectange"},
    { 0, 60, 80, 30, "Circle"}
};
int figure=0;
#define min(x,y)	(((x)<(y)) ? (x) : (y))
#define max(x,y)	(((x)>(y)) ? (x) : (y))
#define N_MENU	(sizeof(menu)/sizeof(struct _menudata))

void DrawFig(Display *dpy,Window win,GC gc,int x0,int y0,int x1,int y1) {
    int x, y, w, h;
    x = min(x0,x1);
    y = min(y0,y1);
    w = max(x0,x1) - min(x0,x1) +1;
    h = max(y0,y1) - min(y0,y1) +1;
    switch (figure) {
    case 0:
	XDrawLine(dpy,win,gc,x0,y0,x1,y1);
	break;
    case 1:
	XDrawRectangle(dpy,win,gc,x,y,w,h);
	break;
    case 2:
	XDrawArc(dpy,win,gc,x,y,w,h,0,360*64);
	break;
    default:
	fprintf(stderr,"unknown figure %d\n",figure);
    }
}

int main() {
    Display *dpy;
    int scr,i;
    Window win,menuwin;
    GC gc, gc2;
    XGCValues gcv;
    XSetWindowAttributes xswa;
    char *xservname=NULL;

    if ((dpy = XOpenDisplay(xservname)) == NULL) {
	fprintf(stderr, "cant open display %s\n",XDisplayName(xservname));
	exit(1);
    }
    scr = DefaultScreen(dpy);
    xswa.background_pixel = WhitePixel(dpy,scr);
    xswa.border_pixel = BlackPixel(dpy,scr);
    xswa.event_mask =
	ExposureMask | ButtonPressMask | ButtonReleaseMask | ButtonMotionMask;
    win=XCreateWindow(dpy,RootWindow(dpy,scr),0,0,320,240,1,
		      CopyFromParent,CopyFromParent,CopyFromParent,
		      CWBackPixel|CWBorderPixel|CWEventMask,&xswa);
    menuwin=XCreateWindow(dpy,RootWindow(dpy,scr),0,0,menu[0].width,
			  menu[N_MENU-1].y+menu[N_MENU-1].height,1,
			  CopyFromParent,CopyFromParent,CopyFromParent,
			  CWBackPixel|CWBorderPixel|CWEventMask,&xswa);
    for (i=0; i<N_MENU; ++i) {
	menu[i].win=XCreateWindow(dpy,menuwin,menu[i].x,menu[i].y,
				  menu[i].width, menu[i].height, 1,
				  CopyFromParent,CopyFromParent,CopyFromParent,
				  (CWBackPixel|CWBorderPixel|CWEventMask),&xswa);
    }
    gcv.background = WhitePixel(dpy,scr);
    gcv.foreground = BlackPixel(dpy,scr);
    gcv.function = GXcopy;
    gc = XCreateGC(dpy,win,(GCForeground|GCBackground|GCFunction),&gcv);
    gcv.function = GXinvert;
    gc2 = XCreateGC(dpy,win,(GCForeground|GCBackground|GCFunction),&gcv);
    XMapWindow(dpy,win);
    XMapWindow(dpy,menuwin);
    XMapSubwindows(dpy,menuwin);
    for (;;) {
	XEvent ev;
	int x0,y0,x1,y1;
	XNextEvent(dpy, &ev);
	switch (ev.type) {
	case Expose:
	    while (XCheckTypedWindowEvent(dpy,ev.xany.window,Expose,&ev));
	    XClearWindow(dpy,ev.xany.window);
	    for (i=0; i<N_MENU; ++i) {
		if (menu[i].win == ev.xany.window) {
		    XDrawString(dpy,ev.xany.window,gc,5,20,menu[i].name,
				strlen(menu[i].name));
		    break;
		}
	    }
	    break;
	case ButtonPress:
	    printf("button press\n");
	    if (ev.xany.window == win) {
		if (ev.xbutton.button == 3) exit(0);
		x0=ev.xbutton.x;
		y0=ev.xbutton.y;
		x1=x0; y1=y0;
		DrawFig(dpy,win,gc2,x0,y0,x1,y1);
	    } else {
		for (i=0; i<N_MENU; ++i) {
		    if (menu[i].win == ev.xany.window) {
			figure = i;
			break;
		    }
		}
	    }
	    break;
	case ButtonRelease:
	    printf("button release\n");
	    if (ev.xany.window == win) {
		DrawFig(dpy,win,gc2,x0,y0,x1,y1);
		x1=ev.xbutton.x;
		y1=ev.xbutton.y;
		DrawFig(dpy,win,gc,x0,y0,x1,y1);
	    }
	    break;
	case MotionNotify:
	    printf("pointer motion\n");
	    if (ev.xany.window == win) {
		DrawFig(dpy,win,gc2,x0,y0,x1,y1);
		x1=ev.xbutton.x;
		y1=ev.xbutton.y;
		DrawFig(dpy,win,gc2,x0,y0,x1,y1);
	    }
	    break;
	default:
	    break;
	}
    }
}


課題

[1]上記のクライアント(x5.c)を動作させてみましょう。
[2]上記のクライアント(x6.c)を動作させてみましょう。
[3]上記のクライアント(x7.c)を動作させてみましょう。
[4]x7.c を拡張して、以下の仕様を満たすように変更した ファイル x10.c を作成して下さい。 線の太さを変えるにはXChangeGC()関数を使って、グラフィック・ コンテクストの「線の太さ」を変更するとよいでしょう。
 XChangeGC関数で線の太さを変える
    XGCValues xgcv;
    xgcv.line_width = 整数値;
    XChangeGC(dpy,gc,GCLineWidth,&xgcv); /* このgcを使って絵を描く */

[提出物]
課題が完成したら、x10.c を[email protected] へ送って下さい。 コンパイルできなかったり、きちんと動作しないものを送っても 提出とは認めませんので注意して下さい。

上記の Email アドレス宛に送ったメイルは http://nw.tsuda.ac.jp/cgi-bin/cgiwrap/p2r9/mailhead で参照できます。Emailを送ったあとで、正しく提出されたかどうか 確認しておいて下さい。

提出期限は来週木曜日の 8:50a.m. です。


Yoshihisa Nitta

http://nw.tsuda.ac.jp/