;;; wid-edit.el --- Functions for creating and using widgets -*-byte-compile-dynamic: t;-*-
;;
-;; Copyright (C) 1996,97,1999,2000,01,02,2003, 2004 Free Software Foundation, Inc.
+;; Copyright (C) 1996, 1997, 1999, 2000, 2001, 2002, 2003,
+;; 2004, 2005, 2006, 2007 Free Software Foundation, Inc.
;;
;; Author: Per Abrahamsen <abraham@dina.kvl.dk>
;; Maintainer: FSF
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 2, or (at your option)
+;; the Free Software Foundation; either version 3, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING. If not, write to the
-;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+;; Boston, MA 02110-1301, USA.
;;; Wishlist items (from widget.texi):
;;; Code:
+(defvar widget)
+
;;; Compatibility.
(defun widget-event-point (event)
:group 'hypermedia)
(defgroup widget-documentation nil
- "Options controling the display of documentation strings."
+ "Options controlling the display of documentation strings."
:group 'widgets)
(defgroup widget-faces nil
:group 'widgets
:group 'faces)
-(defvar widget-documentation-face 'widget-documentation-face
+(defvar widget-documentation-face 'widget-documentation
"Face used for documentation strings in widgets.
This exists as a variable so it can be set locally in certain buffers.")
-(defface widget-documentation-face '((((class color)
- (background dark))
- (:foreground "lime green"))
- (((class color)
- (background light))
- (:foreground "dark green"))
- (t nil))
+(defface widget-documentation '((((class color)
+ (background dark))
+ (:foreground "lime green"))
+ (((class color)
+ (background light))
+ (:foreground "dark green"))
+ (t nil))
"Face used for documentation text."
:group 'widget-documentation
:group 'widget-faces)
+;; backward compatibility alias
+(put 'widget-documentation-face 'face-alias 'widget-documentation)
-(defvar widget-button-face 'widget-button-face
+(defvar widget-button-face 'widget-button
"Face used for buttons in widgets.
This exists as a variable so it can be set locally in certain buffers.")
-(defface widget-button-face '((t (:weight bold)))
+(defface widget-button '((t (:weight bold)))
"Face used for widget buttons."
:group 'widget-faces)
+;; backward compatibility alias
+(put 'widget-button-face 'face-alias 'widget-button)
(defcustom widget-mouse-face 'highlight
"Face used for widget buttons when the mouse is above them."
;; TTY gets special definitions here and in the next defface, because
;; the gray colors defined for other displays cause black text on a black
;; background, at least on light-background TTYs.
-(defface widget-field-face '((((type tty))
- :background "yellow3"
- :foreground "black")
- (((class grayscale color)
- (background light))
- :background "gray85")
- (((class grayscale color)
- (background dark))
- :background "dim gray")
- (t
- :slant italic))
+(defface widget-field '((((type tty))
+ :background "yellow3"
+ :foreground "black")
+ (((class grayscale color)
+ (background light))
+ :background "gray85")
+ (((class grayscale color)
+ (background dark))
+ :background "dim gray")
+ (t
+ :slant italic))
"Face used for editable fields."
:group 'widget-faces)
-
-(defface widget-single-line-field-face '((((type tty))
- :background "green3"
- :foreground "black")
- (((class grayscale color)
- (background light))
- :background "gray85")
- (((class grayscale color)
- (background dark))
- :background "dim gray")
- (t
- :slant italic))
+;; backward-compatibility alias
+(put 'widget-field-face 'face-alias 'widget-field)
+
+(defface widget-single-line-field '((((type tty))
+ :background "green3"
+ :foreground "black")
+ (((class grayscale color)
+ (background light))
+ :background "gray85")
+ (((class grayscale color)
+ (background dark))
+ :background "dim gray")
+ (t
+ :slant italic))
"Face used for editable fields spanning only a single line."
:group 'widget-faces)
+;; backward-compatibility alias
+(put 'widget-single-line-field-face 'face-alias 'widget-single-line-field)
;;; This causes display-table to be loaded, and not usefully.
;;;(defvar widget-single-line-display-table
keys
(char 0)
(arg 1))
- (while (not (or (and (>= char ?0) (< char next-digit))
+ (while (not (or (and (integerp char)
+ (>= char ?0) (< char next-digit))
(eq value 'keyboard-quit)))
;; Unread a SPC to lead to our new menu.
- (setq unread-command-events (cons ?\ unread-command-events))
+ (setq unread-command-events (cons ?\s unread-command-events))
(setq keys (read-key-sequence title))
(setq value
(lookup-key overriding-terminal-local-map keys t)
- char (string-to-char (substring keys 1)))
+ char (aref keys 1))
(cond ((eq value 'scroll-other-window)
(let ((minibuffer-scroll-window
(get-buffer-window buf)))
(insert-and-inherit " ")))
(setq to (point)))
(let ((keymap (widget-get widget :keymap))
- (face (or (widget-get widget :value-face) 'widget-field-face))
+ (face (or (widget-get widget :value-face) 'widget-field))
(help-echo (widget-get widget :help-echo))
+ (follow-link (widget-get widget :follow-link))
(rear-sticky
(or (not widget-field-add-space) (widget-get widget :size))))
(if (functionp help-echo)
;; one character.
(let ((overlay (make-overlay (1- to) to nil t nil)))
(overlay-put overlay 'field 'boundary)
+ ;; We need the real field for tabbing.
+ (overlay-put overlay 'real-field widget)
;; Use `local-map' here, not `keymap', so that normal editing
;; works in the field when, say, Custom uses `suppress-keymap'.
(overlay-put overlay 'local-map keymap)
(overlay-put overlay 'face face)
+ (overlay-put overlay 'follow-link follow-link)
(overlay-put overlay 'help-echo help-echo))
(setq to (1- to))
(setq rear-sticky t))
(overlay-put overlay 'field widget)
(overlay-put overlay 'local-map keymap)
(overlay-put overlay 'face face)
+ (overlay-put overlay 'follow-link follow-link)
(overlay-put overlay 'help-echo help-echo)))
(widget-specify-secret widget))
(end (widget-field-end field)))
(when size
(while (and (> end begin)
- (eq (char-after (1- end)) ?\ ))
+ (eq (char-after (1- end)) ?\s))
(setq end (1- end))))
(while (< begin end)
(let ((old (char-after begin)))
(defun widget-specify-button (widget from to)
"Specify button for WIDGET between FROM and TO."
(let ((overlay (make-overlay from to nil t nil))
+ (follow-link (widget-get widget :follow-link))
(help-echo (widget-get widget :help-echo)))
(widget-put widget :button-overlay overlay)
(if (functionp help-echo)
;; We want to avoid the face with image buttons.
(unless (widget-get widget :suppress-face)
(overlay-put overlay 'face (widget-apply widget :button-face-get))
- (overlay-put overlay 'mouse-face widget-mouse-face))
+ (overlay-put overlay 'mouse-face
+ ;; Make new list structure for the mouse-face value
+ ;; so that different widgets will have
+ ;; different `mouse-face' property values
+ ;; and will highlight separately.
+ (let ((mouse-face-value
+ (widget-apply widget :mouse-face-get)))
+ ;; If it's a list, copy it.
+ (if (listp mouse-face-value)
+ (copy-sequence mouse-face-value)
+ ;; If it's a symbol, put it in a list.
+ (list mouse-face-value)))))
+ (overlay-put overlay 'pointer 'hand)
+ (overlay-put overlay 'follow-link follow-link)
(overlay-put overlay 'help-echo help-echo)))
(defun widget-mouse-help (window overlay point)
(prog1 (progn ,@form)
(goto-char (point-max))))))
-(defface widget-inactive-face '((((class grayscale color)
- (background dark))
- (:foreground "light gray"))
- (((class grayscale color)
- (background light))
- (:foreground "dim gray"))
- (t
- (:slant italic)))
+(defface widget-inactive
+ '((t :inherit shadow))
"Face used for inactive widgets."
:group 'widget-faces)
+;; backward-compatibility alias
+(put 'widget-inactive-face 'face-alias 'widget-inactive)
(defun widget-specify-inactive (widget from to)
"Make WIDGET inactive for user modifications."
(unless (widget-get widget :inactive)
(let ((overlay (make-overlay from to nil t nil)))
- (overlay-put overlay 'face 'widget-inactive-face)
+ (overlay-put overlay 'face 'widget-inactive)
;; This is disabled, as it makes the mouse cursor change shape.
- ;; (overlay-put overlay 'mouse-face 'widget-inactive-face)
+ ;; (overlay-put overlay 'mouse-face 'widget-inactive)
(overlay-put overlay 'evaporate t)
(overlay-put overlay 'priority 100)
(overlay-put overlay 'modification-hooks '(widget-overlay-inactive))
;;; Widget Properties.
(defsubst widget-type (widget)
- "Return the type of WIDGET, a symbol."
+ "Return the type of WIDGET. The type is a symbol."
(car widget))
;;;###autoload
(defun widgetp (widget)
- "Return non-nil iff WIDGET is a widget."
+ "Return non-nil if WIDGET is a widget."
(if (symbolp widget)
(get widget 'widget-type)
(and (consp widget)
value)))
(defun widget-member (widget property)
- "Non-nil iff there is a definition in WIDGET for PROPERTY."
+ "Non-nil if there is a definition in WIDGET for PROPERTY."
(cond ((plist-member (cdr widget) property)
t)
((car widget)
:type 'directory)
(defcustom widget-image-enable t
- "If non nil, use image buttons in widgets when available."
+ "If non-nil, use image buttons in widgets when available."
:version "21.1"
:group 'widgets
:type 'boolean)
;; Oh well.
nil)))
-(defvar widget-button-pressed-face 'widget-button-pressed-face
+(defvar widget-button-pressed-face 'widget-button-pressed
"Face used for pressed buttons in widgets.
This exists as a variable so it can be set locally in certain
buffers.")
(progn (widget-put widget :suppress-face t)
(insert-image image
(propertize
- tag 'mouse-face widget-button-pressed-face)))
+ ;; Use a `list' so it's unique and won't get
+ ;; accidentally merged with neighbouring images.
+ tag 'mouse-face (list widget-button-pressed-face))))
(insert tag)))
+(defun widget-move-and-invoke (event)
+ "Move to where you click, and if it is an active field, invoke it."
+ (interactive "e")
+ (mouse-set-point event)
+ (let ((pos (widget-event-point event)))
+ (if (and pos (get-char-property pos 'button))
+ (widget-button-click event))))
+
;;; Buttons.
(defgroup widget-button nil
&optional button-from button-to
&rest args)
"Return a widget of type TYPE with endpoint FROM TO.
-Optional ARGS are extra keyword arguments for TYPE.
-and TO will be used as the widgets end points. If optional arguments
+No text will be inserted to the buffer, instead the text between FROM
+and TO will be used as the widgets end points. If optional arguments
BUTTON-FROM and BUTTON-TO are given, these will be used as the widgets
button end points.
Optional ARGS are extra keyword arguments for TYPE."
;;; Keymap and Commands.
+;;;###autoload
+(defalias 'advertised-widget-backward 'widget-backward)
+
;;;###autoload
(defvar widget-keymap
(let ((map (make-sparse-keymap)))
(define-key map "\t" 'widget-forward)
- (define-key map [(shift tab)] 'widget-backward)
+ (define-key map "\e\t" 'widget-backward)
+ (define-key map [(shift tab)] 'advertised-widget-backward)
(define-key map [backtab] 'widget-backward)
(define-key map [down-mouse-2] 'widget-button-click)
+ (define-key map [down-mouse-1] 'widget-button-click)
(define-key map "\C-m" 'widget-button-press)
map)
"Keymap containing useful binding for buffers containing widgets.
(call-interactively
(lookup-key widget-global-map (this-command-keys))))))
-(defface widget-button-pressed-face
- '((((class color))
+(defface widget-button-pressed
+ '((((min-colors 88) (class color))
+ (:foreground "red1"))
+ (((class color))
(:foreground "red"))
(t
(:weight bold :underline t)))
"Face used for pressed buttons."
:group 'widget-faces)
+;; backward-compatibility alias
+(put 'widget-button-pressed-face 'face-alias 'widget-button-pressed)
+
+(defvar widget-button-click-moves-point nil
+ "If non-nil, `widget-button-click' moves point to a button after invoking it.
+If nil, point returns to its original position after invoking a button.")
(defun widget-button-click (event)
"Invoke the button that the mouse is pointing at."
(interactive "e")
(if (widget-event-point event)
- (let* ((pos (widget-event-point event))
+ (let* ((oevent event)
+ (mouse-1 (memq (event-basic-type event) '(mouse-1 down-mouse-1)))
+ (pos (widget-event-point event))
(start (event-start event))
(button (get-char-property
pos 'button (and (windowp (posn-window start))
- (window-buffer (posn-window start))))))
- (if button
- ;; Mouse click on a widget button. Do the following
- ;; in a save-excursion so that the click on the button
- ;; doesn't change point.
- (save-selected-window
- (select-window (posn-window (event-start event)))
- (save-excursion
- (goto-char (posn-point (event-start event)))
- (let* ((overlay (widget-get button :button-overlay))
- (face (overlay-get overlay 'face))
- (mouse-face (overlay-get overlay 'mouse-face)))
- (unwind-protect
- ;; Read events, including mouse-movement events
- ;; until we receive a release event. Highlight/
- ;; unhighlight the button the mouse was initially
- ;; on when we move over it.
- (let ((track-mouse t))
+ (window-buffer (posn-window start)))))
+ newpoint)
+ (when (or (null button)
+ (catch 'button-press-cancelled
+ ;; Mouse click on a widget button. Do the following
+ ;; in a save-excursion so that the click on the button
+ ;; doesn't change point.
+ (save-selected-window
+ (select-window (posn-window (event-start event)))
+ (save-excursion
+ (goto-char (posn-point (event-start event)))
+ (let* ((overlay (widget-get button :button-overlay))
+ (pressed-face (or (widget-get button :pressed-face)
+ widget-button-pressed-face))
+ (face (overlay-get overlay 'face))
+ (mouse-face (overlay-get overlay 'mouse-face)))
+ (unwind-protect
+ ;; Read events, including mouse-movement
+ ;; events, waiting for a release event. If we
+ ;; began with a mouse-1 event and receive a
+ ;; movement event, that means the user wants
+ ;; to perform drag-selection, so cancel the
+ ;; button press and do the default mouse-1
+ ;; action. For mouse-2, just highlight/
+ ;; unhighlight the button the mouse was
+ ;; initially on when we move over it.
(save-excursion
(when face ; avoid changing around image
- (overlay-put overlay
- 'face widget-button-pressed-face)
- (overlay-put overlay
- 'mouse-face widget-button-pressed-face))
+ (overlay-put overlay 'face pressed-face)
+ (overlay-put overlay 'mouse-face pressed-face))
(unless (widget-apply button :mouse-down-action event)
- (while (not (widget-button-release-event-p event))
- (setq event (read-event)
- pos (widget-event-point event))
- (if (and pos
- (eq (get-char-property pos 'button)
- button))
- (when face
- (overlay-put overlay
- 'face
- widget-button-pressed-face)
- (overlay-put overlay
- 'mouse-face
- widget-button-pressed-face))
- (overlay-put overlay 'face face)
- (overlay-put overlay 'mouse-face mouse-face))))
+ (let ((track-mouse t))
+ (while (not (widget-button-release-event-p event))
+ (setq event (read-event))
+ (when (and mouse-1 (mouse-movement-p event))
+ (push event unread-command-events)
+ (setq event oevent)
+ (throw 'button-press-cancelled t))
+ (unless (or (integerp event)
+ (memq (car event) '(switch-frame select-window))
+ (eq (car event) 'scroll-bar-movement))
+ (setq pos (widget-event-point event))
+ (if (and pos
+ (eq (get-char-property pos 'button)
+ button))
+ (when face
+ (overlay-put overlay 'face pressed-face)
+ (overlay-put overlay 'mouse-face pressed-face))
+ (overlay-put overlay 'face face)
+ (overlay-put overlay 'mouse-face mouse-face))))))
;; When mouse is released over the button, run
;; its action function.
- (when (and pos
- (eq (get-char-property pos 'button) button))
- (widget-apply-action button event))))
- (overlay-put overlay 'face face)
- (overlay-put overlay 'mouse-face mouse-face))))
-
- (unless (pos-visible-in-window-p (widget-event-point event))
- (mouse-set-point event)
- (beginning-of-line)
- (recenter))
- )
-
- (let ((up t) command)
- ;; Mouse click not on a widget button. Find the global
- ;; command to run, and check whether it is bound to an
- ;; up event.
- (mouse-set-point event)
- (if (memq (event-basic-type event) '(mouse-1 down-mouse-1))
- (cond ((setq command ;down event
- (lookup-key widget-global-map [down-mouse-1]))
- (setq up nil))
- ((setq command ;up event
- (lookup-key widget-global-map [mouse-1]))))
+ (when (and pos (eq (get-char-property pos 'button) button))
+ (goto-char pos)
+ (widget-apply-action button event)
+ (if widget-button-click-moves-point
+ (setq newpoint (point)))))
+ (overlay-put overlay 'face face)
+ (overlay-put overlay 'mouse-face mouse-face))))
+
+ (if newpoint (goto-char newpoint))
+ ;; This loses if the widget action switches windows. -- cyd
+ ;; (unless (pos-visible-in-window-p (widget-event-point event))
+ ;; (mouse-set-point event)
+ ;; (beginning-of-line)
+ ;; (recenter))
+ )
+ nil))
+ (let ((up t) command)
+ ;; Mouse click not on a widget button. Find the global
+ ;; command to run, and check whether it is bound to an
+ ;; up event.
+ (if mouse-1
(cond ((setq command ;down event
- (lookup-key widget-global-map [down-mouse-2]))
+ (lookup-key widget-global-map [down-mouse-1]))
(setq up nil))
((setq command ;up event
- (lookup-key widget-global-map [mouse-2])))))
- (when up
- ;; Don't execute up events twice.
- (while (not (widget-button-release-event-p event))
- (setq event (read-event))))
- (when command
- (call-interactively command)))))
+ (lookup-key widget-global-map [mouse-1]))))
+ (cond ((setq command ;down event
+ (lookup-key widget-global-map [down-mouse-2]))
+ (setq up nil))
+ ((setq command ;up event
+ (lookup-key widget-global-map [mouse-2])))))
+ (when up
+ ;; Don't execute up events twice.
+ (while (not (widget-button-release-event-p event))
+ (setq event (read-event))))
+ (when command
+ (call-interactively command)))))
(message "You clicked somewhere weird.")))
(defun widget-button-press (pos &optional event)
:type 'function
:group 'widgets)
+(defun widget-narrow-to-field ()
+ "Narrow to field."
+ (interactive)
+ (let ((field (widget-field-find (point))))
+ (if field
+ (narrow-to-region (line-beginning-position) (line-end-position)))))
+
(defun widget-complete ()
"Complete content of editable field from point.
When not inside a field, move to the previous button or field."
(interactive)
(let ((field (widget-field-find (point))))
(if field
- (widget-apply field :complete)
- (error "Not in an editable field"))))
+ (save-restriction
+ (widget-narrow-to-field)
+ (widget-apply field :complete))
+ (error "Not in an editable field"))))
;;; Setting up the buffer.
"Return the widget field at POS, or nil if none."
(let ((field (get-char-property (or pos (point)) 'field)))
(if (eq field 'boundary)
- nil
+ (get-char-property (or pos (point)) 'real-field)
field)))
(defun widget-field-buffer (widget)
- "Return the start of WIDGET's editing field."
+ "Return the buffer of WIDGET's editing field."
(let ((overlay (widget-get widget :field-overlay)))
(cond ((overlayp overlay)
(overlay-buffer overlay))
;; or if a special `boundary' field has been added after the widget
;; field.
(if (overlayp overlay)
- (if (and (not (eq (get-char-property (overlay-end overlay)
- 'field
- (widget-field-buffer widget))
- 'boundary))
- (or widget-field-add-space
- (null (widget-get widget :size))))
- (1- (overlay-end overlay))
- (overlay-end overlay))
+ ;; Don't proceed if overlay has been removed from buffer.
+ (when (overlay-buffer overlay)
+ (if (and (not (eq (with-current-buffer
+ (widget-field-buffer widget)
+ (save-restriction
+ ;; `widget-narrow-to-field' can be
+ ;; active when this function is called
+ ;; from an change-functions hook. So
+ ;; temporarily remove field narrowing
+ ;; before to call `get-char-property'.
+ (widen)
+ (get-char-property (overlay-end overlay)
+ 'field)))
+ 'boundary))
+ (or widget-field-add-space
+ (null (widget-get widget :size))))
+ (1- (overlay-end overlay))
+ (overlay-end overlay)))
(cdr overlay))))
(defun widget-field-find (pos)
"Return the field at POS.
-Unlike (get-char-property POS 'field) this, works with empty fields too."
+Unlike (get-char-property POS 'field), this works with empty fields too."
(let ((fields widget-field-list)
field found)
(while fields
;; Field too small.
(save-excursion
(goto-char end)
- (insert-char ?\ (- (+ begin size) end))))
+ (insert-char ?\s (- (+ begin size) end))))
((> (- end begin) size)
;; Field too large and
(if (or (< (point) (+ begin size))
(setq begin (point)))
(save-excursion
(goto-char end)
- (while (and (eq (preceding-char) ?\ )
+ (while (and (eq (preceding-char) ?\s)
(> (point) begin))
(delete-backward-char 1)))))))
(widget-specify-secret field))
The value of the :type attribute should be an unconverted widget type."
(let ((value (widget-get widget :value))
(type (widget-get widget :type)))
- (widget-put widget :children
- (list (widget-create-child-value widget
+ (widget-put widget :children
+ (list (widget-create-child-value widget
(widget-convert type)
value)))))
:offset 0
:format-handler 'widget-default-format-handler
:button-face-get 'widget-default-button-face-get
+ :mouse-face-get 'widget-default-mouse-face-get
:sample-face-get 'widget-default-sample-face-get
:delete 'widget-default-delete
:copy 'identity
(defun widget-default-complete (widget)
"Call the value of the :complete-function property of WIDGET.
-If that does not exists, call the value of `widget-complete-field'."
+If that does not exist, call the value of `widget-complete-field'."
(call-interactively (or (widget-get widget :complete-function)
widget-complete-field)))
((eq escape ?n)
(when (widget-get widget :indent)
(insert ?\n)
- (insert-char ? (widget-get widget :indent))))
+ (insert-char ?\s (widget-get widget :indent))))
((eq escape ?t)
(let ((image (widget-get widget :tag-glyph))
(tag (widget-get widget :tag)))
(delete-backward-char 1))
(insert ?\n)
(setq doc-end (point)))))
+ ((eq escape ?h)
+ (widget-add-documentation-string-button widget))
((eq escape ?v)
(if (and button-begin (not button-end))
(widget-apply widget :value-create)
(widget-clear-undo))
(defun widget-default-format-handler (widget escape)
- ;; We recognize the %h escape by default.
- (let* ((buttons (widget-get widget :buttons)))
- (cond ((eq escape ?h)
- (let* ((doc-property (widget-get widget :documentation-property))
- (doc-try (cond ((widget-get widget :doc))
- ((functionp doc-property)
- (funcall doc-property
- (widget-get widget :value)))
- ((symbolp doc-property)
- (documentation-property
- (widget-get widget :value)
- doc-property))))
- (doc-text (and (stringp doc-try)
- (> (length doc-try) 1)
- doc-try))
- (doc-indent (widget-get widget :documentation-indent)))
- (when doc-text
- (and (eq (preceding-char) ?\n)
- (widget-get widget :indent)
- (insert-char ? (widget-get widget :indent)))
- ;; The `*' in the beginning is redundant.
- (when (eq (aref doc-text 0) ?*)
- (setq doc-text (substring doc-text 1)))
- ;; Get rid of trailing newlines.
- (when (string-match "\n+\\'" doc-text)
- (setq doc-text (substring doc-text 0 (match-beginning 0))))
- (push (widget-create-child-and-convert
- widget 'documentation-string
- :indent (cond ((numberp doc-indent )
- doc-indent)
- ((null doc-indent)
- nil)
- (t 0))
- doc-text)
- buttons))))
- (t
- (error "Unknown escape `%c'" escape)))
- (widget-put widget :buttons buttons)))
+ (error "Unknown escape `%c'" escape))
(defun widget-default-button-face-get (widget)
;; Use :button-face or widget-button-face
(widget-apply parent :button-face-get)
widget-button-face))))
+(defun widget-default-mouse-face-get (widget)
+ ;; Use :mouse-face or widget-mouse-face
+ (or (widget-get widget :mouse-face)
+ (let ((parent (widget-get widget :parent)))
+ (if parent
+ (widget-apply parent :mouse-face-get)
+ widget-mouse-face))))
+
(defun widget-default-sample-face-get (widget)
;; Use :sample-face.
(widget-get widget :sample-face))
(widget-princ-to-string (widget-get widget :value))))
(defun widget-default-active (widget)
- "Return t iff this widget active (user modifiable)."
+ "Return t if this widget is active (user modifiable)."
(or (widget-get widget :always-active)
(and (not (widget-get widget :inactive))
(let ((parent (widget-get widget :parent)))
(widget-default-action widget event))
(defun widget-default-prompt-value (widget prompt value unbound)
- "Read an arbitrary value. Stolen from `set-variable'."
-;; (let ((initial (if unbound
-;; nil
-;; It would be nice if we could do a `(cons val 1)' here.
-;; (prin1-to-string (custom-quote value))))))
+ "Read an arbitrary value."
(eval-minibuffer prompt))
+(defun widget-docstring (widget)
+ "Return the documentation string specificied by WIDGET, or nil if none.
+If WIDGET has a `:doc' property, that specifies the documentation string.
+Otherwise, try the `:documentation-property' property. If this
+is a function, call it with the widget's value as an argument; if
+it is a symbol, use this symbol together with the widget's value
+as the argument to `documentation-property'."
+ (let ((doc (or (widget-get widget :doc)
+ (let ((doc-prop (widget-get widget :documentation-property))
+ (value (widget-get widget :value)))
+ (cond ((functionp doc-prop)
+ (funcall doc-prop value))
+ ((symbolp doc-prop)
+ (documentation-property value doc-prop)))))))
+ (when (and (stringp doc) (> (length doc) 0))
+ ;; Remove any redundant `*' in the beginning.
+ (when (eq (aref doc 0) ?*)
+ (setq doc (substring doc 1)))
+ ;; Remove trailing newlines.
+ (when (string-match "\n+\\'" doc)
+ (setq doc (substring doc 0 (match-beginning 0))))
+ doc)))
+
;;; The `item' Widget.
(define-widget 'item 'default
;;; The `push-button' Widget.
;; (defcustom widget-push-button-gui t
-;; "If non nil, use GUI push buttons when available."
+;; "If non-nil, use GUI push buttons when available."
;; :group 'widgets
;; :type 'boolean)
"An embedded link."
:button-prefix 'widget-link-prefix
:button-suffix 'widget-link-suffix
+ :follow-link "\C-m"
:help-echo "Follow the link."
:format "%[%t%]")
:action 'widget-url-link-action)
(defun widget-url-link-action (widget &optional event)
- "Open the url specified by WIDGET."
+ "Open the URL specified by WIDGET."
(browse-url (widget-value widget)))
;;; The `function-link' Widget.
:action 'widget-emacs-library-link-action)
(defun widget-emacs-library-link-action (widget &optional event)
- "Find the Emacs Library file specified by WIDGET."
+ "Find the Emacs library file specified by WIDGET."
(find-file (locate-library (widget-value widget))))
;;; The `emacs-commentary-link' Widget.
;;; The `editable-field' Widget.
(define-widget 'editable-field 'default
- "An editable text field."
+ "An editable text field.
+Note: In an `editable-field' widget, the `%v' escape must be preceded
+by some other text in the `:format' string (if specified)."
:convert-widget 'widget-value-convert-widget
:keymap widget-field-keymap
:format "%v"
"History of field minibuffer edits.")
(defun widget-field-prompt-internal (widget prompt initial history)
- "Read string for WIDGET promptinhg with PROMPT.
+ "Read string for WIDGET prompting with PROMPT.
INITIAL is the initial input and HISTORY is a symbol containing
the earlier input."
(read-string prompt initial history))
(insert value)
(and size
(< (length value) size)
- (insert-char ?\ (- size (length value))))
+ (insert-char ?\s (- size (length value))))
(unless (memq widget widget-field-list)
(setq widget-field-new (cons widget widget-field-new)))
(move-marker (cdr overlay) (point))
(while (and size
(not (zerop size))
(> to from)
- (eq (char-after (1- to)) ?\ ))
+ (eq (char-after (1- to)) ?\s))
(setq to (1- to)))
(let ((result (buffer-substring-no-properties from to)))
(when secret
(args (widget-get widget :args))
(explicit (widget-get widget :explicit-choice))
current)
- (if (and explicit (equal value (widget-get widget :explicit-choice-value)))
+ (if explicit
(progn
;; If the user specified the choice for this value,
- ;; respect that choice as long as the value is the same.
+ ;; respect that choice.
(widget-put widget :children (list (widget-create-child-value
widget explicit value)))
- (widget-put widget :choice explicit))
+ (widget-put widget :choice explicit)
+ (widget-put widget :explicit-choice nil))
(while args
(setq current (car args)
args (cdr args))
(setq this-explicit t)
(widget-choose tag (reverse choices) event))))
(when current
- ;; If this was an explicit user choice,
- ;; record the choice, and the record the value it was made for.
- ;; widget-choice-value-create will respect this choice,
- ;; as long as the value is the same.
+ ;; If this was an explicit user choice, record the choice,
+ ;; so that widget-choice-value-create will respect it.
(when this-explicit
- (widget-put widget :explicit-choice current)
- (widget-put widget :explicit-choice-value (widget-get widget :value)))
+ (widget-put widget :explicit-choice current))
(widget-value-set widget (widget-default-get current))
(widget-setup)
(widget-apply widget :notify widget event)))
(when sibling
(if (widget-value widget)
(widget-apply sibling :activate)
- (widget-apply sibling :deactivate)))))
+ (widget-apply sibling :deactivate))
+ (widget-clear-undo))))
;;; The `checklist' Widget.
If the item is checked, CHOSEN is a cons whose cdr is the value."
(and (eq (preceding-char) ?\n)
(widget-get widget :indent)
- (insert-char ? (widget-get widget :indent)))
+ (insert-char ?\s (widget-get widget :indent)))
(widget-specify-insert
(let* ((children (widget-get widget :children))
(buttons (widget-get widget :buttons))
;; (setq type (widget-convert type))
(and (eq (preceding-char) ?\n)
(widget-get widget :indent)
- (insert-char ? (widget-get widget :indent)))
+ (insert-char ?\s (widget-get widget :indent)))
(widget-specify-insert
(let* ((value (widget-get widget :value))
(children (widget-get widget :children))
;;; The `editable-list' Widget.
;; (defcustom widget-editable-list-gui nil
-;; "If non nil, use GUI push-buttons in editable list when available."
+;; "If non-nil, use GUI push-buttons in editable list when available."
;; :type 'boolean
;; :group 'widgets)
;; (let ((widget-push-button-gui widget-editable-list-gui))
(cond ((eq escape ?i)
(and (widget-get widget :indent)
- (insert-char ?\ (widget-get widget :indent)))
+ (insert-char ?\s (widget-get widget :indent)))
(apply 'widget-create-child-and-convert
widget 'insert-button
(widget-get widget :append-button-args)))
(widget-specify-insert
(save-excursion
(and (widget-get widget :indent)
- (insert-char ?\ (widget-get widget :indent)))
+ (insert-char ?\s (widget-get widget :indent)))
(insert (widget-get widget :entry-format)))
;; Parse % escapes in format.
(while (re-search-forward "%\\(.\\)" nil t)
value (cdr answer))
(and (eq (preceding-char) ?\n)
(widget-get widget :indent)
- (insert-char ?\ (widget-get widget :indent)))
+ (insert-char ?\s (widget-get widget :indent)))
(push (cond ((null answer)
(widget-create-child widget arg))
((widget-get arg :inline)
(defcustom widget-documentation-link-p 'intern-soft
"Predicate used to test if a string is useful as a link.
-The value should be a function. The function will be called one
+The value should be a function. The function will be called with one
argument, a string, and should return non-nil if there should be a
link for that string."
:type 'function
(narrow-to-region from to)
(goto-char (point-min))
(while (search-forward "\n" nil t)
- (insert-char ?\ indent)))))))
+ (insert-char ?\s indent)))))))
;;; The `documentation-string' Widget.
"A documentation string."
:format "%v"
:action 'widget-documentation-string-action
- :value-create 'widget-documentation-string-value-create)
+ :value-create 'widget-documentation-string-value-create
+ :visibility-widget 'visibility)
(defun widget-documentation-string-value-create (widget)
;; Insert documentation string.
(let ((before (substring doc 0 (match-beginning 0)))
(after (substring doc (match-beginning 0)))
button)
- (insert before ?\ )
+ (when (and indent (not (zerop indent)))
+ (insert-char ?\s indent))
+ (insert before ?\s)
(widget-documentation-link-add widget start (point))
(setq button
(widget-create-child-and-convert
- widget 'visibility
+ widget (widget-get widget :visibility-widget)
:help-echo "Show or hide rest of the documentation."
:on "Hide Rest"
:off "More"
(when shown
(setq start (point))
(when (and indent (not (zerop indent)))
- (insert-char ?\ indent))
+ (insert-char ?\s indent))
(insert after)
(widget-documentation-link-add widget start (point)))
(widget-put widget :buttons (list button)))
+ (when (and indent (not (zerop indent)))
+ (insert-char ?\s indent))
(insert doc)
(widget-documentation-link-add widget start (point))))
(insert ?\n))
(not (widget-get parent :documentation-shown))))
;; Redraw.
(widget-value-set widget (widget-value widget)))
+
+(defun widget-add-documentation-string-button (widget &rest args)
+ "Insert a new `documentation-string' widget based on WIDGET.
+The new widget becomes a child of WIDGET, and is also added to
+its `:buttons' list. The documentation string is found from
+WIDGET using the function `widget-docstring'.
+Optional ARGS specifies additional keyword arguments for the
+`documentation-string' widget."
+ (let ((doc (widget-docstring widget))
+ (indent (widget-get widget :indent))
+ (doc-indent (widget-get widget :documentation-indent)))
+ (when doc
+ (and (eq (preceding-char) ?\n)
+ indent
+ (insert-char ?\s indent))
+ (unless (or (numberp doc-indent) (null doc-indent))
+ (setq doc-indent 0))
+ (widget-put widget :buttons
+ (cons (apply 'widget-create-child-and-convert
+ widget 'documentation-string
+ :indent doc-indent
+ (nconc args (list doc)))
+ (widget-get widget :buttons))))))
\f
;;; The Sexp Widgets.
:match 'widget-regexp-match
:validate 'widget-regexp-validate
;; Doesn't work well with terminating newline.
- ;; :value-face 'widget-single-line-field-face
+ ;; :value-face 'widget-single-line-field
:tag "Regexp")
(defun widget-regexp-match (widget value)
(define-widget 'file 'string
"A file widget.
-It will read a file name from the minibuffer when invoked."
+It reads a file name from an editable text field."
:complete-function 'widget-file-complete
:prompt-value 'widget-file-prompt-value
:format "%{%t%}: %v"
;; Doesn't work well with terminating newline.
- ;; :value-face 'widget-single-line-field-face
+ ;; :value-face 'widget-single-line-field
:tag "File")
(defun widget-file-complete ()
"Perform completion on file name preceding point."
(interactive)
(let* ((end (point))
- (beg (save-excursion
- (skip-chars-backward "^ ")
- (point)))
+ (beg (widget-field-start widget))
(pattern (buffer-substring beg end))
(name-part (file-name-nondirectory pattern))
- (directory (file-name-directory pattern))
+ ;; I think defaulting to root is right
+ ;; because these really should be absolute file names.
+ (directory (or (file-name-directory pattern) "/"))
(completion (file-name-completion name-part directory)))
(cond ((eq completion t))
((null completion)
(with-output-to-temp-buffer "*Completions*"
(display-completion-list
(sort (file-name-all-completions name-part directory)
- 'string<)))
+ 'string<)
+ name-part))
(message "Making completion list...%s" "done")))))
(defun widget-file-prompt-value (widget prompt value unbound)
(abbreviate-file-name
(if unbound
(read-file-name prompt)
- (let ((prompt2 (format "%s (default %s) " prompt value))
+ (let ((prompt2 (format "%s (default %s): " prompt value))
(dir (file-name-directory value))
(file (file-name-nondirectory value))
(must-match (widget-get widget :must-match)))
;;; (file (file-name-nondirectory value))
;;; (menu-tag (widget-apply widget :menu-tag-get))
;;; (must-match (widget-get widget :must-match))
-;;; (answer (read-file-name (concat menu-tag ": (default `" value "') ")
+;;; (answer (read-file-name (concat menu-tag " (default " value "): ")
;;; dir nil must-match file)))
;;; (widget-value-set widget (abbreviate-file-name answer))
;;; (widget-setup)
;; Fixme: use file-name-as-directory.
(define-widget 'directory 'file
"A directory widget.
-It will read a directory name from the minibuffer when invoked."
+It reads a directory name from an editable text field."
:tag "Directory")
(defvar widget-symbol-prompt-value-history nil
(defvar widget-function-prompt-value-history nil
"History of input to `widget-function-prompt-value'.")
-(define-widget 'function 'sexp
+(define-widget 'function 'restricted-sexp
"A Lisp function."
:complete-function (lambda ()
(interactive)
(interactive)
(lisp-complete-symbol 'boundp))
:tag "Variable")
-
+\f
(defvar widget-coding-system-prompt-value-history nil
"History of input to `widget-coding-system-prompt-value'.")
"Read coding-system from minibuffer."
(if (widget-get widget :base-only)
(intern
- (completing-read (format "%s (default %s) " prompt value)
+ (completing-read (format "%s (default %s): " prompt value)
(mapcar #'list (coding-system-list t)) nil nil nil
coding-system-history))
- (read-coding-system (format "%s (default %s) " prompt value) value)))
+ (read-coding-system (format "%s (default %s): " prompt value) value)))
(defun widget-coding-system-action (widget &optional event)
(let ((answer
(widget-apply widget :notify widget event)
(widget-setup)))
\f
+;;; I'm not sure about what this is good for? KFS.
+(defvar widget-key-sequence-prompt-value-history nil
+ "History of input to `widget-key-sequence-prompt-value'.")
+
+(defvar widget-key-sequence-default-value [ignore]
+ "Default value for an empty key sequence.")
+
+(defvar widget-key-sequence-map
+ (let ((map (make-sparse-keymap)))
+ (set-keymap-parent map widget-field-keymap)
+ (define-key map [(control ?q)] 'widget-key-sequence-read-event)
+ map))
+
+(define-widget 'key-sequence 'restricted-sexp
+ "A key sequence."
+ :prompt-value 'widget-field-prompt-value
+ :prompt-internal 'widget-symbol-prompt-internal
+; :prompt-match 'fboundp ;; What was this good for? KFS
+ :prompt-history 'widget-key-sequence-prompt-value-history
+ :action 'widget-field-action
+ :match-alternatives '(stringp vectorp)
+ :format "%{%t%}: %v"
+ :validate 'widget-key-sequence-validate
+ :value-to-internal 'widget-key-sequence-value-to-internal
+ :value-to-external 'widget-key-sequence-value-to-external
+ :value widget-key-sequence-default-value
+ :keymap widget-key-sequence-map
+ :help-echo "C-q: insert KEY, EVENT, or CODE; RET: enter value"
+ :tag "Key sequence")
+
+(defun widget-key-sequence-read-event (ev)
+ (interactive (list
+ (let ((inhibit-quit t) quit-flag)
+ (read-event "Insert KEY, EVENT, or CODE: "))))
+ (let ((ev2 (and (memq 'down (event-modifiers ev))
+ (read-event)))
+ (tr (and (keymapp function-key-map)
+ (lookup-key function-key-map (vector ev)))))
+ (when (and (integerp ev)
+ (or (and (<= ?0 ev) (< ev (+ ?0 (min 10 read-quoted-char-radix))))
+ (and (<= ?a (downcase ev))
+ (< (downcase ev) (+ ?a -10 (min 36 read-quoted-char-radix))))))
+ (setq unread-command-events (cons ev unread-command-events)
+ ev (read-quoted-char (format "Enter code (radix %d)" read-quoted-char-radix))
+ tr nil)
+ (if (and (integerp ev) (not (char-valid-p ev)))
+ (insert (char-to-string ev)))) ;; throw invalid char error
+ (setq ev (key-description (list ev)))
+ (when (arrayp tr)
+ (setq tr (key-description (list (aref tr 0))))
+ (if (y-or-n-p (format "Key %s is translated to %s -- use %s? " ev tr tr))
+ (setq ev tr ev2 nil)))
+ (insert (if (= (char-before) ?\s) "" " ") ev " ")
+ (if ev2
+ (insert (key-description (list ev2)) " "))))
+
+(defun widget-key-sequence-validate (widget)
+ (unless (or (stringp (widget-value widget))
+ (vectorp (widget-value widget)))
+ (widget-put widget :error (format "Invalid key sequence: %S"
+ (widget-value widget)))
+ widget))
+
+(defun widget-key-sequence-value-to-internal (widget value)
+ (if (widget-apply widget :match value)
+ (if (equal value widget-key-sequence-default-value)
+ ""
+ (key-description value))
+ value))
+
+(defun widget-key-sequence-value-to-external (widget value)
+ (if (stringp value)
+ (if (string-match "\\`[[:space:]]*\\'" value)
+ widget-key-sequence-default-value
+ (read-kbd-macro value))
+ value))
+
+\f
(define-widget 'sexp 'editable-field
"An arbitrary Lisp expression."
:tag "Lisp expression"
(setq err "Empty sexp -- use `nil'?")
(unless (widget-apply widget :match (read (current-buffer)))
(setq err (widget-get widget :type-error))))
+ ;; Allow whitespace after expression.
+ (skip-syntax-forward "\\s-")
(if (and (not (eobp))
(not err))
(setq err (format "Junk at end of expression: %s"
nil, or a cons-cell containing a sexp and my-lisp. This will not work
because the `choice' widget does not allow recursion.
-Using the `lazy' widget you can overcome this problem, as in this
-example:
+Using the `lazy' widget you can overcome this problem, as in this
+example:
(define-widget 'sexp-list 'lazy
\"A list of sexps.\"
:format "%{%t%}: %v"
;; We don't convert :type because we want to allow recursive
;; datastructures. This is slow, so we should not create speed
- ;; critical widgets by deriving from this.
+ ;; critical widgets by deriving from this.
:convert-widget 'widget-value-convert-widget
:value-create 'widget-type-value-create
:value-get 'widget-child-value-get
;; Fixme: match
(define-widget 'color 'editable-field
"Choose a color name (with sample)."
- :format "%t: %v (%{sample%})\n"
+ :format "%{%t%}: %v (%{sample%})\n"
:size 10
:tag "Color"
:value "black"
(t
(message "Making completion list...")
(with-output-to-temp-buffer "*Completions*"
- (display-completion-list (all-completions prefix list nil)))
+ (display-completion-list (all-completions prefix list nil)
+ prefix))
(message "Making completion list...done")))))
(defun widget-color-sample-face-get (widget)
(widget-apply widget :notify widget event))))
(defun widget-color-notify (widget child &optional event)
- "Update the sample, and notofy the parent."
+ "Update the sample, and notify the parent."
(overlay-put (widget-get widget :sample-overlay)
'face (widget-apply widget :sample-face-get))
(widget-default-notify widget child event))