]> code.delx.au - gnu-emacs/blob - lisp/button.el
Merge from trunk + rename the event. Not tested yet.
[gnu-emacs] / lisp / button.el
1 ;;; button.el --- clickable buttons
2 ;;
3 ;; Copyright (C) 2001-2012 Free Software Foundation, Inc.
4 ;;
5 ;; Author: Miles Bader <miles@gnu.org>
6 ;; Keywords: extensions
7 ;; Package: emacs
8 ;;
9 ;; This file is part of GNU Emacs.
10 ;;
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25 ;;
26 ;; This package defines functions for inserting and manipulating
27 ;; clickable buttons in Emacs buffers, such as might be used for help
28 ;; hyperlinks, etc.
29 ;;
30 ;; In some ways it duplicates functionality also offered by the
31 ;; `widget' package, but the button package has the advantage that it
32 ;; is (1) much faster, (2) much smaller, and (3) much, much, simpler
33 ;; (the code, that is, not the interface).
34 ;;
35 ;; Buttons can either use overlays, in which case the button is
36 ;; represented by the overlay itself, or text-properties, in which case
37 ;; the button is represented by a marker or buffer-position pointing
38 ;; somewhere in the button. In the latter case, no markers into the
39 ;; buffer are retained, which is important for speed if there are are
40 ;; extremely large numbers of buttons. Note however that if there is
41 ;; an existing face text-property at the site of the button, the
42 ;; button face may not be visible. Using overlays avoids this.
43 ;;
44 ;; Using `define-button-type' to define default properties for buttons
45 ;; is not necessary, but it is encouraged, since doing so makes the
46 ;; resulting code clearer and more efficient.
47 ;;
48
49 ;;; Code:
50
51 \f
52 ;; Globals
53
54 ;; Use color for the MS-DOS port because it doesn't support underline.
55 ;; FIXME if MS-DOS correctly answers the (supports) question, it need
56 ;; no longer be a special case.
57 (defface button '((t :inherit link))
58 "Default face used for buttons."
59 :group 'basic-faces)
60
61 (defvar button-map
62 (let ((map (make-sparse-keymap)))
63 ;; The following definition needs to avoid using escape sequences that
64 ;; might get converted to ^M when building loaddefs.el
65 (define-key map [(control ?m)] 'push-button)
66 (define-key map [mouse-2] 'push-button)
67 ;; FIXME: You'd think that for keymaps coming from text-properties on the
68 ;; mode-line or header-line, the `mode-line' or `header-line' prefix
69 ;; shouldn't be necessary!
70 (define-key map [mode-line mouse-2] 'push-button)
71 (define-key map [header-line mouse-2] 'push-button)
72 map)
73 "Keymap used by buttons.")
74
75 (defvar button-buffer-map
76 (let ((map (make-sparse-keymap)))
77 (define-key map [?\t] 'forward-button)
78 (define-key map "\e\t" 'backward-button)
79 (define-key map [backtab] 'backward-button)
80 map)
81 "Keymap useful for buffers containing buttons.
82 Mode-specific keymaps may want to use this as their parent keymap.")
83
84 ;; Default properties for buttons
85 (put 'default-button 'face 'button)
86 (put 'default-button 'mouse-face 'highlight)
87 (put 'default-button 'keymap button-map)
88 (put 'default-button 'type 'button)
89 ;; action may be either a function to call, or a marker to go to
90 (put 'default-button 'action 'ignore)
91 (put 'default-button 'help-echo (purecopy "mouse-2, RET: Push this button"))
92 ;; Make overlay buttons go away if their underlying text is deleted.
93 (put 'default-button 'evaporate t)
94 ;; Prevent insertions adjacent to the text-property buttons from
95 ;; inheriting its properties.
96 (put 'default-button 'rear-nonsticky t)
97
98 ;; A `category-symbol' property for the default button type
99 (put 'button 'button-category-symbol 'default-button)
100
101 \f
102 ;; Button types (which can be used to hold default properties for buttons)
103
104 ;; Because button-type properties are inherited by buttons using the
105 ;; special `category' property (implemented by both overlays and
106 ;; text-properties), we need to store them on a symbol to which the
107 ;; `category' properties can point. Instead of using the symbol that's
108 ;; the name of each button-type, however, we use a separate symbol (with
109 ;; `-button' appended, and uninterned) to store the properties. This is
110 ;; to avoid name clashes.
111
112 ;; [this is an internal function]
113 (defsubst button-category-symbol (type)
114 "Return the symbol used by button-type TYPE to store properties.
115 Buttons inherit them by setting their `category' property to that symbol."
116 (or (get type 'button-category-symbol)
117 (error "Unknown button type `%s'" type)))
118
119 (defun define-button-type (name &rest properties)
120 "Define a `button type' called NAME (a symbol).
121 The remaining arguments form a sequence of PROPERTY VALUE pairs,
122 specifying properties to use as defaults for buttons with this type
123 \(a button's type may be set by giving it a `type' property when
124 creating the button, using the :type keyword argument).
125
126 In addition, the keyword argument :supertype may be used to specify a
127 button-type from which NAME inherits its default property values
128 \(however, the inheritance happens only when NAME is defined; subsequent
129 changes to a supertype are not reflected in its subtypes)."
130 (let ((catsym (make-symbol (concat (symbol-name name) "-button")))
131 (super-catsym
132 (button-category-symbol
133 (or (plist-get properties 'supertype)
134 (plist-get properties :supertype)
135 'button))))
136 ;; Provide a link so that it's easy to find the real symbol.
137 (put name 'button-category-symbol catsym)
138 ;; Initialize NAME's properties using the global defaults.
139 (let ((default-props (symbol-plist super-catsym)))
140 (while default-props
141 (put catsym (pop default-props) (pop default-props))))
142 ;; Add NAME as the `type' property, which will then be returned as
143 ;; the type property of individual buttons.
144 (put catsym 'type name)
145 ;; Add the properties in PROPERTIES to the real symbol.
146 (while properties
147 (let ((prop (pop properties)))
148 (when (eq prop :supertype)
149 (setq prop 'supertype))
150 (put catsym prop (pop properties))))
151 ;; Make sure there's a `supertype' property
152 (unless (get catsym 'supertype)
153 (put catsym 'supertype 'button))
154 name))
155
156 (defun button-type-put (type prop val)
157 "Set the button-type TYPE's PROP property to VAL."
158 (put (button-category-symbol type) prop val))
159
160 (defun button-type-get (type prop)
161 "Get the property of button-type TYPE named PROP."
162 (get (button-category-symbol type) prop))
163
164 (defun button-type-subtype-p (type supertype)
165 "Return t if button-type TYPE is a subtype of SUPERTYPE."
166 (or (eq type supertype)
167 (and type
168 (button-type-subtype-p (button-type-get type 'supertype)
169 supertype))))
170
171 \f
172 ;; Button properties and other attributes
173
174 (defun button-start (button)
175 "Return the position at which BUTTON starts."
176 (if (overlayp button)
177 (overlay-start button)
178 ;; Must be a text-property button.
179 (or (previous-single-property-change (1+ button) 'button)
180 (point-min))))
181
182 (defun button-end (button)
183 "Return the position at which BUTTON ends."
184 (if (overlayp button)
185 (overlay-end button)
186 ;; Must be a text-property button.
187 (or (next-single-property-change button 'button)
188 (point-max))))
189
190 (defun button-get (button prop)
191 "Get the property of button BUTTON named PROP."
192 (cond ((overlayp button)
193 (overlay-get button prop))
194 ((button--area-button-p button)
195 (get-text-property 0 prop (button--area-button-string button)))
196 (t ; Must be a text-property button.
197 (get-text-property button prop))))
198
199 (defun button-put (button prop val)
200 "Set BUTTON's PROP property to VAL."
201 ;; Treat some properties specially.
202 (cond ((memq prop '(type :type))
203 ;; We translate a `type' property a `category' property, since
204 ;; that's what's actually used by overlays/text-properties for
205 ;; inheriting properties.
206 (setq prop 'category)
207 (setq val (button-category-symbol val)))
208 ((eq prop 'category)
209 ;; Disallow updating the `category' property directly.
210 (error "Button `category' property may not be set directly")))
211 ;; Add the property.
212 (cond ((overlayp button)
213 (overlay-put button prop val))
214 ((button--area-button-p button)
215 (setq button (button--area-button-string button))
216 (put-text-property 0 (length button) prop val button))
217 (t ; Must be a text-property button.
218 (put-text-property
219 (or (previous-single-property-change (1+ button) 'button)
220 (point-min))
221 (or (next-single-property-change button 'button)
222 (point-max))
223 prop val))))
224
225 (defun button-activate (button &optional use-mouse-action)
226 "Call BUTTON's action property.
227 If USE-MOUSE-ACTION is non-nil, invoke the button's mouse-action
228 instead of its normal action; if the button has no mouse-action,
229 the normal action is used instead.
230
231 The action can either be a marker or a function. If it's a
232 marker then goto it. Otherwise it it is a function then it is
233 called with BUTTON as only argument. BUTTON is either an
234 overlay, a buffer position, or (for buttons in the mode-line or
235 header-line) a string."
236 (let ((action (or (and use-mouse-action (button-get button 'mouse-action))
237 (button-get button 'action))))
238 (if (markerp action)
239 (save-selected-window
240 (select-window (display-buffer (marker-buffer action)))
241 (goto-char action)
242 (recenter 0))
243 (funcall action button))))
244
245 (defun button-label (button)
246 "Return BUTTON's text label."
247 (if (button--area-button-p button)
248 (substring-no-properties (button--area-button-string button))
249 (buffer-substring-no-properties (button-start button)
250 (button-end button))))
251
252 (defsubst button-type (button)
253 "Return BUTTON's button-type."
254 (button-get button 'type))
255
256 (defun button-has-type-p (button type)
257 "Return t if BUTTON has button-type TYPE, or one of TYPE's subtypes."
258 (button-type-subtype-p (button-get button 'type) type))
259
260 (defalias 'button--area-button-p 'stringp
261 "Return non-nil if BUTTON is an area button.
262 Such area buttons are used for buttons in the mode-line and header-line.")
263
264 (defalias 'button--area-button-string 'identity
265 "Return area button BUTTON's button-string.")
266 \f
267 ;; Creating overlay buttons
268
269 (defun make-button (beg end &rest properties)
270 "Make a button from BEG to END in the current buffer.
271 The remaining arguments form a sequence of PROPERTY VALUE pairs,
272 specifying properties to add to the button.
273 In addition, the keyword argument :type may be used to specify a
274 button-type from which to inherit other properties; see
275 `define-button-type'.
276
277 Also see `make-text-button', `insert-button'."
278 (let ((overlay (make-overlay beg end nil t nil)))
279 (while properties
280 (button-put overlay (pop properties) (pop properties)))
281 ;; Put a pointer to the button in the overlay, so it's easy to get
282 ;; when we don't actually have a reference to the overlay.
283 (overlay-put overlay 'button overlay)
284 ;; If the user didn't specify a type, use the default.
285 (unless (overlay-get overlay 'category)
286 (overlay-put overlay 'category 'default-button))
287 ;; OVERLAY is the button, so return it
288 overlay))
289
290 (defun insert-button (label &rest properties)
291 "Insert a button with the label LABEL.
292 The remaining arguments form a sequence of PROPERTY VALUE pairs,
293 specifying properties to add to the button.
294 In addition, the keyword argument :type may be used to specify a
295 button-type from which to inherit other properties; see
296 `define-button-type'.
297
298 Also see `insert-text-button', `make-button'."
299 (apply #'make-button
300 (prog1 (point) (insert label))
301 (point)
302 properties))
303
304 \f
305 ;; Creating text-property buttons
306
307 (defun make-text-button (beg end &rest properties)
308 "Make a button from BEG to END in the current buffer.
309 The remaining arguments form a sequence of PROPERTY VALUE pairs,
310 specifying properties to add to the button.
311 In addition, the keyword argument :type may be used to specify a
312 button-type from which to inherit other properties; see
313 `define-button-type'.
314
315 This function is like `make-button', except that the button is actually
316 part of the text instead of being a property of the buffer. That is,
317 this function uses text properties, the other uses overlays.
318 Creating large numbers of buttons can also be somewhat faster
319 using `make-text-button'. Note, however, that if there is an existing
320 face property at the site of the button, the button face may not be visible.
321 You may want to use `make-button' in that case.
322
323 BEG can also be a string, in which case it is made into a button.
324
325 Also see `insert-text-button'."
326 (let ((object nil)
327 (type-entry
328 (or (plist-member properties 'type)
329 (plist-member properties :type))))
330 (when (stringp beg)
331 (setq object beg beg 0 end (length object)))
332 ;; Disallow setting the `category' property directly.
333 (when (plist-get properties 'category)
334 (error "Button `category' property may not be set directly"))
335 (if (null type-entry)
336 ;; The user didn't specify a `type' property, use the default.
337 (setq properties (cons 'category (cons 'default-button properties)))
338 ;; The user did specify a `type' property. Translate it into a
339 ;; `category' property, which is what's actually used by
340 ;; text-properties for inheritance.
341 (setcar type-entry 'category)
342 (setcar (cdr type-entry)
343 (button-category-symbol (car (cdr type-entry)))))
344 ;; Now add all the text properties at once
345 (add-text-properties beg end
346 ;; Each button should have a non-eq `button'
347 ;; property so that next-single-property-change can
348 ;; detect boundaries reliably.
349 (cons 'button (cons (list t) properties))
350 object)
351 ;; Return something that can be used to get at the button.
352 (or object beg)))
353
354 (defun insert-text-button (label &rest properties)
355 "Insert a button with the label LABEL.
356 The remaining arguments form a sequence of PROPERTY VALUE pairs,
357 specifying properties to add to the button.
358 In addition, the keyword argument :type may be used to specify a
359 button-type from which to inherit other properties; see
360 `define-button-type'.
361
362 This function is like `insert-button', except that the button is
363 actually part of the text instead of being a property of the buffer.
364 Creating large numbers of buttons can also be somewhat faster using
365 `insert-text-button'.
366
367 Also see `make-text-button'."
368 (apply #'make-text-button
369 (prog1 (point) (insert label))
370 (point)
371 properties))
372
373 \f
374 ;; Finding buttons in a buffer
375
376 (defun button-at (pos)
377 "Return the button at position POS in the current buffer, or nil.
378 If the button at POS is a text property button, the return value
379 is a marker pointing to POS."
380 (let ((button (get-char-property pos 'button)))
381 (if (or (overlayp button) (null button))
382 button
383 ;; Must be a text-property button; return a marker pointing to it.
384 (copy-marker pos t))))
385
386 (defun next-button (pos &optional count-current)
387 "Return the next button after position POS in the current buffer.
388 If COUNT-CURRENT is non-nil, count any button at POS in the search,
389 instead of starting at the next button."
390 (unless count-current
391 ;; Search for the next button boundary.
392 (setq pos (next-single-char-property-change pos 'button)))
393 (and (< pos (point-max))
394 (or (button-at pos)
395 ;; We must have originally been on a button, and are now in
396 ;; the inter-button space. Recurse to find a button.
397 (next-button pos))))
398
399 (defun previous-button (pos &optional count-current)
400 "Return the previous button before position POS in the current buffer.
401 If COUNT-CURRENT is non-nil, count any button at POS in the search,
402 instead of starting at the next button."
403 (let ((button (button-at pos)))
404 (if button
405 (if count-current
406 button
407 ;; We started out on a button, so move to its start and look
408 ;; for the previous button boundary.
409 (setq pos (previous-single-char-property-change
410 (button-start button) 'button))
411 (let ((new-button (button-at pos)))
412 (if new-button
413 ;; We are in a button again; this can happen if there
414 ;; are adjacent buttons (or at bob).
415 (unless (= pos (button-start button)) new-button)
416 ;; We are now in the space between buttons.
417 (previous-button pos))))
418 ;; We started out in the space between buttons.
419 (setq pos (previous-single-char-property-change pos 'button))
420 (or (button-at pos)
421 (and (> pos (point-min))
422 (button-at (1- pos)))))))
423
424 \f
425 ;; User commands
426
427 (defun push-button (&optional pos use-mouse-action)
428 "Perform the action specified by a button at location POS.
429 POS may be either a buffer position or a mouse-event. If
430 USE-MOUSE-ACTION is non-nil, invoke the button's mouse-action
431 instead of its normal action; if the button has no mouse-action,
432 the normal action is used instead. The action may be either a
433 function to call or a marker to display and is invoked using
434 `button-activate' (which see).
435
436 POS defaults to point, except when `push-button' is invoked
437 interactively as the result of a mouse-event, in which case, the
438 mouse event is used.
439 If there's no button at POS, do nothing and return nil, otherwise
440 return t."
441 (interactive
442 (list (if (integerp last-command-event) (point) last-command-event)))
443 (if (and (not (integerp pos)) (eventp pos))
444 ;; POS is a mouse event; switch to the proper window/buffer
445 (let ((posn (event-start pos)))
446 (with-current-buffer (window-buffer (posn-window posn))
447 (if (posn-area posn)
448 ;; mode-line or header-line event
449 (button-activate (car (posn-string posn)) t)
450 (push-button (posn-point posn)) t)))
451 ;; POS is just normal position
452 (let ((button (button-at (or pos (point)))))
453 (when button
454 (button-activate button use-mouse-action)
455 t))))
456
457 (defun forward-button (n &optional wrap display-message)
458 "Move to the Nth next button, or Nth previous button if N is negative.
459 If N is 0, move to the start of any button at point.
460 If WRAP is non-nil, moving past either end of the buffer continues from the
461 other end.
462 If DISPLAY-MESSAGE is non-nil, the button's help-echo string is displayed.
463 Any button with a non-nil `skip' property is skipped over.
464 Returns the button found."
465 (interactive "p\nd\nd")
466 (let (button)
467 (if (zerop n)
468 ;; Move to start of current button
469 (if (setq button (button-at (point)))
470 (goto-char (button-start button)))
471 ;; Move to Nth next button
472 (let ((iterator (if (> n 0) #'next-button #'previous-button))
473 (wrap-start (if (> n 0) (point-min) (point-max)))
474 opoint fail)
475 (setq n (abs n))
476 (setq button t) ; just to start the loop
477 (while (and (null fail) (> n 0) button)
478 (setq button (funcall iterator (point)))
479 (when (and (not button) wrap)
480 (setq button (funcall iterator wrap-start t)))
481 (when button
482 (goto-char (button-start button))
483 ;; Avoid looping forever (e.g., if all the buttons have
484 ;; the `skip' property).
485 (cond ((null opoint)
486 (setq opoint (point)))
487 ((= opoint (point))
488 (setq fail t)))
489 (unless (button-get button 'skip)
490 (setq n (1- n)))))))
491 (if (null button)
492 (error (if wrap "No buttons!" "No more buttons"))
493 (let ((msg (and display-message (button-get button 'help-echo))))
494 (when msg
495 (message "%s" msg)))
496 button)))
497
498 (defun backward-button (n &optional wrap display-message)
499 "Move to the Nth previous button, or Nth next button if N is negative.
500 If N is 0, move to the start of any button at point.
501 If WRAP is non-nil, moving past either end of the buffer continues from the
502 other end.
503 If DISPLAY-MESSAGE is non-nil, the button's help-echo string is displayed.
504 Any button with a non-nil `skip' property is skipped over.
505 Returns the button found."
506 (interactive "p\nd\nd")
507 (forward-button (- n) wrap display-message))
508
509
510 (provide 'button)
511
512 ;;; button.el ends here