Events

  • there are many types of events that the X server sends to the client program, these events are usually generated by user actions
  • for most graphics programming the most interesting of these events are the button events and the motion events
  • the button event is generated whenever a button is pressed or released, the declaration of the event structure for this type of event is:
        typedef struct {
    	    int type;             /* of event */
    	    unsigned long serial; /* # of last request processed by server */
    	    Bool send_event;      /* true if this came from a SendEvent request */
    	    Display *display;     /* Display the event was read from */
    	    Window window;        /* "event" window it is reported relative to */
    	    Window root;          /* root window that the event occured on */
    	    Window subwindow;     /* child window */
    	    Time time;            /* milliseconds */
    	    int x, y;             /* pointer x, y coordinates in event window */
    	    int x_root, y_root;   /* coordinates relative to root */
    	    unsigned int state;   /* key or button mask */
    	    unsigned int button;  /* detail */
    	    Bool same_screen;     /* same screen flag */
        } XButtonEvent;
    
        typedef XButtonEvent XButtonPressedEvent;
        typedef XButtonEvent XButtonReleasedEvent;
    
  • the event field contains the type of event that this structure represents, all event structures have this field
  • if a button was pressed the event type is ButtonPress and if a button was released the event type is ButtonRelease
  • the serial field is the serial number of the last request processed by the X server, every request sent to the X server by the client has a serial number on it, this field tells the client program how far behind the X server is, that is the number of client requests that haven’t been processed yet
  • the send_event field is a boolean field, if the value of this field is TRUE the event was generated by another client and not the X server
  • display is the display where the event was generated, and the window field contains the window where the event was generated
  • the root field contains the root window of the window where the event was generated, usually this is the highest level window in the window hierarchy for the application
  • if the event was generated in a subwindow of the window given by the window field, then this subwindow is stored in the subwindow field, this only occurs if the subwindow doesn’t have an event handler while its parent does have an event handler
  • the time field contains the time when the event was generated, this time is measured in milliseconds
  • the x and y fields contain the coordinates of the mouse pointer when the button was pressed, these coordinates are relative to the window where the event occured, that is the window given by the window field – this is usually the window that the event handler is attached to
  • the root_x and root_y fields contain the coordinates of the mouse pointer relative to the root window
  • the state and button fields specify the button state when the event occured and the button that generated the event
  • the state field is a bit mask that has one bit for each of the buttons that are monitored by X, some of the possible bits are:
        Button1Mask         Left mouse button
        Button2Mask         Middle mouse button
        Button3Mask         Right mouse button
        ShiftMask           Shift key
        LockMask            Shift lock key
        ControlMask         Control key
    
  • if both the left mouse button and control key were pressed and held down then the value of the state field would be Button1Mask | ControlMask
  • the button field contains the button that caused the event to occur, for a three button mouse the possible values for this field are Button1, Button2, and Button3
  • the ButtonPressMask is used as the event mask for an event handler that is to received button press events, and the ButtonReleaseMask is used as the event mask for event handlers that are to receive button release events, the same event handler could be used for both types of events
  • when the mouse moves MotionNotify events are generated, the event structure for this type of event is:
        typedef struct {
    	    int type;             /* of event */
    	    unsigned long serial; /* # of last request processed by server */
    	    Bool send_event;      /* true if this came from a SendEvent request */
    	    Display *display;     /* Display the event was read from */
    	    Window window;        /* "event" window reported relative to */
    	    Window root;          /* root window that the event occured on */
    	    Window subwindow;     /* child window */
    	    Time time;            /* milliseconds */
    	    int x, y;             /* pointer x, y coordinates in event window */
    	    int x_root, y_root;   /* coordinates relative to root */
    	    unsigned int state;   /* key or button mask */
    	    char is_hint;         /* detail */
    	    Bool same_screen;     /* same screen flag */
        } XMotionEvent;
    
        typedef XMotionEvent XPointerMovedEvent
    
  • most of the fields in this event structure are the same as the one for button presses
  • in this case the value of the type field will be MotionNotify
  • the x and y fields will contain the x and y coordinates of the mouse relative to the window where the mouse motion occured
  • the state field contains a bit mask that specifies the current state of the buttons, so we can tell which buttons were pressed when the event occured
  • there are a number of event masks that can be used for the motion event, these event masks specify when the motion event will be generated
  • if the Button1MotionMask is used the event handler will only be called if the mouse moves while the left mouse button is press
  • similarly if the Button2Mask or Button3Mask is specified the event handler will only be called if the middle or right mouse button is pressed
  • if the ButtonMotionMask is used the event handler will be called if the mouse moves while any of the buttons is pressed
  • if the PointerMotionMask is used the event handler will be called whenever the mouse moves, regardless of whether any of the mouse buttons are pressed
  • the only other event that occurs frequently in graphics programming is the ConfigureNotify event
  • this event is generated whenever the state of the window changes, the important state changes are the position of the window, the size of the window, the size of the window border and its stacking order (the windows that are above and below it)
  • the main use of this event is to detect when an window has become smaller and its contents needs to be redrawn to fit in the smaller size
  • the declaration of this event structure is:
        typedef struct {
    	    int type;
    	    unsigned long serial;   /* # of last request processed by server */
    	    Bool send_event;        /* true if this came from a SendEvent request */
    	    Display *display;       /* Display the event was read from */
    	    Window event;
    	    Window window;
    	    int x, y;
    	    int width, height;
    	    int border_width;
    	    Window above;
    	    Bool override_redirect;
        } XConfigureEvent;
    
  • the type field of this event will have the value ConfigureNotify
  • the window field contains the window whose state has been changed
  • the x and y fields are the new position of the window, these fields contain the position of the windows upper left corner relative to its parent window
  • the width and height fields contain the new width and height of the window
  • the border_width field contains the new width of the window border
  • if the StructureNotifyMask bit is set in the event mask, then the event handler will be called when a configure notify event occurs, the other type of events that can be generated with this mask value (for example window creation events), so the event handler must check the type field to determine if a configure notify event was really received

Using Events in Graphics Programming

  • now we will look at how we can use the X event mechanism in interactive graphics programming
  • in a large number of interactive graphics programs we want to manipulate graphical information on the screen, the information displayed on the screen will change as the user interacts with it
  • out previous example program used a single procedure to draw all the graphics on the screen, this procedure was called each time that an exposure event was generated
  • this technique works well if the graphics stay relatively static, we can place all the graphics calls in a procedure that can be called when events occur
  • this technique doesn’t work very well when the graphics are more dynamic, the number of objects on the screen changes as the user interacts with the program
  • in this case we need to maintain a list of the objects that are currently on the screen, each time the user adds a new object a new item is placed on this list to represent that object
  • similarly, each time an object is removed from the screen the corresponding entry is removed from the list
  • we can call this list a display list, since it represents all the objects that must be displayed
  • there are many ways of organizing a display list, one way is to build a linked list with one entry for each object on the screen, for each type of object we create a record structure that contains all the information required to draw that object
  • in addition each record structure contains at least a type field that indicates the type of information stored in the record, it could also contain a pointer to routine which draws the object
  • to see how his approach works we will construct a very simple drawing editor, this editor will allow the user to enter lines and rectangles
  • a rubberbanding techniques is used for entering the lines and rectangles
  • we will start the example program by producing the display list structure that it needs and the event handler that is required to keep this display list on the screen
  • we will need two structures that we can use to represent the lines and rectangles, these structures will start off with three common fields, the first field contains the type of the record, the second field is a pointer to its draw procedure, and the third field is a pointer to the next record in the list, we get the following declarations:
        typedef void (*func)();
    
        struct line_struct {
    	 int       type;
    	 func      draw;
    	 char      *next;
    	 double    x1, y1;
    	 double    x2, y2;
        };
    
        typedef struct line_struct *Line;
    
        struct rect_struct {
    	 int       type;
    	 func      draw;
    	 char      *next;
    	 double    x1, y1;
    	 double    x2, y2;
        };
    
        typedef struct rect_struct *Rectangle;
    
  • we will need procedures to create these record structures and add them to the display list, we will also need a procedure that will go through the display list and display each of the items on the list
  • in this example we will make use of object oriented programming techniques, this will make it easier to extend the program to deal with other types of geometrical shapes
  • the first use of this idea is the inclusion of a draw procedure in each record of the display list, for each type of geometrical object we will write a procedure that is capable of drawing that object, a pointer to this procedure is placed in the record for that structure
  • when we need to redraw the contents of the screen all we need to do is go down the display list calling the procedure that is pointed to by the draw field
  • our draw procedure takes three parameters, the widget to draw in, the graphics context to be used, and the record itself, in the case of the line record we have the following procedure
        void display_line(w,gc,line)
        Widget w;
        GC gc;
        Line line; {
    	    int x1, y1, x2, y2;
    
    	    x1 = width * line->x1;
    	    y1 = height * (1.0 - line->y1);
    	    x2 = width * line->x2;
    	    y2 = height * (1.0 - line->y2);
    
    	    XDrawLine(XtDisplay(w), XtWindow(w), gc, x1, y1,
    	       x2, y2);
    
        }
    
  • the width and height variables are set by the procedure that redraws the screen
  • there is a very similar procedure for drawing rectangles
  • since we have a procedure to draw each type of object, the redisplay routine is quite simple, we can use the following code:
        redisplay(w)
        Widget w; {
    	    char *list;
    	    Line node;
    	    func p;
    	    int n;
    	    Arg wargs[5];
    	    GC gc;
    
    	    XClearWindow(XtDisplay(w), XtWindow(w));
    
    	    n=0;
    	    XtSetArg(wargs[n], XtNheight, &height;); n++;
    	    XtSetArg(wargs[n], XtNwidth, &width;); n++;
    	    XtGetValues(w, wargs, n);
    
    	    gc = XCreateGC(XtDisplay(w), XtWindow(w),
    		   NULL, NULL);
    	    XSetForeground(XtDisplay(w), gc, 1);
    	    XSetBackground(XtDisplay(w), gc, 0);
    
    	    list = display_list;
    	    while(list != NULL) {
    		    node = (Line) list;
    		    p = node->draw;
    		    (*p)(w, gc, node);
    		    list = node->next;
    	    }
        }
    
  • this procedure first clears the window to be redisplayed
  • it then determine the height and width of the window and stores them in a global variables
  • a graphics context is created for drawing the objects
  • then we go through the display list calling the draw function for each record on the display list
  • note that this procedure knows nothing about what’s being drawn, all its does is call the draw procedure for the object, we can add new objects to this program without changing this procedure, essentially the redraw process is independent of the types of objects to be drawn
  • the display list is maintained by the procedures that are used to create the objects, in the case of line records this procedure has the following form:
        Line make_line(x1, y1, x2, y2)
        int x1, y1, x2, y2; {
    	    Line result;
    
    	    result = (Line) malloc(sizeof *result);
    	    result->type = LINE;
    	    result->draw = display_line;
    	    result->x1 = ((double) x1) / width;
    	    result->y1 = 1.0 - ((double) y1) / height;
    	    result->x2 = ((double) x2) / width;
    	    result->y2 = 1.0 - ((double) y2) / height;
    
    	    result->next = display_list;
    	    display_list = (char *) result;
    
    	    return(result);
        }
    
  • this procedure creates a new line structure, the end points of the line are given in integer pixel coordinates, this will make entering rubberband lines easier
  • this procedure malloc’s the structure for the line, sets its draw procedure, initializes its end points and then links the structure into the display list
  • to handle displaying the objects all we need to do now is set up the event handler and the events that it should respond to
  • for this application we use a core widget as our drawing widget, and the widget ID of this widget is stored in the variable drawing
  • the event handler redisplay_event is used for the redisplay events, to add this event handler to the drawing widget we can use the following statement:
        XtAddEventHandler(drawing, ExposureMask |
    	 StructureNotifyMask, FALSE, redisplay_event, NULL);
    
  • the redisplay_event event handler has the following form:
        void redisplay_event(w, client, ev)
        Widget w;
        XtPointer client;
        XExposeEvent *ev; {
    
    	    if(ev->type == ConfigureNotify)
    		    redisplay(w);
    
    	    if(ev->type == Expose && ev->count == 0)
    		    redisplay(w);
    
        }
    
  • now that we can display objects, we need to write the code that will handle rubberband lines and rubber rectangles
  • to see how we can do this we first need to look at the sequence of events that will occur when the user is entering one of these objects
  • in the case of a rubberband line, the user will first press one of the mouse buttons, we will use button1 in this program, this indicates the start of entering the line and fixes one of the end points of the line
  • the user then drags the mouse around the screen with the button pressed, as the user moves the mouse, the old line is erased from the screen and a line to the new mouse position is drawn
  • when the user releases button1 the second end point of the line is fixed and we are finished entering the line
  • we can summarize these steps in the following way:
        Button1Press      Create a new line, mouse position
    		      is the first end point
    
        ButtonMotion      Erase old line, update line's end
    		      point, draw new line
    
        Button1Release    Fix the second end point
    
  • the same set of steps can be used to enter a rubber rectangle, except that we will press button 2 to start entering the rectangle
  • this indicates that the ButtonMotion and ButtonRelease event handlers will be independent of the type of object the user is entering
  • this calls for more object oriented programming, each time that the mouse is moved we need to update one of the points that defines the object’s shape, the second end point in the case of a line, and the opposite corner in the case of a rectangle
  • we add another procedure pointer to our structures, called update, to handle this
  • each time that the mouse moves the update procedure is called with the new mouse position, this is then added to the structure for the object
  • again this allows us to write code that is independent of the types of objects in our editor
  • in the case of the line the update procedure is:
        void update_line(w,line,x,y)
        Widget w;
        Line line;
        int x, y; {
    	    double x2, y2;
    
    	    x2 = x;
    	    y2 = y;
    	    line->x2 = x2/width;
    	    line->y2 = 1.0 - y2/height;
    
        }
    
  • in order to handle the rubberband line or rubber rectangle we will need some client data that we will pass between the event handlers
  • this client data contains a pointer to the object that we are entering, and the graphics contexts that are used to draw it, the declaration of this structure is:
        struct drag_struct {
    	    GC      gc;
    	    GC      xor_gc;
    	    char    *object;
        } drag_client;
    
  • the press_event event handler is called when the user presses a button, this event handler determines the type of object that the user is entering, the code for this procedure is:
        void press_event(w, drag, ev)
        Widget w;
        struct drag_struct *drag;
        XButtonEvent *ev; {
    
    	    if(ev->button == Button1) {
    		    drag->object = (char *) make_line(ev->x, ev->y,
    					    ev->x, ev->y);
    	    }
    
    	    if(ev->button == Button2) {
    		    drag->object = (char *) make_rectangle(ev->x,
    			     ev->y, ev->x, ev->y);
    	    }
        }
    
  • as the user moves the mouse the motion_event event handler is called, the code for this event handler is:
        oid motion_event(w, drag, ev)
        Widget w;
        struct drag_struct *drag;
        XMotionEvent *ev; {
    	    func draw;
    	    Line obj;
    
    	    obj = (Line) drag->object;
    	    draw = obj->draw;
    	    draw(w, drag->xor_gc, obj);
    	    (*obj->update)(w, obj, ev->x, ev->y);
    	    draw(w, drag->xor_gc, obj);
    
        }
    
  • this event handler draws the old object using the xor function to erase it from the screen
  • it calls the update procedure to specify the new mouse position and update the object’s data structure
  • it then call the draw function again to draw the object for the new mouse position
  • this event handler is independent of the type of object being entered
  • finally the release_event event handler is called when the user releases the mouse button, the code for this event handler is:
        void release-event(w, drag, ev)
        Widget w;
        struct drag_struct *drag;
        XButtonEvent *ev; {
    	    Line obj;
    
    	    obj = (Line) drag->object;
    	    (*obj->draw)(w, drag->gc, obj);
    
        }
    
  • all this event handler does is redraw the object using the copy function, so the object doesn’t have any holes in it
  • now that we have seen the pieces of the program we can look at the complete editor program
  • it consists of three files, an include file that contains the declarations of the data structures, the object.c file that contains the routines that maintain the data structures for the objects, and editor.c that contains the event handlers and the code that creates the widgets
      /**************************************************
       *
       *                   list.h
       *
       *  Include file for simple drawing program example
       *
       ****************************************************/
    
      typedef void (*func)();
    
      struct line_struct {
           int       type;
           func      draw;
           func      update;
           char      *next;
           double    x1, y1;
           double    x2, y2;
      };
    
      typedef struct line_struct *Line;
    
      struct rect_struct {
           int       type;
           func      draw;
           func      update;
           char      *next;
           double    x1, y1;
           double    x2, y2;
      };
    
      typedef struct rect_struct *Rectangle;
    
      #define LINE        1
      #define RECTANGLE   2
    
      Line make_line();
      Rectangle make_rectangle();
    
    
      /*****************************************************
       *
       *                  object.c
       *
       *  Graphics routines for the simple editing program
       *
       *****************************************************/
    
      #include <X11/StringDefs.h>
      #include <X11/Intrinsic.h>
      #include "list.h"
    
      static short width;
      static short height;
    
      static char *display_list = NULL;
    
    
      redisplay(w)
      Widget w; {
           char *list;
           Line node;
           func p;
           int n;
           Arg wargs[5];
           GC gc;
    
           XClearWindow(XtDisplay(w), XtWindow(w));
    
           n=0;
           XtSetArg(wargs[n], XtNheight, &height;); n++;
           XtSetArg(wargs[n], XtNwidth, &width;); n++;
           XtGetValues(w, wargs, n);
    
           gc = XCreateGC(XtDisplay(w), XtWindow(w), NULL, NULL);
           XSetForeground(XtDisplay(w), gc, 1);
           XSetBackground(XtDisplay(w), gc, 0);
    
           list = display_list;
           while(list != NULL) {
    	    node = (Line) list;
    	    p = node->draw;
    	    (*p)(w, gc, node);
    	    list = node->next;
           }
    
      }
    
    
      void display_line(w,gc,line)
      Widget w;
      GC gc;
      Line line; {
           int x1, y1, x2, y2;
    
           x1 = width * line->x1;
           y1 = height * (1.0 - line->y1);
           x2 = width * line->x2;
           y2 = height * (1.0 - line->y2);
    
           XDrawLine(XtDisplay(w), XtWindow(w), gc, x1, y1, x2, y2);
    
      }
    
      void update_line(w,line,x,y)
      Widget w;
      Line line;
      int x, y; {
           double x2, y2;
    
           x2 = x;
           y2 = y;
           line->x2 = x2/width;
           line->y2 = 1.0 - y2/height;
    
      }
    
    
    
      Line make_line(x1, y1, x2, y2)
      int x1, y1, x2, y2; {
           Line result;
    
           result = (Line) malloc(sizeof *result);
           result->type = LINE;
           result->draw = display_line;
           result->update = update_line;
           result->x1 = ((double) x1) / width;
           result->y1 = 1.0 - ((double) y1) / height;
           result->x2 = ((double) x2) / width;
           result->y2 = 1.0 - ((double) y2) / height;
    
           result->next = display_list;
           display_list = (char *) result;
    
           return(result);
      }
    
    
      void display_rectangle(w,gc,rect)
      Widget w;
      GC gc;
      Rectangle rect; {
           int x1, y1, x2, y2;
           int Width, Height;
    
           x1 = width * rect->x1;
           y1 = height * (1.0 - rect->y1);
           x2 = width * rect->x2;
           y2 = height * (1.0 - rect->y2);
           Width = x2 - x1;
           Height = y2 - y1;
           if(Width < 0) {
    	    Width = -Width;
    	    x1 = x2;
           }
           if(Height < 0) {
    	    Height = -Height;
    	    y1 = y2;
           }
    
           XDrawRectangle(XtDisplay(w), XtWindow(w), gc, x1, y1,
    	    Width, Height);
    
      }
    
    
      void update_rectangle(w, rect, x, y)
      Widget w;
      Rectangle rect;
      int x, y; {
           double x2, y2;
    
           x2 = x;
           y2 = y;
           rect->x2 = x2/width;
           rect->y2 = 1.0 - y2/height;
    
      }
    
    
      Rectangle make_rectangle(x1, y1, x2, y2)
      int x1, y1, x2, y2; {
           Rectangle result;
    
           result = (Rectangle) malloc(sizeof *result);
           result->type = RECTANGLE;
           result->draw = display_rectangle;
           result->update = update_rectangle;
           result->x1 = ((double) x1) / width;
           result->y1 = 1.0 - ((double) y1) / height;
           result->x2 = ((double) x2) / width;
           result->y2 = 1.0 - ((double) y2) / height;
    
           result->next = display_list;
           display_list = (char *) result;
    
           return(result);
      }
    
      /********************************************************
       *
       *                 editor.c
       *
       *  Simple editing program that shows how events are
       *  used in X.
       *
       *******************************************************/
    
      #include <X11/StringDefs.h>
      #include <X11/Intrinsic.h>
      #include <X11/Core.h>
      #include <X11/Xaw/Form.h>
      #include "list.h"
      #include "../lib/lib.h"
    
      void redisplay_event(w, client, ev)
      Widget w;
      XtPointer client;
      XExposeEvent *ev; {
    
           if(ev->type == ConfigureNotify)
    	    redisplay(w);
    
           if(ev->type == Expose && ev->count == 0)
    	    redisplay(w);
    
      }
    
    
      struct drag_struct {
           GC   gc;
           GC   xor_gc;
           char *object;
      } drag_client;
    
    
      void press_event(w, drag, ev)
      Widget w;
      struct drag_struct *drag;
      XButtonEvent *ev; {
    
           if(ev->button == Button1) {
    	    drag->object = (char *) make_line(ev->x, ev->y,
    			   ev->x, ev->y);
           }
    
           if(ev->button == Button2) {
    	    drag->object = (char *) make_rectangle(ev->x, ev->y,
    			   ev->x, ev->y);
           }
      }
    
    
      void release_event(w, drag, ev)
      Widget w;
      struct drag_struct *drag;
      XButtonEvent *ev; {
           Line obj;
    
           obj = (Line) drag->object;
           (*obj->draw)(w, drag->gc, obj);
    
      }
    
    
      void motion_event(w, drag, ev)
      Widget w;
      struct drag_struct *drag;
      XMotionEvent *ev; {
           func draw;
           Line obj;
    
           obj = (Line) drag->object;
           draw = obj->draw;
           draw(w, drag->xor_gc, obj);
           (*obj->update)(w, obj, ev->x, ev->y);
           draw(w, drag->xor_gc, obj);
    
      }
    
    
      main(argc,argv)
      int argc;
      char **argv; {
           Widget toplevel;
           Widget form;
           Widget drawing;
           Widget quit;
           int n;
           Arg wargs[10];
    
           toplevel = XtInitialize(argv[0], "editor", NULL, 0,
    		      &argc;, argv);
    
           form = XtCreateManagedWidget("form", formWidgetClass,
    		 toplevel, NULL, 0);
    
           quit = quit_button(form);
    
           drawing = XtCreateManagedWidget("drawing", coreWidgetClass,
    		 form, NULL, 0);
    
           n = 0;
           XtSetArg(wargs[n], XtNheight, 300); n++;
           XtSetArg(wargs[n], XtNwidth, 300); n++;
           XtSetArg(wargs[n], XtNfromVert, quit); n++;
           XtSetValues(drawing, wargs, n);
    
    
           XtAddEventHandler(drawing, ExposureMask | StructureNotifyMask,
    	     FALSE, redisplay_event, NULL);
    
           XtAddEventHandler(drawing, ButtonPressMask, FALSE,
    		 press event, &drag;_client);
    
           XtAddEventHandler(drawing, ButtonReleaseMask, FALSE,
    		 release_event, &drag;_client);
    
           XtAddEventHandler(drawing, ButtonMotionMask, FALSE,
    		 motion_event, &drag;_client);
    
           XtRealizeWidget(toplevel);
    
           /*
    	*  we can't create the graphics contexts until
    	*  after the widgets have been realized
    	*/
    
           drag_client.gc = XCreateGC(XtDisplay(drawing),
    		      XtWindow(drawing),NULL,NULL);
           XSetForeground(XtDisplay(drawing), drag_client.gc, 1);
           XSetBackground(XtDisplay(drawing), drag_client.gc, 0);
    
           drag_client.xor_gc = XCreateGC(XtDisplay(drawing),
    		      XtWindow(drawing),NULL,NULL);
           XSetForeground(XtDisplay(drawing), drag_client.xor_gc, 1);
           XSetBackground(XtDisplay(drawing), drag_client.xor_gc, 0);
           XSetFunction(XtDisplay(drawing), drag_client.xor_gc, GXxor);
    
           XtMainLoop();
    
      }