(Expanding Abbrevs): Fix previous fix.
[emacs.git] / lisp / button.el
blobdcd26846d10371f4c65d8574ae595a4835c60177
1 ;;; button.el --- clickable buttons
2 ;;
3 ;; Copyright (C) 2001 Free Software Foundation, Inc.
4 ;;
5 ;; Author: Miles Bader <miles@gnu.org>
6 ;; Keywords: extensions
7 ;;
8 ;; This file is part of GNU Emacs.
9 ;;
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
25 ;;; Commentary:
27 ;; This package defines functions for inserting and manipulating
28 ;; clickable buttons in Emacs buffers, such as might be used for help
29 ;; hyperlinks, etc.
31 ;; In some ways it duplicates functionality also offered by the
32 ;; `widget' package, but the button package has the advantage that it
33 ;; is (1) much faster, (2) much smaller, and (3) much, much, simpler
34 ;; (the code, that is, not the interface).
36 ;; Buttons can either use overlays, in which case the button is
37 ;; represented by the overlay itself, or text-properties, in which case
38 ;; the button is represented by a marker or buffer-position pointing
39 ;; somewhere in the button. In the latter case, no markers into the
40 ;; buffer are retained, which is important for speed if there are are
41 ;; extremely large numbers of buttons.
43 ;; Using `define-button-type' to define default properties for buttons
44 ;; is not necessary, but it is is encouraged, since doing so makes the
45 ;; resulting code clearer and more efficient.
48 ;;; Code:
51 ;; Globals
53 (defface button '((((type pc) (class color))
54 (:foreground "lightblue"))
55 (t :underline t))
56 "Default face used for buttons."
57 :group 'faces)
59 ;;;###autoload
60 (defvar button-map
61 (let ((map (make-sparse-keymap)))
62 (define-key map "\r" 'push-button)
63 (define-key map [mouse-2] 'push-button)
64 map)
65 "Keymap used by buttons.")
67 ;;;###autoload
68 (defvar button-buffer-map
69 (let ((map (make-sparse-keymap)))
70 (define-key map [?\t] 'forward-button)
71 (define-key map [backtab] 'backward-button)
72 map)
73 "Keymap useful for buffers containing buttons.
74 Mode-specific keymaps may want to use this as their parent keymap.")
76 ;; Default properties for buttons
77 (put 'default-button 'face 'button)
78 (put 'default-button 'mouse-face 'highlight)
79 (put 'default-button 'keymap button-map)
80 (put 'default-button 'type 'button)
81 ;; action may be either a function to call, or a marker to go to
82 (put 'default-button 'action 'ignore)
83 (put 'default-button 'help-echo "mouse-2, RET: Push this button")
84 ;; Make overlay buttons go away if their underlying text is deleted.
85 (put 'default-button 'evaporate t)
86 ;; Prevent insertions adjacent to the text-property buttons from
87 ;; inheriting its properties.
88 (put 'default-button 'rear-nonsticky t)
89 ;; Text property buttons don't have a `button' property of their own, so
90 ;; they inherit this.
91 (put 'default-button 'button t)
93 ;; A `category-symbol' property for the default button type
94 (put 'button 'button-category-symbol 'default-button)
97 ;; Button types (which can be used to hold default properties for buttons)
99 ;; Because button-type properties are inherited by buttons using the
100 ;; special `category' property (implemented by both overlays and
101 ;; text-properties), we need to store them on a symbol to which the
102 ;; `category' properties can point. Instead of using the symbol that's
103 ;; the name of each button-type, however, we use a separate symbol (with
104 ;; `-button' appended, and uninterned) to store the properties. This is
105 ;; to avoid name clashes.
107 ;; [this is an internal function]
108 (defsubst button-category-symbol (type)
109 "Return the symbol used by button-type TYPE to store properties.
110 Buttons inherit them by setting their `category' property to that symbol."
111 (or (get type 'button-category-symbol)
112 (error "Unknown button type `%s'" type)))
114 ;;;###autoload
115 (defun define-button-type (name &rest properties)
116 "Define a `button type' called NAME.
117 The remaining arguments form a sequence of PROPERTY VALUE pairs,
118 specifying properties to use as defaults for buttons with this type
119 \(a button's type may be set by giving it a `type' property when
120 creating the button, using the :type keyword argument).
122 In addition, the keyword argument :supertype may be used to specify a
123 button-type from which NAME inherits its default property values
124 \(however, the inheritance happens only when NAME is defined; subsequent
125 changes to a supertype are not reflected in its subtypes)."
126 (let ((catsym (make-symbol (concat (symbol-name name) "-button")))
127 (super-catsym
128 (button-category-symbol
129 (or (plist-get properties 'supertype)
130 (plist-get properties :supertype)
131 'button))))
132 ;; Provide a link so that it's easy to find the real symbol.
133 (put name 'button-category-symbol catsym)
134 ;; Initialize NAME's properties using the global defaults.
135 (let ((default-props (symbol-plist super-catsym)))
136 (while default-props
137 (put catsym (pop default-props) (pop default-props))))
138 ;; Add NAME as the `type' property, which will then be returned as
139 ;; the type property of individual buttons.
140 (put catsym 'type name)
141 ;; Add the properties in PROPERTIES to the real symbol.
142 (while properties
143 (let ((prop (pop properties)))
144 (when (eq prop :supertype)
145 (setq prop 'supertype))
146 (put catsym prop (pop properties))))
147 ;; Make sure there's a `supertype' property
148 (unless (get catsym 'supertype)
149 (put catsym 'supertype 'button))
150 name))
152 (defun button-type-put (type prop val)
153 "Set the button-type TYPE's PROP property to VAL."
154 (put (button-category-symbol type) prop val))
156 (defun button-type-get (type prop)
157 "Get the property of button-type TYPE named PROP."
158 (get (button-category-symbol type) prop))
160 (defun button-type-subtype-p (type supertype)
161 "Return t if button-type TYPE is a subtype of SUPERTYPE."
162 (or (eq type supertype)
163 (and type
164 (button-type-subtype-p (button-type-get type 'supertype)
165 supertype))))
168 ;; Button properties and other attributes
170 (defun button-start (button)
171 "Return the position at which BUTTON starts."
172 (if (overlayp button)
173 (overlay-start button)
174 ;; Must be a text-property button.
175 (or (previous-single-property-change (1+ button) 'button)
176 (point-min))))
178 (defun button-end (button)
179 "Return the position at which BUTTON ends."
180 (if (overlayp button)
181 (overlay-end button)
182 ;; Must be a text-property button.
183 (or (next-single-property-change button 'button)
184 (point-max))))
186 (defun button-get (button prop)
187 "Get the property of button BUTTON named PROP."
188 (if (overlayp button)
189 (overlay-get button prop)
190 ;; Must be a text-property button.
191 (get-text-property button prop)))
193 (defun button-put (button prop val)
194 "Set BUTTON's PROP property to VAL."
195 ;; Treat some properties specially.
196 (cond ((memq prop '(type :type))
197 ;; We translate a `type' property a `category' property, since
198 ;; that's what's actually used by overlays/text-properties for
199 ;; inheriting properties.
200 (setq prop 'category)
201 (setq val (button-category-symbol val)))
202 ((eq prop 'category)
203 ;; Disallow updating the `category' property directly.
204 (error "Button `category' property may not be set directly")))
205 ;; Add the property.
206 (if (overlayp button)
207 (overlay-put button prop val)
208 ;; Must be a text-property button.
209 (put-text-property
210 (or (previous-single-property-change (1+ button) 'button)
211 (point-min))
212 (or (next-single-property-change button 'button)
213 (point-max))
214 prop val)))
216 (defsubst button-activate (button &optional use-mouse-action)
217 "Call BUTTON's action property.
218 If USE-MOUSE-ACTION is non-nil, invoke the button's mouse-action
219 instead of its normal action; if the button has no mouse-action,
220 the normal action is used instead."
221 (let ((action (or (and use-mouse-action (button-get button 'mouse-action))
222 (button-get button 'action))))
223 (if (markerp action)
224 (save-selected-window
225 (select-window (display-buffer (marker-buffer action)))
226 (goto-char action)
227 (recenter 0))
228 (funcall action button))))
230 (defun button-label (button)
231 "Return BUTTON's text label."
232 (buffer-substring-no-properties (button-start button) (button-end button)))
234 (defsubst button-type (button)
235 "Return BUTTON's button-type."
236 (button-get button 'type))
238 (defun button-has-type-p (button type)
239 "Return t if BUTTON has button-type TYPE, or one of TYPE's subtypes."
240 (button-type-subtype-p (button-get button 'type) type))
243 ;; Creating overlay buttons
245 ;;;###autoload
246 (defun make-button (beg end &rest properties)
247 "Make a button from BEG to END in the current buffer.
248 The remaining arguments form a sequence of PROPERTY VALUE pairs,
249 specifying properties to add to the button.
250 In addition, the keyword argument :type may be used to specify a
251 button-type from which to inherit other properties; see
252 `define-button-type'.
254 Also see `make-text-button', `insert-button'."
255 (let ((overlay (make-overlay beg end nil t nil)))
256 (while properties
257 (button-put overlay (pop properties) (pop properties)))
258 ;; Put a pointer to the button in the overlay, so it's easy to get
259 ;; when we don't actually have a reference to the overlay.
260 (overlay-put overlay 'button overlay)
261 ;; If the user didn't specify a type, use the default.
262 (unless (overlay-get overlay 'category)
263 (overlay-put overlay 'category 'default-button))
264 ;; OVERLAY is the button, so return it
265 overlay))
267 ;;;###autoload
268 (defun insert-button (label &rest properties)
269 "Insert a button with the label LABEL.
270 The remaining arguments form a sequence of PROPERTY VALUE pairs,
271 specifying properties to add to the button.
272 In addition, the keyword argument :type may be used to specify a
273 button-type from which to inherit other properties; see
274 `define-button-type'.
276 Also see `insert-text-button', `make-button'."
277 (apply #'make-button
278 (prog1 (point) (insert label))
279 (point)
280 properties))
283 ;; Creating text-property buttons
285 ;;;###autoload
286 (defun make-text-button (beg end &rest properties)
287 "Make a button from BEG to END in the current buffer.
288 The remaining arguments form a sequence of PROPERTY VALUE pairs,
289 specifying properties to add to the button.
290 In addition, the keyword argument :type may be used to specify a
291 button-type from which to inherit other properties; see
292 `define-button-type'.
294 This function is like `make-button', except that the button is actually
295 part of the text instead of being a property of the buffer. Creating
296 large numbers of buttons can also be somewhat faster using
297 `make-text-button'.
299 Also see `insert-text-button'."
300 (let (prop val)
301 (while properties
302 (setq prop (pop properties))
303 (setq val (pop properties))
304 ;; Note that all the following code is basically equivalent to
305 ;; `button-put', but we can do it much more efficiently since we
306 ;; already have BEG and END.
307 (cond ((memq prop '(type :type))
308 ;; We translate a `type' property into a `category'
309 ;; property, since that's what's actually used by
310 ;; text-properties for inheritance.
311 (setq prop 'category)
312 (setq val (button-category-symbol val)))
313 ((eq prop 'category)
314 ;; Disallow setting the `category' property directly.
315 (error "Button `category' property may not be set directly")))
316 ;; Add the property.
317 (put-text-property beg end prop val)))
318 ;; Return something that can be used to get at the button.
319 beg)
321 ;;;###autoload
322 (defun insert-text-button (label &rest properties)
323 "Insert a button with the label LABEL.
324 The remaining arguments form a sequence of PROPERTY VALUE pairs,
325 specifying properties to add to the button.
326 In addition, the keyword argument :type may be used to specify a
327 button-type from which to inherit other properties; see
328 `define-button-type'.
330 This function is like `insert-button', except that the button is
331 actually part of the text instead of being a property of the buffer.
332 Creating large numbers of buttons can also be somewhat faster using
333 `insert-text-button'.
335 Also see `make-text-button'."
336 (apply #'make-text-button
337 (prog1 (point) (insert label))
338 (point)
339 properties))
342 ;; Finding buttons in a buffer
344 (defun button-at (pos)
345 "Return the button at position POS in the current buffer, or nil."
346 (let ((button (get-char-property pos 'button)))
347 (if (or (overlayp button) (null button))
348 button
349 ;; Must be a text-property button; return a marker pointing to it.
350 (copy-marker pos t))))
352 (defun next-button (pos &optional count-current)
353 "Return the next button after position POS in the current buffer.
354 If COUNT-CURRENT is non-nil, count any button at POS in the search,
355 instead of starting at the next button."
356 (unless count-current
357 ;; Search for the next button boundary.
358 (setq pos (next-single-char-property-change pos 'button)))
359 (and (< pos (point-max))
360 (or (button-at pos)
361 ;; We must have originally been on a button, and are now in
362 ;; the inter-button space. Recurse to find a button.
363 (next-button pos))))
365 (defun previous-button (pos &optional count-current)
366 "Return the Nth button before position POS in the current buffer.
367 If COUNT-CURRENT is non-nil, count any button at POS in the search,
368 instead of starting at the next button."
369 (unless count-current
370 (setq pos (previous-single-char-property-change pos 'button)))
371 (and (> pos (point-min))
372 (or (button-at (1- pos))
373 ;; We must have originally been on a button, and are now in
374 ;; the inter-button space. Recurse to find a button.
375 (previous-button pos))))
378 ;; User commands
380 (defun push-button (&optional pos use-mouse-action)
381 "Perform the action specified by a button at location POS.
382 POS may be either a buffer position or a mouse-event. If
383 USE-MOUSE-ACTION is non-nil, invoke the button's mouse-action
384 instead of its normal action; if the button has no mouse-action,
385 the normal action is used instead. The action may be either a
386 function to call or a marker to display.
387 POS defaults to point, except when `push-button' is invoked
388 interactively as the result of a mouse-event, in which case, the
389 mouse event is used.
390 If there's no button at POS, do nothing and return nil, otherwise
391 return t."
392 (interactive
393 (list (if (integerp last-command-event) (point) last-command-event)))
394 (if (and (not (integerp pos)) (eventp pos))
395 ;; POS is a mouse event; switch to the proper window/buffer
396 (let ((posn (event-start pos)))
397 (with-current-buffer (window-buffer (posn-window posn))
398 (push-button (posn-point posn) t)))
399 ;; POS is just normal position
400 (let ((button (button-at (or pos (point)))))
401 (if (not button)
403 (button-activate button use-mouse-action)
404 t))))
406 (defun forward-button (n &optional wrap display-message)
407 "Move to the Nth next button, or Nth previous button if N is negative.
408 If N is 0, move to the start of any button at point.
409 If WRAP is non-nil, moving past either end of the buffer continues from the
410 other end.
411 If DISPLAY-MESSAGE is non-nil, the button's help-echo string is displayed.
412 Any button with a non-nil `skip' property is skipped over.
413 Returns the button found."
414 (interactive "p\nd\nd")
415 (let (button)
416 (if (zerop n)
417 ;; Move to start of current button
418 (if (setq button (button-at (point)))
419 (goto-char (button-start button)))
420 ;; Move to Nth next button
421 (let ((iterator (if (> n 0) #'next-button #'previous-button))
422 (wrap-start (if (> n 0) (point-min) (point-max))))
423 (setq n (abs n))
424 (setq button t) ; just to start the loop
425 (while (and (> n 0) button)
426 (setq button (funcall iterator (point)))
427 (when (and (not button) wrap)
428 (setq button (funcall iterator wrap-start t)))
429 (when button
430 (goto-char (button-start button))
431 (unless (button-get button 'skip)
432 (setq n (1- n)))))))
433 (if (null button)
434 (error (if wrap "No buttons!" "No more buttons"))
435 (let ((msg (and display-message (button-get button 'help-echo))))
436 (when msg
437 (message "%s" msg)))
438 button)))
440 (defun backward-button (n &optional wrap display-message)
441 "Move to the Nth previous button, or Nth next button if N is negative.
442 If N is 0, move to the start of any button at point.
443 If WRAP is non-nil, moving past either end of the buffer continues from the
444 other end.
445 If DISPLAY-MESSAGE is non-nil, the button's help-echo string is displayed.
446 Any button with a non-nil `skip' property is skipped over.
447 Returns the button found."
448 (interactive "p\nd\nd")
449 (forward-button (- n) wrap display-message))
452 (provide 'button)
454 ;;; arch-tag: 5f2c7627-413b-4097-b282-630f89d9c5e9
455 ;;; button.el ends here