Subsections

Xlib and Motif

  

This Chapter will deal specifically with the introduction of Xlib - the low level X library. Recall that Xlib provides the means of communication between the application and the X system. The Xlib library of (C) subroutines is large and of a comparable size to Motif. Many of Xlib's routines deal with the creation, maintenance and interaction between windows and applications. Xlib does not have any concept of widgets and thus does not provide provide any (high level) means of interaction. In general, writing complete application solely in Xlib is not a good idea. Motif provides many useful, complete GUI building blocks that should always be used if available. For example, do you really want to write a complete text editing library in Xlib, when Motif provides one for free?

If you use Motif then there should never be any need to resort to Xlib for window creation. Motif is far more powerful and flexible. Consequently, in this and forthcoming Chapters, we will only deal with issues that affect the interfacing of Xlib with Motif and the Xt toolkit.

However, for certain tasks we will have to resort to Xlib. The sort of tasks that we will be concerned with in this text are:

Graphics
   -- The responsibility of actually drawing items to a window is the domain of Xlib. Whilst Motif does provide a canvas where graphics can be drawn, Motif has to rely on Xlib functions to actually draw something. Xlib only facilitates simple 2D graphics.
Pixmaps
   -- A Pixmap is an off-screen drawable area where graphics may be placed. Pixmaps are clearly related to usage with Xlib graphics. However, Pixmaps are also used with Images and also to label graphics based widgets e.g. DrawnButton and Toggle Widgets.
Colour
   -- Handling colour is a fundamental element for a windowing system. Colour plays an important part in any GUI for providing a user friendly GUI layout, indicating key features and alerting the user to certain actions. However, in the X system colour can be quite complex as a variety of devices are required to be supported by the X network and each device may have a completely different interpretation of colour. Because of this requirement, colour needs to be handled at the Xlib level. Chapter 17 deals with the major issues of colour and X.
Text
   -- Just like colour handling of Text can vary across devices. The font, typeface (e.g. italic, bold) and size may need to be controlled by Xlib.
Events
   -- Whilst Motif handles events in a robust and efficient manner, the Motif method of event handling is sometimes a little restrictive. In this case responsibility for event handling is sometimes handed over to Xlib.

Xlib Basics

    In Chapter 2 we described the X Window system and explained the relevance of each system component. We briefly mentioned that Xlib provides the interface between the X Protocol and the application program. Xlib therefore has to deal with many low level tasks. In short, Xlib concerns itself with:

Xlib deals with much lower level objects than widgets. When you write or draw in Xlib reference, may be made to the following:

Display
   -- This structure is used by nearly all Xlib functions. It contains details about the server and its screens.
Drawable
   -- this is an identifier to a structure that can contain graphics (i.e. it can be drawn to). There are two common types:
Window
   -- A part of the screen.
Pixmap
   -- An off-screen store of graphical data.
Screen
   -- Information about a single screen of a display.
Graphics Context (GC)
   -- When we draw we need to specify things like line width, line style (e.g solid or dashed), colour, shading or filling patterns etc.. In Xlib we pass such information to a drawing routine via a GC structure. This must be created (and values set) before the routine uses the GC. More than one GC can be used.
Depth
   -- the number of bits per pixel used to display data.
Visual
   -- This structure determines how a display handles screen output such as colour and depends on the number of bits per pixel.

If we are programming in Xlib alone, we would have to create windows and open displays ourselves. However, if we are using a higher level toolkit such a Motif and require to call on Xlib routines then we need to obtain the above information from an appropriate widget in order pass on appropriate parameter values in the Xlib function calls.

Functions are available to obtain this information readily from a Widget. For example XtDisplay(), XtWindow(), XtScreen()    etc. can be used to obtain the ID of a given Xlib Display, Window, or Screen structure respectively from a given widget. Default Values of these structures are also typically used. Functions DefaultDepthofScreen(), RootWindowofScreen()   are examples.

Sometimes, in a Motif program, you may have to create an Xlib structure from scratch. The GC is the most frequently created structure that concerns us. The Function XCreateGC() creates a new GC data structure.

We will look at the mechanics of assembling Xlib graphics within a Motif program when we study DrawingAreas in Chapter 16. For the remainder of this Chapter we will continue to introduce basic Xlib concepts. In the coming Sections, reference is made to programs that are described in Chapter 16.

Graphics Contexts

  

As mentioned in the previous Section, the GC is responsible for setting the properties of lines and basic (2D) shapes. GCs are therefore used with every Xlib drawing function. The draw.c  (Section 16.3.1) program illustrates the setting of a variety of GC elements.

To create a GC use the (Xlib) function XCreateGC() . It has 4 parameters:

The XGCValues  structure contains elements like foreground, background, line_width, line_style, etc. that we can set for obvious results. The mask has predefined values such as GCForeground, GCBackground, and GCLineStyle.

In draw.c we create a GC structure, gc, and set the foreground.

Xlib provides two macros BlackPixel()  and WhitePixel()  which will find the default black and white pixel values for a given Display and Screen if the default colourmaps are installed. Note that the reference to BlackPixel() and WhitePixel() can be a little confusing since the pixel colours returned may not necessarily be Black or White. BlackPixel() actually refers to the foreground colour and WhitePixel() refers to the background colour.

Therefore, to create a GC that only sets foreground colour to the default for a given display and screen:

 gcv.foreground = BlackPixel(display, screen);
 gc = XCreateGC(display, screen, GCForeground, &gcv);

where gcv is a XCGValues structure and gc a GC structure and GCForeground sets the mask to only allow alteration of the foreground.

To set both background and foreground:

 gcv.foreground = BlackPixel(display, screen);
 gcv.background = WhitePixel(display, screen);
 gc = XCreateGC(display, screen, 
                GCForeground | GCBackground, &gcv);

where we use the | (OR) in the mask parameter that allows both the values to be set in the XGCValues structure.

An alternative way to change GC elements is to use Xlib convenience functions to set appropriate GC values. Example functions include :








XSetForeground(), XSetBackground(), XSetLineAttributes()   .

These set GC values for a given display and gc, for example:








XSetBackground(display, gc, WhitePixel(display, screen));








Further examples of their use are shown in the draw.c program (Section 16.3.1).

Two Dimensional Graphics

     Xlib provides a whole range of 2D Graphics functions. See draw.c for examples in use. Most of these functions are fairly easy to understand and use. The functions basically draw a specific graphical primitive (point, line, polygon, arc, circle etc.) to a display according to a specific GC.

The simplest function is XDrawPoint(Display *d, Drawable dr, GC gc, int x, int y)  which draws a point at position (x, y) to a given Drawable on a Display. This effectively colours a single pixel on the Display.

The function XDrawPoints(Display *d, Drawable dr, GC gc, XPoint *pts, int n, int mode)  is similar except that an n element array of XPoints is drawn. The mode may be defined as being either CoordModeOrigin or CoordModePrevious. The former mode draws all points relative to the origin whilst the latter mode draws relative to the last point.

Other Xlib common drawing functions include:

XDrawLine(Display *d, Drawable dr, GC gc, int x1, int y1, int x2, int y2)  draws a line between (x1, y1) and (x2, y2).
XDrawLines(Display *d, Drawable dr, GC gc, XPoint *pts, int n, int mode)  draws a series of connected lines -- taking pairs of coordinates in the list. The mode is defined as for the XDrawPoints() function.

XDrawRectangle(Display *d, Drawable dr, GC gc, int x, int y, int width, height)  draws a rectangle with top left hand corner coordinate (x, y) and of width and height.

XFillRectangle(Display *d, Drawable dr, GC gc, int x, int y, int width, int height)  fills (shades interior) a rectangle. The fill_style controls what shading takes place.

XFillPolygon(Display *d, Drawable dr, GC gc, XPoint *pts, int n, int mode, int mode)  fills a polygon whose outline is defined by the *pts array.

This function behaves much like XDrawLines().

The shape parameter is either Complex, Nonconvex or Convex and controls how the server may configure the shading operation.

XDrawArc(Display *d, Drawable dr, GC gc, int x, int y, int width, int height, int angle1, int angle2)  draws an arc.

XFillArc(Display *d, Drawable dr, GC gc, int x, int y, int width, int height, int angle1, int angle2)  draws an arc and fills it.

The x, y, width and height define a bounding box for the arc. The arc is drawn(Fig. 15.1) from the centre of the box. The angle1 and angle2 define the start and end points of the arc. The angles specify 1/64th degree steps measured anticlockwise. The angle1 is relative to the 3 o'clock position and angle2 is relative to angle1.


  
Figure 15.1: Drawing an Arc

Thus to draw a whole circle set the width and height equal to the diameter of the circle and angle1 = 0, angle2 = 360*64 = 23040.

Pixmaps

   

If a window has been obscured then we will have to redraw the window when it gets re-exposed. This is the responsibility of the application and not the X window manager or system. In order to redraw a window we may have to go through all the drawing function calls that have previously been used to render our window's display. However, re-rendering a display in this manner will be cumbersome and may involve some complicated storage methods -- there maybe be many drawing functions involved and the order in which items are drawn may also be important

Fortunately, X provides a mechanism that overcomes these (and other less serious) problems. The use of Xlib Pixmaps is the best approach.

A Pixmap is an off-screen Drawable area.

We can draw to a Pixmap in the same way as we can draw to a Window. We use the standard Xlib graphics drawing functions (Section 15.3) but instead of specifying a Window ID as the Drawable we provide a Pixmap ID. Note, however, that no immediate visual display effect will occur when drawing to a Pixmap. In order to see any effect we must copy the Pixmap to a Window.

The program draw_input2.c (Section 16.3.3) draws to pixmaps instead of to the window.

To create a Pixmap the XCreatePixmap()  function should be used. This function takes 5 arguments and returns a Pixmap structure:

Display*
-- A pointer to the Display associated with Pixmap.
Drawable
-- The screen on which to place Pixmap.
Width, Height
-- The dimensions of the Pixmap.
Depth
-- The number of bits of each pixel. Usually 8 by default. A Pixmap of depth 1 is usually called a bitmap  and are used for icons and special bitmap files.

When you have finished using a Pixmap it is a good idea to free the memory in which it has been stored by calling:



XFreePixmap(Display*, Pixmap) .



If you want to clear a Pixmap (not done automatically) use XFillRectangle() to draw a background coloured rectangle that is the dimension of the whole Pixmap or use XClearArea(), XClearWindow() or similar functions.

To copy a Pixmap onto another Pixmap or a Window use:

 XCopyArea(Display *display, Drawable source, 
      Drawable destination, GC gc, 
      int src_x, src_y, 
      int width, int height, 
      int dest_x, int dest_y);

where (src_x, src_y) specify the coordinates in the source pixmap where copy starts, width and height specify the dimensions of the copied area and (dest_x, dest_y) are the start coordinates in the destination where pixels are placed.

Fonts

  

Fonts are necessary in Motif as all XmString are drawn to the screen using fonts residing in the X system. A font is a complete set of characters (upper-case and lower-case letters, punctuation marks and numerals) of one size and one typeface. In order for a Motif program to gain access to different typefaces, fonts must be loaded onto the X server. All X fonts are bitmapped.

Not all X servers support all fonts. Therefore it is best to check if a specific font has been loaded correctly within your Motif program. There is a standard X application program, xlsfonts, that lists the fonts available on a particular workstation.

Each font or character set name is referred to by a String name. Fonts are loaded into to an Xlib Font  structure using the XLoadFont()   function with a given Display ID and font name argument. The function returns a Font structure.

Another similar function is XLoadQueryFont()   which takes the same arguments as above but returns an XFontStruct   which contains the Font structure plus information describing the font.

An example function, load_font() which loads a font named ``fixed'', which should be available on most systems but is still checked for is given below:

void load_font(XFontStruct **font_info)

{  Display *display;
   char *fontname = "fixed";
   XFontStruct *font_info;

   display = XtDisplay(some_widget);

   /* load and get font info structure  */

  if (( *font_info = XLoadQueryFont(display, fontname)) == NULL)
      { /* error - quit early */
         printf("Cannot load  %s font\n",  fontname);          
         exit(1);
      }
}

Motif  actually possesses its own font loading and setting functions. These include XmFontListCreate(), XmFontListEntryCreate(), XmFontListEntryLoad()     and XmFontListAdd() . These are used in a similar fashion to the Xlib functions above except that they return an XmFontList   structure. However, in this book, we will be only using fonts at the Xlib level and these Motif functions will not be considered further.

XEvents

    We should now be familiar with the basic notion of events in X and Motif. Mouse button presses, mouse motion and keyboard presses can be used to action menu, buttons etc.. These are all instances of events. Usually we are happy to let Motif take care of event scheduling with the XtAppMainLoop() function, and the setting of appropriate callback resources for widgets (Chapter 4).

Sometimes we may need to gain more control of events in X. To do this we will need to resort to Xlib. A specific example of this will be met in the Chapter 16 (the draw_input1.c program), where the default interaction, provided via callbacks in Motif, is inadequate for our required form of interaction.

XEvent Types

There are many types of events in Xlib. A special XEvent  structure is defined to take care of this.

XEvents exist for all kinds of events, including: mouse button presses, mouse motions, key presses and events concerned with the window management. Most of the mouse/keyboard events are self explanatory and we have already studied them a little. Let us look at some window events further:

XConfigureNotify
  -- If a window changes size then this event is generated.
XCirculateNotify
  -- The event generated if the stacking order of windows has changed.
XColormapNotify
  -- The event generated if colormap changes are made.
XCreateNotify, XDestroyNotify
   -- The events generated when a window is created or deleted respectively.
XExpose, XNoExpose
   -- Windows can be stacked, moved around etc. If part of a window that has been previously obscured becomes visible again then it will need to be redrawn. An XExpose event is sent for this purpose. An XExpose event is also sent when a window first becomes visible.

Note: There is no guarantee that what has previously been drawn to the window will become immediately visible. In fact, it is totally up to the programmer to make sure that this happens by picking up an XExpose event (See Sections 15.4 and 16.3.3 on Pixmaps and DrawingAreas).

Writing Your Own Event Handler

Most Motif applications will not need to do this since they can happily run within the standard application main loop event handling model. If you do need to resort to creating your own (Xlib) event handling routines, be warned: it can quickly become complex, involving a lot of Xlib programming.

Since, for the level of Motif programming described in this text, we will not need to resort to writing elaborate event handlers ourselves we will only study the basics of Motif/Xlib event handling and interaction.

The first step along this path is attaching a callback to an XEvent rather than a Widget callback action. From Motif (or Xt) you attach a callback to a particular event with the function XtAddEventHandler(), which takes 5 parameters:

Widget
-- the ID of the widget concerned.
EventMask
-- This can be used to allow the widget to be receptive to specific events. A complete list of event masks is given in the online support reference material. Multiple events can be assigned by ORing (|) masks together.
Nonmaskable
-- A Boolean almost always set to False. If it is set to True then it can be activated on nomaskable events such as ClientMessage, Graphics Expose, Mapping Notify, NoExpose, SelectionClear,
SelectionNotify or SelectionRequest
.
Callback
-- the callback function.
Client Data
-- Additional data to be passed to the event handler.

As an example we could set an expose_callbck() to be called by an Expose event by the following function call:

XtAddEventHandler(widget, ExposureMask, False, 
                  expose_callbck, NULL);

To set a callback, motion_callbk(), that responds to left or middle mouse motion -- an event triggered when the mouse is moved whilst an appropriate mouse butten is depresses -- we would write:

XtAddEventHandler(widget, Button1MotionMask | Button2MotionMask, 
                  False, motion_callbk, NULL);

There are two other steps that need to be done when writing our own event handler. These are:

These two steps are basically what the XtAppMainLoop()  takes care of in normal operation.



Two Xt functions are typically used in this context:

XtAppNextEvent(XtAppContext, XEvent*)
  gets the next event off the event queue for a given XtAppContext application.
XtDispatchEvent(XEvent*)
  dispatches the event so that callbacks can be invoked.

In between the retrieving of the next event and dispatching this event you may want to write some code that intercepts certain events.

Let us look at how the XtAppMainLoop() function is coded. Note the comments show where we may place custom application intercept code.

 
void XtAppMainLoop(XtAppContext app)

{ XEvent event;

  for (;;) /* forever */
    { XtAppNextEvent(app, &event);

      /* Xevent read off queue */
      /* inspect structure and intercept perhaps? */

      /* intercept code would go here */

      XtDispatchEvent(&event);
    } 
}

Exercises

PLENTY OF SCOPE HERE



Dave Marshall
1/5/1999