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