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):
- Vbutton - buttons, including toggle buttons, to click
- Vline - lines displayed on the window
- Vlist - scrollable list of text items, optionally selectable
- Vmenu_bar - displays a menubar of submenus
- Vmessage - a text message
- Vpane - a containing pane, often displaying a frame
- Vradio_buttons - radio buttons, to select one option
- Vhoriz_scrollbar/Vvert_scrollbar - scrollbars
- Vhoriz_slider/Vvert_slider - horizontal and vertical sliders, to select from a range
- Vtext - text entry, to enter some data as a string
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.
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
-
links in the
vidgets
library - creates a window with the given label (title) and size: notice the parameters are given as key-value pairs in strings
- extract the root pane from the window - it's important to do this once to correctly insert the later vidgets
- 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.)
- Insert the button at the given x/y position in the root pane
- Resize the root - important to correctly locate and display the vidgets.
- 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:
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
- Defines "input_number", "result" and "root" as global variables, so we can access them in all procedures.
- Create the window with given title and size.
- Create a text input widget with given title, and keep a reference to it.
- Concisely create and insert a label with given text and position.
- Keep a reference to the "result" widget for later displaying the result.
- Create a button which will call "convert_temperature" when clicked.
-
input_number.data
retrieves the current value of input text field. -
result.s
references the string on the result message. -
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:
- randweav from ipl, rewritten and dissected
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.)
These vidgets are used to display something on a window.
- Vline
- win - window on which to draw line
- sx - x coordinate of start of line
- sy - y coordinate of start of line
- ex - x coordinate of end of line
- ey - y coordinate of end of line
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
- win - window on which to place the message
- label - text of the message
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
- win - window on which to place the pane
- callback - procedure called when an event occurs on the pane
- id - optional identifier for the vidget instance
- style - one of "grooved", "invisible", "raised", "sunken"
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:
-
vidget.id
returns the optional identifier we gave each pane -
e
is -1 for a down-left button, -4 for an up-left button, etc -
x
andy
refer to coordinates on the underlying window, not relative to the pane
These vidgets are relatively simple, and are designed to act/change state when you interact with them using the mouse.
- Vbutton/Vtoggle
- win - window on which to place the button
- label - string to display as the button's message
- callback - procedure called when the button is clicked
- id - optional identifier for the vidget instance
- style - type of button to display
- width - horizontal size of button
- height - vertical size of button
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.
- "regular" shows the button message only
- "check" shows a square-shaped box, which is filled when enabled
- "circle" shows a circle-shaped box, which is filled when enabled
- "diamond" shows a diamond-shaped box, which is filled when enabled
- "xbox" shows only a square-shaped box, showing an "X" when enabled
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
- win - window on which to draw the slider
- callback - procedure to call for slider events
- id - optional identifier for the vidget instance
- width - width of the slider display
- height - height of the slider display
- low - lower-bound for the slider range
- high - upper-bound for the slider range
- iv - initial value for the 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
- win - window on which to display the radio buttons
- options - list of strings for the different options
- callback - procedure to call when an option is clicked
- id - optional identifer for vidget
- style - optional style value
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:
- V_RECT, V_RECT_NO, V_CHECK, V_CHECK_NO, V_CIRCLE, V_CIRCLE_NO, V_DIAMOND, V_DIAMOND_NO
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
- win - window on which to display the menu
- label, submenu - menu/submenu pair(s)
Each submenu is an instance of:
- Vsub_menu
- win - window on which to display the menu
- item, callback - item/callback pair(s)
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.
The remaining vidgets provide more complex ways of presenting and interacting with information on the screen.
- Vlist
- win - window on which to display the vidget
- callback - procedure to call when user selects an entry
- id - optional identifier for this vidget
- items - list of strings to be displayed
- number - 1 for discontinuous scroll (display updates when you release the mouse button) or &null for continuous scrolling (display updates as you drag the scroll bar)
- width - horizontal size of the list
- height - vertical size of the list
-
style - controls how the list will be displayed
- V_READONLY - no lines can be highlighted
- V_SELECT - callback executed on release of mouse over highlighted line
- V_MULTISELECT - callback executed on release of mouse over several highlighted lines
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
- win - window on which to display the vidget
- label - string label to display to left of text entry
- callback - procedure to call when user presses "Enter"
- id - optional identifier for this vidget
- max_size - number of characters to allow for entry
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
The base vidgets library can be included using link vidgets
. The following
files are also related to vidgets:
- graphics.icn - links to many core graphics procedures
- vbuttons.icn - defines Vbutton/Vtext/Vmessage/Vline
- vcoupler.icn - associates vidgets to variables
- vdefns.icn - background/font/slider size definitions
- vdialog.icn - defines Vdialog
- vfilter.icn - utility procedure for slider/scrollbar
- vframe.icn - defines Vframe
- vgrid.icn - layout vidgets into grid
- viface.icn - basic vidget interface procedures, including VInsert/GetEvents
- vlist.icn - defines Vlist
- vmenu.icn - defines Vmenu_bar/Vsub_menu
- vpane.icn - defines Vpane
- vquery.icn - defines Vchoice/Vinput dialogs
- vradio.icn - defines Vradio_buttons/Vhoriz_radio_buttons
- vscroll.icn - defines Vhoriz_scrollbar/Vvert_scrollbar
- vsetup.icn - for creating a GUI from (i)vib output
- vslider.icn - defines Vhoriz_slider/Vvert_slider
- vstd.icn - some internal vidget procedures
- vstyle.icn - defines the regular/check etc styles
- vtext.icn - defines Vtext