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