comparison lisp/wid-edit.el @ 4178:e687f1912d5b

[xemacs-hg @ 2007-09-20 21:18:33 by didierv] User options interactive prompting improvements
author didierv
date Thu, 20 Sep 2007 21:18:35 +0000
parents 681d0fbb904e
children f00192e1cd49 308d34e9f07d
comparison
equal deleted inserted replaced
4177:d080fe09a356 4178:e687f1912d5b
1 ;;; wid-edit.el --- Functions for creating and using widgets. 1 ;;; wid-edit.el --- Functions for creating and using widgets.
2 ;; 2 ;;
3 ;; Copyright (C) 2007 Didier Verna
3 ;; Copyright (C) 1996-1997, 1999-2002 Free Software Foundation, Inc. 4 ;; Copyright (C) 1996-1997, 1999-2002 Free Software Foundation, Inc.
4 ;; 5 ;;
5 ;; Author: Per Abrahamsen <abraham@dina.kvl.dk> 6 ;; Author: Per Abrahamsen <abraham@dina.kvl.dk>
6 ;; Maintainer: Hrvoje Niksic <hniksic@xemacs.org> 7 ;; Maintainer: Didier Verna <didier@xemacs.org>
7 ;; Keywords: extensions 8 ;; Keywords: extensions
8 ;; Version: 1.9960-x 9 ;; Version: 1.9960-x
9 ;; X-URL: http://www.dina.kvl.dk/~abraham/custom/ 10 ;; X-URL: http://www.dina.kvl.dk/~abraham/custom/
10 11
11 ;; This file is part of XEmacs. 12 ;; This file is part of XEmacs.
328 "Move to where you click, and if it is an active field, invoke it." 329 "Move to where you click, and if it is an active field, invoke it."
329 (interactive "e") 330 (interactive "e")
330 (mouse-set-point event) 331 (mouse-set-point event)
331 (let ((pos (event-point event))) 332 (let ((pos (event-point event)))
332 (if (and pos (get-char-property pos 'button)) 333 (if (and pos (get-char-property pos 'button))
333 (widget-button-click event)))) 334 (widget-button-click event))))
334 335
335 ;;; Widget text specifications. 336 ;;; Widget text specifications.
336 ;; 337 ;;
337 ;; These functions are for specifying text properties. 338 ;; These functions are for specifying text properties.
338 339
589 nil from to :deactivate nil 'glyph-widget))) 590 nil from to :deactivate nil 'glyph-widget)))
590 591
591 (defun widget-specify-active (widget) 592 (defun widget-specify-active (widget)
592 "Make WIDGET active for user modifications." 593 "Make WIDGET active for user modifications."
593 (let ((inactive (widget-get widget :inactive)) 594 (let ((inactive (widget-get widget :inactive))
594 (from (widget-get widget :from)) 595 (from (widget-get widget :from))
595 (to (widget-get widget :to))) 596 (to (widget-get widget :to)))
596 (when (and inactive (not (extent-detached-p inactive))) 597 (when (and inactive (not (extent-detached-p inactive)))
597 ;; Reactivate the buttons and fields covered by the extent. 598 ;; Reactivate the buttons and fields covered by the extent.
598 (map-extents 'widget-activation-widget-mapper 599 (map-extents 'widget-activation-widget-mapper
599 nil from to :activate nil 'button-or-field) 600 nil from to :activate nil 'button-or-field)
600 ;; Reactivate the glyphs. 601 ;; Reactivate the glyphs.
601 (map-extents 'widget-activation-glyph-mapper 602 (map-extents 'widget-activation-glyph-mapper
602 nil from to :activate nil 'end-glyph) 603 nil from to :activate nil 'end-glyph)
603 (delete-extent inactive) 604 (delete-extent inactive)
604 (widget-put widget :inactive nil)))) 605 (widget-put widget :inactive nil))))
605 606
606 607
607 ;;; Widget Properties. 608 ;;; Widget Properties.
704 705
705 ;;; Helper functions. 706 ;;; Helper functions.
706 ;; 707 ;;
707 ;; These are widget specific. 708 ;; These are widget specific.
708 709
710 ;; #### Note: this should probably be a more general utility -- dvl
711 (defsubst widget-prompt-spaceify (prompt)
712 ;; Add a space at the end of PROMPT if needed
713 (if (or (string= prompt "") (eq ? (aref prompt (1- (length prompt)))))
714 prompt
715 (concat prompt " ")))
716
717 (defsubst widget-prompt (widget &optional prompt default-prompt)
718 ;; Construct a prompt for WIDGET.
719 ;; - If PROMPT is given, use it.
720 ;; - Otherwise, use the :tag property, if any.
721 ;; - Otherwise, use DEFAULT-PROMPT, if given.
722 ;; - Otherise, use "Value".
723 ;; - If the result is not the empty string, add a space for later addition
724 ;; of the widget type by `widget-prompt-value'.
725 (unless prompt
726 (setq prompt (or (and (widget-get widget :tag)
727 (replace-in-string (widget-get widget :tag)
728 "^[ \t]+" "" t))
729 default-prompt
730 "Value")))
731 (widget-prompt-spaceify prompt))
732
733
709 ;;;###autoload 734 ;;;###autoload
710 (defun widget-prompt-value (widget prompt &optional value unbound) 735 (defun widget-prompt-value (widget &optional prompt value unbound)
711 "Prompt for a value matching WIDGET, using PROMPT. 736 "Prompt for a value matching WIDGET.
737 Prompt with PROMPT, or WIDGET's :tag otherwise.
712 The current value is assumed to be VALUE, unless UNBOUND is non-nil." 738 The current value is assumed to be VALUE, unless UNBOUND is non-nil."
713 (unless (listp widget) 739 (unless (listp widget)
714 (setq widget (list widget))) 740 (setq widget (list widget)))
715 (setq prompt (format "[%s] %s" (widget-type widget) prompt))
716 (setq widget (widget-convert widget)) 741 (setq widget (widget-convert widget))
717 (let ((answer (widget-apply widget :prompt-value prompt value unbound))) 742 (let ((answer (widget-apply widget
743 :prompt-value
744 (format "%s[%s]"
745 (widget-prompt widget prompt)
746 (widget-type widget))
747 value unbound)))
718 (while (not (widget-apply widget :match answer)) 748 (while (not (widget-apply widget :match answer))
719 (setq answer (signal 'error (list "Answer does not match type" 749 (setq answer (signal 'error (list "Answer does not match type"
720 answer (widget-type widget))))) 750 answer (widget-type widget)))))
721 answer)) 751 answer))
722 752
1781 (setq button-begin (point-marker)) 1811 (setq button-begin (point-marker))
1782 (set-marker-insertion-type button-begin nil)) 1812 (set-marker-insertion-type button-begin nil))
1783 (lambda () ;?\] 1813 (lambda () ;?\]
1784 (setq button-end (point-marker)) 1814 (setq button-end (point-marker))
1785 (set-marker-insertion-type button-end nil)) 1815 (set-marker-insertion-type button-end nil))
1786 (lambda () ;?\{ 1816 (lambda () ;?\{
1787 (setq sample-begin (point))) 1817 (setq sample-begin (point)))
1788 (lambda () ;?\} 1818 (lambda () ;?\}
1789 (setq sample-end (point))) 1819 (setq sample-end (point)))
1790 (lambda () ;?n 1820 (lambda () ;?n
1791 (when (widget-get widget :indent) 1821 (when (widget-get widget :indent)
1792 (insert ?\n) 1822 (insert ?\n)
1793 (insert-char ?\ (widget-get widget :indent)))) 1823 (insert-char ?\ (widget-get widget :indent))))
1794 (lambda () ;?t 1824 (lambda () ;?t
1795 (let* ((tag (widget-get widget :tag)) 1825 (let* ((tag (widget-get widget :tag))
1999 ;; (let ((initial (if unbound 2029 ;; (let ((initial (if unbound
2000 ;; nil 2030 ;; nil
2001 ;; It would be nice if we could do a `(cons val 1)' here. 2031 ;; It would be nice if we could do a `(cons val 1)' here.
2002 ;; (prin1-to-string (custom-quote value)))))) 2032 ;; (prin1-to-string (custom-quote value))))))
2003 ;; XEmacs: make this use default VALUE. Need to check callers. 2033 ;; XEmacs: make this use default VALUE. Need to check callers.
2004 (eval-minibuffer prompt)) 2034 (eval-minibuffer (concat prompt ": ")))
2005 2035
2006 ;;; The `item' Widget. 2036 ;;; The `item' Widget.
2007 2037
2008 (define-widget 'item 'default 2038 (define-widget 'item 'default
2009 "Constant items for inclusion in other widgets." 2039 "Constant items for inclusion in other widgets."
2222 2252
2223 (defun widget-field-prompt-internal (widget prompt initial history) 2253 (defun widget-field-prompt-internal (widget prompt initial history)
2224 "Read string for WIDGET prompting with PROMPT. 2254 "Read string for WIDGET prompting with PROMPT.
2225 INITIAL is the initial input and HISTORY is a symbol containing 2255 INITIAL is the initial input and HISTORY is a symbol containing
2226 the earlier input." 2256 the earlier input."
2227 (read-string prompt initial history)) 2257 (read-string (concat prompt ": ") initial history))
2228 2258
2229 (defun widget-field-prompt-value (widget prompt value unbound) 2259 (defun widget-field-prompt-value (widget prompt value unbound)
2230 "Prompt for a string." 2260 "Prompt for a string."
2231 (widget-apply widget 2261 (widget-apply widget
2232 :value-to-external 2262 :value-to-external
2575 :menu-tag "checklist" 2605 :menu-tag "checklist"
2576 :greedy nil 2606 :greedy nil
2577 :value-create 'widget-checklist-value-create 2607 :value-create 'widget-checklist-value-create
2578 :value-delete 'widget-children-value-delete 2608 :value-delete 'widget-children-value-delete
2579 :value-get 'widget-checklist-value-get 2609 :value-get 'widget-checklist-value-get
2610 :prompt-value 'widget-checklist-prompt-value
2580 :validate 'widget-checklist-validate 2611 :validate 'widget-checklist-validate
2581 :match 'widget-checklist-match 2612 :match 'widget-checklist-match
2582 :match-inline 'widget-checklist-match-inline) 2613 :match-inline 'widget-checklist-match-inline)
2583 2614
2584 (defun widget-checklist-value-create (widget) 2615 (defun widget-checklist-value-create (widget)
2698 (setq child (car children) 2729 (setq child (car children)
2699 children (cdr children)) 2730 children (cdr children))
2700 (if (widget-value (widget-get child :button)) 2731 (if (widget-value (widget-get child :button))
2701 (setq result (append result (widget-apply child :value-inline))))) 2732 (setq result (append result (widget-apply child :value-inline)))))
2702 result)) 2733 result))
2734
2735 ;; #### FIXME: should handle default value some day -- dvl
2736 (defun widget-checklist-prompt-value (widget prompt value unbound)
2737 ;; Prompt for items to be selected, and the prompt for their value
2738 (let* ((args (widget-get widget :args))
2739 (choices (mapcar (lambda (elt)
2740 (cons (widget-get elt :tag) elt))
2741 args))
2742 (continue t)
2743 value)
2744 (while continue
2745 (setq continue (completing-read
2746 (concat (widget-prompt-spaceify prompt)
2747 "select [ret. when done]: ")
2748 choices nil t))
2749 (if (string= continue "")
2750 (setq continue nil)
2751 (push (widget-prompt-value (cdr (assoc continue choices))
2752 prompt nil t)
2753 value)))
2754 (nreverse value)))
2703 2755
2704 (defun widget-checklist-validate (widget) 2756 (defun widget-checklist-validate (widget)
2705 ;; Ticked children must be valid. 2757 ;; Ticked children must be valid.
2706 (let ((children (widget-get widget :children)) 2758 (let ((children (widget-get widget :children))
2707 child button found) 2759 child button found)
3114 :format "%v" 3166 :format "%v"
3115 :value-create 'widget-group-value-create 3167 :value-create 'widget-group-value-create
3116 :value-delete 'widget-children-value-delete 3168 :value-delete 'widget-children-value-delete
3117 :value-get 'widget-editable-list-value-get 3169 :value-get 'widget-editable-list-value-get
3118 :default-get 'widget-group-default-get 3170 :default-get 'widget-group-default-get
3171 :prompt-value 'widget-group-prompt-value
3119 :validate 'widget-children-validate 3172 :validate 'widget-children-validate
3120 :match 'widget-group-match 3173 :match 'widget-group-match
3121 :match-inline 'widget-group-match-inline) 3174 :match-inline 'widget-group-match-inline)
3122 3175
3123 (defun widget-group-value-create (widget) 3176 (defun widget-group-value-create (widget)
3143 (widget-put widget :children (nreverse children)))) 3196 (widget-put widget :children (nreverse children))))
3144 3197
3145 (defun widget-group-default-get (widget) 3198 (defun widget-group-default-get (widget)
3146 ;; Get the default of the components. 3199 ;; Get the default of the components.
3147 (mapcar 'widget-default-get (widget-get widget :args))) 3200 (mapcar 'widget-default-get (widget-get widget :args)))
3201
3202 (defun widget-group-prompt-value (widget prompt value unbound)
3203 ;; Prompt in turn for every component of the group.
3204 (let ((args (widget-get widget :args)))
3205 (widget-apply
3206 widget :value-to-external
3207 (if unbound
3208 (mapcar #'(lambda (arg)
3209 (widget-prompt-value
3210 arg
3211 (concat (widget-prompt-spaceify prompt)
3212 (widget-prompt arg nil ""))
3213 nil t))
3214 args)
3215 ;; If VALUE is bound, the situation is a bit more complex because we
3216 ;; have to split it into a list of default values for every child. Oh,
3217 ;; boy, do I miss 'cl here... -- dvl
3218 (let ((children args)
3219 (defaults (widget-apply widget
3220 :value-to-internal value))
3221 child default result)
3222 (while (setq child (pop children))
3223 (setq default (pop defaults))
3224 (push
3225 (widget-prompt-value
3226 child
3227 (concat (widget-prompt-spaceify prompt)
3228 (widget-prompt child nil ""))
3229 default) result))
3230 (nreverse result))))))
3148 3231
3149 (defun widget-group-match (widget values) 3232 (defun widget-group-match (widget values)
3150 ;; Match if the components match. 3233 ;; Match if the components match.
3151 (and (listp values) 3234 (and (listp values)
3152 (let ((match (widget-group-match-inline widget values))) 3235 (let ((match (widget-group-match-inline widget values)))
3376 (defvar widget-sexp-prompt-value-history nil 3459 (defvar widget-sexp-prompt-value-history nil
3377 "History of input to `widget-sexp-prompt-value'.") 3460 "History of input to `widget-sexp-prompt-value'.")
3378 3461
3379 (defun widget-sexp-prompt-value (widget prompt value unbound) 3462 (defun widget-sexp-prompt-value (widget prompt value unbound)
3380 ;; Read an arbitrary sexp. 3463 ;; Read an arbitrary sexp.
3381 (let ((found (read-string prompt 3464 (let ((found (read-string (concat prompt ": ")
3382 (if unbound nil (cons (prin1-to-string value) 0)) 3465 (if unbound nil (cons (prin1-to-string value) 0))
3383 (widget-get widget :prompt-history)))) 3466 (widget-get widget :prompt-history))))
3384 (save-excursion 3467 (save-excursion
3385 (let ((buffer (set-buffer (get-buffer-create " *Widget Scratch*")))) 3468 (let ((buffer (set-buffer (get-buffer-create " *Widget Scratch*"))))
3386 (erase-buffer) 3469 (erase-buffer)
3500 3583
3501 (defun widget-file-prompt-value (widget prompt value unbound) 3584 (defun widget-file-prompt-value (widget prompt value unbound)
3502 ;; Read file from minibuffer. 3585 ;; Read file from minibuffer.
3503 (abbreviate-file-name 3586 (abbreviate-file-name
3504 (if unbound 3587 (if unbound
3505 (read-file-name prompt) 3588 (read-file-name (concat prompt ": "))
3506 (let ((prompt2 (format "%s (default %s) " prompt value)) 3589 (let ((prompt2 (format "%s: (default %s) " prompt value))
3507 (dir (file-name-directory value)) 3590 (dir (file-name-directory value))
3508 (file (file-name-nondirectory value)) 3591 (file (file-name-nondirectory value))
3509 (must-match (widget-get widget :must-match))) 3592 (must-match (widget-get widget :must-match)))
3510 (read-file-name prompt2 dir nil must-match file))))) 3593 (read-file-name prompt2 dir nil must-match file)))))
3511 3594
3550 (intern value) 3633 (intern value)
3551 value))) 3634 value)))
3552 3635
3553 (defun widget-symbol-prompt-internal (widget prompt initial history) 3636 (defun widget-symbol-prompt-internal (widget prompt initial history)
3554 ;; Read file from minibuffer. 3637 ;; Read file from minibuffer.
3555 (let ((answer (completing-read prompt obarray 3638 (let ((answer (completing-read (concat prompt ": ") obarray
3556 (widget-get widget :prompt-match) 3639 (widget-get widget :prompt-match)
3557 nil initial history))) 3640 nil initial history)))
3558 (if (and (stringp answer) 3641 (if (and (stringp answer)
3559 (not (zerop (length answer)))) 3642 (not (zerop (length answer))))
3560 answer 3643 answer
3822 (defun widget-choice-prompt-value (widget prompt value unbound) 3905 (defun widget-choice-prompt-value (widget prompt value unbound)
3823 "Make a choice." 3906 "Make a choice."
3824 (let ((args (widget-get widget :args)) 3907 (let ((args (widget-get widget :args))
3825 (completion-ignore-case (widget-get widget :case-fold)) 3908 (completion-ignore-case (widget-get widget :case-fold))
3826 current choices old) 3909 current choices old)
3827 ;; Find the first arg that matches VALUE. 3910 ;; Find the first choice matching VALUE (if given):
3828 (let ((look args)) 3911 (unless unbound
3829 (while look 3912 (let ((look args))
3830 (if (widget-apply (car look) :match value) 3913 (while look
3831 (setq old (car look) 3914 (if (widget-apply (car look) :match value)
3832 look nil) 3915 (setq old (car look)
3833 (setq look (cdr look))))) 3916 look nil)
3834 ;; Find new choice. 3917 (setq look (cdr look)))))
3918 ;; If VALUE is invalid (it doesn't match any choice), discard it by
3919 ;; considering it unbound:
3920 (unless old
3921 (setq unbound t)))
3922 ;; Now offer the choice, providing the given default value when/where
3923 ;; appropriate:
3924 (while args
3925 (setq current (car args)
3926 args (cdr args))
3927 (setq choices
3928 (cons (cons (widget-apply current :menu-tag-get)
3929 current)
3930 choices)))
3835 (setq current 3931 (setq current
3836 (cond ((= (length args) 0) 3932 (let ((val (completing-read (concat prompt ": ") choices nil t
3837 nil) 3933 (when old
3838 ((= (length args) 1) 3934 (widget-apply old :menu-tag-get)))))
3839 (nth 0 args)) 3935 (if (stringp val) ;; #### is this really needed ? --dvl
3840 ((and (= (length args) 2) 3936 (let ((try (try-completion val choices)))
3841 (memq old args)) 3937 (when (stringp try) ;; #### and this ? --dvl
3842 (if (eq old (nth 0 args)) 3938 (setq val try))
3843 (nth 1 args) 3939 (cdr (assoc val choices)))
3844 (nth 0 args))) 3940 nil)))
3845 (t
3846 (while args
3847 (setq current (car args)
3848 args (cdr args))
3849 (setq choices
3850 (cons (cons (widget-apply current :menu-tag-get)
3851 current)
3852 choices)))
3853 (let ((val (completing-read prompt choices nil t)))
3854 (if (stringp val)
3855 (let ((try (try-completion val choices)))
3856 (when (stringp try)
3857 (setq val try))
3858 (cdr (assoc val choices)))
3859 nil)))))
3860 (if current 3941 (if current
3861 (widget-prompt-value current prompt nil t) 3942 (widget-prompt-value current
3862 value))) 3943 (concat (widget-prompt-spaceify prompt)
3944 (widget-get current :tag))
3945 (unless unbound
3946 (when (eq current old) value))
3947 (or unbound (not (eq current old))))
3948 (and (not unbound) value))))
3863 3949
3864 (define-widget 'radio 'radio-button-choice 3950 (define-widget 'radio 'radio-button-choice
3865 "A set widget, selecting exactly one from many. 3951 "A set widget, selecting exactly one from many.
3866 3952
3867 The parent of several `radio-button' widgets, one for each option." 3953 The parent of several `radio-button' widgets, one for each option."
3889 :on "on (non-nil)" 3975 :on "on (non-nil)"
3890 :off "off (nil)") 3976 :off "off (nil)")
3891 3977
3892 (defun widget-boolean-prompt-value (widget prompt value unbound) 3978 (defun widget-boolean-prompt-value (widget prompt value unbound)
3893 ;; Toggle a boolean. 3979 ;; Toggle a boolean.
3894 (y-or-n-p prompt)) 3980 (y-or-n-p (concat prompt ": ")))
3895 3981
3896 ;;; The `color' Widget. 3982 ;;; The `color' Widget.
3897 3983
3898 ;; Fixme: match 3984 ;; Fixme: match
3899 (define-widget 'color 'editable-field 3985 (define-widget 'color 'editable-field
4000 4086
4001 Here we attempt to define my-list as a choice of either the constant 4087 Here we attempt to define my-list as a choice of either the constant
4002 nil, or a cons-cell containing a sexp and my-lisp. This will not work 4088 nil, or a cons-cell containing a sexp and my-lisp. This will not work
4003 because the `choice' widget does not allow recursion. 4089 because the `choice' widget does not allow recursion.
4004 4090
4005 Using the `lazy' widget you can overcome this problem, as in this 4091 Using the `lazy' widget you can overcome this problem, as in this
4006 example: 4092 example:
4007 4093
4008 (define-widget 'sexp-list 'lazy 4094 (define-widget 'sexp-list 'lazy
4009 \"A list of sexps.\" 4095 \"A list of sexps.\"
4010 :tag \"Sexp list\" 4096 :tag \"Sexp list\"
4011 :type '(choice (const nil) (cons :value (nil) sexp sexp-list)))" 4097 :type '(choice (const nil) (cons :value (nil) sexp sexp-list)))"
4012 :format "%{%t%}: %v" 4098 :format "%{%t%}: %v"
4013 ;; We don't convert :type because we want to allow recursive 4099 ;; We don't convert :type because we want to allow recursive
4014 ;; datastructures. This is slow, so we should not create speed 4100 ;; datastructures. This is slow, so we should not create speed
4015 ;; critical widgets by deriving from this. 4101 ;; critical widgets by deriving from this.
4016 :convert-widget 'widget-value-convert-widget 4102 :convert-widget 'widget-value-convert-widget
4017 :value-create 'widget-type-value-create 4103 :value-create 'widget-type-value-create
4018 :value-delete 'widget-children-value-delete 4104 :value-delete 'widget-children-value-delete
4019 :value-get 'widget-child-value-get 4105 :value-get 'widget-child-value-get
4020 :value-inline 'widget-child-value-inline 4106 :value-inline 'widget-child-value-inline
4039 Store the newly created widget in the :children attribute. 4125 Store the newly created widget in the :children attribute.
4040 4126
4041 The value of the :type attribute should be an unconverted widget type." 4127 The value of the :type attribute should be an unconverted widget type."
4042 (let ((value (widget-get widget :value)) 4128 (let ((value (widget-get widget :value))
4043 (type (widget-get widget :type))) 4129 (type (widget-get widget :type)))
4044 (widget-put widget :children 4130 (widget-put widget :children
4045 (list (widget-create-child-value widget 4131 (list (widget-create-child-value widget
4046 (widget-convert type) 4132 (widget-convert type)
4047 value))))) 4133 value)))))
4048 4134
4049 (defun widget-type-default-get (widget) 4135 (defun widget-type-default-get (widget)
4050 "Get default value from the :type attribute of WIDGET. 4136 "Get default value from the :type attribute of WIDGET.
4051 4137
4052 The value of the :type attribute should be an unconverted widget type." 4138 The value of the :type attribute should be an unconverted widget type."