Starting programming with the X-Window system

Abstract

This document (http://www.comp.lancs.ac.uk/computing/users/sean/Motif-Workshop/workshop1.html) contains example programs to demonstate the X window system. The widget set used is Motif but the code could easily be adapted to be used with other widget sets. You may also want to see a hypertext X-Windows course also available on the web.

Hello.c

The first program simply displays a button in a window and prints a message to the console when the button is pressed. It shows the basic structure of most X programs and demonstates the use of callbacks.
hello.c:

#include <Xm/Xm.h>
#include <Xm/PushB.h>

void say_hello(w, client_data, event_data)
Widget w;
XtPointer client_data;
XtPointer event_data;
{
    printf("Hello!\n");
}


main(argc, argv)
int argc;
char *argv[];
{
    Widget toplevel, button;
    XtAppContext  app;
    XmString label;

    toplevel = XtVaAppInitialize(&app, "Hello", NULL, 0,
        &argc, argv, NULL, NULL);

    label = XmStringCreateSimple("Push here to say hello"); 
    button = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, toplevel,
        XmNlabelString, label,
        NULL);

    XmStringFree(label);
    XtAddCallback(button, XmNactivateCallback, say_hello, NULL);

    XtRealizeWidget(toplevel);
    XtAppMainLoop(app);
}

To Compile the code you will need a makefile like this:


Makefile:

MOTIFHOME = /usr/dt
CFLAGS = -g -I$(MOTIFHOME)/include -I$(OPENWINHOME)/include

LIBS = -R$(MOTIFHOME)/lib -R$(OPENWINHOME)/lib -L$(MOTIFHOME)/lib -L$(OPENWINHOME)/lib -lXm -lXt -lX11

PROG = hello

OBJS = hello.c

$(PROG): $(OBJS)
        $(CC) $(CFLAGS) $(OBJS) -o $(PROG) $(LIBS)


Discussion Of Hello.c

The first function is a callback procedure and is called when the button is pressed. All callbacks have the same parameters, the widget that the message came from, client data and event data. The client data can be used to pass a pointer or some other information across the application.

The first line of main() (XtVaAppInitialize) initializes the connection to the X-server and passes the command line parameters (so that -display etc can be interpreted). The function returns the id of a shell widget which is used as the parent for the button. "Hello" is used for the application resource name.

XmStringCreateSimple creates an XmString which is used for the label of the button. An XmString is a type which encapsulates a text-string and a font. XmStringCreateSimple creates an XmString with the default font.

XtVaCreateManagedWidget makes the button widget. It's parent is the toplevel widget. The first parameter is the resource name of the widget which can be used for setting various resources from and .Xdefaults/Xresources file.

e.g.

    Hello*pushme.fontList: -*-helvetica-bold-r-*-*-17-*-*-*-*-*-*-*
in a defaults file will tell the application to make the button appear with the text in a different font (you can run "xfontsel" to choose a font-string). Using resource files to specify some widget settings means that recompilation is not needed for many visual changes, as the binary just needs to be re-run with the new file.

XmStringFree frees the XmString data-structure (the widget takes a copy of the XmString so it is not needed any more)

XtAddCallback tells the widget to call the say_hello function when the button is activated.

XtRealizeWidget sets up the whole widget hierachy ready for use.

XtAppMainLoop is the programs event loop and execution will never get past this line. It is an infinite loop which gets and dispatches events forever.


Container.c

The next program shows a RowColumn container widget with two buttons as children.
container.c:

#include <X11/StringDefs.h>
#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>


void say_hello(w, client_data, event_data)
Widget w;
XtPointer client_data;
XtPointer event_data;
{
    printf("Hello!\n");
}


void quit_program(w, client_data, event_data)
Widget w;
XtPointer client_data;
XtPointer event_data;
{
    printf("Bye!\n");
    exit(0);
}


main(argc, argv)
int argc;
char *argv[];
{
    Widget toplevel, button1, button2, container;
    XtAppContext  app;
    XmString label;

    toplevel = XtVaAppInitialize(&app, "Container", NULL, 0,
        &argc, argv, NULL, NULL);

    container = XtVaCreateManagedWidget("box", xmRowColumnWidgetClass, toplevel,
        NULL);


    label = XmStringCreateSimple("Push here to say hello"); 
    button1 = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, container,
        XmNlabelString, label,
        NULL);
    XmStringFree(label);
    XtAddCallback(button1, XmNactivateCallback, say_hello, NULL);


    label = XmStringCreateSimple("Push here to quit"); 
    button2 = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, container,
        XmNlabelString, label,
        NULL);
    XmStringFree(label);
    XtAddCallback(button2, XmNactivateCallback, quit_program, NULL);


    XtRealizeWidget(toplevel);
    XtAppMainLoop(app);
}

You should be able to alter the makefile from the first example to make it work on this example and the subsequent ones.

Discussion Of Container.c

xmRowColumnWidgetClass is the class of a container widget. Any widgets which are added to a RowColumn widget are constrained into rows and columns. RowColumns have a few resources which can be added to change the layout of the children.
If you add:

    XmNorientation, XmHORIZONTAL,
to the argument list of the create line for the RowColumn, the RowColumn widget will lay its children out horizontally.

A bulletin-board widget is another composite widget that can be used to hold children widgets. Each child can have x and y values indicating a position.

Add "#include <Xm/BulletinB.h>" to the list of include files at the top and change "xmRowColumnWidgetClass" to "xmBulletinBoardWidgetClass".

If you compile and run the program then the button widgets will both be at 0,0 in the BulletinBoard. To position the buttons you need to add some arguments to the functions that create the buttons:

e.g.

    button1 = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, container,
        XmNlabelString, label,
        XmNx, 100,
        XmNy, 100,
        NULL);

    button2 = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, container,
        XmNlabelString, label,
        XmNx, 200,
        XmNy, 200,
        NULL);


Text.c

The next program demonstrates the Text widget and the Form widget, another type of composite widget. The program displays a scrolled-text window with two buttons underneath it. If the window is resized then the buttons remain under the text window after the text window resizes. One button will print the text, contained in the text window, to the shell window that the program was run from. The other button exits the program.
text.c:

#include <Xm/Xm.h>
#include <Xm/PushB.h>
#include <Xm/Text.h>
#include <Xm/Form.h>
#include <Xm/RowColumn.h>


void print_text(w, client_data, event_data)
Widget w;
XtPointer client_data;
XtPointer event_data;
{
Widget text = (Widget)client_data;
char *string;

    string = XmTextGetString(text);
    printf("%s\n", string);
    free(string);
}

void quit_program(w, client_data, event_data)
Widget w;
XtPointer client_data;
XtPointer event_data;
{
    exit(0);
}


main(argc, argv)
int argc;
char *argv[];
{
    Widget toplevel, form, scrollw, button, text, container;
    XtAppContext  app;
    XmString label;
    Arg al[10];
    int ac;


    toplevel = XtVaAppInitialize(&app, "Editor", NULL, 0,
        &argc, argv, NULL, NULL);

    form = XtVaCreateManagedWidget("form", xmFormWidgetClass, toplevel,
        NULL);

    ac = 0;
    XtSetArg(al[ac], XmNeditMode, XmMULTI_LINE_EDIT); ac++; 
    XtSetArg(al[ac], XmNscrollVertical, TRUE); ac++; 

    text = XmCreateScrolledText(form, "Text", al, ac);
    XtManageChild(text);

    scrollw = XtParent(text);

    container = XtVaCreateManagedWidget("box", xmRowColumnWidgetClass, form,
        XmNrightAttachment, XmATTACH_FORM, 
        XmNleftAttachment, XmATTACH_FORM, 
        XmNbottomAttachment, XmATTACH_FORM, 
        XmNorientation, XmHORIZONTAL,
        NULL);

    XtVaSetValues(scrollw,
        XmNrightAttachment, XmATTACH_FORM, 
        XmNleftAttachment, XmATTACH_FORM, 
        XmNtopAttachment, XmATTACH_FORM, 
        XmNbottomAttachment, XmATTACH_WIDGET, 
        XmNbottomWidget, container,
        NULL);

    label = XmStringCreateSimple("Print"); 
    button = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, container,
        XmNlabelString, label,
        NULL);
    XmStringFree(label);
    XtAddCallback(button, XmNactivateCallback, print_text, (XtPointer)text);

    label = XmStringCreateSimple("Quit"); 
    button = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, container,
        XmNlabelString, label,
        NULL);
    XmStringFree(label);
    XtAddCallback(button, XmNactivateCallback, quit_program, NULL);


    XtRealizeWidget(toplevel);
    XtAppMainLoop(app);
}



A form widget is used when you need to "join" widgets together so that you can resize a window and maintain relative positions or alter window sizes. This might be useful in a text editor, where a user wants to resize the window to make a larger work area.

Children inside a form widget have resources which tell the form how to manage them. e.g. XmNrightAttachment, XmATTACH_FORM indicates that the right side of the widget should always be "joined" to the edge of the form. XmNbottomAttachment, XmATTACH_WIDGET indicates that the bottom edge of the widget is to be joined to the top of another widget which is specified by XmNbottomWidget.

The argument lists for children within forms can become very complicated. If you were developing a complicated interface with forms then it might be best to use resource files for the attachment and positioning, saving compilation time.

The first function in the program is a callback which prints the contents of the text widget to the shell window. The id of the text widget is passed via the clientdata parameter. This means that a global is not needed to store the id of the text-widget, and that the callback could be used by other buttons and text widgets. The "XtAddCallback" call after the creation of the "Print" button passes the id of the text widget to the button-callback. It must be cast to an XtPointer and then cast back to a widget in the callback routine. This method can be used to pass simple variables (ints, chars) or pointers to your own data-structures.

The text widget is created by the motif function-call "XmCreateScrolledText". This is known as a Motif convienience function. It actually creates two widgets, a ScrolledWindow containing a Text widget. The id of the text widget is returned by the call, so you must find its parent (the scrolled window by using the Xt function "XtParent". Motif convienience functions do not automatically manage the child so you must ensure that XtManageChild() is called for the widget to appear.

Convienience functions must use the arg-array technique for passing resource settings, you cannot use the "Va" variable arguments method as in XtVaCreate...

If you compile and run the program you will see a small text window with two buttons beneath it. The text widget has resources which control the number of rows and columns displayed. These are XmNrows and XmNcolumns. Modify the program so that the text widget starts up with a size of 80x25.

Also try setting XmNwordWrap to true. You can see that, with a bit more code to load and save files, it is possible to write a full text-editor.

Now Read X-Windows Workshop Part Two


Sean Butler
sean@comp.lancs.ac.uk
Doc Status: Under Development...
This page is based upon notes originally written by AAI/AI-ED Group Computing Department Lancaster University