The source code for his chapter can be found in the directory Examples/GTK. Additional directories provide tutorial code, including a small editor.
GTK provides a GUI interface and graphical toolkit for many operating systems. It also includes a GUI designer program called Glade whose output can be read and used by GTK applications. This chapter is not a tutorial on GTK programming.
Several GTK+ tutorials exist, including
http://developer.gnome.org/gtk-tutorial/
The book we used during development is Foundations of GTK+ Development, by Andrew Krause, published by APress, ISBN 978-1-59059-793-4.
The file gtkbindings.fth links to the libgtk library and allows GTK calls to be made from Forth. This provides VFX Forth with a powerful widget library that works on Windows, Linux and Mac OS X.
The file gtktools.fth provides basic functions including the main GTK message loop.
The file graphics.fth provides a graphics interface in the style of the old Borland BGI interface. This is still very useful for many applications.
The file GraphicsDemo.bld loads all needed files
and provides a number of demonstrations, of which ShowGraphics3
is the most useful.
The file TextEd/editor.fth contains a simple text editor based on a tutorial by Micah Carrick.
The code loads required libraries and defines necessary API calls. There are two points to be noted when using this code:
: CCB: \ #in #out "<name>" -- ; -- entry
Behaves as CallBack:
but enforces a C parameter-passing
convention.
: CCBproc: \ #in #out "<name>" -- ; -- entry
Behaves as CallProc:
but enforces a C parameter-passing
convention.
extern: long "C" g_signal_connect_object( void * instance, char * detailed_signal, void * c_handler, void * gobject, uint connect_flags ); extern: long "C" g_signal_connect_data( void * instance, char * detailed_signal, void * c_handler, void * data, int destroy_data, uint connect_flags ); : g_signal_connect \ instance detailed_signal c_handler data -- ulong 0 0 g_signal_connect_data ;
The GTK function gtk_signal_connect_full() is the library signal connection function; the C header file defines the other connect-style functions as macros. These words create the interface as it is more usually used.
extern: unsigned int "c" gtk_signal_connect_full( int * object, char * name, int * func, int * unsupported, int * data, void * destroy_func, int object_signal, int after );
: gtk_signal_connect \ object name func data --
Connect an event handler to a signal.
: gtk_signal_connect_after \ object name func data --
Connect an event handler to a signal, but at the end of the
event chain.
: gtk_signal_connect_object \ object name func slot_object
Connect an event handler which takes a GtkObject pointer
as its argument to a signal.
: gtk_signal_connect_object_after \ object name func slot_object
Connect an event handler which takes a GtkObject pointer
as its argument to a signal, at the end of the event chain.
GTK operates on a number of different object types - and these are frequently checked on call (with assertion failures if the wrong type is used). These words should cause the object to be cast correctly.
\ object -- cast_object
GTK_OBJECT
GTK_CONTAINER
GTK_BOX
GTK_WIDGET
GTK_WINDOW
struct /GError \ -- len
Definition of a GError structure.
: gtk-step \ -- flag
Iterate GTK's inner loop, process any events and exit immediately.
flag is returned true if a GTK event was processed.
: gtk-step-blocking \ -- flag
Iterate gtk's inner loop, process any events, wait until there are
events if none are waiting when the function was called.
: gtkPump \ --
Empty the GTK event queue.
: gui-Idle \ --
Wait for message or GTK events and process it/them.
: gui-BusyIdle \ --
If a message or GTK events are available, process them.
: gui-EmptyIdle \ --
While messages and GTK events are available, process them.
: gui-AppIdle \ --
Process messages until the GetMessage API call returns
zero in response to a WM_QUIT message. The VFX Forth variable
ExitCode
is set to the wParam value of this message.
-- This version includes the GTK inner loop. --
: installGTKhooks \ --
Install the GTK+ versions of the message pumps.
: gui-WaitIdle \ --
Wait for message or GTK events and process it/them.
: gui-BusyIdle \ --
If a message or GTK events are available, process them.
: gui-EmptyIdle \ --
While messages and GTK events are available, process them.
: installGTKhooks \ --
Install the GTK+ versions of the message pumps.
: gui-WaitIdle \ --
Wait for message or GTK events and process it/them.
: gui-BusyIdle \ --
If a message or GTK events are available, process them.
: gui-EmptyIdle \ --
While messages and GTK events are available, process them.
: installGTKhooks \ --
Install the GTK+ versions of the message pumps.
If GTK+ and Glade are not already installed, install them using the system's package manager.
Under Windows, we use the MinGW version of GTK+. Like much Open Source software built with gcc, it uses a C calling convention both for external functions and for callbacks.
We get our GTK + binaries from:
http://www.gtk.org/download/win32.php
The Windows version of GTK+ used by MPE is supplied as a directory tree that you place on your PC. During development, the simplest way to work is to add the bin folder to your PC's system path. Depending on your version of Windows, this is done from Control Panel -> System -> Advanced -> Environment Variables. You may have to reboot after changing the path.
If your application is being distributed into environments controlled by an IT department, it may not be possible to alter the system path. In this case make sure that the GTK libraries and executables are in the same folder as your application.
Glade is the GU designer for GTK+. At first, we tried to install Glade to use the version of GTK+ used for application run-time. This is a mistake! Installing a separate matched Glade/GTK+ pair permits much more reliable development. MPE gets Glade and GTK+ as one package from:
http://ftp.gnome.org/pub/GNOME/binaries/win32/glade3/
If you are feeling adventurous, go to:
http://glade.gnome.org/
We use the GTK-OSX package. You can either use the package built by MPE and supplied as gtkbins.tgz or you can work from the source package. At application run time, it is usually sufficient to use the prebuilt binaries if you use absolute paths to the GTK shared libraries. For development, install from the source package. One day, we'll find a suitable incantation to run Glade from the binary package.
Launch a terminal, select your home folder and unpack the package:
tar -zxvf gtkbins.tgz
Executables will then be in ~/gtkbins/bin and shared libraries in ~/gtkbins/lib. Edit gtkbindings.fth and other files such as gladetest.fth so that library references are correct.
During devlopment, it is convenient to use the source distribution, although it is tedious to install.
If you want to use the Glade designer and you already have MacPorts or Fink installed, you will need to create a new user and install GTK+ for that user in order to avoid conflicts.
Instructions for installing it are at:
http://sourceforge.net/apps/trac/gtk-osx/wiki/Build
Read these instructions fully and carefully before you start. In particular read the Snow Leopard section and apply the changes for a 32 bit build before running jhbuild. Then perform the three main GTK+ builds:
$ jhbuild bootstrap
$ jhbuild build meta-gtk-osx-bootstrap
$ jhbuild build meta-gtk-osx-core
Then build the libglade library needed at runtime and the Glade designer needed for development.
jhbuild build libglade
jhbuild build glade3
Executables will then be in ~/gtk/inst/bin and shared libraries in ~/gtk/inst/lib. Edit gtkbindings.fth and other files such as gladetest.fth so that library references are correct.
In order to run Glade properly, it should be launched by
$ jhbuild shell
$ glade-3 &
This incantation is required to persuade Glade to find its icons.
: zFindCallback \ zCbName -- entry true | 0
Search the default search order for the given callback name.
On success, the entry point is returned.
7 0 CCBproc: BuilderConnect_cb \ *Builder *object zSignal zHandler *ConnObj flags *User --
The main Glade signal connection callback. This is called for
each signal handler specified in the Glade file, i.e. handlers
named by the GUI designer. Handler names are looked up in the
current context. The words found are linked to the appropriate
object by way of the GTK signal connection bindings.
variable CurrBuilder \ -- addr
Holds the current builder handle.
: loadBuilderXML \ z$ -- builder|0
Load a Glade GtkBuilder XML file and return the pointer or
zero on error. On success, the connections are made and the
variable CurrBuilder
is set.
: BuilderObject \ z$ -- *widget
Obtain the widget pointer for the given named widget from the
current builder object.
: freeBuilder \ --
Free the current builder.
: runDlg \ dialog -- response
Given a *GtkDialog, run the dialog as a modal dialog
and return the result.
: ErrorBox \ zMessage zTitle parent --
Displays an error box with the given message, title and
parent window.
: AskSaveFileNameBox \ zaddr len parent -- flag
Given a buffer and parent window, ask for a file name. On
success, the filename is returned (clipped as required) in
the buffer as a zero terminated string. If no file name is
given the buffer is left unchanged.
: AskOpenFileNameBox \ zaddr len parent -- flag
Given a buffer and parent window, ask for an existing file name. On
success, the filename is returned (clipped as required) in
the buffer as a zero terminated string. If no file name is
given the buffer is left unchanged.
The GTK library calls back into Forth on various events. Here are some sample definitions. You may need to modify them or add new ones according to the complexity of your UI. You can also use this code to test your GTK installation.
Compile the file gtkbindings.fth. Then run
.libs .badExterns
This will display the load addresses of the libraries and show you any unresolved external functions. A library value of zero indicates that the library has not been found. If everything is good at this point, run the test window:
gtktest
which should display a "hello world" window.
: delete_event_fn \ *widget *event *data -- 0/1
Handles delete events. It returns zero so that the widget
is destroyed.
3 1 CCB: delete_event \ -- addr ; *widget *event *data -- 0/1
Callback for the delete event. Return 0 to perform the
default handling or return 1 to indicate that the callback
has done everything necessary.
: destroy_fn \ *widget data --
Handles the destroy event for the application. It calls
gtk_main_quit().
2 0 CCB: destroy_event
Callback for the destroy event.
: wd_destroy \ addr --
Destroy the widget
1 0 CCB: widget_destroy_cb \ -- entry
Callback to handle the destroy event.
0 value GTKstarted? \ -- x
Non-zero when GTK has been started.
0 value AppFinished? \ -- x
Non-zero when app must quit.
0 value gtk_main? \ -- x
Non-zero if gtk_main
is in use.
: noVfxGtk \ --
Mark the VFX Forth to GTK interface as unused.
: do_gtk_init { | temp[ cell ] -- }
Run gtk_init.
: do_gtk_main \ --
The tools version of gtk_main
.
: initGTK \ --
Call this to initialise the GTK system. Always call this
word to start the GTK system.
: GtkAppQuit \ --
The GTK+ app should finish.
: hello_world_window \ --
Launch the "hello world" window.
: gtktest \ --
Start GTK+, launch the hello world window, and wait until it
closes.
: hw \ --
As gtktest
but does not initialise GTK+ again.
struct /gwindow \ -- len
Structure to control a graphics window.
variable windows \ -- addr
Anchors chain for keeping track of all created windows.
CELL +USER CANVAS \ -- addr
The current drawing window.
: window> \ -- window
Get the current drawing window.
: penx \ -- addr
The current window's X coordinate for subsequent drawing
commands.
: peny \ -- addr
The current window's Y coordinate for subsequent drawing
commands.
: filled? \ -- addr
The current window's internal flag affecting drawing commands;
see filled
.
CREATE mycolor 0xffff0000 , 0xffff w, 0 w, 0 w,
Initial foreground color for new windows
: window-dims \ window -- w h
Get the dimensions of a window from the window structure.
: load-pixbuf \ zaddr -- pixbuf
Load an image into a Gtk_Pixbuf, which can be drawn to the
current window using PUT
.
: redraw { window -- }
Make changes to a window's graphics visible.
: ?redraw { window -- }
Internal - redraw a window only if it is "dirty" (affected
by any drawing commands). Immediately resets the window's
dirty flag, making it "clean" again
: ChainEach \ ... xt anchor -- ...
Execute xt on the contents of chain with the following structure:
link | ...
The given xt must have the stack effect
... link -- ...
Where link is the address of the link field in the structure.
You can pass your own values to each link, just remember to clean up afterwards.
1 1 CCBproc: timeout_event_cb \ 0 -- true ; -- entry
Callback run from a timer to redraw all "dirty" windows.
: +gtimer \ --
Start the graphics event timer.
: -gtimer \ --
Stop the graphics event timer
: winResized \ window --
Perform this when the window has been resized.
3 0 CCBProc: GframeCallback \ window event data -- ; -- entry
Callback to force window size to be updated and the window
redrawn.
3 0 CCBProc: GExposeCallback \ window event data -- ; -- entry
Callback to force window to be redrawn.
: filled> \ -- flag
Fetch the filled flag
: drawdest> \ -- pixmap gc
Fetch the 2 objects from the current window that are passed
to all GDK graphics functions.
: dirty \ --
Mark the current window as dirty, which signals the GUI's
internal timer to make changes to that window visible. Note
that the flag is reset by redraw
.
: COLOR: \ -- ; --
Builds a new GTK color. When the color is executed, the
foreground color is set. Use in the form:
COLOR: red 0xffff0000 , 0xffff w, 0x0000 w, 0x0000 w,
The following colours are predefined:
red green blue yellow orange magenta
cyan white black ltgrey grey
dkred dkgreen dkblue dkyellow brown
violet dkcyan dkgrey
: onto \ window --
Set the current target window structure for graphics commands.
: pen \ -- x y
Get the current drawing coordinates.
: at \ x y --
Set the current drawing coordinates.
: filled \ --
Makes the next command, such as rectangle or circle, filled
instead of stroked.
: line { destx desty -- }
Draw a line from the pen to (destx,desty).
: lineto \ destx desty --
Draw a line from the pen to (destx,desty) and set the pen to
(destx,desty).
: linerel \ dx dy --
Draw a line relative to the pen.
: linerelto \ dx dy --
Draw a line relative to the pen and move the pen to the end
of the line
: ellipse { width height -- }
Draw an ellipse defined by width, height.
The ellipse is positioned such that the pen points to the
top left corner of an imaginary rectangle around the ellipse.
: circle \ diameter --
Draw a circle.
The circle is positioned such that the pen points to the top
left corner of an imaginary square around the circle.
: rectangle { width height -- }
Draw a rectangle.
: putpixel \ --
Plot a single pixel.
: cleardevice \ --
Clear the current drawing window using the current color.
: put { pixbuf -- }
Draw a Gtk_Pixbuf to the current window at the current pen position.
: gwin: \ <name> -- ; -- window
Declare a named graphics window. The returned window is the
address of a /gwindow
structure.
gwin: MyWin \ -- window
: setupGwin { w h window -- }
Initialize a window control structure. This word is used to
create a new window. SetupGwin
cannot be used with windows
defined in Glade.
: initGladeGwin \ z$name builder window --
Use the Glade widget name (usually a drawing area) in the
Glade builder to set up the given /gWindow
structure.
: initGwin \ *widget gwindow --
Use the widget to set up the given /gWindow
structure.
: addEvent \ cbentry zname event window --
Add a callback to handle the name and event for a window
structure.
: enable-graphics { window -- }
Enable the window for graphics operations and set the initial
state.
The original design and C code is by Micah Carrick, whose tutorial is well worth studying. It is at:
http://www.micahcarrick.com/gtk-glade-tutorial-part-1.html
The Forth code presented here is liberally derived from that presentation and code.
To compile the text editor demo, CD to the directory containing editor.fth and then:
include TextEdDemo.bld
To run the editor from the Forth console:
runTextEd
The code will run unchanged on VFX Forth for Windows, Mac and Linux.
struct /TextEd \ -- len
Everything we need to know about the editor can be derived
from this structure.
0 value pTextEd \ -- addr
Holds the address of a structure for the current text editor.
#1024 constant /NameBuffer \ -- len
Largest file name.
/NameBuffer buffer: zFilenameBuffer \ -- zaddr
Buffer to hold current file name.
#2048 constant /StatusBuffer \ -- len
Size of the status buffer
/StatusBuffer buffer: zStatusBuffer \ -- zaddr
Text buffer for status bar.
: EdErrBox \ zmessage --
Displays an error message box.
: sbParams \ -- sb context
REturn the status bar parameters
: setStatus \ z$1 z$2 --
set the status buffer, merging the two strings. Then update
the status bar.
: clearStatus \ --
Clear the status bar.
: modified? \ -- flag
Return true if the current text has been modified and not saved.
: modified \ --
Mark the current text buffer as modified.
: unmodified \ --
Mark the current text buffer as unmodified.
: inactive \ --
Set the current text window as inactive (unresponsive).
: active \ --
Set the current text window as active (responsive).
: getCurrText \ -- text
Get a copy of the current text. When you are finished with
it, you must release it with g_free ( text -- )
.
: CurrFilename \ -- z$
REturn the current text file name.
: MustSave? \ -- flag
Return true if the buffer has been modified and the user
wants to save it.
: getSaveFileName \ --
Set the current text file name for saving.
: getOpenFileName \ --
Set the current text file name for loading.
: sbSaving... \ --
Show status bar as saving.
: sbLoading... \ --
Show status bar as loading.
: fileStatus \ --
Show filename on status bar.
: loadCurrFile \ -- *text 0 | -1
Load the contents of the current file. On success, return a
pointer to the text and zero. On failure, just return -1.
When finished with, the text pointer must be freed with
g_free
.
: loadCurrText \ --
Load the text of the current window from the current file.
No action is taken if the filename is null.
: writeCurrText \ --
Save the text of the current window to the current file.
No action is taken if the filename is null.
: saveCurrText \ --
As writeCurrText
but asks for a file name if one has
not been set.
: saveAsCurrText \ --
As writeCurrText
but always asks for a file name.
: checkedSave \ --
If text has been modified and the user wants saving,
write the text to a file.
: openCurrText \ --
Open a new file.
: newCurrText \ --
Start with an empty buffer.
: CurrSelection \ -- clipboard
Get the clipboard item for the current selection.
: doCut \ --
Do the cut operation.
: doCopy \ --
Do the copy operation.
: doPaste \ --
Do the paste operation.
: doDelete \ --
Do the delete operation.
2 0 CCBproc: on_new_MainFileMenu_activate \ *widget *editor --
Callback for the "New" button.
2 0 CCBproc: on_Open_MainFileMenu_activate \ *widget *editor --
Callback for the "Open" button.
2 0 CCBproc: on_save_MainFileMenu_activate \ *widget *editor --
Callback for the "Save" button.
2 0 CCBproc: on_SaveAs_MainFileMenu_activate \ *widget *editor --
Callback for the "Save As" button.
2 0 CCBproc: on_MainWindow_destroy \ *wiget *editor --
Callback for final destroy of main window.
3 1 CCBProc: on_MainWindow_delete_event \ *widget *event *editor -- 0/1
When the window is requested to be closed, we need to check if
they have unsaved work. We use this callback to prompt the user to
save their work before they exit the application. From the
"delete-event" signal, we can choose to effectively cancel the
close based on the value we return.
2 0 CCBproc: on_quit_MainFileMenu_activate \ *wiget *editor --
Callback for the "Quit" button.
2 0 CCBproc: on_Cut_MainEditMenu_activate
Callback for the "Cut" button.
2 0 CCBproc: on_Copy_MainEditMenu_activate
Callback for the "Copy" button.
2 0 CCBproc: on_Paste_MainEditMenu_activate
Callback for the "Paste" button.
2 0 CCBproc: on_Delete_MainEditMenu_activate
Callback for the "Delete" button.
2 0 CCBproc: on_About_MainHelpMenu_activate \ *widget *user -- ; -- entry
Callback to run the About dialog.
2 0 CCBproc: on_aboutdialog1_close \ *widget *user -- ; -- entry
Callback when about box is closed.
: loadTeGUI \ --
Load the text editor's GUI. After the file has been loaded,
the widgets we need to access are extracted and their object
pointers saved in a /TextEd
structure. The design file
object is then released.
: termTextEd \ --
free up the application data and perform termination actions.
: RunTextEd \ --
Run the text editor.