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 DisplaysMost 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 SystemGUIs that use a X server, window manager, and Xlib libraries. See X11Graphics.
GLUTThe GLUTApplication class provides support for the GLUT and
OpenGL 3-D graphics libraries and runs on many different platforms.
See GLUTGraphics
GLXThe 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.
Hello, world! in a Text WindowFor 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.
Pane and Stream ClassesWhen 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.
InputEvent ClassCtalk 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.
InputEventsIf 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.
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.
cleanupClean 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.
handleInputReceive InputEvent objects from the receiver’s
paneStream, and take action based on the keyboard input.
newInitializes the pane and its sub-panes, and sets whatever parameters are necessary.
parentAttach a sub-widget to its parent widget.
refreshUpdate 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.
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.
X11Pane ClassThe 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.
Points, Lines, and RectanglesNow 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;
PensIf 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.
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.
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 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.
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 ClassThe 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 ClassGLXCanvasPane 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;
}
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;
...
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.)