LUPG home Tutorials Related material Project Ideas Essays Send comments
[LUPG Home] [Tutorials] [Related Material] [Essays] [Project Ideas] [Send Comments]

Basic Graphics Programming With The Xlib Library - Part II


Windows Hierarchy

When windows are displayed on an X server, they are always ordered in some hierarchy - every window may have child windows, each child window may have its own child windows, etc. Lets see a few properties of this hierarchy, and how they effect the operations like drawing or events propagation.


Root, Parent And Child Windows

On each screen, there is a root window. The root window always spans the entire screen size. This window cannot be destroyed, resized or iconified. When an application creates windows, it first has to create at least one top-level window. This window becomes a direct descendant of the root window, until it is first mapped on the screen. Before this window is mapped, the window manager is notified of the operation about to take place. The window manager then has the privilege of re-parenting the new top-level window. This is used to add a window that will contain the new window, and will be used to draw its frame, title bar, system menu, etc.

Once such a top-level window (which is actually not a top-level window after the re-parenting operation takes place) is created, the application can create child windows inside this window. A child can only be displayed inside its parent window - If it is moved outside, its being clipped by the border of its parent window. Any window may contain more than one child window, and if this is the case, these windows are being ordered in an internal stack. When a top-level window is being raised - all its descendant windows are being raised with it, while retaining their internal ordering. If a child window is raised - it is being raised only amongst its sibling windows.

Lets see how to create a child window inside a given window 'win'.


/* this variable will store the handle of the newly created child window. */
Window child_win;

/* these variables will store the window's width and height. */
int child_win_width = 50;
int child_win_height = 30;

/* these variables will store the window's location.         */
/* position of the child window is top-left corner of the    */
/* parent window. - 0,0.                                     */
int child_win_x = 0;
int child_win_y = 0;

/* create the window, as specified earlier. */
child_win = XCreateSimpleWindow(display,
                                win,
                                child_win_x, child_win_y,
                                child_win_width, child_win_height,
                                child_win_border_width,
                                BlackPixel(display, screen_num),
                                WhitePixel(display, screen_num));


Events Propagation

We have discussed events propagation earlier - If a window is being sent an event and it does not process the event - the event is being passed to the this window's parent window. If that parent window does not handle this event - it is being passed to the parent's parent window, and so on. This behavior does not make a lot of sense for a simple Xlib application, but it does make sense when higher-level graphic libraries are used. They usually associate functions with events occurring in specific windows. In such a case, it is useful to pass the event to the relevant window, with which a proper function is associated.


Interacting With The Window Manager

After we have seen how to create windows and draw on them, we take one step back, and look at how our windows are interacting with their environment - the full screen, and the other windows. First of all, our application needs to interact with the window manager. The window manager is responsible to decorating drawn windows (i.e. adding a frame, an iconify button, a system menu, a title bar), as well as to handling icons shown when windows are being iconified. It also handles ordering of windows on the screen, and other administrative tasks. We need to give it various hints as to how we want it to treat our application's windows.


Window Properties

Many of the parameters communicated to the window manager are passed using data called "properties". These properties are attached by the X server to different windows, and are stored in a format that makes it possible to read them from different machines, that may use different architectures (remember that an X client program may run on a remote machine). Properties may be of various types - numbers, strings, etc. Most window manager hints functions use text properties. A function named XStringListToTextProperty() may be used to turn a normal C string into an X text property, that can later be passed to various Xlib functions. Here is how to use it:



/* This variable will store the newly created property. */
XTextProperty window_title_property;

/* This is the string to be translated into a property. */
char* window_title = "hello, world";

/* translate the given string into an X property. */
int rc = XStringListToTextProperty(&window_title,
                                   1,
                                   &window_title_property);
/* check the success of the translation. */
if (rc == 0) {
    fprintf(stderr, "XStringListToTextProperty - out of memory\n");
    exit(1);
}

the XStringListToTextProperty() function accepts an array of C strings, a count of the number of strings in the array (1 in our case), and a pointer to an XTextProperty variable. It concatenates the list of strings and places it in the XTextProperty variable. It returns a non-zero value on success, or 0 on failure (e.g. not enough memory to perform the operation).


Setting The Window Name And Icon Name

The first thing would be to set the name for our window. This is done using the XSetWMName() function. This name may be used by the window manager as the title of the window (in the title bar), in a task list, etc. This function accepts 3 parameters: a pointer to the display, a window handle, and an XTextProperty containing the desired title. Here is how it is used:


/* assume that window_title_property is our XTextProperty, and is */
/* defined to contain the desired window title.                   */
XSetWMName(display, win, &window_title_property);
in order to set the name of the iconized version of our window, we will use the XSetWMIconName() function in a similar way:

/* this time we assume that icon_name_property is an initialized */
/* XTextProperty variable containing the desired icon name.      */
XSetWMIconName(display, win, &icon_name_property);


Setting Preferred Window Size(s)

In various cases, we wish to let the window manager know that we want to have a given size for our window, and to only let the user resize our window in given quantities. For example, in a terminal application (like xterm), we want our window to always contain complete rows and columns, so the text we display won't be cut off in the middle. In other cases we do not want our window to be resized at all (like in many dialog boxes), etc. We can relay this information to the window manager, although it may simply ignore our request. We need to first create a data structure to hold the information, fill it with proper data, and use the XSetWMNormalHints() function. Here is how this may be done:



/* pointer to the size hints structure. */
XSizeHints* win_size_hints = XAllocSizeHints();
if (!win_size_hints) {
    fprintf(stderr, "XAllocSizeHints - out of memory\n");
    exit(1);
}

/* initialize the structure appropriately. */
/* first, specify which size hints we want to fill in. */
/* in our case - setting the minimal size as well as the initial size. */
win_size_hints->flags = PSize | PMinSize;
/* next, specify the desired limits.                             */
/* in our case - make the window's size at least 300x200 pixels. */
/* and make its initial size 400x250.                            */
win_size_hints->min_width = 300;
win_size_hints->min_height = 200;
win_size_hints->base_width = 400;
win_size_hints->base_height = 250;

/* pass the size hints to the window manager. */
XSetWMNormalHints(display, win, win_size_hints);

/* finally, we can free the size hints structure. */
XFree(win_size_hints);

For full info about the other size hints we may supply, see your manual pages.


Setting Miscellaneous Window Manager Hints

There are some other window manager hints that may be set using the XSetWMHints() function. This function uses a XWMHints structure to pass the data to the window manager. Here is how it may be used:



/* pointer to the WM hints structure. */
XWMHints* win_hints = XAllocWMHints();
if (!win_hints) {
    fprintf(stderr, "XAllocWMHints - out of memory\n");
    exit(1);
}

/* initialize the structure appropriately. */
/* first, specify which hints we want to fill in. */
/* in our case - setting the state hint as well as the icon position hint. */
win_hints->flags = StateHint | IconPositionHint;
/* next, specify the desired hints data.                         */
/* in our case - make the window's initial state be iconized,    */
/* and set the icon position to the top-left part of the screen. */
win_hints->initial_state = IconicState;
win_hints->icon_x = 0;
win_hints->icon_y = 0;

/* pass the hints to the window manager. */
XSetWMHints(display, win, win_hints);

/* finally, we can free the WM hints structure. */
XFree(win_hints);

Other options settable using this function are specified in the manual page.


Setting An Application's Icon

In order to set an icon to be used by the window manager, when a user iconifies our application, we use the XSetWMHints() function mentioned above. However, we first need to create a pixmap that contains the icon's data. Pixmaps are the way that X servers manipulate images, and will be explained fully later in this tutorial. For now, we'll just show you how to set the icon for your application. We assume you got a bitmap file representing this icon, stored using an X bitmap format. An example of such a file is the "icon.bmp" file that comes with this tutorial. Anyway, here is the code:



/* include the definition of the bitmap in our program. */
#include "icon.bmp";

/* pointer to the WM hints structure. */
XWMHints* win_hints;

/* load the given bitmap data and create an X pixmap containing it. */
Pixmap icon_pixmap = XCreateBitmapFromData(display,
                                           win,
                                           icon_bitmap_bits,
                                           icon_bitmap_width,
                                           icon_bitmap_height);
if (!icon_pixmap) {
    fprintf(stderr, "XCreateBitmapFromData - error creating pixmap\n");
    exit(1);
}

/* allocate a WM hints structure. */
win_hints = XAllocWMHints();
if (!win_hints) {
    fprintf(stderr, "XAllocWMHints - out of memory\n");
    exit(1);
}

/* initialize the structure appropriately. */
/* first, specify which size hints we want to fill in. */
/* in our case - setting the icon's pixmap. */
win_hints->flags = IconPixmapHint;
/* next, specify the desired hints data.           */
/* in our case - supply the icon's desired pixmap. */
win_hints->icon_pixmap = icon_pixmap;

/* pass the hints to the window manager. */
XSetWMHints(display, win, win_hints);

/* finally, we can free the WM hints structure. */
XFree(win_hints);

you may use programs such as "xpaint" to create files using the X bitmap format.

To conclude this section, we supply a simple-wm-hints.c, that creates a window, sets the window manager hints shown above, and runs into a very simple event loop, allowing the user to play with the window and see how these hints affect the behavior of the application. Try playing with the various hints, and try to run the program under different window managers, to see the differences in its behavior under each of them. This will teach you something about X programs portability.


Simple Window Operations

One more thing we can do to our windows is manipulate them on the screen - resize them, move them, raise or lower them, iconify them and so on. A set of window operations functions are supplied by Xlib for this purpose.


Mapping And UN-mapping A Window

The first pair of operations we can apply on a window is mapping it, or un-mapping it. Mapping a window causes the window to appear on the screen, as we have seen in our simple window program example. UN-mapping it causes it to be removed from the screen (although the window as a logical entity still exists). This gives the effect of making a window hidden (unmapped) and shown again (mapped). For example, if we have a dialog box window in our program, instead of creating it every time the user asks to open it, we can create the window once, in an unmapped mode, and when the user asks to open it, we simply map the window on the screen. When the user clicked the 'OK' or 'Cancel' button, we simply UN-map the window. This is much faster than creating and destroying the window, however, the cost is wasted resources, both on the client side, and on the X server side.

You will remember That the map operation can be done using the XMapWindow() operation. The UN-mapping operation can be done using the XUnmapWindow() operation. Here is how to apply them:


/* make the window actually appear on the screen. */
XMapWindow(display, win);

/* make the window hidden. */
XUnmapWindow(display, win);
The mapping operation will cause an Expose event to be sent to our application, unless the window is completely covered by other windows.


Moving A Window Around The Screen

Another operation we might want to do with windows is to move them to a different location. This can be done using the XMoveWindow() function. It will accept the new coordinates of the window, in the same fashion that XCreateSimpleWindow() got them when the window was created. The function is invoked like this:


/* move the window to coordinates x=400 and y=100. */
XMoveWindow(display, win, 400, 100);
Note that when the window is moved, it might get partially exposed or partially hidden by other windows, and thus we might get Expose events due to this operation.


Resizing A Window

Yet another operation we can do is to change the size of a window. This is done using the XResizeWindow() function:


/* resize the window to width=200 and height=300 pixels. */
XResizeWindow(display, win, 200, 300);
We can also combine the move and resize operations using the single XMoveResizeWindow() function:

/* move the window to location x=20 y=30, and change its size */
/* to width=100 and height=150 pixels.                        */
XMoveResizeWindow(display, win, 20, 30, 100, 150);


Changing Windows Stacking Order - Raise And Lower

Until now we changed properties of a single window. We'll see that there are properties that relate to the window and other windows. One of them is the stacking order. That is, the order in which the windows are layered on top of each other. the front-most window is said to be on the top of the stack, while the back-most window is at the bottom of the stack. Here is how we manipulate our windows stacking order:


/* move the given window to the top of the stack. */
XRaiseWindow(display, win1);

/* move the given window to the bottom of the stack. */
XLowerWindow(display, win1);


Iconifying And De-Iconifying A Window

One last operation we will show here is the ability to change a window into an iconified mode and vice versa. This is done using the XIconifyWindow() function - to iconify the window, and the XMapWindow() to de-iconify it. To understand why there is no inverse function for XIconifyWindow(), we must realize that when a window is iconified, what actually happens is that the window is being unmapped, and instead its icon window is being mapped. Thus, to make the original window reappear, we simply need to map it again. The icon is indeed another window that is simply related strongly to our normal window - it is not a different state of our window. Here is how to iconify a window and then de-iconify it:


/* iconify our window. Make its icon window appear on the same */
/* screen as our window (assuming we created our window on the */
/* default screen).                                            */
XIconifyWindow(display, win, DefaultScreen(display));

/* de-iconify our window. the icon window will be automatically */
/* unmapped by this operation.                                  */
XMapWindow(display, win);


Getting Info About A Window

Just like we can set various attributes of our windows, we can also ask the X server supply the current values of these attributes. For example, we can check where a window is located on screen, what is its current size, whether it is mapped or not, etc. The XGetWindowAttributes() function may be used to get this information. Here is how it is used:



/* this variable will contain the attributes of the window. */
XWindowAttributes win_attr;

/* query the window's attributes. */
Status rc = XGetWindowAttributes(display, win, &win_attr);

The XWindowAttributes structure contains many fields - here are some of them:
int x, y;
Location of the window, relative to its parent window.
int width, height;
width and height of the window (in pixels).
int border_width
width of the window's border.
Window root;
handle of the root widow of the screen on which our window is displayed.
One problem with this function, is that it returns the location of the window relative to its parent window. This makes these coordinates rather useless for any window manipulation functions (e.g. XMoveWindow). In order to overcome this problem, we need to take a two-step operation. First, we find out the ID of the parent window of our window. We then translate the above relative coordinates to screen coordinates. We use two new functions for this calculation, namely XQueryTree() and XTranslateCoordinates(). These two functions do more than we need, so we will concentrate on the relevant information only.

/* these variables will eventually hold the translated coordinates. */
int screen_x, screen_y;
/* this variable is here simply because it's needed by the          */
/* XTranslateCoordinates function below. For its purpose, see the   */
/* manual page.                                                     */
Window child_win;

/* this variable will store the ID of the parent window of our window. */
Window parent_win;
/* this variable will store the ID of the root window of the screen    */
/* our window is mapped on.                                            */
Window root_win;
/* this variable will store an array of IDs of the child windows of    */
/* our window.                                                         */
Window* child_windows;
/* and this one will store the number of child windows our window has. */
int num_child_windows;

/* finally, make the query for the above values. */
XQueryTree(display, win,
           &root_win,
           &parent_win,
           &child_windows, &num_child_windows);

/* we need to free the list of child IDs, as it was dynamically allocated */
/* by the XQueryTree function.                                            */
XFree(child_windows);

/* next, we make the coordinates translation, from the coordinates system */
/* of the parent window, to the coordinates system of the root window,    */
/* which happens to be the same as that of the screen, since the root     */
/* window always spans the entire screen size.                            */
/* the 'x' and 'y' values are those previously returned by the            */
/* XGetWindowAttributes function.                                         */
XTranslateCoordinates(display, parent_win, root_win,
                      win_attr.x, win_attr.y, &screen_x, &screen_y,
                      &child_win);

/* at this point, screen_x and screen_y contain the location of our original */
/* window, using screen coordinates.                                         */

As you can see, Xlib sometimes makes us work hard for things that could have been much easier, if its interfaces and functions were a little more consistent.

As an example of how these operations all work, check out our window-operations.c program.


Using Colors To Paint The Rainbow

Up until now, all our painting operations were done using black and white. We will (finally) see now how to draw using colors.


Color Maps

In the beginning, there were not enough colors. Screen controllers could only support a limited number of colors simultaneously (16 initially, and then 256). Because of this, an application could not just ask to draw in a "light purple-red" color, and expect that color to be available. Each application allocated the colors it needed, and when all 16 or 256 color entries were in use, the next color allocation would fail.

Thus, the notion of "a color map" was introduced. A color map is a table whose size is the same as the number of simultaneous colors a given screen controller. Each entry contained the RGB (Red, Green and Blue) values of a different color (all colors can be drawn using some combination of red, green and blue). When an application wants to draw on the screen, it does not specify which color to use. Rather, it specifies which color entry of some color map to be used during this drawing. Change the value in this color map entry - and the drawing will use a different color.

In order to be able to draw using colors that got something to do with what the programmer intended, color map allocation functions were supplied. You could ask to allocate a color map entry for a color with a set of RGB values. If one already existed, you'd get its index in the table. If none existed, and the table was not full, a new cell would be allocated to contain the given RGB values, and its index returned. If the table was full, the procedure would fail. You could then ask to get a color map entry with a color that is closest to the one you were asking for. This would mean that the actual drawing on the screen would be done using colors similar to what you wanted, but not the same.

On today's more modern screens, where one runs an X server with support for 1 million colors, this limitation looks a little silly, but remember that there are still older computers with older graphics cards out there. Using color maps, support for these screens becomes transparent to you. On a display supporting 1 million colors, any color entry allocation request would succeed. On a display supporting a limited number of colors, some color allocation requests would return similar colors. It won't look as good, but your application would still work.


Allocating And Freeing Color Maps

When you draw using Xlib, you can choose to use the standard color map of the screen your window is displayed on, or you can allocate a new color map and apply it to a window. In the latter case, each time the mouse moves onto your window, the screen color map will be replaced by your window's color map, and you'll see all the other windows on screen change their colors into something quite bizzar. In fact, this is the effect you get with X applications that use the "-install" command-line option.

In order to access the screen's standard color map, the DefaultColormap macro is defined:


Colormap screen_colormap = DefaultColormap(display, DefaultScreen(display));
this will return a handle for the color map used by default on the first screen (again, remember that an X server may support several different screens, each of which might have its own resources).

The other option, that of allocating a new color map, works as follows:


/* first, find the default visual for our screen. */
Visual* default_visual = DefaultVisual(display, DefaultScreen(display));
/* this creates a new color map. the number of color entries in this map */
/* is determined by the number of colors supported on the given screen.  */
Colormap my_colormap = XCreateColormap(display,
                                       win,
                                       default_visual,
                                       AllocNone);
Note that the window parameter is only used to allow the X server to create the color map for the given screen. We can then use this color map for any window drawn on the same screen.


Allocating And Freeing A Color Entry

Once we got access to some color map, we can start allocating colors. This is done using the XAllocNamedColor() and XAllocColor() functions. The first XAllocNamedColor() accepts a color name (e.g. "red", "blue", "brown" and so on) and allocates the the closest color that can be actually drawn on the screen. The XAllocColor() accepts an RGB color, and allocates the closest color that can be drawn on the screen. Both functions use the XColor structure, that has the following relevant fields:

unsigned long pixel
This is the index of the color map entry that can be used to draw in this color.
unsigned short red
the red part of the RGB value of the color.
unsigned short green
the green part of the RGB value of the color.
unsigned short blue
the blue part of the RGB value of the color.

Here is an example of using these functions:



/* this structure will store the color data actually allocated for us. */
XColor system_color_1, system_color_2;
/* this structure will store the exact RGB values of the named color.  */
/* it might be different from what was actually allocated.             */
XColor exact_color;

/* allocate a "red" color map entry. */
Status rc = XAllocNamedColor(display,
                             screen_colormap,
                             "red",
                             &system_color_1,
                             &exact_color);
/* make sure the allocation succeeded. */
if (rc == 0) {
    fprintf(stderr,
            "XAllocNamedColor - allocation of 'red' color failed.\n");
}
else {
    printf("Color entry for 'red' - allocated as (%d,%d,%d) in RGB values.\n",
           system_color_1.red, system_color_1.green, system_color_1.blue);
}

/* allocate a color with values (30000, 10000, 0) in RGB. */
system_color_2.red = 30000;
system_color_2.green = 10000;
system_color_2.blue = 0;
Status rc = XAllocColor(display,
                        screen_colormap,
                        &system_color_2);
/* make sure the allocation succeeded. */
if (rc == 0) {
    fprintf(stderr,
            "XAllocColor - allocation of (30000,10000,0) color failed.\n");
}
else {
    /* do something with the allocated color... */
    .
    .
}


Drawing With A Color

After we have allocated the desired colors, we can use them when drawing text or graphics. To do that, we need to set these colors as the foreground and background colors of some GC (Graphics Context), and then use this GC to make the drawing. This is done using the XSetForeground() and XSetBackground() functions, as follows:


/* use the previously defined colors as the foreground and background  */
/* colors for drawing using the given GC. assume my_gc is a handle to */
/* a previously allocated GC.                                         */
XSetForeground(display, my_gc, screen_color_1.pixel);
XSetForeground(display, my_gc, screen_color_2.pixel);
As you see, this is rather simple. The actual drawing is done using the same functions we have seen earlier. Note that in order to draw using many different colors, we can do one of two things. We can either change the foreground and/or background colors of the GC before any drawing function, or we can use several different GCs to draw in different colors. The decision as of which option to use is yours. Note that allocating many GCs will use more resources of the X server, but this will sometime lead to more compact code, and will might it easier to replace the drawn colors.

As an example to drawing using colors, look at the color-drawing.c program. This is a copy of the simple-drawing.c program, except that here we also allocate colors and use them to paint the rainbow...


X Bitmaps And Pixmaps

One thing many so-called "Multi-Media" applications need to do, is display images. In the X world, this is done using bitmaps and pixmaps. We have already seen some usage of them when setting an icon for our application. Lets study them further, and see how to draw these images inside a window, along side the simple graphics and text we have seen so far.

One thing to note before delving further, is that Xlib supplies no means of manipulating popular image formats, such as gif, jpeg or tiff. It is left up to the programmer (or to higher level graphics libraries) to translate these image formats into formats that the X server is familiar with - x bitmaps, and x pixmaps.


What Is An X Bitmap? An X Pixmap?

An X bitmap is a two-color image stored in a format specific to the X window system. When stored in a file, the bitmap data looks like a C source file. It contains variables defining the width and height of the bitmap, an array containing the bit values of the bitmap (the size of the array = width * height), and an optional hot-spot location (will be explained later, when discussing mouse cursors).

A X pixmap is a format used to store images in the memory of an X server. This format can store both black and white images (such as x bitmaps) as well as color images. It is the only image format supported by the X protocol, and any image to be drawn on screen, should be first translated into this format.

In actuality, an X pixmap can be thought of as a window that does not appear on the screen. Many graphics operations that work on windows, will also work on pixmaps - just supply the pixmap ID instead of a window ID. In fact, if you check the manual pages, you will see that all these functions accept a 'Drawable', not a 'Window'. since both windows and pixmaps are drawables, they both can be used to "draw on" using functions such as XDrawArc(), XDrawText(), etc.


Loading A Bitmap From A File

We have already seen how to load a bitmap from a file to memory, when we demonstrated setting an icon for an application. The method we showed earlier required the inclusion of the bitmap file in our program, using the C pre-processor '#include' directive. We will see here how we can access the file directly.



/* this variable will contain the ID of the newly created pixmap.    */
Pixmap bitmap;

/* these variables will contain the dimensions of the loaded bitmap. */
unsigned int bitmap_width, bitmap_height;

/* these variables will contain the location of the hot-spot of the   */
/* loaded bitmap.                                                    */
int hotspot_x, hotspot_y;

/* this variable will contain the ID of the root window of the screen */
/* for which we want the pixmap to be created.                        */
Window root_win = DefaultRootWindow(display);

/* load the bitmap found in the file "icon.bmp", create a pixmap      */
/* containing its data in the server, and put its ID in the 'bitmap'  */
/* variable.                                                          */
int rc = XReadBitmapFile(display, root_win,
                         "icon.bmp",
                         &bitmap_width, &bitmap_height,
                         &bitmap,
                         &hotspot_x, &hotspot_y);
/* check for failure or success. */
switch (rc) {
    case BitmapOpenFailed:
        fprintf(stderr, "XReadBitmapFile - could not open file 'icon.bmp'.\n");
        break;
    case BitmapFileInvalid:
        fprintf(stderr,
                "XReadBitmapFile - file '%s' doesn't contain a valid bitmap.\n",
                "icon.bmp");
        break;
    case BitmapNoMemory:
        fprintf(stderr, "XReadBitmapFile - not enough memory.\n");
        break;
    case BitmapSuccess:
        /* bitmap loaded successfully - do something with it... */
        .
        .
        break;
}

Note that the 'root_win' parameter has nothing to do with the given bitmap - the bitmap is not associated with this window. This window handle is used just to specify the screen that we want the pixmap to be created for. This is important, as the pixmap must support the same number of colors as the screen does, in order to make it useful.


Drawing A Bitmap In A Window

Once we got a handle to the pixmap generated from a bitmap, we can draw it on some window, using the XCopyPlane() function. This function allows us to specify what drawable (a window, or even another pixmap) to draw the given pixmap onto, and at what location in that drawable.


/* draw the previously loaded bitmap on the given window, at location   */
/* 'x=100, y=50' in that window. we want to copy the whole bitmap, so   */
/* we specify location 'x=0, y=0' of the bitmap to start the copy from, */
/* and the full size of the bitmap, to specify how much of it to copy.  */
XCopyPlane(display, bitmap, win, gc,
          0, 0,
          bitmap_width, bitmap_height,
          100, 50,
          1);
As you can see, we could also copy a given rectangle of the pixmap, instead of the whole pixmap. Also note the last parameter to the XCopyPlane function (the '1' at the end). This parameter specifies which plane of the source image we want to copy to the target window. For bitmaps, we always copy plane number 1. This will become clearer when we discuss color depths below.


Creating A Pixmap

Sometimes we want to create an un-initialized pixmap, so we can later draw into it. This is useful for image drawing programs (creating a new empty canvas will cause the creation of a new pixmap on which the drawing can be stored). It is also useful when reading various image formats - we load the image data into memory, cerate a pixmap on the server, and then draw the decoded image data onto that pixmap.



/* this variable will store the handle of the newly created pixmap. */
Pixmap pixmap;

/* this variable will contain the ID of the root window of the screen */
/* for which we want the pixmap to be created.                        */
Window root_win = DefaultRootWindow(display);

/* this variable will contain the color depth of the pixmap to create. */
/* this 'depth' specifies the number of bits used to represent a color */
/* index in the color map. the number of colors is 2 to the power of   */
/* this depth.                                                         */
int depth = DefaultDepth(display, DefaultScreen(display));

/* create a new pixmap, with a width of 30 pixels, and height of 40 pixels. */
pixmap = XCreatePixmap(display, root_win, 30, 40, depth);

/* just for fun, draw a pixel in the middle of this pixmap. */
XDrawPoint(display, pixmap, gc, 15, 20);


Drawing A Pixmap In A Window

Once we got a handle to pixmap, we can draw it on some window, using the XCopyArea() function. This function allows us to specify what drawable (a window, or even another pixmap) to draw the given pixmap onto, and at what location in that drawable.


/* draw the previously loaded bitmap on the given window, at location   */
/* 'x=100, y=50' in that window. we want to copy the whole bitmap, so   */
/* we specify location 'x=0, y=0' of the bitmap to start the copy from, */
/* and the full size of the bitmap, to specify how much of it to copy.  */
XCopyArea(display, bitmap, win, gc,
          0, 0,
          bitmap_width, bitmap_height,
          100, 50);
As you can see, we could also copy a given rectangle of the pixmap, instead of the whole pixmap.

One important note should be made - it is possible to create pixmaps of different depths on the same screen. When we perform copy operations (a pixmap onto a window, etc), we should make sure that both source and target have the same depth. If they have a different depth, the operation would fail. The exception to this is if we copy a specific bit plane of the source pixmap, using the XCopyPlane() function shown earlier. In such an event, we can copy a specific plain to the target window - in actuality, setting a specific bit in the color of each pixel copied. This can be used to generate strange graphic effects in window, but is beyond the scope of out tutorial.


Freeing A Pixmap

Finally, when we are done using a given pixmap, we should free it, in order to free resources of the X server. This is done using the XFreePixmap() function:


/* free the pixmap with the given ID. */
XFreePixmap(display, pixmap);
After freeing a pixmap - we must not try accessing it again.

To summarize this section, take a look at the draw-pixmap.c program, to see a pixmap being created using a bitmap file, and then tiled up on a window on your screen.


Messing With The Mouse Cursor

Often we see programs that modify the shape of the mouse pointer (also called the X pointer) when in certain states. For example, a busy application would often display a sand clock over its main window, to give the user a visual hint that they should wait. Without such a visual hint, the user might think that the application got stuck. Lets see how we can change the mouse cursor for our windows.


Creating And Destroying A Mouse Cursor

There are two methods for creating cursors. One of them is by using a set of pre-defined cursors, that are supplied by Xlib. The other is by using user-supplied bitmaps.

In the first method, we use a special font named "cursor", and the function XCreateFontCursor(). This function accepts a shape identifier, and returns a handle to the generated cursor. The list of allowed font identifiers is found in the include file <X11/cursorfont.h>. Here are a few such cursors:

XC_arrow
The normal pointing-arrow cursor displayed by the server.
XC_pencil
A cursor shaped as a pencil.
XC_watch
A sand watch.
And creating a cursor using these symbols is very easy:

#include <X11/cursorfont.h>    /* defines XC_watch, etc. */

/* this variable will hold the handle of the newly created cursor. */
Cursor watch_cursor;

/* create a sand watch cursor. */
watch_cursor = XCreateFontCursor(display, XC_watch);

The other methods of creating a cursor is by using a pair of pixmaps with depth of one (that is, two color pixmaps). One pixmap defines the shape of the cursor, while the other works as a mask, specifying which pixels of the cursor will be actually drawn. The rest of the pixels will be transparent. Creating such a cursor is done using the XCreatePixmapCursor() function. As an example, we will create a cursor using the "icon.bmp" bitmap. We will assume that it was already loaded into memory, and turned into a pixmap, and its handle is stored in the 'bitmap' variable. We will want it to be fully transparent. That is, only the parts of it that are black will be drawn, while the white parts will be transparent. To achieve this effect, we will use the icon both as the cursor pixmap and as the mask pixmap. Try to figure out why...



/* this variable will hold the handle of the newly created cursor. */
Cursor icon_cursor;

/* first, we need to define foreground and background colors for the cursor. */
XColor cursor_fg, cursor_bg;

/* access the default color map of our screen. */
Colormap screen_colormap = DefaultColormap(display, DefaultScreen(display));

/* allocate black and while colors. */
Status rc = XAllocNamedColor(display,
                             screen_colormap,
                             "black",
                             &cursor_fg,
                             &cursor_fg);
if (rc == 0) {
    fprintf(stderr, "XAllocNamedColor - cannot allocate 'black' ??!!??\n");
    exit(1);
}
Status rc = XAllocNamedColor(display,
                             screen_colormap,
                             "white",
                             &cursor_bg,
                             &cursor_bg);
if (rc == 0) {
    fprintf(stderr, "XAllocNamedColor - cannot allocate 'white' ??!!??\n");
    exit(1);
}

/* finally, generate the cursor. make the 'hot spot' be close to the */
/* top-left corner of the cursor - location (x=5, y=4). */
icon_cursor = XCreatePixmapCursor(display, bitmap, bitmap,
				  &cursor_fg, &cursor_bg,
                                  5, 4);

One thing to be explained is the 'hot spot' parameters. When we define a cursor, we need to define which pixel of the cursor is the pointer delivered to the user in the various mouse events. Usually, we will choose a location of the cursor that visually looks like a hot spot. For example, in an arrow cursor, the tip of the arrow will be defined as the hot spot.

Finally, when we are done with a cursor and no longer need it, we can release it using the XFreeCursor() function:


XFreeCursor(display, icon_cursor);


Setting A Window's Mouse Cursor

After we have created a cursor, we can tell the X server to attach this cursor to any of our windows. This is done using the XDefineCursor(), and causes the X server to change the mouse pointer to the shape of that cursor, each time the mouse pointer moves into and across that window. We can later detach this cursor from our window using the XUndefineCursor() function. This will cause the default cursor to be shown when the mouse enter that windows.


/* attach the icon cursor to our window. */
XDefineCursor(display, win, icon_cursor);

/* detach the icon cursor from our window. */
XUndefineCursor(display, win);

As an example, look at our cursor.c program, and see how mouse cursors are set, changed and removed. Run the program, place the mouse pointer over the created window, and watch.


LUPG home Tutorials Related material Project Ideas Essays Send comments
[LUPG Home] [Tutorials] [Related Material] [Essays] [Project Ideas] [Send Comments]

This document is copyright (c) 1999-2002 by guy keren.

The material in this document is provided AS IS, without any expressed or implied warranty, or claim of fitness for a particular purpose. Neither the author nor any contributers shell be liable for any damages incured directly or indirectly by using the material contained in this document.

permission to copy this document (electronically or on paper, for personal or organization internal use) or publish it on-line is hereby granted, provided that the document is copied as-is, this copyright notice is preserved, and a link to the original document is written in the document's body, or in the page linking to the copy of this document.

Permission to make translations of this document is also granted, under these terms - assuming the translation preserves the meaning of the text, the copyright notice is preserved as-is, and a link to the original document is written in the document's body, or in the page linking to the copy of this document.

For any questions about the document and its license, please contact the author.