*** empty log message ***
[bpt/emacs.git] / lisp / wid-edit.el
CommitLineData
bfa6c260 1;;; wid-edit.el --- Functions for creating and using widgets -*-byte-compile-dynamic: t;-*-
d543e20b 2;;
125f1820 3;; Copyright (C) 1996,97,1999,2000,01,02,2003, 2004 Free Software Foundation, Inc.
d543e20b
PA
4;;
5;; Author: Per Abrahamsen <abraham@dina.kvl.dk>
a89a9d34 6;; Maintainer: FSF
d543e20b 7;; Keywords: extensions
d543e20b 8
ef3f635f
RS
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software; you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation; either version 2, or (at your option)
14;; any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
25
7fdbdbea
DL
26;;; Wishlist items (from widget.texi):
27
28;; * The `menu-choice' tag should be prettier, something like the
29;; abbreviated menus in Open Look.
30
31;; * Finish `:tab-order'.
32
33;; * Make indentation work with glyphs and proportional fonts.
34
35;; * Add commands to show overview of object and class hierarchies to
36;; the browser.
37
38;; * Find a way to disable mouse highlight for inactive widgets.
39
40;; * Find a way to make glyphs look inactive.
41
42;; * Add `key-binding' widget.
43
44;; * Add `widget' widget for editing widget specifications.
45
46;; * Find clean way to implement variable length list. See
47;; `TeX-printer-list' for an explanation.
48
49;; * `C-h' in `widget-prompt-value' should give type specific help.
50
51;; * A mailto widget. [This should work OK as a url-link if with
52;; browse-url-browser-function' set up appropriately.]
53
d543e20b
PA
54;;; Commentary:
55;;
56;; See `widget.el'.
57
58;;; Code:
59
d543e20b 60;;; Compatibility.
bfa6c260 61
4084d128
RS
62(defun widget-event-point (event)
63 "Character position of the end of event if that exists, or nil."
17030183 64 (posn-point (event-end event)))
4084d128 65
bfa6c260
DL
66(defun widget-button-release-event-p (event)
67 "Non-nil if EVENT is a mouse-button-release event object."
68 (and (eventp event)
69 (memq (event-basic-type event) '(mouse-1 mouse-2 mouse-3))
70 (or (memq 'click (event-modifiers event))
71 (memq 'drag (event-modifiers event)))))
d543e20b
PA
72
73;;; Customization.
74
75(defgroup widgets nil
76 "Customization support for the Widget Library."
77 :link '(custom-manual "(widget)Top")
62f44662 78 :link '(emacs-library-link :tag "Lisp File" "widget.el")
d543e20b
PA
79 :prefix "widget-"
80 :group 'extensions
d543e20b
PA
81 :group 'hypermedia)
82
8697863a
PA
83(defgroup widget-documentation nil
84 "Options controling the display of documentation strings."
85 :group 'widgets)
86
6aaedd12
PA
87(defgroup widget-faces nil
88 "Faces used by the widget library."
89 :group 'widgets
90 :group 'faces)
91
0b296dac 92(defvar widget-documentation-face 'widget-documentation-face
a89a9d34 93 "Face used for documentation strings in widgets.
0b296dac
RS
94This exists as a variable so it can be set locally in certain buffers.")
95
8697863a
PA
96(defface widget-documentation-face '((((class color)
97 (background dark))
98 (:foreground "lime green"))
99 (((class color)
100 (background light))
101 (:foreground "dark green"))
102 (t nil))
103 "Face used for documentation text."
104 :group 'widget-documentation
105 :group 'widget-faces)
106
2f477381 107(defvar widget-button-face 'widget-button-face
a89a9d34 108 "Face used for buttons in widgets.
2f477381
RS
109This exists as a variable so it can be set locally in certain buffers.")
110
e31c1fd5 111(defface widget-button-face '((t (:weight bold)))
d543e20b 112 "Face used for widget buttons."
6aaedd12 113 :group 'widget-faces)
d543e20b
PA
114
115(defcustom widget-mouse-face 'highlight
116 "Face used for widget buttons when the mouse is above them."
117 :type 'face
6aaedd12 118 :group 'widget-faces)
d543e20b 119
2670cf80
EZ
120;; TTY gets special definitions here and in the next defface, because
121;; the gray colors defined for other displays cause black text on a black
122;; background, at least on light-background TTYs.
123(defface widget-field-face '((((type tty))
7e784293
MB
124 :background "yellow3"
125 :foreground "black")
2670cf80 126 (((class grayscale color)
d543e20b 127 (background light))
7e784293 128 :background "gray85")
d543e20b
PA
129 (((class grayscale color)
130 (background dark))
7e784293 131 :background "dim gray")
bfa6c260 132 (t
7e784293 133 :slant italic))
d543e20b 134 "Face used for editable fields."
6aaedd12 135 :group 'widget-faces)
d543e20b 136
2670cf80 137(defface widget-single-line-field-face '((((type tty))
7e784293
MB
138 :background "green3"
139 :foreground "black")
2670cf80 140 (((class grayscale color)
c953515e 141 (background light))
7e784293 142 :background "gray85")
c953515e
PA
143 (((class grayscale color)
144 (background dark))
7e784293 145 :background "dim gray")
bfa6c260 146 (t
7e784293 147 :slant italic))
c953515e
PA
148 "Face used for editable fields spanning only a single line."
149 :group 'widget-faces)
150
33eae9c0
RS
151;;; This causes display-table to be loaded, and not usefully.
152;;;(defvar widget-single-line-display-table
153;;; (let ((table (make-display-table)))
154;;; (aset table 9 "^I")
155;;; (aset table 10 "^J")
156;;; table)
157;;; "Display table used for single-line editable fields.")
158
159;;;(when (fboundp 'set-face-display-table)
160;;; (set-face-display-table 'widget-single-line-field-face
161;;; widget-single-line-display-table))
c953515e 162
d543e20b
PA
163;;; Utility functions.
164;;
165;; These are not really widget specific.
166
d543e20b 167(defun widget-princ-to-string (object)
bfa6c260
DL
168 "Return string representation of OBJECT, any Lisp object.
169No quoting characters are used; no delimiters are printed around
170the contents of strings."
171 (with-output-to-string
172 (princ object)))
d543e20b
PA
173
174(defun widget-clear-undo ()
175 "Clear all undo information."
176 (buffer-disable-undo (current-buffer))
177 (buffer-enable-undo))
178
a3c88c59
PA
179(defcustom widget-menu-max-size 40
180 "Largest number of items allowed in a popup-menu.
181Larger menus are read through the minibuffer."
182 :group 'widgets
183 :type 'integer)
184
703c3a11
KH
185(defcustom widget-menu-max-shortcuts 40
186 "Largest number of items for which it works to choose one with a character.
187For a larger number of items, the minibuffer is used."
188 :group 'widgets
189 :type 'integer)
190
191(defcustom widget-menu-minibuffer-flag nil
0b296dac
RS
192 "*Control how to ask for a choice from the keyboard.
193Non-nil means use the minibuffer;
194nil means read a single character."
195 :group 'widgets
196 :type 'boolean)
197
d543e20b
PA
198(defun widget-choose (title items &optional event)
199 "Choose an item from a list.
200
201First argument TITLE is the name of the list.
77339a6e 202Second argument ITEMS is a list whose members are either
6d528fc5
PA
203 (NAME . VALUE), to indicate selectable items, or just strings to
204 indicate unselectable items.
d543e20b
PA
205Optional third argument EVENT is an input event.
206
207The user is asked to choose between each NAME from the items alist,
208and the VALUE of the chosen element will be returned. If EVENT is a
209mouse event, and the number of elements in items is less than
210`widget-menu-max-size', a popup menu will be used, otherwise the
211minibuffer."
212 (cond ((and (< (length items) widget-menu-max-size)
fb55ff10 213 event (display-popup-menus-p))
7fdbdbea 214 ;; Mouse click.
d543e20b
PA
215 (x-popup-menu event
216 (list title (cons "" items))))
703c3a11
KH
217 ((or widget-menu-minibuffer-flag
218 (> (length items) widget-menu-max-shortcuts))
0b296dac 219 ;; Read the choice of name from the minibuffer.
e5dfabb4 220 (setq items (widget-remove-if 'stringp items))
d543e20b
PA
221 (let ((val (completing-read (concat title ": ") items nil t)))
222 (if (stringp val)
223 (let ((try (try-completion val items)))
224 (when (stringp try)
225 (setq val try))
bfa6c260 226 (cdr (assoc val items))))))
0b296dac
RS
227 (t
228 ;; Construct a menu of the choices
229 ;; and then use it for prompting for a single character.
7fdbdbea
DL
230 (let* ((overriding-terminal-local-map (make-sparse-keymap))
231 (next-digit ?0)
232 map choice some-choice-enabled value)
0b296dac
RS
233 ;; Define SPC as a prefix char to get to this menu.
234 (define-key overriding-terminal-local-map " "
235 (setq map (make-sparse-keymap title)))
7c0a9c8f 236 (with-current-buffer (get-buffer-create " widget-choose")
d4b8422f
RS
237 (erase-buffer)
238 (insert "Available choices:\n\n")
239 (while items
240 (setq choice (car items) items (cdr items))
241 (if (consp choice)
242 (let* ((name (car choice))
243 (function (cdr choice)))
244 (insert (format "%c = %s\n" next-digit name))
cfc198e5
RS
245 (define-key map (vector next-digit) function)
246 (setq some-choice-enabled t)))
d4b8422f
RS
247 ;; Allocate digits to disabled alternatives
248 ;; so that the digit of a given alternative never varies.
249 (setq next-digit (1+ next-digit)))
250 (insert "\nC-g = Quit"))
cfc198e5
RS
251 (or some-choice-enabled
252 (error "None of the choices is currently meaningful"))
d4b8422f 253 (define-key map [?\C-g] 'keyboard-quit)
0b296dac 254 (define-key map [t] 'keyboard-quit)
4d52438e
KH
255 (define-key map [?\M-\C-v] 'scroll-other-window)
256 (define-key map [?\M--] 'negative-argument)
0b296dac 257 (setcdr map (nreverse (cdr map)))
0b296dac
RS
258 ;; Read a char with the menu, and return the result
259 ;; that corresponds to it.
d4b8422f 260 (save-window-excursion
4d52438e 261 (let ((buf (get-buffer " widget-choose")))
d970106b 262 (fit-window-to-buffer (display-buffer buf))
4d52438e
KH
263 (let ((cursor-in-echo-area t)
264 keys
265 (char 0)
266 (arg 1))
267 (while (not (or (and (>= char ?0) (< char next-digit))
268 (eq value 'keyboard-quit)))
269 ;; Unread a SPC to lead to our new menu.
b5cb36ac 270 (setq unread-command-events (cons ?\ unread-command-events))
4d52438e 271 (setq keys (read-key-sequence title))
bfa6c260
DL
272 (setq value
273 (lookup-key overriding-terminal-local-map keys t)
4d52438e
KH
274 char (string-to-char (substring keys 1)))
275 (cond ((eq value 'scroll-other-window)
bfa6c260
DL
276 (let ((minibuffer-scroll-window
277 (get-buffer-window buf)))
4d52438e 278 (if (> 0 arg)
bfa6c260
DL
279 (scroll-other-window-down
280 (window-height minibuffer-scroll-window))
4d52438e
KH
281 (scroll-other-window))
282 (setq arg 1)))
283 ((eq value 'negative-argument)
284 (setq arg -1))
285 (t
286 (setq arg 1)))))))
0b296dac
RS
287 (when (eq value 'keyboard-quit)
288 (error "Canceled"))
289 value))))
d543e20b 290
e5dfabb4
RS
291(defun widget-remove-if (predictate list)
292 (let (result (tail list))
293 (while tail
294 (or (funcall predictate (car tail))
295 (setq result (cons (car tail) result)))
296 (setq tail (cdr tail)))
297 (nreverse result)))
298
d543e20b 299;;; Widget text specifications.
77339a6e 300;;
bfa6c260 301;; These functions are for specifying text properties.
d543e20b 302
7c0a9c8f
SM
303;; We can set it to nil now that get_local_map uses get_pos_property.
304(defconst widget-field-add-space nil
8697863a 305 "Non-nil means add extra space at the end of editable text fields.
8697863a 306If you don't add the space, it will become impossible to edit a zero
bfa6c260 307size field.")
8697863a 308
bfa6c260 309(defvar widget-field-use-before-change t
da5ec617 310 "Non-nil means use `before-change-functions' to track editable fields.
bfa6c260 311This enables the use of undo, but doesn't work on Emacs 19.34 and earlier.
da5ec617 312Using before hooks also means that the :notify function can't know the
bfa6c260 313new value.")
da5ec617 314
d543e20b 315(defun widget-specify-field (widget from to)
0a3a0b56 316 "Specify editable button for WIDGET between FROM and TO."
0ce5b5d5
PA
317 ;; Terminating space is not part of the field, but necessary in
318 ;; order for local-map to work. Remove next sexp if local-map works
319 ;; at the end of the overlay.
320 (save-excursion
321 (goto-char to)
62f44662
PA
322 (cond ((null (widget-get widget :size))
323 (forward-char 1))
324 (widget-field-add-space
325 (insert-and-inherit " ")))
0ce5b5d5 326 (setq to (point)))
a850ac03
MB
327 (let ((keymap (widget-get widget :keymap))
328 (face (or (widget-get widget :value-face) 'widget-field-face))
329 (help-echo (widget-get widget :help-echo))
330 (rear-sticky
331 (or (not widget-field-add-space) (widget-get widget :size))))
233d5cde 332 (if (functionp help-echo)
77339a6e 333 (setq help-echo 'widget-mouse-help))
a850ac03
MB
334 (when (= (char-before to) ?\n)
335 ;; When the last character in the field is a newline, we want to
336 ;; give it a `field' char-property of `boundary', which helps the
337 ;; C-n/C-p act more naturally when entering/leaving the field. We
338 ;; do this by making a small secondary overlay to contain just that
339 ;; one character.
340 (let ((overlay (make-overlay (1- to) to nil t nil)))
341 (overlay-put overlay 'field 'boundary)
5701edda
DL
342 ;; Use `local-map' here, not `keymap', so that normal editing
343 ;; works in the field when, say, Custom uses `suppress-keymap'.
344 (overlay-put overlay 'local-map keymap)
a850ac03
MB
345 (overlay-put overlay 'face face)
346 (overlay-put overlay 'help-echo help-echo))
347 (setq to (1- to))
348 (setq rear-sticky t))
349 (let ((overlay (make-overlay from to nil nil rear-sticky)))
350 (widget-put widget :field-overlay overlay)
351 ;;(overlay-put overlay 'detachable nil)
352 (overlay-put overlay 'field widget)
5701edda 353 (overlay-put overlay 'local-map keymap)
a850ac03
MB
354 (overlay-put overlay 'face face)
355 (overlay-put overlay 'help-echo help-echo)))
e9367b9c
RS
356 (widget-specify-secret widget))
357
358(defun widget-specify-secret (field)
359 "Replace text in FIELD with value of `:secret', if non-nil."
360 (let ((secret (widget-get field :secret))
361 (size (widget-get field :size)))
362 (when secret
363 (let ((begin (widget-field-start field))
364 (end (widget-field-end field)))
bfa6c260 365 (when size
e9367b9c
RS
366 (while (and (> end begin)
367 (eq (char-after (1- end)) ?\ ))
368 (setq end (1- end))))
369 (while (< begin end)
370 (let ((old (char-after begin)))
371 (unless (eq old secret)
372 (subst-char-in-region begin (1+ begin) old secret)
373 (put-text-property begin (1+ begin) 'secret old))
374 (setq begin (1+ begin))))))))
d543e20b 375
d543e20b 376(defun widget-specify-button (widget from to)
0a3a0b56 377 "Specify button for WIDGET between FROM and TO."
233d5cde
DL
378 (let ((overlay (make-overlay from to nil t nil))
379 (help-echo (widget-get widget :help-echo)))
0a3a0b56 380 (widget-put widget :button-overlay overlay)
233d5cde
DL
381 (if (functionp help-echo)
382 (setq help-echo 'widget-mouse-help))
0a3a0b56 383 (overlay-put overlay 'button widget)
7fdbdbea 384 (overlay-put overlay 'keymap (widget-get widget :keymap))
f24485f1 385 (overlay-put overlay 'evaporate t)
bfa6c260
DL
386 ;; We want to avoid the face with image buttons.
387 (unless (widget-get widget :suppress-face)
0e726aa5
KS
388 (overlay-put overlay 'face (widget-apply widget :button-face-get)))
389 (overlay-put overlay 'pointer 'hand)
233d5cde
DL
390 (overlay-put overlay 'help-echo help-echo)))
391
392(defun widget-mouse-help (window overlay point)
393 "Help-echo callback for widgets whose :help-echo is a function."
394 (with-current-buffer (overlay-buffer overlay)
395 (let* ((widget (widget-at (overlay-start overlay)))
396 (help-echo (if widget (widget-get widget :help-echo))))
397 (if (functionp help-echo)
398 (funcall help-echo widget)
399 help-echo))))
d543e20b 400
d543e20b 401(defun widget-specify-sample (widget from to)
bfa6c260 402 "Specify sample for WIDGET between FROM and TO."
7fdbdbea
DL
403 (let ((overlay (make-overlay from to nil t nil)))
404 (overlay-put overlay 'face (widget-apply widget :sample-face-get))
f24485f1 405 (overlay-put overlay 'evaporate t)
0f648ca2
PA
406 (widget-put widget :sample-overlay overlay)))
407
d543e20b 408(defun widget-specify-doc (widget from to)
bfa6c260 409 "Specify documentation for WIDGET between FROM and TO."
4ee1cf9f
PA
410 (let ((overlay (make-overlay from to nil t nil)))
411 (overlay-put overlay 'widget-doc widget)
412 (overlay-put overlay 'face widget-documentation-face)
f24485f1 413 (overlay-put overlay 'evaporate t)
4ee1cf9f 414 (widget-put widget :doc-overlay overlay)))
d543e20b
PA
415
416(defmacro widget-specify-insert (&rest form)
bfa6c260
DL
417 "Execute FORM without inheriting any text properties."
418 `(save-restriction
419 (let ((inhibit-read-only t)
407e43be
SM
420 (inhibit-modification-hooks t))
421 (narrow-to-region (point) (point))
422 (prog1 (progn ,@form)
423 (goto-char (point-max))))))
d543e20b
PA
424
425(defface widget-inactive-face '((((class grayscale color)
426 (background dark))
427 (:foreground "light gray"))
428 (((class grayscale color)
429 (background light))
83427907 430 (:foreground "dim gray"))
bfa6c260 431 (t
e31c1fd5 432 (:slant italic)))
d543e20b 433 "Face used for inactive widgets."
6aaedd12 434 :group 'widget-faces)
d543e20b
PA
435
436(defun widget-specify-inactive (widget from to)
437 "Make WIDGET inactive for user modifications."
438 (unless (widget-get widget :inactive)
439 (let ((overlay (make-overlay from to nil t nil)))
440 (overlay-put overlay 'face 'widget-inactive-face)
6aaedd12
PA
441 ;; This is disabled, as it makes the mouse cursor change shape.
442 ;; (overlay-put overlay 'mouse-face 'widget-inactive-face)
6d528fc5
PA
443 (overlay-put overlay 'evaporate t)
444 (overlay-put overlay 'priority 100)
a89a9d34 445 (overlay-put overlay 'modification-hooks '(widget-overlay-inactive))
d543e20b
PA
446 (widget-put widget :inactive overlay))))
447
448(defun widget-overlay-inactive (&rest junk)
449 "Ignoring the arguments, signal an error."
450 (unless inhibit-read-only
a89a9d34 451 (error "The widget here is not active")))
d543e20b
PA
452
453
454(defun widget-specify-active (widget)
455 "Make WIDGET active for user modifications."
456 (let ((inactive (widget-get widget :inactive)))
457 (when inactive
458 (delete-overlay inactive)
459 (widget-put widget :inactive nil))))
460
461;;; Widget Properties.
462
463(defsubst widget-type (widget)
464 "Return the type of WIDGET, a symbol."
465 (car widget))
466
0e520006
PA
467;;;###autoload
468(defun widgetp (widget)
469 "Return non-nil iff WIDGET is a widget."
470 (if (symbolp widget)
471 (get widget 'widget-type)
472 (and (consp widget)
bbc562cc
PA
473 (symbolp (car widget))
474 (get (car widget) 'widget-type))))
0e520006 475
944c91b6
PA
476(defun widget-get-indirect (widget property)
477 "In WIDGET, get the value of PROPERTY.
bfa6c260 478If the value is a symbol, return its binding.
944c91b6
PA
479Otherwise, just return the value."
480 (let ((value (widget-get widget property)))
481 (if (symbolp value)
482 (symbol-value value)
483 value)))
484
d543e20b
PA
485(defun widget-member (widget property)
486 "Non-nil iff there is a definition in WIDGET for PROPERTY."
ff83e968 487 (cond ((plist-member (cdr widget) property)
d543e20b
PA
488 t)
489 ((car widget)
490 (widget-member (get (car widget) 'widget-type) property))
491 (t nil)))
492
d543e20b
PA
493(defun widget-value (widget)
494 "Extract the current value of WIDGET."
495 (widget-apply widget
496 :value-to-external (widget-apply widget :value-get)))
497
498(defun widget-value-set (widget value)
499 "Set the current value of WIDGET to VALUE."
500 (widget-apply widget
501 :value-set (widget-apply widget
502 :value-to-internal value)))
503
783824f5 504(defun widget-default-get (widget)
4c2f559e 505 "Extract the default external value of WIDGET."
77339a6e 506 (widget-apply widget :value-to-external
4c2f559e
PA
507 (or (widget-get widget :value)
508 (widget-apply widget :default-get))))
783824f5 509
d543e20b 510(defun widget-match-inline (widget vals)
a89a9d34 511 "In WIDGET, match the start of VALS."
d543e20b
PA
512 (cond ((widget-get widget :inline)
513 (widget-apply widget :match-inline vals))
b2aeee30 514 ((and (listp vals)
d543e20b
PA
515 (widget-apply widget :match (car vals)))
516 (cons (list (car vals)) (cdr vals)))
517 (t nil)))
518
519(defun widget-apply-action (widget &optional event)
520 "Apply :action in WIDGET in response to EVENT."
8697863a
PA
521 (if (widget-apply widget :active)
522 (widget-apply widget :action event)
523 (error "Attempt to perform action on inactive widget")))
6d528fc5 524
a3c88c59
PA
525;;; Helper functions.
526;;
527;; These are widget specific.
528
529;;;###autoload
530(defun widget-prompt-value (widget prompt &optional value unbound)
531 "Prompt for a value matching WIDGET, using PROMPT.
532The current value is assumed to be VALUE, unless UNBOUND is non-nil."
533 (unless (listp widget)
534 (setq widget (list widget)))
535 (setq prompt (format "[%s] %s" (widget-type widget) prompt))
536 (setq widget (widget-convert widget))
537 (let ((answer (widget-apply widget :prompt-value prompt value unbound)))
538 (unless (widget-apply widget :match answer)
bfa6c260 539 (error "Value does not match %S type" (car widget)))
a3c88c59
PA
540 answer))
541
542(defun widget-get-sibling (widget)
543 "Get the item WIDGET is assumed to toggle.
544This is only meaningful for radio buttons or checkboxes in a list."
7fdbdbea 545 (let* ((children (widget-get (widget-get widget :parent) :children))
a3c88c59
PA
546 child)
547 (catch 'child
548 (while children
549 (setq child (car children)
550 children (cdr children))
551 (when (eq (widget-get child :button) widget)
552 (throw 'child child)))
553 nil)))
554
6aaedd12
PA
555(defun widget-map-buttons (function &optional buffer maparg)
556 "Map FUNCTION over the buttons in BUFFER.
557FUNCTION is called with the arguments WIDGET and MAPARG.
558
559If FUNCTION returns non-nil, the walk is cancelled.
560
561The arguments MAPARG, and BUFFER default to nil and (current-buffer),
562respectively."
563 (let ((cur (point-min))
564 (widget nil)
6aaedd12 565 (overlays (if buffer
7c0a9c8f 566 (with-current-buffer buffer (overlay-lists))
6aaedd12
PA
567 (overlay-lists))))
568 (setq overlays (append (car overlays) (cdr overlays)))
569 (while (setq cur (pop overlays))
570 (setq widget (overlay-get cur 'button))
571 (if (and widget (funcall function widget maparg))
572 (setq overlays nil)))))
573
bfa6c260 574;;; Images.
d543e20b 575
bfa6c260
DL
576(defcustom widget-image-directory (file-name-as-directory
577 (expand-file-name "custom" data-directory))
578 "Where widget button images are located.
d543e20b 579If this variable is nil, widget will try to locate the directory
25ac13b5 580automatically."
d543e20b
PA
581 :group 'widgets
582 :type 'directory)
583
bfa6c260
DL
584(defcustom widget-image-enable t
585 "If non nil, use image buttons in widgets when available."
586 :version "21.1"
d543e20b
PA
587 :group 'widgets
588 :type 'boolean)
589
25ac13b5
PA
590(defcustom widget-image-conversion
591 '((xpm ".xpm") (gif ".gif") (png ".png") (jpeg ".jpg" ".jpeg")
592 (xbm ".xbm"))
593 "Conversion alist from image formats to file name suffixes."
594 :group 'widgets
595 :type '(repeat (cons :format "%v"
596 (symbol :tag "Image Format" unknown)
597 (repeat :tag "Suffixes"
598 (string :format "%v")))))
599
bfa6c260
DL
600(defun widget-image-find (image)
601 "Create a graphical button from IMAGE.
602IMAGE should either already be an image, or be a file name sans
3acab5ef 603extension (xpm, xbm, gif, jpg, or png) located in
bfa6c260
DL
604`widget-image-directory' or otherwise where `find-image' will find it."
605 (cond ((not (and image widget-image-enable (display-graphic-p)))
606 ;; We don't want or can't use images.
3acab5ef 607 nil)
bfa6c260
DL
608 ((and (consp image)
609 (eq 'image (car image)))
610 ;; Already an image spec. Use it.
3acab5ef 611 image)
25ac13b5
PA
612 ((stringp image)
613 ;; A string. Look it up in relevant directories.
bfa6c260 614 (let* ((load-path (cons widget-image-directory load-path))
bfa6c260
DL
615 specs)
616 (dolist (elt widget-image-conversion)
617 (dolist (ext (cdr elt))
618 (push (list :type (car elt) :file (concat image ext)) specs)))
619 (setq specs (nreverse specs))
620 (find-image specs)))
d543e20b 621 (t
25ac13b5 622 ;; Oh well.
3acab5ef
PA
623 nil)))
624
bfa6c260
DL
625(defvar widget-button-pressed-face 'widget-button-pressed-face
626 "Face used for pressed buttons in widgets.
627This exists as a variable so it can be set locally in certain
628buffers.")
629
630(defun widget-image-insert (widget tag image &optional down inactive)
3acab5ef 631 "In WIDGET, insert the text TAG or, if supported, IMAGE.
bfa6c260
DL
632IMAGE should either be an image or an image file name sans extension
633\(xpm, xbm, gif, jpg, or png) located in `widget-image-directory'.
634
635Optional arguments DOWN and INACTIVE are used instead of IMAGE when the
636button is pressed or inactive, respectively. These are currently ignored."
637 (if (and (display-graphic-p)
638 (setq image (widget-image-find image)))
639 (progn (widget-put widget :suppress-face t)
640 (insert-image image
641 (propertize
642 tag 'mouse-face widget-button-pressed-face)))
643 (insert tag)))
d543e20b 644
25ac13b5
PA
645;;; Buttons.
646
647(defgroup widget-button nil
648 "The look of various kinds of buttons."
649 :group 'widgets)
650
651(defcustom widget-button-prefix ""
652 "String used as prefix for buttons."
653 :type 'string
3acab5ef 654 :group 'widget-button)
25ac13b5
PA
655
656(defcustom widget-button-suffix ""
657 "String used as suffix for buttons."
658 :type 'string
3acab5ef 659 :group 'widget-button)
25ac13b5 660
d543e20b
PA
661;;; Creating Widgets.
662
663;;;###autoload
664(defun widget-create (type &rest args)
bfa6c260 665 "Create widget of TYPE.
d543e20b
PA
666The optional ARGS are additional keyword arguments."
667 (let ((widget (apply 'widget-convert type args)))
668 (widget-apply widget :create)
669 widget))
670
671(defun widget-create-child-and-convert (parent type &rest args)
672 "As part of the widget PARENT, create a child widget TYPE.
673The child is converted, using the keyword arguments ARGS."
674 (let ((widget (apply 'widget-convert type args)))
675 (widget-put widget :parent parent)
676 (unless (widget-get widget :indent)
677 (widget-put widget :indent (+ (or (widget-get parent :indent) 0)
678 (or (widget-get widget :extra-offset) 0)
679 (widget-get parent :offset))))
680 (widget-apply widget :create)
681 widget))
682
683(defun widget-create-child (parent type)
684 "Create widget of TYPE."
4c2f559e 685 (let ((widget (widget-copy type)))
d543e20b
PA
686 (widget-put widget :parent parent)
687 (unless (widget-get widget :indent)
688 (widget-put widget :indent (+ (or (widget-get parent :indent) 0)
689 (or (widget-get widget :extra-offset) 0)
690 (widget-get parent :offset))))
691 (widget-apply widget :create)
692 widget))
693
694(defun widget-create-child-value (parent type value)
695 "Create widget of TYPE with value VALUE."
4c2f559e 696 (let ((widget (widget-copy type)))
d543e20b
PA
697 (widget-put widget :value (widget-apply widget :value-to-internal value))
698 (widget-put widget :parent parent)
699 (unless (widget-get widget :indent)
700 (widget-put widget :indent (+ (or (widget-get parent :indent) 0)
701 (or (widget-get widget :extra-offset) 0)
702 (widget-get parent :offset))))
703 (widget-apply widget :create)
704 widget))
705
706;;;###autoload
707(defun widget-delete (widget)
708 "Delete WIDGET."
709 (widget-apply widget :delete))
710
4c2f559e
PA
711(defun widget-copy (widget)
712 "Make a deep copy of WIDGET."
713 (widget-apply (copy-sequence widget) :copy))
714
d543e20b 715(defun widget-convert (type &rest args)
bfa6c260 716 "Convert TYPE to a widget without inserting it in the buffer.
d543e20b
PA
717The optional ARGS are additional keyword arguments."
718 ;; Don't touch the type.
bfa6c260 719 (let* ((widget (if (symbolp type)
d543e20b 720 (list type)
ef3f635f 721 (copy-sequence type)))
d543e20b 722 (current widget)
ab6a3668 723 done
d543e20b
PA
724 (keys args))
725 ;; First set the :args keyword.
726 (while (cdr current) ;Look in the type.
ab6a3668
RS
727 (if (and (keywordp (cadr current))
728 ;; If the last element is a keyword,
729 ;; it is still the :args element,
730 ;; even though it is a keyword.
731 (cddr current))
732 (if (eq (cadr current) :args)
733 ;; If :args is explicitly specified, obey it.
734 (setq current nil)
735 ;; Some other irrelevant keyword.
736 (setq current (cdr (cdr current))))
7fdbdbea
DL
737 (setcdr current (list :args (cdr current)))
738 (setq current nil)))
ab6a3668
RS
739 (while (and args (not done)) ;Look in ARGS.
740 (cond ((eq (car args) :args)
741 ;; Handle explicit specification of :args.
742 (setq args (cadr args)
743 done t))
744 ((keywordp (car args))
745 (setq args (cddr args)))
746 (t (setq done t))))
747 (when done
748 (widget-put widget :args args))
d543e20b
PA
749 ;; Then Convert the widget.
750 (setq type widget)
751 (while type
752 (let ((convert-widget (plist-get (cdr type) :convert-widget)))
753 (if convert-widget
754 (setq widget (funcall convert-widget widget))))
755 (setq type (get (car type) 'widget-type)))
756 ;; Finally set the keyword args.
bfa6c260 757 (while keys
d543e20b 758 (let ((next (nth 0 keys)))
bfa6c260
DL
759 (if (keywordp next)
760 (progn
d543e20b
PA
761 (widget-put widget next (nth 1 keys))
762 (setq keys (nthcdr 2 keys)))
763 (setq keys nil))))
764 ;; Convert the :value to internal format.
765 (if (widget-member widget :value)
7fdbdbea
DL
766 (widget-put widget
767 :value (widget-apply widget
768 :value-to-internal
769 (widget-get widget :value))))
d543e20b
PA
770 ;; Return the newly create widget.
771 widget))
772
0e520006 773;;;###autoload
d543e20b 774(defun widget-insert (&rest args)
7fdbdbea 775 "Call `insert' with ARGS even if surrounding text is read only."
d543e20b 776 (let ((inhibit-read-only t)
7fdbdbea 777 (inhibit-modification-hooks t))
4ee1cf9f 778 (apply 'insert args)))
d543e20b 779
8697863a
PA
780(defun widget-convert-text (type from to
781 &optional button-from button-to
782 &rest args)
6aaedd12 783 "Return a widget of type TYPE with endpoint FROM TO.
8697863a 784Optional ARGS are extra keyword arguments for TYPE.
6aaedd12
PA
785and TO will be used as the widgets end points. If optional arguments
786BUTTON-FROM and BUTTON-TO are given, these will be used as the widgets
8697863a
PA
787button end points.
788Optional ARGS are extra keyword arguments for TYPE."
789 (let ((widget (apply 'widget-convert type :delete 'widget-leave-text args))
6aaedd12 790 (from (copy-marker from))
2ff864e0 791 (to (copy-marker to)))
6aaedd12
PA
792 (set-marker-insertion-type from t)
793 (set-marker-insertion-type to nil)
794 (widget-put widget :from from)
795 (widget-put widget :to to)
796 (when button-from
797 (widget-specify-button widget button-from button-to))
798 widget))
799
8697863a 800(defun widget-convert-button (type from to &rest args)
6aaedd12 801 "Return a widget of type TYPE with endpoint FROM TO.
8697863a 802Optional ARGS are extra keyword arguments for TYPE.
6aaedd12
PA
803No text will be inserted to the buffer, instead the text between FROM
804and TO will be used as the widgets end points, as well as the widgets
805button end points."
8697863a
PA
806 (apply 'widget-convert-text type from to from to args))
807
808(defun widget-leave-text (widget)
809 "Remove markers and overlays from WIDGET and its children."
7fdbdbea 810 (let ((button (widget-get widget :button-overlay))
0f648ca2 811 (sample (widget-get widget :sample-overlay))
4ee1cf9f 812 (doc (widget-get widget :doc-overlay))
7fdbdbea
DL
813 (field (widget-get widget :field-overlay)))
814 (set-marker (widget-get widget :from) nil)
815 (set-marker (widget-get widget :to) nil)
208920be
PA
816 (when button
817 (delete-overlay button))
0f648ca2
PA
818 (when sample
819 (delete-overlay sample))
4ee1cf9f
PA
820 (when doc
821 (delete-overlay doc))
208920be
PA
822 (when field
823 (delete-overlay field))
7fdbdbea 824 (mapc 'widget-leave-text (widget-get widget :children))))
6aaedd12 825
d543e20b
PA
826;;; Keymap and Commands.
827
0e520006 828;;;###autoload
bfa6c260
DL
829(defvar widget-keymap
830 (let ((map (make-sparse-keymap)))
831 (define-key map "\t" 'widget-forward)
832 (define-key map [(shift tab)] 'widget-backward)
833 (define-key map [backtab] 'widget-backward)
834 (define-key map [down-mouse-2] 'widget-button-click)
835 (define-key map "\C-m" 'widget-button-press)
836 map)
d543e20b
PA
837 "Keymap containing useful binding for buffers containing widgets.
838Recommended as a parent keymap for modes using widgets.")
839
d543e20b 840(defvar widget-global-map global-map
f4b020f6 841 "Keymap used for events a widget does not handle itself.")
d543e20b
PA
842(make-variable-buffer-local 'widget-global-map)
843
bfa6c260
DL
844(defvar widget-field-keymap
845 (let ((map (copy-keymap widget-keymap)))
bfa6c260
DL
846 (define-key map "\C-k" 'widget-kill-line)
847 (define-key map "\M-\t" 'widget-complete)
848 (define-key map "\C-m" 'widget-field-activate)
8b9a0f45 849 ;; Since the widget code uses a `field' property to identify fields,
0697c662 850 ;; ordinary beginning-of-line does the right thing.
8b9a0f45 851 ;; (define-key map "\C-a" 'widget-beginning-of-line)
0697c662 852 (define-key map "\C-e" 'widget-end-of-line)
bfa6c260 853 map)
d543e20b
PA
854 "Keymap used inside an editable field.")
855
bfa6c260
DL
856(defvar widget-text-keymap
857 (let ((map (copy-keymap widget-keymap)))
8b9a0f45 858 ;; Since the widget code uses a `field' property to identify fields,
0697c662 859 ;; ordinary beginning-of-line does the right thing.
8b9a0f45 860 ;; (define-key map "\C-a" 'widget-beginning-of-line)
0697c662 861 (define-key map "\C-e" 'widget-end-of-line)
bfa6c260 862 map)
d543e20b
PA
863 "Keymap used inside a text field.")
864
d543e20b 865(defun widget-field-activate (pos &optional event)
e2896b22 866 "Invoke the editable field at point."
d543e20b 867 (interactive "@d")
a850ac03 868 (let ((field (widget-field-at pos)))
d543e20b
PA
869 (if field
870 (widget-apply-action field event)
871 (call-interactively
872 (lookup-key widget-global-map (this-command-keys))))))
873
bfa6c260 874(defface widget-button-pressed-face
a3c88c59
PA
875 '((((class color))
876 (:foreground "red"))
877 (t
e31c1fd5 878 (:weight bold :underline t)))
a3c88c59 879 "Face used for pressed buttons."
6aaedd12 880 :group 'widget-faces)
a3c88c59 881
d543e20b 882(defun widget-button-click (event)
bfa6c260 883 "Invoke the button that the mouse is pointing at."
bc3420db 884 (interactive "e")
bfa6c260 885 (if (widget-event-point event)
eaaf76b6 886 (let* ((pos (widget-event-point event))
bc3420db 887 (start (event-start event))
77339a6e 888 (button (get-char-property
bc3420db
RS
889 pos 'button (and (windowp (posn-window start))
890 (window-buffer (posn-window start))))))
eaaf76b6
GM
891 (if button
892 ;; Mouse click on a widget button. Do the following
893 ;; in a save-excursion so that the click on the button
894 ;; doesn't change point.
136b27c5 895 (save-selected-window
bc3420db 896 (select-window (posn-window (event-start event)))
5710730c 897 (save-excursion
bc3420db 898 (goto-char (posn-point (event-start event)))
5710730c
GM
899 (let* ((overlay (widget-get button :button-overlay))
900 (face (overlay-get overlay 'face))
901 (mouse-face (overlay-get overlay 'mouse-face)))
902 (unwind-protect
eaaf76b6
GM
903 ;; Read events, including mouse-movement events
904 ;; until we receive a release event. Highlight/
905 ;; unhighlight the button the mouse was initially
906 ;; on when we move over it.
5710730c
GM
907 (let ((track-mouse t))
908 (save-excursion
909 (when face ; avoid changing around image
910 (overlay-put overlay
911 'face widget-button-pressed-face)
912 (overlay-put overlay
913 'mouse-face widget-button-pressed-face))
914 (unless (widget-apply button :mouse-down-action event)
915 (while (not (widget-button-release-event-p event))
916 (setq event (read-event)
917 pos (widget-event-point event))
918 (if (and pos
919 (eq (get-char-property pos 'button)
920 button))
921 (when face
922 (overlay-put overlay
923 'face
924 widget-button-pressed-face)
925 (overlay-put overlay
926 'mouse-face
927 widget-button-pressed-face))
928 (overlay-put overlay 'face face)
929 (overlay-put overlay 'mouse-face mouse-face))))
eaaf76b6
GM
930
931 ;; When mouse is released over the button, run
932 ;; its action function.
5710730c
GM
933 (when (and pos
934 (eq (get-char-property pos 'button) button))
935 (widget-apply-action button event))))
936 (overlay-put overlay 'face face)
937 (overlay-put overlay 'mouse-face mouse-face))))
938
bc3420db
RS
939 (unless (pos-visible-in-window-p (widget-event-point event))
940 (mouse-set-point event)
941 (beginning-of-line)
942 (recenter))
943 )
eaaf76b6
GM
944
945 (let ((up t) command)
946 ;; Mouse click not on a widget button. Find the global
947 ;; command to run, and check whether it is bound to an
948 ;; up event.
949 (mouse-set-point event)
bfa6c260
DL
950 (if (memq (event-basic-type event) '(mouse-1 down-mouse-1))
951 (cond ((setq command ;down event
952 (lookup-key widget-global-map [down-mouse-1]))
953 (setq up nil))
954 ((setq command ;up event
955 (lookup-key widget-global-map [mouse-1]))))
956 (cond ((setq command ;down event
957 (lookup-key widget-global-map [down-mouse-2]))
958 (setq up nil))
959 ((setq command ;up event
960 (lookup-key widget-global-map [mouse-2])))))
961 (when up
962 ;; Don't execute up events twice.
963 (while (not (widget-button-release-event-p event))
964 (setq event (read-event))))
965 (when command
966 (call-interactively command)))))
bfa6c260 967 (message "You clicked somewhere weird.")))
a3c88c59 968
d543e20b 969(defun widget-button-press (pos &optional event)
25ac13b5 970 "Invoke button at POS."
d543e20b 971 (interactive "@d")
0a3a0b56 972 (let ((button (get-char-property pos 'button)))
d543e20b
PA
973 (if button
974 (widget-apply-action button event)
975 (let ((command (lookup-key widget-global-map (this-command-keys))))
976 (when (commandp command)
977 (call-interactively command))))))
978
8697863a
PA
979(defun widget-tabable-at (&optional pos)
980 "Return the tabable widget at POS, or nil.
981POS defaults to the value of (point)."
a850ac03 982 (let ((widget (widget-at pos)))
8697863a
PA
983 (if widget
984 (let ((order (widget-get widget :tab-order)))
985 (if order
986 (if (>= order 0)
bfa6c260
DL
987 widget)
988 widget)))))
8697863a 989
35194e3f 990(defvar widget-use-overlay-change t
4ee1cf9f 991 "If non-nil, use overlay change functions to tab around in the buffer.
35194e3f 992This is much faster, but doesn't work reliably on Emacs 19.34.")
4ee1cf9f 993
d543e20b
PA
994(defun widget-move (arg)
995 "Move point to the ARG next field or button.
996ARG may be negative to move backward."
0a3a0b56 997 (or (bobp) (> arg 0) (backward-char))
ea13a2b4 998 (let ((wrapped 0)
0a3a0b56 999 (number arg)
407e43be 1000 (old (widget-tabable-at)))
0a3a0b56
PA
1001 ;; Forward.
1002 (while (> arg 0)
4ee1cf9f 1003 (cond ((eobp)
ea13a2b4
AS
1004 (goto-char (point-min))
1005 (setq wrapped (1+ wrapped)))
4ee1cf9f
PA
1006 (widget-use-overlay-change
1007 (goto-char (next-overlay-change (point))))
1008 (t
1009 (forward-char 1)))
ea13a2b4 1010 (and (= wrapped 2)
0a3a0b56
PA
1011 (eq arg number)
1012 (error "No buttons or fields found"))
8697863a 1013 (let ((new (widget-tabable-at)))
0a3a0b56
PA
1014 (when new
1015 (unless (eq new old)
8697863a 1016 (setq arg (1- arg))
0a3a0b56
PA
1017 (setq old new)))))
1018 ;; Backward.
1019 (while (< arg 0)
4ee1cf9f 1020 (cond ((bobp)
ea13a2b4
AS
1021 (goto-char (point-max))
1022 (setq wrapped (1+ wrapped)))
4ee1cf9f
PA
1023 (widget-use-overlay-change
1024 (goto-char (previous-overlay-change (point))))
1025 (t
1026 (backward-char 1)))
ea13a2b4 1027 (and (= wrapped 2)
0a3a0b56
PA
1028 (eq arg number)
1029 (error "No buttons or fields found"))
8697863a 1030 (let ((new (widget-tabable-at)))
0a3a0b56
PA
1031 (when new
1032 (unless (eq new old)
8697863a
PA
1033 (setq arg (1+ arg))))))
1034 (let ((new (widget-tabable-at)))
1035 (while (eq (widget-tabable-at) new)
1036 (backward-char)))
0ce5b5d5
PA
1037 (forward-char))
1038 (widget-echo-help (point))
1039 (run-hooks 'widget-move-hook))
d543e20b
PA
1040
1041(defun widget-forward (arg)
1042 "Move point to the next field or button.
1043With optional ARG, move across that many fields."
1044 (interactive "p")
1045 (run-hooks 'widget-forward-hook)
1046 (widget-move arg))
1047
1048(defun widget-backward (arg)
1049 "Move point to the previous field or button.
1050With optional ARG, move across that many fields."
1051 (interactive "p")
1052 (run-hooks 'widget-backward-hook)
1053 (widget-move (- arg)))
1054
8b9a0f45 1055;; Since the widget code uses a `field' property to identify fields,
0697c662 1056;; ordinary beginning-of-line does the right thing.
8b9a0f45 1057(defalias 'widget-beginning-of-line 'beginning-of-line)
0697c662
MB
1058
1059(defun widget-end-of-line ()
1060 "Go to end of field or end of line, whichever is first.
1061Trailing spaces at the end of padded fields are not considered part of
1062the field."
1063 (interactive)
1064 ;; Ordinary end-of-line does the right thing, because we're inside
1065 ;; text with a `field' property.
1066 (end-of-line)
1067 (unless (eolp)
1068 ;; ... except that we want to ignore trailing spaces in fields that
1069 ;; aren't terminated by a newline, because they are used as padding,
1070 ;; and ignored when extracting the entered value of the field.
1071 (skip-chars-backward " " (field-beginning (1- (point))))))
d543e20b
PA
1072
1073(defun widget-kill-line ()
1074 "Kill to end of field or end of line, whichever is first."
1075 (interactive)
0ce5b5d5 1076 (let* ((field (widget-field-find (point)))
0ce5b5d5 1077 (end (and field (widget-field-end field))))
7fdbdbea 1078 (if (and field (> (line-beginning-position 2) end))
0ce5b5d5 1079 (kill-region (point) end)
d543e20b
PA
1080 (call-interactively 'kill-line))))
1081
0ce5b5d5
PA
1082(defcustom widget-complete-field (lookup-key global-map "\M-\t")
1083 "Default function to call for completion inside fields."
1084 :options '(ispell-complete-word complete-tag lisp-complete-symbol)
1085 :type 'function
1086 :group 'widgets)
1087
1088(defun widget-complete ()
1089 "Complete content of editable field from point.
1090When not inside a field, move to the previous button or field."
1091 (interactive)
1092 (let ((field (widget-field-find (point))))
1093 (if field
1094 (widget-apply field :complete)
1095 (error "Not in an editable field"))))
1096
d543e20b
PA
1097;;; Setting up the buffer.
1098
7c0a9c8f
SM
1099(defvar widget-field-new nil
1100 "List of all newly created editable fields in the buffer.")
d543e20b
PA
1101(make-variable-buffer-local 'widget-field-new)
1102
7c0a9c8f
SM
1103(defvar widget-field-list nil
1104 "List of all editable fields in the buffer.")
d543e20b
PA
1105(make-variable-buffer-local 'widget-field-list)
1106
a850ac03
MB
1107(defun widget-at (&optional pos)
1108 "The button or field at POS (default, point)."
1109 (or (get-char-property (or pos (point)) 'button)
1110 (widget-field-at pos)))
1111
0e520006 1112;;;###autoload
d543e20b
PA
1113(defun widget-setup ()
1114 "Setup current buffer so editing string widgets works."
1115 (let ((inhibit-read-only t)
7fdbdbea 1116 (inhibit-modification-hooks t)
d543e20b
PA
1117 field)
1118 (while widget-field-new
1119 (setq field (car widget-field-new)
1120 widget-field-new (cdr widget-field-new)
1121 widget-field-list (cons field widget-field-list))
0a3a0b56
PA
1122 (let ((from (car (widget-get field :field-overlay)))
1123 (to (cdr (widget-get field :field-overlay))))
bfa6c260 1124 (widget-specify-field field
6aaedd12 1125 (marker-position from) (marker-position to))
0a3a0b56
PA
1126 (set-marker from nil)
1127 (set-marker to nil))))
d543e20b 1128 (widget-clear-undo)
4ee1cf9f 1129 (widget-add-change))
d543e20b
PA
1130
1131(defvar widget-field-last nil)
1132;; Last field containing point.
1133(make-variable-buffer-local 'widget-field-last)
1134
1135(defvar widget-field-was nil)
1136;; The widget data before the change.
1137(make-variable-buffer-local 'widget-field-was)
1138
a850ac03
MB
1139(defun widget-field-at (pos)
1140 "Return the widget field at POS, or nil if none."
1141 (let ((field (get-char-property (or pos (point)) 'field)))
1142 (if (eq field 'boundary)
1143 nil
1144 field)))
1145
0a3a0b56 1146(defun widget-field-buffer (widget)
80a7a1bf 1147 "Return the buffer of WIDGET's editing field."
6aaedd12 1148 (let ((overlay (widget-get widget :field-overlay)))
ec725166
MB
1149 (cond ((overlayp overlay)
1150 (overlay-buffer overlay))
1151 ((consp overlay)
1152 (marker-buffer (car overlay))))))
0a3a0b56
PA
1153
1154(defun widget-field-start (widget)
1155 "Return the start of WIDGET's editing field."
6aaedd12 1156 (let ((overlay (widget-get widget :field-overlay)))
ec725166
MB
1157 (if (overlayp overlay)
1158 (overlay-start overlay)
1159 (car overlay))))
0a3a0b56
PA
1160
1161(defun widget-field-end (widget)
1162 "Return the end of WIDGET's editing field."
6aaedd12 1163 (let ((overlay (widget-get widget :field-overlay)))
a850ac03
MB
1164 ;; Don't subtract one if local-map works at the end of the overlay,
1165 ;; or if a special `boundary' field has been added after the widget
1166 ;; field.
ec725166
MB
1167 (if (overlayp overlay)
1168 (if (and (not (eq (get-char-property (overlay-end overlay)
1169 'field
1170 (widget-field-buffer widget))
1171 'boundary))
1172 (or widget-field-add-space
1173 (null (widget-get widget :size))))
1174 (1- (overlay-end overlay))
1175 (overlay-end overlay))
1176 (cdr overlay))))
0a3a0b56 1177
d543e20b 1178(defun widget-field-find (pos)
0a3a0b56
PA
1179 "Return the field at POS.
1180Unlike (get-char-property POS 'field) this, works with empty fields too."
d543e20b
PA
1181 (let ((fields widget-field-list)
1182 field found)
1183 (while fields
1184 (setq field (car fields)
1185 fields (cdr fields))
7fdbdbea
DL
1186 (when (and (<= (widget-field-start field) pos)
1187 (<= pos (widget-field-end field)))
1188 (when found
1189 (error "Overlapping fields"))
1190 (setq found field)))
d543e20b
PA
1191 found))
1192
4ee1cf9f 1193(defun widget-before-change (from to)
944c91b6
PA
1194 ;; This is how, for example, a variable changes its state to `modified'.
1195 ;; when it is being edited.
540a8bd2
RS
1196 (unless inhibit-read-only
1197 (let ((from-field (widget-field-find from))
1198 (to-field (widget-field-find to)))
1199 (cond ((not (eq from-field to-field))
1200 (add-hook 'post-command-hook 'widget-add-change nil t)
808bcfd2
KH
1201 (signal 'text-read-only
1202 '("Change should be restricted to a single field")))
540a8bd2
RS
1203 ((null from-field)
1204 (add-hook 'post-command-hook 'widget-add-change nil t)
808bcfd2
KH
1205 (signal 'text-read-only
1206 '("Attempt to change text outside editable field")))
540a8bd2 1207 (widget-field-use-before-change
7fdbdbea 1208 (widget-apply from-field :notify from-field))))))
4ee1cf9f
PA
1209
1210(defun widget-add-change ()
4ee1cf9f 1211 (remove-hook 'post-command-hook 'widget-add-change t)
4ee1cf9f 1212 (add-hook 'before-change-functions 'widget-before-change nil t)
4ee1cf9f 1213 (add-hook 'after-change-functions 'widget-after-change nil t))
c6753d66 1214
d543e20b 1215(defun widget-after-change (from to old)
bfa6c260 1216 "Adjust field size and text properties."
7fdbdbea
DL
1217 (let ((field (widget-field-find from))
1218 (other (widget-field-find to)))
1219 (when field
1220 (unless (eq field other)
1221 (error "Change in different fields"))
1222 (let ((size (widget-get field :size)))
1223 (when size
1224 (let ((begin (widget-field-start field))
1225 (end (widget-field-end field)))
1226 (cond ((< (- end begin) size)
1227 ;; Field too small.
1228 (save-excursion
1229 (goto-char end)
1230 (insert-char ?\ (- (+ begin size) end))))
1231 ((> (- end begin) size)
1232 ;; Field too large and
1233 (if (or (< (point) (+ begin size))
1234 (> (point) end))
1235 ;; Point is outside extra space.
1236 (setq begin (+ begin size))
1237 ;; Point is within the extra space.
1238 (setq begin (point)))
1239 (save-excursion
1240 (goto-char end)
1241 (while (and (eq (preceding-char) ?\ )
1242 (> (point) begin))
1243 (delete-backward-char 1)))))))
1244 (widget-specify-secret field))
1245 (widget-apply field :notify field))))
d543e20b
PA
1246
1247;;; Widget Functions
1248;;
bfa6c260 1249;; These functions are used in the definition of multiple widgets.
d543e20b 1250
a3c88c59
PA
1251(defun widget-parent-action (widget &optional event)
1252 "Tell :parent of WIDGET to handle the :action.
1253Optional EVENT is the event that triggered the action."
1254 (widget-apply (widget-get widget :parent) :action event))
1255
d543e20b
PA
1256(defun widget-children-value-delete (widget)
1257 "Delete all :children and :buttons in WIDGET."
bfa6c260 1258 (mapc 'widget-delete (widget-get widget :children))
d543e20b 1259 (widget-put widget :children nil)
bfa6c260 1260 (mapc 'widget-delete (widget-get widget :buttons))
d543e20b
PA
1261 (widget-put widget :buttons nil))
1262
a3c88c59
PA
1263(defun widget-children-validate (widget)
1264 "All the :children must be valid."
1265 (let ((children (widget-get widget :children))
1266 child found)
1267 (while (and children (not found))
1268 (setq child (car children)
1269 children (cdr children)
1270 found (widget-apply child :validate)))
1271 found))
1272
cfa921fd
PA
1273(defun widget-child-value-get (widget)
1274 "Get the value of the first member of :children in WIDGET."
1275 (widget-value (car (widget-get widget :children))))
1276
1277(defun widget-child-value-inline (widget)
1278 "Get the inline value of the first member of :children in WIDGET."
1279 (widget-apply (car (widget-get widget :children)) :value-inline))
1280
1281(defun widget-child-validate (widget)
1282 "The result of validating the first member of :children in WIDGET."
1283 (widget-apply (car (widget-get widget :children)) :validate))
1284
1285(defun widget-type-value-create (widget)
1286 "Convert and instantiate the value of the :type attribute of WIDGET.
1287Store the newly created widget in the :children attribute.
1288
1289The value of the :type attribute should be an unconverted widget type."
1290 (let ((value (widget-get widget :value))
1291 (type (widget-get widget :type)))
0e726aa5
KS
1292 (widget-put widget :children
1293 (list (widget-create-child-value widget
cfa921fd
PA
1294 (widget-convert type)
1295 value)))))
1296
1297(defun widget-type-default-get (widget)
1298 "Get default value from the :type attribute of WIDGET.
1299
1300The value of the :type attribute should be an unconverted widget type."
1301 (widget-default-get (widget-convert (widget-get widget :type))))
1302
1303(defun widget-type-match (widget value)
1304 "Non-nil if the :type value of WIDGET matches VALUE.
1305
1306The value of the :type attribute should be an unconverted widget type."
1307 (widget-apply (widget-convert (widget-get widget :type)) :match value))
1308
4c2f559e
PA
1309(defun widget-types-copy (widget)
1310 "Copy :args as widget types in WIDGET."
1311 (widget-put widget :args (mapcar 'widget-copy (widget-get widget :args)))
1312 widget)
1313
aeba6f9a
DL
1314;; Made defsubst to speed up face editor creation.
1315(defsubst widget-types-convert-widget (widget)
d543e20b
PA
1316 "Convert :args as widget types in WIDGET."
1317 (widget-put widget :args (mapcar 'widget-convert (widget-get widget :args)))
1318 widget)
1319
a3c88c59
PA
1320(defun widget-value-convert-widget (widget)
1321 "Initialize :value from :args in WIDGET."
1322 (let ((args (widget-get widget :args)))
bfa6c260 1323 (when args
a3c88c59
PA
1324 (widget-put widget :value (car args))
1325 ;; Don't convert :value here, as this is done in `widget-convert'.
1326 ;; (widget-put widget :value (widget-apply widget
1327 ;; :value-to-internal (car args)))
1328 (widget-put widget :args nil)))
1329 widget)
1330
1331(defun widget-value-value-get (widget)
1332 "Return the :value property of WIDGET."
1333 (widget-get widget :value))
1334
d543e20b
PA
1335;;; The `default' Widget.
1336
1337(define-widget 'default nil
1338 "Basic widget other widgets are derived from."
1339 :value-to-internal (lambda (widget value) value)
1340 :value-to-external (lambda (widget value) value)
25ac13b5
PA
1341 :button-prefix 'widget-button-prefix
1342 :button-suffix 'widget-button-suffix
bfa6c260 1343 :complete 'widget-default-complete
d543e20b
PA
1344 :create 'widget-default-create
1345 :indent nil
1346 :offset 0
1347 :format-handler 'widget-default-format-handler
77339a6e
JB
1348 :button-face-get 'widget-default-button-face-get
1349 :sample-face-get 'widget-default-sample-face-get
d543e20b 1350 :delete 'widget-default-delete
4c2f559e 1351 :copy 'identity
d543e20b
PA
1352 :value-set 'widget-default-value-set
1353 :value-inline 'widget-default-value-inline
125f1820 1354 :value-delete 'ignore
783824f5 1355 :default-get 'widget-default-default-get
d543e20b 1356 :menu-tag-get 'widget-default-menu-tag-get
99f01612 1357 :validate #'ignore
d543e20b
PA
1358 :active 'widget-default-active
1359 :activate 'widget-specify-active
1360 :deactivate 'widget-default-deactivate
99f01612 1361 :mouse-down-action #'ignore
d543e20b 1362 :action 'widget-default-action
6d528fc5
PA
1363 :notify 'widget-default-notify
1364 :prompt-value 'widget-default-prompt-value)
d543e20b 1365
0ce5b5d5
PA
1366(defun widget-default-complete (widget)
1367 "Call the value of the :complete-function property of WIDGET.
1368If that does not exists, call the value of `widget-complete-field'."
7fdbdbea
DL
1369 (call-interactively (or (widget-get widget :complete-function)
1370 widget-complete-field)))
0ce5b5d5 1371
d543e20b
PA
1372(defun widget-default-create (widget)
1373 "Create WIDGET at point in the current buffer."
1374 (widget-specify-insert
1375 (let ((from (point))
d543e20b
PA
1376 button-begin button-end
1377 sample-begin sample-end
1378 doc-begin doc-end
1379 value-pos)
1380 (insert (widget-get widget :format))
1381 (goto-char from)
1382 ;; Parse escapes in format.
1383 (while (re-search-forward "%\\(.\\)" nil t)
7fdbdbea
DL
1384 (let ((escape (char-after (match-beginning 1))))
1385 (delete-backward-char 2)
d543e20b 1386 (cond ((eq escape ?%)
bfa6c260 1387 (insert ?%))
d543e20b 1388 ((eq escape ?\[)
25ac13b5 1389 (setq button-begin (point))
944c91b6 1390 (insert (widget-get-indirect widget :button-prefix)))
d543e20b 1391 ((eq escape ?\])
944c91b6 1392 (insert (widget-get-indirect widget :button-suffix))
d543e20b
PA
1393 (setq button-end (point)))
1394 ((eq escape ?\{)
1395 (setq sample-begin (point)))
1396 ((eq escape ?\})
1397 (setq sample-end (point)))
1398 ((eq escape ?n)
1399 (when (widget-get widget :indent)
bfa6c260 1400 (insert ?\n)
d543e20b
PA
1401 (insert-char ? (widget-get widget :indent))))
1402 ((eq escape ?t)
bfa6c260 1403 (let ((image (widget-get widget :tag-glyph))
25ac13b5 1404 (tag (widget-get widget :tag)))
bfa6c260
DL
1405 (cond (image
1406 (widget-image-insert widget (or tag "image") image))
25ac13b5
PA
1407 (tag
1408 (insert tag))
1409 (t
bfa6c260
DL
1410 (princ (widget-get widget :value)
1411 (current-buffer))))))
d543e20b 1412 ((eq escape ?d)
25ac13b5
PA
1413 (let ((doc (widget-get widget :doc)))
1414 (when doc
1415 (setq doc-begin (point))
1416 (insert doc)
1417 (while (eq (preceding-char) ?\n)
1418 (delete-backward-char 1))
bfa6c260 1419 (insert ?\n)
25ac13b5 1420 (setq doc-end (point)))))
d543e20b
PA
1421 ((eq escape ?v)
1422 (if (and button-begin (not button-end))
1423 (widget-apply widget :value-create)
1424 (setq value-pos (point))))
bfa6c260 1425 (t
d543e20b
PA
1426 (widget-apply widget :format-handler escape)))))
1427 ;; Specify button, sample, and doc, and insert value.
1428 (and button-begin button-end
1429 (widget-specify-button widget button-begin button-end))
1430 (and sample-begin sample-end
1431 (widget-specify-sample widget sample-begin sample-end))
1432 (and doc-begin doc-end
1433 (widget-specify-doc widget doc-begin doc-end))
1434 (when value-pos
1435 (goto-char value-pos)
1436 (widget-apply widget :value-create)))
7fdbdbea
DL
1437 (let ((from (point-min-marker))
1438 (to (point-max-marker)))
d543e20b
PA
1439 (set-marker-insertion-type from t)
1440 (set-marker-insertion-type to nil)
1441 (widget-put widget :from from)
6d528fc5
PA
1442 (widget-put widget :to to)))
1443 (widget-clear-undo))
d543e20b
PA
1444
1445(defun widget-default-format-handler (widget escape)
1446 ;; We recognize the %h escape by default.
6aaedd12 1447 (let* ((buttons (widget-get widget :buttons)))
d543e20b 1448 (cond ((eq escape ?h)
6aaedd12
PA
1449 (let* ((doc-property (widget-get widget :documentation-property))
1450 (doc-try (cond ((widget-get widget :doc))
820d4181
DL
1451 ((functionp doc-property)
1452 (funcall doc-property
1453 (widget-get widget :value)))
6aaedd12 1454 ((symbolp doc-property)
bfa6c260 1455 (documentation-property
6aaedd12 1456 (widget-get widget :value)
820d4181 1457 doc-property))))
6aaedd12
PA
1458 (doc-text (and (stringp doc-try)
1459 (> (length doc-try) 1)
8697863a
PA
1460 doc-try))
1461 (doc-indent (widget-get widget :documentation-indent)))
6aaedd12
PA
1462 (when doc-text
1463 (and (eq (preceding-char) ?\n)
1464 (widget-get widget :indent)
1465 (insert-char ? (widget-get widget :indent)))
1466 ;; The `*' in the beginning is redundant.
1467 (when (eq (aref doc-text 0) ?*)
1468 (setq doc-text (substring doc-text 1)))
1469 ;; Get rid of trailing newlines.
1470 (when (string-match "\n+\\'" doc-text)
1471 (setq doc-text (substring doc-text 0 (match-beginning 0))))
1472 (push (widget-create-child-and-convert
1473 widget 'documentation-string
8697863a
PA
1474 :indent (cond ((numberp doc-indent )
1475 doc-indent)
1476 ((null doc-indent)
1477 nil)
1478 (t 0))
6aaedd12
PA
1479 doc-text)
1480 buttons))))
bfa6c260 1481 (t
d543e20b
PA
1482 (error "Unknown escape `%c'" escape)))
1483 (widget-put widget :buttons buttons)))
1484
1485(defun widget-default-button-face-get (widget)
1486 ;; Use :button-face or widget-button-face
0b296dac
RS
1487 (or (widget-get widget :button-face)
1488 (let ((parent (widget-get widget :parent)))
1489 (if parent
1490 (widget-apply parent :button-face-get)
2f477381 1491 widget-button-face))))
d543e20b
PA
1492
1493(defun widget-default-sample-face-get (widget)
1494 ;; Use :sample-face.
1495 (widget-get widget :sample-face))
1496
1497(defun widget-default-delete (widget)
bfa6c260 1498 "Remove widget from the buffer."
d543e20b
PA
1499 (let ((from (widget-get widget :from))
1500 (to (widget-get widget :to))
9097aeb7
PA
1501 (inactive-overlay (widget-get widget :inactive))
1502 (button-overlay (widget-get widget :button-overlay))
0f648ca2 1503 (sample-overlay (widget-get widget :sample-overlay))
4ee1cf9f 1504 (doc-overlay (widget-get widget :doc-overlay))
7fdbdbea 1505 (inhibit-modification-hooks t)
0a3a0b56 1506 (inhibit-read-only t))
d543e20b 1507 (widget-apply widget :value-delete)
7d9d1ab6 1508 (widget-children-value-delete widget)
9097aeb7
PA
1509 (when inactive-overlay
1510 (delete-overlay inactive-overlay))
1511 (when button-overlay
1512 (delete-overlay button-overlay))
0f648ca2
PA
1513 (when sample-overlay
1514 (delete-overlay sample-overlay))
4ee1cf9f
PA
1515 (when doc-overlay
1516 (delete-overlay doc-overlay))
d543e20b
PA
1517 (when (< from to)
1518 ;; Kludge: this doesn't need to be true for empty formats.
1519 (delete-region from to))
1520 (set-marker from nil)
6d528fc5
PA
1521 (set-marker to nil))
1522 (widget-clear-undo))
d543e20b
PA
1523
1524(defun widget-default-value-set (widget value)
bfa6c260 1525 "Recreate widget with new value."
d0acc4ea
RS
1526 (let* ((old-pos (point))
1527 (from (copy-marker (widget-get widget :from)))
1528 (to (copy-marker (widget-get widget :to)))
1529 (offset (if (and (<= from old-pos) (<= old-pos to))
1530 (if (>= old-pos (1- to))
1531 (- old-pos to 1)
1532 (- old-pos from)))))
1533 ;;??? Bug: this ought to insert the new value before deleting the old one,
bfa6c260 1534 ;; so that markers on either side of the value automatically
d0acc4ea
RS
1535 ;; stay on the same side. -- rms.
1536 (save-excursion
1537 (goto-char (widget-get widget :from))
1538 (widget-apply widget :delete)
1539 (widget-put widget :value value)
1540 (widget-apply widget :create))
1541 (if offset
1542 (if (< offset 0)
1543 (goto-char (+ (widget-get widget :to) offset 1))
1544 (goto-char (min (+ from offset) (1- (widget-get widget :to))))))))
d543e20b
PA
1545
1546(defun widget-default-value-inline (widget)
bfa6c260 1547 "Wrap value in a list unless it is inline."
d543e20b
PA
1548 (if (widget-get widget :inline)
1549 (widget-value widget)
1550 (list (widget-value widget))))
1551
783824f5 1552(defun widget-default-default-get (widget)
bfa6c260 1553 "Get `:value'."
783824f5
RS
1554 (widget-get widget :value))
1555
d543e20b 1556(defun widget-default-menu-tag-get (widget)
bfa6c260 1557 "Use tag or value for menus."
d543e20b
PA
1558 (or (widget-get widget :menu-tag)
1559 (widget-get widget :tag)
1560 (widget-princ-to-string (widget-get widget :value))))
1561
1562(defun widget-default-active (widget)
1563 "Return t iff this widget active (user modifiable)."
0640c647
GM
1564 (or (widget-get widget :always-active)
1565 (and (not (widget-get widget :inactive))
1566 (let ((parent (widget-get widget :parent)))
77339a6e 1567 (or (null parent)
0640c647 1568 (widget-apply parent :active))))))
d543e20b
PA
1569
1570(defun widget-default-deactivate (widget)
1571 "Make WIDGET inactive for user modifications."
1572 (widget-specify-inactive widget
1573 (widget-get widget :from)
1574 (widget-get widget :to)))
1575
1576(defun widget-default-action (widget &optional event)
bfa6c260 1577 "Notify the parent when a widget changes."
d543e20b
PA
1578 (let ((parent (widget-get widget :parent)))
1579 (when parent
1580 (widget-apply parent :notify widget event))))
1581
1582(defun widget-default-notify (widget child &optional event)
bfa6c260 1583 "Pass notification to parent."
d543e20b
PA
1584 (widget-default-action widget event))
1585
6d528fc5 1586(defun widget-default-prompt-value (widget prompt value unbound)
bfa6c260
DL
1587 "Read an arbitrary value. Stolen from `set-variable'."
1588;; (let ((initial (if unbound
7fdbdbea 1589;; nil
bfa6c260
DL
1590;; It would be nice if we could do a `(cons val 1)' here.
1591;; (prin1-to-string (custom-quote value))))))
7fdbdbea 1592 (eval-minibuffer prompt))
6d528fc5 1593
d543e20b
PA
1594;;; The `item' Widget.
1595
1596(define-widget 'item 'default
1597 "Constant items for inclusion in other widgets."
a3c88c59 1598 :convert-widget 'widget-value-convert-widget
d543e20b
PA
1599 :value-create 'widget-item-value-create
1600 :value-delete 'ignore
a3c88c59 1601 :value-get 'widget-value-value-get
d543e20b
PA
1602 :match 'widget-item-match
1603 :match-inline 'widget-item-match-inline
1604 :action 'widget-item-action
1605 :format "%t\n")
1606
d543e20b 1607(defun widget-item-value-create (widget)
bfa6c260
DL
1608 "Insert the printed representation of the value."
1609 (princ (widget-get widget :value) (current-buffer)))
d543e20b
PA
1610
1611(defun widget-item-match (widget value)
1612 ;; Match if the value is the same.
1613 (equal (widget-get widget :value) value))
1614
1615(defun widget-item-match-inline (widget values)
1616 ;; Match if the value is the same.
1617 (let ((value (widget-get widget :value)))
1618 (and (listp value)
1619 (<= (length value) (length values))
e5dfabb4 1620 (let ((head (widget-sublist values 0 (length value))))
d543e20b 1621 (and (equal head value)
e5dfabb4
RS
1622 (cons head (widget-sublist values (length value))))))))
1623
1624(defun widget-sublist (list start &optional end)
1625 "Return the sublist of LIST from START to END.
1626If END is omitted, it defaults to the length of LIST."
0a3a0b56
PA
1627 (if (> start 0) (setq list (nthcdr start list)))
1628 (if end
bfa6c260 1629 (unless (<= end start)
0a3a0b56
PA
1630 (setq list (copy-sequence list))
1631 (setcdr (nthcdr (- end start 1) list) nil)
1632 list)
1633 (copy-sequence list)))
d543e20b
PA
1634
1635(defun widget-item-action (widget &optional event)
1636 ;; Just notify itself.
1637 (widget-apply widget :notify widget event))
1638
d543e20b
PA
1639;;; The `push-button' Widget.
1640
7fdbdbea
DL
1641;; (defcustom widget-push-button-gui t
1642;; "If non nil, use GUI push buttons when available."
1643;; :group 'widgets
1644;; :type 'boolean)
d543e20b
PA
1645
1646;; Cache already created GUI objects.
7fdbdbea 1647;; (defvar widget-push-button-cache nil)
d543e20b 1648
25ac13b5
PA
1649(defcustom widget-push-button-prefix "["
1650 "String used as prefix for buttons."
1651 :type 'string
1652 :group 'widget-button)
1653
1654(defcustom widget-push-button-suffix "]"
1655 "String used as suffix for buttons."
1656 :type 'string
1657 :group 'widget-button)
1658
d543e20b
PA
1659(define-widget 'push-button 'item
1660 "A pushable button."
25ac13b5
PA
1661 :button-prefix ""
1662 :button-suffix ""
d543e20b
PA
1663 :value-create 'widget-push-button-value-create
1664 :format "%[%v%]")
1665
1666(defun widget-push-button-value-create (widget)
bfa6c260 1667 "Insert text representing the `on' and `off' states."
d543e20b
PA
1668 (let* ((tag (or (widget-get widget :tag)
1669 (widget-get widget :value)))
da5ec617 1670 (tag-glyph (widget-get widget :tag-glyph))
25ac13b5 1671 (text (concat widget-push-button-prefix
7fdbdbea
DL
1672 tag widget-push-button-suffix)))
1673 (if tag-glyph
1674 (widget-image-insert widget text tag-glyph)
1675 (insert text))))
d543e20b 1676
7fdbdbea
DL
1677;; (defun widget-gui-action (widget)
1678;; "Apply :action for WIDGET."
1679;; (widget-apply-action widget (this-command-keys)))
d543e20b
PA
1680
1681;;; The `link' Widget.
1682
25ac13b5
PA
1683(defcustom widget-link-prefix "["
1684 "String used as prefix for links."
1685 :type 'string
1686 :group 'widget-button)
1687
1688(defcustom widget-link-suffix "]"
1689 "String used as suffix for links."
1690 :type 'string
1691 :group 'widget-button)
1692
d543e20b
PA
1693(define-widget 'link 'item
1694 "An embedded link."
25ac13b5
PA
1695 :button-prefix 'widget-link-prefix
1696 :button-suffix 'widget-link-suffix
d543e20b 1697 :help-echo "Follow the link."
25ac13b5 1698 :format "%[%t%]")
d543e20b
PA
1699
1700;;; The `info-link' Widget.
1701
1702(define-widget 'info-link 'link
1703 "A link to an info file."
1704 :action 'widget-info-link-action)
1705
1706(defun widget-info-link-action (widget &optional event)
1707 "Open the info node specified by WIDGET."
7c0a9c8f 1708 (info (widget-value widget)))
d543e20b
PA
1709
1710;;; The `url-link' Widget.
1711
1712(define-widget 'url-link 'link
1713 "A link to an www page."
1714 :action 'widget-url-link-action)
1715
1716(defun widget-url-link-action (widget &optional event)
1717 "Open the url specified by WIDGET."
af0f19d7 1718 (browse-url (widget-value widget)))
d543e20b 1719
a59b7025
KH
1720;;; The `function-link' Widget.
1721
1722(define-widget 'function-link 'link
1723 "A link to an Emacs function."
1724 :action 'widget-function-link-action)
1725
1726(defun widget-function-link-action (widget &optional event)
1727 "Show the function specified by WIDGET."
1728 (describe-function (widget-value widget)))
1729
1730;;; The `variable-link' Widget.
1731
1732(define-widget 'variable-link 'link
1733 "A link to an Emacs variable."
1734 :action 'widget-variable-link-action)
1735
1736(defun widget-variable-link-action (widget &optional event)
1737 "Show the variable specified by WIDGET."
1738 (describe-variable (widget-value widget)))
1739
62f44662
PA
1740;;; The `file-link' Widget.
1741
1742(define-widget 'file-link 'link
1743 "A link to a file."
1744 :action 'widget-file-link-action)
1745
1746(defun widget-file-link-action (widget &optional event)
1747 "Find the file specified by WIDGET."
1748 (find-file (widget-value widget)))
1749
1750;;; The `emacs-library-link' Widget.
1751
1752(define-widget 'emacs-library-link 'link
1753 "A link to an Emacs Lisp library file."
1754 :action 'widget-emacs-library-link-action)
1755
1756(defun widget-emacs-library-link-action (widget &optional event)
1757 "Find the Emacs Library file specified by WIDGET."
1758 (find-file (locate-library (widget-value widget))))
1759
4ee1cf9f 1760;;; The `emacs-commentary-link' Widget.
77339a6e 1761
4ee1cf9f
PA
1762(define-widget 'emacs-commentary-link 'link
1763 "A link to Commentary in an Emacs Lisp library file."
1764 :action 'widget-emacs-commentary-link-action)
77339a6e 1765
4ee1cf9f
PA
1766(defun widget-emacs-commentary-link-action (widget &optional event)
1767 "Find the Commentary section of the Emacs file specified by WIDGET."
1768 (finder-commentary (widget-value widget)))
1769
d543e20b
PA
1770;;; The `editable-field' Widget.
1771
1772(define-widget 'editable-field 'default
1773 "An editable text field."
a3c88c59 1774 :convert-widget 'widget-value-convert-widget
d543e20b
PA
1775 :keymap widget-field-keymap
1776 :format "%v"
7fdbdbea 1777 :help-echo "M-TAB: complete field; RET: enter value"
d543e20b 1778 :value ""
a3c88c59
PA
1779 :prompt-internal 'widget-field-prompt-internal
1780 :prompt-history 'widget-field-history
1781 :prompt-value 'widget-field-prompt-value
d543e20b
PA
1782 :action 'widget-field-action
1783 :validate 'widget-field-validate
1784 :valid-regexp ""
820d4181 1785 :error "Field's value doesn't match allowed forms"
d543e20b
PA
1786 :value-create 'widget-field-value-create
1787 :value-delete 'widget-field-value-delete
1788 :value-get 'widget-field-value-get
1789 :match 'widget-field-match)
1790
a3c88c59
PA
1791(defvar widget-field-history nil
1792 "History of field minibuffer edits.")
1793
1794(defun widget-field-prompt-internal (widget prompt initial history)
bfa6c260
DL
1795 "Read string for WIDGET promptinhg with PROMPT.
1796INITIAL is the initial input and HISTORY is a symbol containing
1797the earlier input."
a3c88c59
PA
1798 (read-string prompt initial history))
1799
1800(defun widget-field-prompt-value (widget prompt value unbound)
bfa6c260 1801 "Prompt for a string."
7fdbdbea
DL
1802 (widget-apply widget
1803 :value-to-external
1804 (widget-apply widget
1805 :prompt-internal prompt
1806 (unless unbound
1807 (cons (widget-apply widget
1808 :value-to-internal value)
1809 0))
1810 (widget-get widget :prompt-history))))
d543e20b 1811
0b296dac 1812(defvar widget-edit-functions nil)
211c9fe9 1813
d543e20b 1814(defun widget-field-action (widget &optional event)
bfa6c260 1815 "Move to next field."
f1231b8e 1816 (widget-forward 1)
0b296dac 1817 (run-hook-with-args 'widget-edit-functions widget))
d543e20b
PA
1818
1819(defun widget-field-validate (widget)
bfa6c260 1820 "Valid if the content matches `:valid-regexp'."
7fdbdbea
DL
1821 (unless (string-match (widget-get widget :valid-regexp)
1822 (widget-apply widget :value-get))
1823 widget))
d543e20b
PA
1824
1825(defun widget-field-value-create (widget)
bfa6c260 1826 "Create an editable text field."
d543e20b
PA
1827 (let ((size (widget-get widget :size))
1828 (value (widget-get widget :value))
0a3a0b56 1829 (from (point))
c953515e
PA
1830 ;; This is changed to a real overlay in `widget-setup'. We
1831 ;; need the end points to behave differently until
bfa6c260 1832 ;; `widget-setup' is called.
0a3a0b56
PA
1833 (overlay (cons (make-marker) (make-marker))))
1834 (widget-put widget :field-overlay overlay)
d543e20b
PA
1835 (insert value)
1836 (and size
1837 (< (length value) size)
1838 (insert-char ?\ (- size (length value))))
1839 (unless (memq widget widget-field-list)
1840 (setq widget-field-new (cons widget widget-field-new)))
0a3a0b56
PA
1841 (move-marker (cdr overlay) (point))
1842 (set-marker-insertion-type (cdr overlay) nil)
1843 (when (null size)
1844 (insert ?\n))
1845 (move-marker (car overlay) from)
1846 (set-marker-insertion-type (car overlay) t)))
d543e20b
PA
1847
1848(defun widget-field-value-delete (widget)
bfa6c260 1849 "Remove the widget from the list of active editing fields."
d543e20b 1850 (setq widget-field-list (delq widget widget-field-list))
ec725166 1851 (setq widget-field-new (delq widget widget-field-new))
d543e20b 1852 ;; These are nil if the :format string doesn't contain `%v'.
0a3a0b56 1853 (let ((overlay (widget-get widget :field-overlay)))
d8f02b91 1854 (when (overlayp overlay)
0a3a0b56 1855 (delete-overlay overlay))))
d543e20b
PA
1856
1857(defun widget-field-value-get (widget)
bfa6c260 1858 "Return current text in editing field."
0a3a0b56
PA
1859 (let ((from (widget-field-start widget))
1860 (to (widget-field-end widget))
1861 (buffer (widget-field-buffer widget))
d543e20b
PA
1862 (size (widget-get widget :size))
1863 (secret (widget-get widget :secret))
1864 (old (current-buffer)))
1865 (if (and from to)
bfa6c260 1866 (progn
0a3a0b56 1867 (set-buffer buffer)
d543e20b
PA
1868 (while (and size
1869 (not (zerop size))
1870 (> to from)
1871 (eq (char-after (1- to)) ?\ ))
1872 (setq to (1- to)))
1873 (let ((result (buffer-substring-no-properties from to)))
1874 (when secret
1875 (let ((index 0))
1876 (while (< (+ from index) to)
1877 (aset result index
0a3a0b56 1878 (get-char-property (+ from index) 'secret))
d543e20b
PA
1879 (setq index (1+ index)))))
1880 (set-buffer old)
1881 result))
1882 (widget-get widget :value))))
1883
1884(defun widget-field-match (widget value)
1885 ;; Match any string.
1886 (stringp value))
1887
1888;;; The `text' Widget.
1889
1890(define-widget 'text 'editable-field
7fce8d93
SM
1891 "A multiline text area."
1892 :keymap widget-text-keymap)
d543e20b
PA
1893
1894;;; The `menu-choice' Widget.
1895
1896(define-widget 'menu-choice 'default
1897 "A menu of options."
1898 :convert-widget 'widget-types-convert-widget
4c2f559e 1899 :copy 'widget-types-copy
d543e20b
PA
1900 :format "%[%t%]: %v"
1901 :case-fold t
1902 :tag "choice"
1903 :void '(item :format "invalid (%t)\n")
1904 :value-create 'widget-choice-value-create
cfa921fd
PA
1905 :value-get 'widget-child-value-get
1906 :value-inline 'widget-child-value-inline
783824f5 1907 :default-get 'widget-choice-default-get
a3c88c59 1908 :mouse-down-action 'widget-choice-mouse-down-action
d543e20b
PA
1909 :action 'widget-choice-action
1910 :error "Make a choice"
1911 :validate 'widget-choice-validate
1912 :match 'widget-choice-match
1913 :match-inline 'widget-choice-match-inline)
1914
1915(defun widget-choice-value-create (widget)
bfa6c260 1916 "Insert the first choice that matches the value."
d543e20b
PA
1917 (let ((value (widget-get widget :value))
1918 (args (widget-get widget :args))
4084d128 1919 (explicit (widget-get widget :explicit-choice))
d543e20b 1920 current)
7fdbdbea 1921 (if (and explicit (equal value (widget-get widget :explicit-choice-value)))
4084d128
RS
1922 (progn
1923 ;; If the user specified the choice for this value,
1924 ;; respect that choice as long as the value is the same.
1925 (widget-put widget :children (list (widget-create-child-value
1926 widget explicit value)))
1927 (widget-put widget :choice explicit))
1928 (while args
1929 (setq current (car args)
1930 args (cdr args))
1931 (when (widget-apply current :match value)
1932 (widget-put widget :children (list (widget-create-child-value
1933 widget current value)))
1934 (widget-put widget :choice current)
1935 (setq args nil
1936 current nil)))
1937 (when current
1938 (let ((void (widget-get widget :void)))
1939 (widget-put widget :children (list (widget-create-child-and-convert
1940 widget void :value value)))
1941 (widget-put widget :choice void))))))
d543e20b 1942
783824f5
RS
1943(defun widget-choice-default-get (widget)
1944 ;; Get default for the first choice.
1945 (widget-default-get (car (widget-get widget :args))))
1946
a3c88c59
PA
1947(defcustom widget-choice-toggle nil
1948 "If non-nil, a binary choice will just toggle between the values.
1949Otherwise, the user will explicitly have to choose between the values
25ac13b5 1950when he invoked the menu."
a3c88c59
PA
1951 :type 'boolean
1952 :group 'widgets)
1953
1954(defun widget-choice-mouse-down-action (widget &optional event)
1955 ;; Return non-nil if we need a menu.
1956 (let ((args (widget-get widget :args))
1957 (old (widget-get widget :choice)))
e2c00a47 1958 (cond ((not (display-popup-menus-p))
a3c88c59
PA
1959 ;; No place to pop up a menu.
1960 nil)
a3c88c59
PA
1961 ((< (length args) 2)
1962 ;; Empty or singleton list, just return the value.
1963 nil)
1964 ((> (length args) widget-menu-max-size)
1965 ;; Too long, prompt.
1966 nil)
1967 ((> (length args) 2)
1968 ;; Reasonable sized list, use menu.
1969 t)
1970 ((and widget-choice-toggle (memq old args))
1971 ;; We toggle.
1972 nil)
1973 (t
1974 ;; Ask which of the two.
1975 t))))
1976
d543e20b
PA
1977(defun widget-choice-action (widget &optional event)
1978 ;; Make a choice.
1979 (let ((args (widget-get widget :args))
1980 (old (widget-get widget :choice))
1981 (tag (widget-apply widget :menu-tag-get))
1982 (completion-ignore-case (widget-get widget :case-fold))
4084d128 1983 this-explicit
d543e20b
PA
1984 current choices)
1985 ;; Remember old value.
1986 (if (and old (not (widget-apply widget :validate)))
1987 (let* ((external (widget-value widget))
1988 (internal (widget-apply old :value-to-internal external)))
1989 (widget-put old :value internal)))
1990 ;; Find new choice.
1991 (setq current
1992 (cond ((= (length args) 0)
1993 nil)
1994 ((= (length args) 1)
1995 (nth 0 args))
a3c88c59
PA
1996 ((and widget-choice-toggle
1997 (= (length args) 2)
d543e20b
PA
1998 (memq old args))
1999 (if (eq old (nth 0 args))
2000 (nth 1 args)
2001 (nth 0 args)))
2002 (t
2003 (while args
2004 (setq current (car args)
2005 args (cdr args))
2006 (setq choices
2007 (cons (cons (widget-apply current :menu-tag-get)
2008 current)
2009 choices)))
4084d128 2010 (setq this-explicit t)
d543e20b 2011 (widget-choose tag (reverse choices) event))))
d0acc4ea 2012 (when current
4084d128
RS
2013 ;; If this was an explicit user choice,
2014 ;; record the choice, and the record the value it was made for.
2015 ;; widget-choice-value-create will respect this choice,
2016 ;; as long as the value is the same.
2017 (when this-explicit
2018 (widget-put widget :explicit-choice current)
2019 (widget-put widget :explicit-choice-value (widget-get widget :value)))
4c2f559e 2020 (widget-value-set widget (widget-default-get current))
d0acc4ea
RS
2021 (widget-setup)
2022 (widget-apply widget :notify widget event)))
d4b8422f 2023 (run-hook-with-args 'widget-edit-functions widget))
d543e20b
PA
2024
2025(defun widget-choice-validate (widget)
2026 ;; Valid if we have made a valid choice.
7fdbdbea
DL
2027 (if (eq (widget-get widget :void) (widget-get widget :choice))
2028 widget
2029 (widget-apply (car (widget-get widget :children)) :validate)))
d543e20b
PA
2030
2031(defun widget-choice-match (widget value)
2032 ;; Matches if one of the choices matches.
2033 (let ((args (widget-get widget :args))
2034 current found)
2035 (while (and args (not found))
2036 (setq current (car args)
2037 args (cdr args)
2038 found (widget-apply current :match value)))
2039 found))
2040
2041(defun widget-choice-match-inline (widget values)
2042 ;; Matches if one of the choices matches.
2043 (let ((args (widget-get widget :args))
2044 current found)
2045 (while (and args (null found))
2046 (setq current (car args)
2047 args (cdr args)
2048 found (widget-match-inline current values)))
2049 found))
2050
2051;;; The `toggle' Widget.
2052
2053(define-widget 'toggle 'item
2054 "Toggle between two states."
2055 :format "%[%v%]\n"
2056 :value-create 'widget-toggle-value-create
2057 :action 'widget-toggle-action
2058 :match (lambda (widget value) t)
2059 :on "on"
2060 :off "off")
2061
2062(defun widget-toggle-value-create (widget)
bfa6c260 2063 "Insert text representing the `on' and `off' states."
d543e20b 2064 (if (widget-value widget)
3058e436 2065 (let ((image (widget-get widget :on-glyph)))
805e9a05 2066 (and (display-graphic-p)
3058e436
MB
2067 (listp image)
2068 (not (eq (car image) 'image))
2069 (widget-put widget :on-glyph (setq image (eval image))))
805e9a05
RS
2070 (widget-image-insert widget
2071 (widget-get widget :on)
3058e436
MB
2072 image))
2073 (let ((image (widget-get widget :off-glyph)))
2074 (and (display-graphic-p)
2075 (listp image)
2076 (not (eq (car image) 'image))
2077 (widget-put widget :off-glyph (setq image (eval image))))
2078 (widget-image-insert widget (widget-get widget :off) image))))
d543e20b
PA
2079
2080(defun widget-toggle-action (widget &optional event)
2081 ;; Toggle value.
d0acc4ea
RS
2082 (widget-value-set widget (not (widget-value widget)))
2083 (widget-apply widget :notify widget event)
d4b8422f 2084 (run-hook-with-args 'widget-edit-functions widget))
6d528fc5 2085
d543e20b
PA
2086;;; The `checkbox' Widget.
2087
2088(define-widget 'checkbox 'toggle
2089 "A checkbox toggle."
25ac13b5
PA
2090 :button-suffix ""
2091 :button-prefix ""
d543e20b
PA
2092 :format "%[%v%]"
2093 :on "[X]"
35a7ac84
DL
2094 ;; We could probably do the same job as the images using single
2095 ;; space characters in a boxed face with a stretch specification to
2096 ;; make them square.
8cf30128
KS
2097 :on-glyph '(create-image "\300\300\141\143\067\076\034\030"
2098 'xbm t :width 8 :height 8
805e9a05 2099 :background "grey75" ; like default mode line
b6715b9f 2100 :foreground "black"
8cf30128 2101 :relief -2
1ed74431 2102 :ascent 'center)
805e9a05 2103 :off "[ ]"
8cf30128
KS
2104 :off-glyph '(create-image (make-string 8 0)
2105 'xbm t :width 8 :height 8
805e9a05
RS
2106 :background "grey75"
2107 :foreground "black"
8cf30128 2108 :relief -2
805e9a05 2109 :ascent 'center)
99f01612 2110 :help-echo "Toggle this item."
d543e20b
PA
2111 :action 'widget-checkbox-action)
2112
2113(defun widget-checkbox-action (widget &optional event)
2114 "Toggle checkbox, notify parent, and set active state of sibling."
2115 (widget-toggle-action widget event)
2116 (let ((sibling (widget-get-sibling widget)))
2117 (when sibling
2118 (if (widget-value widget)
2119 (widget-apply sibling :activate)
2120 (widget-apply sibling :deactivate)))))
2121
2122;;; The `checklist' Widget.
2123
2124(define-widget 'checklist 'default
2125 "A multiple choice widget."
2126 :convert-widget 'widget-types-convert-widget
4c2f559e 2127 :copy 'widget-types-copy
d543e20b
PA
2128 :format "%v"
2129 :offset 4
2130 :entry-format "%b %v"
d543e20b
PA
2131 :greedy nil
2132 :value-create 'widget-checklist-value-create
d543e20b
PA
2133 :value-get 'widget-checklist-value-get
2134 :validate 'widget-checklist-validate
2135 :match 'widget-checklist-match
2136 :match-inline 'widget-checklist-match-inline)
2137
2138(defun widget-checklist-value-create (widget)
2139 ;; Insert all values
2140 (let ((alist (widget-checklist-match-find widget (widget-get widget :value)))
2141 (args (widget-get widget :args)))
bfa6c260 2142 (while args
d543e20b
PA
2143 (widget-checklist-add-item widget (car args) (assq (car args) alist))
2144 (setq args (cdr args)))
2145 (widget-put widget :children (nreverse (widget-get widget :children)))))
2146
2147(defun widget-checklist-add-item (widget type chosen)
bfa6c260
DL
2148 "Create checklist item in WIDGET of type TYPE.
2149If the item is checked, CHOSEN is a cons whose cdr is the value."
d543e20b
PA
2150 (and (eq (preceding-char) ?\n)
2151 (widget-get widget :indent)
2152 (insert-char ? (widget-get widget :indent)))
bfa6c260 2153 (widget-specify-insert
d543e20b
PA
2154 (let* ((children (widget-get widget :children))
2155 (buttons (widget-get widget :buttons))
2156 (button-args (or (widget-get type :sibling-args)
2157 (widget-get widget :button-args)))
2158 (from (point))
2159 child button)
2160 (insert (widget-get widget :entry-format))
2161 (goto-char from)
2162 ;; Parse % escapes in format.
2163 (while (re-search-forward "%\\([bv%]\\)" nil t)
7fdbdbea
DL
2164 (let ((escape (char-after (match-beginning 1))))
2165 (delete-backward-char 2)
d543e20b 2166 (cond ((eq escape ?%)
bfa6c260 2167 (insert ?%))
d543e20b
PA
2168 ((eq escape ?b)
2169 (setq button (apply 'widget-create-child-and-convert
2170 widget 'checkbox
2171 :value (not (null chosen))
2172 button-args)))
2173 ((eq escape ?v)
2174 (setq child
2175 (cond ((not chosen)
2176 (let ((child (widget-create-child widget type)))
2177 (widget-apply child :deactivate)
2178 child))
2179 ((widget-get type :inline)
2180 (widget-create-child-value
2181 widget type (cdr chosen)))
2182 (t
2183 (widget-create-child-value
2184 widget type (car (cdr chosen)))))))
bfa6c260 2185 (t
d543e20b
PA
2186 (error "Unknown escape `%c'" escape)))))
2187 ;; Update properties.
2188 (and button child (widget-put child :button button))
2189 (and button (widget-put widget :buttons (cons button buttons)))
2190 (and child (widget-put widget :children (cons child children))))))
2191
2192(defun widget-checklist-match (widget values)
2193 ;; All values must match a type in the checklist.
2194 (and (listp values)
2195 (null (cdr (widget-checklist-match-inline widget values)))))
2196
2197(defun widget-checklist-match-inline (widget values)
2198 ;; Find the values which match a type in the checklist.
2199 (let ((greedy (widget-get widget :greedy))
ef3f635f 2200 (args (copy-sequence (widget-get widget :args)))
d543e20b
PA
2201 found rest)
2202 (while values
2203 (let ((answer (widget-checklist-match-up args values)))
bfa6c260 2204 (cond (answer
d543e20b
PA
2205 (let ((vals (widget-match-inline answer values)))
2206 (setq found (append found (car vals))
2207 values (cdr vals)
2208 args (delq answer args))))
2209 (greedy
2210 (setq rest (append rest (list (car values)))
2211 values (cdr values)))
bfa6c260 2212 (t
d543e20b
PA
2213 (setq rest (append rest values)
2214 values nil)))))
2215 (cons found rest)))
2216
2217(defun widget-checklist-match-find (widget vals)
bfa6c260
DL
2218 "Find the vals which match a type in the checklist.
2219Return an alist of (TYPE MATCH)."
d543e20b 2220 (let ((greedy (widget-get widget :greedy))
ef3f635f 2221 (args (copy-sequence (widget-get widget :args)))
d543e20b
PA
2222 found)
2223 (while vals
2224 (let ((answer (widget-checklist-match-up args vals)))
bfa6c260 2225 (cond (answer
d543e20b
PA
2226 (let ((match (widget-match-inline answer vals)))
2227 (setq found (cons (cons answer (car match)) found)
2228 vals (cdr match)
2229 args (delq answer args))))
2230 (greedy
2231 (setq vals (cdr vals)))
bfa6c260 2232 (t
d543e20b
PA
2233 (setq vals nil)))))
2234 found))
2235
2236(defun widget-checklist-match-up (args vals)
bfa6c260 2237 "Return the first type from ARGS that matches VALS."
d543e20b
PA
2238 (let (current found)
2239 (while (and args (null found))
2240 (setq current (car args)
2241 args (cdr args)
2242 found (widget-match-inline current vals)))
2243 (if found
bfa6c260 2244 current)))
d543e20b
PA
2245
2246(defun widget-checklist-value-get (widget)
2247 ;; The values of all selected items.
2248 (let ((children (widget-get widget :children))
2249 child result)
bfa6c260 2250 (while children
d543e20b
PA
2251 (setq child (car children)
2252 children (cdr children))
2253 (if (widget-value (widget-get child :button))
2254 (setq result (append result (widget-apply child :value-inline)))))
2255 result))
2256
2257(defun widget-checklist-validate (widget)
2258 ;; Ticked chilren must be valid.
2259 (let ((children (widget-get widget :children))
2260 child button found)
2261 (while (and children (not found))
2262 (setq child (car children)
2263 children (cdr children)
2264 button (widget-get child :button)
2265 found (and (widget-value button)
2266 (widget-apply child :validate))))
2267 found))
2268
2269;;; The `option' Widget
2270
2271(define-widget 'option 'checklist
2272 "An widget with an optional item."
2273 :inline t)
2274
2275;;; The `choice-item' Widget.
2276
2277(define-widget 'choice-item 'item
2278 "Button items that delegate action events to their parents."
a3c88c59 2279 :action 'widget-parent-action
d543e20b
PA
2280 :format "%[%t%] \n")
2281
d543e20b
PA
2282;;; The `radio-button' Widget.
2283
2284(define-widget 'radio-button 'toggle
2285 "A radio button for use in the `radio' widget."
2286 :notify 'widget-radio-button-notify
2287 :format "%[%v%]"
25ac13b5
PA
2288 :button-suffix ""
2289 :button-prefix ""
d543e20b
PA
2290 :on "(*)"
2291 :on-glyph "radio1"
2292 :off "( )"
2293 :off-glyph "radio0")
2294
2295(defun widget-radio-button-notify (widget child &optional event)
2296 ;; Tell daddy.
15aa7790 2297 (widget-apply (widget-get widget :parent) :action widget event))
d543e20b
PA
2298
2299;;; The `radio-button-choice' Widget.
2300
2301(define-widget 'radio-button-choice 'default
2302 "Select one of multiple options."
2303 :convert-widget 'widget-types-convert-widget
4c2f559e 2304 :copy 'widget-types-copy
d543e20b
PA
2305 :offset 4
2306 :format "%v"
2307 :entry-format "%b %v"
d543e20b 2308 :value-create 'widget-radio-value-create
d543e20b
PA
2309 :value-get 'widget-radio-value-get
2310 :value-inline 'widget-radio-value-inline
2311 :value-set 'widget-radio-value-set
2312 :error "You must push one of the buttons"
2313 :validate 'widget-radio-validate
2314 :match 'widget-choice-match
2315 :match-inline 'widget-choice-match-inline
2316 :action 'widget-radio-action)
2317
2318(defun widget-radio-value-create (widget)
2319 ;; Insert all values
2320 (let ((args (widget-get widget :args))
2321 arg)
bfa6c260 2322 (while args
d543e20b
PA
2323 (setq arg (car args)
2324 args (cdr args))
2325 (widget-radio-add-item widget arg))))
2326
2327(defun widget-radio-add-item (widget type)
2328 "Add to radio widget WIDGET a new radio button item of type TYPE."
2329 ;; (setq type (widget-convert type))
2330 (and (eq (preceding-char) ?\n)
2331 (widget-get widget :indent)
2332 (insert-char ? (widget-get widget :indent)))
bfa6c260 2333 (widget-specify-insert
d543e20b
PA
2334 (let* ((value (widget-get widget :value))
2335 (children (widget-get widget :children))
2336 (buttons (widget-get widget :buttons))
2337 (button-args (or (widget-get type :sibling-args)
2338 (widget-get widget :button-args)))
2339 (from (point))
2340 (chosen (and (null (widget-get widget :choice))
2341 (widget-apply type :match value)))
2342 child button)
2343 (insert (widget-get widget :entry-format))
2344 (goto-char from)
2345 ;; Parse % escapes in format.
2346 (while (re-search-forward "%\\([bv%]\\)" nil t)
7fdbdbea
DL
2347 (let ((escape (char-after (match-beginning 1))))
2348 (delete-backward-char 2)
d543e20b 2349 (cond ((eq escape ?%)
bfa6c260 2350 (insert ?%))
d543e20b
PA
2351 ((eq escape ?b)
2352 (setq button (apply 'widget-create-child-and-convert
bfa6c260 2353 widget 'radio-button
d543e20b
PA
2354 :value (not (null chosen))
2355 button-args)))
2356 ((eq escape ?v)
2357 (setq child (if chosen
2358 (widget-create-child-value
2359 widget type value)
2360 (widget-create-child widget type)))
bfa6c260 2361 (unless chosen
d543e20b 2362 (widget-apply child :deactivate)))
bfa6c260 2363 (t
d543e20b
PA
2364 (error "Unknown escape `%c'" escape)))))
2365 ;; Update properties.
2366 (when chosen
2367 (widget-put widget :choice type))
bfa6c260 2368 (when button
d543e20b
PA
2369 (widget-put child :button button)
2370 (widget-put widget :buttons (nconc buttons (list button))))
2371 (when child
2372 (widget-put widget :children (nconc children (list child))))
2373 child)))
2374
2375(defun widget-radio-value-get (widget)
2376 ;; Get value of the child widget.
2377 (let ((chosen (widget-radio-chosen widget)))
2378 (and chosen (widget-value chosen))))
2379
2380(defun widget-radio-chosen (widget)
2381 "Return the widget representing the chosen radio button."
2382 (let ((children (widget-get widget :children))
2383 current found)
2384 (while children
2385 (setq current (car children)
2386 children (cdr children))
7fdbdbea
DL
2387 (when (widget-apply (widget-get current :button) :value-get)
2388 (setq found current
2389 children nil)))
d543e20b
PA
2390 found))
2391
2392(defun widget-radio-value-inline (widget)
2393 ;; Get value of the child widget.
2394 (let ((children (widget-get widget :children))
2395 current found)
2396 (while children
2397 (setq current (car children)
2398 children (cdr children))
7fdbdbea
DL
2399 (when (widget-apply (widget-get current :button) :value-get)
2400 (setq found (widget-apply current :value-inline)
2401 children nil)))
d543e20b
PA
2402 found))
2403
2404(defun widget-radio-value-set (widget value)
2405 ;; We can't just delete and recreate a radio widget, since children
2406 ;; can be added after the original creation and won't be recreated
2407 ;; by `:create'.
2408 (let ((children (widget-get widget :children))
2409 current found)
2410 (while children
2411 (setq current (car children)
2412 children (cdr children))
2413 (let* ((button (widget-get current :button))
2414 (match (and (not found)
2415 (widget-apply current :match value))))
2416 (widget-value-set button match)
bfa6c260
DL
2417 (if match
2418 (progn
d543e20b
PA
2419 (widget-value-set current value)
2420 (widget-apply current :activate))
2421 (widget-apply current :deactivate))
2422 (setq found (or found match))))))
2423
2424(defun widget-radio-validate (widget)
2425 ;; Valid if we have made a valid choice.
2426 (let ((children (widget-get widget :children))
2427 current found button)
2428 (while (and children (not found))
2429 (setq current (car children)
2430 children (cdr children)
2431 button (widget-get current :button)
2432 found (widget-apply button :value-get)))
2433 (if found
2434 (widget-apply current :validate)
2435 widget)))
2436
2437(defun widget-radio-action (widget child event)
2438 ;; Check if a radio button was pressed.
2439 (let ((children (widget-get widget :children))
2440 (buttons (widget-get widget :buttons))
2441 current)
2442 (when (memq child buttons)
2443 (while children
2444 (setq current (car children)
2445 children (cdr children))
2446 (let* ((button (widget-get current :button)))
2447 (cond ((eq child button)
2448 (widget-value-set button t)
2449 (widget-apply current :activate))
2450 ((widget-value button)
2451 (widget-value-set button nil)
2452 (widget-apply current :deactivate)))))))
2453 ;; Pass notification to parent.
2454 (widget-apply widget :notify child event))
2455
2456;;; The `insert-button' Widget.
2457
2458(define-widget 'insert-button 'push-button
2ff864e0
DL
2459 "An insert button for the `editable-list' widget."
2460 :tag "INS"
2461 :help-echo "Insert a new item into the list at this position."
d543e20b
PA
2462 :action 'widget-insert-button-action)
2463
2464(defun widget-insert-button-action (widget &optional event)
2465 ;; Ask the parent to insert a new item.
bfa6c260 2466 (widget-apply (widget-get widget :parent)
d543e20b
PA
2467 :insert-before (widget-get widget :widget)))
2468
2ff864e0
DL
2469;;; The `delete-button' Widget.
2470
2471(define-widget 'delete-button 'push-button
2472 "A delete button for the `editable-list' widget."
2473 :tag "DEL"
2474 :help-echo "Delete this item from the list."
2475 :action 'widget-delete-button-action)
2476
2477(defun widget-delete-button-action (widget &optional event)
2478 ;; Ask the parent to insert a new item.
2479 (widget-apply (widget-get widget :parent)
2480 :delete-at (widget-get widget :widget)))
2481
d543e20b
PA
2482;;; The `editable-list' Widget.
2483
7fdbdbea
DL
2484;; (defcustom widget-editable-list-gui nil
2485;; "If non nil, use GUI push-buttons in editable list when available."
2486;; :type 'boolean
2487;; :group 'widgets)
d543e20b
PA
2488
2489(define-widget 'editable-list 'default
2490 "A variable list of widgets of the same type."
2491 :convert-widget 'widget-types-convert-widget
4c2f559e 2492 :copy 'widget-types-copy
d543e20b
PA
2493 :offset 12
2494 :format "%v%i\n"
2495 :format-handler 'widget-editable-list-format-handler
2ff864e0 2496 :entry-format "%i %d %v"
d543e20b 2497 :value-create 'widget-editable-list-value-create
d543e20b 2498 :value-get 'widget-editable-list-value-get
a3c88c59 2499 :validate 'widget-children-validate
d543e20b
PA
2500 :match 'widget-editable-list-match
2501 :match-inline 'widget-editable-list-match-inline
2502 :insert-before 'widget-editable-list-insert-before
2503 :delete-at 'widget-editable-list-delete-at)
2504
2505(defun widget-editable-list-format-handler (widget escape)
2506 ;; We recognize the insert button.
407e43be 2507 ;; (let ((widget-push-button-gui widget-editable-list-gui))
d543e20b
PA
2508 (cond ((eq escape ?i)
2509 (and (widget-get widget :indent)
7fdbdbea 2510 (insert-char ?\ (widget-get widget :indent)))
bfa6c260 2511 (apply 'widget-create-child-and-convert
d543e20b
PA
2512 widget 'insert-button
2513 (widget-get widget :append-button-args)))
bfa6c260 2514 (t
7fdbdbea 2515 (widget-default-format-handler widget escape)))
407e43be 2516 ;; )
7fdbdbea 2517 )
d543e20b
PA
2518
2519(defun widget-editable-list-value-create (widget)
2520 ;; Insert all values
2521 (let* ((value (widget-get widget :value))
2522 (type (nth 0 (widget-get widget :args)))
d543e20b
PA
2523 children)
2524 (widget-put widget :value-pos (copy-marker (point)))
2525 (set-marker-insertion-type (widget-get widget :value-pos) t)
2526 (while value
2527 (let ((answer (widget-match-inline type value)))
2528 (if answer
2529 (setq children (cons (widget-editable-list-entry-create
2530 widget
7fdbdbea 2531 (if (widget-get type :inline)
d543e20b
PA
2532 (car answer)
2533 (car (car answer)))
2534 t)
2535 children)
2536 value (cdr answer))
2537 (setq value nil))))
2538 (widget-put widget :children (nreverse children))))
2539
2540(defun widget-editable-list-value-get (widget)
2541 ;; Get value of the child widget.
2542 (apply 'append (mapcar (lambda (child) (widget-apply child :value-inline))
2543 (widget-get widget :children))))
2544
d543e20b
PA
2545(defun widget-editable-list-match (widget value)
2546 ;; Value must be a list and all the members must match the type.
2547 (and (listp value)
2548 (null (cdr (widget-editable-list-match-inline widget value)))))
2549
2550(defun widget-editable-list-match-inline (widget value)
2551 (let ((type (nth 0 (widget-get widget :args)))
2552 (ok t)
2553 found)
2554 (while (and value ok)
2555 (let ((answer (widget-match-inline type value)))
bfa6c260 2556 (if answer
d543e20b
PA
2557 (setq found (append found (car answer))
2558 value (cdr answer))
2559 (setq ok nil))))
2560 (cons found value)))
2561
2562(defun widget-editable-list-insert-before (widget before)
2563 ;; Insert a new child in the list of children.
2564 (save-excursion
2565 (let ((children (widget-get widget :children))
2566 (inhibit-read-only t)
c6753d66 2567 before-change-functions
d543e20b 2568 after-change-functions)
bfa6c260 2569 (cond (before
d543e20b
PA
2570 (goto-char (widget-get before :entry-from)))
2571 (t
2572 (goto-char (widget-get widget :value-pos))))
bfa6c260 2573 (let ((child (widget-editable-list-entry-create
d543e20b
PA
2574 widget nil nil)))
2575 (when (< (widget-get child :entry-from) (widget-get widget :from))
2576 (set-marker (widget-get widget :from)
2577 (widget-get child :entry-from)))
d543e20b
PA
2578 (if (eq (car children) before)
2579 (widget-put widget :children (cons child children))
2580 (while (not (eq (car (cdr children)) before))
2581 (setq children (cdr children)))
2582 (setcdr children (cons child (cdr children)))))))
2583 (widget-setup)
0a3a0b56 2584 (widget-apply widget :notify widget))
d543e20b
PA
2585
2586(defun widget-editable-list-delete-at (widget child)
2587 ;; Delete child from list of children.
2588 (save-excursion
ef3f635f 2589 (let ((buttons (copy-sequence (widget-get widget :buttons)))
d543e20b
PA
2590 button
2591 (inhibit-read-only t)
c6753d66 2592 before-change-functions
d543e20b
PA
2593 after-change-functions)
2594 (while buttons
2595 (setq button (car buttons)
2596 buttons (cdr buttons))
2597 (when (eq (widget-get button :widget) child)
2598 (widget-put widget
2599 :buttons (delq button (widget-get widget :buttons)))
2600 (widget-delete button))))
2601 (let ((entry-from (widget-get child :entry-from))
2602 (entry-to (widget-get child :entry-to))
2603 (inhibit-read-only t)
c6753d66 2604 before-change-functions
d543e20b
PA
2605 after-change-functions)
2606 (widget-delete child)
2607 (delete-region entry-from entry-to)
2608 (set-marker entry-from nil)
2609 (set-marker entry-to nil))
2610 (widget-put widget :children (delq child (widget-get widget :children))))
2611 (widget-setup)
2612 (widget-apply widget :notify widget))
2613
2614(defun widget-editable-list-entry-create (widget value conv)
2615 ;; Create a new entry to the list.
2616 (let ((type (nth 0 (widget-get widget :args)))
407e43be 2617 ;; (widget-push-button-gui widget-editable-list-gui)
2ff864e0 2618 child delete insert)
bfa6c260 2619 (widget-specify-insert
d543e20b
PA
2620 (save-excursion
2621 (and (widget-get widget :indent)
7fdbdbea 2622 (insert-char ?\ (widget-get widget :indent)))
d543e20b
PA
2623 (insert (widget-get widget :entry-format)))
2624 ;; Parse % escapes in format.
2625 (while (re-search-forward "%\\(.\\)" nil t)
7fdbdbea
DL
2626 (let ((escape (char-after (match-beginning 1))))
2627 (delete-backward-char 2)
d543e20b 2628 (cond ((eq escape ?%)
bfa6c260 2629 (insert ?%))
2ff864e0
DL
2630 ((eq escape ?i)
2631 (setq insert (apply 'widget-create-child-and-convert
2632 widget 'insert-button
2633 (widget-get widget :insert-button-args))))
2634 ((eq escape ?d)
2635 (setq delete (apply 'widget-create-child-and-convert
2636 widget 'delete-button
2637 (widget-get widget :delete-button-args))))
d543e20b
PA
2638 ((eq escape ?v)
2639 (if conv
bfa6c260 2640 (setq child (widget-create-child-value
d543e20b 2641 widget type value))
bfa6c260 2642 (setq child (widget-create-child-value
4c2f559e 2643 widget type (widget-default-get type)))))
bfa6c260 2644 (t
d543e20b 2645 (error "Unknown escape `%c'" escape)))))
407e43be
SM
2646 (let ((buttons (widget-get widget :buttons)))
2647 (if insert (push insert buttons))
2648 (if delete (push delete buttons))
2649 (widget-put widget :buttons buttons))
7fdbdbea
DL
2650 (let ((entry-from (point-min-marker))
2651 (entry-to (point-max-marker)))
d543e20b
PA
2652 (set-marker-insertion-type entry-from t)
2653 (set-marker-insertion-type entry-to nil)
2654 (widget-put child :entry-from entry-from)
2655 (widget-put child :entry-to entry-to)))
407e43be
SM
2656 (if insert (widget-put insert :widget child))
2657 (if delete (widget-put delete :widget child))
d543e20b
PA
2658 child))
2659
2660;;; The `group' Widget.
2661
2662(define-widget 'group 'default
a89a9d34 2663 "A widget which groups other widgets inside."
d543e20b 2664 :convert-widget 'widget-types-convert-widget
4c2f559e 2665 :copy 'widget-types-copy
d543e20b
PA
2666 :format "%v"
2667 :value-create 'widget-group-value-create
d543e20b 2668 :value-get 'widget-editable-list-value-get
783824f5 2669 :default-get 'widget-group-default-get
a3c88c59 2670 :validate 'widget-children-validate
d543e20b
PA
2671 :match 'widget-group-match
2672 :match-inline 'widget-group-match-inline)
2673
2674(defun widget-group-value-create (widget)
2675 ;; Create each component.
2676 (let ((args (widget-get widget :args))
2677 (value (widget-get widget :value))
2678 arg answer children)
2679 (while args
2680 (setq arg (car args)
2681 args (cdr args)
2682 answer (widget-match-inline arg value)
2683 value (cdr answer))
2684 (and (eq (preceding-char) ?\n)
2685 (widget-get widget :indent)
7fdbdbea 2686 (insert-char ?\ (widget-get widget :indent)))
3acab5ef
PA
2687 (push (cond ((null answer)
2688 (widget-create-child widget arg))
2689 ((widget-get arg :inline)
7fdbdbea 2690 (widget-create-child-value widget arg (car answer)))
3acab5ef 2691 (t
7fdbdbea 2692 (widget-create-child-value widget arg (car (car answer)))))
3acab5ef 2693 children))
d543e20b
PA
2694 (widget-put widget :children (nreverse children))))
2695
783824f5
RS
2696(defun widget-group-default-get (widget)
2697 ;; Get the default of the components.
2698 (mapcar 'widget-default-get (widget-get widget :args)))
2699
d543e20b
PA
2700(defun widget-group-match (widget values)
2701 ;; Match if the components match.
2702 (and (listp values)
2703 (let ((match (widget-group-match-inline widget values)))
2704 (and match (null (cdr match))))))
2705
2706(defun widget-group-match-inline (widget vals)
2707 ;; Match if the components match.
2708 (let ((args (widget-get widget :args))
2709 argument answer found)
2710 (while args
2711 (setq argument (car args)
2712 args (cdr args)
2713 answer (widget-match-inline argument vals))
bfa6c260 2714 (if answer
d543e20b
PA
2715 (setq vals (cdr answer)
2716 found (append found (car answer)))
2717 (setq vals nil
2718 args nil)))
2719 (if answer
bfa6c260 2720 (cons found vals))))
d543e20b 2721
3acab5ef 2722;;; The `visibility' Widget.
d543e20b 2723
3acab5ef
PA
2724(define-widget 'visibility 'item
2725 "An indicator and manipulator for hidden items."
2726 :format "%[%v%]"
2727 :button-prefix ""
2728 :button-suffix ""
c6753d66
RS
2729 :on "Hide"
2730 :off "Show"
3acab5ef
PA
2731 :value-create 'widget-visibility-value-create
2732 :action 'widget-toggle-action
2733 :match (lambda (widget value) t))
d543e20b 2734
3acab5ef
PA
2735(defun widget-visibility-value-create (widget)
2736 ;; Insert text representing the `on' and `off' states.
2737 (let ((on (widget-get widget :on))
2738 (off (widget-get widget :off)))
2739 (if on
2740 (setq on (concat widget-push-button-prefix
2741 on
2742 widget-push-button-suffix))
2743 (setq on ""))
2744 (if off
2745 (setq off (concat widget-push-button-prefix
c6753d66
RS
2746 off
2747 widget-push-button-suffix))
3acab5ef
PA
2748 (setq off ""))
2749 (if (widget-value widget)
bfa6c260
DL
2750 (widget-image-insert widget on "down" "down-pushed")
2751 (widget-image-insert widget off "right" "right-pushed"))))
c6753d66 2752
8697863a
PA
2753;;; The `documentation-link' Widget.
2754;;
2755;; This is a helper widget for `documentation-string'.
3acab5ef 2756
8697863a
PA
2757(define-widget 'documentation-link 'link
2758 "Link type used in documentation strings."
2759 :tab-order -1
bfa6c260 2760 :help-echo "Describe this symbol"
8697863a
PA
2761 :action 'widget-documentation-link-action)
2762
8697863a 2763(defun widget-documentation-link-action (widget &optional event)
f9923499 2764 "Display documentation for WIDGET's value. Ignore optional argument EVENT."
9dccd7ef
RS
2765 (let* ((string (widget-get widget :value))
2766 (symbol (intern string)))
2767 (if (and (fboundp symbol) (boundp symbol))
f9923499 2768 ;; If there are two doc strings, give the user a way to pick one.
9dccd7ef
RS
2769 (apropos (concat "\\`" (regexp-quote string) "\\'"))
2770 (if (fboundp symbol)
2771 (describe-function symbol)
2772 (describe-variable symbol)))))
8697863a
PA
2773
2774(defcustom widget-documentation-links t
2775 "Add hyperlinks to documentation strings when non-nil."
2776 :type 'boolean
2777 :group 'widget-documentation)
2778
2779(defcustom widget-documentation-link-regexp "`\\([^\n`' ]+\\)'"
2780 "Regexp for matching potential links in documentation strings.
2781The first group should be the link itself."
2782 :type 'regexp
2783 :group 'widget-documentation)
2784
2785(defcustom widget-documentation-link-p 'intern-soft
2786 "Predicate used to test if a string is useful as a link.
2787The value should be a function. The function will be called one
2788argument, a string, and should return non-nil if there should be a
2789link for that string."
2790 :type 'function
2791 :options '(widget-documentation-link-p)
2792 :group 'widget-documentation)
2793
2794(defcustom widget-documentation-link-type 'documentation-link
2795 "Widget type used for links in documentation strings."
2796 :type 'symbol
2797 :group 'widget-documentation)
2798
2799(defun widget-documentation-link-add (widget from to)
2800 (widget-specify-doc widget from to)
2801 (when widget-documentation-links
2802 (let ((regexp widget-documentation-link-regexp)
a89a9d34
DL
2803 (buttons (widget-get widget :buttons))
2804 (widget-mouse-face (default-value 'widget-mouse-face))
2805 (widget-button-face widget-documentation-face)
2806 (widget-button-pressed-face widget-documentation-face))
8697863a
PA
2807 (save-excursion
2808 (goto-char from)
2809 (while (re-search-forward regexp to t)
2810 (let ((name (match-string 1))
a1a4fa22
PA
2811 (begin (match-beginning 1))
2812 (end (match-end 1)))
7fdbdbea
DL
2813 (when (funcall widget-documentation-link-p name)
2814 (push (widget-convert-button widget-documentation-link-type
2815 begin end :value name)
8697863a
PA
2816 buttons)))))
2817 (widget-put widget :buttons buttons)))
2818 (let ((indent (widget-get widget :indent)))
2819 (when (and indent (not (zerop indent)))
bfa6c260 2820 (save-excursion
8697863a
PA
2821 (save-restriction
2822 (narrow-to-region from to)
2823 (goto-char (point-min))
2824 (while (search-forward "\n" nil t)
2825 (insert-char ?\ indent)))))))
2826
2827;;; The `documentation-string' Widget.
0ce5b5d5 2828
3acab5ef
PA
2829(define-widget 'documentation-string 'item
2830 "A documentation string."
2831 :format "%v"
2832 :action 'widget-documentation-string-action
3acab5ef
PA
2833 :value-create 'widget-documentation-string-value-create)
2834
2835(defun widget-documentation-string-value-create (widget)
2836 ;; Insert documentation string.
2837 (let ((doc (widget-value widget))
8697863a 2838 (indent (widget-get widget :indent))
6aaedd12
PA
2839 (shown (widget-get (widget-get widget :parent) :documentation-shown))
2840 (start (point)))
3acab5ef
PA
2841 (if (string-match "\n" doc)
2842 (let ((before (substring doc 0 (match-beginning 0)))
2843 (after (substring doc (match-beginning 0)))
7fdbdbea 2844 button)
bfa6c260 2845 (insert before ?\ )
8697863a 2846 (widget-documentation-link-add widget start (point))
7fdbdbea
DL
2847 (setq button
2848 (widget-create-child-and-convert
3acab5ef 2849 widget 'visibility
8697863a 2850 :help-echo "Show or hide rest of the documentation."
dd98f00a 2851 :on "Hide Rest"
c6753d66 2852 :off "More"
0640c647 2853 :always-active t
3acab5ef 2854 :action 'widget-parent-action
7fdbdbea 2855 shown))
3acab5ef 2856 (when shown
0ce5b5d5 2857 (setq start (point))
8697863a
PA
2858 (when (and indent (not (zerop indent)))
2859 (insert-char ?\ indent))
0ce5b5d5 2860 (insert after)
8697863a 2861 (widget-documentation-link-add widget start (point)))
7fdbdbea 2862 (widget-put widget :buttons (list button)))
6aaedd12 2863 (insert doc)
8697863a 2864 (widget-documentation-link-add widget start (point))))
bfa6c260 2865 (insert ?\n))
3acab5ef
PA
2866
2867(defun widget-documentation-string-action (widget &rest ignore)
2868 ;; Toggle documentation.
2869 (let ((parent (widget-get widget :parent)))
bfa6c260 2870 (widget-put parent :documentation-shown
3acab5ef
PA
2871 (not (widget-get parent :documentation-shown))))
2872 ;; Redraw.
d543e20b 2873 (widget-value-set widget (widget-value widget)))
fc56773e 2874\f
d543e20b
PA
2875;;; The Sexp Widgets.
2876
2877(define-widget 'const 'item
2878 "An immutable sexp."
6d528fc5 2879 :prompt-value 'widget-const-prompt-value
d543e20b
PA
2880 :format "%t\n%d")
2881
6d528fc5
PA
2882(defun widget-const-prompt-value (widget prompt value unbound)
2883 ;; Return the value of the const.
2884 (widget-value widget))
2885
2886(define-widget 'function-item 'const
d543e20b
PA
2887 "An immutable function name."
2888 :format "%v\n%h"
2889 :documentation-property (lambda (symbol)
2890 (condition-case nil
2891 (documentation symbol t)
2892 (error nil))))
2893
6d528fc5 2894(define-widget 'variable-item 'const
d543e20b
PA
2895 "An immutable variable name."
2896 :format "%v\n%h"
2897 :documentation-property 'variable-documentation)
2898
cc0a25e1
RS
2899(define-widget 'other 'sexp
2900 "Matches any value, but doesn't let the user edit the value.
2901This is useful as last item in a `choice' widget.
2902You should use this widget type with a default value,
b720878d 2903as in (other DEFAULT) or (other :tag \"NAME\" DEFAULT).
cc0a25e1
RS
2904If the user selects this alternative, that specifies DEFAULT
2905as the value."
2906 :tag "Other"
2907 :format "%t%n"
2908 :value 'other)
2909
6d528fc5
PA
2910(defvar widget-string-prompt-value-history nil
2911 "History of input to `widget-string-prompt-value'.")
2912
a3c88c59
PA
2913(define-widget 'string 'editable-field
2914 "A string"
2915 :tag "String"
2916 :format "%{%t%}: %v"
0ce5b5d5 2917 :complete-function 'ispell-complete-word
a3c88c59 2918 :prompt-history 'widget-string-prompt-value-history)
6d528fc5 2919
d543e20b
PA
2920(define-widget 'regexp 'string
2921 "A regular expression."
6d528fc5
PA
2922 :match 'widget-regexp-match
2923 :validate 'widget-regexp-validate
4ee1cf9f
PA
2924 ;; Doesn't work well with terminating newline.
2925 ;; :value-face 'widget-single-line-field-face
d543e20b
PA
2926 :tag "Regexp")
2927
6d528fc5
PA
2928(defun widget-regexp-match (widget value)
2929 ;; Match valid regexps.
2930 (and (stringp value)
a3c88c59 2931 (condition-case nil
6d528fc5
PA
2932 (prog1 t
2933 (string-match value ""))
2934 (error nil))))
2935
2936(defun widget-regexp-validate (widget)
2937 "Check that the value of WIDGET is a valid regexp."
7fdbdbea
DL
2938 (condition-case data
2939 (prog1 nil
2940 (string-match (widget-value widget) ""))
2941 (error (widget-put widget :error (error-message-string data))
2942 widget)))
6d528fc5 2943
d543e20b 2944(define-widget 'file 'string
bfa6c260 2945 "A file widget.
25ac13b5 2946It will read a file name from the minibuffer when invoked."
f1231b8e 2947 :complete-function 'widget-file-complete
6d528fc5 2948 :prompt-value 'widget-file-prompt-value
a3c88c59 2949 :format "%{%t%}: %v"
4ee1cf9f
PA
2950 ;; Doesn't work well with terminating newline.
2951 ;; :value-face 'widget-single-line-field-face
f1231b8e
RS
2952 :tag "File")
2953
2954(defun widget-file-complete ()
2955 "Perform completion on file name preceding point."
2956 (interactive)
2957 (let* ((end (point))
2958 (beg (save-excursion
2959 (skip-chars-backward "^ ")
2960 (point)))
2961 (pattern (buffer-substring beg end))
2962 (name-part (file-name-nondirectory pattern))
2963 (directory (file-name-directory pattern))
2964 (completion (file-name-completion name-part directory)))
2965 (cond ((eq completion t))
2966 ((null completion)
2967 (message "Can't find completion for \"%s\"" pattern)
2968 (ding))
2969 ((not (string= name-part completion))
2970 (delete-region beg end)
2971 (insert (expand-file-name completion directory)))
2972 (t
2973 (message "Making completion list...")
7fdbdbea
DL
2974 (with-output-to-temp-buffer "*Completions*"
2975 (display-completion-list
2976 (sort (file-name-all-completions name-part directory)
2977 'string<)))
f1231b8e 2978 (message "Making completion list...%s" "done")))))
d543e20b 2979
6d528fc5
PA
2980(defun widget-file-prompt-value (widget prompt value unbound)
2981 ;; Read file from minibuffer.
2982 (abbreviate-file-name
2983 (if unbound
2984 (read-file-name prompt)
a3c88c59 2985 (let ((prompt2 (format "%s (default %s) " prompt value))
6d528fc5
PA
2986 (dir (file-name-directory value))
2987 (file (file-name-nondirectory value))
2988 (must-match (widget-get widget :must-match)))
2989 (read-file-name prompt2 dir nil must-match file)))))
2990
f1231b8e
RS
2991;;;(defun widget-file-action (widget &optional event)
2992;;; ;; Read a file name from the minibuffer.
2993;;; (let* ((value (widget-value widget))
2994;;; (dir (file-name-directory value))
2995;;; (file (file-name-nondirectory value))
2996;;; (menu-tag (widget-apply widget :menu-tag-get))
2997;;; (must-match (widget-get widget :must-match))
2998;;; (answer (read-file-name (concat menu-tag ": (default `" value "') ")
2999;;; dir nil must-match file)))
3000;;; (widget-value-set widget (abbreviate-file-name answer))
3001;;; (widget-setup)
3002;;; (widget-apply widget :notify widget event)))
d543e20b 3003
bd1f16ce 3004;; Fixme: use file-name-as-directory.
d543e20b 3005(define-widget 'directory 'file
bfa6c260 3006 "A directory widget.
25ac13b5 3007It will read a directory name from the minibuffer when invoked."
d543e20b
PA
3008 :tag "Directory")
3009
a3c88c59
PA
3010(defvar widget-symbol-prompt-value-history nil
3011 "History of input to `widget-symbol-prompt-value'.")
3012
3013(define-widget 'symbol 'editable-field
4084d128 3014 "A Lisp symbol."
d543e20b
PA
3015 :value nil
3016 :tag "Symbol"
a3c88c59 3017 :format "%{%t%}: %v"
d543e20b 3018 :match (lambda (widget value) (symbolp value))
f1231b8e 3019 :complete-function 'lisp-complete-symbol
a3c88c59
PA
3020 :prompt-internal 'widget-symbol-prompt-internal
3021 :prompt-match 'symbolp
3022 :prompt-history 'widget-symbol-prompt-value-history
d543e20b
PA
3023 :value-to-internal (lambda (widget value)
3024 (if (symbolp value)
3025 (symbol-name value)
3026 value))
3027 :value-to-external (lambda (widget value)
3028 (if (stringp value)
3029 (intern value)
3030 value)))
3031
a3c88c59
PA
3032(defun widget-symbol-prompt-internal (widget prompt initial history)
3033 ;; Read file from minibuffer.
bfa6c260 3034 (let ((answer (completing-read prompt obarray
a3c88c59
PA
3035 (widget-get widget :prompt-match)
3036 nil initial history)))
3037 (if (and (stringp answer)
3038 (not (zerop (length answer))))
3039 answer
3040 (error "No value"))))
3041
3042(defvar widget-function-prompt-value-history nil
3043 "History of input to `widget-function-prompt-value'.")
3044
d543e20b 3045(define-widget 'function 'sexp
4084d128 3046 "A Lisp function."
7fdbdbea
DL
3047 :complete-function (lambda ()
3048 (interactive)
3049 (lisp-complete-symbol 'fboundp))
a3c88c59
PA
3050 :prompt-value 'widget-field-prompt-value
3051 :prompt-internal 'widget-symbol-prompt-internal
3052 :prompt-match 'fboundp
3053 :prompt-history 'widget-function-prompt-value-history
3054 :action 'widget-field-action
bd1f16ce 3055 :match-alternatives '(functionp)
7fdbdbea
DL
3056 :validate (lambda (widget)
3057 (unless (functionp (widget-value widget))
3058 (widget-put widget :error (format "Invalid function: %S"
3059 (widget-value widget)))
3060 widget))
3061 :value 'ignore
d543e20b
PA
3062 :tag "Function")
3063
a3c88c59
PA
3064(defvar widget-variable-prompt-value-history nil
3065 "History of input to `widget-variable-prompt-value'.")
3066
d543e20b 3067(define-widget 'variable 'symbol
be96282a 3068 "A Lisp variable."
a3c88c59
PA
3069 :prompt-match 'boundp
3070 :prompt-history 'widget-variable-prompt-value-history
7fdbdbea
DL
3071 :complete-function (lambda ()
3072 (interactive)
3073 (lisp-complete-symbol 'boundp))
d543e20b
PA
3074 :tag "Variable")
3075
fc56773e
RS
3076(defvar widget-coding-system-prompt-value-history nil
3077 "History of input to `widget-coding-system-prompt-value'.")
77339a6e 3078
fc56773e
RS
3079(define-widget 'coding-system 'symbol
3080 "A MULE coding-system."
3081 :format "%{%t%}: %v"
3082 :tag "Coding system"
7fdbdbea 3083 :base-only nil
fc56773e
RS
3084 :prompt-history 'widget-coding-system-prompt-value-history
3085 :prompt-value 'widget-coding-system-prompt-value
7fdbdbea
DL
3086 :action 'widget-coding-system-action
3087 :complete-function (lambda ()
3088 (interactive)
3089 (lisp-complete-symbol 'coding-system-p))
3090 :validate (lambda (widget)
3091 (unless (coding-system-p (widget-value widget))
3092 (widget-put widget :error (format "Invalid coding system: %S"
3093 (widget-value widget)))
3094 widget))
3095 :value 'undecided
3096 :prompt-match 'coding-system-p)
3097
fc56773e 3098(defun widget-coding-system-prompt-value (widget prompt value unbound)
7fdbdbea
DL
3099 "Read coding-system from minibuffer."
3100 (if (widget-get widget :base-only)
3101 (intern
3102 (completing-read (format "%s (default %s) " prompt value)
3103 (mapcar #'list (coding-system-list t)) nil nil nil
3104 coding-system-history))
3105 (read-coding-system (format "%s (default %s) " prompt value) value)))
fc56773e
RS
3106
3107(defun widget-coding-system-action (widget &optional event)
fc56773e
RS
3108 (let ((answer
3109 (widget-coding-system-prompt-value
3110 widget
3111 (widget-apply widget :menu-tag-get)
3112 (widget-value widget)
3113 t)))
3114 (widget-value-set widget answer)
3115 (widget-apply widget :notify widget event)
3116 (widget-setup)))
fc56773e 3117\f
a3c88c59 3118(define-widget 'sexp 'editable-field
be96282a 3119 "An arbitrary Lisp expression."
d543e20b 3120 :tag "Lisp expression"
a3c88c59 3121 :format "%{%t%}: %v"
d543e20b
PA
3122 :value nil
3123 :validate 'widget-sexp-validate
3124 :match (lambda (widget value) t)
3125 :value-to-internal 'widget-sexp-value-to-internal
6d528fc5 3126 :value-to-external (lambda (widget value) (read value))
a3c88c59 3127 :prompt-history 'widget-sexp-prompt-value-history
6d528fc5 3128 :prompt-value 'widget-sexp-prompt-value)
d543e20b
PA
3129
3130(defun widget-sexp-value-to-internal (widget value)
3131 ;; Use pp for printer representation.
6d1ab9d4
RS
3132 (let ((pp (if (symbolp value)
3133 (prin1-to-string value)
3134 (pp-to-string value))))
d543e20b
PA
3135 (while (string-match "\n\\'" pp)
3136 (setq pp (substring pp 0 -1)))
3137 (if (or (string-match "\n\\'" pp)
3138 (> (length pp) 40))
3139 (concat "\n" pp)
3140 pp)))
3141
3142(defun widget-sexp-validate (widget)
3143 ;; Valid if we can read the string and there is no junk left after it.
99f01612
DL
3144 (with-temp-buffer
3145 (insert (widget-apply widget :value-get))
3146 (goto-char (point-min))
1d869634
DL
3147 (let (err)
3148 (condition-case data
3149 (progn
3150 ;; Avoid a confusing end-of-file error.
3151 (skip-syntax-forward "\\s-")
3152 (if (eobp)
3153 (setq err "Empty sexp -- use `nil'?")
7fdbdbea 3154 (unless (widget-apply widget :match (read (current-buffer)))
1d869634
DL
3155 (setq err (widget-get widget :type-error))))
3156 (if (and (not (eobp))
3157 (not err))
3158 (setq err (format "Junk at end of expression: %s"
3159 (buffer-substring (point)
3160 (point-max))))))
3161 (end-of-file ; Avoid confusing error message.
3162 (setq err "Unbalanced sexp"))
3163 (error (setq err (error-message-string data))))
3164 (if (not err)
3165 nil
3166 (widget-put widget :error err)
3167 widget))))
d543e20b 3168
6d528fc5
PA
3169(defvar widget-sexp-prompt-value-history nil
3170 "History of input to `widget-sexp-prompt-value'.")
3171
3172(defun widget-sexp-prompt-value (widget prompt value unbound)
3173 ;; Read an arbitrary sexp.
3174 (let ((found (read-string prompt
a3c88c59
PA
3175 (if unbound nil (cons (prin1-to-string value) 0))
3176 (widget-get widget :prompt-history))))
bfa6c260
DL
3177 (let ((answer (read-from-string found)))
3178 (unless (= (cdr answer) (length found))
3179 (error "Junk at end of expression: %s"
3180 (substring found (cdr answer))))
3181 (car answer))))
a3c88c59 3182
0b296dac
RS
3183(define-widget 'restricted-sexp 'sexp
3184 "A Lisp expression restricted to values that match.
3185To use this type, you must define :match or :match-alternatives."
3186 :type-error "The specified value is not valid"
3187 :match 'widget-restricted-sexp-match
3188 :value-to-internal (lambda (widget value)
3189 (if (widget-apply widget :match value)
3190 (prin1-to-string value)
3191 value)))
3192
3193(defun widget-restricted-sexp-match (widget value)
3194 (let ((alternatives (widget-get widget :match-alternatives))
3195 matched)
3196 (while (and alternatives (not matched))
3197 (if (cond ((functionp (car alternatives))
3198 (funcall (car alternatives) value))
3199 ((and (consp (car alternatives))
3200 (eq (car (car alternatives)) 'quote))
3201 (eq value (nth 1 (car alternatives)))))
3202 (setq matched t))
3203 (setq alternatives (cdr alternatives)))
3204 matched))
fc56773e 3205\f
0b296dac 3206(define-widget 'integer 'restricted-sexp
d543e20b
PA
3207 "An integer."
3208 :tag "Integer"
3209 :value 0
3210 :type-error "This field should contain an integer"
0b296dac
RS
3211 :match-alternatives '(integerp))
3212
3213(define-widget 'number 'restricted-sexp
d9bfd9dc 3214 "A number (floating point or integer)."
0b296dac
RS
3215 :tag "Number"
3216 :value 0.0
d9bfd9dc 3217 :type-error "This field should contain a number (floating point or integer)"
0b296dac 3218 :match-alternatives '(numberp))
d543e20b 3219
d9bfd9dc
MR
3220(define-widget 'float 'restricted-sexp
3221 "A floating point number."
3222 :tag "Floating point number"
3223 :value 0.0
3224 :type-error "This field should contain a floating point number"
3225 :match-alternatives '(floatp))
3226
a3c88c59 3227(define-widget 'character 'editable-field
0b296dac 3228 "A character."
d543e20b
PA
3229 :tag "Character"
3230 :value 0
bfa6c260 3231 :size 1
d543e20b 3232 :format "%{%t%}: %v\n"
6d528fc5
PA
3233 :valid-regexp "\\`.\\'"
3234 :error "This field should contain a single character"
d543e20b 3235 :value-to-internal (lambda (widget value)
bfa6c260 3236 (if (stringp value)
a3c88c59
PA
3237 value
3238 (char-to-string value)))
d543e20b
PA
3239 :value-to-external (lambda (widget value)
3240 (if (stringp value)
3241 (aref value 0)
3242 value))
a3c88c59 3243 :match (lambda (widget value)
99f01612 3244 (char-valid-p value)))
d543e20b 3245
d543e20b 3246(define-widget 'list 'group
be96282a 3247 "A Lisp list."
d543e20b
PA
3248 :tag "List"
3249 :format "%{%t%}:\n%v")
3250
3251(define-widget 'vector 'group
be96282a 3252 "A Lisp vector."
d543e20b
PA
3253 :tag "Vector"
3254 :format "%{%t%}:\n%v"
3255 :match 'widget-vector-match
3256 :value-to-internal (lambda (widget value) (append value nil))
3257 :value-to-external (lambda (widget value) (apply 'vector value)))
3258
bfa6c260 3259(defun widget-vector-match (widget value)
d543e20b
PA
3260 (and (vectorp value)
3261 (widget-group-match widget
bd042c03 3262 (widget-apply widget :value-to-internal value))))
d543e20b
PA
3263
3264(define-widget 'cons 'group
3265 "A cons-cell."
3266 :tag "Cons-cell"
3267 :format "%{%t%}:\n%v"
3268 :match 'widget-cons-match
3269 :value-to-internal (lambda (widget value)
3270 (list (car value) (cdr value)))
3271 :value-to-external (lambda (widget value)
407e43be 3272 (apply 'cons value)))
d543e20b 3273
bfa6c260 3274(defun widget-cons-match (widget value)
d543e20b
PA
3275 (and (consp value)
3276 (widget-group-match widget
3277 (widget-apply widget :value-to-internal value))))
fc56773e 3278\f
cfa921fd
PA
3279;;; The `lazy' Widget.
3280;;
3281;; Recursive datatypes.
3282
3283(define-widget 'lazy 'default
3284 "Base widget for recursive datastructures.
3285
3286The `lazy' widget will, when instantiated, contain a single inferior
3287widget, of the widget type specified by the :type parameter. The
3288value of the `lazy' widget is the same as the value of the inferior
3289widget. When deriving a new widget from the 'lazy' widget, the :type
3290parameter is allowed to refer to the widget currently being defined,
3291thus allowing recursive datastructures to be described.
3292
3293The :type parameter takes the same arguments as the defcustom
3294parameter with the same name.
3295
3296Most composite widgets, i.e. widgets containing other widgets, does
3297not allow recursion. That is, when you define a new widget type, none
3298of the inferior widgets may be of the same type you are currently
3299defining.
3300
3301In Lisp, however, it is custom to define datastructures in terms of
3302themselves. A list, for example, is defined as either nil, or a cons
3303cell whose cdr itself is a list. The obvious way to translate this
3304into a widget type would be
3305
3306 (define-widget 'my-list 'choice
3307 \"A list of sexps.\"
3308 :tag \"Sexp list\"
3309 :args '((const nil) (cons :value (nil) sexp my-list)))
3310
3311Here we attempt to define my-list as a choice of either the constant
3312nil, or a cons-cell containing a sexp and my-lisp. This will not work
3313because the `choice' widget does not allow recursion.
3314
0e726aa5
KS
3315Using the `lazy' widget you can overcome this problem, as in this
3316example:
cfa921fd
PA
3317
3318 (define-widget 'sexp-list 'lazy
3319 \"A list of sexps.\"
3320 :tag \"Sexp list\"
3321 :type '(choice (const nil) (cons :value (nil) sexp sexp-list)))"
3322 :format "%{%t%}: %v"
3323 ;; We don't convert :type because we want to allow recursive
3324 ;; datastructures. This is slow, so we should not create speed
0e726aa5 3325 ;; critical widgets by deriving from this.
cfa921fd
PA
3326 :convert-widget 'widget-value-convert-widget
3327 :value-create 'widget-type-value-create
3328 :value-get 'widget-child-value-get
3329 :value-inline 'widget-child-value-inline
3330 :default-get 'widget-type-default-get
3331 :match 'widget-type-match
3332 :validate 'widget-child-validate)
3333
3334\f
fc56773e
RS
3335;;; The `plist' Widget.
3336;;
3337;; Property lists.
3338
3339(define-widget 'plist 'list
3340 "A property list."
3341 :key-type '(symbol :tag "Key")
3342 :value-type '(sexp :tag "Value")
3343 :convert-widget 'widget-plist-convert-widget
3344 :tag "Plist")
3345
3346(defvar widget-plist-value-type) ;Dynamic variable
3347
3348(defun widget-plist-convert-widget (widget)
3349 ;; Handle `:options'.
3350 (let* ((options (widget-get widget :options))
4681ca3a 3351 (widget-plist-value-type (widget-get widget :value-type))
bfa6c260 3352 (other `(editable-list :inline t
fc56773e 3353 (group :inline t
7fdbdbea 3354 ,(widget-get widget :key-type)
4681ca3a 3355 ,widget-plist-value-type)))
fc56773e
RS
3356 (args (if options
3357 (list `(checklist :inline t
3358 :greedy t
3359 ,@(mapcar 'widget-plist-convert-option
3360 options))
3361 other)
3362 (list other))))
3363 (widget-put widget :args args)
3364 widget))
d543e20b 3365
fc56773e
RS
3366(defun widget-plist-convert-option (option)
3367 ;; Convert a single plist option.
3368 (let (key-type value-type)
3369 (if (listp option)
3370 (let ((key (nth 0 option)))
3371 (setq value-type (nth 1 option))
3372 (if (listp key)
31d5543d 3373 (setq key-type key)
fc56773e
RS
3374 (setq key-type `(const ,key))))
3375 (setq key-type `(const ,option)
3376 value-type widget-plist-value-type))
3377 `(group :format "Key: %v" :inline t ,key-type ,value-type)))
3378
3379
3380;;; The `alist' Widget.
3381;;
3382;; Association lists.
3383
3384(define-widget 'alist 'list
3385 "An association list."
a7013a02 3386 :key-type '(sexp :tag "Key")
fc56773e
RS
3387 :value-type '(sexp :tag "Value")
3388 :convert-widget 'widget-alist-convert-widget
3389 :tag "Alist")
3390
3391(defvar widget-alist-value-type) ;Dynamic variable
3392
3393(defun widget-alist-convert-widget (widget)
3394 ;; Handle `:options'.
3395 (let* ((options (widget-get widget :options))
4681ca3a 3396 (widget-alist-value-type (widget-get widget :value-type))
bfa6c260 3397 (other `(editable-list :inline t
fc56773e 3398 (cons :format "%v"
7fdbdbea 3399 ,(widget-get widget :key-type)
4681ca3a 3400 ,widget-alist-value-type)))
fc56773e
RS
3401 (args (if options
3402 (list `(checklist :inline t
3403 :greedy t
3404 ,@(mapcar 'widget-alist-convert-option
3405 options))
3406 other)
3407 (list other))))
3408 (widget-put widget :args args)
3409 widget))
3410
3411(defun widget-alist-convert-option (option)
3412 ;; Convert a single alist option.
3413 (let (key-type value-type)
3414 (if (listp option)
3415 (let ((key (nth 0 option)))
3416 (setq value-type (nth 1 option))
3417 (if (listp key)
31d5543d 3418 (setq key-type key)
fc56773e
RS
3419 (setq key-type `(const ,key))))
3420 (setq key-type `(const ,option)
3421 value-type widget-alist-value-type))
3422 `(cons :format "Key: %v" ,key-type ,value-type)))
3423\f
d543e20b
PA
3424(define-widget 'choice 'menu-choice
3425 "A union of several sexp types."
3426 :tag "Choice"
c6753d66 3427 :format "%{%t%}: %[Value Menu%] %v"
8697863a
PA
3428 :button-prefix 'widget-push-button-prefix
3429 :button-suffix 'widget-push-button-suffix
a3c88c59
PA
3430 :prompt-value 'widget-choice-prompt-value)
3431
3432(defun widget-choice-prompt-value (widget prompt value unbound)
bfa6c260 3433 "Make a choice."
a3c88c59
PA
3434 (let ((args (widget-get widget :args))
3435 (completion-ignore-case (widget-get widget :case-fold))
3436 current choices old)
7fdbdbea 3437 ;; Find the first arg that matches VALUE.
a3c88c59
PA
3438 (let ((look args))
3439 (while look
3440 (if (widget-apply (car look) :match value)
3441 (setq old (car look)
3442 look nil)
3443 (setq look (cdr look)))))
3444 ;; Find new choice.
3445 (setq current
3446 (cond ((= (length args) 0)
3447 nil)
3448 ((= (length args) 1)
3449 (nth 0 args))
3450 ((and (= (length args) 2)
3451 (memq old args))
3452 (if (eq old (nth 0 args))
3453 (nth 1 args)
3454 (nth 0 args)))
3455 (t
3456 (while args
3457 (setq current (car args)
3458 args (cdr args))
3459 (setq choices
3460 (cons (cons (widget-apply current :menu-tag-get)
3461 current)
3462 choices)))
3463 (let ((val (completing-read prompt choices nil t)))
3464 (if (stringp val)
3465 (let ((try (try-completion val choices)))
3466 (when (stringp try)
3467 (setq val try))
3468 (cdr (assoc val choices)))
3469 nil)))))
3470 (if current
3471 (widget-prompt-value current prompt nil t)
3472 value)))
fc56773e 3473\f
d543e20b
PA
3474(define-widget 'radio 'radio-button-choice
3475 "A union of several sexp types."
3476 :tag "Choice"
a3c88c59
PA
3477 :format "%{%t%}:\n%v"
3478 :prompt-value 'widget-choice-prompt-value)
d543e20b
PA
3479
3480(define-widget 'repeat 'editable-list
3481 "A variable length homogeneous list."
3482 :tag "Repeat"
3483 :format "%{%t%}:\n%v%i\n")
3484
3485(define-widget 'set 'checklist
3486 "A list of members from a fixed set."
3487 :tag "Set"
3488 :format "%{%t%}:\n%v")
3489
3490(define-widget 'boolean 'toggle
3491 "To be nil or non-nil, that is the question."
3492 :tag "Boolean"
6d528fc5 3493 :prompt-value 'widget-boolean-prompt-value
8697863a
PA
3494 :button-prefix 'widget-push-button-prefix
3495 :button-suffix 'widget-push-button-suffix
c6753d66
RS
3496 :format "%{%t%}: %[Toggle%] %v\n"
3497 :on "on (non-nil)"
3498 :off "off (nil)")
d543e20b 3499
6d528fc5
PA
3500(defun widget-boolean-prompt-value (widget prompt value unbound)
3501 ;; Toggle a boolean.
a3c88c59 3502 (y-or-n-p prompt))
fc56773e 3503\f
d543e20b
PA
3504;;; The `color' Widget.
3505
77339a6e 3506;; Fixme: match
bfa6c260 3507(define-widget 'color 'editable-field
0f648ca2
PA
3508 "Choose a color name (with sample)."
3509 :format "%t: %v (%{sample%})\n"
3510 :size 10
3511 :tag "Color"
3512 :value "black"
3513 :complete 'widget-color-complete
3514 :sample-face-get 'widget-color-sample-face-get
3515 :notify 'widget-color-notify
3516 :action 'widget-color-action)
3517
3518(defun widget-color-complete (widget)
3519 "Complete the color in WIDGET."
99f01612 3520 (require 'facemenu) ; for facemenu-color-alist
0f648ca2
PA
3521 (let* ((prefix (buffer-substring-no-properties (widget-field-start widget)
3522 (point)))
451a66e3 3523 (list (or facemenu-color-alist (defined-colors)))
0f648ca2
PA
3524 (completion (try-completion prefix list)))
3525 (cond ((eq completion t)
3526 (message "Exact match."))
3527 ((null completion)
3528 (error "Can't find completion for \"%s\"" prefix))
3529 ((not (string-equal prefix completion))
3530 (insert-and-inherit (substring completion (length prefix))))
3531 (t
3532 (message "Making completion list...")
7fdbdbea
DL
3533 (with-output-to-temp-buffer "*Completions*"
3534 (display-completion-list (all-completions prefix list nil)))
0f648ca2 3535 (message "Making completion list...done")))))
d543e20b 3536
0f648ca2 3537(defun widget-color-sample-face-get (widget)
4ee1cf9f
PA
3538 (let* ((value (condition-case nil
3539 (widget-value widget)
76834555
GM
3540 (error (widget-get widget :value)))))
3541 (if (color-defined-p value)
546cf5b0 3542 (list (cons 'foreground-color value))
76834555 3543 'default)))
d543e20b 3544
d543e20b 3545(defun widget-color-action (widget &optional event)
bd1f16ce 3546 "Prompt for a color."
d543e20b
PA
3547 (let* ((tag (widget-apply widget :menu-tag-get))
3548 (prompt (concat tag ": "))
4ee1cf9f
PA
3549 (value (widget-value widget))
3550 (start (widget-field-start widget))
99f01612 3551 (answer (facemenu-read-color prompt)))
d543e20b
PA
3552 (unless (zerop (length answer))
3553 (widget-value-set widget answer)
0a3a0b56
PA
3554 (widget-setup)
3555 (widget-apply widget :notify widget event))))
d543e20b 3556
0f648ca2
PA
3557(defun widget-color-notify (widget child &optional event)
3558 "Update the sample, and notofy the parent."
bfa6c260 3559 (overlay-put (widget-get widget :sample-overlay)
0f648ca2
PA
3560 'face (widget-apply widget :sample-face-get))
3561 (widget-default-notify widget child event))
fc56773e 3562\f
d543e20b
PA
3563;;; The Help Echo
3564
d543e20b 3565(defun widget-echo-help (pos)
233d5cde 3566 "Display help-echo text for widget at POS."
d543e20b
PA
3567 (let* ((widget (widget-at pos))
3568 (help-echo (and widget (widget-get widget :help-echo))))
233d5cde
DL
3569 (if (functionp help-echo)
3570 (setq help-echo (funcall help-echo widget)))
3b26f44c 3571 (if help-echo (message "%s" (eval help-echo)))))
d543e20b
PA
3572
3573;;; The End:
3574
3575(provide 'wid-edit)
3576
ab5796a9 3577;;; arch-tag: a076e75e-18a1-4b46-8be5-3f317bcbc707
aeba6f9a 3578;;; wid-edit.el ends here