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.
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.
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.
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.
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 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;
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.
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.)