;;; -*- Mode: Lisp -*- ;;; CLIM text style selection dialog. Call with: ;;; ;;; (clim-user::select-text-style) ;;; ;;; It returns a CLIM text style ;;; Paolo Amoroso - July 24, 2005 ;;; No dedicated package because it's a hack. However, it might one day be part ;;; of a collection of portable CLIM utilities... (in-package :clim-user) ;;; Utilities (defun familyp (family) (first (member family '(:fix :serif :sans-serif)))) (defun facep (face) (first (member face '(:roman :bold :italic (:bold :italic)) :test #'equal))) (defun sizep (size) (or (first (member size '(:tiny :very-small :small :normal :large :very-large :huge :larger :smaller))) (and (numberp size) size))) (defun style-component-to-style-spec (component) "Return a text style specifier from one of its COMPONENTs. E.g. if COMPONENT is `:roman', returns (nil :roman nil)." (list (familyp component) (facep component) (sizep component))) (defun dismiss-dialog-callback (gadget) "Set frame's `dismiss-value' slot and dismiss dialog. Assumes that the application frame has a `dismiss-value' slot and that the push button has a meaningful ID, i.e. t for OK, nil for Cancel, and possibly other application-specific values." (setf (dismiss-value *application-frame*) (gadget-id gadget)) (frame-exit *application-frame*)) ;;; Callbacks ;;; Value changed and activate callbacks have different lambda lists: ;;; ;;; activate : ;;; value changed : ;;; ;;; We use a single function for both callbacks by ignoring the argument ;;; when not needed, and selecting the appropriate gadget ID based on the kind ;;; of gadget (radio box or push button) (defun style-change-callback (gadget &optional (value nil providedp)) (setf (sample-style *application-frame*) (merge-text-styles (style-component-to-style-spec (if providedp (gadget-id value) (gadget-id gadget))) (sample-style *application-frame*))) (redisplay-frame-pane *application-frame* (get-frame-pane *application-frame* 'sample) :force-p t)) ;;; FIXME: should handle real numbers for text style point sizes, not just ;;; integers. See `make-text-style' in the CLIM 2 specification (defun points-callback (gadget) (let ((new-value (parse-integer (gadget-value gadget) :junk-allowed t))) (when (numberp new-value) (setf (sample-style *application-frame*) (merge-text-styles (style-component-to-style-spec new-value) (sample-style *application-frame*))) (redisplay-frame-pane *application-frame* (get-frame-pane *application-frame* 'sample) :force-p t)))) ;;; Application frame (define-application-frame text-style-selection () ((dismiss-value :initarg :dismiss-value :accessor dismiss-value :initform nil) (sample-style :initarg :sample-style :accessor sample-style :initform (make-text-style :fix :roman :normal))) (:menu-bar nil) (:panes (family (with-radio-box (:orientation :vertical :value-changed-callback 'style-change-callback) (radio-box-current-selection (make-pane 'toggle-button :id :fix :label "Fix")) (make-pane 'toggle-button :id :serif :label "Serif") (make-pane 'toggle-button :id :sans-serif :label "Sans Serif"))) (face (with-radio-box (:orientation :vertical :value-changed-callback 'style-change-callback) (radio-box-current-selection (make-pane 'toggle-button :id :roman :label "Roman")) (make-pane 'toggle-button :id :bold :label "Bold") (make-pane 'toggle-button :id :italic :label "Italic") (make-pane 'toggle-button :id '(:bold :italic) :label "Bold Italic"))) (size (with-radio-box (:orientation :vertical :value-changed-callback 'style-change-callback) (make-pane 'toggle-button :id :tiny :label "Tiny") (make-pane 'toggle-button :id :very-small :label "Very small") (make-pane 'toggle-button :id :small :label "Small") (radio-box-current-selection (make-pane 'toggle-button :id :normal :label "Normal")) (make-pane 'toggle-button :id :large :label "Large") (make-pane 'toggle-button :id :very-large :label "Very large") (make-pane 'toggle-button :id :huge :label "Huge"))) (smaller :push-button :id :smaller :label "Smaller" :activate-callback 'style-change-callback) (larger :push-button :id :larger :label "Larger" :activate-callback 'style-change-callback) ;; Incremental redisplay would be possible, but probably overkill (sample :application :display-function 'display-sample-text :display-time nil :text-style (sample-style *application-frame*) :width 400 :height 100 :scroll-bars t :end-of-line-action :wrap) (points :text-field :value "10" :activate-callback 'points-callback) (ok-button :push-button :id t :label " OK " :activate-callback 'dismiss-dialog-callback) (cancel-button :push-button :id nil :label "Cancel" :activate-callback 'dismiss-dialog-callback)) (:layouts (default (labelling (:label "Select Text Style" :align-x :center :text-style (make-text-style :sans-serif :roman :huge)) (vertically (:equalize-width t) (horizontally () +fill+ (labelling (:label "Family" :align-x :center) family) (labelling (:label "Face" :align-x :center) face) (labelling (:label "Size" :align-x :center) (horizontally (:equalize-height t) size 10 (vertically (:equalize-width t) +fill+ smaller larger +fill+ points +fill+))) +fill+) 10 sample 10 (horizontally (:equalize-width t) +fill+ ok-button +fill+ cancel-button +fill+)))))) (defmethod display-sample-text ((frame text-style-selection) stream) (window-clear stream) (with-text-style (stream (sample-style *application-frame*)) (write-string "This is some sample text" stream))) ;;; FIXME: at dialog startup, family/face/size gadgets should be updated based ;;; on the default style supplied by the caller. Currently, only the text sample ;;; is updated. Use a :before method on `run-frame-top-level'? (defun select-text-style (&optional (default (make-text-style :fix :roman :normal))) "Return a text style interactively selected by the user, nil otherwise." (let ((frame (make-application-frame 'text-style-selection :sample-style default ;; If you don't use a CLIM implementation ;; compatible with Franz's or McCLIM, you ;; may need to remove the following ;; argument. It's required to properly ;; refresh the calling frame while the ;; dialog is open :calling-frame *application-frame*))) (run-frame-top-level frame) (when (dismiss-value frame) (sample-style frame))))