Vidgets

Graphics programming and visualisation was one of the strengths of Icon. Unfortunately, the lack of development of Icon means the GUI offerings are limited by today's standards.

Here, I want to look at one of Icon's graphics libraries: vidgets. The vidgets library is described quite thoroughly in the various Icon books.

Available vidgets (as found in vsetup - more are in the files):

However, the "standard" way of using the vidget library is to build a GUI within (i)vib: this produces a coded setup file for your application, which you can then use to generate a GUI using a call to vsetup. Examples of this are in the "ipl/gprogs" folder of the Unicon distribution and discussed in the Icon books.

However, I'm more familiar with hand-coding GUIs, so how can we hand-code GUIs in Icon?

These examples use the Icon implementation and code available with Unicon.

Examples

Example 1: Hello

A simple, single-button GUI could be:

link vidgets                                                              # <1>

procedure main()
  win := Window("label=Hello", "size=500,300")                            # <2>
  root := Vroot_frame(win)                                                # <3>

  button := Vbutton(win, "Click to close", exit_program, , , 120, 20)     # <4>
  VInsert(root, button, 20, 20)                                           # <5>

  VResize(root)                                                           # <6>
  GetEvents(root)                                                         # <7>
end

procedure exit_program()
  exit()
end
  1. links in the vidgets library
  2. creates a window with the given label (title) and size: notice the parameters are given as key-value pairs in strings
  3. extract the root pane from the window - it's important to do this once to correctly insert the later vidgets
  4. creates a button for the given window - we specify the label, the call back procedure, and the width/height. (The missing values control the button style.)
  5. Insert the button at the given x/y position in the root pane
  6. Resize the root - important to correctly locate and display the vidgets.
  7. Wait for the GUI to process events - later, we will add some events to process, but here we just want to wait for the button to be pressed.

Compile and run with:

> icont hello.icn
> hello

(TIP: use -G when compiling, and the executable can be run by double-clicking without showing the text window.)

And you should see a window:

Example 2: Temperature Conversion

The temperature conversion example introduces a text input vidget, and shows how to extract and set the values displayed in some vidgets.

link vidgets

global input_number, result, root                                 # <1>

procedure main()
  local win

  win := Window("label=Temperature Conversion", "size=300,200")   # <2>
  root := Vroot_frame(win)

  input_number := Vtext(win, "Fahrenheit: ", , , 6)               # <3>
  VInsert(root, input_number, 50, 20)
  VInsert(root, Vmessage(win, "in Celsius is:"), 30, 50, 100, 20) # <4>
  result := Vmessage(win, "------")                               # <5>
  VInsert(root, result, 150, 50)

  VInsert(root,
    Vbutton(win, "Convert", convert_temperature, , , 100, 20),    # <6>
    130,
    80)

  VResize(root)
  GetEvents(root)
end

procedure convert_temperature()
  local temperature

  temperature := integer(input_number.data) | 0                   # <7>
  result.s := fahrenheit_to_celsius(temperature)                  # <8>
  VResize(root)                                                   # <9>
end

procedure fahrenheit_to_celsius(temperature)
  return (temperature - 32) * 5.0 / 9
end
  1. Defines "input_number", "result" and "root" as global variables, so we can access them in all procedures.
  2. Create the window with given title and size.
  3. Create a text input widget with given title, and keep a reference to it.
  4. Concisely create and insert a label with given text and position.
  5. Keep a reference to the "result" widget for later displaying the result.
  6. Create a button which will call "convert_temperature" when clicked.
  7. input_number.data retrieves the current value of input text field.
  8. result.s references the string on the result message.
  9. After changing the result message, we need to call VResize to refresh the display.

Compile and run with:

> icont conversion.icn
> conversion

And you should see a dialog:

More Examples

Description of Vidgets

The following gives the name of each vidget and the parameters used in constructing an instance along with an example. (More could be said about every vidget.)

Display Vidgets

These vidgets are used to display something on a window.

Vline

The line vidget is quite straightforward - a grooved line between two coordinates. Because the line definition includes its coordinates, you do not need to specify these when calling VInsert.

link vidgets

procedure main()
  win := Window("label=Lines", "size=500,300")
  root := Vroot_frame(win)

  # vertical line
  VInsert(root, Vline(win, 50, 10, 50, 100))
  # horizontal line
  VInsert(root, Vline(win, 10, 10, 90, 10))
  VInsert(root, Vline(win, 10, 100, 90, 100))
  # diagonal lines
  VInsert(root, Vline(win, 100, 10, 200, 100))
  VInsert(root, Vline(win, 100, 100, 200, 10))

  VResize(root)
  GetEvents(root)
end
Vmessage

The message vidget is used to display some fixed text on the window. The position and size of the message must be defined in the call to VInsert.

link vidgets

procedure main()
  win := Window("label=Message", "size=500,300")
  root := Vroot_frame(win)

  VInsert(root,
    Vmessage(win, "Hello from IconVidgets!"),
    20, 20, 188, 20) # placed at (20, 20) of size (188, 20)

  VResize(root)
  GetEvents(root)
end
Vpane

This vidget is often called a "region" in the Icon books, or labelled "Rect" in (i)vib.

link vidgets
link ximage

procedure main()
  win := Window("label=Pane", "size=500,300")
  root := Vroot_frame(win)

  VInsert(root,
    Vpane(win, pane1_cb, "left_pane", "grooved"),
    10, 10, 90, 90)
  VInsert(root,
    Vpane(win, pane2_cb, "hidden_pane", "invisible"),
    100, 10, 20, 20)
  VInsert(root,
    Vpane(win, pane3_cb, "middle_pane", "raised"),
    100, 40, 20, 20)
  VInsert(root,
    Vpane(win, pane4_cb, , "sunken"),
    100, 70, 20, 20)

  VResize(root)
  GetEvents(root)
end

procedure pane1_cb(vidget, e, x, y)
  write("Clicked on pane 1 ", vidget.id, " at ", x, ", ", y, " - ", ximage(e))
end

procedure pane2_cb(vidget, e, x, y)
  write("Clicked on pane 2 ", vidget.id, " at ", x, ", ", y, " - ", ximage(e))
end

procedure pane3_cb(vidget, e, x, y)
  write("Clicked on pane 3 ", vidget.id, " at ", x, ", ", y, " - ", ximage(e))
end

procedure pane4_cb(vidget, e, x, y)
  write("Clicked on pane 4 ", vidget.id, " at ", x, ", ", y, " - ", ximage(e))
end

Clicking within one of the panes triggers an event:

Interactive Vidgets

These vidgets are relatively simple, and are designed to act/change state when you interact with them using the mouse.

Vbutton/Vtoggle

A button displays a message and responds to a mouse click. A toggle is a kind of button which holds a state, and can be enabled or disabled.

The display of the button can be adjusted by the style - apart from "regular", these styles are for the toggle button only.

All styles are shown with a surrounding rectangle, which can be removed by ending the style name in "no".

link vidgets

procedure main()
  win := Window("label=Button", "size=500,300")
  root := Vroot_frame(win)

  # Simple button
  VInsert(root,
    Vbutton(win, "Button", button_cb, "simple-button", "regular", 75, 20),
    20, 20)
  VInsert(root,
    Vbutton(win, "Button", button_cb, "simple-button", "regularno", 75, 20),
    120, 20)
  # Toggle button
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "regular", 75, 20),
    20, 50)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "regularno", 75, 20),
    120, 50)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "check", 75, 20),
    20, 80)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "checkno", 75, 20),
    120, 80)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "circle", 75, 20),
    20, 110)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "circleno", 75, 20),
    120, 110)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "diamond", 75, 20),
    20, 140)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "diamondno", 75, 20),
    120, 140)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "xbox", 20, 20),
    20, 170)
  VInsert(root,
    Vtoggle(win, "Toggle", button_cb, "toggle-button", "xboxno", 20, 20),
    120, 170)

  VResize(root)
  GetEvents(root)
end

procedure button_cb(vidget, e, x, y)
  case vidget.id of {
    "simple-button": write("You clicked a simple button")
    "toggle-button": if 1 = \VGetState(vidget) then write("Toggle is enabled")
    else write("Toggle is disabled")
  }
end

The following screenshot shows the output after some of the toggle buttons have been enabled.

Vhoriz_slider/Vvert_slider

The following example constructs two sliders - one oriented horizontally with a range of 0 to 100, and the other oriented vertically with a range of 0.0 to 1.0. The value of the slider can be observed as the slider is moved using the callback procedure by calling vidget.data - this is a floating point value.

link vidgets

procedure main()
  win := Window("label=Slider", "size=500,300")
  root := Vroot_frame(win)

  VInsert(root,
    Vhoriz_slider(win, update_value, "horizontal",
                  100, 20,
                  0, 100, 50),
    20, 20)

  VInsert(root,
    Vvert_slider(win, update_value, "vertical",
                 20, 50,
                 0.0, 1.0, 0.0),
    70, 50)

  VResize(root)
  GetEvents(root)
end

procedure update_value(vidget, e, x, y)
  write("Slider ", vidget.id, " is now at ", vidget.data)
end
Vradio_buttons/Vhoriz_radio_buttons

The radio buttons are displayed in a row, either vertically or horizontally. The "style" parameter affects the display in a similar way to toggle buttons, but the style must be given as an identifier, as one of the following:

The following example shows some of these options:

link vidgets

procedure main()
  win := Window("label=Radio", "size=500,300")
  root := Vroot_frame(win)

  # Plain radio buttons
  VInsert(root,
    Vradio_buttons(win, ["icon", "java", "ruby", "scheme"], radio_cb, "language"),
    20, 20)
  # Use check-box style button style
  VInsert(root,
    Vradio_buttons(win, ["apple", "banana", "pear"], radio_cb, "fruit", V_CHECK_NO),
    150, 20)
  # Horizontal buttons
  VInsert(root,
    Vhoriz_radio_buttons(win, ["Amiga", "Atari ST", "ZX Spectrum"], radio_cb, "computer", V_DIAMOND),
    20, 140)

  VResize(root)
  GetEvents(root)
end

procedure radio_cb(vidget, e, x, y)
  write("Selected ", vidget.id, " is ", vidget.data)
end

Support for providing a menu bar within a window.

Vmenu_bar

Each submenu is an instance of:

Vsub_menu

The label/submenu pairs in a menu_bar can be repeated, as shown in the example below, to create more than one menu at a time; similarly, the submenu item/callback pairs can be repeated to make a menu with several items. A submenu of a submenu can be created by replacing the callback with a submenu.

link vidgets

procedure main()
  win := Window("label=Menu", "size=500,300")
  root := Vroot_frame(win)

  VInsert(root,
    Vmenu_bar(win,
      "Program", Vsub_menu(win, "About", about_cb, "Exit", exit_program),
      "File", Vsub_menu(win, "Open", open_cb, "Save", save_cb)
    ),
    0, 0)

  VResize(root)
  GetEvents(root)
end

procedure exit_program()
  exit()
end

procedure about_cb()
  write("About")
end

procedure open_cb()
  write("Open file menu")
end

procedure save_cb()
  write("Save file menu")
end

The screenshot shows one of the menus pulled down.

More Complex Vidgets

The remaining vidgets provide more complex ways of presenting and interacting with information on the screen.

Vlist

NB: The multi-select option is not working for me on Windows.

link vidgets
link ximage

procedure main()
  win := Window("label=List", "size=500,300")
  root := Vroot_frame(win)

  numbers := []
  every i := 1 to 100 do put(numbers, "Number " || i)

  VInsert(root,
    Vlist(win, , "readonly", numbers, , 110, 200, V_READONLY),
    20, 20)

  VInsert(root,
    Vlist(win, pick_number, "single_select", numbers, 1, 110, 200, V_SELECT),
    150, 20)

  VInsert(root, # TODO multi selection is not working for me
    Vlist(win, pick_number, "multi_select", numbers, 1, 110, 200, V_MULTISELECT),
    270, 20)

  VResize(root)
  GetEvents(root)
end

procedure pick_number(vidget, item, indices)
  write("Picked ...", ximage(item), " on list ", vidget.id, " at ", ximage(indices))
end
Vhoriz_scrollbar/Vvert_slider

The vidgets library includes Vhoriz_scrollbar and Vvert_scrollbar for providing standalone scrollbars: these work identically to sliders except for the different visual appearance, so an example is not provided here.

Vtext

The Vtext vidget displays a label and a field to allow entry of information. We have seen an example of this in the Temperature Conversion example above.

The field value can be initialised by providing a value in the vidget label, as in the second text example here which initialises the field to the string "England".

link vidgets

procedure main()
  win := Window("label=Text", "size=500,300")
  root := Vroot_frame(win)

  VInsert(root,
    Vtext(win, "    Name: ", show_value, "name_entry", 20),
    20, 20)
  VInsert(root,
    Vtext(win, "Location: \\=England", show_value, "location_entry", 20),
    20, 50)

  VResize(root)
  GetEvents(root)
end

procedure show_value(vidget)
  write(vidget.id, " contains ", vidget.data)
end

Implementation

The base vidgets library can be included using link vidgets. The following files are also related to vidgets:


Page from Peter's Scrapbook, output from a VimWiki on 2024-01-21.