Hi all....
I was clearing up some old files and came across this text that I put together a million years ago but never 'published'. I suppose this would be a fair read for somebody about to dive into C++/Motif and ViewKit for the first time. Please download the indispensible "ViewKit Programmers Guide" and the two volumes of Motif Reference if you want to make any headway beyond this "Hello World".
Cheers,
Jimmer
----
A "Hello World" for IRIX using Motif and ViewKit.
If you want to write software that looks like it belongs on IRIX, you need Motif and Motif means the complexities of Xt/Xlib. Motif is a crusty old dog of a library containing C-language functions to create and manage a graphical user interface on the X11 display server. It's built on top of another ancient library called
Xt, which in turn is built on the low-level core X11 library
Xlib. Writing software for this stack of inter-dependent libraries can be complicated.
ViewKit is a library developed by the nice folks at SGI to hide much of the Motif/Xt/Xlib complexity. At the same time it adds some neat Object Oriented Programming (OOP) concepts to make GUI development with Motif significantly less painful. ViewKit was written in C++, the OOP version of C, so if we want to use the library we need to use C++ as our programming language.
As you probably know, C and C++ code needs to be run through a compiler to get an executable program. We could use GCC or MIPSpro, but due to a technical peculiarity of C++ we need to use the same compiler ViewKit was built with. As we're using the IRIX ViewKit which was built with MIPSpro, we'll be using MIPSpro as our C++ compiler.
If you don't have access to MIPSpro you can try and use the
ViewKlass clone of ViewKit from viewklass.sourceforge.net. It builds fine using GCC and thus most likely you'll be able to use GCC/ViewKlass for the tutorial. If there's much interest in using ViewKlass and GCC I might write some instructions on how to compile and install it.
Finally, you'll need a text editor. I tend to use nano to edit configuration files and nedit 5.5 for markup and programming. I have no idea why I dont use One Editor To Rule Them All.
1. Text-based "Hello World" in C++.
First let's just do the formalities of a text-based C++ "Hello World". Fire up your editor and type:
Code:
Hello world.
Makes sense to us. A computer however, has no frame of reference so we need to tell it everything it needs to know in order to 'say' hello.
First, we need to put our text into double quotes so that the computer knows it's a single piece of
data:
Code:
"Hello World."
Second, we need to have something to put this data into so we can carry it around our program. This something is called a
variable. Variables come in different
Types, for example: whole numbers like 212 or floating-point numbers like 3.14. In our case we need a C++ variable of type 'string' because our data is alpha-numeric, like so:
Code:
string _bla
Then, after we've declared our shiny new variable to the C++ compiler, we need to
assign our data to it, like so:
Code:
_bla = "Hello World."
Finally, we want the computer to 'say' hello. We do this by sending our data-filled variable to your shell 'console' with a C++ function called
cout:
Code:
cout << _bla << endl
Note the direction of the arrows and also note how we send
endl at the end of the stream too. It's the C++ way to send an 'end of line'.
So the complete program would look like this:
Code:
string _bla
_bla = "Hello World."
cout << _bla << endl
Great. The logic of our program is finished. But we need to sprinkle a bunch of syntactic sugar over our logic for it to become valid C++.
First, there's the fact that in C++ every line of code ends with an semi-colon, like so:
Code:
string _bla ;
_bla = "Hello World." ;
cout << _bla << endl ;
Second, we need to tell the compiler where to find the code for the 'cout' and 'endl' commands and the code for creating 'string' variables. We do this by including this code from a library of files which comes pre-installed with IRIX and MIPSpro, like so:
Code:
#include <string>
#include <iostream>
string _bla;
_bla = "Hello World.";
cout << _bla << endl;
Finally, there's the fact that the compiler always starts compiling from a
main function, which we dont have yet. So we need to wrap our code in one, like so:
Code:
#include <string>
#include <iostream>
int main(int argc, char *argv[]){
string _bla;
_bla = "Hello World.";
cout << _bla << endl;
}
There's a bit of black magic here, but for now we'll just say that the
int before
main tells the compiler that it should expect an integer (int) number as a return value from the main function, and
argc and
argv[] are parameters that are sent into the main function, though we're not using them in our program.
With these modifications we've turned our simple logic into valid C++. Save your C++ code to a file called
HelloWorld.C and send it to the C++ the compiler from the shell-prompt, like so:
Code:
CC -O2 -LANG:std HelloWorld.C -o hello
CC being the C++ compiler,
-O2 -LANG:std a few compiler options,
HelloWorld.C your file with C++ code and
-o hello the name of your output file in which to store your compiled program.
After all of this, you can finally run your program:
Code:
./hello
2. Graphical "Hello World" using C++, Motif and ViewKit.Creating our ViewKit version of "Hello World" is a lot more involved than the text version. We'll need to make a string of text display in a window in a certain font size, we'll need to put that window on the screen, and we'll need to setup a connection to the X11 server so our application can run in the first place.
Like before we'll start from the inside with our string of data, like so:
Code:
"Hello World"
Next, we need a Motif component to show text, which turns out to exist and is called a
Label Widget. You'd think we could come up with something like:
Code:
Widget *ui_label = makeAWidget(labelWidgetType, "Hello World");
Unfortunately, this wont work. Motif and Xt provide us with a complicated system of supporting various types of languages and character sets like Chinese and Arabic. Which is nice but it also means there's no 'simple' way of putting a string into a Motif widget. First, we need to use a Motif function to turn our data into something called a Motif
Compound String. Like so:
Code:
XmString _bla;
_bla = XmStringCreateLocalized("Hello World");
Once we have a compound string, we can feed that into a second function which will create a label for us. Unfortunately, the Xt complications don't end here. Motif makes use of the
Xt Resource system. Colour of a thing might be a resource, as might the text of a label, as might height, width and many other things relevant to how our label looks. We need to feed all the relevant Xt resources into our label creation function.
The way to do this is by using an
array. This is a data container which can hold a number of items of a particular datatype. For example: a carton with a dozen eggs would be a 2x6 array of type 'egg'. In our case, Xt resources are of type
Xarg and in C/C++ we use square brackets define the size of an array. An array called
args large enough to hold 10 Xt Resources would be declared like this:
Code:
Xarg args[10];
The Xt Resource system also requires us to give each Widget in our interface a unique name, so that it can apply new resources to each particular Widget. In our case we might want to use:
UILabel.
Finally, the
XtCreateWidget() function needs to know how many items are in the Xt resources array we're sending into it. Currently that's just the one label resource so the value is:
1.
If we take all of this into account our 'simple' label making code now looks like this:
Code:
Xarg args[10];
XmString _bla;
_bla = XmStringCreateLocalized("Hello World");
XtSetArg(args[0], XmNlabelString, _bla);
Widget *ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, args, 1);
With our label code more or less in place, we need to start thinking about putting our label into a window and this is where we get our first taste of using ViewKit and its Object Oriented Programming (OOP) goodness. In OOP a running program is made up of
objects that send each other messages requesting activities to be performed or status to be reported. Each new object is created based on a blueprint called a
class.
Amongst the many classes that ViewKit provides, the
VkSimpleWindow class allows us to easily create a Motif window object and then tell that window object to show itself on the screen, like so:
Code:
VkSimpleWindow *ui_win = new VkSimpleWindow("My First Window");
ui_win->show();
So simple! But how to add our label widget to this window? In order to do this we need to make a
sub-class of VkSimpleWindow. A sub-class is a new class which inherits all the properties and the abilities of a 'parent' class, but allows you to add whatever extra properties and abilities you need beyond the ones it inherited. Our VkSimpleWindow has the
show() ability for an entire window, but it knows nothing about showing a label. Our sub-class will contain both the basic VkSimpleWindow abilities and the new 'show a label' abilities which we're going to add.
The C++ idiom to define a new HelloWorldWindow class inheriting from the VkSimpleWindow class looks like this:
Code:
class HelloWorldWindow : public VkSimpleWindow {
public:
HelloWorldWindow(const char *);
~HelloWorldWindow();
virtual const char *className();
};
There's a lot of OOP gospel relating to these few lines, but for the moment all you need to know is the following:
HelloWorldWindow(const char *) is a C++ language requirement called a
Constructor function. All C++ classes need a constructor, it's a function that gets called automatically when a new object is created.
The
className() function is a ViewKit requirement.
Lets continue by filling out the functions of our new HelloWorldWindow class by adding our label code, like so:
Code:
HelloWorldWindow::HelloWorldWindow(const char* name) : VkSimpleWindow(name) {
int nr_args;
Arg args[10];
XmString _string;
nr_args = 0;
_string = XmStringCreateLocalized("Hello World");
XtSetArg(args[nr_args], XmNlabelString, _string); nr_args++;
Widget ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, ui_root, args, nr_args);
XtManageChild(ui_label);
XmStringFree(_string);
}
HelloWorldWindow::~HelloWorldWindow() { }
const char* HelloWorldWindow::className() { return "HelloWorldWindow"; }
Note how this version of the Xt resource array in our HelloWorldWindow() constructor function uses an extra
nr_args variable to sum the number of items we stick into the
args array. This is a clever-ish way of keeping track of the number of values used for the XtCreateWidget() function without having to recount by hand each time you add or remove a resource from the array.
Apart from the C++ syntactic sugar, all this does is stick the label creation code into the constructor of the class. In other words: when the window object gets created, the Motif label code is run and a label will be created. However, one essential part of this process is missing: the
position of the label in the window.
Positioning widgets in a Motif window is a Black Art performed by using the Motif
Layout Manager widgets. In our example we're going to use the
Form manager widget. The Form manager works by making the edges of all widgets 'sticky'. You can stick one widget edge to the edge of another widget or even to the form itself.You can also layer Form widgets inside each other to create complex layouts. Think of it as building a brick wall, or if you know about HTML/CSS, as nesting DIVs.
Here's what our code looks like if we add the Motif Form manager widget and the Xt Resources that control it to our code:
Code:
HelloWorldWindow::HelloWorldWindow(const char* name) : VkSimpleWindow(name) {
int nr_args;
Arg args[10];
XmString _string;
nr_args = 0;
XtSetArg(args[nr_args], XmNwidth, 400); nr_args++;
XtSetArg(args[nr_args], XmNheight, 300); nr_args++;
Widget ui_root = XtCreateWidget("UIRoot", xmFormWidgetClass, mainWindowWidget(), args, nr_args);
nr_args = 0;
_string = XmStringCreateLocalized("Hello World");
XtSetArg(args[nr_args], XmNlabelString, _string); nr_args++;
XtSetArg(args[nr_args], XmNtopAttachment, XmATTACH_FORM); nr_args++;
XtSetArg(args[nr_args], XmNleftAttachment, XmATTACH_FORM); nr_args++;
XtSetArg(args[nr_args], XmNrightAttachment, XmATTACH_FORM); nr_args++;
XtSetArg(args[nr_args], XmNbottomAttachment, XmATTACH_FORM); nr_args++;
Widget ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, ui_root, args, nr_args);
XtManageChild(ui_label);
XmStringFree(_string);
addView(ui_root);
}
Note how we've added an extra parameter to the XtCreateWidget() functions. For
ui_root it's
mainWindowWidget() and for
ui_label its
ui_root. These parameters are essential to positioning widgets - they tell Motif/Xt about the parent/child relationships between the various widgets in the GUI
Widget Tree.
There are two ViewKit functions in use here that make all the difference:
mainWindowWidget() and
addView(). mainWindowWidget() gives you the 'hidden' parent of this Motif window - another Motif/Xt/Xlib complexity made easy. And addView() is a function defined in the VkSimpleWindow class that sets the ui_root Form widget to be the root of this window, making everything else fall into place.
Finally, we need to add our
main function so all of this goodness will work:
Code:
int main(int argc, char *argv[]) {
VkApp *app = new VkApp("HelloWorld", &argc, argv);
cout << "Hello World Started." << endl;
HelloWorldWindow *win = new HelloWorldWindow("Hello World Window");
win->show();
app->run();
}
Here we create a VkApp object that connects us to the X11 server and does all the nasty Xt/Xlib initialisation stuff. Good riddance. Then we create a new HelloWorldWindow object and
show() it. Then we tell our VkApp object to
run() and process any mouse or other events for as long as the application is running. In our case where there isn;t even a button to click that means it'll just wait for a signal from the window manager that you've closed the window. In which case it'll terminate the program.
Because Motif relies on a whole bunch of libraries and the compiler needs to be told about this... it's useful to use a utility program to help compile this code. The program is called
make and it reads a
Makefile with instructions how to go about compiling various files of code that would make up a complete program.
Below are the Makefile and the complete C++ code with all the includes and an extra bonus element of changing the fontsize of the label of our HelloWorldVk.C program.
Please note that the fontsize thing only works with Motif v2.1 so you need to make sure you've run the symlink script in
/usr/Motif-2.1/lib/mksymlinks.
Enjoy!
Jimmer
HelloWorldVk.C
Code:
#include <iostream>
using namespace std;
#include <Vk/VkApp.h>
#include <Vk/VkSimpleWindow.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
class HelloWorldWindow : public VkSimpleWindow {
public:
HelloWorldWindow(const char *);
~HelloWorldWindow();
virtual const char *className();
private:
static String ui_defaultResources[];
};
String HelloWorldWindow::ui_defaultResources[] = {
"*renderTable: bold24",
"*fontType: FONT_IS_FONT",
"*bold24.fontName: -*-helvetica-bold-r-normal-*-24-*-*-*-*-*-*-*",
NULL
};
HelloWorldWindow::HelloWorldWindow(const char* name) : VkSimpleWindow(name) {
int nr_args;
Arg args[10];
XmString _string;
setDefaultResources(mainWindowWidget(), ui_defaultResources);
nr_args = 0;
XtSetArg(args[nr_args], XmNwidth, 400); nr_args++;
XtSetArg(args[nr_args], XmNheight, 300); nr_args++;
Widget ui_root = XtCreateWidget("UIRoot", xmFormWidgetClass, mainWindowWidget(), args, nr_args);
nr_args = 0;
_string = XmStringCreate("Hello World", "bold24");
XtSetArg(args[nr_args], XmNlabelString, _string); nr_args++;
XtSetArg(args[nr_args], XmNtopAttachment, XmATTACH_FORM); nr_args++;
XtSetArg(args[nr_args], XmNleftAttachment, XmATTACH_FORM); nr_args++;
XtSetArg(args[nr_args], XmNrightAttachment, XmATTACH_FORM); nr_args++;
XtSetArg(args[nr_args], XmNbottomAttachment, XmATTACH_FORM); nr_args++;
Widget ui_label = XtCreateWidget("UILabel", xmLabelWidgetClass, ui_root, args, nr_args);
XtManageChild(ui_label);
XmStringFree(_string);
addView(ui_root);
}
HelloWorldWindow::~HelloWorldWindow() {
}
const char* HelloWorldWindow::className() { return "HelloWorldWindow"; }
int main(int argc, char *argv[]) {
VkApp *app = new VkApp("HelloWorld", &argc, argv);
cout << "Hello World Started." << endl;
HelloWorldWindow *win = new HelloWorldWindow("Hello World Window");
win->show();
app->run();
}
Makefile
Code:
APP_NAME = hello
APP_OBJS = HelloWorldVk.o
INCDIRS = -I. -I/usr/local/include -I/usr/nekoware/include
LIBDIRS = -L/usr/local/lib -L/usr/nekoware/lib
STDLIBS = -lm
STDCPPLIBS = -lC -lCio
X11LIBS = -lXt -lX11 -lPW
MOTIFLIBS = -lSgm -lXm
VKLIBS = -lvk
LIBS= $(VKLIBS) \
$(MOTIFLIBS)\
$(X11LIBS)\
$(STDCPPLIBS)\
$(STDLIBS)
CXX = CC
CXXOPTS = -O2 -LANG:std
CC = c99
COPTS = -O2
all : $(APP_NAME)
$(APP_NAME): ${APP_OBJS}
${CXX} ${APP_OBJS} ${LIBDIRS} ${LIBS} -o ${APP_NAME}
%.o : %.c
${CC} ${COPTS} ${INCDIRS} -c $<
%.o : %.C
${CXX} ${CXXOPTS} ${INCDIRS} -c $<
clean :
rm -rf ${APP_NAME} *.o core *~ ii_*