Next: , Previous: , Up: Top   [Index]


Panes and Graphics

So far, this tutorial has used either C’s printf function or printOn and other methods of various classes to display information on a terminal.

This section describes how to use Pane class and its subclasses to display information in windows, handle user input, and retrieve the information.

There are Pane subclasses for the following graphics libraries and display types.

ANSI Compatible, Text-mode Displays

Most UNIX consoles, xterms, VT-100 and many other serial RS-232 terminals, and in some cases, Win32 ‘cmd.exe’ windows and OS X terminal apps. See ANSIGraphics.

X Window System

GUIs that use a X server, window manager, and Xlib libraries. See X11Graphics.

GLUT

The GLUTApplication class provides support for the GLUT and OpenGL 3-D graphics libraries and runs on many different platforms. See GLUTGraphics

GLX

The GLXCanvasPane class is another toolkit that provides OpenGL’s 3-D graphics capabilities on systems that have a X display. See GLXGraphics.

Closely related to these Pane classes are several Stream subclasses that handle the chores of processing keyboard and mouse input. Graphical User Interfaces use more sophisticated input devices than simple text prompts, and the following sections also describe how to process and respond to different types of input.

5 Hello, world! in a Text Window

For the sake of portability and (relative) simplicity, we’ll start with an example that uses the Pane subclass, ANSIMessageBoxPane. This class is a widget that pops up a message window on a the screen of a xterm or physical serial terminal, then waits for the user’s input, and then closes the window.

The program itself is relatively simple. Here is the listing.


int main () {
  ANSIMessageBoxPane new messageBox;
  messageBox withText "Hello, world!";
  messageBox show 10, 10;
  messageBox cleanup;
}

The program displays the message until the user presses Enter to close the window. Pressing Esc also closes windows, but, for most widget classes, Esc signals that the user wants to close the window without returning any input.

As you might expect, what happens behind the scenes is a little more involved.

6 Pane and Stream Classes

When a program creates a new instance of a widget (in this case, a subclass of ANSIWidgetPane), the new object initializes the instance variables it inherits from its superclasses, initializes the storage to hold the information that is to be displayed, and a Stream object that handles the window’s input and output. In the case of ANSIWidgetPane subclasses, the input and output is handled by an ANSITerminalStream object, which is also an instance variable of the widget.

Here are the classes that handle Pane graphics, and their corresponding Stream classes. This organization makes it relatively easy to add widgets and support for other text-mode and GUI interfaces.

 Pane                       Stream
  ANSITerminalPane           TerminalStream
   ANSIWidgetPane             ANSITerminalStream
    ANSIButtonPane
    ANSIMessageBoxPane
    ANSITextBoxPane
    ANSITextEntryPane
    ANSIYesNoBoxPane
  X11Pane                    X11TerminalStream

This example demonstrates a little of what ANSITerminalPane and its subclasses can do. There are also many other graphics classes and methods, though.

The next sections begin relatively simply, with an app for text-mode terminals. The tutorial then progresses to the classes and methods that support the X Window System’s drawing and user interface capabilities. If you’re already familiar with GUI programming, then feel free to skip ahead, and refer back to these sections if some concept or capability seems unfamiliar to you.

7 InputEvent Class

Ctalk uses InputEvent objects to communicate between the Pane's stream object, which receives its information from the serial tty device that communicates with the xterm or physical serial terminal, and the pane objects that you see on the screen. Ctalk uses InputEvent objects extensively, and you need to know about them to write a graphical app.

Text-mode displays use instances of ANSITerminalStream class to handle user input. The input can come from either a console (if you’re the superuser or your system doesn’t have a GUI), a xterm, or a physical serial terminal.

When a program creates an ANSITerminalStream object, Ctalk initializes its input and output to the application’s standard input and output channels. For the sake of simplicity, the following examples use standard input and output.

Text-mode displays use the getCh method (class ANSITerminalStream) to receive characters and return them to the application. ANSITerminalStream also provides some basic terminal capabilities, and you can also use the class independently of a window.

Here is a brief example that echoes characters to the display.

int main () {
  ANSITerminalStream new term;  /* Initializes input and output */
                                /* to stdin and stdout.         */
  Character new c;

  term rawMode;
  term clear;
  term gotoXY 1, 1;

  while ((c = term getCh) != EOF)
    term printOn "%c", c;

  term restoreTerm;
}

There are a few things to note about this example. The first is that the program sets the terminal to raw mode. That means the characters you type at the keyboard are received directly by the application, without any buffering or echoing by the terminal device itself. All terminal output is handled by the methods clear, gotoXY, and printOn.

It also means that the program must restore the terminal to its original state before exiting. That is the purpose of the restoreTerm method. If the application did not restore the terminal before exiting, the display would likely be unusable, so be sure to use restoreTerm whenever you use rawMode in your applications.

The ANSITerminalStream version of printOn notes what mode the terminal is in, so the program can display output regardless of whether the terminal is raw mode or not.

You should note that getCh recognizes an EOF (C-z) character, but in practice it is often difficult or impossible to enter an EOF from the keyboard.

The example above would have difficulty handling many control characters, and it doesn’t recognize terminal escape sequences; for example, of the type generated when you press a cursor key.

That is why applications that use panes use InputEvent objects. InputEvent objects contain information about what type (or class) of input is coming from the terminal, and the actual data.

In the case of terminal input, there are two event classes: KBDCUR, for terminal escape sequences, and KBDKEY, for alphanumeric keypresses.

The terminal stream queues these events, and the pane can retrieve then and process them in whatever manner it needs to.

Programs that use GUI displays don’t actually need to use input events, but you will find that handling input beyond a simple press of the Return key can become quite involved. For an example, look at the ANSITerminalPane section of the language reference.

8 Using Queued InputEvents

If you’re wondering how to go about adding all of this to a program, don’t worry. All of the ANSIWidgetPane subclasses implement a method handleInput, which directs the pane’s ANSITerminalStream to queue the events, and handles the events as it receives them.

In addition, all instances of ANSITerminalPane and its subclasses contain an instance variable (paneStream) that is an ANSITerminalStream object. You might recall from the previous sections that ANSITerminalStream initializes its input and output to stdin and stdout. Most applications need not handle that chore themselves.

If you plan to write widgets, you might need to see to the I/O initialization yourself. However, if the widget is a subclass of ANSITerminalPane, then the constructor in that class takes care of I/O initialization anyway.

For now, we’ll just describe ANSIWidgetPane’s implementation of the handleInput method. Here it is, minus a few comments omitted for space reasons.

ANSIWidgetPane instanceMethod handleInput (void) {

  Integer new c;
  InputEvent new iEvent;

  while ((c = self paneStream getCh) != EOF) {
    iEvent become self paneStream nextInputEvent;
    switch (iEvent eventClass)
      {
      case KBDCHAR:
      case KBDCUR:
        switch (iEvent eventData)
          {
          case ESC:
            self withdraw;
            self paneStream restoreTerm;
            return NULL;
            break;
          }
        break;
      }
  }
  return NULL;
}

This method is very basic–it handles only one key sequence: ESC, which withdraws the pane from the display (Withdraw, in this case, is functionally synonymous with, close, although it’s the following methods that close the terminal connection and delete the pane object.) This method also uses the getCh and restoreTerm methods that we mentioned earlier, with the pane’s paneStream instance variable as the receiver.

More involved implementations of handleInput also use the openInputQueue method from ANSITerminalPane to enable event queueing. You might want to look at the handleInput methods of other classes to see how this is accomplished.

9 The Widget API

Ctalk widget classes generally implement a set of methods that handle the common chores of displaying information and handling input. The methods listed here are a minimum set of methods. Widget classes can implement additional methods as needed.

Here we’ll describe the API for the ANSITerminalPane classes and its subclasses. We’ll discuss the API for X11 panes later on. See X Widget API.

cleanup

Clean up any additional data used by the widgets. This includes the paneBuffer and paneBackingStore storage, and any additional storage. Deletion of the widget objects themselves is handled by the normal object cleanup mechanisms.

handleInput

Receive InputEvent objects from the receiver’s paneStream, and take action based on the keyboard input.

new

Initializes the pane and its sub-panes, and sets whatever parameters are necessary.

parent

Attach a sub-widget to its parent widget.

refresh

Update the pane and its child panes on the display.

It’s also likely that a widget class needs to implement its own map and unmap methods, similar to those in ANSITerminalStream class.

Many ANSITerminalPane subclasses also implement a show method, which pops up the window at a specified x, y position on the terminal. Strictly speaking, however, show is a convenience method.

This section has not discussed adding shadows, borders, titles, and other graphical effects to pane objects. There are a lot of them, and they are described in each class’s section of the language reference.

There is one point that we should note about graphics effects here, however. In order to keep the widget classes and their libraries as fast as possible, when refreshing subpanes, the terminal’s graphics attributes (the graphical effects other than borders and shadows; i.e., bold, reverse, or blinking text, graphics character sets, and so forth) are not automatically updated when a parent window is refreshed. The program needs to update the subpanes individually. As in this example, from the ANSITextBoxPane show method.

self refresh;                    /* Refresh the main widget.    */
self dismissButton               /* Toggle the button's focus   */
                                 /* highlight.                 */
   focusHighlightOnOff;
self dismissButton refresh;      /* Refreshes the button widget */
                                 /* independently.              */

If the method did not independently refresh the receiver’s dismissButton subwidget, the change to the button’s focus highlight would not be visible.

Complex widgets that have more than one button or text input box need to keep track of the input focus and its display. There is a bit more about these issues in the next section.

10 Serial Terminals

Applications that handle input and output for serial terminals must also use the openOn and setTty methods of ANSITerminalStream class. Their use is relatively simple.

ANSIYesNoBoxPane new myWidget;  /* Initialize stdin and stdout. */

myWidget paneStream openOn "/dev/ttyS0"; /* A Linux tty device. */
myWidget paneStream setTty 9600, 8, 'n', 1;

The ANSIWidgetPane subclasses can, but don’t always, handle all of sequences defined by the ANSI, VT-100, or xterm specifications. It’s up to each class and subclass to determine how to handle the escape sequences it needs.

11 X11Pane Class

The X11Pane class can display text in a X window and handle a set of X input events, both from the X server and the window manager.

The next few sections describe basic drawing with X11Pane objects. This provides a more gentle introduction to all of the features of X11Pane objects and the Pen, X11Font, Point, Line, and Rectangle objects which provide specific drawing features. We’ll describe the X widget API later on. See X Widget API.

We’ll just mention here that X11Pane objects use the X window’s default visual, but they also maintain a copy of the window’s graphics context, so you can create your own visuals if necessary. If you’re not certain what that means, it allows classes to be flexible enough that you can still create complex windows using the Xlib API if a class doesn’t provide a specific feature that the application needs.

12 Points, Lines, and Rectangles

Now that we’ve described how to communicate with pane objects and windows, we can finally discuss drawing things on the screen. The classes Point, Line, and Rectangle provide the ability to draw on X11Pane windows.

Each of these classes provides a draw method that lets you draw the object on a X window. The dimensions of these objects are determined by their instance variables, which are all Point objects.

For example, to draw a point on a window an application might use the following code.


Point new myPoint;

myPoint x = 35;
myPoint y = 35;

myPoint draw xPane;

Note that the argument to draw is the X11Pane object. X11Pane class has its own draw method as well, so an application could also use the following statement, and the result would be the same as the previous example.


xPane draw myPoint;

A Line object has two Point objects as instance variables, which describe the start and end of the line.

Line new myLine;

myLine start x = 90;
myLine start y = 10;
myLine end x = 10;
myLine end y = 90;

xPane draw myLine;

Similarly, a Rectangle object has four Line instance variables that describe the sides of the rectangle.

Here is the declaration from Rectangle class.


Rectangle instanceVariable top Line NULL;
Rectangle instanceVariable right Line NULL;
Rectangle instanceVariable bottom Line NULL;
Rectangle instanceVariable left Line NULL;

When creating a rectangle, you might use statements like these.


rectangle top start x = 10;
rectangle top start y = 10;
rectangle right end x = 80;
rectangle right end y = 80;
...

xPane draw rectangle;

As a shortcut, the dimensions method uses the upper left and lower right corners of the rectangle to fill in the instance variables.


rectangle dimensions 10, 10, 100, 100;

If you want to draw a solid rectangle, instead of an outline, use the fill method instead of draw.


xPane fill rectangle;

13 Pens

If a program provides only the dimensions of a shape, the default width of a point or line is 1 pixel, and the shape’s color is black.

If you want to set the width and color of a shape; for example, to a thick green line, you can provide a Pen object that contains the color and width information.


Line new myLine;
Pen new greenPen;

greenPen width = 10;
greenPen color = "green";

xPane drawWithPen myLine, greenPen;

Instead of using draw in these cases, you would use the drawWithPen method (or fillWithPen for solid Rectangle objects).

The Ctalk Language Reference contains simple examples for drawing each of these classes. Here, however, is a demonstration program that draws red, green, and blue dots inside a window.

int main () {
  X11Pane new xPane;
  InputEvent new e;
  Pen new bluePen;
  Pen new redPen;
  Pen new greenPen;
  Point new redPoint;
  Point new bluePoint;
  Point new greenPoint;

  xPane initialize 10, 10, 250, 250;
  xPane map;
  xPane raiseWindow;
  xPane openEventStream;

  xPane background "yellow";
  xPane clearWindow;

  redPen width = 100;
  redPen colorName = "red";
  redPoint x = 40;
  redPoint y = 40;

  greenPen width = 100;
  greenPen colorName = "green";
  greenPoint x = 120;
  greenPoint y = 40;

  bluePen width = 100;
  bluePen colorName = "blue";
  bluePoint x = 80;
  bluePoint y = 90;

  while (TRUE) {
    xPane inputStream queueInput;
    if (xPane inputStream eventPending) {
      e become xPane inputStream inputQueue unshift;
      switch (e eventClass value)
        {
        case WINDELETE:
          xPane deleteAndClose;
          exit (0);
          break;
        case EXPOSE:
        case RESIZENOTIFY:
	  xPane drawWithPen redPoint, redPen;
	  xPane drawWithPen greenPoint, greenPen;
	  xPane drawWithPen bluePoint, bluePen;
          break;
        default:
          break;
        }
    }
  }
}

The program first initializes the window and starts receiving messages from the X server with the initialize and openEventStream messages. Only then can the program perform any drawing on the window, including setting the background and updating the window (with the background and clearWindow messages). It is necessary for X client programs to send and receive window system requests in synchronization with the display hardware.

14 Fonts

X11Pane objects also provide simple font support.

If you set the fontDesc instance variable to a X Logical Font Descriptor, as in this example, then text is displayed using that font.


myPane fontDesc = "-*-courier-medium-r-*-*-12-120-*-*-*-*-*-*";

Modern operating systems provide many different types of fonts. For now, though, this tutorial discusses the basic X fonts and their descriptions. If you’re not familiar with them, the manual page for xfontsel(1) provides a good starting point.

Ctalk’s libraries determine the actual font from the font descriptor that you provide (like the descriptor given by xfontsel), and fill in the metrics in the pane’s font instance variable, which is a X11Font object.

If you want to specify a font simply by setting the fontDesc instance variable, then you must do it before the program calls the initialize method and Ctalk initializes the actual window parameters.

If, however, you want to change fonts in the middle of a program, then use the useFont method, as in this example.

myPane useFont "-*-courier-medium-r-*-*-12-120-*-*-*-*-*-*"

In either case, Ctalk fills in the font’s dimensions: its ascender, descender, and total height; and the width of its widest character.

If you simply want to find the dimensions of a font without actually using it, then you can use the getFontInfo method (class X11Font), and it will fill in the dimensions in an X11Font’s instance variables.

If the program doesn’t specify a font, then text is displayed using the system’s fixed font.

15 The X Widget API

The widget API is still in development. So far, it is composed of three classes: X11PaneDispatcher, X11CanvasPane, and X11TextPane. To do the mechanics of actual drawing, these classes rely on the X11Bitmap class.

Most of the information in the previous sections also applies here. However, the widget API also provides the methods to dispatch events to subpanes and other methods to handle the events. The API’s design can also allow multiple panes within a single window, although at present only one subpane may occupy a window.

The Pane Hierarchy

The basic way to create an application that uses subpanes is to attach a X11PaneDispatcher object to a X11Pane object, and then a pane class like X11CanvasPane to the X11PaneDispatcher object.

Each class handles specific chores for drawing in the windows and handling input from the user and the display system. The following diagram should make this clearer.


 ------------------
|                  |     Receives events from the display system
|    X11Pane       |     via the inputStream instance variable,
|                  |     and sends them to subpanes, if any.
 ------------------
        |
        v
 ------------------
|                  |     Defines the basic handler methods 
| X11PaneDispatcher|     for different types of events, and
|                  |     dispatches various events to subpane
 ------------------      handlers if they exist.
        |
        v
 ------------------
| X11CanvasPane,   |     Provides the methods and instance data
| X11TextPane, etc.|     used by the application for drawing text 
|                  |     or graphics in the subpane's window, 
 ------------------      and updating the main window.
        |
        v
 ------------------
| X11Bitmap        |     Contains information about the 
|                  |     resources used by the hardware and 
|                  |     system libraries for drawable surfaces,
 ------------------      colors, fonts, and so on.

As with other complex pane objects, programs attach subpanes to their parent panes. As an example, here is the dots program from earlier in this chapter. In this case, the application uses a X11CanvasPane object for drawing.

int main () {
  X11Pane new xPane;
  InputEvent new e;
  X11PaneDispatcher new xTopLevelPane;
  X11CanvasPane new xCanvasPane;
  Application new paneApp;

  paneApp enableExceptionTrace;
  paneApp installExitHandlerBasic;

  xPane initialize 10, 10, 250, 250;
  xTopLevelPane attachTo xPane;        /* The attachTo methods also */
  xCanvasPane attachTo xTopLevelPane;  /* set the dimensions of the */
                                       /* subpanes before they are  */
                                       /* mapped and raised along   */
                                       /* with the top-level pane.  */

  xPane map;
  xPane raiseWindow;
  xPane openEventStream;               /* Before we can do any      */
                                       /* drawing on the window, we */
                                       /* need to start sending and */
                                       /* receiving events from the */
                                       /* X server.  That is what   */
                                       /* openEventStream does.     */

  xPane background "yellow";           /* Setting the background of */
  xPane clearWindow;                   /* an X11Pane object sets the*/
                                       /* background of the actual  */
                                       /* window.                   */

  xCanvasPane background "yellow";     /* Setting the background of */
                                       /* a buffered pane like a    */
                                       /* X11CanvasPane sets the    */
                                       /* background color of its   */
                                       /* buffer.                   */

  xCanvasPane clearRectangle 0, 0, 250, 250; /* In both cases, we   */
                                             /* need to update the  */
                                             /* pane before the new */
                                             /* color is visible,   */
                                             /* with either,        */
                                             /* "clearWindow," or,  */
                                             /* "clearRectangle."   */
  xCanvasPane pen width = 100;
  xCanvasPane pen colorName = "red";
  xCanvasPane drawPoint 40, 40;
  xCanvasPane pen colorName = "green";
  xCanvasPane drawPoint 120, 40;
  xCanvasPane pen colorName = "blue";
  xCanvasPane drawPoint 80, 90;

  while (TRUE) {
    xPane inputStream queueInput;
    if (xPane inputStream eventPending) {
      e become xPane inputStream inputQueue unshift;
      xPane subPaneNotify e;          /* We need to notify subPanes */
                                      /* e.g., xCanvasPane of the   */
                                      /* input events from the GUI. */
      switch (e eventClass value)
        {
        case WINDELETE:
          xPane deleteAndClose;
          exit (0);
          break;
        case EXPOSE:
        case RESIZENOTIFY:
	  xCanvasPane pen width = 100;
	  xCanvasPane pen colorName = "red";
	  xCanvasPane drawPoint 40, 40;
	  xCanvasPane pen colorName = "green";
	  xCanvasPane drawPoint 120, 40;
	  xCanvasPane pen colorName = "blue";
	  xCanvasPane drawPoint 80, 90;
          break;
        default:
          break;
        }
    }
  }
}

For another example program, look at the simple drawing program in the X11CanvasPane section of the Ctalk Language Reference.

X Window System Events

Like other graphical programs, objects of X11Pane class and its subclasses respond to events from the keyboard, mouse, and display.

Here are events types that the API provides. An application program does not need to handle all of them - only the events that relate to the application’s functions.

The table also lists the data that each event type provides, and the data contained in each of the event’s instance variables.

eventclass     eventdata1 eventdata2 eventdata3 eventdata4 eventdata5
-------------  ---------- ---------- ---------- ---------- ----------
BUTTONPRESS    x          y          state      button     -
BUTTONRELEASE  x          y          state      button     -
KEYPRESS       x          y          state      keycode    X11 keysym
KEYRELEASE     x          y          state      keycode    X11 keysym
MOTIONNOTIFY   x          y          state      is_hint
MAPNOTIFY      event      window id  -          -          -
EXPOSE         x          y          width      height     count
MOVENOTIFY     x          y          width      height     border width
RESIZENOTIFY   x          y          width      height     border width
WINDELETE      -          -          -          -          -

Some of the data provided by these events are defined by the window system protocol. Other information is provided by the Ctalk libraries. For example, the X11 keysym information provided by a KEYPRESS or KEYRELEASE event translates a hardware specific keycode into a portable X11 keysym, so an application doesn’t need to worry about what type of keyboard the system has.

If you’re exploring the X Window System API, you might find it useful to have the application monitor these events. The demos/xhello.c sample program provides an example of how applications might monitor these events.

GLUTApplication Class

The GLUT toolkit, which is available on most platforms that support the OpenGL graphics API, is a cross-platform toolkit that greatly simplifies writing applications for 3D modeling and graphics.

The GLUTApplication class provides methods that help you define callbacks for the various GUI events, pointer and keystroke handlers, background and animation callbacks, and a simplified API for creating and managing windows that is compatible with most systems that have GUI’s.

To do the actual drawing, GLUTApplication class programs use the OpenGL API. OpenGL is a large library that flexibly supports application specific graphics.

There are many books and tutorials that describe OpenGL and the subject of 3D graphics. Here, we’ll just show a simple program that integrates some of the basic Ctalk methods with GLUT and OpenGL. This program is in the Ctalk source package, as the file, demos/glut/tetra.ca.


/*
  Demonstration that draws a tetrahedron, manually rendered with
  blended colors between vertexes. 

  Pressing [F1] toggles a full screen display.
  Pressing [Esc] exits the program.
*/

/*
  To build manually with Linux/UNIX use a series of commands like the following.

    $ ctalk tetra.ca -o tetra.i
    $ gcc tetra.i -o tetra -lctalk -lreadline -lhistory -lGL -lGLU -lglut

  or simply,

    $ ctcc -x tetra.ca -o tetra

  On OS X, to build with the GLUT framework (but with the standard GL includes):

    $ ctalk -I /usr/X11R6/include tetra.ca -o tetra.i
    $ gcc -framework GLUT tetra.i -o tetra -lctalk -lreadline \
        -L/usr/X11R6/lib -lGL -lGLU
 */

#ifndef ESC
#define ESC 27
#endif
#define WINWIDTH 640
#define WINHEIGHT 480

GLUTApplication new tetra;
Boolean new isFullScreen;

#include <ctalk/ctalkGLUTdefs.h>

float face1[3][3] = {{0.0f, 2.0f, 0.0f},
		     {-2.0f, -2.0f, 2.0f},
		     {2.0f, -2.0f, 2.0f}};
float face2[3][3] = {{0.0f, 2.0f, 0.0f},
		     {2.0f, -2.0f, 2.0f},
		     {2.0f, -2.0f, -2.0f}};
float face3[3][3] = {{0.0f, 2.0f, 0.0f},
		     {2.0f, -2.0f, -2.0f},
		     {-2.0f, -2.0f, -2.0f}};
float face4[3][3] = {{0.0f, 2.0f, 0.0f},
		     {-2.0f, -2.0f, -2.0f},
		     {-2.0f, -2.0f, 2.0f}};

float base[4][3] = {{2.0f, -2.0f, 2.0f},
		    {2.0f, -2.0f, -2.0f},
		    {-2.0f, -2.0f, -2.0f},
		    {-2.0f, -2.0f, 2.0f}};
float angle = 0.0f;

void mydisplay (void) {
  glEnable (GL_NORMALIZE);
  glEnable(GL_DEPTH_TEST);
  glClearColor(0.0, 0.0, 0.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glLineWidth (1.0f);

  glLoadIdentity ();
  glColor4f (1.0f, 1.0f, 1.0f, 1.0f);

  glRotatef (angle, 0.0f, 1.0f, 0.0f);
  glRotatef (10.0f, 0.0f, 0.0f, 1.0f);

  glBegin (GL_TRIANGLES);
  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face1[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face1[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face1[2]);

  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face2[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face2[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face2[2]);
			
  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face3[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face3[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face3[2]);
			
  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face4[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face4[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face4[2]);
  glEnd ();

  glBegin (GL_QUADS);

  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (base[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (base[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (base[2]);
  glColor3f (1.0f, 0.0f, 1.0f);
  glVertex3fv (base[3]);

  glEnd ();

  glutSwapBuffers ();
}

void animation (void) {
  angle += 0.2f;
  if (angle >= 360.0f)
    angle = 0.0f;
  glutPostRedisplay();
}

void fn_key (int keycode, int x, int y) {
  if (keycode == GLUT_KEY_F1) {
    if (!isFullScreen) {
      tetra fullScreen;
    } else {
      tetra reshape WINWIDTH, WINHEIGHT;
    }
  }
  isFullScreen = !isFullScreen;
}

void key (unsigned char c, int x, int y) {
  if (c == ESC)
    exit (0);
}

int main (int argc, char **argv) {
  tetra initGLUT(argc, argv);
  tetra initWindow (WINWIDTH, WINHEIGHT);
  tetra createMainWindow ("Tetrahedron -- GLUTApplication Class");
  tetra defineDisplayFn mydisplay;
  tetra defineIdleFn animation;
  tetra defineSpecialFn fn_key;
  tetra defineKeyboardFn key;
  tetra installCallbackFns;
  tetra run;
  
}

GLXCanvasPane Class

GLXCanvasPane is another class that provides support for 3-D graphics using OpenGL. GLX provides visual classes that allow OpenGL to perform 3-D modeling in a X window. Because it is integrated with the X server, GLX is available on nearly all modern systems that use X desktops.

Like other OpenGL utility libraries, GLXCanvasPane doesn’t provide many of the actual drawing routines, leaving that to OpenGL itself. However, the class does provide a framework that facilitates 3-D modeling on as many compatible systems as possible.

Printed below is a sample GLXCanvasPane application. This program is included in the Ctalk distribution as demos/glx/glx.ca.

The program is similar to the example program in the GLUTApplication section, except that the event handling methods that provide the callbacks are defined in GLXCanvasPane, so you can subclass and modify application classes as necessary.

For detailed information about GLXCanvasPane class, refer to the GLXCanvasPane section of the Ctalk Language Reference.


#include <X11/Xlib.h>
#include <GL/glx.h>

#define DEFAULT_WIDTH 500
#define DEFAULT_HEIGHT 500

float face1[3][3] = {{0.0f, 2.0f, 0.0f},
		     {-2.0f, -2.0f, 2.0f},
		     {2.0f, -2.0f, 2.0f}};
float face2[3][3] = {{0.0f, 2.0f, 0.0f},
		     {2.0f, -2.0f, 2.0f},
		     {2.0f, -2.0f, -2.0f}};
float face3[3][3] = {{0.0f, 2.0f, 0.0f},
		     {2.0f, -2.0f, -2.0f},
		     {-2.0f, -2.0f, -2.0f}};
float face4[3][3] = {{0.0f, 2.0f, 0.0f},
		     {-2.0f, -2.0f, -2.0f},
		     {-2.0f, -2.0f, 2.0f}};

float base[4][3] = {{2.0f, -2.0f, 2.0f},
		    {2.0f, -2.0f, -2.0f},
		    {-2.0f, -2.0f, -2.0f},
		    {-2.0f, -2.0f, 2.0f}};

float angle = 20.0;

GLXCanvasPane instanceMethod draw (void) {
  glEnable (GL_NORMALIZE);
  glEnable(GL_DEPTH_TEST);
  glClearColor(0.0, 0.0, 0.0, 1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glLineWidth (1.0f);

  glLoadIdentity ();
  glColor4f (1.0f, 1.0f, 1.0f, 1.0f);

  glRotatef (angle, 0.0f, 1.0f, 0.0f);
  glRotatef (10.0f, 0.0f, 0.0f, 1.0f);

  glBegin (GL_TRIANGLES);
  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face1[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face1[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face1[2]);

  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face2[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face2[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face2[2]);
			
  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face3[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face3[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face3[2]);
			
  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (face4[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (face4[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (face4[2]);
  glEnd ();

  glBegin (GL_QUADS);

  glColor3f (1.0f, 0.0f, 0.0f);
  glVertex3fv (base[0]);
  glColor3f (0.0f, 1.0f, 0.0f);
  glVertex3fv (base[1]);
  glColor3f (0.0f, 0.0f, 1.0f);
  glVertex3fv (base[2]);
  glColor3f (1.0f, 0.0f, 1.0f);
  glVertex3fv (base[3]);

  glEnd ();

  glRotatef (20.0, 0.0f, 0.0f, 1.0f);
  glRotatef (angle, 0.0f, 1.0f, 0.0f);

  self swapBuffers;
}

GLXCanvasPane instanceMethod initGL (void) {
  glViewport (0, 0, DEFAULT_WIDTH, DEFAULT_HEIGHT);
  glClearColor(0.0, 0.0, 0.0, 1.0);
  glLineWidth (1.0);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glEnable (GL_LINE_SMOOTH);
  glHint (GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  if (DEFAULT_WIDTH <= DEFAULT_HEIGHT) {
    glOrtho (-5.0, 5.0,
	     -5.0 * ((float)DEFAULT_HEIGHT / (float)DEFAULT_WIDTH),
	     5.0 * ((float)DEFAULT_HEIGHT / (float)DEFAULT_WIDTH),
	     -5.0, 5.0);
  } else {
    glOrtho (-5.0, 5.0,
	     -5.0 * ((float)DEFAULT_WIDTH / (float)DEFAULT_HEIGHT),
	     5.0 * ((float)DEFAULT_WIDTH / (float)DEFAULT_HEIGHT),
	     -5.0, 5.0);
  }
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
}

GLXCanvasPane instanceMethod myTimerTickHandler (void) {
  angle += 1.0;
  self draw;
}

/* This definition comes from the machine's X11/keysymdef.h file. */
#define XK_Escape 0xff1b

GLXCanvasPane instanceMethod myKeyPressMethod (Integer xKeySym,
					       Integer keyCode,
					       Integer shiftState) {
  if (xKeySym == XK_Escape) {
    self deleteAndClose;
    exit (0);
  }
}

GLXCanvasPane instanceMethod myExposeMethod (Integer nEvents) {
  if (nEvents == 0)
    self draw;
}

GLXCanvasPane instanceMethod myResizeMethod (Integer width,
					     Integer height) {
  float ar;

  glViewport (0, 0, width, height);
  glMatrixMode (GL_PROJECTION);
  glLoadIdentity ();
  if (width <= height)
    ar = (float)height / (float)width;
  else
    ar = (float)width / (float)height;
  glOrtho (-5.0, 5.0, -5.0 * ar, 5.0 * ar, -5.0, 5.0);
  glMatrixMode (GL_MODELVIEW);
  glLoadIdentity ();
}

int main () {
  GLXCanvasPane new pane;

  pane initialize (1, 150, 500, 500);
  pane title "GLXCanvasPane Demonstration";
  pane map;
  pane raiseWindow;

  pane onKeyPress "myKeyPressMethod";
  pane onExpose "myExposeMethod";
  pane onTimerTick "myTimerTickHandler";
  pane onResize "myResizeMethod";

  pane initGL;
  
  pane run;

}


Command Line Options

Support for command line options in terminal programs is relatively straightforward. There are many examples of how to use ‘argc’ and ‘argv’ that the system provides as arguments to the main () function.

Ctalk has more support for command line options than that. The tutorial discusses it here because the way an application handles command line options helps determine how a program interacts with the desktop.

First, the Application class provides the method, parseArgs. This method, given the argc and argv arguments that the system provides to main (), saves them in the Application’s cmdLineArgs instance variable, which is an Array.

Most of the time, an application parses the command line arguments as soon as possible when starting, as in this example, which also has a simple method for setting the application’s objects from the command line arguments.


Application instanceMethod appOptions (void) {

  Integer new i;
  Integer new nParams;
  String new param;

  nParams = self cmdLineArgs size;

  for (i = 1; i < nParams; i++) {

    param = self cmdLineArgs at i;

    if (param  == "-g") {
      geomString = self cmdLineArgs at i + 1;
      i = i + 1;
      continue;
    }

    if (param  == "-fg") {
      fgColor = self cmdLineArgs at i + 1;
      i = i + 1;
      continue;
    }

    if (param  == "-bg") {
      bgColor = self cmdLineArgs at i + 1;
      i = i + 1;
      continue;
    }

    if (param  == "-h")
      exit_help ("myApp");

  }
}

int main (int argc, char **argv) {

    Application new myApp;

    myApp parseArgs argc, argv;

    myApp appOptions; 
    ...
}

That’s most of the command line processing that a text-mode application needs. For GUI applications, unless they are very simple, it’s normal to allow the user the option of setting the window size and position. For that purpose, the Application class provides the parseX11Geometry method.

Extending the example above, if the user provided a geometry specification on the command line, the application can check for it, as in this code snippet.


  if (geomString length > 0) 
    myApp parseX11Geometry geomString;

A window geometry specification, if you’re not familiar with them, is a string that specifies a window’s dimensions and placement. For example, the string


300x300+100+150

places a window with a width and height of 300 pixels at 100 pixels to the right and 150 pixels down from the screen’s upper left-hand corner.

The geometry string is only a hint to the window manager. The user option is called that because it’s a recommendation. When drawing the application’s window, the window manager also takes into account the width of the window’s frame, title bar, and any menus and buttons on the window frame. This has some implications that the further examples take into account.

For example, if you provide a geometry like this one,

+0+0

then the window manager places the window’s frame at the upper left hand corner of the screen. The window described by our application’s X11Pane object is actually inset within the window frame.

You can also give a negative position. In that case, the window manager uses a different corner of the screen as its origin.

For example, the position


-0-0

places the window at the lower right-hand corner of the screen.

To handle this, the X11Pane class overloads the initialize method. In its simplest form, initialize takes two arguments, the width and height of the window in pixels, and lets the window system handle the window placement.

So for very simple applications, you can simply start the application with several expressions like these.


int main () {

    X11Pane new myPane;

    myPane initialize 200, 250; // Set the window's initial size.

...
}

For more complete applications, to set the window size and placement with a command line option, you need to use the version of initialize that takes five arguments - the position and size of the window in pixels, and a fifth argument, the geometry flags that that the parseX11Geometry method provides.

That way the window manager can handle specifications like negative positions, and positions off the screen. Even with this support, the window initialization can become quite complex, depending on what options the user provided.

This example is abbreviated from the ctxlogo program, and takes into account whether or not the user provided the window’s size and placement as a command line option, and tries to provide some reasonable default values.


int main (int argc, char **argv) {
  Integer new xWindowSize;
  Integer new yWindowSize;
  Integer new xWindowOrg;
  Integer new yWindowOrg;

  X11Pane new xPane;
  X11PaneDispatcher new xTopLevelPane;
  X11CanvasPane new xCanvasPane;
  Application new ctxlogo;

  ctxlogo parseArgs argc, argv;
  ctxlogo ctxlogoOptions;
  if (geomString length > 0) 
    ctxlogo parseX11Geometry geomString;

  // The window needs to have a height and a width, so check
  // for dimensions of zero.  
  //
  // winWidth and winHeight are filled in by the parseArgs method,
  // above.

  if (ctxlogo winWidth > 0) 
    xWindowSize = ctxlogo winWidth;
  else
    xWindowSize = 230;

  if (ctxlogo winHeight > 0) 
    yWindowSize = ctxlogo winHeight;
  else
    yWindowSize = 230;

  // A zero in the x or y coordinate could also be negative,
  // so we need to wait to check the geometry flags in the
  // initialize method.
  xWindowOrg = ctxlogo winXOrg;
  yWindowOrg = ctxlogo winYOrg;


  //
  // The geomFlags instance variable is filled in by the 
  // parseX11Geometry method, above.
  //
  xPane initialize xWindowOrg, yWindowOrg, xWindowSize, yWindowSize,
    ctxlogo geomFlags;
  xTopLevelPane attachTo xPane;
  xCanvasPane attachTo xTopLevelPane;

  xPane map;
  xPane raiseWindow;


  ...

X Example Programs

Because the X Window System API is still being developed, for now you should look at the example programs provided with the Ctalk package. The xhello.c program mentioned above is one of them. The example programs from this manual and the Ctalk Language Reference are in the test/expect/examples-x11 and test/expect/x11-tests subdirectories. (They’re part of the Ctalk package.)


Next: , Previous: , Up: Top   [Index]