U S I N G   T H E   C S 3 2 1   G R A P H I C S   S H E L L

To simplify development of graphical programs and to provide a more robust and uniform interface for products a specialized graphical shell has been developed for CS321. It uses Motif and the X Window System to provide a simple graphical user interface (GUI) with a drawing area, buttons, menu, and command line. Your project WILL BE REQUIRED to work within this shell.

All of the code and details necessary to run this shell can be found in the directory /usr/local/include/cs321. As with most C++ projects it is composed of a series of classes which are designed to interact with each other. Most of the classes are defined using a set of files as follows:

  • *.cpp - the C++ source code with the definition of the class
  • *.h - the C++ header file with the declaration of the class
  • *.o - the compiled version of the cpp file
  • *.uil - the Motif interface description for the graphical objects in this class
  • *.uid - the compiled version of the interface description

The following C++ classes have been developed and have the following relevant interface:

  • MRMbase: <mrmbase.h> - a base class used by other class objects to access UID files (MRM = Motif Resource Manager)
  • Shell: <shell.h> - a base class which builds a functionless graphical user interface
    • It's constructor requires the unix command line parameters (argc and argv) and the path to the applications context file. Calling the constructor (which all derived classes must do) will draw the shell and enable most of the built-in features.
    • Once created the shell is dormant until Activate() is called. This member function does not return since it turns control over to the X Toolkit event handler (see below). If you want multiple Shells construct them all before calling Activate() for any one of them.
    • Internally the Shell class provides a number of data members to facilitate X Windows System programming. The only ones of real interest are display, drawable, window, and screen which should be familiar to most X programmers.
    • Additional functionality of the shell is directly accessed using the following member functions:
      • int Left_Pixel() - Determine the left-most pixel displayed in the drawing area.
      • int Top_Pixel() - Determine the top-most pixel displayed in the drawing area.
      • void Log_Cmd(const string& cmd) - Add a text command to the recall list accessed through the recall up arrow.
    • Functionality is added to the Shell by deriving a customized class from it which overrides the following virtual methods:
      • void Erase() - Erase the picture
      • void Zoom() - Zoom in
      • void Unzoom() - Zoom out
      • void Reset() - Revert picture to original state
      • void Cmd_Parse(const string& cmd) - Parse text from the Shell's command line
      • void Draw(int tag, XEvent *ev) - Process event from drawing window. The tag has one of four values:
        • G_EXPOSE - the drawing area has been exposed
        • G_INPUT - the mouse has generated an event
        • G_RESIZE - the drawing area has been resized
        • G_MOTION - the mouse has moved while a button is pressed
      • const string Help_Text() - Returns text on how to use the derived Shell's features.
      • void Read_File(const string& fn) - Read data from file fn
      • void Write_File(const string& fn) - Write data to file fn
    • The derived class constructor can be used to add extra GUI elements.
    • Other members of the Shell class should not be overridden.
  • Message: <message.h>  - a class for displaying a text message in a pop-up window. Only one object of this class is allowed so messages will share the same window. This class has three methods:
    • void Show_message(Widget w, const string& title, const string& msg) - This might typically be invoked as: Message::Show_message(parent, "Hello Window", "This message says hello.\n");
    • void Append_message(const string& msg) - Add a message to the existing message window. This might by invoked as: Message::Append_message("This appears at the end\n");
    • void Cleanup() - this method is called: Message::Cleanup() to destroy the single object of this class. It may be called safely even if Show_message was never used. The Shell class automatically does this for you in its destructor.
  • Color: <color.h> - this class manages a RGB color editor that can be used to dynamically change the color of drawn objects. It requires that a read/write color cell be allocated use XAllocColorCells before each color editor is created. Plane masks are not necessary for these colors. See xcolor.html for help with colors.
    • Color editors are often created dynamically and should not be constructed until the first call to Draw. The typical usage would be: Color* myeditor = new Color(parent, pixel, "This is my color");
    • Like all dynamic objects your derived Shell destructor must delete them. This includes both the color cell (using XFreeColors) and the color editor.
    • There are some minor bugs in eXcursion that may not have color changes appear until the next Exposure.
    • Multiple editors can be created; each should use a different name to avoid confusion.
  • Button: <button.h> - this is a base class for adding customized buttons to the Shell.
    • Buttons are best added in the derived Shell class constructor.
    • Functionless Buttons are constructed using Button(user_button, "MyButton")
    • Each Button should have its own text to avoid confusion.
    • As with all objects they should be destroyed.
    • Derived Button classes should override the void Pressed(Widget w, int* tag, XmPushButtonCallbackStruct *cb) method to add functionality when the button is pressed.
  • Slider: <slider.h> - this is a base class for adding customized slider controls to the Shell.
    • Sliders are best added in the derived Shell class constructor.
    • Functionless Sliders are constructed using Slider(parent, name, min, max, init, size, orientation); where:
      • name - the string name to place on the slider window
      • min - the minimum integer value on the slider
      • max - the maximum integer value on the slider
      • init - the initial integer value of the slider
      • size - the size in pixels of the slider window
      • orientation - either Slider::HORIZ or Slider::VERT
    • Each Slider should have its own name to avoid confusion.
    • As will all objects they should be destroyed.
    • Derived Slider classes should override the void New_value(Widget w) method to add functionality when the Slider values changes. The value of a Slider can be accessed using void XmScaleGetValue(Widget w, int* valptr);.
  • File: <file.h> - this class manages the file choice dialog. It should not be used.
While this all sounds rather complicated a functionless shell (i.e. one without derived classes) can be created and run by copying the following files from: /usr/local/include/cs321/
  • cs321.cpp - a sample main() which creates and activates a Shell
  • Makefile - the building instructions for the shell
Copies these files to your working directory and execute the command make. This will build the executable cs321 which can be run. As each of the elements of the Shell are activated those requiring functionality will simply write a text message to cout. As you add functionality using derived classes you will want to make changes to the Makefile and to cs321.cpp to create the proper type of Shell. 

The CS321 Shell is built around an event driven model which means that as the user activates Shell controls or exercises the mouse in the drawing window area the X Toolkit automatically dispatches calls to the various methods within the classes described above. One of the features provided by the basic shell code is to automatically map the Shell event to the proper object and member function. Unfortunately the work does not end here. Since each of the events is essentially asynchronous (from the program's perspective) each of the methods (e.g. Draw) must communicate data and state information with the other methods. This is relatively easy to do by adding data members to derived classes to share this information, however, care should be taken to avoid a proliferation of "global" data elements by using static local variables that allow a method to share state information with future calls to itself. 

The drawing area has the most complex assortment of events which are classed as follows:

  • Exposure - whenever a portion of the drawing area is uncovered the operating system will ask it to redraw its data. This is best done by simply redrawing all the data after first making sure that the exposure count is zero (for efficiency). The first time Draw is called with an exposure event will signify that the drawing window has been created and it is now safe to draw images. To do this a graphical context will need to be generated using:
      XGCValues val;
      val.foreground = WhitePixelOfScreen(screen);
      val.background = BlackPixelOfScreen(screen);
      gc = XCreateGC (disp,draw,GCForeground|GCBackground,&val);
    Where gc is a private data object of the derived Shell class of type GC. As with color cells all graphical contexts created by your program should be freed in the destructor (using XFreeGC).

Known Bugs:

The following bugs have been observed while using the shell and do not necessarily indicate an error in shell coding, but rather an X Window System implementation problem.
  • Bugs while using eXcursion
    • Despite using Read/Write color cells in the X colormap it is not unusual for objects immediately drawn  in the new color specified by the built-in color editor to be drawn in the previous color. This is often fixed by forcing an Exposure event in the drawing window. This bug is erratic and does not always occur.
  • Bugs while using eXcursion with Windows95 only
    • Attempting to draw objects with coordinates that overflow the int data type may cause the Windows95 machine to crash requiring a reset.

This page is maintained by Henry Welch; it was last updated on September 22, 1999.
Copyright  ©1999 by Henry L. Welch