Loading GTK Builder files

: 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.

Dialogs

: 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.

Event Callbacks

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.

GTK startup and shutdown

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.

GTK test code

: 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.

Graphics in the Borland style

Global Data

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.

Internal operations

: 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  ( -- addr ) 0xffff0000 l, 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.

Application words

: 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.

A text editor in Glade

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.

Tools

: EdErrBox      \ zmessage --
Displays an error message box.

Status bar operations

: 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.

TextViews and buffers

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

Loading and saving 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.

Clipboard

: 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.

Callbacks

File Menu

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.

Edit menu

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.

Help menu

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.

Initialisation and termination

: 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.