;;; eieio-custom.el --- eieio object customization -*- lexical-binding:t -*- ;; Copyright (C) 1999-2001, 2005, 2007-2021 Free Software Foundation, ;; Inc. ;; Author: Eric M. Ludlam ;; Old-Version: 0.2 (using "Version:" made Emacs think this is package ;; eieio-0.2). ;; Keywords: OO, lisp ;; Package: eieio ;; This file is part of GNU Emacs. ;; GNU Emacs is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs. If not, see . ;;; Commentary: ;; ;; This contains support customization of eieio objects. Enabling ;; your object to be customizable requires use of the slot attribute ;; `:custom'. (require 'eieio) (require 'widget) (require 'wid-edit) ;;; Compatibility ;;; Code: (defclass eieio-widget-test-class nil ((a-string :initarg :a-string :initform "The moose is loose" :custom string :label "Amorphous String" :group (default foo) :documentation "A string for testing custom. This is the next line of documentation.") (listostuff :initarg :listostuff :initform '("1" "2" "3") :type list :custom (repeat (string :tag "Stuff")) :label "List of Strings" :group foo :documentation "A list of stuff.") (uninitialized :initarg :uninitialized :type string :custom string :documentation "This slot is not initialized. Used to make sure that custom doesn't barf when it encounters one of these.") (a-number :initarg :a-number :initform 2 :custom integer :documentation "A number of thingies.")) "A class for testing the widget on.") (defcustom eieio-widget-test (eieio-widget-test-class) "Test variable for editing an object." :type 'object :group 'eieio) (defface eieio-custom-slot-tag-face '((((class color) (background dark)) (:foreground "light blue")) (((class color) (background light)) (:foreground "blue")) (t (:italic t))) "Face used for unpushable variable tags." :group 'custom-faces) (defvar eieio-wo nil "Buffer local variable in object customize buffers for the current widget.") (defvar eieio-co nil "Buffer local variable in object customize buffers for the current obj.") (defvar eieio-cog nil "Buffer local variable in object customize buffers for the current group.") (defvar eieio-custom-ignore-eieio-co nil "When true, all customizable slots of the current object are updated. Updates occur regardless of the current customization group.") (define-widget 'object-slot 'group "Abstractly modify a single slot in an object." :tag "Slot" :format "%t %v%h\n" :convert-widget 'widget-types-convert-widget :value-create 'eieio-slot-value-create :value-get 'eieio-slot-value-get :value-delete 'widget-children-value-delete :validate 'widget-children-validate :match 'eieio-object-match ;; same ) (defun eieio-slot-value-create (widget) "Create the value of WIDGET." (let ((chil nil)) (setq chil (cons (widget-create-child-and-convert widget (widget-get widget :childtype) :tag "" :value (widget-get widget :value)) chil)) (widget-put widget :children chil))) (defun eieio-slot-value-get (widget) "Get the value of WIDGET." (widget-value (car (widget-get widget :children)))) (defun eieio-custom-toggle-hide (widget) "Toggle visibility of WIDGET." (let ((vc (car (widget-get widget :children)))) (cond ((eq (widget-get vc :eieio-custom-state) 'hidden) (widget-put vc :eieio-custom-state 'visible) (widget-put vc :value-face (widget-get vc :orig-face))) (t (widget-put vc :eieio-custom-state 'hidden) (widget-put vc :orig-face (widget-get vc :value-face)) (widget-put vc :value-face 'invisible) )) (widget-value-set vc (widget-value vc)))) (defun eieio-custom-toggle-parent (widget &rest _) "Toggle visibility of parent of WIDGET. Optional argument IGNORE is an extraneous parameter." (eieio-custom-toggle-hide (widget-get widget :parent))) (define-widget 'object-edit 'group "Abstractly modify a CLOS object." :tag "Object" :format "%v" :convert-widget 'widget-types-convert-widget :value-create 'eieio-object-value-create :value-get 'eieio-object-value-get :value-delete 'widget-children-value-delete :validate 'widget-children-validate :match 'eieio-object-match :clone-object-children nil ) (defun eieio-object-match (_widget _value) "Match info for WIDGET against VALUE." ;; Write me t) (defun eieio-filter-slot-type (widget slottype) "Filter WIDGETs SLOTTYPE." (if (widget-get widget :clone-object-children) slottype (cond ((eq slottype 'object) 'object-edit) ((and (listp slottype) (eq (car slottype) 'object)) (cons 'object-edit (cdr slottype))) ((equal slottype '(repeat object)) '(repeat object-edit)) ((and (listp slottype) (equal (car slottype) 'repeat) (listp (car (cdr slottype))) (equal (car (car (cdr slottype))) 'object)) (list 'repeat (cons 'object-edit (cdr (car (cdr slottype)))))) (t slottype)))) (defun eieio-object-value-create (widget) "Create the value of WIDGET." (if (not (widget-get widget :value)) (widget-put widget :value (cond ((widget-get widget :objecttype) (funcall (eieio--class-constructor (widget-get widget :objecttype)) "Custom-new")) ((widget-get widget :objectcreatefcn) (funcall (widget-get widget :objectcreatefcn))) (t (error "No create method specified"))))) (let* ((chil nil) (obj (widget-get widget :value)) (master-group (widget-get widget :eieio-group)) (cv (eieio--object-class obj)) (slots (eieio--class-slots cv))) ;; First line describes the object, but may not editable. (if (widget-get widget :eieio-show-name) (setq chil (cons (widget-create-child-and-convert widget 'string :tag "Object " :sample-face 'bold (eieio-object-name-string obj)) chil))) ;; Display information about the group being shown (when master-group (let ((groups (eieio--class-option (eieio--object-class obj) :custom-groups))) (widget-insert "Groups:") (while groups (widget-insert " ") (if (eq (car groups) master-group) (widget-insert "*" (capitalize (symbol-name master-group)) "*") (widget-create 'push-button :thing (cons obj (car groups)) :notify (lambda (widget &rest _) (eieio-customize-object (car (widget-get widget :thing)) (cdr (widget-get widget :thing)))) (capitalize (symbol-name (car groups))))) (setq groups (cdr groups))) (widget-insert "\n\n"))) ;; Loop over all the slots, creating child widgets. (dotimes (i (length slots)) (let* ((slot (aref slots i)) (sname (eieio-slot-descriptor-name slot)) (props (cl--slot-descriptor-props slot))) ;; Output this slot if it has a customize flag associated with it. (when (and (alist-get :custom props) (or (not master-group) (member master-group (alist-get :group props))) (slot-boundp obj (cl--slot-descriptor-name slot))) ;; In this case, this slot has a custom type. Create its ;; children widgets. (let ((type (eieio-filter-slot-type widget (alist-get :custom props))) (stuff nil)) ;; This next bit is an evil hack to get some EDE functions ;; working the way I like. (if (and (listp type) (setq stuff (member :slotofchoices type))) (let ((choices (eieio-oref obj (car (cdr stuff)))) (newtype nil)) (while (not (eq (car type) :slotofchoices)) (setq newtype (cons (car type) newtype) type (cdr type))) (while choices (setq newtype (cons (list 'const (car choices)) newtype) choices (cdr choices))) (setq type (nreverse newtype)))) (setq chil (cons (widget-create-child-and-convert widget 'object-slot :childtype type :sample-face 'eieio-custom-slot-tag-face :tag (concat (make-string (or (widget-get widget :indent) 0) ?\s) (or (alist-get :label props) (let ((s (symbol-name (or (eieio--class-slot-initarg (eieio--object-class obj) sname) sname)))) (capitalize (if (string-match "^:" s) (substring s (match-end 0)) s))))) :value (slot-value obj sname) :doc (or (alist-get :documentation props) "Slot not Documented.") :eieio-custom-visibility 'visible ) chil)) )))) (widget-put widget :children (nreverse chil)) )) (defun eieio-object-value-get (widget) "Get the value of WIDGET." (let* ((obj (widget-get widget :value)) (master-group eieio-cog) (wids (widget-get widget :children)) (name (if (widget-get widget :eieio-show-name) (car (widget-apply (car wids) :value-inline)) nil)) (chil (if (widget-get widget :eieio-show-name) (nthcdr 1 wids) wids)) (cv (eieio--object-class obj)) (i 0) (slots (eieio--class-slots cv))) ;; If there are any prefix widgets, clear them. ;; -- None yet ;; Create a batch of initargs for each slot. (while (and (< i (length slots)) chil) (let* ((slot (aref slots i)) (props (cl--slot-descriptor-props slot)) (cust (alist-get :custom props))) ;; ;; Shouldn't I be incremented unconditionally? Or ;; better shouldn't we simply mapc on the slots vector ;; avoiding use of this integer variable? PLN Sat May ;; 2 07:35:45 2015 ;; (setq i (+ i 1)) (if (and cust (or eieio-custom-ignore-eieio-co (not master-group) (member master-group (alist-get :group props))) (slot-boundp obj (cl--slot-descriptor-name slot))) (progn ;; Only customized slots have widgets (let ((eieio-custom-ignore-eieio-co t)) (eieio-oset obj (cl--slot-descriptor-name slot) (car (widget-apply (car chil) :value-inline)))) (setq chil (cdr chil)))))) ;; Set any name updates on it. (when name (setf (slot-value obj 'object-name) name)) ;; This is the same object we had before. obj)) (cl-defmethod eieio-done-customizing ((_obj eieio-default-superclass)) "When applying change to a widget, call this method. This method is called by the default widget-edit commands. User made commands should also call this method when applying changes. Argument OBJ is the object that has been customized." nil) ;;;###autoload (defun customize-object (obj &optional group) "Customize OBJ in a custom buffer. Optional argument GROUP is the sub-group of slots to display." (eieio-customize-object obj group)) (defvar eieio-custom-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map widget-keymap) map) "Keymap for EIEIO Custom mode") (define-derived-mode eieio-custom-mode fundamental-mode "EIEIO Custom" "Major mode for customizing EIEIO objects. \\{eieio-custom-mode-map}") (cl-defmethod eieio-customize-object ((obj eieio-default-superclass) &optional group) "Customize OBJ in a specialized custom buffer. To override call the `eieio-custom-widget-insert' to just insert the object widget. Optional argument GROUP specifies a subgroup of slots to edit as a symbol. These groups are specified with the `:group' slot flag." ;; Insert check for multiple edits here. (let* ((g (or group 'default))) (switch-to-buffer (get-buffer-create (concat "*CUSTOMIZE " (eieio-object-name obj) " " (symbol-name g) "*"))) (setq buffer-read-only nil) (kill-all-local-variables) (eieio-custom-mode) (erase-buffer) (let ((all (overlay-lists))) ;; Delete all the overlays. (mapc 'delete-overlay (car all)) (mapc 'delete-overlay (cdr all))) ;; Add an apply reset option at the top of the buffer. (eieio-custom-object-apply-reset obj) (widget-insert "\n\n") (widget-insert "Edit object " (eieio-object-name obj) "\n\n") ;; Create the widget editing the object. (setq-local eieio-wo (eieio-custom-widget-insert obj :eieio-group g)) ;;Now generate the apply buttons (widget-insert "\n") (eieio-custom-object-apply-reset obj) ;; Now initialize the buffer (widget-setup) ;;(widget-minor-mode) (goto-char (point-min)) (widget-forward 3) (setq-local eieio-co obj) (setq-local eieio-cog g))) (cl-defmethod eieio-custom-object-apply-reset ((_obj eieio-default-superclass)) "Insert an Apply and Reset button into the object editor. Argument OBJ is the object being customized." (widget-create 'push-button :notify (lambda (&rest _) (widget-apply eieio-wo :value-get) (eieio-done-customizing eieio-co) (bury-buffer)) "Accept") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest _) ;; I think the act of getting it sets ;; its value through the get function. (message "Applying Changes...") (widget-apply eieio-wo :value-get) (eieio-done-customizing eieio-co) (message "Applying Changes...Done")) "Apply") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest _) (message "Resetting") (eieio-customize-object eieio-co eieio-cog)) "Reset") (widget-insert " ") (widget-create 'push-button :notify (lambda (&rest _) (bury-buffer)) "Cancel")) (cl-defmethod eieio-custom-widget-insert ((obj eieio-default-superclass) &rest flags) "Insert the widget used for editing object OBJ in the current buffer. Arguments FLAGS are widget compatible flags. Must return the created widget." (apply 'widget-create 'object-edit :value obj flags)) (define-widget 'object 'object-edit "Instance of a CLOS class." :format "%{%t%}:\n%v" :value-to-internal 'eieio-object-value-to-abstract :value-to-external 'eieio-object-abstract-to-value :clone-object-children t ) (defun eieio-object-value-to-abstract (_widget value) "For WIDGET, convert VALUE to an abstract /safe/ representation." (if (eieio-object-p value) value)) (defun eieio-object-abstract-to-value (_widget value) "For WIDGET, convert VALUE from an abstract /safe/ representation." value) ;;; customization group functions ;; ;; These functions provide the ability to create dynamic menus to ;; customize specific sections of an object. They do not hook directly ;; into a filter, but can be used to create easymenu vectors. (cl-defmethod eieio-customize-object-group ((obj eieio-default-superclass)) "Create a list of vectors for customizing sections of OBJ." (mapcar (lambda (group) (vector (concat "Group " (symbol-name group)) (list 'customize-object obj (list 'quote group)) t)) (eieio--class-option (eieio--object-class obj) :custom-groups))) (defvar eieio-read-custom-group-history nil "History for the custom group reader.") (cl-defmethod eieio-read-customization-group ((obj eieio-default-superclass)) "Do a completing read on the name of a customization group in OBJ. Return the symbol for the group, or nil." (let ((g (eieio--class-option (eieio--object-class obj) :custom-groups))) (if (= (length g) 1) (car g) ;; Make the association list (setq g (mapcar (lambda (g) (cons (symbol-name g) g)) g)) (cdr (assoc (completing-read (concat (if (slot-exists-p obj 'name) (concat (slot-value obj (intern "name" obarray)) "") "") "Custom Group: ") g nil t nil 'eieio-read-custom-group-history) g))))) (provide 'eieio-custom) ;; Local variables: ;; generated-autoload-file: "eieio-loaddefs.el" ;; End: ;;; eieio-custom.el ends here