1.3.3 Menu Input

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.