You are looking at historical revision 25597 of this page. It may differ significantly from its current revision.

Tutorial on the Iup Graphical User Interface toolkit

Iup is a small and easy to use GUI toolkit, originally written in pure C, but accompanied by an optional resource language LED, to make life easier. It has later been ported to Lua and other scripting languages, and nowadays it is considered Lua's native GUI-toolkit. Thanks to Thomas Chust Chicken and Racket ports exist as well.

Unfortunately, the documentation is somewhat scattered in the internet:

the official API docs.

IUP "portable user interface" GUI library.

Hence you need to consult the original C documentation very often. So it seems appropriate to start with an example (Hello World, you guess it) showing how a C program is translated to Scheme. This way you'll become comfortable writing Scheme programs while using the original C documentation. But before that let me recapitulate some design principles of Iup.

Iup's design principles

Iup programs consist of dialogs, which communicate with each other. A dialog can contain only one widget, usually a container, which in turn can contain other widgets, containers included. For widgets to be visible, they must transparently be mapped to native widgets of the system, which are gtk-widgets in Linux, in general. This mapping happens only, when a dialog is shown. Only dialogs need to be destroyed at the end of a program.

Like other GUI-toolkits, interaction with the user happens within a main loop. But what makes Iup stand out of the crowd are two principles:

First: No widget is ever positioned with explicit coordinates. Everything is done logically by nesting containers and positioning widgets like fill. This facilitates matters considerably.

Second: All widgets and dialogs are controlled by attributes, including callbacks. This makes the library small.

In C attribute names as well as attribute values are strings, in Scheme the former are keywords, the latter might be symbols or numbers as well. All attributes can be get and set. Getting is done (in Scheme) by the attribute function, setting by the attribute-set! procedure or a generalized set!, i.e. either by

(attribute-set! widget name: val) or (set! (attribute widget name:) val)

whichever you prefer. The same applies to callbacks, which are get and set by callback and callback-set! respectively.

In Scheme's Iup callbacks are usually functions of one argument, self most of the time, which return a symbol, for example 'close, to close a dialog, or 'default, to keep it open. In C, on the other hand, the return values are #define'd integers.

Hello World

Let's start with the most trivial version, in C and in Chicken:

hello0.c


#include <iup.h>
#include <stdlib.h>

int main (int argc, char *argv[]) {
  IupOpen(&argc, &argv);
  IupShow(IupDialog(IupLabel ("Hello, world!")));
  IupMainLoop();
  IupClose();
  return 0;
}

hello0.scm


(use iup)
(show (dialog (label "Hello, world!")))
(main-loop)
(exit 0)

Note, that initializing and closing iup disappears in Chicken, since it is done, when inporting iup. Note also, that the Chicken names are much friendlier than the C ones: Chicken's module system makes Iup prefixes superfluous, they can be added, if needed, with appropriate import clauses.

Now a version which shows, how to use attributes and callbacks, again in C, and in two Chicken versions. The first is almost a literal translation of the C version, the second a condensed version, where all attributes are set in the creation process.

hello1.c


#include <iup.h>
#include <stdlib.h>

int exit_cb (void) {
  return IUP_CLOSE;
}

int main (int argc, char *argv[]) {

  // declare widgets
  Ihandle *btn, *lbl, *vb, *dlg;

  // initialize iup
  IupOpen(&argc, &argv);
  
  // create widgets and set their attributes
  btn=IupButton("&Ok", "");
  IupSetCallback(btn,"ACTION", (Icallback) exit_cb);
  IupSetAttribute(btn, "EXPAND", "Yes");
  IupSetAttribute(btn, "TIP", "Exit button");
  
  lbl=IupLabel("Hello,world!");

  vb=IupVbox(lbl, btn);
  IupSetAttribute(vb, "GAP", "10");
  IupSetAttribute(vb, "MARGIN", "10x10");
  IupSetAttribute(vb, "ALIGNMENT", "ACENTER");

  dlg=IupDialog(vb);
  IupSetAttribute(dlg, "TITLE", "Hello");

  // Map widgets and show dialog
  IupShow(dlg);

  // Wait for user interaction
  IupMainLoop();

  // Clean up
  IupDestroy(dlg);
  IupClose();
  return EXIT_SUCCESS;
}

hello11.scm

Here is a translation to Scheme ...


(use iup)

(define (cb-exit self) 'close)

(define btn (button))
(set! (callback btn action:) cb-exit)
(set! (attribute btn title:) '&Ok)
(set! (attribute btn expand:) #t)
(set! (attribute btn tip:) "Close button")

(define lbl (label "Hello World!")) 

(define vb (vbox lbl btn))
(attribute-set! vb gap: 10)
(attribute-set! vb margin: '15x15)
(attribute-set! vb alignment: 'ACENTER)

(define dlg (dialog vb))
(attribute-set! dlg title: 'Hello)

(show dlg)
(main-loop)
(destroy! dlg)
(exit 0)

Note, how the upper-case C-names of attributes change to lower-case Chicken-keywords (by the way, Chicken-keywords can be either written with a trailing colon or a leading hash-colon, but I prefere the former, which looks nicer).

Note also, that attribute values can be numbers or symbols as well. And last, but not least, note the tips: predicate, which defines tooltips, and the ampersand in the button's title, which defines a shortcut, Alt+underlined-character, to execute the callback.

hello12.scm

... and here a condensed version.


(use iup)

(define dlg
  (dialog
    (vbox
      (label "Hello, World!")
      (button title: '&Ok
              expand: #t
              tip: "Close button"
              action: (lambda (self) 'close))
      gap: 10
      alignment: 'ACENTER
      margin: '15x15)
    title: 'IUP))

(show dlg)
(main-loop)
(destroy! dlg)
(exit 0)

Note, how smoothly the indentation reflects the logical representation of the dialog.

The LED resource language

The condensed version above can be almost literally transformed into a LED resource file, which can be either loaded into a C program or a Scheme program. Those resources are interpreted at runtime, so that even in a compiled program the resource can be changed afterwards, provided the identifiers remain consistent.

The form of a LED-declaration looks as follows


widget=widget-type[attribute-name=attribute-value, ...](arg, ...)

where the attributes' names and values are written without enclosing quotes, but interpreted as strings.

Here is an example and its use in Chicken.

hello.led

btn = button[TIP = "Close window", EXPAND = Yes]("&Ok", 0)

dlg = dialog[TITLE = Hello](

           vbox[GAP = 10, MARGIN = 15x15, ALIGNMENT = ACENTER](
               label("Hello world!"),
               btn))

hello13.scm


(use iup)

(load/led "hello.led")

(callback-set! (handle-ref "btn")
               action: (lambda (self) 'close))

(define dlg (handle-ref "dlg"))

(show dlg)
(main-loop)
(destroy! dlg)
(exit 0)

Note, that the LED-import is done via load/let and the identification of LED-names with Chicken-variables via handle-ref.

Note also, that btn must be coded separately and given a name, because we need to set the callback, which can not be a string.

You can compile hello13.scm with csc and change hello.led afterwards. Try it out and play with some attributes, for example, remove the EXPAND attribute from btn and set it for label, ...

Porting some examples from the Iup Distribution

Now we'll show, how some predefined widgets work. For that we'll port some of the C-examples.

fill.scm

The following dialog shows, how different positions of the fill widget change the dialog's appearance.


(use iup)

;;; Create frame with left aligned button
(define frame-left
  (frame
    (hbox
      (button title: '&Left
              tip: "Left button"
              action: (lambda (self)
                        (print "Stay in with Left button")
                        'default))
      (fill))))
(set! (attribute frame-left title:) "Left aligned")

;;; Create frame with centered button
(define frame-center
  (frame
    (hbox
      (fill)
      (button title: '&Center
              tip: "Central button"
              action: (lambda (self)
                        (print "Way out with Central button")
                        'close))
      (fill))))
(attribute-set! frame-center title: "Centered")

;;; Create frame with right aligned button
(define frame-right
  (frame
    (hbox
      (fill)
      (button title: '&Right
              tip: "Right button"
              action: (lambda (self)
                        (print "Way out with Right button")
                        'close)))
    title: "Right aligned"))

; Note, that callbacks should return a symbol.
; Only the symbol 'close of the action: callbacks closes the dialog!

;;; Create dialog with these three frames
(define dlg
  (dialog
    (vbox
      frame-left
      frame-center
      frame-right)))
(set! (attribute dlg size:) 120)
(set! (attribute dlg title:) 'Fill)

(show dlg)
(main-loop)
(destroy! dlg)
(exit 0)

hbox.scm

This dialog shows, how horizontal boxes within vertical ones work.


(use iup)

(define dlg
  (dialog
    (vbox
      (frame
        (hbox (fill)
              (button "1" "" size: "30x30")
              (button "2" "" size: "30x40")
              (button "3" "" size: "30x50")
              (fill)
              alignment: "ATOP"
              gap: 10
              size: 200)
        title: "alignment: ATOP gap: 10 size: 200"
        gap: 10
        size: 200)
      (frame
        (hbox (fill)
              (button "1" "" size: "30x30")
              (button "2" "" size: "30x40")
              (button "3" "" size: "30x50")
              (fill)
              alignment: "ACENTER"
              gap: 20)
        title: "alignment: ACENTER gap: 20"
        gap: 20)
      (frame
        (hbox (fill)
              (button "1" "" size: "30x30")
              (button "2" "" size: "30x40")
              (button "3" "" size: "30x50")
              (fill)
              alignment: "ABOTTOM"
              size: 150)
        title: "alignment: ABOTTOM size: 150"
        size: 150))
    title: "Hbox"))

(show dlg x: 'center y: 'center)
(main-loop)
(destroy! dlg)

Now a dialog with a menu and submenus, one of whose items call a predefined message-dialog.


(use iup iup-dialogs)

(define item-open (menu-item "&Open"))

(define item-save (menu-item "&Save"))

(define item-undo (menu-item "&Undo" active: "NO"))

(define item-exit (menu-item "E&xit"))
(set! (callback item-exit action:)
      (lambda (self) 'close))

(define file-menu
  (menu item-open 
        item-save 
        (menu-separator)
        item-undo
        item-exit))

(define item-about (menu-item "&About"))
(define (about-cb self)
  (show (message-dialog value: "Information goes here ...") modal?: #t)
  'default)
(callback-set! item-about action: about-cb)

(define help-menu
  (menu item-about))

(define mnu
  (menu (menu-item "&File" file-menu)
        (menu-item "&Help" help-menu)))
;(set! (handle-name mnu) "mymenu")

(define dlg (dialog (canvas "")))
(set! (attribute dlg menu:) mnu);"mymenu")
(set! (attribute dlg title:) "Menu")

(show dlg)
(main-loop)
(exit 0)

Note, that the underlined characters behave differently in menus and submenus. In the former they execute their callbacks with the Alt prefix, in the latter without.

Note also, that predefined dialogs can only be shown with the modal?: attribute set.

filedlg.scm

Now we use two predefined dialogs, file-dialag and message-dialog. You should note, that they are mapped to gtk-dialogs!


(use iup)

(define (popup dlg . args)
  (apply show dlg #:modal #t args))

(define dlg (file-dialog title: "File save"
                         dialogtype: 'SAVE
                         filter: "*.led"
                         filterinfo: "Iup resource file"))

(popup dlg x: 'center y: 'center)

(let ((status (attribute dlg status:)))
  (cond
    ((string=? status "1")
     (popup
       (message-dialog value: (string-append  "New File: "
                                              (attribute dlg value:)))))
    ((string=? status "0")
     (popup
       (message-dialog value: (string-append  "File already exists: "
                                              (attribute dlg value:)))))
    ((string=? status "-1")
     (popup
       (message-dialog value: "File-dialog: Operation Canceled")))))

(destroy! dlg)
(exit 0) 

Note, that the status: attribute of the file-dialog is a string!

multiline.scm

Now, we'll show how widgets within a dialog can communicate whith each other. The trick is, that Iup can reference widgets by name, or, to be more precise, by string name. You have seen this in the LED example above: In LED a widget is named by a string and constructed by the LED interpreter. To use it in Scheme, you reference the widget proper from its name via handle-ref. This can be done without LED as well. But then you must give the widget a string name via handle-name-set! or the generalized version (set! (handle-name widget) name).

The following dialog contains two textboxes, one of them multiline, a dropdown listbox and some buttons, allowing to get or set the multiline predicates. Please take note, how handle-ref and handle-name is used.


(use iup)

(define (message title value)
  (show (message-dialog title: title value: value) #:modal? #t))

(define (set-attribute keyword)
  (let (
    (msg (sprintf "Attribute ~A set with value ~A"
                  (keyword->string keyword) (attribute line value:)))
    (multi (handle-ref "multi"))
    (line (handle-ref "line"))
    )
    (attribute-set! multi keyword (attribute line value:))
    (message "Set attribute" msg)
    'default))

(define (get-attribute keyword)
  (let (
    (msg (sprintf "Attribute ~A get with value ~A"
                  (keyword->string keyword) (attribute line value:)))
    (multi (handle-ref "multi"))
    (line (handle-ref "line"))
    )
    (attribute-set! line value: (attribute multi keyword))
    (message "Get attribute" msg)
    'default))

(define btn-append
  (button title: '&Append
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute append:)
                        (get-attribute append:))
                      'default))))

(define btn-insert
  (button title: '&Insert
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute insert:)
                        (get-attribute insert:))
                      'default))))

(define btn-border
  (button title: '&Border
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute border:)
                        (get-attribute border:))
                      'default))))

(define btn-caret
  (button title: '&Caret
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute caret:)
                        (get-attribute caret:))
                      'default))))

(define btn-readonly
  (button title: '&Readonly
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute readonly:)
                        (get-attribute readonly:))
                      'default))))

(define btn-selection
  (button title: '&Selection
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute selection:)
                        (get-attribute selection:))
                      'default))))

(define btn-selectedtextbox
  (button title: 'Selected&textbox
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute selectedbox:)
                        (get-attribute selectedbox:))
                      'default))))

(define btn-nc
  (button title: '&Nc
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute nc:)
                        (get-attribute nc:))
                      'default))))

(define btn-value
  (button title: '&Value
          action: (lambda (self)
                    (let ((lb (handle-ref "lb")))
                      (if (not (attribute lb value:))
                        (set! (attribute lb value:) 1))
                      (if (eqv? (string->number (attribute lb value:)) 1)
                        (set-attribute value:)
                        (get-attribute value:))
                      'default))))

(define lb (listbox #:1 'set #:2 'get value: 1 dropdown: #t))
(define multi (textbox expand: #t multiline: #t))
(define line (textbox expand: 'horizontal))


(set! (handle-name lb) "lb")
(set! (handle-name multi) "multi")
(set! (handle-name line) "line")

(define dlg
  (dialog (vbox multi
                (hbox lb
                      line)
                (hbox btn-append btn-insert btn-border btn-caret
                      btn-readonly btn-selection)
                (hbox btn-selectedtextbox btn-nc btn-value))
          title: "Multiline example"
          size: 'HALFxQUARTER))

(show dlg x: 'center y: 'center)
(main-loop)
(destroy! dlg)
(exit 0)

There is one caveat in the listbox: The lines must be denoted with prefixed keyword notation, 1: will not work, for example!

Concluding remark

All these examples show, that Iup is really easy to use ...

Author

Juergen Lorenz

Initial version

Nov 25, 2011

Last updated

Nov 26, 2011