*** empty log message ***
[bpt/emacs.git] / lisp / tooltip.el
CommitLineData
b670783a 1;;; tooltip.el --- show tooltip windows
7840ced1 2
8bc973e9 3;; Copyright (C) 1997, 1999, 2000, 2001, 2002, 2003, 2004, 2005
f2a1c81b 4;; Free Software Foundation, Inc.
7840ced1
GM
5
6;; Author: Gerd Moellmann <gerd@acm.org>
7;; Keywords: help c mouse tools
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 2, or (at your option)
14;; 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; see the file COPYING. If not, write to the
23;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
24;; Boston, MA 02111-1307, USA.
25
26;;; Commentary:
27
7840ced1
GM
28;;; Code:
29
60516f7b 30(eval-when-compile (require 'cl)) ; for case macro
18385083 31
7840ced1
GM
32\f
33;;; Customizable settings
34
35(defgroup tooltip nil
36 "Customization group for the `tooltip' package."
37 :group 'help
3e6ed970 38 :group 'gud
7840ced1
GM
39 :group 'mouse
40 :group 'tools
01b23b99 41 :version "21.1"
7840ced1
GM
42 :tag "Tool Tips")
43
88751b11 44(defcustom tooltip-delay 0.7
7840ced1
GM
45 "Seconds to wait before displaying a tooltip the first time."
46 :tag "Delay"
47 :type 'number
48 :group 'tooltip)
49
7840ced1
GM
50(defcustom tooltip-short-delay 0.1
51 "Seconds to wait between subsequent tooltips on different items."
52 :tag "Short delay"
53 :type 'number
54 :group 'tooltip)
55
7840ced1 56(defcustom tooltip-recent-seconds 1
01b23b99
DL
57 "Display tooltips if changing tip items within this many seconds.
58Do so after `tooltip-short-delay'."
7840ced1
GM
59 :tag "Recent seconds"
60 :type 'number
61 :group 'tooltip)
62
88751b11 63(defcustom tooltip-hide-delay 10
a93c8ba8
GM
64 "Hide tooltips automatically after this many seconds."
65 :tag "Hide delay"
66 :type 'number
67 :group 'tooltip)
68
e4df9b40 69(defcustom tooltip-x-offset nil
37a29d46 70 "X offset, in pixels, for the display of tooltips.
e4df9b40
GM
71The offset is relative to the position of the mouse. It must
72be chosen so that the tooltip window doesn't contain the mouse
7142670a
EZ
73when it pops up. If the value is nil, the default offset is 5
74pixels.
75
76If `tooltip-frame-parameters' includes the `left' parameter,
77the value of `tooltip-x-offset' is ignored."
e4df9b40
GM
78 :tag "X offset"
79 :type '(choice (const :tag "Default" nil)
80 (integer :tag "Offset" :value 1))
81 :group 'tooltip)
82
e4df9b40 83(defcustom tooltip-y-offset nil
37a29d46 84 "Y offset, in pixels, for the display of tooltips.
e4df9b40
GM
85The offset is relative to the position of the mouse. It must
86be chosen so that the tooltip window doesn't contain the mouse
7142670a
EZ
87when it pops up. If the value is nil, the default offset is -10
88pixels.
89
90If `tooltip-frame-parameters' includes the `top' parameter,
91the value of `tooltip-y-offset' is ignored."
e4df9b40
GM
92 :tag "Y offset"
93 :type '(choice (const :tag "Default" nil)
94 (integer :tag "Offset" :value 1))
95 :group 'tooltip)
96
7840ced1
GM
97(defcustom tooltip-frame-parameters
98 '((name . "tooltip")
7840ced1 99 (internal-border-width . 5)
7840ced1 100 (border-width . 1))
7142670a
EZ
101 "Frame parameters used for tooltips.
102
103If `left' or `top' parameters are included, they specify the absolute
104position to pop up the tooltip."
7840ced1
GM
105 :type 'sexp
106 :tag "Frame Parameters"
107 :group 'tooltip)
108
2621f5a9
GM
109(defface tooltip
110 '((((class color))
39440204
JPW
111 :background "lightyellow"
112 :foreground "black"
113 :inherit variable-pitch)
114 (t
115 :inherit variable-pitch))
2621f5a9
GM
116 "Face for tooltips."
117 :group 'tooltip)
118
7840ced1 119(defcustom tooltip-gud-tips-p nil
321ce330 120 "*Non-nil means show tooltips in GUD sessions."
7840ced1
GM
121 :type 'boolean
122 :tag "GUD"
123 :group 'tooltip)
124
8bc973e9 125(defcustom tooltip-gud-modes '(gud-mode c-mode c++-mode fortran-mode)
7840ced1
GM
126 "List of modes for which to enable GUD tips."
127 :type 'sexp
128 :tag "GUD modes"
129 :group 'tooltip)
130
7840ced1
GM
131(defcustom tooltip-gud-display
132 '((eq (tooltip-event-buffer tooltip-gud-event)
30044d57 133 (marker-buffer gud-overlay-arrow-position)))
7840ced1
GM
134 "List of forms determining where GUD tooltips are displayed.
135
136Forms in the list are combined with AND. The default is to display
137only tooltips in the buffer containing the overlay arrow."
138 :type 'sexp
139 :tag "GUD buffers predicate"
140 :group 'tooltip)
141
72200f89 142(defcustom tooltip-use-echo-area nil
8be44f9d
DL
143 "Use the echo area instead of tooltip frames.
144This is only relevant GUD display, since otherwise it is equivalent to
145turning off Tooltip mode."
72200f89 146 :type 'boolean
e775fcc4 147 :tag "Use echo area"
72200f89
SS
148 :group 'tooltip)
149
7840ced1
GM
150\f
151;;; Variables that are not customizable.
152
153(defvar tooltip-hook nil
154 "Functions to call to display tooltips.
155Each function is called with one argument EVENT which is a copy of
156the last mouse movement event that occurred.")
157
7840ced1
GM
158(defvar tooltip-timeout-id nil
159 "The id of the timeout started when Emacs becomes idle.")
160
7840ced1
GM
161(defvar tooltip-last-mouse-motion-event nil
162 "A copy of the last mouse motion event seen.")
163
7840ced1
GM
164(defvar tooltip-hide-time nil
165 "Time when the last tooltip was hidden.")
166
7840ced1
GM
167\f
168;;; Event accessors
169
170(defun tooltip-event-buffer (event)
171 "Return the buffer over which event EVENT occurred.
172This might return nil if the event did not occur over a buffer."
173 (let ((window (posn-window (event-end event))))
174 (and window (window-buffer window))))
175
7840ced1
GM
176\f
177;;; Switching tooltips on/off
178
179;; We don't set track-mouse globally because this is a big redisplay
180;; problem in buffers having a pre-command-hook or such installed,
181;; which does a set-buffer, like the summary buffer of Gnus. Calling
182;; set-buffer prevents redisplay optimizations, so every mouse motion
183;; would be accompanied by a full redisplay.
184
185;;;###autoload
8bc973e9
NR
186(define-minor-mode tooltip-mode
187 "Toggle Tooltip display.
7840ced1 188With ARG, turn tooltip mode on if and only if ARG is positive."
8bc973e9 189 :global t
d5e2843d
LT
190 ;; If you change the :init-value below, you also need to change the
191 ;; corresponding code in startup.el.
192 :init-value (not (or noninteractive
193 emacs-quick-startup
194 (not (display-graphic-p))
195 (not (fboundp 'x-show-tip))))
8bc973e9 196 :group 'tooltip
ef75a647 197 (unless (or (null tooltip-mode) (fboundp 'x-show-tip))
c6da4eb4 198 (error "Sorry, tooltips are not yet available on this system"))
8bc973e9 199 (let ((hook-fn (if tooltip-mode 'add-hook 'remove-hook)))
7840ced1
GM
200 (funcall hook-fn 'change-major-mode-hook 'tooltip-change-major-mode)
201 (tooltip-activate-mouse-motions-if-enabled)
202 (funcall hook-fn 'pre-command-hook 'tooltip-hide)
203 (funcall hook-fn 'tooltip-hook 'tooltip-gud-tips)
204 (funcall hook-fn 'tooltip-hook 'tooltip-help-tips)
8bc973e9 205 (setq show-help-function (if tooltip-mode 'tooltip-show-help-function nil))
7840ced1
GM
206 ;; `ignore' is the default binding for mouse movements.
207 (define-key global-map [mouse-movement]
8bc973e9 208 (if tooltip-mode 'tooltip-mouse-motion 'ignore))))
dea87733 209
7840ced1
GM
210\f
211;;; Timeout for tooltip display
212
7840ced1
GM
213(defun tooltip-delay ()
214 "Return the delay in seconds for the next tooltip."
215 (let ((delay tooltip-delay)
a1f84f6d 216 (now (float-time)))
7840ced1
GM
217 (when (and tooltip-hide-time
218 (< (- now tooltip-hide-time) tooltip-recent-seconds))
219 (setq delay tooltip-short-delay))
220 delay))
221
06f76f9f 222(defun tooltip-cancel-delayed-tip ()
7840ced1
GM
223 "Disable the tooltip timeout."
224 (when tooltip-timeout-id
225 (disable-timeout tooltip-timeout-id)
226 (setq tooltip-timeout-id nil)))
227
06f76f9f 228(defun tooltip-start-delayed-tip ()
7840ced1
GM
229 "Add a one-shot timeout to call function tooltip-timeout."
230 (setq tooltip-timeout-id
231 (add-timeout (tooltip-delay) 'tooltip-timeout nil)))
232
7840ced1
GM
233(defun tooltip-timeout (object)
234 "Function called when timer with id tooltip-timeout-id fires."
235 (run-hook-with-args-until-success 'tooltip-hook
236 tooltip-last-mouse-motion-event))
237
7840ced1
GM
238\f
239;;; Reacting on mouse movements
240
241(defun tooltip-change-major-mode ()
242 "Function added to `change-major-mode-hook' when tooltip mode is on."
243 (add-hook 'post-command-hook 'tooltip-activate-mouse-motions-if-enabled))
244
7840ced1
GM
245(defun tooltip-activate-mouse-motions-if-enabled ()
246 "Reconsider for all buffers whether mouse motion events are desired."
247 (remove-hook 'post-command-hook 'tooltip-activate-mouse-motions-if-enabled)
8bc973e9 248 (dolist (buffer (buffer-list))
7840ced1 249 (save-excursion
8bc973e9
NR
250 (set-buffer buffer)
251 (if (and tooltip-mode
252 tooltip-gud-tips-p
253 (memq major-mode tooltip-gud-modes))
254 (tooltip-activate-mouse-motions t)
255 (tooltip-activate-mouse-motions nil)))))
7840ced1 256
ba81b532
RS
257(defvar tooltip-mouse-motions-active nil
258 "Locally t in a buffer if tooltip processing of mouse motion is enabled.")
7840ced1
GM
259
260(defun tooltip-activate-mouse-motions (activatep)
261 "Activate/deactivate mouse motion events for the current buffer.
262ACTIVATEP non-nil means activate mouse motion events."
263 (if activatep
264 (progn
ba81b532
RS
265 (make-local-variable 'tooltip-mouse-motions-active)
266 (setq tooltip-mouse-motions-active t)
7840ced1
GM
267 (make-local-variable 'track-mouse)
268 (setq track-mouse t))
ba81b532
RS
269 (when tooltip-mouse-motions-active
270 (kill-local-variable 'tooltip-mouse-motions-active)
271 (kill-local-variable 'track-mouse))))
7840ced1
GM
272
273(defun tooltip-mouse-motion (event)
274 "Command handler for mouse movement events in `global-map'."
275 (interactive "e")
276 (tooltip-hide)
277 (when (car (mouse-pixel-position))
278 (setq tooltip-last-mouse-motion-event (copy-sequence event))
06f76f9f 279 (tooltip-start-delayed-tip)))
7840ced1 280
7840ced1
GM
281\f
282;;; Displaying tips
283
2621f5a9 284(defun tooltip-set-param (alist key value)
4b2bb9be 285 "Change the value of KEY in alist ALIST to VALUE.
f1180544 286If there's no association for KEY in ALIST, add one, otherwise
2621f5a9
GM
287change the existing association. Value is the resulting alist."
288 (let ((param (assq key alist)))
289 (if (consp param)
290 (setcdr param value)
291 (push (cons key value) alist))
292 alist))
293
7840ced1 294(defun tooltip-show (text)
7142670a
EZ
295 "Show a tooltip window displaying TEXT.
296
297Text larger than `x-max-tooltip-size' (which see) is clipped.
298
299If the alist in `tooltip-frame-parameters' includes `left' and `top'
300parameters, they determine the x and y position where the tooltip
301is displayed. Otherwise, the tooltip pops at offsets specified by
302`tooltip-x-offset' and `tooltip-y-offset' from the current mouse
303position."
72200f89
SS
304 (if tooltip-use-echo-area
305 (message "%s" text)
e5603149 306 (condition-case error
2621f5a9
GM
307 (let ((params (copy-sequence tooltip-frame-parameters))
308 (fg (face-attribute 'tooltip :foreground))
309 (bg (face-attribute 'tooltip :background)))
06f76f9f
GM
310 (when (stringp fg)
311 (setq params (tooltip-set-param params 'foreground-color fg))
312 (setq params (tooltip-set-param params 'border-color fg)))
313 (when (stringp bg)
314 (setq params (tooltip-set-param params 'background-color bg)))
2621f5a9
GM
315 (x-show-tip (propertize text 'face 'tooltip)
316 (selected-frame)
f3b05e99 317 params
a93c8ba8 318 tooltip-hide-delay
2621f5a9
GM
319 tooltip-x-offset
320 tooltip-y-offset))
f1180544 321 (error
e5603149
GM
322 (message "Error while displaying tooltip: %s" error)
323 (sit-for 1)
324 (message "%s" text)))))
325
7840ced1
GM
326(defun tooltip-hide (&optional ignored-arg)
327 "Hide a tooltip, if one is displayed.
328Value is non-nil if tooltip was open."
06f76f9f 329 (tooltip-cancel-delayed-tip)
7840ced1 330 (when (x-hide-tip)
a1f84f6d 331 (setq tooltip-hide-time (float-time))))
7840ced1 332
7840ced1
GM
333\f
334;;; Debugger-related functions
335
336(defun tooltip-identifier-from-point (point)
337 "Extract the identifier at POINT, if any.
338Value is nil if no identifier exists at point. Identifier extraction
339is based on the current syntax table."
340 (save-excursion
341 (goto-char point)
342 (let ((start (progn (skip-syntax-backward "w_") (point))))
343 (unless (looking-at "[0-9]")
344 (skip-syntax-forward "w_")
345 (when (> (point) start)
346 (buffer-substring start (point)))))))
347
7840ced1
GM
348(defmacro tooltip-region-active-p ()
349 "Value is non-nil if the region is currently active."
350 (if (string-match "^GNU" (emacs-version))
351 `(and transient-mark-mode mark-active)
352 `(region-active-p)))
353
7840ced1
GM
354(defun tooltip-expr-to-print (event)
355 "Return an expression that should be printed for EVENT.
356If a region is active and the mouse is inside the region, print
357the region. Otherwise, figure out the identifier around the point
358where the mouse is."
359 (save-excursion
360 (set-buffer (tooltip-event-buffer event))
361 (let ((point (posn-point (event-end event))))
362 (if (tooltip-region-active-p)
363 (when (and (<= (region-beginning) point) (<= point (region-end)))
364 (buffer-substring (region-beginning) (region-end)))
365 (tooltip-identifier-from-point point)))))
366
7840ced1
GM
367(defun tooltip-process-prompt-regexp (process)
368 "Return regexp matching the prompt of PROCESS at the end of a string.
369The prompt is taken from the value of COMINT-PROMPT-REGEXP in the buffer
370of PROCESS."
371 (let ((prompt-regexp (save-excursion
372 (set-buffer (process-buffer process))
373 comint-prompt-regexp)))
374 ;; Most start with `^' but the one for `sdb' cannot be easily
375 ;; stripped. Code the prompt for `sdb' fixed here.
376 (if (= (aref prompt-regexp 0) ?^)
377 (setq prompt-regexp (substring prompt-regexp 1))
378 (setq prompt-regexp "\\*"))
379 (concat "\n*" prompt-regexp "$")))
380
7840ced1
GM
381(defun tooltip-strip-prompt (process output)
382 "Return OUTPUT with any prompt of PROCESS stripped from its end."
383 (let ((prompt-regexp (tooltip-process-prompt-regexp process)))
384 (save-match-data
385 (when (string-match prompt-regexp output)
386 (setq output (substring output 0 (match-beginning 0)))))
387 output))
388
7840ced1
GM
389\f
390;;; Tips for `gud'
391
392(defvar tooltip-gud-original-filter nil
393 "Process filter to restore after GUD output has been received.")
394
7840ced1
GM
395(defvar tooltip-gud-dereference nil
396 "Non-nil means print expressions with a `*' in front of them.
397For C this would dereference a pointer expression.")
398
7840ced1
GM
399(defvar tooltip-gud-event nil
400 "The mouse movement event that led to a tooltip display.
401This event can be examined by forms in TOOLTIP-GUD-DISPLAY.")
402
7840ced1 403(defun tooltip-gud-toggle-dereference ()
024e5da6 404 "Toggle whether tooltips should show `* expr' or `expr'."
7840ced1
GM
405 (interactive)
406 (setq tooltip-gud-dereference (not tooltip-gud-dereference))
407 (when (interactive-p)
408 (message "Dereferencing is now %s."
409 (if tooltip-gud-dereference "on" "off"))))
410
f1180544 411; This will only display data that comes in one chunk.
5ce0fb91 412; Larger arrays (say 400 elements) are displayed in
58e10469 413; the tootip incompletely and spill over into the gud buffer.
5ce0fb91 414; Switching the process-filter creates timing problems and
58e10469
NR
415; it may be difficult to do better. gdba in gdb-ui.el
416; gets round this problem.
7840ced1
GM
417(defun tooltip-gud-process-output (process output)
418 "Process debugger output and show it in a tooltip window."
419 (set-process-filter process tooltip-gud-original-filter)
58e10469 420 (tooltip-show (tooltip-strip-prompt process output)))
7840ced1
GM
421
422(defun tooltip-gud-print-command (expr)
423 "Return a suitable command to print the expression EXPR.
424If TOOLTIP-GUD-DEREFERENCE is t, also prepend a `*' to EXPR."
425 (when tooltip-gud-dereference
426 (setq expr (concat "*" expr)))
dea87733
NR
427 (case gud-minor-mode
428 ((gdb gdba) (concat "server print " expr))
9e06a930 429 (dbx (concat "print " expr))
7840ced1
GM
430 (xdb (concat "p " expr))
431 (sdb (concat expr "/"))
432 (perldb expr)))
a1f84f6d 433
7840ced1 434(defun tooltip-gud-tips (event)
01b23b99
DL
435 "Show tip for identifier or selection under the mouse.
436The mouse must either point at an identifier or inside a selected
437region for the tip window to be shown. If tooltip-gud-dereference is t,
438add a `*' in front of the printed expression.
7840ced1
GM
439
440This function must return nil if it doesn't handle EVENT."
8bc973e9 441 (let (process)
7840ced1
GM
442 (when (and (eventp event)
443 tooltip-gud-tips-p
444 (boundp 'gud-comint-buffer)
8bc973e9 445 (setq process (get-buffer-process gud-comint-buffer))
7840ced1
GM
446 (posn-point (event-end event))
447 (progn (setq tooltip-gud-event event)
448 (eval (cons 'and tooltip-gud-display))))
449 (let ((expr (tooltip-expr-to-print event)))
450 (when expr
c39e8983
MB
451 (let ((cmd (tooltip-gud-print-command expr)))
452 (unless (null cmd) ; CMD can be nil if unknown debugger
58e10469 453 (case gud-minor-mode
f1180544 454 (gdba (gdb-enqueue-input
58e10469 455 (list (concat cmd "\n") 'gdb-tooltip-print)))
f1180544 456 (t
58e10469
NR
457 (setq tooltip-gud-original-filter (process-filter process))
458 (set-process-filter process 'tooltip-gud-process-output)
459 (gud-basic-call cmd)))
460 expr)))))))
461
462(defun gdb-tooltip-print ()
e07aa7db 463 (tooltip-show
58e10469 464 (with-current-buffer (gdb-get-buffer 'gdb-partial-output-buffer)
8bc973e9
NR
465 (let ((string (buffer-string)))
466 ;; remove newline for tooltip-use-echo-area
467 (substring string 0 (- (length string) 1))))))
7840ced1
GM
468
469\f
470;;; Tooltip help.
471
472(defvar tooltip-help-message nil
473 "The last help message received via `tooltip-show-help-function'.")
474
7840ced1
GM
475(defun tooltip-show-help-function (msg)
476 "Function installed as `show-help-function'.
477MSG is either a help string to display, or nil to cancel the display."
86f0d417 478 (let ((previous-help tooltip-help-message))
7840ced1
GM
479 (setq tooltip-help-message msg)
480 (cond ((null msg)
06f76f9f
GM
481 ;; Cancel display. This also cancels a delayed tip, if
482 ;; there is one.
7840ced1 483 (tooltip-hide))
06f76f9f
GM
484 ((equal previous-help msg)
485 ;; Same help as before (but possibly the mouse has moved).
486 ;; Keep what we have.
487 )
7840ced1 488 (t
f1180544 489 ;; A different help. Remove a previous tooltip, and
06f76f9f
GM
490 ;; display a new one, with some delay.
491 (tooltip-hide)
492 (tooltip-start-delayed-tip)))))
7840ced1 493
7840ced1
GM
494(defun tooltip-help-tips (event)
495 "Hook function to display a help tooltip.
06f76f9f 496This is installed on the hook `tooltip-hook', which is run when
110c171f 497the timer with ID `tooltip-timeout-id' fires.
7840ced1
GM
498Value is non-nil if this function handled the tip."
499 (when (stringp tooltip-help-message)
500 (tooltip-show tooltip-help-message)
7840ced1
GM
501 t))
502
5ce0fb91 503(provide 'tooltip)
7840ced1 504
18385083 505;; arch-tag: 3d61135e-4618-4a78-af28-183f6df5636f
7840ced1 506;;; tooltip.el ends here