Abstract

⎕GTK is a GNU APL system function that allows an APL program to create and control a graphical user interface (GUI). The GUI is based on Gtk+ (Gimp Toolkit) version 3+ (aka. GTK 3+). This document describes, by means of simple examples, how ⎕GTK based GUIs can created and managed from GNU APL.

Philosophy

A modern GUI such as Gtk provides a powerful API for controlling almost every aspect of the GUI. A consequence of that power is an API that consists of thousands of functions. The entire Gtk API is spread over 11 different libraries:


$ pkg-config gtk+-3.0 --libs
-lgtk-3 -lgdk-3 -latk-1.0 -lgio-2.0 -lpangocairo-1.0 -lgdk_pixbuf-2.0
-lcairo-gobject -lpango-1.0 -lcairo -lgobject-2.0 -lglib-2.0

Each library contains tens, hundreds, or even thousands of symbols. Every symbol represents either a function or a variable, and the vast majority of symbols in the different libraries represent functions.

Table 1. Gtk API Symbols
Gtk Library Number of Symbols

TOTAL

9950

libgtk-3.so

4176

libgdk-3.so

632

libatk-1.0.so

265

libgio-2.0.so

1872

libpangocairo-1.0.so

33

libgdk_pixbuf-2.0.so

117

libcairo-gobject.so

33

libpango-1.0.so

405

libcairo.so

395

libgobject-2.0.so

427

libglib-2.0.so

1595

These numbers suggest that mapping every Gtk function to a corresponding APL function would create an almost unusable API at APL level. Even the approach used for ⎕FIO or ⎕SQL - using function numbers as APL axis elements - reaches its limits when used with almost 10000 functions.

Now, the huge number of API functions in the different Gtk libraries contrasts considerably with the moderate number of widgets (around 80) in a GUI and even more with number of actions (like clicking, marking text, typing text, etc.) that a user can do with each of the widgets.

If we look at the program glade, which is the default tool for constructing Gtk based GUIs, then we see that there are 12 top-level widgets (aka. "windows"), 20 containers (widgets containing other widgets), and 36 control widgets (most of them variants of the same base widget, like different types of buttons).

This discrepancy between the huge number of functions in provided by Gtk and rather small number of things that a user can do (like: click on a button, select an item in a selection box, enter text in some text entry, etc) or perceive (color, font, etc.) suggests that there might be a clever way to control a GUI without mapping each of the almost 10000 Gtk functions to a corresponding APL function in a 1:1 fashion.

⎕GTK uses a rigorous approach to reduce the number of functions:

  • Every GUI is required to be constructed with the GUI construction tool glade (which comes with GTK), This requirement reduces the need for all functions that are concerned with the instantiation of the GUI elements and their properties. It also speeds up the design of the GUI itself.

  • To the extent possible, visual widget aspects that are usually static (i.e. that do not change over time) such as colors or widget sizes are required to be specified with CSS (Cascading Style Sheets). This requirement further reduces the need for the according GTK functions. It also speeds up the design of the GUI itself. The downside is a somewhat static look-and-feel of the GUI, because, for example, colors cannot be changed after the GUI was instantiated. However, the benefit of fancy dynamic GUIs (if any) seems not to justify the complexity in the API that would be needed to support them.

  • Implementation on demand. ⎕GTK starts with a rather small (and therefore incomplete) set of GTK widgets and events, but has a simple way to extend that set. The mapping between ⎕GTK on one side and libgtk functions and events on the other side is defined in a single macro file Gtk/Gtk_map.def that can be easily extended to accommodate new widgets and signals,

Architecture

The philosophy above has been implemented in the following architecture.

  1. The Gtk GUI itself has been implemented in a separate process running the program Gtk_server. The Gtk_server is linked with the different Gtk libraries, so that Gtk itself remains outside GNU APL.

  2. The Gtk_server is started by ⎕GTK, and after that ⎕GTK communicates with Gtk_server over a socket pair. The communication consists of a sequence of TLV (Tag/Length/Value) buffers encoded with 33 ⎕CR and decoded with 34 ⎕CR (aka. ¯33 ⎕CR) at the APL end. A user action (e.g. a button click) results in an "event" (= a TLV buffer) received by APL while an "command" (sent from APL to the Gtk_server results in some change in the GUI. However, many details such as a button changing its form or its color when being pressed are handled by the Gtk_server server process (resp. one of the Gtk libraries) and are not communicated to APL. The encoding and decoding of TLVs that are between apl and &Gtk_server takes place inside ⎕GTK so that the APL programmer is not concerned with the details of this communication.


                                                          ╔════════════════════════════╗
                                                          ║ GUI-description.xml file   ║
                                                          ║  ↓ GUI-stylesheet.css file ║
                                                          ║  ↓  ↓                      ║
                                                          ║  ↓  ↓   ┌───────────────┐  ║
                                                          ║  ↓  ↓   │    GTK GUI    │  ║
    ╔════════════════╗      Commands                      ║  ↓  ↓   └─┬─────────┬┬──┘  ║
    ║ GNU APL     ┌──╨───┐  33⎕CR → → →         socket    ║┌──────────┴─┐   ┌───┼┴────┐║
    ║ application │ ⎕GTK ├───────────────────────-----────╫┤ Gtk_server │ ┌─┴───┴────┐│║
    ║ program.apl └───╥──┘  34⎕CR ← ← ←          pair     ║└────────────┘ │ GTK libs ├┘║
    ╚═════════════════╝     Responses, Events             ║               └──────────┘ ║
                                                          ╚════════════════════════════╝

  1. One Gtk_server handles a single top-level window and its children (see also Caveats.

  2. For GUIs that consist of multiple top-level windows, one apl interpreter process can start several Gtk_server processes.

Development Work-flow

The architecture above leads to the following work-flow.

  1. The programmer specifies the GUI by means of the glade GUI design tool that comes with Gtk. The result is an XML file that describes the GUI, The XML file can be changed later on as needed, so that the programmer can start with a subset of the GUI functionality and then extend that functionality in an incremental fashion.

  2. The programmer writes a CSS specification for the the widgets in the GUI. The CSS file specifies the styling (i.e. colors. sizes etc.) of the widgets. In theory, this step is optional, but in practice inevitable for a somewhat good-looking GUI.

  3. The programmer write a brief start-up code for the GUI in which the files created in steps 1. and 2. above are being loaded into the GUI.

  4. the programmer displays the GUI - either at the start of the APL application if the GUI shall be permanently visible or at some time if the GUI is, for example, only popped up in order to request input from the user of the APL application. In the latter case the GUI is a single window (a "modal dialog") which is polled from APL until some final action of the user has occurred, while the former case the APL application has to poll the GUI for events at rather short intervals. to poll ⎕GTK at regular intervals.


    ╔══════════════════════╗
    ║  GUI Design (glade)  ║   →  my-application.ui
    ╚══════════╤═══════════╝
               │
    ╔══════════╧═══════════╗
    ║ Widget Details (CSS) ║   →  my-application.css
    ╚══════════╤═══════════╝
               │
    ╔══════════╧═══════════╗
    ║   APL application    ║   →  my-application.apl
    ║ ──────────────────── ║
    ║      GUI start       ║   → loads my-application.ui and my-application.css
    ║ ──────────────────── ║
    ║   GUI event loop     ║   × polls and reacts to events (user actions)
    ╚══════════════════════╝

The example given below will be a modal dialog with the non-modal case left as an exercise for the reader.

Principles

As may have become obvious by now, there isn’t much coding left to be done for the APL application. After APL has started the GUI, the process running GNU APL and the Gtk_server send "signals" to each other in an asynchronous fashion and according to the following rules:

  • Every APL application that has a GUI (and uses ⎕GTK) starts its own Gtk_server(s). In a GUI with several top-level windows, each top-level window uses its own Gtk_server which is identified bu an integer (handle) in APL. Multiple Gtk_servers are independent of each other, and the coordination of events from between the windows must be performed in the APL application.

  • GNU APL and Gtk_server then send signals (aka, TLV buffers) to each other. A signal is a sequence of bytes, starting with a 4-byte tag, followed by a 4-byte data length, and concluded with 0 or more data bytes as indicated by the signal length.

  • The encoding of the signals is the same in both directions, but we call signals from APL to Gtk_server commands and signals from Gtk_server to APL either responses (if they contain the result of a prior command), or else events (which are caused by a user’s activity such as clicking a button.

    • Commands are used to modify the GUI in some way, for example to enter an initial value into a text-entry widget.

    • Events are sent by the Gtk_server upon final user actions such as pushing a button, or pressing ENTER after having modified the text in a text-entry widget.

  • Normally larger amounts of data are not sent in events, but in command responses, Assume, for example, a user has entered some (possibly larger) text into a (hypothetical) text-editor widget and leaves the text-editor. The text-editor would then only send an event to APL indicating that some new text is available. The new text itself, however, is not packed into that event but needs to be fetched from the text-editor widget in the Gtk_server by means of an according retrieval command. The new text is then sent from the Gtk_server to APL in the response to the retrieval command. In other words, larger amounts of data are usually pulled by apl from a Gtk_server rather than pushed by the Gtk_server into APL. The reasons for this approach are:

    • the retrieval commands are usually needed anyway, It is often easier to specify the initial content of a widget in glade than to set it from the APL application.

    • the events become more uniform (not differing by different types of content). This, in turn, allows the same event to be used by different widgets that differ only by the type of data that they produce.

  • As a consequence, most events contain only the type of event and the widget that has sent the event.

  • Every signal is a TLV buffer whose length is dictated by the length of the Value component. Such a TLV buffer is therefore already determined by an integer tags and the value bytes V; the length is then ⍴V. In APL it is therefore sufficient to specify only T and V as a vector TV←Tag,Value whose first element Tag = ↑TV identifies a particular command or event, and the rest 1↓TV is a (frequently empty) argument of that command or event. The GNU APL system functions 33 ⎕CR resp. 34 ⎕CR have been tailored to encode resp. decode tag/value vectors TV into/from the TLV buffers exchanged between APL and Gtk_server.

  • Sending of a TLV from APL to Gtk_server is done by encoding the TLV with 33 ⎕FIO TLV, followed by a single write() (similar to ⎕FIO[42]) onto the connection (the "Handle") between APL and Gtk_server. Sending of only part of a TLV (for example, first the tag in one write() and then the length and the value in a second write()) is not allowed in either direction because that would hang the other side. That tags are divided into the following ranges:

    • 1…999: commands that have no response

    • 1001…1999: commands that have a response (i.e. that expect a result)

    • -1999…-1000 error indications for commands 1000…1999. If command 1xxx fails in Gtk_server, then Gtk_server sends -1xxx to APL.

    • 2001-2999: responses (2xxx is the response for command 1xxx)

    • 3001-3999: events

  • Receiving from Gtk_server is done with single read() call (like ⎕FIO[43]) from the handle followed by decoding the received bytes with 34 ⎕CR.

Note the details of TLV encoding and tag ranges are only mentioned for completeness and are not needed by the APL programmer.

Detailed Example

In the following, the Implementation of a simple GUI will be described step-by-step. The GUI will consist of a window that contains two labels (= fixed texts), two text entry boxes, and a button that the user can push to end the text entry.

Step 1: Defining the GUI with Glade

Start glade without a filename:


$ glade

A window with four larger areas pops up:

glade-0.png

The four areas are:

  1. A widget selection area on the left,

  2. A GUI visualization in the middle (empty)

  3. A GUI structure on the (top-) right (empty), and

  4. A widget property area on the (bottom-) right (empty).

The widget selection area is divided into several widget groups named

  • Actions,

  • Toplevels,

  • Containers,

  • Control and Display, and

  • GTK+ Unix Print Toplevels

Tip If you move the mouse pointer over one of the widgets, then the widget is highlighted and a tool-tip showing the type of the widget pops up.

Step 1a: Add top-level Window

Drag the first widget of type Window from the Toplevels group and drop it into the GUI visualization area. That is, move the mouse pointer over the widget, push and hold down the left mouse button, move the mouse pointer to the GUI visualization area in the middle, and release the mouse button.

You will now see a new rectangular are in the GUI visualization area, a widget item named window1 in the GUI structure, and the properties of the new widget below.

Your GUI now consists of one widget. You see the grid as child grid1 of window1 in the GUI structure.

Step 1b: Add a positioning Grid

Next, drag the widget of type Grid onto the window1 in the middle and enter 6 rows / 10 columns in the dialog that pops up.

This grid is a helper-widget for controlling the placement of other widget inside the window1.

Step 1c: Add labels and text entry widgets to the positioning Grid

Next, drag the widget of type Label from the "Control and Display" group onto row 2, column 2 of the grid and another one of type Label onto row 3, column 2 of the grid. Similarly, drag 2 widgets of type "Text Entry" to rows 2 and 3, column 4,

Unfortunately this makes the grid uneven because the newly placed widgets are larger than the fields in the grid. You can fix that by:

  • selecting each widget in the GUI structure,

  • selecting the Packing tab in the properties area, and

  • setting the Width property from its default 1 to, say, 6.

That tells glade to use 6 grid cells instead of 1, so that the grid looks almost normal again.

At this point you can change the property label under the General tab to that text that the label should have. We choose Employee for label2 and Position for label2.

Step 1d: Add an OK button and save the GUI

Finally, drag the widget of type Button from the Control and Display group to row 5, column 5, then set its width to 2, and its label to OK (you need to scroll down in the General tab to see that property).

Then SAVE THE GUI !!! in glade's menu-bar under File → Save As and give it the filename my-application.ui.

In glade, the GUI should now look like this:

glade-1.png

At this point you can exit glade. glade has produced the file my-application.ui, which is an XML file describing the GUI, Its content is this:


<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.16.1 -->
<interface>
  <requires lib="gtk+" version="3.10"/>
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <child>
      <object class="GtkGrid" id="grid1">
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <child>
          <object class="GtkLabel" id="label1">
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Employee</property>
          </object>
          <packing>
            <property name="left_attach">1</property>
            <property name="top_attach">1</property>
            <property name="width">1</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkLabel" id="label2">
            <property name="name">label2</property>
            <property name="visible">True</property>
            <property name="can_focus">False</property>
            <property name="label" translatable="yes">Position</property>
          </object>
          <packing>
            <property name="left_attach">1</property>
            <property name="top_attach">2</property>
            <property name="width">1</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkEntry" id="entry1">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
          </object>
          <packing>
            <property name="left_attach">3</property>
            <property name="top_attach">1</property>
            <property name="width">6</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkEntry" id="entry2">
            <property name="visible">True</property>
            <property name="can_focus">True</property>
          </object>
          <packing>
            <property name="left_attach">3</property>
            <property name="top_attach">2</property>
            <property name="width">6</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <object class="GtkButton" id="button1">
            <property name="label" translatable="yes">OK</property>
            <property name="name">OK-button</property>
            <property name="visible">True</property>
            <property name="can_focus">True</property>
            <property name="receives_default">True</property>
            <signal name="clicked" handler="clicked" swapped="no"/>
          </object>
          <packing>
            <property name="left_attach">4</property>
            <property name="top_attach">4</property>
            <property name="width">2</property>
            <property name="height">1</property>
          </packing>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
        <child>
          <placeholder/>
        </child>
      </object>
    </child>
  </object>
</interface>

Step 2: Starting the GUI from APL

The GUI is not yet finished, but we can already look at it from APL. In the following we assume that:

  • The GUI file is /home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui

You most likely need to adjust there paths on your machine. Then start GNU APL and enter the following:


      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui'
      Handle ← ⎕GTK GUI_path             ⍝ fork Gtk_server and connect to it

We see a new window at the top-left corner of the screen (that position can be changed by window properties in glade later on):

glade-2.png

You can then stop the GUI with )CLEAR before leaving APL with )OFF:


      )CLEAR
      )OFF

Step 3: Styling the GUI with CSS

There exist numerous functions for controlling how widgets look line (colors, fonts, sizes, etc). However, constructing a GUI using these functions is a tedious and error-prone task.

For that reason, ⎕GTK does not provide access to all these functions. Instead, one can specify these properties by means of a standard CSS (cascading style sheet) file as the (optional) left argument of dyadic ⎕GTK:


      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui'
      CSS_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.css'
      Handle ← CSS_path ⎕GTK GUI_path             ⍝ fork Gtk_server and connect to it

Consider the following CSS example, (file my-application.css):


/* general button */
.BUTTON { color: #F00;      background: #4F4; }

/* the OK button */
#OK-button { color: #F00; background: #FFF; }

It says that buttons should, in general have a green background and a red foreground (= text color), but that the button with the name "OK-button" in the XML file my-application.ui shall have a white background instead. The relevant name in CSS is not the ID: in the General tab in glade, but the Widget Name: property in the Common tab in glade.

The "general button" has no effect in our simple example because it has only one button and the stronger #OK-button clause wins over the more general (and thus weaker) .BUTTON clause.

With the my-application.css example file above we now get this:

glade-3.png

Step 4: Managing windows

Monadic ⎕GTK with integer argument 0 returns a (possibly empty) integer vector containing all window handles that are currently open.

Dyadic ⎕GTK with integer right argument 0 and integer left argument Ah closes the window whose handle is Ah.

Note there is a summary of all ⎕GTK syntax variants (a ⎕GTK cheat sheet) in the Appendix of this document.

      )CLEAR
      ⎕GTK 0   ⍝ get window handles (empty)

      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui'
      CSS_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.css'
      Handle ← CSS_path ⎕GTK GUI_path             ⍝ fork Gtk_server and connect to it

      ⎕GTK 0   ⍝ get window handles (now there is one window)
6

      6 ⎕GTK 0   ⍝ close window handle 6
0

The handle above (6) can, in theory, also be used with ⎕FIO functions like fread() if you now what you are doing. This practice is, however, dangerous because it might create inconsistencies between ⎕GTK and ⎕FIO. For example, closing a ⎕GTK handle with ⎕FIO is technically possible, but usually bad idea.

Please note that a window can normally closed from two ends: from APL with Ah ⎕GTK 0 and from the GUI via Close in the top-left window menu. The latter may, however, close the window but leave the Gtk_server process that was running the window alive. You should therefore aim at always closing windows from APL, possibly by connecting window close signals to APL.

Step 5: Managing widgets

A window is a (top-level) widget that usually contains other widgets. When you place a new widget in glade, then glade assigns a unique ID to the widget and makes sure that the id remains unique.

Caution ⎕GTK requires that you do not change the ID assigned by glade (even though glade allows you to do that). The ID assigned by glade is found as property ID: under the General tab and shall not be confused with the widget name: under the Common tab. The ID: property is used in the direction APL → Gtk_server to address widgets, while the widget name: property is used in the reverse direction APL ← Gtk_server to identify the widget that has sent a signal.

The ID consists of class name followed by an instance number, for example "window1", ⎕GTK uses the class name in the ID to construct the function name in GTK that shall be called.

⎕GTK has different function signatures, which follow a simple rule:

  • functions that address a GUI (opened with some .ui file) have no axis argument

  • functions that address widgets inside a GUI have an axis argument

  • the axis argument is: H_ID ← H, ID where H is the GUI and ID is the ID discussed above

address a GUI or window) have an axis argument. The axis argument is an integer H (which is the window handle returned by

All properties are strings, even though the content of some widgets can only be numbers. Currently only a small subset of the many existing GTK functions implemented in ⎕GTK resp. Gtk_server. However, the mapping from ⎕GTK functions to functions in libGtk can be easily extended by:

  • adding new mappings to src/Gtk/Gtk_map.def

  • recompiling and re-installing GNU APL

The second step also updates the ⎕GTK Cheat Sheet in the Appendix below. We extend our example to set the initial value of the employee to "John Doe",


      )CLEAR
      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui'
      CSS_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.css'
      Handle ← CSS_path ⎕GTK GUI_path             ⍝ fork Gtk_server and connect to it

      H_ID ← Handle, "entry1"            ⍝ the Employee: entry
      "John Doe" ⎕GTK[H_ID] "set_text"   ⍝ set the entry to  "John Doe"

      6 ⎕GTK 0   ⍝ close window handle 6
0

The GUI now looks like this:

glade-4.png

Finally, we can read data back from widgets. The following example is rather crude (just good enough to demonstrate the access to a widget’s content), but we will see a smarter way in the next chapter (Managing Events). For the moment we simply give the user 20 seconds time to enter data in the "Position:" entry and read the entry data after that time has elapsed (and regardless of whether the user has entered any data or not).


      )CLEAR
      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui'
      CSS_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.css'
      Handle ← CSS_path ⎕GTK GUI_path    ⍝ fork Gtk_server and connect to it

      H_ID ← Handle, "entry1"            ⍝ the Employee: entry
      "John Doe" ⎕GTK[H_ID] "set_text"   ⍝ set the entry to  "John Doe"

      "Please enter the Position: within 20 seconds..."
      ⊣⎕DL 20   ⍝ wait 20 seconds
      "End of input interval"

      H_ID ← Handle, "entry2"            ⍝ H_ID is the Position: entry
      ⎕GTK[H_ID] "get_text"              ⍝ get the content of entry2

      6 ⎕GTK 0   ⍝ close window handle 6
0

Let Bint be:

  • the (positive) function number B if B is a positive integer scalar, or

  • the function number corresponding to B if B is a function name like "set_text" above.

Then result of ⎕GTK[H_ID] B is:

  • Bint if the corresponding Gtk function was successful but returns no data (which is typical is for all set_XXX() functions like set_text() above), or

  • -Bint if the corresponding Gtk function has failed, or

  • a vector Bint, DATA if the corresponding Gtk function was successful and has returned the result DATA (which is typical is for all get_XXX() functions like get_text() above).

Therefore, even though some Gtk functions (i.e. those having a C/C++ result type of void) do not return values, ⎕GTK always returns a value.

Step 6: Managing Events

In the example developed so far The transfer of data to and from widgets was initiated from APL. Of course, giving a user 20 seconds time to enter some data is a ridiculous approach for a serious application program. What is needed instead is a way for the GUI to indicate that something of interest has happened, such as the user pressing the OK button after she has finished her input. Another option would have been to indicate the end of input by hitting the return key.

If an application program having a GUI is written C/C+/+, then events such as pressing of a button or a key on the keyboard are usually forwarded to the application by means of callback functions. Whenever the events used by the application change - typically accompanied by a change of the GUI - then the application needs to be recompiled so that the callbacks are properly updated,

Since APL normally has no such thing as a callback function, and since we do not want to recompile Gtk_server for every APL application, the following approach is used:

  • Gtk_server currently provides a rather small set of callback functions:

    • clicked

    • activate

    • changed

    • draw

    • destroy

  • in the glade GUI design, the "real" events (called "signals" in the Gtk documentation) are connected to the above callback functions. An event emitted by a widget then causes the connected callback function to be called.

  • every callback inserts a single event into an event queue inside ⎕GTK.

  • ⎕GTK is be used poll events out of the event queue. The event queue in ⎕GTK decouples the creation of an event from the handling of the event.

Note: The Gtk widget class can currently emit around 70 different signals, while Gtk_server currently provides only a rather small set of callback functions. This set is supposed to grow over time as the need for new callbacks arises. However, the plan is not to end up with 70 callbacks in Gtk_server,

Technically speaking a set of callbacks is sufficient for sending and identifying every signal in APL if:

  • for every signal with arguments a1, a2, … aN of type t1, t2, … tN in Gtk there is at lease one matching callback in Gtk_server to which the signal can be connected, and

  • for every widgets that can emit N different signals with the same argument types (i.e. t1, t2, … tN) there are N different callbacks to which these signals can be connected.

In short this means that every signal can be connected to a callback and all signals emitted by a widget can be connected to different callbacks. A set of callbacks satisfying the above then:

  • makes Gtk_server more compact, and

  • reduces the size of the APL function(s) decoding the events, but

  • possibly decreases the readability of the event decoder because a signal name may not reflect its true meaning,

6a. The life-cycle of an Event

It might be useful to briefly describe the entire life-cycle of an event. Details (like how to do things) will follow after that. We use the example discussed so far.

The entire life-cycle of an event is this:

  1. Prerequisite: In glade, the signal named clicked of the OK button was connected to the callback function clicked in Gtk_server

  2. The user clicks the green OK button

  3. Gtk calls the function clicked() in the Gtk_server.

  4. function clicked() in the Gtk_server fries() the event onto the connection between APL and Gtk_server.

  5. APL read()s the event from the connection between APL and Gtk_server. and stores it into its event queue.

  6. the APL application polls the event queue out of the queue. At this point the event has become an APL data structure in the APL workspace. The life-time of the event ends here, but the (application-specific) processing of the APL data usually continues a little further. In our example, the OK button was meant to let the user tell when her input was finished, so the next actions would be to read the contents of the entry1 and entry2 widgets as already demonstrated above.

Now to the details …

6b. Connecting Signals to Gtk_server Callback Functions

Every signal of a widget that shall be sent to APL needs to be connected in glade:

  • start glade with our example GUI file:


$ glade my-application.ui

  • select the button1 widget by clicking onto the OK button in the GUI visualization area. The single widget window1 in the widget structure area (top-right of the glade window) will open and show its children. One of the children - button1 is now shown as being selected.

  • select the "Signals" tab in the widget property area (bottom-right of the glade window)

  • select the row with signal "clicked", which highlights the row in blue

  • click on the selected row below the column "Handler", which opens a small text entry field.

  • in the text entry field, enter "clicked", which is the name of the handler function in Gtk_server that shall be called when widget button1 was clicked. [If you need to pass another object to the callback, then enter "clicked_1" instead of "clicked" and select the object in the dialog that pops up if you click on "User Data:" in the blue row, Don’t do that in this example.]

  • Finally, save the GUI in glade's menu-bar under File → Save.

For those interested in the details, glade has added one line to our file my-application.ui:


             <signal name="clicked" handler="clicked" swapped="no"/>

At this point the Prerequisite for sending the clicked signal from button1 to APL is met, and steps 2-5 in the life-cycle of an event happen automatically at the proper point in time, What remains is step 6.

6c. Polling the Event Loop in APL

The event loop can be polled from APL in two fashions: blocking or non-blocking. Blocking means that the APL functions that do the poll returns an event to APL immediately if the event has occurred already (e.g. the user has clicked button1 before the event queue was polled), and otherwise waits until that event has occurred. Non-blocking means that the APL functions that do the poll always return immediately, but return a special event with number 0 to indicate that the event queue was empty at the time when the poll function was called.

Non-blocking calls are inevitably performed in some kind of loop at APL level and therefore are more complicated. Blocking calls are simpler to program but stop the APL application until the next event (which may or not be the event that the application is waiting for) has occurred.

Another aspect is the scope of a poll. The APL application can either poll for an event from a single GUI, or for an event in any of the GUIs that were started from the interpreter instance.

The function for a blocking poll is Z←⎕GTK 1; The functions for a non-blocking poll is Z←⎕GTK 2.

The return value Z of a non-blocking poll is the integer scalar 0 if the event queue is empty. In all other cases Z is either a 3-element APL vector Gi,Ws,Es or a 4-element APL vector Gi,Ws,Es,Us if an event with User Data: Us is received, where:

  • G is an integer scalar for the handle of the GUI handle in which the event was created,

  • Ws is the name of the widget that has created the event (button1 in our example),

  • Ws is the name of the signal in glade (clicked in our example), and

  • Us (if present) is the name of an object specified as User Data: in glade.

6d. Waiting for a Click Event from the OK Button

We are now ready to replace the awkward 20 seconds interval in our example by a more appropriate one: we give the user as much time as she needs to fill out the Employee: and Position: fields until she pushes the OK-button to indicate that the entry of data is complete.

Since out GUI only has one relevant event (clicking the OK button; the editing of the Employee: and Position: fields is managed internally by Gtk), we keep things simple and use a blocking poll of the ⎕GTK event queue. The connection from the OK-button to the GTK callback clicked() was already done in Step 6b. above (and we assume that the updated my-application.ui is used). All that is needed then is to replace:


      "Please enter the Position: within 20 seconds..."
      ⊣⎕DL 20   ⍝ wait 20 seconds
      "End of input interval

by:


      ⊣⎕GTK 1   ⍝ wait for click on the OK button

The example therefore becomes:


      )CLEAR
      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui'
      CSS_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.css'
      Handle ← CSS_path ⎕GTK GUI_path    ⍝ fork Gtk_server and connect to it

      H_ID ← Handle, "entry1"            ⍝ the Employee: entry
      "John Doe" ⎕GTK[H_ID] "set_text"   ⍝ set the entry to  "John Doe"

      ⎕GTK 1                             ⍝ wait for click on the OK button

      ⍝ [ at this point the user enters "Engineer" into entry2 and then pushes the OK button ]

      H_ID ← Handle, "entry2"            ⍝ H_ID is the Position: entry
      ⎕GTK[H_ID] "get_text"              ⍝ get the content of entry2

      Handle ⎕GTK 0                      ⍝ close window handle 6
0

6e. Wrap-up

The last thing to do is to put our example code into a defined function. We remove the H_ID variable (which was only used to make Step 5 above explicit), and we hide useless return values from ⎕GTK 1 and ⎕GTK[] "get_text" with ⊣:


∇Z←get_NAME_and_POSITION;GUI_path;CSS_path;Handle
 GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.ui'
 CSS_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/my-application.css'
 Handle ← CSS_path ⎕GTK GUI_path              ⍝ fork Gtk_server and connect to it
 ⊣⎕GTK 1                                      ⍝ wait for click on the OK button
 Z ←   ⊂1↓⎕GTK[Handle, "entry1"] "get_text"   ⍝ get the content of entry1
 Z ← Z,⊂1↓⎕GTK[Handle, "entry2"] "get_text"   ⍝ get the content of entry2
 ⊣Handle ⎕GTK 0                               ⍝ close the GUI handle
∇

Step 7 (optional): Custom Drawings

Gtk provides the widget type GtkDrawingArea that can be used to create, for example, a canvas onto which one can draw lines, circles, texts, and so on. In order to simplify the drawing onto a canvas from APL, ⎕GTK provides special functions for this purpose. The simple example described in the following explains the steps needed to draw a red circle onto a white background.

7a. The GUI

In glade, design a GUI with a top-level widget of type Window that has one child of type DrawingArea. That child is the canvas on which the circle will be drawn. In the Common tab of the canvas, set the property Width request to 400 and Height request to 300, and store the GUI as file circle.ui. That defines the size of the canvas, In the Signals tab, set the handler for signal draw to do_draw. The resulting XML file circle.ui is then:


<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.16.1 -->
<interface>
  <requires lib="gtk+" version="3.10"/>
  <object class="GtkWindow" id="window1">
    <property name="can_focus">False</property>
    <child>
      <object class="GtkDrawingArea" id="drawingarea1">
        <property name="width_request">400</property>
        <property name="height_request">300</property>
        <property name="visible">True</property>
        <property name="can_focus">False</property>
        <signal name="draw" handler="do_draw" swapped="no"/>
      </object>
    </child>
  </object>
</interface>

7b. APL Start-up Code

The start-up code for the GUI is similar to the previous example, except that no CSS file is used:


      )CLEAR
      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/circle.ui'
      Handle ← ⎕GTK GUI_path    ⍝ fork Gtk_server and connect to it

That code alone creates an empty canvas. That empty canvas can then be filled by means of drawing commands as described below.

7c. APL Drawing Code

A single drawing command is a string starting with a keyword followed by parameters. An entire drawing is a nested vector of drawing commands. Our simple example uses four drawing commands: two for filling the canvas background and two for drawing the red circle. A real-world example would, of course, consists of many more drawing commands. GNU APL provides two convenient methods for creating vectors of strings: ⎕INP for strings entered in immediate execution mode (and therefore also for apl scripts) and multi-line strings inside defined functions.

Our example in immediate execution mode or in an APL scripts could use a variable, say DrawCmd, that is constructed with ⎕INP:


      DrawCmd ← ⎕INP '▄'
background  255 255 255          ⍝ white background
fill -color 255 0 0              ⍝ red ellipse at 200:150 with radii 100 and 75
ellipse     (200 150) (100 75)
▄

An alternative to a variable is a niladic defined function using multi-line strings:


∇Z←DrawCmd
 Z←1↓"
background  255 255 255          ⍝ white background
fill -color 255 0 0              ⍝ red ellipse at 200:150 with radii 100 and 75
ellipse     (200 150) (100 75)"
∇

In both cases is DrawCmd a 4-element vector of strings. Once the DrawCmd are constructed they are send to the drawing area widget:


      H_ID ← Handle, "drawingarea1"
      DrawCmd ⎕GTK[H_ID] "draw_commands"

7d. Wrap-up

Putting the pieces above together we get:


      )CLEAR
      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/circle.ui'
      Handle ← ⎕GTK GUI_path    ⍝ fork Gtk_server and connect to it

      DrawCmd ← ⎕INP '▄'
background  255 255 255          ⍝ white background
fill-color  255 0 0              ⍝ red ellipse at 200:150 with radii 100 and 75
ellipse     (200 150) (100 75)
▄

      H_ID ← Handle, "drawingarea1"
      DrawCmd ⎕GTK[H_ID] "draw_commands"

The GUI created by the APL code above then looks like this:

glade-5.png

A slightly more advanced example that uses the same XML file circle.ui but different drawing commands to show additional features (lines, text, objects with and without borders is:


      )CLEAR
      GUI_path ← '/home/eedjsa/projects/juergen/apl-1.7/HOWTOs/circle.ui'
      Handle ← ⎕GTK GUI_path    ⍝ fork Gtk_server and connect to it

      DrawCmd ← ⎕INP '▄'
background  208 208 255    ⍝ blueish background
line-color  0 0 0 255      ⍝ black opaque pen → areas have a border
line-width  200%
text        (40 20) shapes with line-width = 200%
line        (0 140) (400 140)
fill-color  0 160 0        ⍝ green rectangle...
rectangle   (20 60) (80 100)
fill-color  255 255 0      ⍝ yellow circle...
circle      (130 80) 30
fill-color  255 0 0        ⍝ red ellipse...
ellipse     (250 80) (50 30)
fill-color  128 128 255    ⍝ blue triangle...
polygon     (320 60) (380 60)  (350 100)
line-color  0 0 0 0   ⍝ transparent pen → areas have no border
font-family monospace
text        (40 170) shapes without a border
fill-color  0 160 0        ⍝ green rectangle...
rectangle   (20 210) (60 250)
fill-color  255 255 0      ⍝ yellow circle...
circle      (130 230) 30
fill-color  255 0 0        ⍝ red ellipse...
ellipse     (250 230) (50 30)
fill-color  128 128 255    ⍝ blue triangle...
polygon     (320 210) (380 210)  (350 250)·
▄

      H_ID ← Handle, "drawingarea1"
      DrawCmd ⎕GTK[H_ID] "draw_commands"

It produces the following GUI:

glade-6.png

7e. Notes

The following notes may be helpful when creating custom drawings.

  • The Appendix contains a table that lists the syntax of all drawing commands that are currently understood by ⎕GTK.

  • Every call of DrawCommands ⎕GTK[] "draw_commands" overrides the previous one. If a drawing cannot be created in one go (like above) then you need to collect pieces into a single DrawCommands variable before calling ⎕GTK (once). The need to construct a drawing in pieces typically occurs when the drawing commands are being computed (e.g. when writing a data plotting application).

  • Opaque colors are specified as a 3-element vector containing the red, green, and blue component of each color. Each color component is an integer value between 0 and 255 (inclusive), The background is always opaque.

  • Transparent colors are specified as a 4-element vector containing the red, green, and blue component of each color (o-255), and an opacity value between 0 (fully transparent) and 100 (fully opaque).

  • Most graphical objects are controlled by two colors:

    • A line color (called the pen color in other places), and

    • A fill color (called "brush* color elsewhere).

    • The opaqueness of the line and fill colors can be used to control how a 2-dmensional object is being painted:

      • Fill color opacity = 0: (only) the border (in line color), or

      • Line color opacity = 0: (only) the area inside the border (in fill color), or else

      • a combination of the above according to the two opacity values

    • A pen width of 0 has the same effect as a pen color opacity of 0, although for different reasons.

  • Lines are 1-dimensional and therefore have no fill color. They are painted with the line color.

  • In order to simplify the drawing of objects, the colors are not assigned to individual objects, but are sent once using the one of the following commands:

    • background R G B

    • fill-color R G B [A]

    • fill color R G B [A]

    • the optional parameter A controls the opacity

  • these color commands are then applied to subsequent drawing commands,

  • Valid font names are system-specific, but at least the generic names used in CSS2 (i.e. serif, sans-serif, cursive, fantasy, monospace are supposed to work,

Appendix

The ⎕GTK Cheat Sheet

In dwhe Table 2 below, it is assumed that:


      ⍝ H is a window handle (from H ← Zh←⎕GTK "filename.ui")
      ⍝ ID is a string containing the id= property of some widget in filename.ui
      H_ID ← H, ID   ⍝ e.g.  6,"entry1"; H_ID uniquely identifies a widget

Table 2. ⎕GTK Syntax Variants (Signatures)
Syntax Fun
(Bi)
Result/Action

Zh←⎕GTK "file.ui"

-

Zh is a handle for a new window according to glade file file.ui (no CSS)

Zh← "file.css" ⎕GTK "file.ui"

-

Zh is a handle for a new window according to CSS file file.css and glade XML file file.ui

Zi← ⎕GTK 0

-

Zi is an integer vector containing all open window handles

Zi← Ah ⎕GTK 0

0

close window handle Ah, return 0 on success, or errno otherwise

Zi← ⎕GTK 1

1

blocking poll for the next event

Zi← ⎕GTK 2

2

non-blocking poll for the next event (Zi≡0 means: no event)

Zi←Ah ⎕GTK 3

3

increment the (debug-) verbosity of Gtk_server (subject to be changed)

Zi←Ah ⎕GTK 4

4

decrement the (debug-) verbosity of Gtk_server (subject to be changed)

Text ⎕GTK[H_ID] 1 --or--
Text ⎕GTK[H_ID] "set_text"

1

set the text in e.g. a text entry

Text ← ⎕GTK[H_ID] 2 --or--
Text ← ⎕GTK[H_ID] "get_text"

2

return the text in e.g. a text entry

Text ⎕GTK[H_ID] 3 --or--
Text ⎕GTK[H_ID] "set_label"

3

set the text of e.g. a label

Fraction ⎕GTK[H_ID] 4 --or--
Fraction ⎕GTK[H_ID] "set_fraction"

4

set the progress shown by a progress bar

Fraction ← ⎕GTK[H_ID] 5 --or--
Fraction ← ⎕GTK[H_ID] "get_fraction"

5

return the progress shown by a progress bar

Level ⎕GTK[H_ID] 6 --or--
Level ⎕GTK[H_ID] "set_value"

6

set the level shown by a level bar

Level ← ⎕GTK[H_ID] 7 --or--
Level ← ⎕GTK[H_ID] "get_value"

7

return the level shown by level bar

Selected ⎕GTK[H_ID] 8 --or--
Selected ⎕GTK[H_ID] "set_active"

8

set the selected item of a combo box (-1 for none)

Selected ← ⎕GTK[H_ID] 9 --or--
Selected ← ⎕GTK[H_ID] "get_active"

9

return the selected item of a combo box (-1 for none)

DrawCmd ⎕GTK[H_ID] 10 --or--
DrawCmd ⎕GTK[H_ID] "draw_commands"

10

set the commands needed to draw a drawing area

SX_SY ⎕GTK[H_ID] 11 --or--
SX_SY ⎕GTK[H_ID] "set_area_size"

11

set the area size to SX_SY >> 16 : SX_SY & 0xFFFF

Origin ⎕GTK[H_ID] 12 --or--
Origin ⎕GTK[H_ID] "set_Y_origin"

12

set the Y-origin to the top (1) or to the bottom (0)

⎕GTK 1 and ⎕GTK 2 return events according to Table 3 below. The elements of the events have the following meaning:

  • Gi (integer): the handle (= Gtk_server instance) on which the event was received (the handle was returned by ⎕GTK "file.ui").

  • Ns (string): the widget property Widget Name if given in glade tab Common, or "" if not.

  • Es (string): The event (callback name)

  • Is (string): The ID of the widget that has sent the event (in glade tab General)

  • Cs (string): The Gtk class of the widget that has sent the event

Table 3. ⎕GTK Events
Callback (specified in glade) argc APL Structure of the ⎕GTK 1 Result APL Example of a ⎕GTK 1 Result

clicked

1

Gi, Ns, Es, Is, Cs

6 'OK-button' 'clicked' 'button1' 'GtkButton'

activate

1

Gi, Ns, Es, Is, Cs

6 'Text-entry' 'activate' 'entry1' 'GtkEntry'

changed

1

Gi, Ns, Es, Is, Cs

6 'Select-box' 'changed' 'combobox1' 'GtkComboBox'

draw

1

Gi, Ns, Es, Is, Cs

6 'Canvas' 'draw' 'drawingarea1' 'GtkDrawingArea'

destroy

1

Gi, Ns, Es, Is, Cs

6 'Main-Window' 'destroy' 'window1' 'GtkWindow'

Table 4. ⎕GTK Drawing Commands
Command Effect

background R G B

Set the background color of the drawing area widget. This command shall be the first command given.

line-width W

set the line width that is used to draw lines and borders. W=100 (%) gives the default line width in Gtk.

line-color R G B

same as line-color R G B 100 (fully opaque color).

line-color R G B A

set the line color to the color with red, green, and blue components of R, G, and B resp, and opacity A (%).

fill-color R G B

same as fill-color R G B 100 (fully opaque color).

fill-color R G B A

set the fill color to the color with red, green, and blue components of R, G, and B resp. and opacity A (%).

font-size S

set the font size used in subsequent text commands. S=100 (%) is the default font size of Gtk.

font-family F

set the font family (serif, sans-serif, cursive, fantasy, monospace) used in subsequent text commands.

font-slant S

set the font slant used in subsequent text commands. S is NORMAL, ITALIC, or OBLIQUE.

font-weight W

set the font weight used in subsequent text commands. W is either NORMAL or BOLD

line (X0 Y0) (X1 Y1)

draw a line from point (X0 Y0) to point (X1 Y1).

rectangle (X0 Y0) (X1 Y1)

draw a rectangle with corners (X0 Y0) and (X1 Y1).

circle (X Y) R

draw a circle with center (X Y) and radius R.

ellipse (X Y) (RX RY)

draw an ellipse with center (X Y) and radii RX and RY.

polygon (X0 Y0) (X1 Y1) (X2 Y2) …

draw a polygon with points (X0 Y0), (X1 Y1), (X2 Y2) …

text (X Y) T

draw the text T at position (X Y).

Caveats

With glade it is possible to design a single GUI that contains multiple top-level windows or no top-level window. However, ⎕GTK currently supports only GUIs with one top-level widget of type "Window (the first widget in the top-level group).