Starting programming with the X-Window system

Abstract

This document 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.

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.

Hosted by www.Geocities.ws

Starting programming with the X-Windows system

Starting programming with the X-Windows system: Part Two.

Abstract

This document continues the tutorial started in part one. In this tutorial we will cover menus, popups and some basic XLib functions.

Menu.c

The first program displays a text window with pulldown menus above it:
Menu.c:

#include <stdio.h>
#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Intrinsic.h>
#include <Xm/MainW.h>
#include <Xm/RowColumn.h>
#include <Xm/TextF.h>

void file_pulldown_cb(w, data, event )
Widget w;
int data;
XtPointer event;
{
	fprintf ( stdout, "data = %d\n", data );

	if (data == 5 )
		exit(0);
}


main(argc, argv)
int	argc;
char	*argv[];
{
	XtAppContext	app;

	Widget	toplevel_shell, main_window, canvas_field, top_menubar, file_pulldown;
	XmString	file, 	new, new_acc, open, open_acc, save, save_acc, saveas, close, quit, quit_acc;

	toplevel_shell = XtVaAppInitialize(&app, "Menus", NULL, 0, 
		&argc, argv, NULL, NULL);

	main_window = XtVaCreateManagedWidget("main_window", xmMainWindowWidgetClass, toplevel_shell,
				NULL);

	file = XmStringCreateSimple("File");
	top_menubar = XmVaCreateSimpleMenuBar(main_window, "top_menubar", 
				XmVaCASCADEBUTTON, file, 'F',
				NULL );
	XmStringFree(file);

	new = XmStringCreateSimple("New");
	new_acc = XmStringCreateSimple("Ctrl+N");

	open = XmStringCreateSimple("Open...");
	open_acc = XmStringCreateSimple("Ctrl+O");

	save = XmStringCreateSimple("Save");
	save_acc = XmStringCreateSimple("Ctrl+S");

	saveas = XmStringCreateSimple("Save As...");
	close = XmStringCreateSimple("Close");

	quit = XmStringCreateSimple("Quit");
	quit_acc = XmStringCreateSimple("Ctrl+C");

	file_pulldown = XmVaCreateSimplePulldownMenu(top_menubar, "file_pulldown", 0, file_pulldown_cb,
				XmVaPUSHBUTTON, new, 'N', "Ctrl<Key> n", new_acc,
				XmVaPUSHBUTTON, open, 'O', "Ctrl<Key> o", open_acc,
				XmVaPUSHBUTTON, save, 'S', "Ctrl<Key> s", save_acc,
				XmVaPUSHBUTTON, saveas, 'A', NULL, NULL,
				XmVaSEPARATOR,
				XmVaPUSHBUTTON, close, 'C', NULL, NULL,
				XmVaPUSHBUTTON, quit, 'Q', "Ctrl<Key> c", quit_acc,
				NULL );
	XmStringFree(new);
	XmStringFree(new_acc);
	XmStringFree(open);
	XmStringFree(save);
	XmStringFree(saveas);
	XmStringFree(close);
	XmStringFree(quit);

	XmStringFree(new_acc);
	XmStringFree(open_acc);
	XmStringFree(save_acc);
	XmStringFree(quit_acc);

	XtManageChild(top_menubar);

	XtRealizeWidget(toplevel_shell);
	XtAppMainLoop(app);
}


See Part One of this workshop www page for an example of how to compile the source code.

Discussion Of Menu.c

In main() - a MainWindow widget containing a menu-bar is created. A MainWindow widget is used as the highest level container for applications which wish to conform to the motif style.

The menubar is created using the XmVaCreateSimpleMenuBar() function. The returned widget must be managed with XtManageChild().

Another function is used to make pulldown menus inside the menubar - XmVaCreateSimplePulldownMenu().

The variable argument functions used have a set of arguments defined which can be used to specify of what class we want the child widgets to be. (XmVaCASCADEBUTTON, XmVaPUSHBUTTON etc. ) This is instead of creating each sub-widget explicitly with a seperate function.

Experiment with the menu program - try adding more pulldown menus so that you get a menu like those seen is full blown applications.

    Project	Edit
    Open...	Cut
    Save	Copy
    Save as...	Paste
    Print...
    Quit

Popup.c

The next program demonstrates the use of popups. Popups allow more than one main window per application.
Popup.c:

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

Widget toplevel;

void close_window(w, client_data, event_data)
{
Widget popup = (Widget)client_data;

    XtPopdown(popup);
    XtDestroyWidget(popup);
}

void pop(w, client_data, event_data)
Widget w;
XtPointer client_data;
XtPointer event_data;
{
Widget a, button, popup;

    popup = XtVaCreatePopupShell("Popup", transientShellWidgetClass, toplevel, NULL);
    button = XtVaCreateManagedWidget("Close", xmPushButtonWidgetClass, popup,
        NULL);

    XtAddCallback(button, XmNactivateCallback, close_window, (XtPointer)popup);

    XtPopup(popup, XtGrabNone);
}


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

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

    label = XmStringCreateSimple("Make popup"); 
    button = XtVaCreateManagedWidget("pushme", xmPushButtonWidgetClass, toplevel,
        XmNlabelString, label,
        NULL);

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

    XtRealizeWidget(toplevel);
    XtAppMainLoop(app);
}


Discussion Of Popup.c

This is quite a short program which displays a single button in a window. When the button is pressed, a new window will popup. The popups each contain a single "close" button which removes the window from the display.

The popups are created with the toplevel shell widget as the parent. This means that if you iconize the toplevel shell, then all associated popup windows will also iconize.

The line that actually causes the popup-window to be displayed is:

	XtPopup(popup, XtGrabNone);
If XtGrabNone is changed to "XtGrabExclusive", then that window is made to be the only one that accepts events and you won't be able to press the "make popup" button on the main shell window until you have closed the popup. This is useful for getting information from the user before the application can proceed e.g. getting a save filename before saving a file.

Draw.c

The next program demonstates event-handlers and some basic XLib functions for drawing lines and rectangles:
draw.c:

#include <X11/StringDefs.h>
#include <Xm/Xm.h>
#include <Xm/DrawingA.h>

void redraw(w, calldata, ev)
Widget w;
XtPointer calldata;
XtPointer ev;
{
GC gc = (GC)calldata;
Window win = XtWindow(w);
Display *d = XtDisplay(w);

    XDrawLine(d, win, gc, 0, 0, 150, 150);
    XDrawRectangle(d, win, gc, 150, 150, 200, 100);
    XFillRectangle(d, win, gc, 200, 200, 100, 50);
}

main(argc, argv)
int     argc;   
char    *argv[];
{
Widget toplevel, viewport;
XGCValues values;  
GC gc;
XtAppContext appcon;

    toplevel = XtVaAppInitialize(&appcon, "Draw", NULL, 0, &argc, argv, NULL, 0);

    viewport = XtVaCreateManagedWidget("viewport", xmDrawingAreaWidgetClass, toplevel,
        XmNwidth, 400,
        XmNheight, 300,
        NULL);

    XtVaGetValues(viewport, 
        XtNforeground, &values.foreground,
        XtNbackground, &values.background,
        NULL);

    gc = XtGetGC(viewport, GCForeground | GCBackground, &values);

    XtAddEventHandler(viewport, ButtonPressMask , FALSE, redraw, (XtPointer)gc);

    XtRealizeWidget(toplevel);

    XtAppMainLoop(appcon);
}       


Discussion Of Draw.c

The program simply displays a single window. If the mouse is clicked inside the window then a line, a box and a filled-rectangle are drawn.

Notice that, if another window is dragged over this one, the drawing is lost. Refresh the window by clicking in the window once again.

The button-press is detected by the event-handler "XtAddEventHandler()". This tells the application to call the redraw function whenever a button is pressed in the viewport widget.

Event-handlers work in the same way as callbacks but allow the programmer to specify the exact event to react to. The event-masks can be added together so that one event handler can wait for more than one different event type (e.g. if the programmer wanted to watch for mouse-button presses AND key-presses - "KeyPressMask | ButtonPressMask" would be used.

(for a full list of event-masks, see the Event Definitions in /usr/include/X11/X.h).

It is up to the application to refresh the picture when it needs to be redrawn. This can be done by watching for expose events. Expose events occur when parts of the window get corrupted.

Change "ButtonPressMask" to "ExposureMask" - this will tell the application to automatically draw into the window every time it needs to.

Three XLib functions are used in this program XDrawLine, XDrawRectangle, XFillRectangle. All of these functions use the same first three parameters.

The first parameter is the display - this is a handle to the X-display to use. Normally this is not referenced from Xt but for use with XLib functions, it can be retrieved by using XtDisplay on a widget.

Widgets are built from lower-level XLib Windows. The widget being used to draw into is the DrawArea widget. This is a composite widget which can also be used for graphics. The Window-id of a widget is obtained by using the XtWindow function.

The third parameter is the Graphics Context. This is a data-structure which is used to store information about the current graphics setting. It stores current colours (foreground and background), line-style (dotted etc), line-width etc.

The lines program needs to find the default foreground and background so that the lines are drawn in the correct colours. This is done by reading the XtNforeground, XtNbackground resource values from the main widget. The values are copied into the XGCValues structure and used in the creation of the graphics context. If you wish to change the line-width or some other attribute then you would do this in a similar way.

Try adding: values.line_style = LineOnOffDash;

before the XtGetGC function, and change the value mask in the XtGetGC function to "GCForeground | GCBackground | GCLineStyle".

For more GC functions, see the xman page for XCreateGC.

Hosted by www.Geocities.ws

1