[mcclim-cvs] CVS mcclim/ESA

thenriksen thenriksen at common-lisp.net
Fri Dec 28 10:08:58 UTC 2007


Update of /project/mcclim/cvsroot/mcclim/ESA
In directory clnet:/tmp/cvs-serv2026/ESA

Modified Files:
	packages.lisp utils.lisp 
Log Message:
Added support for "modes" (roughly similar to Emacs' minor-modes) to Drei.


--- /project/mcclim/cvsroot/mcclim/ESA/packages.lisp	2007/12/19 15:10:20	1.7
+++ /project/mcclim/cvsroot/mcclim/ESA/packages.lisp	2007/12/28 10:08:52	1.8
@@ -42,11 +42,21 @@
            #:invoke-with-dynamic-bindings
            #:maptree
            #:subtype-compatible-p
+           #:capitalize
            #:observable-mixin
            #:add-observer #:remove-observer
            #:observer-notified #:notify-observers
            #:name-mixin #:name
-           #:subscriptable-name-mixin #:subscripted-name #:subscript #:subscript-generator))
+           #:subscriptable-name-mixin #:subscripted-name #:subscript #:subscript-generator
+           #:mode #:modual-mixin
+           #:available-modes
+           #:mode-directly-applicable-p #:mode-applicable-p
+           #:mode-enabled-p
+           #:enabled-modes
+           #:nonapplicable-mode
+           #:change-class-for-enabled-mode
+           #:change-class-for-disabled-mode
+           #:enable-mode #:disable-mode))
 
 (defpackage :esa
   (:use :clim-lisp :clim :esa-utils)
--- /project/mcclim/cvsroot/mcclim/ESA/utils.lisp	2007/12/08 08:53:48	1.4
+++ /project/mcclim/cvsroot/mcclim/ESA/utils.lisp	2007/12/28 10:08:52	1.5
@@ -213,6 +213,12 @@
   (some (lambda (x)
           (subtypep x `(and , at types))) types))
 
+(defun capitalize (string)
+  "Return `string' with the first character
+capitalized (destructively modified)."
+  (setf (elt string 0) (char-upcase (elt string 0)))
+  string)
+
 (defclass observable-mixin ()
   ((%observers :accessor observers
                :initform '()))
@@ -308,3 +314,165 @@
 (defmethod (setf name) :after (new-name (name-mixin subscriptable-name-mixin))
   (setf (subscript name-mixin)
         (funcall (subscript-generator name-mixin) new-name)))
+
+;;; "Modes" are a generally useful concept, so let's define some
+;;; primitives for them here.
+
+(defclass mode ()
+  ()
+  (:documentation "A superclass for all modes."))
+
+(defclass modual-mixin ()
+  ((%original-class-name :accessor original-class-name
+                         :documentation "The original name of the
+class the `modual-mixin' is part of, the actual name will change
+as modes are added and removed."))
+  (:documentation "A mixin for objects supporting modes."))
+
+(defmethod initialize-instance :after ((object modual-mixin) &rest initargs)
+  (declare (ignore initargs))
+  (setf (original-class-name object) (class-name (class-of object))))
+
+(defgeneric available-modes (modual)
+  (:documentation "Return all available modes for `modual'. Not
+all of the modes may be applicable, use the `applicable-modes'
+function if you're only interested in these.")
+  (:method-combination append)
+  (:method append ((modual modual-mixin))
+    '()))
+
+(defgeneric mode-directly-applicable-p (modual mode-name)
+  (:documentation "Return true if the mode of the name
+`mode-name' can be directly enabled for `modual'. If the mode of
+name `mode-name' is unapplicable, an error of type
+`nonapplicable-mode' will be signalled. This allows a sort of
+\"opt-out\" where a mode can forcefully prevent another specific
+mode from being enabled. ")
+  (:method-combination or)
+  (:method or ((modual modual-mixin) mode-name)
+     nil))
+
+(defgeneric mode-applicable-p (modual mode-name)
+  (:documentation "Return true if the mode of the name
+`mode-name' can be enabled for `modual' or some sub-object of
+`modual'. If the mode of name `mode-name' is unapplicable, an
+error of type `nonapplicable-mode' will be signalled. This allows
+a sort of \"opt-out\" where a mode can forcefully prevent another
+specific mode from being enabled. ")
+  (:method-combination or)
+  (:method or ((modual modual-mixin) mode-name)
+     (mode-directly-applicable-p modual mode-name)))
+
+(defgeneric enabled-modes (modual)
+  (:documentation "Return a list of the names of the modes
+directly enabled for `modual'.")
+  (:method-combination append)
+  (:method append ((modual modual-mixin))
+     '()))
+
+(defgeneric mode-enabled-p (modual mode-name)
+  (:documentation "Return true if `mode-name' is enabled for
+`modual' or any modual \"sub-objects\"." )
+  (:method-combination or)
+  (:method or ((modual modual-mixin) mode-name)
+     (member mode-name (enabled-modes modual) :test #'equal)))
+
+(define-condition nonapplicable-mode (error)
+  ((%modual :accessor modual
+            :initarg :modual
+            :initform (error "The modual used in the error-causing operation must be supplied")
+            :documentation "The modual that the mode is attempted to be enabled for")
+   (%mode-name :accessor mode-name
+               :initarg :mode-name
+               :initform (error "The name of the problematic mode must be supplied")
+               :documentation "The name of the mode that cannot be enabled for the view"))
+  (:documentation "This error is signalled if a mode is attempted
+enabled for a modual that the mode is not applicable to.")
+  (:report (lambda (condition stream)
+             (format
+              stream "The mode ~A is not applicable for ~A"
+              (mode-name condition) (modual condition)))))
+
+(defun nonapplicable-mode (modual mode-name)
+  "Signal an error of type `nonapplicable-mode' with `modual' and
+`mode-name' as arguments."
+  (error 'nonapplicable-mode :modual modual :mode-name mode-name))
+
+(defgeneric enable-mode (modual mode-name &rest initargs)
+  (:documentation "Enable the mode of the name `mode-name' for
+`modual', using `initargs' as options for the mode. If the mode
+is already enabled, do nothing. If the mode is not applicable to
+`modual', signal an `nonapplicable-mode' error.")
+  (:method :around ((modual modual-mixin) mode-name &rest initargs)
+     (declare (ignore initargs))
+     (unless (mode-enabled-p modual mode-name)
+       (call-next-method))))
+
+(defgeneric disable-mode (modual mode-name)
+  (:documentation "Disable the mode of the name `mode-name' for
+`modual'. If a mode of the provided name is not enabled, do
+nothing.")
+  (:method :around ((modual modual-mixin) mode-name)
+     (when (mode-enabled-p modual mode-name)
+       (call-next-method))))
+
+;;; In a perfect world, we would just combine `change-class' with
+;;; anonymous classes to transparently add and remove mode classes
+;;; (the "stealth mixin" concept). However, anonymous classes are the
+;;; ugly child of CL, not well supported at all, so we'll have to do
+;;; some ugly hacks involving the `eval'ing of constructed `defclass'
+;;; forms, and caching the created classes to prevent memory leaking.
+
+(defvar *class-cache* (make-hash-table :test #'equal)
+  "A hash table mapping the name of a \"modual\" class to a
+second hash table. This second hash table maps a list of mode
+names to a class implementing this particular set of modes for
+the modual class. Note that the order in which the modes appear
+in the list is significant.")
+
+(defun make-class-implementing-modes (modual modes)
+  "Generate a class that is a subclass of `modual' that
+implements all the modes listed as names in `modes'."
+  ;; Avert thine eyes, thy of gentle spirit.
+  (if (null modes)
+      (find-class modual)
+      (eval `(defclass ,(gensym) (,modual , at modes) ()))))
+
+(defun find-class-implementing-modes (modual modes)
+  "Find, possibly create, the class implementing `modual' (a
+class name) with `modes' (a list of mode names) as the enabled
+modes."
+  (let* ((modual-cache-hit (gethash modual *class-cache*))
+         (modes-cache-hit (and modual-cache-hit
+                               (gethash modes modual-cache-hit))))
+    (or modes-cache-hit
+        (setf (gethash modes
+                       (or modual-cache-hit
+                           (setf (gethash modual *class-cache*) 
+                                 (make-hash-table :test #'equal))))
+              (make-class-implementing-modes modual modes)))))
+
+(defun change-class-for-enabled-mode (modual mode-name &rest initargs)
+  "Change the class of `modual' so that it has a mode of name
+`mode-name', created with the provided `initargs'."
+  (apply #'change-class modual (find-class-implementing-modes
+                                (original-class-name modual)
+                                (cons mode-name (enabled-modes modual)))
+         initargs))
+
+(defun change-class-for-disabled-mode (modual mode-name)
+  "Change the class of `modual' so that it does not have a mode
+of name `mode-name'."
+  (change-class modual (find-class-implementing-modes
+                        (original-class-name modual)
+                        (remove mode-name (enabled-modes modual)
+                         :test #'equal))))
+
+(defmethod enable-mode ((modual modual-mixin) mode-name &rest initargs)
+  (if (mode-directly-applicable-p modual mode-name)
+      (apply #'change-class-for-enabled-mode modual mode-name initargs)
+      (nonapplicable-mode modual mode-name)))
+
+(defmethod disable-mode ((modual modual-mixin) mode-name)
+  (when (mode-directly-applicable-p modual mode-name)
+    (change-class-for-disabled-mode modual mode-name)))




More information about the Mcclim-cvs mailing list