TEACH MOTIF Tom Khabaza Dec 1990 Updated: Adrian Howard Mar 1992 Updated: Julian Clinton Nov 1993 This teach file contains a tutorial to demonstrate the creation and manipulation of some widgets from the Motif widget set. The tutorial in this file can also be found in the X chapter of the Poplog User Guide. In order to run this tutorial, you must be running Poplog on a system which provides the "Xm" (Motif) widget set libraries, and be running an X Window Server which is capable of supporting Motif applications. See HELP *MOTIF for details of the Poplog Motif widget set interface. This teach files follows the same format and provides an essentially similar demonstration program to that in TEACH *OPENLOOK. The complete source code for this tutorial is in LIB *XmTutorial. This tutorial is a direct parallel to TEACH *OPENLOOK and LIB *XolTutorial. CONTENTS - (Use g to access required sections) -- The Task -- The Application Code -- Loading The X-related Libraries -- Loading Motif Widget Classes -- Initialising The Toolkit -- Creating A Top Level Window -- Creating widgets -- The Role Of "Callback Routines" -- Updating The Resources -- Adding Callbacks To The Widgets -- Initialising The program -- Things To Try -- Destroying The Panel -- The Task ----------------------------------------------------------- We want to be able to examine, display and modify the (integer) values of a collection of variables. We are going to build a simple application using the X Toolkit Intrinsics and the Motif widget set to perform this task. The application contains a single button and a "scale". The button is labelled with a variable name and its value. The scale displays the same value, and can also be used to set it using the mouse. Pressing the button changes the variable displayed; repeated pressing cycles through the complete set of variables. -- The Application Code ----------------------------------------------- The names of variables to be manipulated by the application are stored in a list in the global variable -variable_list-. The variables being manipulated are also global, and are initialised to zero: vars variable_list = [a b c d e]; vars a=0, b=0, c=0, d=0, e=0; In order to "cycle" through the variables stored in variable_list, another global variable stores the current position in the list, initially set to the beginning. Another holds the name of the variable currently selected: vars current_variable, current_variable_list = variable_list; We then provide a routine for selecting the current variable. If -current_variable_list- is -nil-, then we should "wrap round", starting again at the beginning of -variable_list-. The head of -current_variable_list- becomes the current variable, and the tail becomes the new -current_variable_list- (for use next time). define new_current_variable; if current_variable_list = [] then variable_list -> current_variable_list; endif; hd(current_variable_list) -> current_variable; tl(current_variable_list) -> current_variable_list; enddefine; -- Loading The X-related Libraries ------------------------------------ A number of X-related libraries are needed for this application. First we load the general library for making other X-related libraries available: uses popxlib; We also require the libraries for widget handling, callback manipulation and event management: uses xt_widget; uses xt_callback; uses xt_event; Finally, we will define a set of general X-related constants and structures provided in Poplog to make coercion between X types and Poplog types more convenient. Here we will compile them using * loadinclude: loadinclude xpt_coretypes; -- Loading Motif Widget Classes --------------------------------------- Widget classes are accessed through a number of constants defined in autoloadable libraries. Individual widget classes are accessed through these constants --- thus the Motif Scale widget class can be made accessible by: uses xmScaleWidget; ;;; defines xmScaleWidget constant xmScaleWidget => ** (Notice that we refer to this class as the "ScaleWidget" class rather than just the "Scale" class, since we may at other times need to access a "ScaleGadget" class.) In addition to loading the Motif widgets, we also require one widget supplied in the X Toolkit widget set: the ApplicationShellWidget. This will be the top-level window for our application. The following code thus loads the ApplicationShellWidget, RowColumnWidget, PushButtonWidget and ScaleWidget classes and all the associated procedures and data. exload_batch; uses xtApplicationShellWidget, xmRowColumnWidget, xmPushButtonWidget, xmScaleWidget; endexload_batch; This is surrounded by the brackets "exload_batch ... endexload_batch" to ensure that then external loading of procedures and data-structures takes place in a single "batch", that is in a single call to the linker, for increased efficiency. Motif also has a number of constants which are typically used as attribute specifiers (e.g. -XmHORIZONTAL-, -XmVERTICAL-). These are defined in an include file, 'XmConstants.ph'. In this example, we will compile the file rather than simply including it as it would be in a source file: loadinclude XmConstants; ;;; rather than: include XmConstants -- Initialising The Toolkit ------------------------------------------- Before creating any Motif widgets, we must perform various initialisations. The X Toolkit must be initialised, and we must create a connection with the X server, and an initial application context. These functions are performed by the single call: XptDefaultSetup(); -- Creating A Top Level Window ---------------------------------------- We now create a top-level application shell in which to place other widgets. The application shell is created by the standard Toolkit routine -XtAppCreateShell-, which takes the application name, the application "class" name, the application shell widget class, the display on which the shell is to appear, and an ArgList. An "ArgList" is an X Toolkit structure containing a set of attribute value pairs; in this case the attributes are "resources", that is slots in a widget used to hold its attributes. ArgLists may be used to specify various properties of a widget or an application shell, for example at the point where it is created. vars topwin = XtAppCreateShell('panel', 'Demo', xtApplicationShellWidget, XptDefaultDisplay, [{allowShellResize ^true}]); The application name is used as the title for the window created; the application class name is used by the X resource database manager, but we can ignore it here. In this example, the ArgList is used to set the base window's allowShellResize resource to the boolean -true-, indicating that this window should respond to resize requests from its children. -- Creating widgets --------------------------------------------------- We now create three widgets, one from each of the classes loaded previously. To create a widget we must supply a name (a string), the class of the widget, the widget's "parent" and an ArgList specifying the widget's initial resources. Widgets are arranged hierarchically, with every widget (except for top-level shells) having a "parent" widget. Normally each widget, when displayed, will be visually "inside" its parent widget (where the parent is visible). Of the widget classes loaded above, the "row column" class needs some explanation. A row column widget is used to contain and manage other widgets. Widgets placed within a row column widget are automatically arranged in a sensible default arrangement with sufficient space and no overlap. By default, resizing actions are performed when necessary. The Poplog X Toolkit routine -XtCreateManagedWidget- is used to create the widgets. -XtCreateManagedWidget- corresponds to the standard X Toolkit routine used to create widgets that respond appropriately to management from their parent and window manager: vars rowcol = XtCreateManagedWidget('mycontrol', xmRowColumnWidget, topwin, [{orientation ^XmHORIZONTAL}]), button = XtCreateManagedWidget('mybutton', xmPushButtonWidget, rowcol, [{recomputeSize ^false}]), scale = XtCreateManagedWidget('myscale', xmScaleWidget, rowcol, [ {width 100} {orientation ^XmHORIZONTAL} {maximum 50}]); The rowcol is created with the top window as its parent; the button and scale both have the rowcol as their parent. Thus the rowcol is used to manage to two visible widgets. The rowcol has an ArgList specifying that it is organised horizontally. The button has an ArgList which specifies that its size should not change when it's label is altered. The scale is given three non-default resource settings: a width of 100 pixels, a horizontal orientation and a maximum value of 50. The value of the constant -XmHORIZONTAL- is provided by the Motif constans include file, 'XmConstants.ph' (supplying -XmVERTICAL- would specify a vertical orientation). -- The Role Of "Callback Routines" ------------------------------------ Having created the widgets for our panel, we now specify how they should behave. This is achieved by supplying the widgets with "callbacks", that is procedures to be called when the widget is manipulated with the mouse. Each callback has a name indicating the circumstances under which the procedure is called. For example, the "activate" callback is called when the associated widget is selected with the mouse, and the "drag" callback is called when the associated scale is moved using the mouse. -- Updating The Resources --------------------------------------------- The callback routines attached to both widgets will change the display by updating the resources of the widgets. The resources to be updated are the label of the button and the value of the scale. The former is always set to the current variable name concatenated with its value and separated by a space. The latter is always set to the value of the current variable. Separate routines provide these two functions: define set_button_label; lvars string; XmStringCreateLtoR( current_variable sys_>< ' ' sys_>< valof(current_variable), XmSTRING_DEFAULT_CHARSET) -> string; string -> XptValue(button,XmN labelString,TYPESPEC(:XmString)); enddefine; define set_scale_value; valof(current_variable) -> XptValue(scale, XmN value); enddefine; Resources are updated using the Poplog X Toolkit routine -XptValue-. This takes a widget, a resource name and an (optional) coercion type. If the coercion type is not provided, it defaults to "int" --- the correct type in -set_scale_value-. However, in -set_button_label- we are dealing with a Motif compound string. First we build the string using the Motif procedure -XmStringCreateLtoR-, passing it a string and a "character set". Then we set the resource using the "TYPESPEC(:XptString)" coercion argument. The resource name argument is a string. The -XmN- convenience macro is used here to convert a word into a string, so repeated use of the same resource name will not create a new string each time. -- Adding Callbacks To The Widgets ------------------------------------ Every callback routine is called with three arguments: the widget whose callback is being invoked, the "client data" supplied when the callback is added to the widget, and the "call data" indicating the event which caused the callback. In the case of the button's callback, none of this data is used; we simply call -new_current_variable- to move on to the next variable, -set_button_label- to give the button its new label, and -set_scale_value- to set the scale value to the value of the new variable: define switch_variable_callback(widget, client_data, call_data); lvars widget, client_data, call_data; new_current_variable(); set_button_label(); set_scale_value(); enddefine; This is attached to the activate callback of the button using the X Toolkit routine -XtAddCallback-: XtAddCallback(button, XmN activateCallback, switch_variable_callback, 'Data for button activate callback'); The final argument of this call is the "client data" passed to the callback routine when it is called. This allows the callback routine to distinguish between widgets in cases where the same callback routine has been used for more than one widget. However, in the present comparatively simple case this is not used by the callback routines, and is therefore effectively a dummy argument. The second callback procedure, -set_variable_callback-, is used to set the value of the selected variable when the scale is moved. Here, the call data argument is used to access the new value given by the position of the scale. The argument contains a Poplog "external pointer" object, pointing to a Motif Scale callback structure XmScaleCallbackStruct. First the -call_data- argument is declared as an XmScaleCallbackStruct type, using the -l_typespec- construct. Then the "value" field of this structure is accessed using -exacc- (see REF *EXTERNAL for details). This value is assigned to the current variable and the button label is set appropriately. define set_variable_callback(widget, client_data, call_data); lvars widget, client_data, call_data; l_typespec call_data: XmScaleCallbackStruct; exacc call_data.value -> valof(current_variable); set_button_label(); enddefine; This is attached to the scale's valueChanged and drag callbacks thus: XtAddCallback(scale, XmN valueChangedCallback, set_variable_callback, 'Data for scale valueChanged callback'); XtAddCallback(scale, XmN dragCallback, set_variable_callback, 'Data for scale drag callback'); The valueChanged callback is called every time the value represented by the slider position is changed. The drag callback is called when the slider is continuously "dragged" with a mouse button. We have to add the procedure to drag and valueChanged since the valueChanged callback is only called when the mouse button is released during "dragging". -- Initialising The program ------------------------------------------- First we set up the current variable and set the button's label: new_current_variable(); set_button_label(); We now "realise" the widgets, making them visible on the screen: XtRealizeWidget(topwin); The application now appears on the display and responds to mouse events in the manner that we have specified. -- Things To Try ------------------------------------------------------ The Scale widget includes several resources which you might be interested in changing: To give the scale a label, use: vars label = XmStringCreateLtoR('value', XmSTRING_DEFAULT_CHARSET); label -> XptValue(scale, XmN titleString, TYPESPEC(:XmString)); The scale can also have a small display showing its current value: true -> XptValue(scale, XmN showValue, TYPESPEC(:XptBoolean)); This value display can be set to display a floating point number, such as 0.4 or 1.5 by moving the placement of the decimal point: 1 -> XptValue(scale, XtN decimalPoints); The scale position can be altered directly from Pop-11 using the following procedure: define update_scale(x); lvars x; x -> valof(current_variable); set_button_label(); set_scale_value(); enddefine; ;;; Set the current variable to 45 update_scale(45); -- Destroying The Panel ----------------------------------------------- The panel continues to exist as long as the variables topwin, rowcol, scale and button contain the widgets that comprise it. If these objects are discarded, by assigning another value to the variables thus false ->> topwin ->> rowcol ->> scale -> button; then the widgets will destroyed when there are garbage-collected. Alternatively, the widgets can be explicitly destroyed using -XtDestroyWidget- thus: XtDestroyWidget(topwin); When a widget is destroyed with -XtDestroyWidget- all it's children are also destroyed. Therefore we can safely destroy the entire application by destroying the top level widget. --- C.x/x/pop/teach/motif --- Copyright University of Sussex 1992. All rights reserved. ----------