1.3.1 A Simple Menu

The example client program creates and displays a simple pop-up menu consisting of a column of strings--a title string followed by selectable menu item strings. The implementation uses one window to represent the entire menu, plus a set of subwindows, one for each menu item. Here is the definition of a structure which represents such a menu.

(defstruct (menu)
   "A simple menu of text strings."
   (title "Choose an item:")
   item-alist                   ;((item-window item-string))
   window
   gcontext
   width
   title-width
   item-width
   item-height
   (geometry-changed-p t))      ;nil if unchanged since displayed
        

The window slot will contain the window object that represents the menu. The item- alist represents the relationship between the menu items and their associated subwindows. Each entry in item-alist is a list whose first element is a (sub)window object and whose second element is the corresponding item string. A window object is an instance of a CLX-defined data type which represents X windows. A window object actually carries two pieces of information: an X window ID integer and a display object. A display is another CLX-defined data type that represents a connection to a specific X display server. The gcontext slot contains an instance of a CLX data type known as a graphics context. A graphics context is a set of display attribute values, such as foreground color, fill style, line style, text font, and so forth. Each X graphics request (and hence each CLX graphics function call) must supply a graphics context to use in displaying the request. The menu's gcontext will thus hold all of the attribute values used during menu display.

The first thing to do is make an instance of a menu object:

(defun create-menu (parent-window text-color background-color text-font)
   (make-menu
      ;; Create menu graphics context
      :gcontext (CREATE-GCONTEXT :drawable   parent-window
                                 :foreground text-color
                                 :background background-color
                                 :font       text-font)

      ;; Create menu window
      :window    (CREATE-WINDOW
                  :parent            parent-window
                  :class             :input-output
                  :x                 0                     ;temporary value
                  :y                 0                     ;temporary value
                  :width             16                    ;temporary value
                  :height            16                    ;temporary value
                  :border-width 2  
                  :border            text-color
                  :background        background-color
                  :save-under        :on
                  :override-redirect :on                   ;override window mgr when positioning
                  :event-mask        (MAKE-EVENT-MASK :leave-window :exposure))))
        

create-window is one of the most important CLX functions, since it creates and returns a window object. Several of its options are shown here. The default window class is :input-output, but X provides for :input-only windows, too. Every window must have a parent window, except for a system-defined root window, which represents an entire display screen. The :event-mask keyword value, a CLX event-mask data type, says that an input event will be received for the menu window when the window is exposed and also when the pointer cursor leaves the window. The window border is a pattern-filled or (as in this case) a solid-colored boundary which is maintained automatically by the X server; a client cannot draw in a window's border, since all graphics requests are relative to the origin (upper-left corner) of the window's interior and are clipped by the server to this inside region. Turning on the :save-under option is a hint to the X server that, when this window is made visible, it may be more efficient to save the pixels it obscures, rather than require several client programs to refresh their windows when the pop-up menu disappears. This is a way to work around X's client-managed refresh policy when only a small amount of screen space is needed temporarily.

Why is :override-redirect turned on for the menu window? This is actually a little unusual, because it prevents any window manager client from redirecting the position of the menu when it is popped up. Remember that the window manager represents the user's policy for controlling the positions of his windows, so this kind of redirection is ordinarily correct. However, in this case, as a favor to the user, the menu avoids redirection in order to pop up the menu at a very specific location; that is, under the pointer cursor.

What about the item subwindows? The menu-set-item-list function in the following example creates them whenever the menu's item list is changed. The upper-left x and y coordinates and the width and height are not important yet, because they are computed just before the menu is displayed. This function also calls create-window, demonstrating the equal treatment of parent and children windows in the X window hierarchy.

(defun menu-set-item-list (menu &rest item-strings)
  ;; Assume the new items will change the menu's width and height
  (setf (menu-geometry-changed-p menu) t)

  ;; Destroy any existing item windows
  (dolist (item (menu-item-alist menu))
    (DESTROY-WINDOW (first item)))
  
  ;; Add (item-window item-string) elements to item-alist
  (setf (menu-item-alist menu)
        (let (alist)
          (dolist (item item-strings (nreverse alist))
            (push (list (CREATE-WINDOW
                         :parent       (menu-window menu)
                         :x            0           ;temporary value
                         :y            0           ;temporary value
                         :width        16          ;temporary value
                         :height       16          ;temporary value
                         :background   (GCONTEXT-BACKGROUND (menu-gcontext menu))
                         :event-mask   (MAKE-EVENT-MASK :enter-window
                                                        :leave-window
                                                        :button-press
                                                        :button-release))
                        item)
                  alist)))))