Update of /project/mcclim/cvsroot/mcclim In directory clnet:/tmp/cvs-serv27201
Modified Files: text-editor-gadget.lisp gadgets.lisp Log Message: Attempt at cleaning up the text-field and text-editor gadget mess. Drei/Goatee selection now more elegant and complex setups (scrolling, minibuffer for Drei) now handled well without relying on undocumented McCLIM quirks. The various size-specification-features should also work now.
--- /project/mcclim/cvsroot/mcclim/text-editor-gadget.lisp 2007/02/07 12:44:17 1.8 +++ /project/mcclim/cvsroot/mcclim/text-editor-gadget.lisp 2007/08/21 22:09:01 1.9 @@ -27,30 +27,89 @@
;;; This file contains the concrete implementation of the text-field ;;; and text-editor gadgets. It is loaded rather late, because it -;;; requires Drei. +;;; requires Drei. Half of the complexity here is about working around +;;; annoying Goatee quirks, generalising it to three editor substrates +;;; is nontrivial.
(in-package :clim-internals)
-;;; ------------------------------------------------------------------------------------------ -;;; 30.4.8 The concrete text-field Gadget +;;; The text editor gadget(s) is implemented as a class implementing +;;; the text editor gadget protocol, but containing an editor +;;; substrate object that takes care of the actual editing logic, +;;; redisplay, etc. The substrates need to be gadgets themselves and +;;; are defined here.
-(defclass text-field-pane (text-field - drei:drei-gadget-pane) - ((previous-focus :accessor previous-focus :initform nil - :documentation - "The pane that previously had keyboard focus") - (activation-gestures :accessor activation-gestures - :initarg :activation-gestures - :documentation "gestures that cause the -activate callback to be called")) - (:default-initargs - :activation-gestures *standard-activation-gestures*)) +(defparameter *default-text-field-text-style* + (make-text-style :fixed :roman :normal)) + +(defclass editor-substrate-mixin (value-gadget) + ((activation-gestures :reader activation-gestures + :initarg :activation-gestures) + (user :reader user-gadget + :initarg :user-gadget + :documentation "The editor gadget using this editor substrate." + :initform (error "Editor substrates must have a user."))) + (:documentation "A mixin class for text editor gadget substrates.") + (:default-initargs :activation-gestures '())) + +(defmethod gadget-id ((gadget editor-substrate-mixin)) + (gadget-id (user-gadget gadget))) + +(defmethod (setf gadget-id) (value (gadget editor-substrate-mixin)) + (setf (gadget-id (user-gadget gadget)) value)) + +(defmethod gadget-client ((gadget editor-substrate-mixin)) + (gadget-client (user-gadget gadget))) + +(defmethod (setf gadget-client) (value (gadget editor-substrate-mixin)) + (setf (gadget-client (user-gadget gadget)) value)) + +(defmethod gadget-armed-callback ((gadget editor-substrate-mixin)) + (gadget-armed-callback (user-gadget gadget))) + +(defmethod gadget-disarmed-callback ((gadget editor-substrate-mixin)) + (gadget-disarmed-callback (user-gadget gadget))) + +(defclass text-field-substrate-mixin (editor-substrate-mixin) + () + (:documentation "A mixin class for editor substrates used for text field gadgets."))
-(defmethod initialize-instance :after ((object text-field-pane) &key value) - ;; Why doesn't `value-gadget' do this for us? - (setf (gadget-value object) value)) +(defclass text-editor-substrate-mixin (editor-substrate-mixin) + ((ncolumns :reader text-editor-ncolumns + :initarg :ncolumns + :initform nil + :type (or null integer)) + (nlines :reader text-editor-nlines + :initarg :nlines + :initform nil + :type (or null integer))) + (:documentation "A mixin class for editor substrates used for text editor gadgets.")) + +;;; Now, define the Drei substrate. + +(defclass drei-editor-substrate (drei:drei-gadget-pane + editor-substrate-mixin) + () + (:documentation "A class for Drei-based editor substrates."))
-(defmethod compose-space ((pane text-field-pane) &key width height) +(defmethod (setf gadget-value) :after (value (gadget drei-editor-substrate) + &key invoke-callback) + (declare (ignore invoke-callback)) + ;; Hm! I wonder if this can cause trouble. I think not. + (drei:display-drei gadget)) + +(defclass drei-text-field-substrate (text-field-substrate-mixin + drei-editor-substrate) + () + (:documentation "The class for Drei-based text field substrates.")) + +(defmethod drei:handle-gesture ((drei drei-text-field-substrate) gesture) + (if (with-activation-gestures ((activation-gestures drei)) + (activation-gesture-p gesture)) + (activate-callback drei (gadget-client drei) (gadget-id drei)) + (call-next-method))) + +(defmethod compose-space ((pane drei-text-field-substrate) &key width height) (declare (ignore width height)) (with-sheet-medium (medium pane) (let ((as (text-style-ascent (medium-text-style medium) medium)) @@ -59,44 +118,14 @@ (let ((width w) (height (+ as ds))) (make-space-requirement :height height :max-height height :min-height height - :min-width width :width width))))) + :min-width width :width width)))))
-(defmethod drei:handle-gesture ((drei text-field-pane) gesture) - (if (with-activation-gestures ((activation-gestures drei)) - (activation-gesture-p gesture)) - (activate-callback drei (gadget-client drei) (gadget-id drei)) - (call-next-method))) - -(defmethod allocate-space ((pane text-field-pane) w h) - (resize-sheet pane w h)) - -;;; ------------------------------------------------------------------------------------------ -;;; 30.4.9 The concrete text-editor Gadget +(defclass drei-text-editor-substrate (text-editor-substrate-mixin + drei-editor-substrate) + () + (:documentation "The class for Drei-based text editor substrates."))
-(defclass text-editor-pane (text-editor drei:drei-gadget-pane) - ((ncolumns :type (or null integer) - :initarg :ncolumns - :initform nil - :accessor text-editor-ncolumns) - (nlines :type (or null integer) - :initarg :nlines - :initform nil - :accessor text-editor-nlines)) - (:default-initargs :activation-gestures nil)) - -(defmethod initialize-instance :after ((object text-editor-pane) &key value) - ;; Why doesn't `value-gadget' do this for us? - (setf (gadget-value object) value)) - -(defmethod make-pane-1 :around (fm (frame application-frame) - (type (eql 'text-editor)) - &rest args &key) - (apply #'make-pane-1 fm frame :drei - :drei-class 'text-editor-pane - :minibuffer t - args)) - -(defmethod compose-space ((pane text-editor-pane) &key width height) +(defmethod compose-space ((pane drei-text-editor-substrate) &key width height) (with-sheet-medium (medium pane) (let* ((text-style (medium-text-style medium)) (line-height (+ (text-style-height text-style medium) @@ -113,86 +142,72 @@ (height (if nlines (+ (* nlines line-height)) height))) - (list :width width :max-width width :min-width width - :height height :max-height height :min-height height))))))) + (list + :width width :max-width width :min-width width + :height height :max-height height :min-height height)))))))
-(defmethod allocate-space ((pane text-editor-pane) w h) +(defmethod allocate-space ((pane drei-text-editor-substrate) w h) (resize-sheet pane w h))
-;;; ------------------------------------------------------------------------------------------ -;;; 30.4.9 Alternative Goatee-based implementation - -(defparameter *default-text-field-text-style* - (make-text-style :fixed :roman :normal)) +;;; Now, define the Goatee substrate.
-(defclass goatee-text-field-pane (text-field - standard-extended-output-stream - standard-output-recording-stream - basic-pane) - ((area :accessor area :initform nil - :documentation "The Goatee area used for text editing.") - (previous-focus :accessor previous-focus :initform nil - :documentation - "The pane that previously had keyboard focus") - (activation-gestures :accessor activation-gestures - :initarg :activation-gestures - :documentation "gestures that cause the - activate callback to be called")) +(defclass goatee-editor-substrate (editor-substrate-mixin + text-field + clim-stream-pane) + ((area :accessor area + :initform nil + :documentation "The Goatee area used for text editing.") + ;; This hack is necessary because the Goatee editing area is not + ;; created until the first redisplay... yuck. + (value :documentation "The initial value for the Goatee area.")) (:default-initargs - :text-style *default-text-field-text-style* - :activation-gestures *standard-activation-gestures*)) + :text-style *default-text-field-text-style*))
-(defmethod initialize-instance :after ((gadget text-field) &rest rest) - (unless (getf rest :normal) - (setf (slot-value gadget 'current-color) +white+ - (slot-value gadget 'normal) +white+))) - -(defmethod initialize-instance :after ((pane goatee-text-field-pane) &rest rest) +(defmethod initialize-instance :after ((pane goatee-editor-substrate) &rest rest) (declare (ignore rest)) - #-nil (setf (medium-text-style (sheet-medium pane)) - (slot-value pane 'text-style))) + (setf (medium-text-style (sheet-medium pane)) + (slot-value pane 'text-style)))
;; Is there really a benefit to waiting until the first painting to ;; create the goatee instance? Why not use INITIALIZE-INSTANCE? -(defmethod handle-repaint :before ((pane goatee-text-field-pane) region) +(defmethod handle-repaint :before ((pane goatee-editor-substrate) region) (declare (ignore region)) (unless (area pane) (multiple-value-bind (cx cy) - (stream-cursor-position pane) + (stream-cursor-position pane) (setf (cursor-visibility (stream-text-cursor pane)) nil) (setf (area pane) (make-instance 'goatee:simple-screen-area - :area-stream pane - :x-position cx - :y-position cy - :initial-contents (slot-value pane - 'value)))) + :area-stream pane + :x-position cx + :y-position cy + :initial-contents (slot-value pane 'value)))) (stream-add-output-record pane (area pane))))
;;; This implements click-to-focus-keyboard-and-pass-click-through ;;; behaviour. -(defmethod handle-event :before - ((gadget goatee-text-field-pane) (event pointer-button-press-event)) +(defmethod handle-event :before + ((gadget goatee-editor-substrate) (event pointer-button-press-event)) (let ((previous (stream-set-input-focus gadget))) (when (and previous (typep previous 'gadget)) (disarmed-callback previous (gadget-client previous) (gadget-id previous))) (armed-callback gadget (gadget-client gadget) (gadget-id gadget))))
-(defmethod armed-callback :after ((gadget goatee-text-field-pane) client id) +(defmethod armed-callback :after ((gadget goatee-editor-substrate) client id) (declare (ignore client id)) (handle-repaint gadget +everywhere+) ;FIXME: trigger initialization (let ((cursor (cursor (area gadget)))) (letf (((cursor-state cursor) nil)) (setf (cursor-appearance cursor) :solid))))
-(defmethod disarmed-callback :after ((gadget goatee-text-field-pane) client id) +(defmethod disarmed-callback :after ((gadget goatee-editor-substrate) client id) (declare (ignore client id)) (handle-repaint gadget +everywhere+) ;FIXME: trigger initialization (let ((cursor (cursor (area gadget)))) (letf (((cursor-state cursor) nil)) (setf (cursor-appearance cursor) :hollow))))
-(defmethod handle-event - ((gadget goatee-text-field-pane) (event key-press-event)) +(defmethod handle-event + ((gadget goatee-editor-substrate) (event key-press-event)) (let ((gesture (convert-to-gesture event)) (*activation-gestures* (activation-gestures gadget))) (when (activation-gesture-p gesture) @@ -209,7 +224,7 @@ (gadget-id gadget) new-value)))))
-(defmethod (setf gadget-value) :after (new-value (gadget goatee-text-field-pane) +(defmethod (setf gadget-value) :after (new-value (gadget goatee-editor-substrate) &key invoke-callback) (declare (ignore invoke-callback)) (let* ((area (area gadget)) @@ -221,7 +236,7 @@ (goatee::redisplay-area area)))
#+nil -(defmethod handle-repaint ((pane goatee-text-field-pane) region) +(defmethod handle-repaint ((pane goatee-editor-substrate) region) (declare (ignore region)) (with-special-choices (pane) (with-sheet-medium (medium pane) @@ -233,8 +248,12 @@ :align-x :left :align-y :baseline)))))
+(defclass goatee-text-field-substrate (text-field-substrate-mixin + goatee-editor-substrate) + () + (:documentation "The class for Goatee-based text field substrates."))
-(defmethod compose-space ((pane goatee-text-field-pane) &key width height) +(defmethod compose-space ((pane goatee-text-field-substrate) &key width height) (declare (ignore width height)) (with-sheet-medium (medium pane) (let ((as (text-style-ascent (medium-text-style medium) medium)) @@ -243,48 +262,140 @@ (let ((width w) (height (+ as ds))) (make-space-requirement :width width :height height - :max-width width :max-height height - :min-width width :min-height height))))) + :max-width width :max-height height + :min-width width :min-height height))))) + +(defclass goatee-text-editor-substrate (text-editor-substrate-mixin + goatee-editor-substrate) + () + (:documentation "The class for Goatee-based text field substrates."))
-(defmethod allocate-space ((pane goatee-text-field-pane) w h) +(defmethod compose-space ((pane goatee-text-editor-substrate) &key width height) + (with-sheet-medium (medium pane) + (let* ((text-style (medium-text-style medium)) + (line-height (+ (text-style-height text-style medium) + (stream-vertical-spacing pane))) + (column-width (text-style-width text-style medium))) + (with-accessors ((ncolumns text-editor-ncolumns) + (nlines text-editor-nlines)) pane + (apply #'space-requirement-combine* #'(lambda (req1 req2) + (or req2 req1)) + (call-next-method) + (let ((width (if ncolumns + (+ (* ncolumns column-width)) + width)) + (height (if nlines + (+ (* nlines line-height)) + height))) + (list :width width :max-width width :min-width width + :height height :max-height height :min-height height))))))) + +(defmethod allocate-space ((pane goatee-text-editor-substrate) w h) (resize-sheet pane w h))
-(defclass goatee-text-editor-pane (goatee-text-field-pane) - ((width :type integer - :initarg :width - :initform 300 - :reader text-editor-width) - (height :type integer - :initarg :height - :initform 300 - :reader text-editor-height)) - (:default-initargs :activation-gestures nil)) - -(defmethod compose-space ((pane goatee-text-editor-pane) &key width height) - (declare (ignore width height)) - (let ((width (text-editor-width pane)) - (height (text-editor-height pane))) - (make-space-requirement :width width - :min-width width - :max-width width - :height height - :min-height height - :max-height height))) +(defun make-text-field-substrate (user &rest args) + "Create an appropriate text field gadget editing substrate object." + (let* ((substrate (apply #'make-pane (if *use-goatee* + 'goatee-text-field-substrate + 'drei-text-field-substrate) + :user-gadget user args)) + (sheet substrate)) + (values substrate sheet))) + +(defun make-text-editor-substrate (user &rest args &key scroll-bars value + &allow-other-keys) + "Create an appropriate text editor gadget editing substrate +object. Returns two values, the first is the substrate object, +the second is the sheet that should be adopted by the user +gadget." + (let* ((minibuffer (when (and (not *use-goatee*) scroll-bars) + (make-pane 'drei::drei-minibuffer-pane))) + (substrate (apply #'make-pane (if *use-goatee* + 'goatee-text-editor-substrate + 'drei-text-editor-substrate) + :user-gadget user + :minibuffer minibuffer args)) + (sheet (if scroll-bars + (scrolling (:scroll-bars scroll-bars) + substrate) + substrate))) + (if *use-goatee* + (setf (slot-value substrate 'value) value) + (setf (gadget-value substrate) value)) + (values substrate (if minibuffer + (vertically ()
[90 lines skipped] --- /project/mcclim/cvsroot/mcclim/gadgets.lisp 2007/03/04 22:27:30 1.106 +++ /project/mcclim/cvsroot/mcclim/gadgets.lisp 2007/08/21 22:09:01 1.107 @@ -644,6 +644,11 @@ (:documentation "The value is a string") (:default-initargs :value ""))
+(defmethod initialize-instance :after ((gadget text-field) &rest rest) + (unless (getf rest :normal) + (setf (slot-value gadget 'current-color) +white+ + (slot-value gadget 'normal) +white+))) + ;;; 30.4.9 The abstract text-editor Gadget
(defclass text-editor (text-field)