Emacs widget: A data entry oriented GUI

Authors: Joao Barbosa de Souza Filho, Reny Cury Filho

1  Installation and test

Lemac is a simple Graphical User Interface for OCaml; it is based on one of Elliott Otis's proof of concept (tiny experiment with functional widgets) . You can download it from here. The download page offers two kinds of packaging, to wit,

Choose the one that fits your needs. For Linux, lemac_beta.tar.gz is better; lemac_beta.zip is usual in a Windows environment. After deflating the package, enter the folder lemac_beta and try an example:

ocamlopt graphics.cmxa wid.ml ntxt.ml -o ntxt.exe

If you are in a Windows machine without a C compiler, you may prefer to compile the example into bytecode:

ocamlc graphics.cma wid.ml ntxt.ml -o ntxt.exe

To test the example, type ntxt.exe from the command line. Now that you certified that everything is working properly, let us learn the basics of creating and using a Graphical User Interface, or GUI, for short.

2  What is new?

bugs report to lemacproject@yahoo.com.

3  More tests

Let us start compiling and running the ready to go examples. Compilation is simple, as you can see from the following command line:

ocamlopt graphics.cmxa wid.ml ntxt.ml -o ntxt.exe

Figure 1: Edit fields

If you run the program ntxt.exe, you will get a window like the one shown above. In the example, there are three Emacs like widgets. The first and the second one are plain text editors. If you click on the tab of the first editor, you can input text as in the real Emacs. Commands involve the CONTROL key or the META key. C-<chr> means hold the CONTROL key while typing the character <chr>. Thus, C-f would be: hold the CONTROL key and type f. Other exemple, if you type M-<chr> means press and release the ESC key, and then type <chr>. Here is a summary of cursor-moving operations, and other editing commands:

Move forward a character
Move backward a character
Move to next line
Move to previous line
Move to beginning of line
Move to end of line
Move back to beginning of the text
Move forward to end of the text
Sets mark
Copy text from mark to cursor into the kill-ring
Delete text from mark to cursor

Besides these commands, you can use C-t to execute a function associated to the editor. In the example, the function changes the text into uppercase. Another way to trigger the editor function is to click on the X button at the upper right hand corner of the editor rectangle. Finally there are buttons that can apply functions to the editor text. For instance, the buttons uppercase and lowercase do what they mean.

Up to now, you have learned how to use the plain text editor widget. There is also form widgets, that accept input only into pre-defined fields (see figure 2). If you click the mouse in one of the fields, the cursor goes to the begining of the field, and is ready to accept input. There is also a function associated with each form widget. In the present example, the function calculates the average of the contents of the field, whenever you press C-t. BTW in order to toggle between the input and the output of the form function, one must press C-v. As in the plain text editor widget, one can trigger the function associated with the form widget by pressing the small X-button at the upper right hand side corner of the form.

Figure 2: Edit fields

4  How to write programs

In this section, you will learn how to create a plain text editor widget.

1 let main () = 2 Wid.open_gr "eMacs like Editor - Gui Example"; 3 let ed = Wid.mk_edt ~txt:(Wid.nlines 20) 4 ~fn:String.uppercase "Lemac" and 5 ext= Wid.exit_button() 6 in 7 while true do 8 Wid.refresh (); 9 ed.Wid.process_event(); 10 ext.Wid.process_event() 11 done; 12 Wid.close_gr() 13 ;; 14 15 main ();;

The simplest GUI program is indeed simple. One starts by creating an editor and storing it in the variable ed; the function Wid.mk_edt is used to create the widget; it has an initial text (in the example, 20 lines filled with spaces), and a function (String.uppercase). It is a good idea to add an exit button to the application too. Bothe the editor widget and the exit button must be placed inside a perpetual while loop. That is about all you need to do in order to create a simple application. You can study the ntxt.ml example to see how to create forms, and applications with buttons and usefull active functions.

5  More Emacs commands

Emacs and Lemac have a few commands that require two keys to start. Here is a summary of cursor-moving operations, and other editing commands:

C-x C-s
Save the buffer
C-x C-w
Write buffer to file whose name is given on the mini-buffer. You must press C-x, then C-w; finally type a file name on the mini-buffer, and press enter. The editor tab will be changed to the file name. The buffer contents will be saved into the given file.
C-x C-f
Visit a file whose name you must type into the mini-buffer.
Search string forward. Type the search string into the mini-buffer.
Search string backwards. Type the search string into the mini-buffer.

6  Programming with forms

A form must have a mask specifying the entry fields. You can create masks using a sequence of hashes; for instance, the mask (###,###) creates a field to input a Cartesian pair that represents the center of a circle; on the other hand, the mask ## can be used to input the radius. Given the description of a mask as a list of strings, the function Wid.the_mask creates the real mask; the function Wid.mask_fun creates a filter for the mask (the filter will prevent the nal user from typing text outside the mask). In figure 3, one uses a form to get the center and the radius of a circle, that function draw_circle will plot on the windows canvas. The function Wid.get_fields is used to filter the contents of the masks.

1 let draw_circle msk s= 2 let xs= Wid.getFloatList (Wid.get_fields msk s) in 3 let is= List.map int_of_float xs in 4 match is with 5 (a::b::r::more) -> Graphics.draw_circle a b r; s 6 | other -> "Error" 7 ;; 8 9 let main () = 10 Wid.open_gr "Draw Circle"; 11 let msk= Wid.the_mask [ "Centro: (###,###) ";"Raio: ## "] in 12 let ed = Wid.mk_entry ~txt:msk ~mask:(Wid.mask_fun msk) 13 ~fn:(draw_circle msk) "Cculo" and 14 ext= Wid.exit_button() and 15 clr= Wid.button 0 (fun () -> Wid.clear_graph()) "Clear" 16 in 17 while true do 18 ed.Wid.process_event (); 19 ext.Wid.process_event(); 20 uppr.Wid.process_event(); 21 Wid.refresh () 22 done; 23 Wid.close_gr() 24 ;; 25 26 main ();;

7  Graphic User Interface

OCaml is a computer language that has very cute features:

  1. Strong type checking that helps you to find errors at compile time,…
  2. Type inference that frees you from type declaration, and allows terse and concise programming,…
  3. A stingy and fast code generating compiler.

Let us assume that you have installed the OCaml compiler in your machine. Then, you can enter the interpreter, in order to type and test a few definitions:

        Objective Caml version 3.10.0

# let pi= 4.0 *. (atan 1.0);;
val pi : float = 3.1415926535897931
# let area r= pi *. r ** 2.0;;
val area : float -> float = <fun>
# area 50.0;;
- : float = 7853.981633974483

The shortcomings of this approach are obvious:

  1. The interpreter is slow.
  2. People who want to use your applications must install OCaml in their machines, even if they do not have special interest in programming.

To address this shortcomings, OCaml distribution is shipped with a compiler. Therefore, you can prepare a file with the source code of your program, and create a command line application.

1 (* File: circle.ml 2 Compilation: ocamlc circle.ml -o circle.exe 3 **************************************************** *) 4 5 let pi= 4.0 *. (atan 1.0);; 6 7 let area r = pi *. r ** 2.0;; 8 9 print_endline "Area of a circle";; 10 11 print_string "Type the radius: " ;; 12 13 let r= float_of_string (read_line()) in 14 print_float (area r); 15 print_newline() ;;

You can compile this program, and people interested in finding the area of a circle can use the resulting application without installing OCaml; to tell the whole truth, even people who do not know that OCaml exists can use your program. This kind of compiled program is called command line application, because you must use the command line to interact with it. I do like command line applications, but most people don't. The average computer user prefer GUI application, where GUI stands for Graphical User Interface. OCaml has no ready to deploy Graphical User Interface, but if you need one, there are many ways to go.

The problem with GUI providing dynamical libraries is that the person who will use your program must have the library installed in his/her machine. For instance, if your application is based on GTK, any person who wants it must install GTK in his/her computer. In order to do the installation, the person must find the installation program in the Internet, download from 6 to 8 MB of code, and install it. Most people find the installation to be quite tricky, because one must set a path to the GTK libraries. Linking the lablgtk bindings is also very difficult, because you will need a lot of third party libraries.

TCL/TK is much easier than GTK. This does not mean that it is easy. The person who will use your TCL/TK based application will need to install TCL/TK in his machine. Most people, who are not computer wizards, regard finding the right version of TCL/TK in the internet, and installing it quite difficult. Yes! One cannot use any version of TCL/TK; one needs the exact version that was in your machine when you compiled the application.

8  Emacs widget

In order to overcome the difficulties of interpreted GUI, J. Barbosa created the Emacs widget. Emacs is a very popular text editor. The Emacs widget is a device that provides enough Emacs functionality to perform data input/output and to interact with the final user of your application. Here are the features of the Emacs widget:

As everything in life, the Emacs widget has its shortcomings. While GTK and TCL/TK has a team of dedicated people who will keep actively developing the libraries and the associated script languages.

9  The document metaphor

GUI applications are based on metaphors. The idea is to build a human/machine interface that resembles something that the final user knows how to deal with. Most GUI applications use a Control Panel metaphor; this means that the GUI resembles a control panel, i. e., it has buttons, keyboards, and displays. The Emacs widget, on the other hand, uses the document metaphor; this means the the person that will use your application will input data using a document and a text editor, which is the Emacs widget.

Let us consider a very simple problem: Your application will input a text, and replace it with the uppercase equivalent.

1 (* Compilation: 2 ocamlopt graphics.cmxa wid.ml uc.ml -o uc.exe *) 3 4 let main () = 5 Wid.open_gr "Emacs widget - Gui Example"; 6 let ed = Wid.mk_edt "Type" ~fn:String.uppercase 7 and ext= Wid.exit_button() 8 in while true do 9 ed.Wid.process_event (); 10 ext.Wid.process_event(); 11 Wid.refresh () 12 done;; 13 14 main ();;

The program is very simple to understand. From lines 4 to 12, one defines a function main that will be executed on line 14. The main function has a single argument, that is the constant (), of type unit. On line 5, one opens a graphic window, where the widgets will reside. On line 6, one creates the ed Emacs widget, and on line 7 one creates an exit button. The while-loop that spans from line 8 to line 12 polls the Emacs widget and the exit button; line 11 refreshes the graphic window, if user resizes it.

When you create a Emacs widget, as shown on line 6, you provide a function of type string -> string, i.e., a function that take a string argument, and returns a string value. String.uppercase is a function of such a type; however, it is unlikely that many people will be interested in an application that does nothing except producing the uppercase version of a string. To create a useful applications, you can use compile construction methods to transform the input string into a more amenable data structure, like a list of floats, before processing it; for instance, the program below finds the sum of a list of floats.

1 (* ocamlopt graphics.cmxa wid.ml sum.ml -o sum.exe *) 2 3 let add s= 4 let rec loop xs acc= match xs with 5 [] -> acc 6 | f::fs -> loop fs (f +. acc) in 7 string_of_float(loop (Wid.getFloatList s) 0.0) ;; 8 9 let main () = 10 Wid.open_gr "Emacs widget - Gui Example"; 11 let ed = Wid.mk_edt "Type" ~fn:add 12 and ext= Wid.exit_button() 13 in while true do 14 ed.Wid.process_event (); 15 ext.Wid.process_event(); 16 Wid.refresh () 17 done;; 18 19 main ();;

Function add receives an input string, and uses Wid.getFloatList to compile it into a list of floats; local function loop sums up the list of float; finally, function string_of_float procures the string representation of the sum.

Figure 3: Edit fields

Figure 3 shows how the Graphical User Interface looks like to the final user. If the user presses the Exit button, s/he will leave the application. If s/he presses the Type tab, the application will start the Emacs widget, where one can type text, like in the real Emacs.

This document was translated from LATEX by HEVEA.