The menu-recompute-geometry
function (shown in the
following example) handles the job of calculating the size of the menu,
based on its current item list and its current text font. CLX provides
a way to inquire the geometrical properties of a font object (for
example, its ascent and descent from the baseline) and also a text-extents
function.
text-extents returns the geometry of a given string as
displayed in a given font. Notice the use of the with-state
macro when setting a window's geometry attributes. CLX strives to
preserve the familiar setf style of accessing individual window
attributes, even though an attribute access actually involves sending a
request to a (possibly remote) server and/or waiting for a reply. with-state
tells CLX to batch together all read and write accesses to a given
window, using a local cache to minimize the number of server requests.
This CLX feature can result in a dramatic improvement in client
performance without burdening the programmer interface.
menu-recompute-geometry
causes all the item subwindows to
become mapped. Mapping a window means attempting to make it
visible on the screen. However, a subwindow will not actually be
visible until it and all of its ancestors are mapped. Even then,
another window might be covering up the subwindow.
(defun menu-recompute-geometry (menu) (when (menu-geometry-changed-p menu) (let* ((menu-font (GCONTEXT-FONT (menu-gcontext menu))) (title-width (TEXT-EXTENTS menu-font (menu-title menu))) (item-height (+ (FONT-ASCENT menu-font) (FONT-DESCENT menu-font) *menu-item-margin*)) (item-width 0) (items (menu-item-alist menu)) menu-width) ;; Find max item string width (setf item-width (+ *menu-item-margin* (dolist (next-item items item-width) (setf item-width (max item-width (TEXT-EXTENTS menu-font (second next-item))))))) ;; Compute final menu width, taking margins into account (setf menu-width (max title-width (+ item-width *menu-item-margin*))) (let ((window (menu-window menu))) ;; Update width and height of menu window (WITH-STATE (window) (setf (DRAWABLE-WIDTH window) menu-width (DRAWABLE-HEIGHT window) (* (1+ (length items)) item-height))) ;; Update width, height, position of item windows (let ((item-left (round (- menu-width item-width) 2)) (next-item-top (- item-height (round *menu-item-margin* 2)))) (dolist (next-item items) (let ((window (first next-item))) (WITH-STATE (window) (setf (DRAWABLE-HEIGHT window) item-height (DRAWABLE-WIDTH window) item-width (DRAWABLE-X window) item-left (DRAWABLE-Y window) next-item-top))) (incf next-item-top item-height)))) ;; Map all item windows (MAP-SUBWINDOWS (menu-window menu)) ;; Save item geometry (setf (menu-item-width menu) item-width (menu-item-height menu) item-height (menu-width menu) menu-width (menu-title-width menu) title-width (menu-geometry-changed-p menu) nil))))
Of course, the sample client must know how to draw/redraw the menu and
its items, so the function menu-refresh
is defined next to
handle that task (shown in the following example). Note that the
location of window output is given relative to the window origin.
Windows and subwindows have different coordinate systems. The location
of the origin (upper-left corner) of a subwindow's coordinate
system is given with respect to its parent window's coordinate
system. Negative coordinates are valid, although only output to the
+x/+y quadrant of a window's coordinate system will ever be visible.
(defun menu-refresh (menu) (let* ((gcontext (menu-gcontext menu)) (baseline-y (FONT-ASCENT (GCONTEXT-FONT gcontext)))) ;; Show title centered in "reverse-video" (let ((fg (GCONTEXT-BACKGROUND gcontext)) (bg (GCONTEXT-FOREGROUND gcontext))) (WITH-GCONTEXT (gcontext :foreground fg :background bg) (DRAW-IMAGE-GLYPHS (menu-window menu) gcontext (round (- (menu-width menu) (menu-title-width menu)) 2) ;start x baseline-y ;start y (menu-title menu)))) ;; Show each menu item (position is relative to item window) (let ((box-margin (round *menu-item-margin* 2))) (dolist (item (menu-item-alist menu)) (DRAW-IMAGE-GLYPHS (first item) gcontext box-margin ;start x (+ baseline-y box-margin) ;start y (second item))))))
with-gcontext is a CLX macro that allows you temporarily to modify a graphics context within the dynamic scope of the macro body. draw-image-glyphs is a CLX text drawing function which produces a terminal-like rendering: foreground character on a background block. (More sophisticated text rendering functions are also available.) The strange use of glyphs instead of string here actually highlights an important fact: X and Common Lisp have totally different concepts of a character. A Common Lisp character is an object whose implementation can comprehend a vast universe of text complexities (typefaces, type styles, international character sets, symbols, and so forth). However, to X, a string is just a sequence of integer indexes into the array of bitmaps represented by a CLX font object. In general, draw-image-glyphs , text-extents , and other CLX text functions accept a :translate keyword argument. Its value is a function which translates the characters of a string argument into the appropriate font-and-index pairs needed by CLX. This example relies upon the default translation function, which simply uses char-code to compute an index into the current font.