Now that a menu can be displayed, the sample client program must define
how the menu will process user input. The menu-choose
function (shown in the following example) has the classic structure of
an X client program. First, do some initialization (for example,
present the menu at a given location). Then, enter an input event loop.
Read an input event, process it, and repeat the loop until a
termination event is received. The event-case
macro continues reading an event from the menu window's display
object until one of its clauses returns non-nil. These clauses
specify the action to be taken for each event type and also bind values
from the event report to local variables, such as the event-window
receiving the event. Notice that the :force-output-p option
is enabled, causing
event-case to begin by sending any client requests which CLX
has not yet output to the server. To improve performance, CLX quietly
queues up requests and periodically sends them off in a batch. However,
in an interactive feedback loop such as this, it is important to keep
the display crisply up-to-date.
(defun menu-choose (menu x y) ;; Display the menu so that first item is at x,y. (menu-present menu x y) (let ((items (menu-item-alist menu)) (mw (menu-window menu)) selected-item) ;; Event processing loop (do () (selected-item) (EVENT-CASE ((DRAWABLE-DISPLAY mw) :force-output-p t) (:exposure (count) ;; Discard all but final :exposure then display the menu (when (zerop count) (menu-refresh menu)) t) (:button-release (event-window) ;;Select an item (setf selected-item (second (assoc event-window items))) t) (:enter-notify (window) ;;Highlight an item (menu-highlight-item menu (find window items :key #'first)) t) (:leave-notify (window kind) (if (eql mw window) ;; Quit if pointer moved out of main menu window (setf selected-item (when (eq kind :ancestor) :none)) ;; Otherwise, unhighlight the item window left (menu-unhighlight-item menu (find window items :key #'first))) t) (otherwise () ;;Ignore and discard any other event t))) ;; Erase the menu (UNMAP-WINDOW mw) ;; Return selected item string, if any (unless (eq selected-item :none) selected-item)))
The event loop in menu-choose
demonstrates an idiom used
in all X programs: the contents of a window are displayed (in this
case, by calling menu-refresh
) only when an :exposure
event is received, signaling that the server has actually made the
window viewable. The handling of :exposure in
menu-choose
also implements a little trick for improving
efficiency. In general, when a window is exposed after being previously
obscured (perhaps only partially), the server is free to send several :exposure
events, one for each rectangular tile of the exposed region. For small
windows like this menu, it is not worth the trouble to redraw the image
one tile at a time. So the code above just ignores all but the last
tile exposure and redraws everything in one call to menu-refresh
.