|C H A P T E R 8|
Structured Code Generation and Reusable Definitions
This chapter describes how X-Designer helps you to control the structure of the generated code. Being able to do this is essential for creating reusable widget hierarchies. These reusable hierarchies, known as definitions, appear on the widget palette and can be added to the hierarchy like any other widget. A detailed description of definitions also appears in this chapter.
X-Designer provides controls for structuring your generated code so that it is more flexible and can be reused more easily. Before reading this section, you should review the structure of the default generated code in Analysis of the Primary Module. In particular, note that the default code has a single creation procedure for each Shell in the design. Widgets are declared as local if they have not been named and global if they are named or they are Session Shells or Application Shells.
The structured code controls let you:
X-Designer's controls for structuring code are located on the "Code generation" page of the Core resource panel.
The simplest case of structured code generation is to designate a widget as a function structure. This makes X-Designer generate a separate function that creates that widget and its descendants. This function is called by the creation procedure for the enclosing widget.
To do this, select the "Code generation" page of the Core resource panel and select "Function" from the "Structure" option menu.
The hierarchy shown in FIGURE 8-1 produces the following generated code, slightly simplified for clarity:
This module now has two functions: one (create_shell()) for creating the whole hierarchy and one (create_button_box()) for creating the button box.
The next type of code structuring is the data structure. This is similar to a function structure, in that X-Designer generates a separate creation procedure for the widget and its descendants. When a widget is designated as a data structure, X-Designer also generates a typedef for a structure including that widget and its children. The creation procedure for the widget creates and sets up that type of structure and returns a pointer to it. A deletion function (delete_<widget_name>) is also generated so that the allocated memory can be freed.
To designate a widget as a data structure, select the "Code generation" page from the Core resource panel and select "Data structure" from the "Structure" option menu.
Using the same hierarchy as shown above, but with button_box designated as a data structure, the following code is produced, slightly simplified for clarity:
The use of C++ classes is very similar to data structures. X-Designer does not wrap each widget in the hierarchy with a C++ class, but instead designates sections of the hierarchy as classes in their own right. Each widget designated as a C++ class has a class defined for it. Its named descendant widgets become members of that class and widget creation and widget destruction methods are supplied. In addition, if the class contains members that are themselves (pointers to) classes, a constructor and destructor method is generated to create and destroy these members. Note that the widgets are not created at the time of the class instance but by an explicit call to the widget creation function. Similarly, destroying the class instance does not destroy the widgets.
To designate a widget as a C++ class, select the "Code generation" page of the Core resource panel and select "C++/Java class" from the "Structure" option menu. Note that if you designate a widget as a C++ class, then generate C, the widget is treated as a data structure.
This section describes C++ classes. For information on Java classes in X-Designer, see Chapter 10.
The C++ code generated from this example is shown below, simplified for clarity:
If a widget is designated a C++ class and C code is generated, the widget is treated as if it were a data structure.
By default, the generated class is derived from one of the supplied X-Designer base classes. You can override this by specifying the base class in the field below the C++ Access option menu. The X-Designer base classes supplied with the release provide minimal support, sufficient for the generated code to execute correctly. You can modify and extend those classes to provide reusable methods that suit your approach to GUI development.
Descendant widgets appear as protected members of the class if they are named, or if they are themselves data structures or C++ classes. It is therefore important to name the C++ class widget itself and any of its descendants that you want to access as class members. You can alter the default access control by selecting the required level (Public, Protected, or Private) from the C++ Access option menu.
Using an unnamed widget for the C++ class widget itself does not cause an immediate error. However, this is not recommended as numbers assigned by X-Designer can change when you edit your hierarchy.
The X toolkit functions which invoke callback functions expect a callback function in the following form:
void my_callback (Widget, XtPointer, XtPointer)
An ordinary member function is not suitable as a callback function because the C++ compiler passes it an extra first parameter--the this pointer--that lets it find the instance data for the object. If you use an ordinary member function as a callback function, the member function interprets the widget pointer as the instance data pointer and does not work as expected.
X-Designer uses a common technique to work around this. A static member function (which does not expect a this pointer) is declared and used as the callback function:
static void my_callback (Widget, XtPointer client_data, XtPointer call_data)
The client data parameter is used to pass in a pointer to the instance. The static member function merely calls an ordinary non-static member function using that instance pointer and passes on the widget and call data parameters. The non-static member function has the following form:
virtual void my_callback (Widget, XtPointer call_data)
X-Designer generates both function declarations, all the code for the static callback function and a stub for the regular member function which is written by you. Note, because this function is declared as virtual, you can override it in a derived class to modify the behavior. For a discussion of this technique, see Object-Oriented Programming with C++ and OSF/Motif by Douglas Young.
When you add a callback method, X-Designer also adds a declaration for the method (if it has not already been declared). Pressing the "Methods" button in the Callbacks dialog shows you a list of the methods declared in the enclosing class of the currently selected widget.
By default, X-Designer declares methods as not pure virtual and with public access. If these attributes are not as you intended, use the Method Declarations dialog to change them. See Method Declarations for details.
If you add a callback as a method, for convenience X-Designer adds the declaration of the method in the enclosing class for that widget. You can view, add and remove method declarations by selecting the widget which is the enclosing class and selecting "Method declarations" from the "Widget" menu. The Method Declarations dialog is shown in FIGURE 8-3.
To find which widget is the enclosing class, use "Structure colors" from the "View" menu, as described in , and select the nearest ancestor of the widget for which you have added a method. Of course, this would be the same widget if it is defined as a C++ class.
By default, methods added by X-Designer have public access. You can control the access for individual callback methods using the "Access" option menu in the Method Declarations dialog.
You can set the "Pure virtual" toggle to declare the non-static member function as pure virtual. For example, if you set this toggle for a callback method OnNew() in a menubar class, X-Designer would declare the method as:
Because the function is pure virtual, you do not have to provide an implementation of menubar_c::OnNew() and menubar_c becomes an abstract class. That is, you cannot create an instance of menubar_c but only use it as a base class for others.
By default, methods added by X-Designer are not pure virtual.
When you remove a callback method from a widget you are only removing the use of the method (the call to it). When you add a method callback in X-Designer, a declaration of the method is automatically added for you. If you want to remove this declaration as well you must remove it from the method declarations list of the widget which is the enclosing class. See Method Declarations for more information on how to do this and for information on the declaration added by X-Designer.
When a callback method is added, the method is declared in the enclosing class, as described above. If you change the structure of this widget (the enclosing class) so that it is no longer a class, the method becomes invalid. To help you when this happens, X-Designer displays the Invalidated Methods dialog, shown in FIGURE 8-4.
This dialog is modal--you can not continue working on your design until it is closed. It is only ever displayed when you change a widget's structure in such a way that method declarations are made invalid.
The Widget list on the left shows all the widgets with methods which are invalidated by changing the structure. When you select a widget any invalidated methods are listed on the right. For each selected method, this dialog shows you the class in which it is currently declared and suggests a new class for the declaration of your method. The "Proposed Class" is always the nearest ancestor class. If there is no other suitable class, this dialog serves as a warning that the method will become a function.
Pressing "Declare" changes the declaration of the selected method to the "Proposed Class". Pressing "Declare All" changes each invalidated method to its respective "Proposed Class".
You can add additional data or function members to a C++ class using the "Code preludes" dialog. Select "Public methods", "Protected methods", or "Private methods" and type your declarations into the text area (or into the code if you are editing in place). C++ code preludes are generated into the class declaration, both in the primary module and in the Externs file.
To add a function to a class it is often better to write a new class derived from the generated class. The logical gap between the subclass and generated base class can be used to add members and provide implementations for virtual functions.
By default, X-Designer derives the name of a C++ class from the variable name of the root widget and so the class for the widget menubar is menubar_c:
When X-Designer generates code to create an instance of the class, it uses the same name:
menubar = new menubar_c;
You can change the default behavior so that X-Designer declares the generated class under one name and creates the instance under another. For example:
menubar = new mymenubar_c;
To make this change, use the "Instantiate as" field on the Code Generation page of the Core resource panel.
By default, X-Designer derives a generated class from a base class appropriate to the type of the root widget. For example, a class with a MenuBar at the root of its widget hierarchy is derived from xd_XmMenuBar_c. The name of the base class can be changed in the Core resource panel.
The X-Designer distribution contains a sample implementation of a set of base classes. These can be used as they stand or modified to add extra functionality appropriate to a particular application area.
A Makefile is included to build the sample base classes. X-Designer makes two assumptions about the base classes:
There is a data member _xd_rootwidget of type Widget.
There is an accessor function xd_rootwidget() that returns the value of _xd_rootwidget to be retrieved.
These assumptions, together with a few items of basic class restrictions, are encapsulated in the class xd_base_c:
X-Designer places no other constraints on the base classes used. In other words, any set of base classes can be used provided that they are derived from xd_base_c (or another base class that satisfies X-Designer's assumptions).
Note that actual parameters for the base class constructor can be supplied with the class name. If parameters are supplied (if the base class string contains a '()', the class is forced to have a constructor and the parameter string is passed to the base class. For example, setting the "Base class" string to mymenubar_c ("Hello World") for the widget menubar will cause X-Designer to generate:
The Children Only structure option lets you designate one widget (the Children Only widget) as a container structure for another structure. Children Only widgets provide context for their descendants in the hierarchy, but no code is generated for them. Consider the following example:
When you generate code from the design shown in FIGURE 8-5, X-Designer produces code for the pulldown menu structure only. This feature lets you generate fragments of the design that can be controlled by your application program.
Note - If you specify a widget as "children only", code is only generated for children which are structured or named. Therefore, if all you have underneath a "children only" widget is unstructured and unnamed widgets, then all you will see in the code is Widget declarations.
When you are in Microsoft Windows mode, you cannot make the child of a shell a C++ class. To overcome this, so that you can create a hierarchy with a "children only" shell, add a "dummy" container (a rowcolumn or form widget) beneath the shell and then make the container beneath that a C++ class. This would also be useful for creating definitions in Microsoft Windows mode, where the root widget must be structured but the child of the shell cannot be.
When generating UIL for a design that contains structures of some kind, the approach is basically similar to that for C and C++. Independent hierarchies are generated into the UIL file and separate creation functions are generated into the code file. The creation function fetches the appropriate widgets from the UIL hierarchy and fills in the data structure fields as appropriate.
Widgets are normally declared locally in the enclosing creation function unless they are structured in some way, or named. In this case they are declared in the enclosing structure if there is one, or as global variables. This default behavior can be modified by setting the storage class of a widget in the Core resource panel. Setting the storage class to Local forces a widget that would otherwise be declared globally or within a structure to be local to the creation function. Setting the storage class to Global forces an unnamed widget or a named element of a structure to be global. Global status is especially useful for widget-type resources and links as discussed in Unreachable Widgets. The Static option is similar to Global but the declaration is static to the module.
There is no way to force an unnamed widget into a data structure. Unnamed children of a data structure widget are created and managed locally to the data structure's creation procedure.
When you use the structured code generation in conjunction with widget-type resources such as XmNdefaultButton for a BulletinBoard, you could specify designs that reference widgets that are not in scope. These are considered unreachable widgets. X-Designer attempts to detect these cases and warns you at code generation time. Also, if you use unreachable widgets in conjunction with Children Only structures or dynamic run-time creation of hierarchies, unexpected failures may result.
An unreachable widget is illustrated in FIGURE 8-6. b1 must be available to the Form's creation function so that it can be used as the default button argument. However, since b1 is local to the button_box function, it is not in scope in the Form's creation function. X-Designer detects this situation and displays the following warning at code generation time.
Code is still generated but it may not compile or run as expected. The simplest solution to this is to force the appropriate widget to be global by using the Storage Class option.
Once a hierarchy of widgets has been encapsulated as a structure (either a C++ class or a C structure), you can re-use it in other designs by turning it into a definition. A definition is a reusable hierarchy of widgets which is added to the X-Designer widget palette. Selecting a definition from the palette creates an instance of the definition in the design. This instance can be further modified and in turn be made into a definition.
A widget hierarchy can become a definition provided that:
Designating a definition requires that the design file containing the widget is saved and the widget marked in it as being a definition. To mark the widget as a definition use the Definition toggle in the Widget menu. Creating a definition freezes the widgets within it. Their resource panels are disabled and you cannot add widgets or change widget names. You can edit the widgets that make up a definition only by temporarily removing the definition status. This should be done with caution to avoid conflicts with designs that use the definition. For details, see Modifying a Definition.
To make the definition available for use in other designs X-Designer needs an external reference to it. This is provided by means of a definitions file which is edited using the Edit Definitions dialog.
The "Define" button in the Palette Menu is a quick way of adding a new definition. It designates the currently selected widget as a definition, saves the design and adds the definition to the palette. The header filename for the definition is taken from the type declarations filename in the code generation dialog. No icon is used.
The definitions file is read by X-Designer to establish the set of definitions which are to appear on the palette. The definitions filename is specified by setting the definitionsFileName resource. The default value is $HOME/.xddefinitionsrc.
If you need to work on multiple projects, each of which uses a different set of definitions, you can change the definitions file by setting the resource. For example:
The value of this resource can include environment variables:
To change to the new setting, exit and restart X-Designer.
To modify the definitions file, use the Edit Definitions button in the Palette menu.
This displays the dialog shown in FIGURE 8-8.
You can use this dialog to add a new definition, delete a definition, or edit an existing definition. To add a definition, you must supply:
You can also specify:
Attributes not set at creation time can be set later. For example, you can test and debug a definition before designing its icon.
You can use the "Prime" button to fill in several of the fields for the currently selected widget.
If a definition is specified with a relative file name (a name that does not start with /), X-Designer adds the base directory to the front of the file name. If a base directory is not specified, the directory that contains the definitions design file is used.
To specify a base directory, display the Edit Definitions dialog, click on "Base Directory", select a new directory and click on "Apply". The new base directory is saved in your definitions file and is immediately used in the current session of X-Designer. The base directory cannot be changed if the current design contains instances of existing definitions.
Widgets in the definition are frozen. You cannot add or delete widgets, rename them, set constraints on them in the layout editor, or reset resources. To modify a definition, you must temporarily undefine it. When you need to modify a definition, use the following steps:
1. Open the save file that contains the definition.
2. Select the root widget of the definition.
3. Pull down the Widget Menu and turn off the "Definition" toggle.
Turning off the toggle unfreezes the widgets in the definition so you can make any necessary changes. After making your edits:
4. Select the root widget and set the "Definition" toggle on again.
5. Regenerate the code file and externs file.
6. Save the design.
Changing a definition affects every design file that uses it. Each time you open a design that uses a definition, X-Designer also opens the file that contains the definition and merges information from the two files. If the definition has been modified, X-Designer tries to reconcile the new definition with the design that uses the old version of it.
If any changes cannot be reconciled, X-Designer displays an error message and saves any irreconcilable parts of the design in temporary X-Designer clipboard files. At this stage there are several ways to proceed:
To minimize the risk of incompatibilities:
To create an instance of a definition simply click on the appropriate button in the palette. The instance is shown with a colored background.
Definitions and instances must be in separate designs. Although you can see them in the same design within X-Designer, the generated code does not compile unless they are separate.
Definitions are grouped together on the widget palette according to their family. An option menu above the definitions on the widget palette allows you to change which family is currently displayed. See Editing the Definitions File for details on specifying a definition's family.
Creating an instance of a definition corresponds to creating an instance of the structure (either a C structure or a C++ class). You can modify an instance after you have created it provided that the modifications can be reflected in the generated code. For example, you can set resources on widgets or add children to widgets only if they are accessible (i.e. if they are named and, for C++, they have an appropriate access mode). You cannot remove widgets or change their names. The root widget is an exception. Because the root widget of the instance is always accessible (through the member function xd_rootwidget()), it can always be modified.
It is frequently useful to create a new structure that is derived from the definition. To do this simply set the Structure option on the Code generation page of the Core resources dialog. The derived structure can only be set to the same value as the definition, e.g. it is not possible to derive a C++ class from a C structure.
Inherited methods from definitions can be overridden in the instance so that the instance has different behavior from that specified in the definition.
To compile code generated from a design containing an instance of a definition, you need to link in the definition code too. There are two ways to do this:
These are explained separately below.
To link a library containing the definition code in with the instance code, first compile the code for the definition into a library. Usually, on UNIX and using C or C++, this is done by typing the following lines into a terminal window:
You then need to edit your Makefile for the code containing the instance so that:
Another way of compiling the instance of a definition involves generating the definition, the instance of it and a corresponding Makefile into the same directory. You can tell X-Designer to configure the Makefile so that the definition and instance can both be compiled into the same application. The following instructions show you how to do this.
1. Open the design containing the instance first.
2. Make sure you are generating a "Main program"
3. Set the "New" and "Template" toggles in the Makefile Options dialog.
4. Generate all the required files.
5. Open the definition design.
6. Unset the "Main Program" generate toggle.
7. Unset the "New" toggle in the Makefile options dialog, leaving the "Template" toggle on.
8. In the Code Options dialog, set "Links" to None (you have already generated the links functions, doing so twice would result in a linker error).
9. Generate the code, externs and Makefile (and resources if required).
10. Type: make at the command prompt.
Doing the above will give you one application containing your instance.
Resource values for widgets that are components of definitions can be either hard-coded or specified in resource files.
When you specify a resource file for a definition, X-Designer #includes that file in the resource file for any design that contains an instance of the definition. The Xlib mechanisms that read the resource file interpret this directive and use it to find the resource file for the definition.
To record information about a definition and communicate with other developers who are using it, you can provide online help for definitions. The online help is accessed in the X-Designer interface by using the <Tab> and arrow keys to get to the icon or button for the definition, then pressing the <osfHelp> key (usually <F1>).
Help files are stored in subdirectories of the X-Designer help directory. The help directory is determined by the helpDir resource. By default, it is
where XDROOT is the path to the X-Designer installation root directory and LANG is the name of your locale (default C).
Text help documents are in HTML format. The name of the file is formed by concatenating the document name and marker name. These are joined using the value of the XDesigner.userHelpCatString resource. By default this resource is set to "." The file is then given a ".html" suffix. X-Designer looks for this file in the UserDocs subdirectory of the X-Designer help directory.