Design forms using EMACS Widget library

Using the function sqlite-query, we can now interact programmatically with SQLITE. For end users, we need to design a user friendly GUI. The EMACS widget library is about as friendly as EMACS will get. There is very limited information on how to use the EMACS widgets. You can read about the widget library within EMACS info, or on the web. Ye Wenbin also has a useful tutorial. Should you find other resources, please let me know. Essentially you create a form with a widget for each field, such as a text field, dropdown, radio button etc. Once the user has populated the fields, a button assembles and submits the SQL statement. Here is a simple example with widgets for some of the fields in our “ab” database table:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
(require 'widget)
(eval-when-compile
(require 'wid-edit))


(defun add-antibody()
"Prep a buffer for handling sqlite queries."
(interactive)
(setq buffer (get-buffer-create "*Sqlite Forms*"))
(switch-to-buffer buffer)
(sqlite-mode)

(let ((inhibit-read-only t))
(erase-buffer))
(remove-overlays)

(widget-insert "Antibody Entry form")
(widget-insert "\n\n")

(widget-insert "\n\n")
(widget-create 'editable-field
:size 10
:notify (lambda (widget &rest ignore)
(setq ab-name (widget-value widget)))
:format "Antibody name: %v " ; Text after the field!
ab-name)


(widget-insert "\n\n")
(widget-create 'editable-field
:size 50
:notify (lambda (widget &rest ignore)
;;(widget-setup)
(setq ab-supplier (widget-value widget)))
:format "Supplier: %v " ; Text after the field!
ab-supplier)
(widget-insert "\n\n")


(widget-create 'menu-choice
:tag "Select Host Species"
:value "unknown"
:help-echo "Click to view dropdown"
:notify (lambda (widget &rest ignore)
(setq host-species (widget-value widget)))
'(item "mouse")
'(item "rabbit")
'(item "goat")
'(item "donkey"))



(widget-insert "\n")
(widget-create 'push-button
:notify (lambda (&rest ignore)
:button-face 'custom-button
(run-sql-script)
(message "Ran SQL script"))
"Add antibody to database")

(widget-insert "\n")
(widget-insert "\n")


(widget-create 'push-button
:button-face 'custom-button
:notify (lambda (&rest ignore)
(clear-add-antibody-form)
(message ""))
" Clear Form ")


(widget-insert "\n")
(widget-insert "\n\n")
(widget-setup))



To use the form, issue the command (add-antibody), which is also connected to one of the menu items discussed the the sqlite-mode section. Once the data has been entered, a “submit” button is pressed which will assemble the sql statement:

1
2
3
4
(setq sql-command (concat "INSERT INTO ab VALUES(NULL, '" (car (cdr (split-string selected-gene-id "\t"))) "','"
(chomp ab-name) "','" antigen-species "','" host-species "','"(chomp ab-supplier) "','"(chomp ab-catnum) "','"
ab-isotype "','" ab-specificity "','" ab-applications"','" (chomp ab-epitope) "','" (chomp pdf) "','" (chomp clone-id) "','"
(chomp notes) "');"))

Note that any input through a text field is processed with chomp to remove whitespace.


Handling Checkbox user input

Here is a truncated example showing 2 of 6 checkboxes on one of my forms:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(widget-create 'checkbox
:format "%[%v%] Western "
:notify (lambda (wid &rest ignore)
(if (widget-value wid)
(setq western t)
(setq western nil)))
western)


(widget-create 'checkbox
:format "%[%v%] ELISA\n"
:notify (lambda (wid &rest ignore)
(if (widget-value wid)
(setq elisa t)
(setq elisa nil)))
elisa)

First set up variables to hold the status of each element in the check box list, as well as a variable to hold the final semicolon delimited string. I am showing all six variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
;;antibody application variables
(defvar ab-applications ""
"Holds concatenated string of applications")

(defvar western nil
"True if western etc.")

(defvar ip nil
"")

(defvar elisa nil
"")

(defvar immunohistochemistry nil
"")

(defvar immunof nil
"")

(defvar hcs nil
"")

;;;end applications variables
;;;

Next is the function to be invoked when the user clicks the “OK” or similar button on the form. Items that are selected can be cons into a list for further processing. Here I simply concatenate the list using semicolon as a separator for the purpose of storage in the database and reporting.

1
2
3
4
5
6
7
8
(defun create-ab-applications-list ()
(setq ab-applications-list ())
(if western (setq ab-applications-list (cons "w" ab-applications-list)))
(if ip (setq ab-applications-list (cons "IP" ab-applications-list)))
(if elisa (setq ab-applications-list (cons "ELISA" ab-applications-list)))
(if immunohistochemistry (setq ab-applications-list (cons "IH" ab-applications-list)))
(if hcs (setq ab-applications-list (cons "HCS" ab-applications-list)))
(setf ab-applications (mapconcat 'identity ab-applications-list ";")))

Populating dropdown lists on-the-fly from database content

Quite often you wish to give the user a prepopulated dropdown where the content comes from a field in the database. Here is an example of form code for a dropdown:

1
2
3
4
5
6
7
8
9
10
(widget-create 'menu-choice
:tag "Select Host Species"
:value "unknown"
:help-echo "Click to view dropdown"
:notify (lambda (widget &rest ignore)
(setq host-species (widget-value widget)))
'(item "mouse")
'(item "rabbit")
'(item "goat")
'(item "donkey"))

The dropdown content is:

1
2
3
4
'(item "mouse") 
'(item "rabbit")
'(item "goat")
'(item "donkey")

Suppose rather than hard coding I want to pull out all the species available in the ab.host field in my database? Using my sqlite-query command, it would be easy enough to get the list:

( “mouse” “rabbit” “goat” “donkey” )

1
2
(setq sql-command "SELECT host FROM ab;")
(setq host-species (sqlite-query sql-command))

But how to I convert that to the format shown above for dropdown content? The dropdown content shown above is equivalent to:

‘(item “mouse”) ‘(item “rabbit”) ‘(item “goat”) ‘(item “donkey”)

which is equivalent to:

(quote (item “mouse”)) (quote (item “rabbit”)) (quote (item “goat”)) (quote (item “donkey”))

Note that this is not a list, as it does not have circumscribed parentheses. Since we want to write code, a macro is the way to go. This is what it looks like:

1
2
3
4
5
6
7
8
(defmacro multi-item ()
`(widget-create 'menu-choice
:tag "Select Host Species"
:value "unknown"
:help-echo "Click to view dropdown"
:notify (lambda (widget &rest ignore)
(setq selected-host-species (widget-value widget)))
,@(mapcar '(lambda (a) `'(item ,a)) host-species)))

I mapcar over my host-species list, creating the statement (quote (item “element”)) for each element of the list, then splice that into my backquoted widget-create code. With this code, it is easy now to create a dropdown the contents of which depend on other selections in the form e.g. an initial dropdown, checkbox, or radio button. The notify statement of the initial selection should set a variable and then refresh the form, invoking the query that will populate the dependant dropdown list. You could, for example, make the second list visible only after the initial selection.

Though the widget library is crude by current standards, it will meet most needs either directly or with a hack.

Share