home *** CD-ROM | disk | FTP | other *** search
Text File | 1990-07-31 | 44.6 KB | 1,105 lines |
- ;;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Syntax:Common-Lisp -*-
-
- ;;;----------------------------------------------------------------------------------+
- ;;; |
- ;;; TEXAS INSTRUMENTS INCORPORATED |
- ;;; P.O. BOX 149149 |
- ;;; AUSTIN, TEXAS 78714 |
- ;;; |
- ;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
- ;;; |
- ;;; Permission is granted to any individual or institution to use, copy, modify, and |
- ;;; distribute this software, provided that this complete copyright and permission |
- ;;; notice is maintained, intact, in all copies and supporting documentation. |
- ;;; |
- ;;; Texas Instruments Incorporated provides this software "as is" without express or |
- ;;; implied warranty. |
- ;;; |
- ;;;----------------------------------------------------------------------------------+
-
- ;;;
- ;;; Dialog-button and dialog-item, buttons and items that bring up general
- ;;; dialogs when messed with. Dialogs include menus, property-sheets, commands,
- ;;; and confirms. Also defined here is the menu protocol event translations,
- ;;; for press-drag-release and click-move-click.
-
-
- (in-package "CLIO-OPEN")
-
- (export '(
- dialog-button
- dialog-item
- make-dialog-item
- make-dialog-button
- button-dialog
- present-dialog ; As good a place as any.
- )
- 'clio-open)
-
-
- ;;;
- ;;; Contact definitions and interface functions.
-
- (defcontact dialog-button (action-button)
- ((dialog :type (or null list function contact)
- :reader button-dialog ; Note (setf button-dialog) below.
- :initarg :dialog
- :initform nil))
- (:resources
- (dialog :initform nil
- :type (or null list function contact))))
-
- (defun make-dialog-button (&rest initargs)
- (apply #'make-contact 'dialog-button initargs))
-
- (defmethod (setf button-dialog) (new-dialog (button dialog-button))
- (check-type new-dialog (or null contact))
- (with-slots (preferred-width dialog) button
- ;; (LG) Force preferred-size to recalculate width.
- (setq preferred-width nil)
- (when (and dialog
- (not (eq dialog new-dialog)))
- (disassociate-dialog-from-button dialog button)))
- (associate-dialog-with-button new-dialog button))
-
-
- ;; A DIALOG-ITEM is a specialization of an ACTION-ITEM and is intended for use
- ;; in OL compliant menus. It differs from a DIALOG-BUTTON in appearance as well
- ;; as in its sensitivity to various mouse gestures depending on the mode of the
- ;; menu which contains it.
- (defcontact dialog-item (action-item)
- ((dialog :type (or null list function contact)
- :reader button-dialog ; Note (setf button-dialog) below.
- :initarg :dialog
- :initform nil)
- (last-x :type integer ; For drag-right checking.
- :initform 0)
- (active-x :type (or null integer) ; Ditto.
- :initform nil))
- (:resources
- (dialog :initform nil
- :type (or null list function contact))))
-
- (defun make-dialog-item (&rest initargs)
- (apply #'make-contact 'dialog-item initargs))
-
- (defmethod (setf button-dialog) (new-dialog (item dialog-item))
- (check-type new-dialog (or null contact))
- (with-slots (preferred-width dialog) item
- ;; (LG) Force preferred-size to recalculate width.
- (setq preferred-width nil)
- (when (and dialog
- (not (eq dialog new-dialog)))
- (disassociate-dialog-from-button dialog item)))
- (associate-dialog-with-button new-dialog item))
-
- (defmethod resize :after ((item dialog-item) width height border-width)
- (declare (ignore width height border-width))
- (with-slots (active-x) item
- (setq active-x nil)))
-
- ;;;
- ;;; Other definitions.
-
- ;; A way to get from the dialog back to the button.
- (defmacro button-owning-dialog (contact)
- `(getf (window-plist ,contact) 'button-owning-dialog))
-
- (defun pointer-inside-menu-p (button menu)
- (declare (ignore button))
- (multiple-value-bind (pointer-x pointer-y same-screen-p)
- (pointer-position menu)
- (and same-screen-p (inside-contact-p menu pointer-x pointer-y))))
-
- ;; A handy place to put the state (nil, press-drag-release, or click-move-click).
- (defmacro menu-state (menu)
- `(getf (window-plist ,menu) 'menu-state))
-
- ;; Flag used to handle off-menu presses and releases.
- (defmacro menu-button-press-p (menu)
- `(getf (window-plist ,menu) 'menu-button-press-p))
-
- (defparameter *menu-item-drag-right-distance* 5
- "Distance in pixels to drag the pointer rightward over a menu item
- to bring up a submenu.")
-
- (defparameter *menu-cursor-index* top-left-arrow-cursor ; (That's 132.)
- "Index of glyph used for pointer cursor when grabbed by menu.")
-
- ;; Flag used to prevent multiple drag-mode submenus from appearing.
- (defmacro menu-present-in-progress (container)
- `(getf (window-plist ,container) 'present-dialog-in-progress))
-
- ;;;
- ;;; Initialisation code.
-
- ;; Allow for a class-name-symbol or list of class-name and initargs by
- ;; parsing the :dialog initarg and making it a contact before passing it
- ;; on to the rest of the init method.
- (defmethod initialize-instance :around ((self dialog-button) &rest initargs &key dialog parent)
- (let ((new-dialog (parse-dialog-spec dialog parent)))
- (apply #'call-next-method self :dialog new-dialog initargs)))
-
- (defmethod initialize-instance :around ((self dialog-item) &rest initargs &key dialog parent)
- (let ((new-dialog (parse-dialog-spec dialog parent)))
- (apply #'call-next-method self :dialog new-dialog initargs)))
-
- (defun parse-dialog-spec (spec parent)
- (etypecase spec
- ((or contact null) spec)
- ((or symbol function) (funcall spec :parent parent))
- (list (apply (car spec) :parent parent (cdr spec)))))
-
- (defmethod initialize-instance :after ((self dialog-button) &key &allow-other-keys)
- (associate-dialog-with-button (button-dialog self) self))
-
- (defmethod initialize-instance :after ((item dialog-item) &key &allow-other-keys)
- (associate-dialog-with-button (button-dialog item) item))
-
- (defmethod associate-dialog-with-button ((new-dialog t) button)
- (with-slots (dialog) (the dialog-button button)
- (setq dialog new-dialog)))
-
- (defmethod associate-dialog-with-button :after ((new-dialog menu) button)
- (associate-menu-with-dialog-button new-dialog button))
-
- ;; These dialogs use callback instead of event because the action is supposed to happen on release,
- ;; while pressing just highlights. The callback works, because that's what action-button
- ;; is doing.
- (defmethod associate-dialog-with-button :after ((new-dialog command) button)
- (add-callback button :release #'(lambda ()
- (present-dialog (button-dialog button)))))
-
- (defmethod associate-dialog-with-button :after ((new-dialog confirm) button)
- ;; A bit of a hack for confirm: We don't want the menu to dismiss until the
- ;; the confirm does, so if there's a dismiss-menu callback (indicating that
- ;; our owning button is within a menu), we remove it, extracting its menu
- ;; argument, and put it on the confirm's :accept and :cancel callbacks instead.
- (let* ((off-callbacks (callback-p button :off))
- (dismiss-callback (assoc #'dismiss-menu off-callbacks)))
- (when dismiss-callback
- (delete-callback button :off #'dismiss-menu) ; Move from here ...
- (when (typep button 'toggle-button)
- (delete-callback button :off #'dismiss-menu))
- (add-callback new-dialog :cancel #'dismiss-menu (second dismiss-callback)) ; ... to here.
- (add-callback new-dialog :accept #'dismiss-menu (second dismiss-callback))))
- (setf (button-owning-dialog new-dialog) button)
- (add-callback button :release #'(lambda ()
- (setf (confirm-near (button-dialog button))
- (viewable-ancestor button))
- (present-dialog (button-dialog button)))))
-
- (defmethod associate-dialog-with-button :after ((new-dialog property-sheet) button)
- (add-callback button :release #'(lambda ()
- (present-dialog (button-dialog button)))))
-
- (defun associate-menu-with-dialog-button (menu button)
- (declare (type menu menu)
- (type (or dialog-button dialog-item) button))
- ;; Make-menu handles associating dismiss-menu with :on and :off callbacks
- ;; on each item. This :unmap callback handles taking down submenus and
- ;; doing choice-item-release when the menu is withdrawn by dismiss-menu.
- (add-callback menu :unmap #'dismiss-menu-group menu button)
- ;; Remember owning button for later use in event-handlers.
- (setf (button-owning-dialog menu) button))
-
-
- (defmethod disassociate-dialog-from-button ((dialog menu) button)
- (disassociate-menu-from-dialog-button dialog button))
-
- (defmethod disassociate-dialog-from-button ((dialog command) button)
- (delete-callback button :release))
-
- (defmethod disassociate-dialog-from-button ((dialog confirm) button)
- (add-callback dialog :cancel #'dismiss-menu)
- (add-callback dialog :accept #'dismiss-menu)
- (setf (button-owning-dialog dialog) nil)
- (delete-callback button :release))
-
- (defmethod disassociate-dialog-from-button ((dialog property-sheet) button)
- (delete-callback button :release))
-
- (defmethod disassociate-dialog-from-button ((dialog null) button)
- (declare (ignore button))
- nil)
-
- (defun disassociate-menu-from-dialog-button (menu button)
- (declare (type menu menu)
- (ignore button))
- (setf (button-owning-dialog menu) nil)
- (delete-callback menu :unmap #'dismiss-menu-group))
-
- ;; Hook for an off-menu-press problem: when leaving an item, turn off the
- ;; off-menu-press flag so an off-menu-release won't dismiss the menu, because
- ;; the press was within an item, not off the menu. Also a hook for a confirm-
- ;; related grab problem: when firing an action-item, ungrab the pointer and
- ;; set the menu-state to a special state, finishing, that just ignores enter
- ;; and leave events on the menu. We need to do this for items whose callbacks
- ;; call confirm-p or some similar dialog-presenting function, so the dialog
- ;; gets a chance to get button presses and releases.
- (defmethod add-menu-item-callbacks :after (item menu)
- (add-callback item :canceling-change
- #'(lambda (to-selected-p)
- (declare (ignore to-selected-p))
- (setf (menu-button-press-p menu) nil)))
- (add-callback item :release #'(lambda ()
- (setf (menu-state menu) 'finishing)
- (ungrab-pointer (contact-display menu))
- )))
-
- (defun viewable-ancestor (contact)
- (let ((parent (typecase contact
- (menu
- (button-owning-dialog contact))
- (shell
- (shell-owner contact))
- (otherwise
- (contact-parent contact)))))
- (if (typep parent 'root)
- contact
- (let ((ancestor (viewable-ancestor parent)))
- (if (and (mapped-p contact)
- (eq ancestor parent))
- contact
- ancestor)))))
-
-
- ;;;
- ;;; Action functions for dialog-button and dialog-item.
-
- ;; Present-dialog methods for other dialogs are in their respective files.
- ;; This method starts the menu protocol defined below in the event handlers,
- ;; and sets position according to the complicated Open Look rules.
- (defmethod present-dialog ((menu menu) &key x y button state)
- (declare (type (or card16 null) x y))
- (declare (ignore x y)) ; Stick to Open Look positioning rules.
- (check-type button (or (member :button-1 :button-2 :button-3 :button-4 :button-5) null))
- (check-type state (or mask16 null))
- (let ((owning-button (button-owning-dialog menu)))
- (cond (owning-button
- (set-menu-position owning-button menu
- (and button state
- (not (logtest (make-state-mask button) state)))))
- (:else
- ;; No button, this is a pop-up menu.
- (set-menu-position nil menu
- (and button state
- (not (logtest (make-state-mask button) state))))
- (associate-menu-with-dialog-button menu nil)
- ;; Need this to do the initial grab-handoff to the menu so we can
- ;; start popups in press-drag-release -- a quick enough button-release
- ;; will switch to click-move-click, but I'm not sure of the mechanism.
- ;; Need to do it as a callback because we can't grab until we're mapped,
- ;; and that doesn't happen immediately.
- (add-callback menu :map
- #'(lambda ()
- (ungrab-pointer (contact-display menu))
- (grab-pointer menu #.(make-event-mask :button-release :enter-window :leave-window)
- :owner-p t
- :cursor (contact-glyph-cursor menu *menu-cursor-index*))))))
- (setf (contact-state menu) :mapped)
- (setf (menu-state menu) nil)))
-
- ;; Default case, just position it and map it (this method handles commands and
- ;; property-sheets, but not confirms or menus).
- (defmethod present-dialog ((contact contact) &key x y button state)
- (declare (type (or (member :button-1 :button-2 :button-3 :button-4 :button-5) null) button)
- (type (or mask16 null) state))
- (declare (ignore button state))
- (check-type x (or card16 null))
- (check-type y (or card16 null))
- (unless (or x y)
- (multiple-value-setq (x y)
- (pointer-position (contact-parent contact))))
- (change-geometry contact :x x :y y :accept-p t)
- (setf (contact-state contact) :mapped))
-
-
- ;; This function is called in the :unmap callback of a menu, which dismiss-menu
- ;; causes to happen by withdrawing the menu. Other cleanup, like taking down any
- ;; submenus and releasing the button or item, happens here.
- (defun dismiss-menu-group (menu button)
- ;; If there are any submenus up, take them down, too.
- (mapc #'dismiss-submenu-item
- (composite-children (menu-choice menu)))
-
- (when button
- (setf (menu-present-in-progress (contact-parent button)) nil))
-
- (when (and button ; Button will be NIL for pop-up.
- ;; Special state, only during leave-notify of menu when exiting
- ;; to left, which means take down the menu but not any superiors.
- (not (eq (menu-state menu) 'exiting-to-left)))
- ;; NOTE we defer the "release" of the button until the associated
- ;; menu is dismissed. We do this because the menu button will
- ;; normally never see the actual release event. Note also that,
- ;; as an action button, the :ON callback is not invoked until the
- ;; release method is invoked.
- (choice-item-release button)
- (release-select button)))
-
- (defmethod dismiss-submenu-item (item)
- (declare (ignore item))
- nil)
-
- (defmethod dismiss-submenu-item ((item dialog-item))
- (with-slots (dialog) item
- (when (and (typep dialog 'menu)
- (mapped-p dialog))
- (dismiss-menu dialog))))
-
- ;; Used to dismiss any dialogs active under a given menu, when bringing
- ;; up a different dialog from that menu.
- (defmethod dismiss-active-dialogs (item)
- (declare (ignore item))
- nil)
-
- (defmethod dismiss-active-dialogs ((item dialog-item))
- (with-slots (dialog) item
- (when (mapped-p dialog)
- ;; This flag, originally used when taking down drag-mode menus by
- ;; exiting to the left, here is used to prevent superior menus of this
- ;; one from being taken down.
- (when (typep dialog 'menu)
- (setf (menu-state dialog) 'exiting-to-left))
- (setf (contact-state dialog) :withdrawn)
- (display-button-unhighlighted item))))
-
-
- ;; Special methods for dialog-button because we need to display the default
- ;; on press and select it on release. The special stuff will only be called
- ;; when the dialog is a menu, the others will just call the next method and
- ;; get the action-button normal stuff. I'd like to do this in an :after
- ;; method or some other cleaner way, but I need to do this all inside the
- ;; conditional, and I'm not sure how to tell whether it was true.
- (DEFMETHOD press-select ((dialog-button dialog-button))
- (with-slots (dialog) dialog-button
- (if (typep dialog 'menu)
- (press-select-show-default dialog-button dialog)
- (call-next-method))))
-
- (DEFMETHOD press-select ((dialog-item dialog-item))
- (with-slots (dialog) dialog-item
- (if (typep dialog 'menu)
- (press-select-show-default dialog-item dialog)
- (call-next-method))))
-
- (defun press-select-show-default (dialog-button dialog)
- (declare (type action-button dialog-button)) ; Both dialog-item and dialog-button are.
- (with-event (x y)
- (WHEN (and (inside-contact-p dialog-button x y)
- (choice-item-press dialog-button))
- ;; Show the default value in the button.
- (with-slots (font label fill-color foreground last-displayed-as width height) dialog-button
- (LET* ((scale (contact-scale dialog-button))
- (choice (menu-choice dialog))
- (default (or (choice-default choice) ; Could be NIL, but Open Look insists.
- (first (composite-children choice))))
- (ab-foreground foreground)
- (ab-fill-color fill-color)
- (ab-font font)
- (dims (getf *button-dimensions-by-scale* scale))
- (text-x (ab-left-button-end-width dims))
- (text-y (1+ (ab-text-baseline dims)))) ; 0+ for dialog-item.
-
- ;; Experiment: try changing the label and redisplaying. Problems: doesn't
- ;; suppress the menu mark, doesn't show the more-text-to-right gray arrow.
- ; (with-slots (label) dialog-button
- ; (let ((old-label label))
- ; (unwind-protect
- ; (progn (setq label (button-label default))
- ; (redisplay-button dialog-button))
- ; (setq label old-label))))
-
- ;; Avoid error on abbreviated buttons -- interior width ends up negative.
- ;; This way, we just highlight and don't even try to show the default.
- (unless (< width (+ (ab-left-button-end-width dims)
- (ab-right-button-end-width dims)))
-
- (using-gcontext
- (gc
- :drawable dialog-button
- :foreground ab-foreground
- :background ab-fill-color
- :font ab-font)
- (just-clear-body-of-button dialog-button gc))
-
- (using-gcontext
- (gc
- :drawable dialog-button
- :foreground ab-fill-color
- :background ab-foreground
- :font ab-font)
-
- (let ((default-label (button-label default)))
- (if (stringp default-label)
- (display-constrained-text
- dialog-button gc default-label ab-font
- (label-width dialog-button label)
- :x text-x :y text-y)
-
- (let*
- ((label-width (label-width dialog-button default-label))
- (label-height (getf (pixmap-plist default-label) :height)))
- (with-gcontext (gc :fill-style :tiled :tile default-label)
- (draw-rectangle
- dialog-button gc
- text-x (max 0 (pixel-round (- height label-height) 2))
- label-width label-height t))))))))))))
-
- (DEFUN display-constrained-text (contact gc text font available-width &key (x 0) (y 0))
- (LET* ((more-arrow (GETF *more-text-arrows-by-scale* (contact-scale contact)))
- (more-arrow-image (more-text-arrow-image more-arrow))
- (more-arrow-width (image-width more-arrow-image))
- )
- (FLET
- ((get-displayable-width-of-text (text font available-width)
- ;; Returns (<#-of-chars-in-text>) if entire text fits.
- ;; Returns (<#-of-displayable-chars> <npixels-displayable>) if not.
- (IF (<= (text-width font text) available-width)
- (LENGTH text)
- ;; else we gotta figure out how many chars will fit.
- ;; Since text-width is a very expensive function we're going to try to get an estimate
- ;; for where in the text we get too wide to fit before we start calling it.
-
- (DO* ((reduced-space-for-text (- available-width more-arrow-width))
- (est-displayable-length (FLOOR reduced-space-for-text (max-char-width font)))
- (i (1+ est-displayable-length) (1+ i))
- (test-width)
- (last-test-width (text-width font text :end est-displayable-length) test-width)
- )
- ((>= i (LENGTH text)))
- (SETF test-width (text-width font text :end i))
- (WHEN (> test-width reduced-space-for-text)
- (RETURN (VALUES (1- i) last-test-width))))))
- )
-
- ;; Get the # of characters that fit (and their width if truncating)...
- (MULTIPLE-VALUE-BIND (displayable-length-of-text displayable-width-of-text)
- (get-displayable-width-of-text text font available-width)
-
- ;; Draw the characters that we can...
- (draw-glyphs contact gc x y text :end displayable-length-of-text)
-
- ;; If the entire label would not fit, place a More Text Arrow to the right of it...
- ;; We assume here that the pixmap for this scale's More Text Arrow has already been
- ;; cached so contact-mask can pick it up...
- (WHEN displayable-width-of-text
- (LET* ((more-arrow-x (+ x displayable-width-of-text
- (more-text-arrow-offset-from-text more-arrow)))
- (more-arrow-y (+ y (more-text-arrow-offset-from-baseline more-arrow)))
- (more-arrow-pixmap (contact-image-mask contact more-arrow-image :depth 1)))
- (with-gcontext (gc :clip-x more-arrow-x
- :clip-y more-arrow-y
- :clip-mask more-arrow-pixmap)
- (draw-rectangle contact gc more-arrow-x more-arrow-y
- more-arrow-width (image-height more-arrow-image) t))))))))
-
- (DEFMETHOD release-select ((dialog-button dialog-button))
- (with-slots (dialog) dialog-button
- (if (typep dialog 'menu)
- (release-select-choose-default dialog-button dialog)
- (call-next-method))))
-
- (DEFMETHOD release-select ((dialog-item dialog-item))
- (with-slots (dialog) dialog-item
- (if (typep dialog 'menu)
- (release-select-choose-default dialog-item dialog)
- (call-next-method))))
-
- (defun release-select-choose-default (dialog-button dialog)
- (with-slots (last-displayed-as) (the dialog-button dialog-button)
- ;; Do nothing unless highlighted/selected already...
- (WHEN (EQ last-displayed-as :highlighted)
- (let ((ultimate-default (find-ultimate-default (menu-choice dialog))))
- (choice-item-press ultimate-default)
- (choice-item-release ultimate-default)
- (choice-item-release dialog-button)))))
-
- (defun find-ultimate-default (choice)
- (let ((default (or (choice-default choice) ; Could be NIL, but Open Look insists on a default.
- (first (composite-children choice)))))
- (typecase default
- ((or dialog-button dialog-item)
- (let ((dialog (button-dialog default)))
- (if (typep dialog 'menu)
- (find-ultimate-default (menu-choice dialog))
- default)))
- (otherwise
- default))))
-
- ;;;
- ;;; Event translations for dialog-button/item and menus.
- ;;;
- ;;; These implement a sort of state machine. The components of the current state
- ;;; are the state of dialog (:mapped or not), the type of the dialog (menus behave
- ;;; differently than other dialogs), and the menu-state of the menu (nil,
- ;;; press-drag-release, or click-move-click). Mostly they use the type to decide
- ;;; their sensitivity to the event, the state of the dialog to determine whether
- ;;; this is the first time for this event (for example, startup should only happen
- ;;; once), and the menu-state to differentiate between modes for grabbing purposes.
- ;;;
- ;;; Dialog button translations.
-
- (defevent dialog-button
- (:button-press :button-3)
- dialog-button-do-startup)
-
- (defun dialog-button-do-startup (button)
- (let ((dialog (button-dialog button)))
- (when (and (typep dialog 'menu)
- (not (mapped-p dialog))
- (choice-item-press button))
- ;; Present-dialog on menu sets menu-state to nil.
- (present-dialog dialog :button :button-3 :state (with-event (state) state)))))
-
-
- (defevent dialog-button
- (:button-release :button-3)
- dialog-button-button-3-release)
-
- (defun dialog-button-button-3-release (button)
- (let ((dialog (button-dialog button)))
- (when (and (typep dialog 'menu)
- (mapped-p dialog)
- (null (menu-state dialog)))
- ;; Menu just brought up by preceding press, go into click-move-click mode.
- (display-action-button-busy button)
- (grab-pointer dialog #.(make-event-mask :button-press :button-release :enter-window)
- :owner-p t
- :cursor (contact-glyph-cursor dialog *menu-cursor-index*))
- (setf (menu-state dialog) 'click-move-click))))
-
-
- (defevent dialog-button
- :leave-notify
- dialog-button-leave-notify)
-
- (defun dialog-button-leave-notify (button)
- (declare (type dialog-button button))
- (let ((dialog (button-dialog button)))
- (if (and (typep dialog 'menu)
- (mapped-p dialog)
- (null (menu-state dialog)))
- (with-event (time mode kind root-x root-y)
- (when (eq mode :normal)
- ;; We ungrab the pointer independent of its current location since
- ;; the menu will be responsible for fielding any release event.
- (ungrab-pointer (contact-display button) :time time)
-
- (multiple-value-bind (dialog-x dialog-y)
- (contact-translate (contact-root button) root-x root-y dialog)
- (if (inside-contact-p dialog dialog-x dialog-y) ; Avoid server round-trip.
- (grab-pointer dialog #.(make-event-mask :button-release :enter-window :leave-window)
- :owner-p t
- :cursor (contact-glyph-cursor dialog *menu-cursor-index*))
- (grab-pointer dialog #.(make-event-mask :button-release :enter-window)
- :cursor (contact-glyph-cursor dialog *menu-cursor-index*))))
- (setf (menu-state dialog) 'press-drag-release)))
-
- (with-slots (last-displayed-as) button
- ;; Do nothing unless highlighted/selected already...
- (WHEN (EQ last-displayed-as :highlighted)
- (leave button))))))
-
-
- ;;;
- ;;; Menu translations.
-
- (defevent menu
- :button-press
- dialog-button-button-press)
-
- (defun dialog-button-button-press (menu)
- (setf (menu-button-press-p menu) t))
-
-
- (defevent menu
- :button-release
- dialog-button-dismiss-menu-group)
-
- (defun dialog-button-dismiss-menu-group (menu)
- (cond ((null (menu-state menu))
- (setf (menu-state menu) 'click-move-click))
- ((or (menu-button-press-p menu)
- (eq (menu-state menu) 'press-drag-release))
- (dismiss-menu menu)))
- (setf (menu-button-press-p menu) nil))
-
-
- (defevent menu
- :enter-notify
- dialog-button-menu-enter-notify)
-
- (defun dialog-button-menu-enter-notify (menu)
- (with-event (time mode state)
- (flet ((pdr-enter ()
- ;; First we ungrab the pointer so choice items will get proper
- ;; event notifications
- (ungrab-pointer (contact-display menu) :time time)
- (grab-pointer menu #.(make-event-mask :button-release :enter-window :leave-window)
- :owner-p t
- :cursor (contact-glyph-cursor menu *menu-cursor-index*)))
- (cmc-enter ()
- (ungrab-pointer (contact-display menu) :time time)
- (grab-pointer menu #.(make-event-mask :button-press :button-release)
- :owner-p t
- :cursor (contact-glyph-cursor menu *menu-cursor-index*))))
- (ecase (menu-state menu)
- ((nil)
- ;; Pop-up menu, a la SCIFI. Choose mode based on button state.
- ;; The test below will be T if button-3 is down, meaning we've entered
- ;; the menu with the button pressed, hence press-drag-release mode. If
- ;; the button is up, we go to click-move-click.
- (cond ((logtest #.(make-state-mask :button-3) state)
- (setf (menu-state menu) 'press-drag-release)
- (pdr-enter))
- (:else
- (setf (menu-state menu) 'click-move-click)
- (cmc-enter))))
- (press-drag-release
- (when (eq mode :normal)
- (pdr-enter)))
- (click-move-click
- (when (eq mode :normal)
- (cmc-enter)))
- (finishing
- ; (when (eq mode :normal)
- ; (setf (menu-state menu) 'click-move-click)
- ; (cmc-enter))
- )
- (exiting-to-left
- ;; May happen if we leave a dialog-item before the menu's up
- ;; and have to take it down again.
- nil)))))
-
-
- (defevent menu
- :leave-notify
- dialog-button-menu-leave-notify)
-
- (defun dialog-button-menu-leave-notify (menu)
- (with-event (time mode x y)
- (when (eq mode :normal)
- (ecase (menu-state menu)
- (press-drag-release
- (ungrab-pointer (contact-display menu) :time time)
- (let ((button (button-owning-dialog menu)))
- (cond ((and (typep button 'dialog-item)
- (< x 0)) ; A crude leave-left-edge test for items.
- (setf (menu-state menu) 'exiting-to-left) ; Flag for dismiss-menu-group.
- (setf (contact-state menu) :withdrawn)
- ;; +++ I want to do choice-item-leave if the new position isn't within the button.
- ;; The event coordinates are relative to the menu, though, so how exactly do
- ;; I translate them? In the meantime, it seems to be better to leave always.
- (choice-item-leave button)
- )
- (:else
- (grab-pointer menu #.(make-event-mask :button-release :enter-window)
- :cursor (contact-glyph-cursor menu *menu-cursor-index*))))))
- (click-move-click
- (ungrab-pointer (contact-display menu) :time time)
- (grab-pointer menu #.(make-event-mask :button-press :button-release :enter-window)
- :cursor (contact-glyph-cursor menu *menu-cursor-index*)))
- (exiting-to-left
- ;; Need this because there'll be another leave-notify during the unmapping.
- nil)
- (finishing
- nil)))))
-
-
- ;;;
- ;;; Dialog item translations.
-
- (defevent dialog-item
- (:button-press :button-3)
- choice-item-press)
-
-
- (defevent dialog-item
- (:button-release :button-3)
- dialog-item-start-cmc-mode)
-
- (defun dialog-item-start-cmc-mode (item)
- (let ((dialog (button-dialog item)))
- (when (not (mapped-p dialog))
- (cond ((typep dialog 'menu)
- ;; If there are any dialogs up at this level, take them down.
- (mapc #'dismiss-active-dialogs
- (composite-children (contact-parent item)))
- ;; Dialog-item, superior menu in stay-up mode, we fire on the release
- ;; and bring up the submenu in stay-up mode.
- (present-dialog dialog :button :button-3 :state 0)
- ;; This is dialog-button-button-3-release without the grab-pointer.
- (display-action-button-busy item)
- (setf (menu-state dialog) 'click-move-click))
- (:else
- (choice-item-release item))))))
-
-
- (defevent dialog-item
- :leave-notify
- leave-dialog-item)
-
- (defun leave-dialog-item (item)
- (declare (type dialog-item item))
- (with-event (state mode)
- (cond ((and (logtest #.(make-state-mask :button-3) state)
- (not (mapped-p (button-dialog item))))
- ;; We set last-x to the right-hand end of the item to force recalculation
- ;; when we re-enter.
- (with-slots (last-x width) (the dialog-item item)
- (setq last-x width))
- ;; We ungrab the pointer independent of its current location since
- ;; the menu will be responsible for fielding any release event.
- (with-event (time mode)
- (with-slots (last-displayed-as) item
- (when (and (eq mode :normal)
- (eq last-displayed-as :highlighted))
- (ungrab-pointer (contact-display item) :time time)
- (choice-item-leave item)))))
- (:else
- (with-slots (last-displayed-as) item
- ;; Do nothing unless highlighted/selected already...
- (when (eq last-displayed-as :highlighted)
- (leave item)))))))
-
-
- (defevent dialog-item
- :enter-notify
- dialog-item-enter-notify)
-
- (defmethod dialog-item-enter-notify ((item dialog-item))
- (with-slots (dialog last-x active-x width last-displayed-as) item
- (when (and (not (mapped-p dialog)) (eq last-displayed-as :unhighlighted))
- (with-event (x y state)
- (if (and (inside-contact-p item x y) ; +++ Inactive items don't get enter-notify, remove this?
- (logtest #.(make-state-mask :button-3) state)
- (or (not (typep dialog 'menu))
- (not (menu-present-in-progress (contact-parent item)))) ; Don't allow multiple PDR menus.
- ;; The pointer has been dragged over this button w/menu button
- ;; pressed. This has the same side effects as pressing the
- ;; select button so we go ahead and use the press procedure
- ;; to take care of visuals and approve the transition.
- (choice-item-press item))
- ;; Transition was approved and button is now highlighted.
- ;; The choice-item-press is enough for non-menus, but menus have more:
- (when (typep dialog 'menu)
- (when (null active-x)
- (let ((dims (getf *button-dimensions-by-scale* (contact-scale item))))
- (setq active-x (- width
- (ab-right-button-end-width dims)
- (image-width (ab-horizontal-menu-mark-image dims))))))
- (setq last-x x)
- (when (>= x active-x)
- ;; Entered in the "submenu region," which is that area from the
- ;; left edge of the menu mark to the right edge of the item.
- ;; If there are any dialogs up at this level, take them down.
- (mapc #'dismiss-active-dialogs
- (composite-children (contact-parent item)))
- ;; Bring up the menu and go into the submenu protocol.
- (present-dialog dialog :button :button-3 :state state)
- (setf (menu-present-in-progress (contact-parent item)) t)
- ; (setf (menu-state dialog) 'press-drag-release)
- ))
- ;; Transition not approved, so inhibit the drag-right check on :motion-notify.
- (when (typep dialog 'menu)
- (setq last-x width)))))))
-
-
- (defevent dialog-item
- :motion-notify
- dialog-item-drag-right)
-
- (defmethod dialog-item-drag-right ((item dialog-item))
- (with-slots (dialog last-x active-x width) item
- (when (and (typep dialog 'menu)
- (not (mapped-p dialog))
- (not (menu-present-in-progress (contact-parent item)))
- active-x) ; Paranoia check.
- (with-event (x y state)
- (when (and (inside-contact-p item x y)
- (logtest #.(make-state-mask :button-3) state))
- (cond ((or (>= x active-x)
- (> (- x last-x) *menu-item-drag-right-distance*))
- ;; If there are any dialogs up at this level, take them down.
- (mapc #'dismiss-active-dialogs
- (composite-children (contact-parent item)))
- ;; Dragged right far enough, or into active area, bring up menu.
- (present-dialog dialog :button :button-3 :state state)
- (setq last-x width) ; Force recalculation on later entries.
- (setf (menu-state dialog) 'press-drag-release))
- ((< x last-x) ; Moving left, save leftmost.
- (setq last-x x))
- (:else ; Moving right, keep old left.
- nil)))))))
-
-
- ;;;
- ;;; Display code. Dialog-buttons and dialog-items show a menu mark or
- ;;; window mark to the right of the item. These functions and methods
- ;;; allow space for it and do the drawing.
-
- (defvar *inside-display-window-mark* nil) ; Don't do it inside internal routine.
-
- ;; Daemons on the Dialog Button's label manipulation methods to adjust the width
- ;; of the label for the menu mark and the display the menu mark.
- (defmethod label-width :around ((button dialog-button) label)
- (if *inside-display-window-mark*
- (call-next-method)
- (with-slots (dialog) button
- (let ((dims (getf *button-dimensions-by-scale* (contact-scale button))))
- (+ (call-next-method)
- (additional-label-width dialog button dims)
- (- (ab-right-button-end-width dims)
- 2)))))) ; Right border thickness
-
- (defmethod label-width :around ((button dialog-item) label)
- (if *inside-display-window-mark*
- (call-next-method)
- (with-slots (dialog) button
- (let ((dims (getf *button-dimensions-by-scale* (contact-scale button))))
- (+ (call-next-method)
- (additional-label-width dialog button dims)
- (- (ab-right-button-end-width dims)
- 2)))))) ; Right border thickness
-
-
- (defmethod additional-label-width ((dialog null) button dims)
- (declare (ignore button dims))
- 0)
-
- (defmethod additional-label-width ((dialog menu) (button dialog-button) dims)
- (image-width (ab-vertical-menu-mark-image dims)))
-
- (defmethod additional-label-width ((dialog menu) (button dialog-item) dims)
- (image-width (ab-horizontal-menu-mark-image dims)))
-
- (defmethod additional-label-width ((dialog command) button dims)
- (declare (ignore dims))
- (text-extents (button-font button) "..."))
-
- (defmethod additional-label-width ((dialog confirm) button dims)
- (declare (ignore dims))
- (text-extents (button-font button) "..."))
-
- (defmethod additional-label-width ((dialog property-sheet) button dims)
- (declare (ignore dims))
- (text-extents (button-font button) "..."))
-
-
- (DEFMETHOD display-button-label :after ((button dialog-button) gc)
- ;; Now draw in the menu-mark at the right end of the button, just to the left of the
- ;; right-button-end (which leaves right-margin pixels to the right of the mark)
- (with-slots (dialog) button
- (after-display-button-label dialog button gc)))
-
- (DEFMETHOD display-button-label :after ((item dialog-item) gc)
- ;; Now draw in the menu-mark at the right end of the button, just to the left of the
- ;; right-button-end (which leaves right-margin pixels to the right of the mark)
- (with-slots (dialog) item
- (after-display-button-label dialog item gc)))
-
- (defmethod after-display-button-label ((dialog null) button gc)
- (declare (ignore button gc))
- nil)
-
- (defmethod after-display-button-label ((dialog menu) (button dialog-button) gc)
- (display-menu-mark button gc :below))
-
- (defmethod after-display-button-label ((dialog menu) (item dialog-item) gc)
- (display-menu-mark item gc :to-the-right))
-
- (defun display-menu-mark (button gc direction)
- (let ((width (contact-width button)))
- (LET* ((scale (contact-scale button))
- (dims (getf *button-dimensions-by-scale* scale))
- (button-pixmaps (get-button-pixmaps button))
- (menu-mark-image (ecase direction
- (:to-the-right
- (ab-horizontal-menu-mark-image dims))
- (:below
- (ab-vertical-menu-mark-image dims))))
- (menu-mark-pixmap (ecase direction
- (:to-the-right
- (horizontal-menu-mark-pixmap button-pixmaps))
- (:below
- (vertical-menu-mark-pixmap button-pixmaps))))
- (menu-mark-x (- width
- (ecase direction
- (:below
- (ab-right-button-end-width dims))
- (:to-the-right
- (ai-button-end-width dims)))
- (image-width menu-mark-image)))
- (menu-mark-y (- (ecase direction
- (:below (ab-text-baseline dims))
- (:to-the-right (1- (ai-text-baseline dims))))
- (image-height menu-mark-image)
- ;; The 1- for :to-the-right is correction to this.
- (ab-menu-mark-bottom-rel-to-baseline dims))))
- (with-gcontext (gc :clip-x menu-mark-x
- :clip-y menu-mark-y
- :clip-mask menu-mark-pixmap)
- (draw-rectangle button gc
- menu-mark-x menu-mark-y
- (image-width menu-mark-image) (image-height menu-mark-image)
- t)))))
-
-
- (defmethod after-display-button-label ((dialog command) button gc)
- (display-window-mark button gc))
-
- (defmethod after-display-button-label ((dialog property-sheet) button gc)
- (display-window-mark button gc))
-
- (defmethod after-display-button-label ((dialog confirm) button gc)
- (display-window-mark button gc))
-
- ;; Draw the window mark flush against the right end of the label, using
- ;; similar computations to those from display-button-label.
- (defmethod display-window-mark ((button dialog-button) gc)
- (with-slots (font label-alignment label width) button
- (let* ((scale (contact-scale button))
- (dims (GETF *button-dimensions-by-scale* scale))
- (label-width (let ((*inside-display-window-mark* t))
- (label-width button label)))
- (margin (ab-left-button-end-width dims))
- (left-margin (max margin
- (case label-alignment
- (:left 0)
- (:center (pixel-round (- width label-width) 2))
- (:right (- width margin label-width)))))
- (window-mark-x (+ left-margin label-width 1)) ; Extra pixel looks better.
- (window-mark-y (1+ (ab-text-baseline dims))))
- (with-gcontext (gc :font font)
- (draw-glyphs button gc window-mark-x window-mark-y "...")))))
-
- (defmethod display-window-mark ((item dialog-item) gc)
- (with-slots (font label-alignment label width) item
- (let* ((scale (contact-scale item))
- (dims (GETF *button-dimensions-by-scale* scale))
- (label-width (let ((*inside-display-window-mark* t))
- (label-width item label)))
- (margin (ai-button-end-width dims))
- (left-margin (max margin
- (case label-alignment
- (:left 0)
- (:center (pixel-round (- width label-width) 2))
- (:right (- width margin label-width)))))
- (window-mark-x (+ left-margin label-width 1)) ; Extra pixel looks better.
- (window-mark-y (ai-text-baseline dims)))
- (with-gcontext (gc :font font)
- (draw-glyphs item gc window-mark-x window-mark-y "...")))))
-
- ;;;
- ;;; Position the menu according to the Open Look rules:
- ;;; For a button, centered horizontally with the top edge against the bottom
- ;;; edge of the button. For an item, with the default item centered vertically
- ;;; relative to the item itself. In press-drag-release mode (release-p NIL),
- ;;; positioned horizontally so the left end of the default item is over the
- ;;; mouse; in click-move-click mode (release-p T), positioned horizontally so
- ;;; the left edge of the menu is a pixel away from right edge of the item.
- ;;;
- ;;; For pop-ups (not yet implemented), the button will be NIL. In that case,
- ;;; we align the default item vertically with the mouse, and place the menu so
- ;;; that the mouse is a pixel or two to the left of the left edge of the default.
-
- (DEFMETHOD set-menu-position ((self dialog-button) menu &optional release-p)
- (declare (ignore release-p))
- (with-slots (width height x y border-width parent) self
- (unless (realized-p menu)
- (initialize-geometry menu))
-
- (let ((menu-width (contact-width (contact-parent (menu-choice menu)))))
- ;; We use the width of the *container* so menu will be
- ;; centered without considering the drop shadow.
- (multiple-value-bind (menu-x menu-y)
- (contact-translate
- (contact-parent self)
- (- (+ x (round width 2)) (round menu-width 2))
- (+ y border-width border-width height 1)
- (contact-parent menu))
- (SETF menu-x (MIN (MAX 0 menu-x)
- (- (contact-width (contact-parent menu)) menu-width)))
- (change-geometry menu
- :x menu-x
- :y menu-y
- :accept-p t)))))
-
- ;; For a dialog-item, the menu comes up to the right, with the default item aligned with
- ;; the item, center to center. In pdr mode, the X coordinate is such that the left end of the
- ;; default item is under the pointer; in cmc mode, the left edge of the menu is one pixel
- ;; to the right of the item.
- (DEFMETHOD set-menu-position ((self dialog-item) menu &optional release-p)
- (initialize-geometry menu) ; Needed to get correct sizes for Y coord.
- (with-slots (width height x y border-width parent) self
- (let* ((choice (menu-choice menu))
- (default (or (choice-default choice) ; Could be NIL, but Open Look insists on a default.
- (first (composite-children choice))))
- (default-scale (contact-scale default))
- (dims (GETF *button-dimensions-by-scale* default-scale))
- (container (contact-parent choice))
- (menu-width (contact-width container))
- (menu-height (contact-height container)))
- ;; We use the width of the *container* so menu will be
- ;; centered without considering the drop shadow.
- (multiple-value-bind (default-x default-y)
- ;; Translate default-item position into offset from menu 0,0.
- (contact-translate (contact-parent default)
- (contact-x default)
- (contact-y default)
- menu)
- (multiple-value-bind (menu-x menu-y)
- (contact-translate (contact-parent self)
- (if release-p
- (+ x width border-width border-width 2) ; I think that's 1 + 1 for default-ring.
- (- (+ (pointer-position self) ; Should be the pointer X.
- x)
- (ab-left-button-end-width dims)
- default-x))
- (- (+ y (round height 2)) ; Align the centers in Y.
- (+ default-y (round (contact-height default) 2)))
- (contact-parent menu))
- (setq menu-x (MIN (MAX 0 menu-x)
- (- (contact-width (contact-parent menu)) menu-width))
- menu-y (MIN (MAX 0 menu-y)
- (- (contact-height (contact-parent menu)) menu-height)))
- (change-geometry menu
- :x menu-x
- :y menu-y
- :accept-p t))))))
-
- ;; For a pop-up menu, there is no item. Bring it up under the mouse, with the default
- ;; item centered vertically and its left edge a couple of pixels to the right of the mouse.
- (defmethod set-menu-position ((self null) menu &optional release-p)
- (declare (ignore release-p))
- (initialize-geometry menu) ; Needed to get correct sizes for Y coord.
- (let* ((choice (menu-choice menu))
- (default (or (choice-default choice) ; Could be NIL, but Open Look insists on a default.
- (first (composite-children choice))))
- (container (contact-parent choice))
- (menu-width (contact-width container))
- (menu-height (contact-height container)))
- ;; We use the width of the *container* so menu will be
- ;; centered without considering the drop shadow.
- (multiple-value-bind (pointer-x pointer-y)
- (pointer-position (contact-parent menu))
- (multiple-value-bind (default-x default-y)
- ;; Translate default-item position into offset from menu 0,0.
- (contact-translate (contact-parent default)
- (contact-x default)
- (contact-y default)
- menu)
- (let ((menu-x (- pointer-x (- default-x 2)))
- (menu-y (- pointer-y default-y (round (contact-height default) 2))))
- (setq menu-x (MIN (MAX 0 menu-x)
- (- (contact-width (contact-parent menu)) menu-width))
- menu-y (MIN (MAX 0 menu-y)
- (- (contact-height (contact-parent menu)) menu-height)))
- (change-geometry menu
- :x menu-x
- :y menu-y
- :accept-p t))))))
-
-