]> code.delx.au - gnu-emacs/blob - lisp/wid-edit.el
(quoted-insert-character-offset): Initialize more cleanly.
[gnu-emacs] / lisp / wid-edit.el
1 ;;; wid-edit.el --- Functions for creating and using widgets.
2 ;;
3 ;; Copyright (C) 1996, 1997 Free Software Foundation, Inc.
4 ;;
5 ;; Author: Per Abrahamsen <abraham@dina.kvl.dk>
6 ;; Keywords: extensions
7 ;; Version: 1.90
8 ;; X-URL: http://www.dina.kvl.dk/~abraham/custom/
9
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
14 ;; the Free Software Foundation; either version 2, or (at your option)
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
24 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25 ;; Boston, MA 02111-1307, USA.
26
27 ;;; Commentary:
28 ;;
29 ;; See `widget.el'.
30
31 ;;; Code:
32
33 (require 'widget)
34
35 (eval-when-compile (require 'cl))
36
37 ;;; Compatibility.
38
39 (eval-and-compile
40 (autoload 'pp-to-string "pp")
41 (autoload 'Info-goto-node "info")
42
43 (when (string-match "XEmacs" emacs-version)
44 (condition-case nil
45 (require 'overlay)
46 (error (load-library "x-overlay"))))
47
48 (if (string-match "XEmacs" emacs-version)
49 ;; XEmacs spell `intangible' as `atomic'.
50 (defun widget-make-intangible (from to side)
51 "Make text between FROM and TO atomic with regard to movement.
52 Third argument should be `start-open' if it should be sticky to the rear,
53 and `end-open' if it should sticky to the front."
54 (require 'atomic-extents)
55 (let ((ext (make-extent from to)))
56 ;; XEmacs doesn't understant different kinds of read-only, so
57 ;; we have to use extents instead.
58 (put-text-property from to 'read-only nil)
59 (set-extent-property ext 'read-only t)
60 (set-extent-property ext 'start-open nil)
61 (set-extent-property ext 'end-open nil)
62 (set-extent-property ext side t)
63 (set-extent-property ext 'atomic t)))
64 (defun widget-make-intangible (from to size)
65 "Make text between FROM and TO intangible."
66 (put-text-property from to 'intangible 'front)))
67
68 ;; The following should go away when bundled with Emacs.
69 (condition-case ()
70 (require 'custom)
71 (error nil))
72
73 (unless (and (featurep 'custom) (fboundp 'custom-declare-variable))
74 ;; We have the old custom-library, hack around it!
75 (defmacro defgroup (&rest args) nil)
76 (defmacro defcustom (var value doc &rest args)
77 (` (defvar (, var) (, value) (, doc))))
78 (defmacro defface (&rest args) nil)
79 (define-widget-keywords :prefix :tag :load :link :options :type :group)
80 (when (fboundp 'copy-face)
81 (copy-face 'default 'widget-documentation-face)
82 (copy-face 'bold 'widget-button-face)
83 (copy-face 'italic 'widget-field-face)))
84
85 (unless (fboundp 'event-point)
86 ;; XEmacs function missing in Emacs.
87 (defun event-point (event)
88 "Return the character position of the given mouse-motion, button-press,
89 or button-release event. If the event did not occur over a window, or did
90 not occur over text, then this returns nil. Otherwise, it returns an index
91 into the buffer visible in the event's window."
92 (posn-point (event-start event))))
93
94 (unless (fboundp 'error-message-string)
95 ;; Emacs function missing in XEmacs.
96 (defun error-message-string (obj)
97 "Convert an error value to an error message."
98 (let ((buf (get-buffer-create " *error-message*")))
99 (erase-buffer buf)
100 (display-error obj buf)
101 (buffer-string buf)))))
102
103 ;;; Customization.
104
105 (defgroup widgets nil
106 "Customization support for the Widget Library."
107 :link '(custom-manual "(widget)Top")
108 :link '(url-link :tag "Development Page"
109 "http://www.dina.kvl.dk/~abraham/custom/")
110 :prefix "widget-"
111 :group 'extensions
112 :group 'faces
113 :group 'hypermedia)
114
115 (defface widget-documentation-face '((((class color)
116 (background dark))
117 (:foreground "lime green"))
118 (((class color)
119 (background light))
120 (:foreground "dark green"))
121 (t nil))
122 "Face used for documentation text."
123 :group 'widgets)
124
125 (defface widget-button-face '((t (:bold t)))
126 "Face used for widget buttons."
127 :group 'widgets)
128
129 (defcustom widget-mouse-face 'highlight
130 "Face used for widget buttons when the mouse is above them."
131 :type 'face
132 :group 'widgets)
133
134 (defface widget-field-face '((((class grayscale color)
135 (background light))
136 (:background "gray85"))
137 (((class grayscale color)
138 (background dark))
139 (:background "dark gray"))
140 (t
141 (:italic t)))
142 "Face used for editable fields."
143 :group 'widgets)
144
145 (defcustom widget-menu-max-size 40
146 "Largest number of items allowed in a popup-menu.
147 Larger menus are read through the minibuffer."
148 :group 'widgets
149 :type 'integer)
150
151 ;;; Utility functions.
152 ;;
153 ;; These are not really widget specific.
154
155 (defsubst widget-plist-member (plist prop)
156 ;; Return non-nil if PLIST has the property PROP.
157 ;; PLIST is a property list, which is a list of the form
158 ;; (PROP1 VALUE1 PROP2 VALUE2 ...). PROP is a symbol.
159 ;; Unlike `plist-get', this allows you to distinguish between a missing
160 ;; property and a property with the value nil.
161 ;; The value is actually the tail of PLIST whose car is PROP.
162 (while (and plist (not (eq (car plist) prop)))
163 (setq plist (cdr (cdr plist))))
164 plist)
165
166 (defun widget-princ-to-string (object)
167 ;; Return string representation of OBJECT, any Lisp object.
168 ;; No quoting characters are used; no delimiters are printed around
169 ;; the contents of strings.
170 (save-excursion
171 (set-buffer (get-buffer-create " *widget-tmp*"))
172 (erase-buffer)
173 (let ((standard-output (current-buffer)))
174 (princ object))
175 (buffer-string)))
176
177 (defun widget-clear-undo ()
178 "Clear all undo information."
179 (buffer-disable-undo (current-buffer))
180 (buffer-enable-undo))
181
182 (defun widget-choose (title items &optional event)
183 "Choose an item from a list.
184
185 First argument TITLE is the name of the list.
186 Second argument ITEMS is an list whose members are either
187 (NAME . VALUE), to indicate selectable items, or just strings to
188 indicate unselectable items.
189 Optional third argument EVENT is an input event.
190
191 The user is asked to choose between each NAME from the items alist,
192 and the VALUE of the chosen element will be returned. If EVENT is a
193 mouse event, and the number of elements in items is less than
194 `widget-menu-max-size', a popup menu will be used, otherwise the
195 minibuffer."
196 (cond ((and (< (length items) widget-menu-max-size)
197 event (fboundp 'x-popup-menu) window-system)
198 ;; We are in Emacs-19, pressed by the mouse
199 (x-popup-menu event
200 (list title (cons "" items))))
201 ((and (< (length items) widget-menu-max-size)
202 event (fboundp 'popup-menu) window-system)
203 ;; We are in XEmacs, pressed by the mouse
204 (let ((val (get-popup-menu-response
205 (cons title
206 (mapcar
207 (function
208 (lambda (x)
209 (if (stringp x)
210 (vector x nil nil)
211 (vector (car x) (list (car x)) t))))
212 items)))))
213 (setq val (and val
214 (listp (event-object val))
215 (stringp (car-safe (event-object val)))
216 (car (event-object val))))
217 (cdr (assoc val items))))
218 (t
219 (setq items (remove-if 'stringp items))
220 (let ((val (completing-read (concat title ": ") items nil t)))
221 (if (stringp val)
222 (let ((try (try-completion val items)))
223 (when (stringp try)
224 (setq val try))
225 (cdr (assoc val items)))
226 nil)))))
227
228 (defun widget-get-sibling (widget)
229 "Get the item WIDGET is assumed to toggle.
230 This is only meaningful for radio buttons or checkboxes in a list."
231 (let* ((parent (widget-get widget :parent))
232 (children (widget-get parent :children))
233 child)
234 (catch 'child
235 (while children
236 (setq child (car children)
237 children (cdr children))
238 (when (eq (widget-get child :button) widget)
239 (throw 'child child)))
240 nil)))
241
242 ;;; Helper functions.
243 ;;
244 ;; These are widget specific.
245
246 ;;;###autoload
247 (defun widget-prompt-value (widget prompt &optional value unbound)
248 "Prompt for a value matching WIDGET, using PROMPT.
249 The current value is assumed to be VALUE, unless UNBOUND is non-nil."
250 (unless (listp widget)
251 (setq widget (list widget)))
252 (setq widget (widget-convert widget))
253 (let ((answer (widget-apply widget :prompt-value prompt value unbound)))
254 (unless (widget-apply widget :match answer)
255 (error "Value does not match %S type." (car widget)))
256 answer))
257
258 ;;; Widget text specifications.
259 ;;
260 ;; These functions are for specifying text properties.
261
262 (defun widget-specify-none (from to)
263 ;; Clear all text properties between FROM and TO.
264 (set-text-properties from to nil))
265
266 (defun widget-specify-text (from to)
267 ;; Default properties.
268 (add-text-properties from to (list 'read-only t
269 'front-sticky t
270 'start-open t
271 'end-open t
272 'rear-nonsticky nil)))
273
274 (defun widget-specify-field (widget from to)
275 ;; Specify editable button for WIDGET between FROM and TO.
276 (widget-specify-field-update widget from to)
277
278 ;; Make it possible to edit the front end of the field.
279 (add-text-properties (1- from) from (list 'rear-nonsticky t
280 'end-open t
281 'invisible t))
282 (when (or (string-match "\\(.\\|\n\\)%v" (widget-get widget :format))
283 (widget-get widget :hide-front-space))
284 ;; WARNING: This is going to lose horrible if the character just
285 ;; before the field can be modified (e.g. if it belongs to a
286 ;; choice widget). We try to compensate by checking the format
287 ;; string, and hope the user hasn't changed the :create method.
288 (widget-make-intangible (- from 2) from 'end-open))
289
290 ;; Make it possible to edit back end of the field.
291 (add-text-properties to (1+ to) (list 'front-sticky nil
292 'read-only t
293 'start-open t))
294
295 (cond ((widget-get widget :size)
296 (put-text-property to (1+ to) 'invisible t)
297 (when (or (string-match "%v\\(.\\|\n\\)" (widget-get widget :format))
298 (widget-get widget :hide-rear-space))
299 ;; WARNING: This is going to lose horrible if the character just
300 ;; after the field can be modified (e.g. if it belongs to a
301 ;; choice widget). We try to compensate by checking the format
302 ;; string, and hope the user hasn't changed the :create method.
303 (widget-make-intangible to (+ to 2) 'start-open)))
304 ((string-match "XEmacs" emacs-version)
305 ;; XEmacs does not allow you to insert before a read-only
306 ;; character, even if it is start.open.
307 ;; XEmacs does allow you to delete an read-only extent, so
308 ;; making the terminating newline read only doesn't help.
309 ;; I tried putting an invisible intangible read-only space
310 ;; before the newline, which gave really weird effects.
311 ;; So for now, we just have trust the user not to delete the
312 ;; newline.
313 (put-text-property to (1+ to) 'read-only nil))))
314
315 (defun widget-specify-field-update (widget from to)
316 ;; Specify editable button for WIDGET between FROM and TO.
317 (let ((map (widget-get widget :keymap))
318 (secret (widget-get widget :secret))
319 (secret-to to)
320 (size (widget-get widget :size))
321 (face (or (widget-get widget :value-face)
322 'widget-field-face))
323 (help-echo (widget-get widget :help-echo))
324 (help-property (if (featurep 'balloon-help)
325 'balloon-help
326 'help-echo)))
327 (unless (or (stringp help-echo) (null help-echo))
328 (setq help-echo 'widget-mouse-help))
329
330 (when secret
331 (while (and size
332 (not (zerop size))
333 (> secret-to from)
334 (eq (char-after (1- secret-to)) ?\ ))
335 (setq secret-to (1- secret-to)))
336
337 (save-excursion
338 (goto-char from)
339 (while (< (point) secret-to)
340 (let ((old (get-text-property (point) 'secret)))
341 (when old
342 (subst-char-in-region (point) (1+ (point)) secret old)))
343 (forward-char))))
344
345 (set-text-properties from to (list 'field widget
346 'read-only nil
347 'keymap map
348 'local-map map
349 help-property help-echo
350 'face face))
351
352 (when secret
353 (save-excursion
354 (goto-char from)
355 (while (< (point) secret-to)
356 (let ((old (following-char)))
357 (subst-char-in-region (point) (1+ (point)) old secret)
358 (put-text-property (point) (1+ (point)) 'secret old))
359 (forward-char))))
360
361 (unless (widget-get widget :size)
362 (add-text-properties to (1+ to) (list 'field widget
363 help-property help-echo
364 'face face)))
365 (add-text-properties to (1+ to) (list 'local-map map
366 'keymap map))))
367
368 (defun widget-specify-button (widget from to)
369 ;; Specify button for WIDGET between FROM and TO.
370 (let ((face (widget-apply widget :button-face-get))
371 (help-echo (widget-get widget :help-echo))
372 (help-property (if (featurep 'balloon-help)
373 'balloon-help
374 'help-echo)))
375 (unless (or (null help-echo) (stringp help-echo))
376 (setq help-echo 'widget-mouse-help))
377 (add-text-properties from to (list 'button widget
378 'mouse-face widget-mouse-face
379 'start-open t
380 'end-open t
381 help-property help-echo
382 'face face))))
383
384 (defun widget-mouse-help (extent)
385 "Find mouse help string for button in extent."
386 (let* ((widget (widget-at (extent-start-position extent)))
387 (help-echo (and widget (widget-get widget :help-echo))))
388 (cond ((stringp help-echo)
389 help-echo)
390 ((and (symbolp help-echo) (fboundp help-echo)
391 (stringp (setq help-echo (funcall help-echo widget))))
392 help-echo)
393 (t
394 (format "(widget %S :help-echo %S)" widget help-echo)))))
395
396 (defun widget-specify-sample (widget from to)
397 ;; Specify sample for WIDGET between FROM and TO.
398 (let ((face (widget-apply widget :sample-face-get)))
399 (when face
400 (add-text-properties from to (list 'start-open t
401 'end-open t
402 'face face)))))
403
404 (defun widget-specify-doc (widget from to)
405 ;; Specify documentation for WIDGET between FROM and TO.
406 (add-text-properties from to (list 'widget-doc widget
407 'face 'widget-documentation-face)))
408
409 (defmacro widget-specify-insert (&rest form)
410 ;; Execute FORM without inheriting any text properties.
411 (`
412 (save-restriction
413 (let ((inhibit-read-only t)
414 result
415 after-change-functions)
416 (insert "<>")
417 (narrow-to-region (- (point) 2) (point))
418 (widget-specify-none (point-min) (point-max))
419 (goto-char (1+ (point-min)))
420 (setq result (progn (,@ form)))
421 (delete-region (point-min) (1+ (point-min)))
422 (delete-region (1- (point-max)) (point-max))
423 (goto-char (point-max))
424 result))))
425
426 (defface widget-inactive-face '((((class grayscale color)
427 (background dark))
428 (:foreground "light gray"))
429 (((class grayscale color)
430 (background light))
431 (:foreground "dark gray"))
432 (t
433 (:italic t)))
434 "Face used for inactive widgets."
435 :group 'widgets)
436
437 (defun widget-specify-inactive (widget from to)
438 "Make WIDGET inactive for user modifications."
439 (unless (widget-get widget :inactive)
440 (let ((overlay (make-overlay from to nil t nil)))
441 (overlay-put overlay 'face 'widget-inactive-face)
442 (overlay-put overlay 'evaporate t)
443 (overlay-put overlay 'priority 100)
444 (overlay-put overlay (if (string-match "XEmacs" emacs-version)
445 'read-only
446 'modification-hooks) '(widget-overlay-inactive))
447 (widget-put widget :inactive overlay))))
448
449 (defun widget-overlay-inactive (&rest junk)
450 "Ignoring the arguments, signal an error."
451 (unless inhibit-read-only
452 (error "Attempt to modify inactive widget")))
453
454
455 (defun widget-specify-active (widget)
456 "Make WIDGET active for user modifications."
457 (let ((inactive (widget-get widget :inactive)))
458 (when inactive
459 (delete-overlay inactive)
460 (widget-put widget :inactive nil))))
461
462 ;;; Widget Properties.
463
464 (defsubst widget-type (widget)
465 "Return the type of WIDGET, a symbol."
466 (car widget))
467
468 (defun widget-put (widget property value)
469 "In WIDGET set PROPERTY to VALUE.
470 The value can later be retrived with `widget-get'."
471 (setcdr widget (plist-put (cdr widget) property value)))
472
473 (defun widget-get (widget property)
474 "In WIDGET, get the value of PROPERTY.
475 The value could either be specified when the widget was created, or
476 later with `widget-put'."
477 (let ((missing t)
478 value tmp)
479 (while missing
480 (cond ((setq tmp (widget-plist-member (cdr widget) property))
481 (setq value (car (cdr tmp))
482 missing nil))
483 ((setq tmp (car widget))
484 (setq widget (get tmp 'widget-type)))
485 (t
486 (setq missing nil))))
487 value))
488
489 (defun widget-member (widget property)
490 "Non-nil iff there is a definition in WIDGET for PROPERTY."
491 (cond ((widget-plist-member (cdr widget) property)
492 t)
493 ((car widget)
494 (widget-member (get (car widget) 'widget-type) property))
495 (t nil)))
496
497 ;;;###autoload
498 (defun widget-apply (widget property &rest args)
499 "Apply the value of WIDGET's PROPERTY to the widget itself.
500 ARGS are passed as extra arguments to the function."
501 (apply (widget-get widget property) widget args))
502
503 (defun widget-value (widget)
504 "Extract the current value of WIDGET."
505 (widget-apply widget
506 :value-to-external (widget-apply widget :value-get)))
507
508 (defun widget-value-set (widget value)
509 "Set the current value of WIDGET to VALUE."
510 (widget-apply widget
511 :value-set (widget-apply widget
512 :value-to-internal value)))
513
514 (defun widget-match-inline (widget vals)
515 ;; In WIDGET, match the start of VALS.
516 (cond ((widget-get widget :inline)
517 (widget-apply widget :match-inline vals))
518 ((and vals
519 (widget-apply widget :match (car vals)))
520 (cons (list (car vals)) (cdr vals)))
521 (t nil)))
522
523 (defun widget-apply-action (widget &optional event)
524 "Apply :action in WIDGET in response to EVENT."
525 (if (widget-apply widget :active)
526 (widget-apply widget :action event)
527 (error "Attempt to perform action on inactive widget")))
528
529 ;;; Glyphs.
530
531 (defcustom widget-glyph-directory (concat data-directory "custom/")
532 "Where widget glyphs are located.
533 If this variable is nil, widget will try to locate the directory
534 automatically. This does not work yet."
535 :group 'widgets
536 :type 'directory)
537
538 (defcustom widget-glyph-enable t
539 "If non nil, use glyphs in images when available."
540 :group 'widgets
541 :type 'boolean)
542
543 (defun widget-glyph-insert (widget tag image)
544 "In WIDGET, insert the text TAG or, if supported, IMAGE.
545 IMAGE should either be a glyph, or a name sans extension of an xpm or
546 xbm file located in `widget-glyph-directory'.
547
548 WARNING: If you call this with a glyph, and you want the user to be
549 able to activate the glyph, make sure it is unique. If you use the
550 same glyph for multiple widgets, activating any of the glyphs will
551 cause the last created widget to be activated."
552 (cond ((not (and (string-match "XEmacs" emacs-version)
553 widget-glyph-enable
554 (fboundp 'make-glyph)
555 image))
556 ;; We don't want or can't use glyphs.
557 (insert tag))
558 ((and (fboundp 'glyphp)
559 (glyphp image))
560 ;; Already a glyph. Insert it.
561 (widget-glyph-insert-glyph widget tag image))
562 (t
563 ;; A string. Look it up in.
564 (let ((file (concat widget-glyph-directory
565 (if (string-match "/\\'" widget-glyph-directory)
566 ""
567 "/")
568 image
569 (if (featurep 'xpm) ".xpm" ".xbm"))))
570 (if (file-readable-p file)
571 (widget-glyph-insert-glyph widget tag (make-glyph file))
572 ;; File not readable, give up.
573 (insert tag))))))
574
575 (defun widget-glyph-insert-glyph (widget tag glyph)
576 "In WIDGET, with alternative text TAG, insert GLYPH."
577 (set-glyph-image glyph (cons 'tty tag))
578 (set-glyph-property glyph 'widget widget)
579 (insert "*")
580 (add-text-properties (1- (point)) (point)
581 (list 'invisible t
582 'end-glyph glyph))
583 (let ((help-echo (widget-get widget :help-echo)))
584 (when help-echo
585 (let ((extent (extent-at (1- (point)) nil 'end-glyph))
586 (help-property (if (featurep 'balloon-help)
587 'balloon-help
588 'help-echo)))
589 (set-extent-property extent help-property (if (stringp help-echo)
590 help-echo
591 'widget-mouse-help))))))
592
593 ;;; Creating Widgets.
594
595 ;;;###autoload
596 (defun widget-create (type &rest args)
597 "Create widget of TYPE.
598 The optional ARGS are additional keyword arguments."
599 (let ((widget (apply 'widget-convert type args)))
600 (widget-apply widget :create)
601 widget))
602
603 (defun widget-create-child-and-convert (parent type &rest args)
604 "As part of the widget PARENT, create a child widget TYPE.
605 The child is converted, using the keyword arguments ARGS."
606 (let ((widget (apply 'widget-convert type args)))
607 (widget-put widget :parent parent)
608 (unless (widget-get widget :indent)
609 (widget-put widget :indent (+ (or (widget-get parent :indent) 0)
610 (or (widget-get widget :extra-offset) 0)
611 (widget-get parent :offset))))
612 (widget-apply widget :create)
613 widget))
614
615 (defun widget-create-child (parent type)
616 "Create widget of TYPE."
617 (let ((widget (copy-sequence type)))
618 (widget-put widget :parent parent)
619 (unless (widget-get widget :indent)
620 (widget-put widget :indent (+ (or (widget-get parent :indent) 0)
621 (or (widget-get widget :extra-offset) 0)
622 (widget-get parent :offset))))
623 (widget-apply widget :create)
624 widget))
625
626 (defun widget-create-child-value (parent type value)
627 "Create widget of TYPE with value VALUE."
628 (let ((widget (copy-sequence type)))
629 (widget-put widget :value (widget-apply widget :value-to-internal value))
630 (widget-put widget :parent parent)
631 (unless (widget-get widget :indent)
632 (widget-put widget :indent (+ (or (widget-get parent :indent) 0)
633 (or (widget-get widget :extra-offset) 0)
634 (widget-get parent :offset))))
635 (widget-apply widget :create)
636 widget))
637
638 ;;;###autoload
639 (defun widget-delete (widget)
640 "Delete WIDGET."
641 (widget-apply widget :delete))
642
643 (defun widget-convert (type &rest args)
644 "Convert TYPE to a widget without inserting it in the buffer.
645 The optional ARGS are additional keyword arguments."
646 ;; Don't touch the type.
647 (let* ((widget (if (symbolp type)
648 (list type)
649 (copy-sequence type)))
650 (current widget)
651 (keys args))
652 ;; First set the :args keyword.
653 (while (cdr current) ;Look in the type.
654 (let ((next (car (cdr current))))
655 (if (and (symbolp next) (eq (aref (symbol-name next) 0) ?:))
656 (setq current (cdr (cdr current)))
657 (setcdr current (list :args (cdr current)))
658 (setq current nil))))
659 (while args ;Look in the args.
660 (let ((next (nth 0 args)))
661 (if (and (symbolp next) (eq (aref (symbol-name next) 0) ?:))
662 (setq args (nthcdr 2 args))
663 (widget-put widget :args args)
664 (setq args nil))))
665 ;; Then Convert the widget.
666 (setq type widget)
667 (while type
668 (let ((convert-widget (plist-get (cdr type) :convert-widget)))
669 (if convert-widget
670 (setq widget (funcall convert-widget widget))))
671 (setq type (get (car type) 'widget-type)))
672 ;; Finally set the keyword args.
673 (while keys
674 (let ((next (nth 0 keys)))
675 (if (and (symbolp next) (eq (aref (symbol-name next) 0) ?:))
676 (progn
677 (widget-put widget next (nth 1 keys))
678 (setq keys (nthcdr 2 keys)))
679 (setq keys nil))))
680 ;; Convert the :value to internal format.
681 (if (widget-member widget :value)
682 (let ((value (widget-get widget :value)))
683 (widget-put widget
684 :value (widget-apply widget :value-to-internal value))))
685 ;; Return the newly create widget.
686 widget))
687
688 (defun widget-insert (&rest args)
689 "Call `insert' with ARGS and make the text read only."
690 (let ((inhibit-read-only t)
691 after-change-functions
692 (from (point)))
693 (apply 'insert args)
694 (widget-specify-text from (point))))
695
696 ;;; Keymap and Commands.
697
698 (defvar widget-keymap nil
699 "Keymap containing useful binding for buffers containing widgets.
700 Recommended as a parent keymap for modes using widgets.")
701
702 (unless widget-keymap
703 (setq widget-keymap (make-sparse-keymap))
704 (define-key widget-keymap "\C-k" 'widget-kill-line)
705 (define-key widget-keymap "\t" 'widget-forward)
706 (define-key widget-keymap "\M-\t" 'widget-backward)
707 (define-key widget-keymap [(shift tab)] 'widget-backward)
708 (define-key widget-keymap [backtab] 'widget-backward)
709 (if (string-match "XEmacs" (emacs-version))
710 (progn
711 (define-key widget-keymap [button2] 'widget-button-click)
712 (define-key widget-keymap [button1] 'widget-button1-click))
713 (define-key widget-keymap [mouse-2] 'ignore)
714 (define-key widget-keymap [down-mouse-2] 'widget-button-click))
715 (define-key widget-keymap "\C-m" 'widget-button-press))
716
717 (defvar widget-global-map global-map
718 "Keymap used for events the widget does not handle themselves.")
719 (make-variable-buffer-local 'widget-global-map)
720
721 (defvar widget-field-keymap nil
722 "Keymap used inside an editable field.")
723
724 (unless widget-field-keymap
725 (setq widget-field-keymap (copy-keymap widget-keymap))
726 (unless (string-match "XEmacs" (emacs-version))
727 (define-key widget-field-keymap [menu-bar] 'nil))
728 (define-key widget-field-keymap "\C-m" 'widget-field-activate)
729 (define-key widget-field-keymap "\C-a" 'widget-beginning-of-line)
730 (define-key widget-field-keymap "\C-e" 'widget-end-of-line)
731 (set-keymap-parent widget-field-keymap global-map))
732
733 (defvar widget-text-keymap nil
734 "Keymap used inside a text field.")
735
736 (unless widget-text-keymap
737 (setq widget-text-keymap (copy-keymap widget-keymap))
738 (unless (string-match "XEmacs" (emacs-version))
739 (define-key widget-text-keymap [menu-bar] 'nil))
740 (define-key widget-text-keymap "\C-a" 'widget-beginning-of-line)
741 (define-key widget-text-keymap "\C-e" 'widget-end-of-line)
742 (set-keymap-parent widget-text-keymap global-map))
743
744 (defun widget-field-activate (pos &optional event)
745 "Activate the ediable field at point."
746 (interactive "@d")
747 (let ((field (get-text-property pos 'field)))
748 (if field
749 (widget-apply-action field event)
750 (call-interactively
751 (lookup-key widget-global-map (this-command-keys))))))
752
753 (defun widget-button-click (event)
754 "Activate button below mouse pointer."
755 (interactive "@e")
756 (cond ((and (fboundp 'event-glyph)
757 (event-glyph event))
758 (let ((widget (glyph-property (event-glyph event) 'widget)))
759 (if widget
760 (widget-apply-action widget event)
761 (message "You clicked on a glyph."))))
762 ((event-point event)
763 (let ((button (get-text-property (event-point event) 'button)))
764 (if button
765 (widget-apply-action button event)
766 (call-interactively
767 (or (lookup-key widget-global-map [ button2 ])
768 (lookup-key widget-global-map [ down-mouse-2 ])
769 (lookup-key widget-global-map [ mouse-2]))))))
770 (t
771 (message "You clicked somewhere weird."))))
772
773 (defun widget-button1-click (event)
774 "Activate glyph below mouse pointer."
775 (interactive "@e")
776 (if (and (fboundp 'event-glyph)
777 (event-glyph event))
778 (let ((widget (glyph-property (event-glyph event) 'widget)))
779 (if widget
780 (widget-apply-action widget event)
781 (message "You clicked on a glyph.")))
782 (call-interactively (lookup-key widget-global-map (this-command-keys)))))
783
784 (defun widget-button-press (pos &optional event)
785 "Activate button at POS."
786 (interactive "@d")
787 (let ((button (get-text-property pos 'button)))
788 (if button
789 (widget-apply-action button event)
790 (let ((command (lookup-key widget-global-map (this-command-keys))))
791 (when (commandp command)
792 (call-interactively command))))))
793
794 (defun widget-move (arg)
795 "Move point to the ARG next field or button.
796 ARG may be negative to move backward."
797 (while (> arg 0)
798 (setq arg (1- arg))
799 (let ((next (cond ((get-text-property (point) 'button)
800 (next-single-property-change (point) 'button))
801 ((get-text-property (point) 'field)
802 (next-single-property-change (point) 'field))
803 (t
804 (point)))))
805 (if (null next) ; Widget extends to end. of buffer
806 (setq next (point-min)))
807 (let ((button (next-single-property-change next 'button))
808 (field (next-single-property-change next 'field)))
809 (cond ((or (get-text-property next 'button)
810 (get-text-property next 'field))
811 (goto-char next))
812 ((and button field)
813 (goto-char (min button field)))
814 (button (goto-char button))
815 (field (goto-char field))
816 (t
817 (let ((button (next-single-property-change (point-min) 'button))
818 (field (next-single-property-change (point-min) 'field)))
819 (cond ((and button field) (goto-char (min button field)))
820 (button (goto-char button))
821 (field (goto-char field))
822 (t
823 (error "No buttons or fields found"))))))
824 (setq button (widget-at (point)))
825 (if (or (and button (widget-get button :tab-order)
826 (< (widget-get button :tab-order) 0))
827 (and button (not (widget-apply button :active))))
828 (setq arg (1+ arg))))))
829 (while (< arg 0)
830 (if (= (point-min) (point))
831 (forward-char 1))
832 (setq arg (1+ arg))
833 (let ((previous (cond ((get-text-property (1- (point)) 'button)
834 (previous-single-property-change (point) 'button))
835 ((get-text-property (1- (point)) 'field)
836 (previous-single-property-change (point) 'field))
837 (t
838 (point)))))
839 (if (null previous) ; Widget extends to beg. of buffer
840 (setq previous (point-max)))
841 (let ((button (previous-single-property-change previous 'button))
842 (field (previous-single-property-change previous 'field)))
843 (cond ((and button field)
844 (goto-char (max button field)))
845 (button (goto-char button))
846 (field (goto-char field))
847 (t
848 (let ((button (previous-single-property-change
849 (point-max) 'button))
850 (field (previous-single-property-change
851 (point-max) 'field)))
852 (cond ((and button field) (goto-char (max button field)))
853 (button (goto-char button))
854 (field (goto-char field))
855 (t
856 (error "No buttons or fields found"))))))))
857 (let ((button (previous-single-property-change (point) 'button))
858 (field (previous-single-property-change (point) 'field)))
859 (cond ((and button field)
860 (goto-char (max button field)))
861 (button (goto-char button))
862 (field (goto-char field)))
863 (setq button (widget-at (point)))
864 (if (or (and button (widget-get button :tab-order)
865 (< (widget-get button :tab-order) 0))
866 (and button (not (widget-apply button :active))))
867 (setq arg (1- arg)))))
868 (widget-echo-help (point))
869 (run-hooks 'widget-move-hook))
870
871 (defun widget-forward (arg)
872 "Move point to the next field or button.
873 With optional ARG, move across that many fields."
874 (interactive "p")
875 (run-hooks 'widget-forward-hook)
876 (widget-move arg))
877
878 (defun widget-backward (arg)
879 "Move point to the previous field or button.
880 With optional ARG, move across that many fields."
881 (interactive "p")
882 (run-hooks 'widget-backward-hook)
883 (widget-move (- arg)))
884
885 (defun widget-beginning-of-line ()
886 "Go to beginning of field or beginning of line, whichever is first."
887 (interactive)
888 (let ((bol (save-excursion (beginning-of-line) (point)))
889 (prev (previous-single-property-change (point) 'field)))
890 (goto-char (max bol (or prev bol)))))
891
892 (defun widget-end-of-line ()
893 "Go to end of field or end of line, whichever is first."
894 (interactive)
895 (let ((bol (save-excursion (end-of-line) (point)))
896 (prev (next-single-property-change (point) 'field)))
897 (goto-char (min bol (or prev bol)))))
898
899 (defun widget-kill-line ()
900 "Kill to end of field or end of line, whichever is first."
901 (interactive)
902 (let ((field (get-text-property (point) 'field))
903 (newline (save-excursion (search-forward "\n")))
904 (next (next-single-property-change (point) 'field)))
905 (if (and field (> newline next))
906 (kill-region (point) next)
907 (call-interactively 'kill-line))))
908
909 ;;; Setting up the buffer.
910
911 (defvar widget-field-new nil)
912 ;; List of all newly created editable fields in the buffer.
913 (make-variable-buffer-local 'widget-field-new)
914
915 (defvar widget-field-list nil)
916 ;; List of all editable fields in the buffer.
917 (make-variable-buffer-local 'widget-field-list)
918
919 (defun widget-setup ()
920 "Setup current buffer so editing string widgets works."
921 (let ((inhibit-read-only t)
922 (after-change-functions nil)
923 field)
924 (while widget-field-new
925 (setq field (car widget-field-new)
926 widget-field-new (cdr widget-field-new)
927 widget-field-list (cons field widget-field-list))
928 (let ((from (widget-get field :value-from))
929 (to (widget-get field :value-to)))
930 (widget-specify-field field from to)
931 (move-marker from (1- from))
932 (move-marker to (1+ to)))))
933 (widget-clear-undo)
934 ;; We need to maintain text properties and size of the editing fields.
935 (make-local-variable 'after-change-functions)
936 (if widget-field-list
937 (setq after-change-functions '(widget-after-change))
938 (setq after-change-functions nil)))
939
940 (defvar widget-field-last nil)
941 ;; Last field containing point.
942 (make-variable-buffer-local 'widget-field-last)
943
944 (defvar widget-field-was nil)
945 ;; The widget data before the change.
946 (make-variable-buffer-local 'widget-field-was)
947
948 (defun widget-field-find (pos)
949 ;; Find widget whose editing field is located at POS.
950 ;; Return nil if POS is not inside and editing field.
951 ;;
952 ;; This is only used in `widget-field-modified', since ordinarily
953 ;; you would just test the field property.
954 (let ((fields widget-field-list)
955 field found)
956 (while fields
957 (setq field (car fields)
958 fields (cdr fields))
959 (let ((from (widget-get field :value-from))
960 (to (widget-get field :value-to)))
961 (if (and from to (< from pos) (> to pos))
962 (setq fields nil
963 found field))))
964 found))
965
966 (defun widget-after-change (from to old)
967 ;; Adjust field size and text properties.
968 (condition-case nil
969 (let ((field (widget-field-find from))
970 (inhibit-read-only t))
971 (cond ((null field))
972 ((not (eq field (widget-field-find to)))
973 (debug)
974 (message "Error: `widget-after-change' called on two fields"))
975 (t
976 (let ((size (widget-get field :size)))
977 (if size
978 (let ((begin (1+ (widget-get field :value-from)))
979 (end (1- (widget-get field :value-to))))
980 (widget-specify-field-update field begin end)
981 (cond ((< (- end begin) size)
982 ;; Field too small.
983 (save-excursion
984 (goto-char end)
985 (insert-char ?\ (- (+ begin size) end))
986 (widget-specify-field-update field
987 begin
988 (+ begin size))))
989 ((> (- end begin) size)
990 ;; Field too large and
991 (if (or (< (point) (+ begin size))
992 (> (point) end))
993 ;; Point is outside extra space.
994 (setq begin (+ begin size))
995 ;; Point is within the extra space.
996 (setq begin (point)))
997 (save-excursion
998 (goto-char end)
999 (while (and (eq (preceding-char) ?\ )
1000 (> (point) begin))
1001 (delete-backward-char 1))))))
1002 (widget-specify-field-update field from to)))
1003 (widget-apply field :notify field))))
1004 (error (debug))))
1005
1006 ;;; Widget Functions
1007 ;;
1008 ;; These functions are used in the definition of multiple widgets.
1009
1010 (defun widget-children-value-delete (widget)
1011 "Delete all :children and :buttons in WIDGET."
1012 (mapcar 'widget-delete (widget-get widget :children))
1013 (widget-put widget :children nil)
1014 (mapcar 'widget-delete (widget-get widget :buttons))
1015 (widget-put widget :buttons nil))
1016
1017 (defun widget-types-convert-widget (widget)
1018 "Convert :args as widget types in WIDGET."
1019 (widget-put widget :args (mapcar 'widget-convert (widget-get widget :args)))
1020 widget)
1021
1022 ;;; The `default' Widget.
1023
1024 (define-widget 'default nil
1025 "Basic widget other widgets are derived from."
1026 :value-to-internal (lambda (widget value) value)
1027 :value-to-external (lambda (widget value) value)
1028 :create 'widget-default-create
1029 :indent nil
1030 :offset 0
1031 :format-handler 'widget-default-format-handler
1032 :button-face-get 'widget-default-button-face-get
1033 :sample-face-get 'widget-default-sample-face-get
1034 :delete 'widget-default-delete
1035 :value-set 'widget-default-value-set
1036 :value-inline 'widget-default-value-inline
1037 :menu-tag-get 'widget-default-menu-tag-get
1038 :validate (lambda (widget) nil)
1039 :active 'widget-default-active
1040 :activate 'widget-specify-active
1041 :deactivate 'widget-default-deactivate
1042 :action 'widget-default-action
1043 :notify 'widget-default-notify
1044 :prompt-value 'widget-default-prompt-value)
1045
1046 (defun widget-default-create (widget)
1047 "Create WIDGET at point in the current buffer."
1048 (widget-specify-insert
1049 (let ((from (point))
1050 (tag (widget-get widget :tag))
1051 (glyph (widget-get widget :tag-glyph))
1052 (doc (widget-get widget :doc))
1053 button-begin button-end
1054 sample-begin sample-end
1055 doc-begin doc-end
1056 value-pos)
1057 (insert (widget-get widget :format))
1058 (goto-char from)
1059 ;; Parse escapes in format.
1060 (while (re-search-forward "%\\(.\\)" nil t)
1061 (let ((escape (aref (match-string 1) 0)))
1062 (replace-match "" t t)
1063 (cond ((eq escape ?%)
1064 (insert "%"))
1065 ((eq escape ?\[)
1066 (setq button-begin (point)))
1067 ((eq escape ?\])
1068 (setq button-end (point)))
1069 ((eq escape ?\{)
1070 (setq sample-begin (point)))
1071 ((eq escape ?\})
1072 (setq sample-end (point)))
1073 ((eq escape ?n)
1074 (when (widget-get widget :indent)
1075 (insert "\n")
1076 (insert-char ? (widget-get widget :indent))))
1077 ((eq escape ?t)
1078 (cond (glyph
1079 (widget-glyph-insert widget (or tag "image") glyph))
1080 (tag
1081 (insert tag))
1082 (t
1083 (let ((standard-output (current-buffer)))
1084 (princ (widget-get widget :value))))))
1085 ((eq escape ?d)
1086 (when doc
1087 (setq doc-begin (point))
1088 (insert doc)
1089 (while (eq (preceding-char) ?\n)
1090 (delete-backward-char 1))
1091 (insert "\n")
1092 (setq doc-end (point))))
1093 ((eq escape ?v)
1094 (if (and button-begin (not button-end))
1095 (widget-apply widget :value-create)
1096 (setq value-pos (point))))
1097 (t
1098 (widget-apply widget :format-handler escape)))))
1099 ;; Specify button, sample, and doc, and insert value.
1100 (and button-begin button-end
1101 (widget-specify-button widget button-begin button-end))
1102 (and sample-begin sample-end
1103 (widget-specify-sample widget sample-begin sample-end))
1104 (and doc-begin doc-end
1105 (widget-specify-doc widget doc-begin doc-end))
1106 (when value-pos
1107 (goto-char value-pos)
1108 (widget-apply widget :value-create)))
1109 (let ((from (copy-marker (point-min)))
1110 (to (copy-marker (point-max))))
1111 (widget-specify-text from to)
1112 (set-marker-insertion-type from t)
1113 (set-marker-insertion-type to nil)
1114 (widget-put widget :from from)
1115 (widget-put widget :to to)))
1116 (widget-clear-undo))
1117
1118 (defun widget-default-format-handler (widget escape)
1119 ;; We recognize the %h escape by default.
1120 (let* ((buttons (widget-get widget :buttons))
1121 (doc-property (widget-get widget :documentation-property))
1122 (doc-try (cond ((widget-get widget :doc))
1123 ((symbolp doc-property)
1124 (documentation-property (widget-get widget :value)
1125 doc-property))
1126 (t
1127 (funcall doc-property (widget-get widget :value)))))
1128 (doc-text (and (stringp doc-try)
1129 (> (length doc-try) 1)
1130 doc-try)))
1131 (cond ((eq escape ?h)
1132 (when doc-text
1133 (and (eq (preceding-char) ?\n)
1134 (widget-get widget :indent)
1135 (insert-char ? (widget-get widget :indent)))
1136 ;; The `*' in the beginning is redundant.
1137 (when (eq (aref doc-text 0) ?*)
1138 (setq doc-text (substring doc-text 1)))
1139 ;; Get rid of trailing newlines.
1140 (when (string-match "\n+\\'" doc-text)
1141 (setq doc-text (substring doc-text 0 (match-beginning 0))))
1142 (push (if (string-match "\n." doc-text)
1143 ;; Allow multiline doc to be hiden.
1144 (widget-create-child-and-convert
1145 widget 'widget-help
1146 :doc (progn
1147 (string-match "\\`.*" doc-text)
1148 (match-string 0 doc-text))
1149 :widget-doc doc-text
1150 "?")
1151 ;; A single line is just inserted.
1152 (widget-create-child-and-convert
1153 widget 'item :format "%d" :doc doc-text nil))
1154 buttons)))
1155 (t
1156 (error "Unknown escape `%c'" escape)))
1157 (widget-put widget :buttons buttons)))
1158
1159 (defun widget-default-button-face-get (widget)
1160 ;; Use :button-face or widget-button-face
1161 (or (widget-get widget :button-face) 'widget-button-face))
1162
1163 (defun widget-default-sample-face-get (widget)
1164 ;; Use :sample-face.
1165 (widget-get widget :sample-face))
1166
1167 (defun widget-default-delete (widget)
1168 ;; Remove widget from the buffer.
1169 (let ((from (widget-get widget :from))
1170 (to (widget-get widget :to))
1171 (inhibit-read-only t)
1172 after-change-functions)
1173 (widget-apply widget :value-delete)
1174 (when (< from to)
1175 ;; Kludge: this doesn't need to be true for empty formats.
1176 (delete-region from to))
1177 (set-marker from nil)
1178 (set-marker to nil))
1179 (widget-clear-undo))
1180
1181 (defun widget-default-value-set (widget value)
1182 ;; Recreate widget with new value.
1183 (save-excursion
1184 (goto-char (widget-get widget :from))
1185 (widget-apply widget :delete)
1186 (widget-put widget :value value)
1187 (widget-apply widget :create)))
1188
1189 (defun widget-default-value-inline (widget)
1190 ;; Wrap value in a list unless it is inline.
1191 (if (widget-get widget :inline)
1192 (widget-value widget)
1193 (list (widget-value widget))))
1194
1195 (defun widget-default-menu-tag-get (widget)
1196 ;; Use tag or value for menus.
1197 (or (widget-get widget :menu-tag)
1198 (widget-get widget :tag)
1199 (widget-princ-to-string (widget-get widget :value))))
1200
1201 (defun widget-default-active (widget)
1202 "Return t iff this widget active (user modifiable)."
1203 (and (not (widget-get widget :inactive))
1204 (let ((parent (widget-get widget :parent)))
1205 (or (null parent)
1206 (widget-apply parent :active)))))
1207
1208 (defun widget-default-deactivate (widget)
1209 "Make WIDGET inactive for user modifications."
1210 (widget-specify-inactive widget
1211 (widget-get widget :from)
1212 (widget-get widget :to)))
1213
1214 (defun widget-default-action (widget &optional event)
1215 ;; Notify the parent when a widget change
1216 (let ((parent (widget-get widget :parent)))
1217 (when parent
1218 (widget-apply parent :notify widget event))))
1219
1220 (defun widget-default-notify (widget child &optional event)
1221 ;; Pass notification to parent.
1222 (widget-default-action widget event))
1223
1224 (defun widget-default-prompt-value (widget prompt value unbound)
1225 ;; Read an arbitrary value. Stolen from `set-variable'.
1226 ;; (let ((initial (if unbound
1227 ;; nil
1228 ;; ;; It would be nice if we could do a `(cons val 1)' here.
1229 ;; (prin1-to-string (custom-quote value))))))
1230 (eval-minibuffer prompt ))
1231
1232 ;;; The `item' Widget.
1233
1234 (define-widget 'item 'default
1235 "Constant items for inclusion in other widgets."
1236 :convert-widget 'widget-item-convert-widget
1237 :value-create 'widget-item-value-create
1238 :value-delete 'ignore
1239 :value-get 'widget-item-value-get
1240 :match 'widget-item-match
1241 :match-inline 'widget-item-match-inline
1242 :action 'widget-item-action
1243 :format "%t\n")
1244
1245 (defun widget-item-convert-widget (widget)
1246 ;; Initialize :value from :args in WIDGET.
1247 (let ((args (widget-get widget :args)))
1248 (when args
1249 (widget-put widget :value (widget-apply widget
1250 :value-to-internal (car args)))
1251 (widget-put widget :args nil)))
1252 widget)
1253
1254 (defun widget-item-value-create (widget)
1255 ;; Insert the printed representation of the value.
1256 (let ((standard-output (current-buffer)))
1257 (princ (widget-get widget :value))))
1258
1259 (defun widget-item-match (widget value)
1260 ;; Match if the value is the same.
1261 (equal (widget-get widget :value) value))
1262
1263 (defun widget-item-match-inline (widget values)
1264 ;; Match if the value is the same.
1265 (let ((value (widget-get widget :value)))
1266 (and (listp value)
1267 (<= (length value) (length values))
1268 (let ((head (subseq values 0 (length value))))
1269 (and (equal head value)
1270 (cons head (subseq values (length value))))))))
1271
1272 (defun widget-item-action (widget &optional event)
1273 ;; Just notify itself.
1274 (widget-apply widget :notify widget event))
1275
1276 (defun widget-item-value-get (widget)
1277 ;; Items are simple.
1278 (widget-get widget :value))
1279
1280 ;;; The `push-button' Widget.
1281
1282 (defcustom widget-push-button-gui t
1283 "If non nil, use GUI push buttons when available."
1284 :group 'widgets
1285 :type 'boolean)
1286
1287 ;; Cache already created GUI objects.
1288 (defvar widget-push-button-cache nil)
1289
1290 (define-widget 'push-button 'item
1291 "A pushable button."
1292 :value-create 'widget-push-button-value-create
1293 :text-format "[%s]"
1294 :format "%[%v%]")
1295
1296 (defun widget-push-button-value-create (widget)
1297 ;; Insert text representing the `on' and `off' states.
1298 (let* ((tag (or (widget-get widget :tag)
1299 (widget-get widget :value)))
1300 (text (format (widget-get widget :text-format) tag))
1301 (gui (cdr (assoc tag widget-push-button-cache))))
1302 (if (and (fboundp 'make-gui-button)
1303 (fboundp 'make-glyph)
1304 widget-push-button-gui
1305 (fboundp 'device-on-window-system-p)
1306 (device-on-window-system-p)
1307 (string-match "XEmacs" emacs-version))
1308 (progn
1309 (unless gui
1310 (setq gui (make-gui-button tag 'widget-gui-action widget))
1311 (push (cons tag gui) widget-push-button-cache))
1312 (widget-glyph-insert-glyph widget text
1313 (make-glyph (car (aref gui 1)))))
1314 (insert text))))
1315
1316 (defun widget-gui-action (widget)
1317 "Apply :action for WIDGET."
1318 (widget-apply-action widget (this-command-keys)))
1319
1320 ;;; The `link' Widget.
1321
1322 (define-widget 'link 'item
1323 "An embedded link."
1324 :help-echo "Follow the link."
1325 :format "%[_%t_%]")
1326
1327 ;;; The `info-link' Widget.
1328
1329 (define-widget 'info-link 'link
1330 "A link to an info file."
1331 :action 'widget-info-link-action)
1332
1333 (defun widget-info-link-action (widget &optional event)
1334 "Open the info node specified by WIDGET."
1335 (Info-goto-node (widget-value widget))
1336 ;; Steal button release event.
1337 (if (and (fboundp 'button-press-event-p)
1338 (fboundp 'next-command-event))
1339 ;; XEmacs
1340 (and event
1341 (button-press-event-p event)
1342 (next-command-event))
1343 ;; Emacs
1344 (when (memq 'down (event-modifiers event))
1345 (read-event))))
1346
1347 ;;; The `url-link' Widget.
1348
1349 (define-widget 'url-link 'link
1350 "A link to an www page."
1351 :action 'widget-url-link-action)
1352
1353 (defun widget-url-link-action (widget &optional event)
1354 "Open the url specified by WIDGET."
1355 (require 'browse-url)
1356 (funcall browse-url-browser-function (widget-value widget)))
1357
1358 ;;; The `editable-field' Widget.
1359
1360 (define-widget 'editable-field 'default
1361 "An editable text field."
1362 :convert-widget 'widget-item-convert-widget
1363 :keymap widget-field-keymap
1364 :format "%v"
1365 :value ""
1366 :action 'widget-field-action
1367 :validate 'widget-field-validate
1368 :valid-regexp ""
1369 :error "No match"
1370 :value-create 'widget-field-value-create
1371 :value-delete 'widget-field-value-delete
1372 :value-get 'widget-field-value-get
1373 :match 'widget-field-match)
1374
1375 ;; History of field minibuffer edits.
1376 (defvar widget-field-history nil)
1377
1378 (defun widget-field-action (widget &optional event)
1379 ;; Edit the value in the minibuffer.
1380 (let ((tag (widget-apply widget :menu-tag-get))
1381 (invalid (widget-apply widget :validate)))
1382 (when invalid
1383 (error (widget-get invalid :error)))
1384 (widget-value-set widget
1385 (widget-apply widget
1386 :value-to-external
1387 (read-string (concat tag ": ")
1388 (widget-apply
1389 widget
1390 :value-to-internal
1391 (widget-value widget))
1392 'widget-field-history)))
1393 (widget-apply widget :notify widget event)
1394 (widget-setup)))
1395
1396 (defun widget-field-validate (widget)
1397 ;; Valid if the content matches `:valid-regexp'.
1398 (save-excursion
1399 (let ((value (widget-apply widget :value-get))
1400 (regexp (widget-get widget :valid-regexp)))
1401 (if (string-match regexp value)
1402 nil
1403 widget))))
1404
1405 (defun widget-field-value-create (widget)
1406 ;; Create an editable text field.
1407 (insert " ")
1408 (let ((size (widget-get widget :size))
1409 (value (widget-get widget :value))
1410 (from (point)))
1411 (insert value)
1412 (and size
1413 (< (length value) size)
1414 (insert-char ?\ (- size (length value))))
1415 (unless (memq widget widget-field-list)
1416 (setq widget-field-new (cons widget widget-field-new)))
1417 (widget-put widget :value-to (copy-marker (point)))
1418 (set-marker-insertion-type (widget-get widget :value-to) nil)
1419 (if (null size)
1420 (insert ?\n)
1421 (insert ?\ ))
1422 (widget-put widget :value-from (copy-marker from))
1423 (set-marker-insertion-type (widget-get widget :value-from) t)))
1424
1425 (defun widget-field-value-delete (widget)
1426 ;; Remove the widget from the list of active editing fields.
1427 (setq widget-field-list (delq widget widget-field-list))
1428 ;; These are nil if the :format string doesn't contain `%v'.
1429 (when (widget-get widget :value-from)
1430 (set-marker (widget-get widget :value-from) nil))
1431 (when (widget-get widget :value-from)
1432 (set-marker (widget-get widget :value-to) nil)))
1433
1434 (defun widget-field-value-get (widget)
1435 ;; Return current text in editing field.
1436 (let ((from (widget-get widget :value-from))
1437 (to (widget-get widget :value-to))
1438 (size (widget-get widget :size))
1439 (secret (widget-get widget :secret))
1440 (old (current-buffer)))
1441 (if (and from to)
1442 (progn
1443 (set-buffer (marker-buffer from))
1444 (setq from (1+ from)
1445 to (1- to))
1446 (while (and size
1447 (not (zerop size))
1448 (> to from)
1449 (eq (char-after (1- to)) ?\ ))
1450 (setq to (1- to)))
1451 (let ((result (buffer-substring-no-properties from to)))
1452 (when secret
1453 (let ((index 0))
1454 (while (< (+ from index) to)
1455 (aset result index
1456 (get-text-property (+ from index) 'secret))
1457 (setq index (1+ index)))))
1458 (set-buffer old)
1459 result))
1460 (widget-get widget :value))))
1461
1462 (defun widget-field-match (widget value)
1463 ;; Match any string.
1464 (stringp value))
1465
1466 ;;; The `text' Widget.
1467
1468 (define-widget 'text 'editable-field
1469 :keymap widget-text-keymap
1470 "A multiline text area.")
1471
1472 ;;; The `menu-choice' Widget.
1473
1474 (define-widget 'menu-choice 'default
1475 "A menu of options."
1476 :convert-widget 'widget-types-convert-widget
1477 :format "%[%t%]: %v"
1478 :case-fold t
1479 :tag "choice"
1480 :void '(item :format "invalid (%t)\n")
1481 :value-create 'widget-choice-value-create
1482 :value-delete 'widget-children-value-delete
1483 :value-get 'widget-choice-value-get
1484 :value-inline 'widget-choice-value-inline
1485 :action 'widget-choice-action
1486 :error "Make a choice"
1487 :validate 'widget-choice-validate
1488 :match 'widget-choice-match
1489 :match-inline 'widget-choice-match-inline)
1490
1491 (defun widget-choice-value-create (widget)
1492 ;; Insert the first choice that matches the value.
1493 (let ((value (widget-get widget :value))
1494 (args (widget-get widget :args))
1495 current)
1496 (while args
1497 (setq current (car args)
1498 args (cdr args))
1499 (when (widget-apply current :match value)
1500 (widget-put widget :children (list (widget-create-child-value
1501 widget current value)))
1502 (widget-put widget :choice current)
1503 (setq args nil
1504 current nil)))
1505 (when current
1506 (let ((void (widget-get widget :void)))
1507 (widget-put widget :children (list (widget-create-child-and-convert
1508 widget void :value value)))
1509 (widget-put widget :choice void)))))
1510
1511 (defun widget-choice-value-get (widget)
1512 ;; Get value of the child widget.
1513 (widget-value (car (widget-get widget :children))))
1514
1515 (defun widget-choice-value-inline (widget)
1516 ;; Get value of the child widget.
1517 (widget-apply (car (widget-get widget :children)) :value-inline))
1518
1519 (defun widget-choice-action (widget &optional event)
1520 ;; Make a choice.
1521 (let ((args (widget-get widget :args))
1522 (old (widget-get widget :choice))
1523 (tag (widget-apply widget :menu-tag-get))
1524 (completion-ignore-case (widget-get widget :case-fold))
1525 current choices)
1526 ;; Remember old value.
1527 (if (and old (not (widget-apply widget :validate)))
1528 (let* ((external (widget-value widget))
1529 (internal (widget-apply old :value-to-internal external)))
1530 (widget-put old :value internal)))
1531 ;; Find new choice.
1532 (setq current
1533 (cond ((= (length args) 0)
1534 nil)
1535 ((= (length args) 1)
1536 (nth 0 args))
1537 ((and (= (length args) 2)
1538 (memq old args))
1539 (if (eq old (nth 0 args))
1540 (nth 1 args)
1541 (nth 0 args)))
1542 (t
1543 (while args
1544 (setq current (car args)
1545 args (cdr args))
1546 (setq choices
1547 (cons (cons (widget-apply current :menu-tag-get)
1548 current)
1549 choices)))
1550 (widget-choose tag (reverse choices) event))))
1551 (when current
1552 (widget-value-set widget
1553 (widget-apply current :value-to-external
1554 (widget-get current :value)))
1555 (widget-apply widget :notify widget event)
1556 (widget-setup))))
1557
1558 (defun widget-choice-validate (widget)
1559 ;; Valid if we have made a valid choice.
1560 (let ((void (widget-get widget :void))
1561 (choice (widget-get widget :choice))
1562 (child (car (widget-get widget :children))))
1563 (if (eq void choice)
1564 widget
1565 (widget-apply child :validate))))
1566
1567 (defun widget-choice-match (widget value)
1568 ;; Matches if one of the choices matches.
1569 (let ((args (widget-get widget :args))
1570 current found)
1571 (while (and args (not found))
1572 (setq current (car args)
1573 args (cdr args)
1574 found (widget-apply current :match value)))
1575 found))
1576
1577 (defun widget-choice-match-inline (widget values)
1578 ;; Matches if one of the choices matches.
1579 (let ((args (widget-get widget :args))
1580 current found)
1581 (while (and args (null found))
1582 (setq current (car args)
1583 args (cdr args)
1584 found (widget-match-inline current values)))
1585 found))
1586
1587 ;;; The `toggle' Widget.
1588
1589 (define-widget 'toggle 'item
1590 "Toggle between two states."
1591 :format "%[%v%]\n"
1592 :value-create 'widget-toggle-value-create
1593 :action 'widget-toggle-action
1594 :match (lambda (widget value) t)
1595 :on "on"
1596 :off "off")
1597
1598 (defun widget-toggle-value-create (widget)
1599 ;; Insert text representing the `on' and `off' states.
1600 (if (widget-value widget)
1601 (widget-glyph-insert widget
1602 (widget-get widget :on)
1603 (widget-get widget :on-glyph))
1604 (widget-glyph-insert widget
1605 (widget-get widget :off)
1606 (widget-get widget :off-glyph))))
1607
1608 (defun widget-toggle-action (widget &optional event)
1609 ;; Toggle value.
1610 (widget-value-set widget (not (widget-value widget)))
1611 (widget-apply widget :notify widget event))
1612
1613 ;;; The `checkbox' Widget.
1614
1615 (define-widget 'checkbox 'toggle
1616 "A checkbox toggle."
1617 :format "%[%v%]"
1618 :on "[X]"
1619 :on-glyph "check1"
1620 :off "[ ]"
1621 :off-glyph "check0"
1622 :action 'widget-checkbox-action)
1623
1624 (defun widget-checkbox-action (widget &optional event)
1625 "Toggle checkbox, notify parent, and set active state of sibling."
1626 (widget-toggle-action widget event)
1627 (let ((sibling (widget-get-sibling widget)))
1628 (when sibling
1629 (if (widget-value widget)
1630 (widget-apply sibling :activate)
1631 (widget-apply sibling :deactivate)))))
1632
1633 ;;; The `checklist' Widget.
1634
1635 (define-widget 'checklist 'default
1636 "A multiple choice widget."
1637 :convert-widget 'widget-types-convert-widget
1638 :format "%v"
1639 :offset 4
1640 :entry-format "%b %v"
1641 :menu-tag "checklist"
1642 :greedy nil
1643 :value-create 'widget-checklist-value-create
1644 :value-delete 'widget-children-value-delete
1645 :value-get 'widget-checklist-value-get
1646 :validate 'widget-checklist-validate
1647 :match 'widget-checklist-match
1648 :match-inline 'widget-checklist-match-inline)
1649
1650 (defun widget-checklist-value-create (widget)
1651 ;; Insert all values
1652 (let ((alist (widget-checklist-match-find widget (widget-get widget :value)))
1653 (args (widget-get widget :args)))
1654 (while args
1655 (widget-checklist-add-item widget (car args) (assq (car args) alist))
1656 (setq args (cdr args)))
1657 (widget-put widget :children (nreverse (widget-get widget :children)))))
1658
1659 (defun widget-checklist-add-item (widget type chosen)
1660 ;; Create checklist item in WIDGET of type TYPE.
1661 ;; If the item is checked, CHOSEN is a cons whose cdr is the value.
1662 (and (eq (preceding-char) ?\n)
1663 (widget-get widget :indent)
1664 (insert-char ? (widget-get widget :indent)))
1665 (widget-specify-insert
1666 (let* ((children (widget-get widget :children))
1667 (buttons (widget-get widget :buttons))
1668 (button-args (or (widget-get type :sibling-args)
1669 (widget-get widget :button-args)))
1670 (from (point))
1671 child button)
1672 (insert (widget-get widget :entry-format))
1673 (goto-char from)
1674 ;; Parse % escapes in format.
1675 (while (re-search-forward "%\\([bv%]\\)" nil t)
1676 (let ((escape (aref (match-string 1) 0)))
1677 (replace-match "" t t)
1678 (cond ((eq escape ?%)
1679 (insert "%"))
1680 ((eq escape ?b)
1681 (setq button (apply 'widget-create-child-and-convert
1682 widget 'checkbox
1683 :value (not (null chosen))
1684 button-args)))
1685 ((eq escape ?v)
1686 (setq child
1687 (cond ((not chosen)
1688 (let ((child (widget-create-child widget type)))
1689 (widget-apply child :deactivate)
1690 child))
1691 ((widget-get type :inline)
1692 (widget-create-child-value
1693 widget type (cdr chosen)))
1694 (t
1695 (widget-create-child-value
1696 widget type (car (cdr chosen)))))))
1697 (t
1698 (error "Unknown escape `%c'" escape)))))
1699 ;; Update properties.
1700 (and button child (widget-put child :button button))
1701 (and button (widget-put widget :buttons (cons button buttons)))
1702 (and child (widget-put widget :children (cons child children))))))
1703
1704 (defun widget-checklist-match (widget values)
1705 ;; All values must match a type in the checklist.
1706 (and (listp values)
1707 (null (cdr (widget-checklist-match-inline widget values)))))
1708
1709 (defun widget-checklist-match-inline (widget values)
1710 ;; Find the values which match a type in the checklist.
1711 (let ((greedy (widget-get widget :greedy))
1712 (args (copy-sequence (widget-get widget :args)))
1713 found rest)
1714 (while values
1715 (let ((answer (widget-checklist-match-up args values)))
1716 (cond (answer
1717 (let ((vals (widget-match-inline answer values)))
1718 (setq found (append found (car vals))
1719 values (cdr vals)
1720 args (delq answer args))))
1721 (greedy
1722 (setq rest (append rest (list (car values)))
1723 values (cdr values)))
1724 (t
1725 (setq rest (append rest values)
1726 values nil)))))
1727 (cons found rest)))
1728
1729 (defun widget-checklist-match-find (widget vals)
1730 ;; Find the vals which match a type in the checklist.
1731 ;; Return an alist of (TYPE MATCH).
1732 (let ((greedy (widget-get widget :greedy))
1733 (args (copy-sequence (widget-get widget :args)))
1734 found)
1735 (while vals
1736 (let ((answer (widget-checklist-match-up args vals)))
1737 (cond (answer
1738 (let ((match (widget-match-inline answer vals)))
1739 (setq found (cons (cons answer (car match)) found)
1740 vals (cdr match)
1741 args (delq answer args))))
1742 (greedy
1743 (setq vals (cdr vals)))
1744 (t
1745 (setq vals nil)))))
1746 found))
1747
1748 (defun widget-checklist-match-up (args vals)
1749 ;; Rerturn the first type from ARGS that matches VALS.
1750 (let (current found)
1751 (while (and args (null found))
1752 (setq current (car args)
1753 args (cdr args)
1754 found (widget-match-inline current vals)))
1755 (if found
1756 current
1757 nil)))
1758
1759 (defun widget-checklist-value-get (widget)
1760 ;; The values of all selected items.
1761 (let ((children (widget-get widget :children))
1762 child result)
1763 (while children
1764 (setq child (car children)
1765 children (cdr children))
1766 (if (widget-value (widget-get child :button))
1767 (setq result (append result (widget-apply child :value-inline)))))
1768 result))
1769
1770 (defun widget-checklist-validate (widget)
1771 ;; Ticked chilren must be valid.
1772 (let ((children (widget-get widget :children))
1773 child button found)
1774 (while (and children (not found))
1775 (setq child (car children)
1776 children (cdr children)
1777 button (widget-get child :button)
1778 found (and (widget-value button)
1779 (widget-apply child :validate))))
1780 found))
1781
1782 ;;; The `option' Widget
1783
1784 (define-widget 'option 'checklist
1785 "An widget with an optional item."
1786 :inline t)
1787
1788 ;;; The `choice-item' Widget.
1789
1790 (define-widget 'choice-item 'item
1791 "Button items that delegate action events to their parents."
1792 :action 'widget-choice-item-action
1793 :format "%[%t%] \n")
1794
1795 (defun widget-choice-item-action (widget &optional event)
1796 ;; Tell parent what happened.
1797 (widget-apply (widget-get widget :parent) :action event))
1798
1799 ;;; The `radio-button' Widget.
1800
1801 (define-widget 'radio-button 'toggle
1802 "A radio button for use in the `radio' widget."
1803 :notify 'widget-radio-button-notify
1804 :format "%[%v%]"
1805 :on "(*)"
1806 :on-glyph "radio1"
1807 :off "( )"
1808 :off-glyph "radio0")
1809
1810 (defun widget-radio-button-notify (widget child &optional event)
1811 ;; Tell daddy.
1812 (widget-apply (widget-get widget :parent) :action widget event))
1813
1814 ;;; The `radio-button-choice' Widget.
1815
1816 (define-widget 'radio-button-choice 'default
1817 "Select one of multiple options."
1818 :convert-widget 'widget-types-convert-widget
1819 :offset 4
1820 :format "%v"
1821 :entry-format "%b %v"
1822 :menu-tag "radio"
1823 :value-create 'widget-radio-value-create
1824 :value-delete 'widget-children-value-delete
1825 :value-get 'widget-radio-value-get
1826 :value-inline 'widget-radio-value-inline
1827 :value-set 'widget-radio-value-set
1828 :error "You must push one of the buttons"
1829 :validate 'widget-radio-validate
1830 :match 'widget-choice-match
1831 :match-inline 'widget-choice-match-inline
1832 :action 'widget-radio-action)
1833
1834 (defun widget-radio-value-create (widget)
1835 ;; Insert all values
1836 (let ((args (widget-get widget :args))
1837 arg)
1838 (while args
1839 (setq arg (car args)
1840 args (cdr args))
1841 (widget-radio-add-item widget arg))))
1842
1843 (defun widget-radio-add-item (widget type)
1844 "Add to radio widget WIDGET a new radio button item of type TYPE."
1845 ;; (setq type (widget-convert type))
1846 (and (eq (preceding-char) ?\n)
1847 (widget-get widget :indent)
1848 (insert-char ? (widget-get widget :indent)))
1849 (widget-specify-insert
1850 (let* ((value (widget-get widget :value))
1851 (children (widget-get widget :children))
1852 (buttons (widget-get widget :buttons))
1853 (button-args (or (widget-get type :sibling-args)
1854 (widget-get widget :button-args)))
1855 (from (point))
1856 (chosen (and (null (widget-get widget :choice))
1857 (widget-apply type :match value)))
1858 child button)
1859 (insert (widget-get widget :entry-format))
1860 (goto-char from)
1861 ;; Parse % escapes in format.
1862 (while (re-search-forward "%\\([bv%]\\)" nil t)
1863 (let ((escape (aref (match-string 1) 0)))
1864 (replace-match "" t t)
1865 (cond ((eq escape ?%)
1866 (insert "%"))
1867 ((eq escape ?b)
1868 (setq button (apply 'widget-create-child-and-convert
1869 widget 'radio-button
1870 :value (not (null chosen))
1871 button-args)))
1872 ((eq escape ?v)
1873 (setq child (if chosen
1874 (widget-create-child-value
1875 widget type value)
1876 (widget-create-child widget type)))
1877 (unless chosen
1878 (widget-apply child :deactivate)))
1879 (t
1880 (error "Unknown escape `%c'" escape)))))
1881 ;; Update properties.
1882 (when chosen
1883 (widget-put widget :choice type))
1884 (when button
1885 (widget-put child :button button)
1886 (widget-put widget :buttons (nconc buttons (list button))))
1887 (when child
1888 (widget-put widget :children (nconc children (list child))))
1889 child)))
1890
1891 (defun widget-radio-value-get (widget)
1892 ;; Get value of the child widget.
1893 (let ((chosen (widget-radio-chosen widget)))
1894 (and chosen (widget-value chosen))))
1895
1896 (defun widget-radio-chosen (widget)
1897 "Return the widget representing the chosen radio button."
1898 (let ((children (widget-get widget :children))
1899 current found)
1900 (while children
1901 (setq current (car children)
1902 children (cdr children))
1903 (let* ((button (widget-get current :button))
1904 (value (widget-apply button :value-get)))
1905 (when value
1906 (setq found current
1907 children nil))))
1908 found))
1909
1910 (defun widget-radio-value-inline (widget)
1911 ;; Get value of the child widget.
1912 (let ((children (widget-get widget :children))
1913 current found)
1914 (while children
1915 (setq current (car children)
1916 children (cdr children))
1917 (let* ((button (widget-get current :button))
1918 (value (widget-apply button :value-get)))
1919 (when value
1920 (setq found (widget-apply current :value-inline)
1921 children nil))))
1922 found))
1923
1924 (defun widget-radio-value-set (widget value)
1925 ;; We can't just delete and recreate a radio widget, since children
1926 ;; can be added after the original creation and won't be recreated
1927 ;; by `:create'.
1928 (let ((children (widget-get widget :children))
1929 current found)
1930 (while children
1931 (setq current (car children)
1932 children (cdr children))
1933 (let* ((button (widget-get current :button))
1934 (match (and (not found)
1935 (widget-apply current :match value))))
1936 (widget-value-set button match)
1937 (if match
1938 (progn
1939 (widget-value-set current value)
1940 (widget-apply current :activate))
1941 (widget-apply current :deactivate))
1942 (setq found (or found match))))))
1943
1944 (defun widget-radio-validate (widget)
1945 ;; Valid if we have made a valid choice.
1946 (let ((children (widget-get widget :children))
1947 current found button)
1948 (while (and children (not found))
1949 (setq current (car children)
1950 children (cdr children)
1951 button (widget-get current :button)
1952 found (widget-apply button :value-get)))
1953 (if found
1954 (widget-apply current :validate)
1955 widget)))
1956
1957 (defun widget-radio-action (widget child event)
1958 ;; Check if a radio button was pressed.
1959 (let ((children (widget-get widget :children))
1960 (buttons (widget-get widget :buttons))
1961 current)
1962 (when (memq child buttons)
1963 (while children
1964 (setq current (car children)
1965 children (cdr children))
1966 (let* ((button (widget-get current :button)))
1967 (cond ((eq child button)
1968 (widget-value-set button t)
1969 (widget-apply current :activate))
1970 ((widget-value button)
1971 (widget-value-set button nil)
1972 (widget-apply current :deactivate)))))))
1973 ;; Pass notification to parent.
1974 (widget-apply widget :notify child event))
1975
1976 ;;; The `insert-button' Widget.
1977
1978 (define-widget 'insert-button 'push-button
1979 "An insert button for the `editable-list' widget."
1980 :tag "INS"
1981 :help-echo "Insert a new item into the list at this position."
1982 :action 'widget-insert-button-action)
1983
1984 (defun widget-insert-button-action (widget &optional event)
1985 ;; Ask the parent to insert a new item.
1986 (widget-apply (widget-get widget :parent)
1987 :insert-before (widget-get widget :widget)))
1988
1989 ;;; The `delete-button' Widget.
1990
1991 (define-widget 'delete-button 'push-button
1992 "A delete button for the `editable-list' widget."
1993 :tag "DEL"
1994 :help-echo "Delete this item from the list."
1995 :action 'widget-delete-button-action)
1996
1997 (defun widget-delete-button-action (widget &optional event)
1998 ;; Ask the parent to insert a new item.
1999 (widget-apply (widget-get widget :parent)
2000 :delete-at (widget-get widget :widget)))
2001
2002 ;;; The `editable-list' Widget.
2003
2004 (defcustom widget-editable-list-gui nil
2005 "If non nil, use GUI push-buttons in editable list when available."
2006 :type 'boolean
2007 :group 'widgets)
2008
2009 (define-widget 'editable-list 'default
2010 "A variable list of widgets of the same type."
2011 :convert-widget 'widget-types-convert-widget
2012 :offset 12
2013 :format "%v%i\n"
2014 :format-handler 'widget-editable-list-format-handler
2015 :entry-format "%i %d %v"
2016 :menu-tag "editable-list"
2017 :value-create 'widget-editable-list-value-create
2018 :value-delete 'widget-children-value-delete
2019 :value-get 'widget-editable-list-value-get
2020 :validate 'widget-editable-list-validate
2021 :match 'widget-editable-list-match
2022 :match-inline 'widget-editable-list-match-inline
2023 :insert-before 'widget-editable-list-insert-before
2024 :delete-at 'widget-editable-list-delete-at)
2025
2026 (defun widget-editable-list-format-handler (widget escape)
2027 ;; We recognize the insert button.
2028 (let ((widget-push-button-gui widget-editable-list-gui))
2029 (cond ((eq escape ?i)
2030 (and (widget-get widget :indent)
2031 (insert-char ? (widget-get widget :indent)))
2032 (apply 'widget-create-child-and-convert
2033 widget 'insert-button
2034 (widget-get widget :append-button-args)))
2035 (t
2036 (widget-default-format-handler widget escape)))))
2037
2038 (defun widget-editable-list-value-create (widget)
2039 ;; Insert all values
2040 (let* ((value (widget-get widget :value))
2041 (type (nth 0 (widget-get widget :args)))
2042 (inlinep (widget-get type :inline))
2043 children)
2044 (widget-put widget :value-pos (copy-marker (point)))
2045 (set-marker-insertion-type (widget-get widget :value-pos) t)
2046 (while value
2047 (let ((answer (widget-match-inline type value)))
2048 (if answer
2049 (setq children (cons (widget-editable-list-entry-create
2050 widget
2051 (if inlinep
2052 (car answer)
2053 (car (car answer)))
2054 t)
2055 children)
2056 value (cdr answer))
2057 (setq value nil))))
2058 (widget-put widget :children (nreverse children))))
2059
2060 (defun widget-editable-list-value-get (widget)
2061 ;; Get value of the child widget.
2062 (apply 'append (mapcar (lambda (child) (widget-apply child :value-inline))
2063 (widget-get widget :children))))
2064
2065 (defun widget-editable-list-validate (widget)
2066 ;; All the chilren must be valid.
2067 (let ((children (widget-get widget :children))
2068 child found)
2069 (while (and children (not found))
2070 (setq child (car children)
2071 children (cdr children)
2072 found (widget-apply child :validate)))
2073 found))
2074
2075 (defun widget-editable-list-match (widget value)
2076 ;; Value must be a list and all the members must match the type.
2077 (and (listp value)
2078 (null (cdr (widget-editable-list-match-inline widget value)))))
2079
2080 (defun widget-editable-list-match-inline (widget value)
2081 (let ((type (nth 0 (widget-get widget :args)))
2082 (ok t)
2083 found)
2084 (while (and value ok)
2085 (let ((answer (widget-match-inline type value)))
2086 (if answer
2087 (setq found (append found (car answer))
2088 value (cdr answer))
2089 (setq ok nil))))
2090 (cons found value)))
2091
2092 (defun widget-editable-list-insert-before (widget before)
2093 ;; Insert a new child in the list of children.
2094 (save-excursion
2095 (let ((children (widget-get widget :children))
2096 (inhibit-read-only t)
2097 after-change-functions)
2098 (cond (before
2099 (goto-char (widget-get before :entry-from)))
2100 (t
2101 (goto-char (widget-get widget :value-pos))))
2102 (let ((child (widget-editable-list-entry-create
2103 widget nil nil)))
2104 (when (< (widget-get child :entry-from) (widget-get widget :from))
2105 (set-marker (widget-get widget :from)
2106 (widget-get child :entry-from)))
2107 (widget-specify-text (widget-get child :entry-from)
2108 (widget-get child :entry-to))
2109 (if (eq (car children) before)
2110 (widget-put widget :children (cons child children))
2111 (while (not (eq (car (cdr children)) before))
2112 (setq children (cdr children)))
2113 (setcdr children (cons child (cdr children)))))))
2114 (widget-setup)
2115 widget (widget-apply widget :notify widget))
2116
2117 (defun widget-editable-list-delete-at (widget child)
2118 ;; Delete child from list of children.
2119 (save-excursion
2120 (let ((buttons (copy-sequence (widget-get widget :buttons)))
2121 button
2122 (inhibit-read-only t)
2123 after-change-functions)
2124 (while buttons
2125 (setq button (car buttons)
2126 buttons (cdr buttons))
2127 (when (eq (widget-get button :widget) child)
2128 (widget-put widget
2129 :buttons (delq button (widget-get widget :buttons)))
2130 (widget-delete button))))
2131 (let ((entry-from (widget-get child :entry-from))
2132 (entry-to (widget-get child :entry-to))
2133 (inhibit-read-only t)
2134 after-change-functions)
2135 (widget-delete child)
2136 (delete-region entry-from entry-to)
2137 (set-marker entry-from nil)
2138 (set-marker entry-to nil))
2139 (widget-put widget :children (delq child (widget-get widget :children))))
2140 (widget-setup)
2141 (widget-apply widget :notify widget))
2142
2143 (defun widget-editable-list-entry-create (widget value conv)
2144 ;; Create a new entry to the list.
2145 (let ((type (nth 0 (widget-get widget :args)))
2146 (widget-push-button-gui widget-editable-list-gui)
2147 child delete insert)
2148 (widget-specify-insert
2149 (save-excursion
2150 (and (widget-get widget :indent)
2151 (insert-char ? (widget-get widget :indent)))
2152 (insert (widget-get widget :entry-format)))
2153 ;; Parse % escapes in format.
2154 (while (re-search-forward "%\\(.\\)" nil t)
2155 (let ((escape (aref (match-string 1) 0)))
2156 (replace-match "" t t)
2157 (cond ((eq escape ?%)
2158 (insert "%"))
2159 ((eq escape ?i)
2160 (setq insert (apply 'widget-create-child-and-convert
2161 widget 'insert-button
2162 (widget-get widget :insert-button-args))))
2163 ((eq escape ?d)
2164 (setq delete (apply 'widget-create-child-and-convert
2165 widget 'delete-button
2166 (widget-get widget :delete-button-args))))
2167 ((eq escape ?v)
2168 (if conv
2169 (setq child (widget-create-child-value
2170 widget type value))
2171 (setq child (widget-create-child widget type))))
2172 (t
2173 (error "Unknown escape `%c'" escape)))))
2174 (widget-put widget
2175 :buttons (cons delete
2176 (cons insert
2177 (widget-get widget :buttons))))
2178 (let ((entry-from (copy-marker (point-min)))
2179 (entry-to (copy-marker (point-max))))
2180 (widget-specify-text entry-from entry-to)
2181 (set-marker-insertion-type entry-from t)
2182 (set-marker-insertion-type entry-to nil)
2183 (widget-put child :entry-from entry-from)
2184 (widget-put child :entry-to entry-to)))
2185 (widget-put insert :widget child)
2186 (widget-put delete :widget child)
2187 child))
2188
2189 ;;; The `group' Widget.
2190
2191 (define-widget 'group 'default
2192 "A widget which group other widgets inside."
2193 :convert-widget 'widget-types-convert-widget
2194 :format "%v"
2195 :value-create 'widget-group-value-create
2196 :value-delete 'widget-children-value-delete
2197 :value-get 'widget-editable-list-value-get
2198 :validate 'widget-editable-list-validate
2199 :match 'widget-group-match
2200 :match-inline 'widget-group-match-inline)
2201
2202 (defun widget-group-value-create (widget)
2203 ;; Create each component.
2204 (let ((args (widget-get widget :args))
2205 (value (widget-get widget :value))
2206 arg answer children)
2207 (while args
2208 (setq arg (car args)
2209 args (cdr args)
2210 answer (widget-match-inline arg value)
2211 value (cdr answer))
2212 (and (eq (preceding-char) ?\n)
2213 (widget-get widget :indent)
2214 (insert-char ? (widget-get widget :indent)))
2215 (push (cond ((null answer)
2216 (widget-create-child widget arg))
2217 ((widget-get arg :inline)
2218 (widget-create-child-value widget arg (car answer)))
2219 (t
2220 (widget-create-child-value widget arg (car (car answer)))))
2221 children))
2222 (widget-put widget :children (nreverse children))))
2223
2224 (defun widget-group-match (widget values)
2225 ;; Match if the components match.
2226 (and (listp values)
2227 (let ((match (widget-group-match-inline widget values)))
2228 (and match (null (cdr match))))))
2229
2230 (defun widget-group-match-inline (widget vals)
2231 ;; Match if the components match.
2232 (let ((args (widget-get widget :args))
2233 argument answer found)
2234 (while args
2235 (setq argument (car args)
2236 args (cdr args)
2237 answer (widget-match-inline argument vals))
2238 (if answer
2239 (setq vals (cdr answer)
2240 found (append found (car answer)))
2241 (setq vals nil
2242 args nil)))
2243 (if answer
2244 (cons found vals)
2245 nil)))
2246
2247 ;;; The `widget-help' Widget.
2248
2249 (define-widget 'widget-help 'push-button
2250 "The widget documentation button."
2251 :format "%[[%t]%] %d"
2252 :help-echo "Toggle display of documentation."
2253 :action 'widget-help-action)
2254
2255 (defun widget-help-action (widget &optional event)
2256 "Toggle documentation for WIDGET."
2257 (let ((old (widget-get widget :doc))
2258 (new (widget-get widget :widget-doc)))
2259 (widget-put widget :doc new)
2260 (widget-put widget :widget-doc old))
2261 (widget-value-set widget (widget-value widget)))
2262
2263 ;;; The Sexp Widgets.
2264
2265 (define-widget 'const 'item
2266 "An immutable sexp."
2267 :prompt-value 'widget-const-prompt-value
2268 :format "%t\n%d")
2269
2270 (defun widget-const-prompt-value (widget prompt value unbound)
2271 ;; Return the value of the const.
2272 (widget-value widget))
2273
2274 (define-widget 'function-item 'const
2275 "An immutable function name."
2276 :format "%v\n%h"
2277 :documentation-property (lambda (symbol)
2278 (condition-case nil
2279 (documentation symbol t)
2280 (error nil))))
2281
2282 (define-widget 'variable-item 'const
2283 "An immutable variable name."
2284 :format "%v\n%h"
2285 :documentation-property 'variable-documentation)
2286
2287 (define-widget 'string 'editable-field
2288 "A string"
2289 :prompt-value 'widget-string-prompt-value
2290 :tag "String"
2291 :format "%[%t%]: %v")
2292
2293 (defvar widget-string-prompt-value-history nil
2294 "History of input to `widget-string-prompt-value'.")
2295
2296 (defun widget-string-prompt-value (widget prompt value unbound)
2297 ;; Read a string.
2298 (read-string prompt (if unbound nil (cons value 1))
2299 'widget-string-prompt-value-history))
2300
2301 (define-widget 'regexp 'string
2302 "A regular expression."
2303 :match 'widget-regexp-match
2304 :validate 'widget-regexp-validate
2305 :tag "Regexp")
2306
2307 (defun widget-regexp-match (widget value)
2308 ;; Match valid regexps.
2309 (and (stringp value)
2310 (condition-case data
2311 (prog1 t
2312 (string-match value ""))
2313 (error nil))))
2314
2315 (defun widget-regexp-validate (widget)
2316 "Check that the value of WIDGET is a valid regexp."
2317 (let ((val (widget-value widget)))
2318 (condition-case data
2319 (prog1 nil
2320 (string-match val ""))
2321 (error (widget-put widget :error (error-message-string data))
2322 widget))))
2323
2324 (define-widget 'file 'string
2325 "A file widget.
2326 It will read a file name from the minibuffer when activated."
2327 :prompt-value 'widget-file-prompt-value
2328 :format "%[%t%]: %v"
2329 :tag "File"
2330 :action 'widget-file-action)
2331
2332 (defun widget-file-prompt-value (widget prompt value unbound)
2333 ;; Read file from minibuffer.
2334 (abbreviate-file-name
2335 (if unbound
2336 (read-file-name prompt)
2337 (let ((prompt2 (concat prompt "(default `" value "') "))
2338 (dir (file-name-directory value))
2339 (file (file-name-nondirectory value))
2340 (must-match (widget-get widget :must-match)))
2341 (read-file-name prompt2 dir nil must-match file)))))
2342
2343 (defun widget-file-action (widget &optional event)
2344 ;; Read a file name from the minibuffer.
2345 (let* ((value (widget-value widget))
2346 (dir (file-name-directory value))
2347 (file (file-name-nondirectory value))
2348 (menu-tag (widget-apply widget :menu-tag-get))
2349 (must-match (widget-get widget :must-match))
2350 (answer (read-file-name (concat menu-tag ": (default `" value "') ")
2351 dir nil must-match file)))
2352 (widget-value-set widget (abbreviate-file-name answer))
2353 (widget-apply widget :notify widget event)
2354 (widget-setup)))
2355
2356 (define-widget 'directory 'file
2357 "A directory widget.
2358 It will read a directory name from the minibuffer when activated."
2359 :tag "Directory")
2360
2361 (define-widget 'symbol 'string
2362 "A lisp symbol."
2363 :value nil
2364 :tag "Symbol"
2365 :match (lambda (widget value) (symbolp value))
2366 :value-to-internal (lambda (widget value)
2367 (if (symbolp value)
2368 (symbol-name value)
2369 value))
2370 :value-to-external (lambda (widget value)
2371 (if (stringp value)
2372 (intern value)
2373 value)))
2374
2375 (define-widget 'function 'sexp
2376 ;; Should complete on functions.
2377 "A lisp function."
2378 :tag "Function")
2379
2380 (define-widget 'variable 'symbol
2381 ;; Should complete on variables.
2382 "A lisp variable."
2383 :tag "Variable")
2384
2385 (define-widget 'sexp 'string
2386 "An arbitrary lisp expression."
2387 :tag "Lisp expression"
2388 :value nil
2389 :validate 'widget-sexp-validate
2390 :match (lambda (widget value) t)
2391 :value-to-internal 'widget-sexp-value-to-internal
2392 :value-to-external (lambda (widget value) (read value))
2393 :prompt-value 'widget-sexp-prompt-value)
2394
2395 (defun widget-sexp-value-to-internal (widget value)
2396 ;; Use pp for printer representation.
2397 (let ((pp (pp-to-string value)))
2398 (while (string-match "\n\\'" pp)
2399 (setq pp (substring pp 0 -1)))
2400 (if (or (string-match "\n\\'" pp)
2401 (> (length pp) 40))
2402 (concat "\n" pp)
2403 pp)))
2404
2405 (defun widget-sexp-validate (widget)
2406 ;; Valid if we can read the string and there is no junk left after it.
2407 (save-excursion
2408 (let ((buffer (set-buffer (get-buffer-create " *Widget Scratch*"))))
2409 (erase-buffer)
2410 (insert (widget-apply widget :value-get))
2411 (goto-char (point-min))
2412 (condition-case data
2413 (let ((value (read buffer)))
2414 (if (eobp)
2415 (if (widget-apply widget :match value)
2416 nil
2417 (widget-put widget :error (widget-get widget :type-error))
2418 widget)
2419 (widget-put widget
2420 :error (format "Junk at end of expression: %s"
2421 (buffer-substring (point)
2422 (point-max))))
2423 widget))
2424 (error (widget-put widget :error (error-message-string data))
2425 widget)))))
2426
2427 (defvar widget-sexp-prompt-value-history nil
2428 "History of input to `widget-sexp-prompt-value'.")
2429
2430 (defun widget-sexp-prompt-value (widget prompt value unbound)
2431 ;; Read an arbitrary sexp.
2432 (let ((found (read-string prompt
2433 (if unbound nil (cons (prin1-to-string value) 1))
2434 'widget-sexp-prompt-value)))
2435 (let ((buffer (set-buffer (get-buffer-create " *Widget Scratch*"))))
2436 (erase-buffer)
2437 (insert found)
2438 (goto-char (point-min))
2439 (let ((answer (read buffer)))
2440 (unless (eobp)
2441 (error "Junk at end of expression: %s"
2442 (buffer-substring (point) (point-max))))
2443 answer))))
2444
2445 (define-widget 'integer 'sexp
2446 "An integer."
2447 :tag "Integer"
2448 :value 0
2449 :type-error "This field should contain an integer"
2450 :value-to-internal (lambda (widget value)
2451 (if (integerp value)
2452 (prin1-to-string value)
2453 value))
2454 :match (lambda (widget value) (integerp value)))
2455
2456 (define-widget 'character 'string
2457 "An character."
2458 :tag "Character"
2459 :value 0
2460 :size 1
2461 :format "%{%t%}: %v\n"
2462 :valid-regexp "\\`.\\'"
2463 :error "This field should contain a single character"
2464 :value-to-internal (lambda (widget value)
2465 (if (integerp value)
2466 (char-to-string value)
2467 value))
2468 :value-to-external (lambda (widget value)
2469 (if (stringp value)
2470 (aref value 0)
2471 value))
2472 :match (lambda (widget value) (integerp value)))
2473
2474 (define-widget 'number 'sexp
2475 "A floating point number."
2476 :tag "Number"
2477 :value 0.0
2478 :type-error "This field should contain a number"
2479 :value-to-internal (lambda (widget value)
2480 (if (numberp value)
2481 (prin1-to-string value)
2482 value))
2483 :match (lambda (widget value) (numberp value)))
2484
2485 (define-widget 'list 'group
2486 "A lisp list."
2487 :tag "List"
2488 :format "%{%t%}:\n%v")
2489
2490 (define-widget 'vector 'group
2491 "A lisp vector."
2492 :tag "Vector"
2493 :format "%{%t%}:\n%v"
2494 :match 'widget-vector-match
2495 :value-to-internal (lambda (widget value) (append value nil))
2496 :value-to-external (lambda (widget value) (apply 'vector value)))
2497
2498 (defun widget-vector-match (widget value)
2499 (and (vectorp value)
2500 (widget-group-match widget
2501 (widget-apply widget :value-to-internal value))))
2502
2503 (define-widget 'cons 'group
2504 "A cons-cell."
2505 :tag "Cons-cell"
2506 :format "%{%t%}:\n%v"
2507 :match 'widget-cons-match
2508 :value-to-internal (lambda (widget value)
2509 (list (car value) (cdr value)))
2510 :value-to-external (lambda (widget value)
2511 (cons (nth 0 value) (nth 1 value))))
2512
2513 (defun widget-cons-match (widget value)
2514 (and (consp value)
2515 (widget-group-match widget
2516 (widget-apply widget :value-to-internal value))))
2517
2518 (define-widget 'choice 'menu-choice
2519 "A union of several sexp types."
2520 :tag "Choice"
2521 :format "%[%t%]: %v")
2522
2523 (define-widget 'radio 'radio-button-choice
2524 "A union of several sexp types."
2525 :tag "Choice"
2526 :format "%{%t%}:\n%v")
2527
2528 (define-widget 'repeat 'editable-list
2529 "A variable length homogeneous list."
2530 :tag "Repeat"
2531 :format "%{%t%}:\n%v%i\n")
2532
2533 (define-widget 'set 'checklist
2534 "A list of members from a fixed set."
2535 :tag "Set"
2536 :format "%{%t%}:\n%v")
2537
2538 (define-widget 'boolean 'toggle
2539 "To be nil or non-nil, that is the question."
2540 :tag "Boolean"
2541 :prompt-value 'widget-boolean-prompt-value
2542 :format "%{%t%}: %[%v%]\n")
2543
2544 (defun widget-boolean-prompt-value (widget prompt value unbound)
2545 ;; Toggle a boolean.
2546 (cond (unbound
2547 (y-or-n-p prompt))
2548 (value
2549 (message "Off")
2550 nil)
2551 (t
2552 (message "On")
2553 t)))
2554
2555 ;;; The `color' Widget.
2556
2557 (define-widget 'color-item 'choice-item
2558 "A color name (with sample)."
2559 :format "%v (%{sample%})\n"
2560 :sample-face-get 'widget-color-item-button-face-get)
2561
2562 (defun widget-color-item-button-face-get (widget)
2563 ;; We create a face from the value.
2564 (require 'facemenu)
2565 (condition-case nil
2566 (facemenu-get-face (intern (concat "fg:" (widget-value widget))))
2567 (error 'default)))
2568
2569 (define-widget 'color 'push-button
2570 "Choose a color name (with sample)."
2571 :format "%[%t%]: %v"
2572 :tag "Color"
2573 :value "black"
2574 :value-create 'widget-color-value-create
2575 :value-delete 'widget-children-value-delete
2576 :value-get 'widget-color-value-get
2577 :value-set 'widget-color-value-set
2578 :action 'widget-color-action
2579 :match 'widget-field-match
2580 :tag "Color")
2581
2582 (defvar widget-color-choice-list nil)
2583 ;; Variable holding the possible colors.
2584
2585 (defun widget-color-choice-list ()
2586 (unless widget-color-choice-list
2587 (setq widget-color-choice-list
2588 (mapcar '(lambda (color) (list color))
2589 (x-defined-colors))))
2590 widget-color-choice-list)
2591
2592 (defun widget-color-value-create (widget)
2593 (let ((child (widget-create-child-and-convert
2594 widget 'color-item (widget-get widget :value))))
2595 (widget-put widget :children (list child))))
2596
2597 (defun widget-color-value-get (widget)
2598 ;; Pass command to first child.
2599 (widget-apply (car (widget-get widget :children)) :value-get))
2600
2601 (defun widget-color-value-set (widget value)
2602 ;; Pass command to first child.
2603 (widget-apply (car (widget-get widget :children)) :value-set value))
2604
2605 (defvar widget-color-history nil
2606 "History of entered colors")
2607
2608 (defun widget-color-action (widget &optional event)
2609 ;; Prompt for a color.
2610 (let* ((tag (widget-apply widget :menu-tag-get))
2611 (prompt (concat tag ": "))
2612 (answer (cond ((string-match "XEmacs" emacs-version)
2613 (read-color prompt))
2614 ((fboundp 'x-defined-colors)
2615 (completing-read (concat tag ": ")
2616 (widget-color-choice-list)
2617 nil nil nil 'widget-color-history))
2618 (t
2619 (read-string prompt (widget-value widget))))))
2620 (unless (zerop (length answer))
2621 (widget-value-set widget answer)
2622 (widget-apply widget :notify widget event)
2623 (widget-setup))))
2624
2625 ;;; The Help Echo
2626
2627 (defun widget-echo-help-mouse ()
2628 "Display the help message for the widget under the mouse.
2629 Enable with (run-with-idle-timer 1 t 'widget-echo-help-mouse)"
2630 (let* ((pos (mouse-position))
2631 (frame (car pos))
2632 (x (car (cdr pos)))
2633 (y (cdr (cdr pos)))
2634 (win (window-at x y frame))
2635 (where (coordinates-in-window-p (cons x y) win)))
2636 (when (consp where)
2637 (save-window-excursion
2638 (progn ; save-excursion
2639 (select-window win)
2640 (let* ((result (compute-motion (window-start win)
2641 '(0 . 0)
2642 (window-end win)
2643 where
2644 (window-width win)
2645 (cons (window-hscroll) 0)
2646 win)))
2647 (when (and (eq (nth 1 result) x)
2648 (eq (nth 2 result) y))
2649 (widget-echo-help (nth 0 result))))))))
2650 (unless track-mouse
2651 (setq track-mouse t)
2652 (add-hook 'post-command-hook 'widget-stop-mouse-tracking)))
2653
2654 (defun widget-stop-mouse-tracking (&rest args)
2655 "Stop the mouse tracking done while idle."
2656 (remove-hook 'post-command-hook 'widget-stop-mouse-tracking)
2657 (setq track-mouse nil))
2658
2659 (defun widget-at (pos)
2660 "The button or field at POS."
2661 (or (get-text-property pos 'button)
2662 (get-text-property pos 'field)))
2663
2664 (defun widget-echo-help (pos)
2665 "Display the help echo for widget at POS."
2666 (let* ((widget (widget-at pos))
2667 (help-echo (and widget (widget-get widget :help-echo))))
2668 (cond ((stringp help-echo)
2669 (message "%s" help-echo))
2670 ((and (symbolp help-echo) (fboundp help-echo)
2671 (stringp (setq help-echo (funcall help-echo widget))))
2672 (message "%s" help-echo)))))
2673
2674 ;;; The End:
2675
2676 (provide 'wid-edit)
2677
2678 ;; wid-edit.el ends here