;;; -*- mode:lisp; package: tv; base: 8; -*- ;;; ;;; MULTLINE ;;; This file contains code to implement multiline menu items in menus. ;;; ;;; Hacked 15 August 1985 Richard Mark Soley for Lambda port ;;; **************************************************************** ;;; Necessary Loads, Constants, Declarations. ;;; **************************************************************** #+franz (declare (macros t)) ; Allow macros ;;; **************************************************************** ;;; Internal Macros, Structure Definitions, FLAVOR definitions. ;;; **************************************************************** (defflavor multiline-item-mixin () () (:required-flavors basic-menu)) (defflavor momentary-multiline-menu () (multiline-item-mixin momentary-menu)) (defflavor left-multiline-item-mixin () () (:required-flavors multiline-item-mixin)) (defflavor momentary-left-multiline-menu () (left-multiline-item-mixin multiline-item-mixin momentary-menu)) ;;; **************************************************************** ;;; User Accessible Code ;;; **************************************************************** ;;; **************************************************************** ;;; Internal Code ;;; **************************************************************** ;(defwrapper (multiline-item-mixin :set-label) ; (label &body body) ; `(progn ,@body ; (send self ':menu-compute-geometry t))) (defmethod (multiline-item-mixin :after :set-label) (&rest ignore) (send self ':menu-compute-geometry t)) ;;; The only difference between this and the original is that here we ;;; compute the MAXIMUM x excursion, instead of the final one. This ;;; is important when the string extends over multiple lines. (DEFMETHOD (multiline-item-mixin :MENU-ITEM-STRING-WIDTH) (ITEM &OPTIONAL STOP-X) (MULTIPLE-VALUE-BIND (STRING FONT) (MENU-ITEM-STRING ITEM) (multiple-value-bind (nil nil max) (FUNCALL-SELF ':STRING-LENGTH STRING 0 NIL STOP-X FONT) max))) ;;; this works by computing the number of lines in the string (1+ number ;;; of return characters) and returning the product of lines and font-height. ;;; It's not perfect, but should be close. (defmethod (multiline-item-mixin :menu-item-string-height) (item &optional stop-x) (MULTIPLE-VALUE-BIND (STRING FONT) (MENU-ITEM-STRING ITEM) font stop-x ; These aren't used. (* (1+ (loop for char from 0 to (1- (string-length string)) count (= (aref string char) #\return))) line-height))) ;(+ 2 (font-raster-height font)) (defmethod (multiline-item-mixin :menu-max-width) (ilist) (loop for item in ilist maximize (send self ':menu-item-string-width item))) (defmethod (multiline-item-mixin :menu-max-height) (ilist) (loop for item in ilist maximize (send self ':menu-item-string-height item))) #+LMI (defvar set-edges-mode nil) (DEFMETHOD (multiline-item-mixin :MENU-COMPUTE-GEOMETRY) (DRAW-P &OPTIONAL INSIDE-WIDTH INSIDE-HEIGHT) "This function is called whenever something related to the geometry changes. The menu is redrawn if DRAW-P is T." (COND ((INSTANCE-VARIABLE-BOUNDP ITEM-LIST) ;Do nothing if item-list not specified yet ;; Get the new N-ROWS and so forth. (SETQ TOP-ROW 0 ROW-HEIGHT (send self ':menu-max-height item-list)) (MULTIPLE-VALUE (COLUMNS SCREEN-ROWS INSIDE-WIDTH INSIDE-HEIGHT) (send self ':MENU-DEDUCE-PARAMETERS NIL NIL INSIDE-WIDTH INSIDE-HEIGHT NIL NIL)) ;; Recompute the row map (MULTIPLE-VALUE (ROW-MAP TOTAL-ROWS) (send self ':MENU-COMPUTE-ROW-MAP INSIDE-WIDTH)) (FUNCALL-SELF ':NEW-SCROLL-POSITION TOP-ROW) (SETQ COLUMN-WIDTH (AND (NOT (GEOMETRY-FILL-P GEOMETRY)) (// (+ INSIDE-WIDTH MENU-INTERCOLUMN-SPACING) COLUMNS))) (COND ((AND (= INSIDE-HEIGHT (SHEET-INSIDE-HEIGHT)) (= INSIDE-WIDTH (SHEET-INSIDE-WIDTH))) (AND DRAW-P (SHEET-FORCE-ACCESS (SELF :NO-PREPARE) (FUNCALL-SELF ':MENU-DRAW)))) ((send self ':SET-INSIDE-SIZE INSIDE-WIDTH INSIDE-HEIGHT ':VERIFY) ;; Room to do this in current place. (LET-GLOBALLY ((SET-EDGES-MODE T)) (send self ':SET-INSIDE-SIZE INSIDE-WIDTH INSIDE-HEIGHT))) (T ;; Else try to be approximately in the same place (LET ((CX (+ X-OFFSET (// WIDTH 2))) (CY (+ Y-OFFSET (// HEIGHT 2)))) (WITH-SHEET-DEEXPOSED (SELF) (LET-GLOBALLY ((SET-EDGES-MODE T)) (FUNCALL-SELF ':SET-INSIDE-SIZE INSIDE-WIDTH INSIDE-HEIGHT)) (CENTER-WINDOW-AROUND SELF CX CY))))))) NIL) (DEFMETHOD (multiline-item-mixin :MOUSE-MOVES) (X Y &AUX ITEM ITEMS ROW XREL BLINKER BLX (BLWIDTH 0) COLN STOP-ITEM (FILL-P (GEOMETRY-FILL-P GEOMETRY))) (MOUSE-SET-BLINKER-CURSORPOS) (SETQ ROW (// (- Y (SHEET-INSIDE-TOP)) ROW-HEIGHT) XREL (- X (SHEET-INSIDE-LEFT)) BLINKER (CAR BLINKER-LIST)) (COND ((AND ( XREL 0) ;If inside the menu (< X (SHEET-INSIDE-RIGHT)) ( Y (SHEET-INSIDE-TOP)) (< Y (SHEET-INSIDE-BOTTOM))) ;;If mouse is past the last displayed row, blink item on that row. (AND (OR (>= (+ TOP-ROW ROW) TOTAL-ROWS) (>= ROW SCREEN-ROWS)) (SETQ ROW (1- (MIN SCREEN-ROWS (- TOTAL-ROWS TOP-ROW))))) (IF (MINUSP ROW) (SETQ ITEMS NIL STOP-ITEM NIL) ;No items visible (SETQ ITEMS (AREF ROW-MAP (+ TOP-ROW ROW)) STOP-ITEM (AREF ROW-MAP (+ TOP-ROW ROW 1)))) (COND (FILL-P ;Fill mode, cogitate (SETQ BLX 0) (DO ((L ITEMS (CDR L)) (ITM) (OITM NIL ITM) (X 0 (+ X (SETQ BLWIDTH (send self ':MENU-ITEM-STRING-WIDTH ITM)) MENU-INTERWORD-SPACING))) ((OR (NULL L) (> X XREL)) ;If this string crosses the mouse, it's the one (SETQ ITEM OITM BLX (1- BLX) BLWIDTH (+ BLWIDTH 1))) (AND (EQ L STOP-ITEM) ;; The next item on next line -- punt (RETURN NIL)) (SETQ ITM (CAR L) BLX X))) (T ;Columnated, find which column (SETQ COLN (// XREL COLUMN-WIDTH)) ;Column selected (SETQ ITEM (CAR (NTHCDR COLN ITEMS))) ;This may be NIL (SETQ BLWIDTH (1+ (send self ':MENU-ITEM-STRING-WIDTH ITEM COLUMN-WIDTH))) (SETQ BLX (+ (* COLN COLUMN-WIDTH) ;Start of column -1 (MAX 0 (// (- COLUMN-WIDTH ;Centering MENU-INTERCOLUMN-SPACING BLWIDTH) 2)))))))) ;; If this item is non-selectable, don't select it. (AND (NOT (ATOM ITEM)) (NOT (ATOM (CDR ITEM))) (NOT (ATOM (CDDR ITEM))) (EQ (CADR ITEM) ':NO-SELECT) (SETQ ITEM NIL)) ;; Now make the blinker be where and what we have just found it should be. (BLINKER-SET-VISIBILITY BLINKER (NOT (NULL ITEM))) (SETQ CURRENT-ITEM ITEM) (COND (ITEM (FUNCALL BLINKER ':SET-SIZE-AND-CURSORPOS BLWIDTH row-height ;;;; (+ (FONT-CHAR-HEIGHT (AREF FONT-MAP 0)) 2) BLX (1- (* ROW ROW-HEIGHT)))))) (DEFMETHOD (multiline-item-mixin :MENU-DRAW) (&AUX (FILL-P (GEOMETRY-FILL-P GEOMETRY))) ;; Make sure the mouse knows we're changing (AND EXPOSED-P (MOUSE-WAKEUP)) (PREPARE-SHEET (SELF) (FUNCALL-SELF ':CLEAR-SCREEN) (DO ((ROW TOP-ROW (1+ ROW)) (Y-POS 0 (+ Y-POS ROW-HEIGHT)) (LIM (MIN TOTAL-ROWS (+ TOP-ROW SCREEN-ROWS)))) (( ROW LIM)) (DO ((ITEMS (AREF ROW-MAP ROW) (CDR ITEMS)) (END-ITEM-LIST (AREF ROW-MAP (1+ ROW))) (STR) (FONT) (FLAG) (X-POS 0)) ((EQ ITEMS END-ITEM-LIST)) (MULTIPLE-VALUE (STR FONT) (MENU-ITEM-STRING (CAR ITEMS))) (UNWIND-PROTECT (PROGN (AND (SETQ FLAG (AND (NEQ FONT CURRENT-FONT) CURRENT-FONT)) (FUNCALL-SELF ':SET-CURRENT-FONT FONT)) (COND (FILL-P ;Filled, put string followed by spacing (FUNCALL-SELF ':SET-CURSORPOS X-POS Y-POS) (FUNCALL-SELF ':STRING-OUT STR) (SETQ X-POS (+ (FUNCALL-SELF ':READ-CURSORPOS) MENU-INTERWORD-SPACING))) (T ;Columnated, center text within column (loop for substring in (get-substrings str) for i from 0 by line-height do (send self ':display-centered-string substring x-pos (- (+ X-POS COLUMN-WIDTH) MENU-INTERCOLUMN-SPACING) (+ y-pos i)) finally (setq x-pos (+ x-pos column-width)))))) (AND FLAG (FUNCALL-SELF ':SET-CURRENT-FONT FLAG))))))) ;;; This function returns the list of strings that form the lines in ;;; a multiline item string. ;;; For example, (get-substrings "FOOBAR") --> ("FOO" "BAR") (defun get-substrings (string) (loop with ptr1 = 0 for ptr2 = (string-search-char #\return string ptr1) collect (substring string ptr1 (if ptr2 ptr2)) until (null ptr2) do (setq ptr1 (1+ ptr2)))) ;;; **************************************************************** ;;; The following have to be defined to eliminate DEFUN-METHODS. ;;; **************************************************************** (DEFMETHOD (multiline-item-mixin :AFTER :INIT) (INIT-PLIST) (SETF (BLINKER-VISIBILITY (CAR BLINKER-LIST)) NIL) (if (and (instance-variable-boundp item-list) item-list) (menu-compute-font-map item-list)) (send self ':MENU-COMPUTE-GEOMETRY nil) (FUNCALL-SELF ':SET-SAVE-BITS (GET INIT-PLIST ':MENU-SAVE-BITS))) (DEFMETHOD (multiline-item-mixin :AFTER :CHANGE-OF-SIZE-OR-MARGINS) (&REST IGNORE) (COND ((EQ SET-EDGES-MODE T)) ;Recursive call, caller will take care of it ((EQ SET-EDGES-MODE ':USER) ;; Some sort of explicit setting of edges -- make the new size sticky (SETF (GEOMETRY-INSIDE-WIDTH GEOMETRY) (SHEET-INSIDE-WIDTH)) (SETF (GEOMETRY-INSIDE-HEIGHT GEOMETRY) (SHEET-INSIDE-HEIGHT)) (send self ':MENU-COMPUTE-GEOMETRY NIL)) ((EQ SET-EDGES-MODE NIL) (send self ':MENU-COMPUTE-GEOMETRY NIL)) (T ;; Some change other than by user or margins or compute geometry -- recompute ;; geometry, use current size, but don't make it sticky. ;; E.g. :MOVE-NEAR-WINDOW (send self ':MENU-COMPUTE-GEOMETRY NIL (SHEET-INSIDE-WIDTH) (SHEET-INSIDE-HEIGHT))))) (DEFMETHOD (multiline-item-mixin :SET-ITEM-LIST) (NEW-ITEM-LIST) (SETQ ITEM-LIST NEW-ITEM-LIST LAST-ITEM NIL CURRENT-ITEM NIL) (send self ':SET-FONT-MAP (MENU-COMPUTE-FONT-MAP NEW-ITEM-LIST)) #+LMI (setq geometry (list 1 nil nil nil nil nil)) #+LMI (send self ':MENU-COMPUTE-GEOMETRY nil) (send self ':MENU-COMPUTE-GEOMETRY T) ;Recompute parameters, and redraw menu NEW-ITEM-LIST) #+Symbolics (defmacro local-compute-font-map (item-list font) `(menu-compute-font-map ,item-list ,font)) #+LMI (defun local-compute-font-map (item-list font) (let ((fonts (menu-compute-font-map item-list))) (if (memq font fonts) fonts (nconc fonts (list font))))) (DEFMETHOD (multiline-item-mixin :SET-DEFAULT-FONT) (FONT) (FUNCALL-SELF ':SET-FONT-MAP (local-COMPUTE-FONT-MAP ITEM-LIST FONT)) (send self ':MENU-COMPUTE-GEOMETRY T)) (DEFMETHOD (multiline-item-mixin :SET-GEOMETRY) (&REST NEW-GEOMETRY) (DECLARE (ARGLIST (&OPTIONAL N-COLUMNS N-ROWS INSIDE-WIDTH INSIDE-HEIGHT MAX-WIDTH MAX-HEIGHT))) "NIL for an argument means make it unconstrained. T or unsupplied means leave it alone" (OR ( (LENGTH NEW-GEOMETRY) (LENGTH GEOMETRY)) (FERROR NIL "Too many args to :SET-GEOMETRY")) (DO ((G NEW-GEOMETRY (CDR G)) (CG GEOMETRY (CDR CG))) ((NULL G)) (IF (NEQ (CAR G) T) (RPLACA CG (CAR G)))) (send self ':MENU-COMPUTE-GEOMETRY T)) (DEFMETHOD (multiline-item-mixin :MENU-COMPUTE-ROW-MAP) (&OPTIONAL (INSIDE-WIDTH (SHEET-INSIDE-WIDTH)) &AUX (MAP (MAKE-ARRAY (1+ (LENGTH ITEM-LIST)))) WID (FILL-P (GEOMETRY-FILL-P GEOMETRY))) (DO ((ITEMS ITEM-LIST) (ROW 0 (1+ ROW))) ((NULL ITEMS) (VALUES (ADJUST-ARRAY-SIZE MAP (1+ ROW)) ;Last element always contains NIL ROW)) (ASET ITEMS MAP ROW) ;This is where this row starts (IF FILL-P ;Fill mode, we have some hairy calculation to do (DO ((SPACE INSIDE-WIDTH)) ((NULL ITEMS)) (SETQ WID (send self ':MENU-ITEM-STRING-WIDTH (CAR ITEMS))) (COND ((> WID SPACE) ;This one won't fit, break the line (AND (> WID INSIDE-WIDTH) (FERROR NIL "The item /"~A/" is too wide for this fill-mode menu" (CAR ITEMS))) (RETURN NIL))) (SETQ SPACE (- SPACE (+ WID MENU-INTERWORD-SPACING)) ITEMS (CDR ITEMS))) (SETQ ITEMS (NTHCDR COLUMNS ITEMS))))) (DEFMETHOD (multiline-item-mixin :ITEM-CURSORPOS) (ITEM &AUX (ALEN (ARRAY-LENGTH ROW-MAP))) (DO ((ROW (1- (MIN (+ TOP-ROW SCREEN-ROWS) ;last row on screen ALEN)) ;last row that exists (1- ROW))) ((< ROW TOP-ROW) NIL) (AND (MEMQ ITEM (AREF ROW-MAP ROW)) (OR (= ROW (1- ALEN)) (NOT (MEMQ ITEM (AREF ROW-MAP (1+ ROW))))) (RETURN (IF (NOT (GEOMETRY-FILL-P GEOMETRY)) (+ (* (FIND-POSITION-IN-LIST ITEM (AREF ROW-MAP ROW)) COLUMN-WIDTH) (// COLUMN-WIDTH 2)) (DO ((L (AREF ROW-MAP ROW) (CDR L)) (XSTART 0 (+ XSTART SWIDTH MENU-INTERWORD-SPACING)) (SWIDTH)) (NIL) (SETQ SWIDTH (send self ':MENU-ITEM-STRING-WIDTH (CAR L))) (AND (EQ (CAR L) ITEM) (RETURN (+ XSTART (// SWIDTH 2)))))) (+ (* (- ROW TOP-ROW) ROW-HEIGHT) (// ROW-HEIGHT 2)))))) (DEFMETHOD (multiline-item-mixin :ITEM-RECTANGLE) (ITEM &AUX (X 0) SWIDTH (ALEN (ARRAY-LENGTH ROW-MAP))) (DO ((ROW (1- (MIN (+ TOP-ROW SCREEN-ROWS) ;last row on screen ALEN)) ;last row that exists (1- ROW))) ((< ROW TOP-ROW) NIL) (COND ((AND (MEMQ ITEM (AREF ROW-MAP ROW)) (OR (= ROW (1- ALEN)) (NOT (MEMQ ITEM (AREF ROW-MAP (1+ ROW)))))) (IF (NOT (GEOMETRY-FILL-P GEOMETRY)) (SETQ SWIDTH (send self ':MENU-ITEM-STRING-WIDTH ITEM COLUMN-WIDTH) X (+ (* (FIND-POSITION-IN-LIST ITEM (AREF ROW-MAP ROW)) COLUMN-WIDTH) (// (- COLUMN-WIDTH MENU-INTERCOLUMN-SPACING SWIDTH) 2))) (DOLIST (IT (AREF ROW-MAP ROW)) (SETQ SWIDTH (send self ':MENU-ITEM-STRING-WIDTH IT)) (AND (EQ IT ITEM) (RETURN)) (SETQ X (+ X SWIDTH MENU-INTERWORD-SPACING)))) (RETURN (1- X) (1- (* (- ROW TOP-ROW) ROW-HEIGHT)) (+ X SWIDTH 1) (1- (* (1+ (- ROW TOP-ROW)) ROW-HEIGHT))))))) (DEFMETHOD (multiline-item-mixin :MENU-DEDUCE-PARAMETERS) (N-COLUMNS N-ROWS INSIDE-WIDTH INSIDE-HEIGHT MAX-WIDTH MAX-HEIGHT &AUX TEM COL-WIDTH (N-ITEMS (LENGTH ITEM-LIST)) FILL-P) ;; Pick up default constraints from GEOMETRY (SETQ N-COLUMNS (OR N-COLUMNS (GEOMETRY-N-COLUMNS GEOMETRY)) N-ROWS (OR N-ROWS (GEOMETRY-N-ROWS GEOMETRY)) INSIDE-WIDTH (OR INSIDE-WIDTH (GEOMETRY-INSIDE-WIDTH GEOMETRY)) INSIDE-HEIGHT (OR INSIDE-HEIGHT (GEOMETRY-INSIDE-HEIGHT GEOMETRY)) MAX-WIDTH (OR MAX-WIDTH (GEOMETRY-MAX-WIDTH GEOMETRY)) MAX-HEIGHT (OR MAX-HEIGHT (GEOMETRY-MAX-HEIGHT GEOMETRY))) ;; If any of the arguments was :UNCONSTRAINED, that means use NIL ;; even if the geometry is non-NIL, whereas if an argument was NIL ;; that means use any constraint that is in the geometry. (AND (EQ N-COLUMNS ':UNCONSTRAINED) (SETQ N-COLUMNS NIL)) (AND (EQ N-ROWS ':UNCONSTRAINED) (SETQ N-ROWS NIL)) (AND (EQ INSIDE-WIDTH ':UNCONSTRAINED) (SETQ INSIDE-WIDTH NIL)) (AND (EQ INSIDE-HEIGHT ':UNCONSTRAINED) (SETQ INSIDE-HEIGHT NIL)) (AND (EQ MAX-WIDTH ':UNCONSTRAINED) (SETQ MAX-WIDTH NIL)) (AND (EQ MAX-HEIGHT ':UNCONSTRAINED) (SETQ MAX-HEIGHT NIL)) ;; Decide whether it is fill mode or array mode (AND (SETQ FILL-P (AND N-COLUMNS (ZEROP N-COLUMNS))) (SETQ N-COLUMNS NIL)) ;; Realize any immediately clear implications (AND N-ROWS (NULL INSIDE-HEIGHT) (SETQ INSIDE-HEIGHT (* N-ROWS row-HEIGHT))) (AND INSIDE-HEIGHT (NULL N-ROWS) (SETQ N-ROWS (// INSIDE-HEIGHT row-HEIGHT))) (SETQ MAX-HEIGHT (MIN (OR INSIDE-HEIGHT MAX-HEIGHT 10000) (- (SHEET-INSIDE-HEIGHT SUPERIOR) TOP-MARGIN-SIZE BOTTOM-MARGIN-SIZE)) MAX-WIDTH (MIN (OR INSIDE-WIDTH MAX-WIDTH 10000) (- (SHEET-INSIDE-WIDTH SUPERIOR) LEFT-MARGIN-SIZE RIGHT-MARGIN-SIZE))) ;; Compute the horizontal parameters. (COND ((AND INSIDE-WIDTH (OR N-COLUMNS FILL-P)) ) ;It's fully-determined (INSIDE-WIDTH ;We have the width, and it's not in fill mode, compute (SETQ N-COLUMNS ; N-COLUMNS based on widest item, but always fill the space (MAX (MIN (// (+ INSIDE-WIDTH MENU-INTERCOLUMN-SPACING) (+ (send self ':MENU-MAX-WIDTH ITEM-LIST) MENU-INTERCOLUMN-SPACING)) (IF N-ROWS (// (+ N-ITEMS (1- N-ROWS)) N-ROWS) N-ITEMS)) 1))) (N-COLUMNS ;We don't have the width, but do know how many columns, compute width (SETQ INSIDE-WIDTH (MIN (- (* (+ (send self ':MENU-MAX-WIDTH ITEM-LIST) MENU-INTERCOLUMN-SPACING) N-COLUMNS) MENU-INTERCOLUMN-SPACING) MAX-WIDTH))) (N-ROWS ;We know how high, make it wide enough to come out this high (IF FILL-P (SETQ INSIDE-WIDTH (MIN (// (+ (MENU-FILL-WIDTH ITEM-LIST) (1- N-ROWS)) N-ROWS) MAX-WIDTH)) (SETQ N-COLUMNS (MAX (// (+ N-ITEMS (1- N-ROWS)) N-ROWS) 1) INSIDE-WIDTH (- (* (SETQ COL-WIDTH (+ (send self ':MENU-MAX-WIDTH ITEM-LIST) MENU-INTERCOLUMN-SPACING)) N-COLUMNS) MENU-INTERCOLUMN-SPACING)))) ((NOT FILL-P) ;No geometry supplied, pick N-ROWS and N-COLUMNS to make it look nice ;Use the largest number of columns which does not make the ratio ;of height to width less than the Golden ratio (SETQ TEM (* (SETQ COL-WIDTH (send self ':MENU-MAX-WIDTH ITEM-LIST)) N-ITEMS row-HEIGHT) COL-WIDTH (+ COL-WIDTH MENU-INTERCOLUMN-SPACING) N-COLUMNS (MAX (// (ISQRT (FIX (// TEM MENU-GOLDEN-RATIO))) COL-WIDTH) 1) INSIDE-WIDTH (- (* COL-WIDTH N-COLUMNS) MENU-INTERCOLUMN-SPACING))) (T ;No geometry supplied, and in fill mode, make it like above (SETQ INSIDE-WIDTH (MAX (ISQRT (FIX (// (* (MENU-FILL-WIDTH ITEM-LIST) row-HEIGHT) MENU-GOLDEN-RATIO))) 40)))) ;Don't get zero, and don't get absurdly small ;; Now figure out the vertical characteristics (OR N-ROWS (SETQ N-ROWS (IF FILL-P (// (+ (MENU-FILL-WIDTH ITEM-LIST) INSIDE-WIDTH -1) INSIDE-WIDTH) (// (+ N-ITEMS N-COLUMNS -1) N-COLUMNS)))) (OR INSIDE-HEIGHT (SETQ INSIDE-HEIGHT (* N-ROWS row-HEIGHT))) ;; If there is a label, the menu must be at least wide enough to accomodate it (LET ((L (FUNCALL-SELF ':SEND-IF-HANDLES ':LABEL-SIZE))) (AND L (SETQ INSIDE-WIDTH (MAX INSIDE-WIDTH L)))) ;; If this came out too high or too wide, retrench (AND (> INSIDE-HEIGHT MAX-HEIGHT) (SETQ N-ROWS (// MAX-HEIGHT row-HEIGHT) INSIDE-HEIGHT (* N-ROWS row-HEIGHT))) (COND ((> INSIDE-WIDTH MAX-WIDTH) (SETQ INSIDE-WIDTH MAX-WIDTH) (AND COL-WIDTH ;If N-COLUMNS was not user-supplied, recompute it (SETQ N-COLUMNS (MAX (// (+ INSIDE-WIDTH MENU-INTERCOLUMN-SPACING) COL-WIDTH) 1))))) ;; At this point, INSIDE-WIDTH, INSIDE-HEIGHT, N-COLUMNS (if not FILL-P), and N-ROWS ;; are all valid and consistent, and not bigger than the available area, ;; provided that the user's original parameters were not illegally huge. ;; Return all the dependent parameters as multiple values (VALUES (IF FILL-P 0 N-COLUMNS) N-ROWS INSIDE-WIDTH INSIDE-HEIGHT)) (DEFMETHOD (multiline-item-mixin :MOVE-NEAR-WINDOW) (W) (MULTIPLE-VALUE-BIND (LEFT TOP RIGHT BOTTOM) (FUNCALL W ':EDGES) (MULTIPLE-VALUE-BIND (IGNORE IGNORE IGNORE NEW-HEIGHT) (send self ':MENU-DEDUCE-PARAMETERS NIL NIL (- RIGHT LEFT) NIL NIL NIL) (SETQ NEW-HEIGHT (+ NEW-HEIGHT TOP-MARGIN-SIZE BOTTOM-MARGIN-SIZE)) ;If it won't fit below try putting it above (AND (> (+ BOTTOM NEW-HEIGHT) (SHEET-INSIDE-BOTTOM SUPERIOR)) (SETQ BOTTOM (MAX (- TOP NEW-HEIGHT) 0))) ;Put it there (LET-GLOBALLY ((SET-EDGES-MODE ':MOVE-NEAR)) (FUNCALL-SELF ':SET-EDGES LEFT BOTTOM RIGHT (+ BOTTOM NEW-HEIGHT))) (FUNCALL-SELF ':EXPOSE)))) ;;; **************************************************************** ;;; Left-multiline-item-mixin ;;; **************************************************************** (DEFMETHOD (left-multiline-item-mixin :MENU-DRAW) (&AUX (FILL-P (GEOMETRY-FILL-P GEOMETRY))) ;; Make sure the mouse knows we're changing (AND EXPOSED-P (MOUSE-WAKEUP)) (PREPARE-SHEET (SELF) (FUNCALL-SELF ':CLEAR-SCREEN) (DO ((ROW TOP-ROW (1+ ROW)) (Y-POS 0 (+ Y-POS ROW-HEIGHT)) (LIM (MIN TOTAL-ROWS (+ TOP-ROW SCREEN-ROWS)))) (( ROW LIM)) (DO ((ITEMS (AREF ROW-MAP ROW) (CDR ITEMS)) (END-ITEM-LIST (AREF ROW-MAP (1+ ROW))) (STR) (FONT) (FLAG) (X-POS 0)) ((EQ ITEMS END-ITEM-LIST)) (MULTIPLE-VALUE (STR FONT) (MENU-ITEM-STRING (CAR ITEMS))) (UNWIND-PROTECT (PROGN (AND (SETQ FLAG (AND (NEQ FONT CURRENT-FONT) CURRENT-FONT)) (FUNCALL-SELF ':SET-CURRENT-FONT FONT)) (COND (FILL-P ;Filled, put string followed by spacing (FUNCALL-SELF ':SET-CURSORPOS X-POS Y-POS) (FUNCALL-SELF ':STRING-OUT STR) (SETQ X-POS (+ (FUNCALL-SELF ':READ-CURSORPOS) MENU-INTERWORD-SPACING))) (T ;Columnated, center text within column (loop for substring in (get-substrings str) for i from 0 by line-height do (send self ':set-cursorpos x-pos (+ y-pos i)) do (send self ':string-out substring) finally (setq x-pos (+ x-pos column-width)))))) (AND FLAG (FUNCALL-SELF ':SET-CURRENT-FONT FLAG))))))) (DEFMETHOD (left-multiline-item-mixin :MOUSE-MOVES) (X Y &AUX ITEM ITEMS ROW XREL BLINKER BLX (BLWIDTH 0) COLN STOP-ITEM (FILL-P (GEOMETRY-FILL-P GEOMETRY))) (MOUSE-SET-BLINKER-CURSORPOS) (SETQ ROW (// (- Y (SHEET-INSIDE-TOP)) ROW-HEIGHT) XREL (- X (SHEET-INSIDE-LEFT)) BLINKER (CAR BLINKER-LIST)) (COND ((AND ( XREL 0) ;If inside the menu (< X (SHEET-INSIDE-RIGHT)) ( Y (SHEET-INSIDE-TOP)) (< Y (SHEET-INSIDE-BOTTOM))) ;;If mouse is past the last displayed row, blink item on that row. (AND (OR (>= (+ TOP-ROW ROW) TOTAL-ROWS) (>= ROW SCREEN-ROWS)) (SETQ ROW (1- (MIN SCREEN-ROWS (- TOTAL-ROWS TOP-ROW))))) (IF (MINUSP ROW) (SETQ ITEMS NIL STOP-ITEM NIL) ;No items visible (SETQ ITEMS (AREF ROW-MAP (+ TOP-ROW ROW)) STOP-ITEM (AREF ROW-MAP (+ TOP-ROW ROW 1)))) (COND (FILL-P ;Fill mode, cogitate (SETQ BLX 0) (DO ((L ITEMS (CDR L)) (ITM) (OITM NIL ITM) (X 0 (+ X (SETQ BLWIDTH (send self ':MENU-ITEM-STRING-WIDTH ITM)) MENU-INTERWORD-SPACING))) ((OR (NULL L) (> X XREL)) ;If this string crosses the mouse, it's the one (SETQ ITEM OITM BLX (1- BLX) BLWIDTH (+ BLWIDTH 1))) (AND (EQ L STOP-ITEM) ;; The next item on next line -- punt (RETURN NIL)) (SETQ ITM (CAR L) BLX X))) (T ;Columnated, find which column (SETQ COLN (// XREL COLUMN-WIDTH)) ;Column selected (SETQ ITEM (CAR (NTHCDR COLN ITEMS))) ;This may be NIL (SETQ BLWIDTH (1+ (send self ':MENU-ITEM-STRING-WIDTH ITEM COLUMN-WIDTH))) (SETQ BLX (+ (* COLN COLUMN-WIDTH) ;Start of column -1)))))) ;; If this item is non-selectable, don't select it. (AND (NOT (ATOM ITEM)) (NOT (ATOM (CDR ITEM))) (NOT (ATOM (CDDR ITEM))) (EQ (CADR ITEM) ':NO-SELECT) (SETQ ITEM NIL)) ;; Now make the blinker be where and what we have just found it should be. (BLINKER-SET-VISIBILITY BLINKER (NOT (NULL ITEM))) (SETQ CURRENT-ITEM ITEM) (COND (ITEM (FUNCALL BLINKER ':SET-SIZE-AND-CURSORPOS BLWIDTH row-height ;;;; (+ (FONT-CHAR-HEIGHT (AREF FONT-MAP 0)) 2) BLX (1- (* ROW ROW-HEIGHT))))))