#include
directives
in the source code must be found. If the files are located in the current directory
there's no problem. Many files in other directories are also found by the compiler
without problems: this is true of all files in /usr/include
and /usr/local/include
.
If you have placed an include file in some unknown (to the compiler) directory, you
can direct the compiler to look in that directory with an -I
-directive. For exemple,
because the include files for X reside in /usr/openwin/include
you need the directive
-I/usr/openwin/include
when you compile X programs; the files actually reside in
/usr/openwin/include/X11
but by convention the X11 part is included in the include
directive in the source code (you write #include <X11/Xlib.h>
). Example:
cc -I/usr/openwin/include drawX.c -cwhere
-c
instructs g++
to execute only the compiler step, not the linking. Other include file directories - already defined in the Makefile stubs - used in the exercises include:
/usr/dt/include // Include files for Motif /home/grafik/SRGP // Include files for SRGP /home/grafik/SPHIGS // Include files for SPHIGS /home/grafik/X/v/includex // Include files for VSRGP header files are references internally by SPHIGS header files.
libX11.a
and libX11.so
, for instance. Code from static libraries that is
referenced by your object modules is loaded with the executable at link-time and
becomes part of the executable file. Static libraries have the extension ".a
"
Code from dynamic libraries is connected to your program at run-time (or at load-time just before
run-time, rather). Since this is done every time you run your program, the library files must
exist when you run your program. Dynamic libraries have the extension ".so
".
Some libraries, like the C (and C++) standard library, are automatically found by the linker. But most
libraries must be mentioned explicitly on the command line: enter the name of the library
after an -l
-directive. If your program uses math functions
(declared in math.h
) you can link it with the following command
g++ myprogram.o -lmThe linker automatically searches a few directories (
/usr/lib
and /usr/local/lib
),
but if your library is placed somewhere else you have to tell the linker to search that directory. For this,
you use an -L
-directive:
cc drawX.o -L/usr/openwin/lib -o drawX -lX11where -o is used to change the default output name from a.out to drawX. The procedure as described so far works for static libraries. If you use dynamic libraries (as
libX11.so
really is) you also need to tell
the linker where to look for them at run-time (or, more precisely, load-time). This you do with a
-R
-directive:
cc drawX.o -L/usr/openwin/lib -R/usr/openwin/lib -o drawX -lX11If you use the first version, the one without
-R
, your program will link without
problems, but when you try to run it you will get an error message:
ld.so.1: myapplication: fatal: libX11.so.4: can't open file: errno=2This means that the loader did not find the library when attempting to load the program in order to run it. Unless some evil wrong-doer has removed the library from its designated place after you linked your program, the message indicates that you forgot the
-R
-directive.
Static libraries need no -R
-directive, since relevant code is already part of the executable.
Often, of course, compiling and linking is merged into one single command with all the necessary options:
cc drawX.c -I/usr/openwin/include -L/usr/openwin/lib \ -R/usr/openwin/lib -o drawX -lX11Important (for this course) library file directories and libraries:
/home/grafik/SRGP/lib: libsrgp.a - The SRGP library /home/grafik/SPHIGS/lib: libsphigs.a - The SPHIGS library /home/grafik/X/v/lib/sun4: libV.a - The V library /usr/openwin/lib: libX11.so - Xlib functions and procedures libXt.so - The Xt toolkit libXmu.so - Miscellaneous X utility functions libXext.so - Extensions, supports non-rectangular windows, etc. libXaw.so - The Athena widgets libxview.so - The XView toolkit /usr/dt/lib: libXm.so - The Motif toolkit /opt/tdb/lib: libtk.a - The Tk toolkit libtcl.a - The Tcl toolkitNote that the dynamic libraries exist in static versions as well. This is necessary if one develops X applications on one machine but wants it to be executable on another one without the libraries installed.
Libary paths may also be defined by setting the environment variable LD_LIBRARY_PATH
. Paths included
there will be searched by the linker automatically. I think LD_LIBRARY_PATH
only specifies
-R
-paths, at least that is what the name suggests to me. However, empirical
studies (oh, these euphemisms for a single test) show that it seems to specify -L
-paths as well.
In complete disregard of good programming practice is seems the file "sphigs.h" does not support C++. The remedy here is to tell the compiler that functions declared here indeed are C functions. You do this with the extern directive:
extern "C" { #include "sphigs.h" }Don't do this if you write your program in C!
Skip the section "Makefiles in ~grafik/ExamplesSPHIGS" if you plan to use /home/grafik/HT96/E2/Makefile.
Here's how you compile and link your program: First you copy the appropriate Makefile (Makefile or MakeC++) to your directory (rename MakeC++ and give it the default name Makefile). Then, in order to compile your program (xxxx.c if you use c, xxxx.cc if you use C++) enter the command:
make PROG=xxxxIf everything is OK, the Makefile then executes
gcc -fpcc-struct-return -I/home/grafik/SRGP/include -I/home/grafik/SPHIGS/include -I/usr/openwin/include xxxx*.c -L/home/grafik/SRGP/lib -L/home/grafik/SPHIGS/lib -lsphigs -lsrgp -lX11 -lm -o xxxxor
g++ -fpcc-struct-return -I/home/grafik/SRGP/include -I/home/grafik/SPHIGS/include -I/usr/openwin/include xxxx*.cc -L/home/grafik/SRGP/lib -L/home/grafik/SPHIGS/lib -lsphigs -lsrgp -lX11 -lm -o xxxx
Note that you determine how many colours and how many shades of each colour you have available in SPH_begin().
The structure of a V application is quite simple. I will briefly describe the most important classes and how they work together. Parts of the text here is from the web page cited above ( (c) Bruce Wampler, 1995, 1996). Other sources of information include pages 24 and 25 of the "Introduction to the X Window System" and the tutorial program in ~grafik/X/v/tutor (aka "Tutor.Listing" in the list of X example programs that was handed out 14/11). In what follows, I assume that you are familiar with C++, including classes, inheritance, and polymorphism. Please note that descriptions of some methods, constructors, etc. are simplified: defaulted parameters removed, etc.
vApp
. This is your
main application class. The global variable theApp
(type *vApp
)
is automagically set to point to this instance when it is created. This instance
must be created before main is executed: thus a static global object is necessary. No windows
will be created at this point.
main
, your main should be AppMain()
.
AppMain()
must at least create a window: use vApp
's method NewAppWin()
.
vApp
and override methods you need to change.
vApp::NewAppWin()
. Command windows in V are not very
flexible: a menu bar with pulldown menus, a command bar, a canvas, and a status bar.
vAppWinInfo
may optionally be
connected to every command window. Use this feature to connect your application model.
vApp( char *name)
: constructor
vWindow* NewAppWin(vWindow *, char *, int h, int w, vAppWinInfo *)
: create a new command
window with canvas size h*w pixels. Your overloaded NewAppWin
must call
vApp:NewAppWin()
after doing its job which normally includes creating a command
window. You may also create a instance of your derived vAppWinInfo
class. Either
or both of the command window and vAppWinInfo
references may be created outside NewAppWin
.
void AppCommand(vWindow *, ItemVal)
: commands not processed by windows arrive here
void KeyIn(vWinow *, vKey, uint)
: keys not processed by windows arrive here
vAppWinInfo* getAppWinInfo( vWindow *win)
: return the application information pointer
for a window.
void CloseAppWin( vWindow *)
: closes the window
void Exit()
: exit from the V application
vAppWinInfo
instance is available via vApp::getAppWinInfo()
.
vAppWinInfo( char *name, void *ptr=0)
: constructor, the idea here is that
you provide a pointer to your application data as the second parameter. Of course, since
you derive a version of the class there are many other ways to include application data.
virtual void* getPtr()
: returns the pointer given to the constructor.
vCmdWindow
is the main control window. It contains menus,
canvases, etc. You should derive your window class from vCmdWindow
.
vCmdWindow(char *name)
: constructor
vCmdWindow(char *name, int h, int w)
: constructor, h*w is canvas size.
virtual void AddPane(vPane *)
: add a pane to a window. Normally you first
create the panes you need and use AddPane to display them.
virtal void ShowWindow()
: display the command window.
virtual void KeyIn( vKey, uint shift)
: invoked when a key is pressed while the
window has input focus.
virtual void MenuCommand(ItemVal)
: called when a menu command is selected. You
don't have to override this method unless you want to distinguish between menu and command events.
The default behaviour is to pass the command along to WindowCommand.
virtual void WindowCommand(ItemVal id, ItemVal val, CmdType type)
: called when a
command is selected. Id is the id
of the command, val
the value,
and type
the type. What this really means is explained in the section about
command objects in dialogs below. This method is usually a switch statement where each case
corresponds to one of the menu or command events.
virtual void CloseWin()
: close the window
vPane
class serves as a base class for various pane objects (menus, commands, and
canvases). vMenuPane
, vCommandPane
,and vCanvasPane
are derived from vPane
.
vMenuPane
consists of vMenu
items. Since vMenu
may contain
lists of other vMenu
items as submenus the structure can be nested - or recursive.
The leftmost choice in any plldown menu is usually File (a vMenu
item)
with a list of operations such as Open, Save, and Exit
(an array of vMenu
items).
Static arrays of vMenu
items declared in the implementation file of your derivation
of vCmdWindow
are usually used. The vMenu
initializer:
vMenu xx = { char *name, ItemVal menuId, int sensitive, int checked, char *keyLabel, vKey accel, vMenu *SubMenu };where name is the label of the button, menuId is a user-assigned unique id sent to the WindowCommand method, sensitive should be one of
notSens
or isSens
,
checked should be one of isChk, notChk, or notUsed (for the top-level menu bar), keyLabel is
a label for an accelerator key, key is an accelerator key, and SubMenu is either noSub (for
the leaves in the menu hierarchy or an address to a submenu. There are a number of predefined
ItemVal values beginning with M_ you can use.
vMenu
initializations looks like:const ItemVal m_Special= 100; vMenu mysub = { { "Special", m_Special, isSens, notCkh, noKeyLbl, noKey, noSub}, { "Exit", M_Exit, isSens, notCkh, noKeyLbl, noKey, noSub} } vMenu StandardMenu[] = { { "File", M_File, isSens, notUsed, notUsed, noKey, &mysub[0]}, { "Edit", M_Edit, isSens, notUsed, notUsed, noKey, noSub} }The constructor for vMenuPane takes an address of a vMenu hierarchy as parameter:
vMenuPane( vMenu *)
CommandObjects
s,
which will be described in the section about dialogs.Constructor:
vCommandPane( CommandObject *)
vCmdWindows
's constructor with new or as they are needed - either
dynamically with new or statically by simply declaring them.
The class vDialog
for modeless diaglogs:
vDialog( vBaseWindow* parent, int isModal=0, char *title="")
: constructor, the
parent
is ususally a vCmdWindow
.
vDialog( vApp* parent, int isModal=0, char *title="")
: constructor
void AddDialogCommands( CommandObject *)
: add a list of commands
to a dialog. Called after the dialog object has been created.
virtual void ShowDialog( char *msg)
: display the dialog
virtual void DialogCommand(ItemVal id, ItemVal val, CmdType type)
: Invoked when the user
selects some command item of the dialog. You normally override this method but you can call
vDialog::DialogCommand()
for default actions for cancel and ok buttons. Like WindowCommand()
,
DialogCommand()
if usually a switch statement.
virtual void GetTextIn( ItemVal Id, char *str, int maxlen)
: retrieve text entered in a C_TextIn (see
CommandObject
) item.
virtual int GetValue(ItemVal Id)
: retrieve the value of a command item.
virtual void CloseDialog()
: close the dialog.
vModalDialog
for modal (meaning that all other interaction is locked
until the user interacts and dismisses the dialog window) dialogs (vModalDialog
is derived from
vDialog
so the methods of that class are inherited (or overridden in some cases):
vModalDialog( vBaseWindow* parent, char *title)
: constructor
vModalDialog( vApp* parent, char *title)
: constructor
virtual ItemVal ShowModalDialog( char *msg, ItemVal&retval)
: shows the dialog and does not
return until the dialog is closed.
CommandObject
and DialogCmd
are different names for the
same thing. DialogCmd
is the older name, available only for backwards compatibility.Command objects are building blocks used in dialogs and command panes. Each dialog is made up of an array of command objects. The command object structure:
typedef struct CommandObject { CmdType cmdType; // what kind of item is this ItemVal cmdId; // unique id for the item ItemVal retVal; // value returned when picked (might be < 0) char* title; // string (for C_Label, C_Text, and C_TextIn) void* itemList; // a list of stuff to use for the cmd (for lists) CmdAttribute attrs; // bit map of attributes int Sensitive; // if item is sensitive or not ItemVal cFrame; // Frame used for an item (or NoFrame) ItemVal cRightOf; // Item placed left of this id ItemVal cBelow; // Item placed below this one int size; // Used for sizes } CommandObject;There are many different kines of command items (
CmdType
). The most important ones
(for this exercise) are:
C_Button - self-explanatory, I should hope
C_EndOfList - must be entered as the last item in a CommandObject
array.
C_Frame - a line around a group of related items.
C_Label - a labelm usually a static text string
C_Text - boxed (framed) text, for information that may change
C_TextIn - user-editable text
CommandObject
s are used in the two types of dialogs and in vCommandPane
s.
vFileSelect
is a specialization of vModalDialog
for selecting filenames.
It does not open or close a file, it merely constructs a legal filename for use in opening a file.
vFileSelect ( vBaseWindow *win)
: constructor
FileSelect( char* prompt, char *filename, int maxlen, char **filter, int& filterindex)
:
pops up the modal dialog. When the user dismisses the dialog (full path of) the selected filenmame is
returned in filename. FileSelect returns True if OK was selected, False if Cancel was selected.
vYNReplyDialog
is another specialization of a modal dialog
that can be used for confirmation of user actions (such as exiting without
saving data):
vYNReplyDialog( vBaseWindow *win)
: constructor
int AskYN( char *prompt)
: pop up the dialog, returns 1 if
the user selected Yes, 0 for No, and -1 for Cancel.
vTextCanvasPane
since that is the variety you will be using in
the exercise.
vCanvasPane()
: constructor, size is determined by vCmdWindow
parameters.
Clear()
: clear the canvas
DrawText(const char *)
: draw text.
void UpdateIt( Widget w, XtPointer client, XtPointer call) { Window win = XtWindow( w ); XmDrawingAreaCallbackStruct *dacs = (XmDrawingAreaCallbackStruct *) call; XButtonEvent *ev = (XButtonEvent *) dacs->event; if (ev->type != ButtonPress) return; /*ignore releases*/ /* do the drawing */ }
One way of accomplishing this is through global variables - purists may raise objections, but a global pointer to an application model is not such a bad idea (that's my view, anyway). In C++, for example, the application may be modelled with a class, and the various callback functions may then send messages to a globally accessible instance of that class (call member functions, to put it bluntly) in response to user actions.
In Xt/Motif, there are two ways to communicate between callback functions and between the application model and callback functions
client_data
(the second parameter)
// Declared in a header file: typedef struct { int a; char b[10]; } TestStruct; // Created in main (or another function but we would need // dynamic allocation in that case). TestStruct myStruct = { 1, "kalle" }; // Widget creation and setup of callback Widget testw = XtVaCreateManagedWidget("testw", xmPushButtonWidgetClass, bb, XmNx, 10, XmNy, 10, XMNwidth, 50, XmNheight, 30, NULL ); XtAddCallback ( testw, XmNactivateCallback, TestIt, (XtPointer) &myStruct); // The callback function void TestIt( Widget w, XtPointer client, XtPointer call) { TestStruct *ts = (TestStruct *) client; printf("%d %s\n", ts -> a, ts -> b ); // will print ' 1 kalle' exit(0); }
In this example, I create a button and a text field and assume that, in order to process button events correctly, the button callback must be able to retrieve the text in the text field. I add that capability to the button by setting the userData resource to point at the text field widget.
Widget tf, pb; tf = XtVaCreateManagedWidget("text", xmTextFieldWidgetClass, parent, XmNx, 10, XmNy, 10, XmNwidth, 180, XmNheight, 20, XmNvalue, "test text", NULL); pb = XtVaCreateManagedWidget("PushButtonText", xmPushButtonWidgetClass, parent, XmNx, 200, XmNy, 10, XmNwidth, 80, XmNheight, 20, XmNuserData, tf; NULL); // // Code with access to pb (callback functions, for example) // may access the text in tf in the following manner // (we assume a callback function for pb, so w is pb): // void TestCallback( Widget w, XtPointer client, XtPointer call) { Widget textf; char *txt; XtVaGetValues( w, XmNuserData, &textf, NULL ); XtVaGetValues( textf, XmNvalue, &txt, NULL ); printf("Text field value is '%s'\n", txt); free ( txt ); // should be freed after use !!! }
In X, colours are always represented by the abstract data type
Pixel
.
dpy
is a pointer to your Display
struct,
easily retrieved by calling XtDisplay(myWidget)
, win
is the X window (from XtWindow(myWidget)
).
XGCValues gcv; gcv.foreground = BlackPixel( dpy, DefaultScreen(dpy) ); gcv.background = WhitePixel( dpy, DefaultScreen(dpy) ); gc = XCreateGC( dpy, win, GCForeground | GCBackground , &gcv);
BlackPixel
and WhitePixel
are macros that return the pixel value
of black and white, respectively - the only two colours guaranteed to exist on
every display.
An alternative to XCreateGC
from Xlib is XtGetGC
from Xt:
gc = XtGetGC( w, GCForeground | GCBackground , &gcv);where
w
is a widget. Because XtGetGC
and XCreateGC
are very
similar, very little is gained by using the higher-level library in
this case.
XColor cbgr, cfgr; XGCValues gcv; cbgr.flags = cfgr.flags = DoRed | DoGreen | DoBlue; cbgr.red = 65535; cbgr.green = cbgr.blue = 0; cfgr.blue = 65535; cfgr.green = cfgr.red = 0; XAllocColor( dpy, DefaultColormap(dpy, DefaultScreen(dpy)), &cbgr ); XAllocColor( dpy, DefaultColormap(dpy, DefaultScreen(dpy)), &cfgr ); gcv.background = cbgr.pixel; gcv.foreground = cfgr.pixel; gc = XCreateGC( dpy, win, GCForeground | GCBackground , &gcv);Note that 16 bits per primary (red, green, and blue) are used. As an alternative to specifying the colours by digits, there is also a function for allocation of named colours ("red", "green"). Example:
XAllocNamedColor( dpy, DefaultColormap(dpy, DefaultScreen(dpy)), "yellow", &cfgr, &exact );The fourth and fifth parameters are both output parameters of type
XColor
, cfgr
in the example contains the colour
and pixel of the cell actually allocated while the last parameter contains
the values (in the database) of the named colour. The two may differ if the
function failed to allocate the exact colour you requested.
Pixel fgr, bgr; XGCValues gcv; XtVaGetValues( w, XmNforeground, &fgr, XmNbackground, &bgr, NULL); gcv.background = bgr; gcv.foreground = fgr; gc = XtGetGC( w, GCForeground | GCBackground , &gcv);where
w
once more is the widget we're drawing in. Or,
simplifying and hiding the data type Pixel
:
XGCValues gcv; XtVaGetValues( w, XmNforeground, &(gcv.foreground), XmNbackground, &(gcv.background), NULL); gc = XtGetGC( w, GCForeground | GCBackground , &gcv);Here is more information about resource setting in the C code; it applies to all
Xt
functions that manipulate resources, not only
XtVaGetValues
. As you may have guessed, the tiny font used
here indicates "only for those interested in Xt peculiarities".
There are two programmer interfaces (two sets of Xt functions)
for resource manipulation: one older (the ArgList
interface)
and one newer (which uses the varargs feature of C, the XtVa
routines). Here I will only describe the varargs interface: it is less
error-prone, easier to program, and has some features that are missing
from the ArgList
variety (but using the varargs functions is
slighlty less efficient).
Resources are name/value pairs. A resource name is a symbolic constant defined
in <Xm/Xm.h>
(or <X11/StringDefs.h>
in
some cases). The constants are strings; the rationale for using symbolic
constants instead of strings is that the compiler will catch a misspelled
constant but not a "misspelled" string. Resource values differ in type:
some are single int's, others are complex structures. A rule of thumb is
that if the value is no bigger than a longword, the value itself should be
sent, if it is longer a pointer to the value should be used - the exact
definition of each type can be found in Motif reference guides, for instance.
The above applies for setting resources, XtVaGetValues
needs
pointers for small values, and pointers to pointers for large values.
Motif widgets return a copy to the resource value, not a pointer to the
value itself, so the programmer may free structures after use (this is
not entirely true: due to a bug in Motif some resources should not
be freed). Athena widgets, on the other hand, return pointers to internal
data, so strings, for example, queried from Athena widgets should not
be manipulated or freed.
Note that there is no way for Motif to typecheck the resources. It is the responsibility of the programmer to make sure that correct types are used.
Lists of resources for each widget are available in Motif reference guides and on-line (man).
Skip this section unless you have a great deal of interest in what's
inside a widget (I would have used the tiny font here too, if only
I could bring myself to "degrade" a whole section of text).
This way of getting resources is intended only
for widget programmers. The code here accesses widget records that
are really private - in order to use them the file DrawingAP.h
(the capital P means private) must be included.
But the code actually displays the object-oriented philosophy of X in the way the core part defines the background pixel (which is thus "inherited" by all other widgets if you recall the figure on page 13 of "Introduction to the X Window System"):
XGCValues gcv; XmDrawingAreaRec *daw = (XmDrawingAreaRec*) w; gcv.foreground = daw -> manager . foreground; gcv.background = daw -> core . background_pixel; gc = XtGetGC( w, GCForeground | GCBackground , &gcv);Of course the claim that this is the most "sophisticated" way of setting resources is highly questionable: retrieving data that are public only because a program package isn't implemented in an OO language is well down in sophistication (but it may elevate me to hitherto unknown heights in the eyes of hackers, perhaps).