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