1 ;;; -*- Mode:Emacs-Lisp -*-
2 ;;; This file contains font and menu hacks for BBDB.
4 ;;; This file is the part of the Insidious Big Brother Database (aka BBDB),
5 ;;; copyright (c) 1992, 1993, 1994 Jamie Zawinski <jwz@netscape.com>.
7 ;;; The Insidious Big Brother Database is free software; you can redistribute
8 ;;; it and/or modify it under the terms of the GNU General Public License as
9 ;;; published by the Free Software Foundation; either version 2, or (at your
10 ;;; option) any later version.
12 ;;; BBDB is distributed in the hope that it will be useful, but WITHOUT ANY
13 ;;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 ;;; FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 ;;; You should have received a copy of the GNU General Public License
18 ;;; along with GNU Emacs; see the file COPYING. If not, write to
19 ;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21 ;;; This code is kind of kludgey, mostly because it needs to parse the contents
22 ;;; of the *BBDB* buffer, since BBDB doesn't save the buffer-positions of the
23 ;;; various fields when it fills in that buffer (doing that would be slow and
24 ;;; cons a lot, so it doesn't seem to be worth it.)
29 ;; compiler whinage. Some of this is legacy stuff that would probably
31 (defvar scrollbar-height nil)
35 (if (fboundp 'set-specifier)
36 (defalias 'bbdb-set-specifier 'set-specifier)
37 (defalias 'bbdb-set-specifier 'ignore))
38 (if (fboundp 'make-glyph)
39 (defalias 'bbdb-make-glyph 'make-glyph)
40 (defalias 'bbdb-make-glyph 'ignore))
41 (if (fboundp 'set-glyph-face)
42 (defalias 'bbdb-set-glyph-face 'set-glyph-face)
43 (defalias 'bbdb-set-glyph-face 'ignore))
44 (if (fboundp 'highlight-headers-x-face)
45 (defalias 'bbdb-highlight-headers-x-face 'highlight-headers-x-face)
46 (defalias 'bbdb-highlight-headers-x-face 'ignore))
47 (if (fboundp 'highlight-headers-x-face-to-pixmap)
48 (defalias 'bbdb-highlight-headers-x-face-to-pixmap
49 'highlight-headers-x-face-to-pixmap)
50 (defalias 'bbdb-highlight-headers-x-face-to-pixmap 'ignore)))
53 (if (featurep 'xemacs)
55 (define-key bbdb-mode-map 'button3 'bbdb-menu)
56 (define-key bbdb-mode-map 'button2
60 (bbdb-toggle-records-display-layout nil))))
61 (define-key bbdb-mode-map [mouse-3] 'bbdb-menu)
62 (define-key bbdb-mode-map [mouse-2]
66 (bbdb-toggle-records-display-layout nil))))
69 (if (fboundp 'find-face)
70 (defalias 'bbdb-find-face 'find-face)
71 (if (fboundp 'internal-find-face) ;; GRR.
72 ;; This should be facep in Emacs 21
73 (defalias 'bbdb-find-face 'internal-find-face)
74 (defalias 'bbdb-find-face 'ignore)))) ; noop - you probably don't HAVE faces.
76 (or (bbdb-find-face 'bbdb-name)
77 (face-differs-from-default-p (make-face 'bbdb-name))
78 (set-face-underline-p 'bbdb-name t))
81 (or (bbdb-find-face 'bbdb-company)
82 (face-differs-from-default-p (make-face 'bbdb-company))
83 (make-face-italic 'bbdb-company)) ;; this can fail on emacs
86 (or (bbdb-find-face 'bbdb-field-value)
87 (make-face 'bbdb-field-value))
89 (or (bbdb-find-face 'bbdb-field-name)
90 (face-differs-from-default-p (make-face 'bbdb-field-name))
91 (copy-face 'bold 'bbdb-field-name))
93 ;;; Extents vs. Overlays unhappiness
94 ;;; FIXME: see if VM is around, and call its extents code instead;
95 ;;; change bbdb-foo-extents below to vm-foo-extents, etc.
97 (if (fboundp 'make-extent)
98 (defalias 'bbdb-make-extent 'make-extent)
99 (defalias 'bbdb-make-extent 'make-overlay))
101 (if (fboundp 'delete-extent)
102 (defalias 'bbdb-delete-extent 'delete-extent)
103 (defalias 'bbdb-delete-extent 'delete-overlay))
105 (if (fboundp 'mapcar-extents)
106 (defmacro bbdb-list-extents() `(mapcar-extents 'identity))
107 (defun bbdb-list-extents()
108 (let ((o (overlay-lists))) (nconc (car o) (cdr o)))))
110 (if (fboundp 'mapcar-extents)
111 (defmacro bbdb-extents-in (s e)
112 (list 'mapcar-extents ''identity nil nil s e))
113 (defmacro bbdb-extents-in (s e)
114 (list 'overlays-in s e)))
116 (if (fboundp 'set-extent-property)
117 (defalias 'bbdb-set-extent-property 'set-extent-property)
118 (defun bbdb-set-extent-property( e p v )
119 (if (eq 'highlight p)
121 (overlay-put e 'mouse-face 'highlight)
122 (overlay-put e 'mouse-face nil)))
123 (overlay-put e p v)))
125 (if (fboundp 'extent-property)
126 (defalias 'bbdb-extent-property 'extent-property)
127 (defalias 'bbdb-extent-property 'overlay-get))
129 (if (fboundp 'extent-at)
130 (defalias 'bbdb-extent-at 'extent-at)
131 (defun bbdb-extent-at (pos buf tag) "NOT FULL XEMACS IMPLEMENTATION"
132 (let ((o (overlays-at pos))
138 (if (or (null minpri) (> minpri (overlay-get x 'priority)))
140 minpri (overlay-get x 'priority))))
144 (if (fboundp 'highlight-extent)
145 (defalias 'bbdb-highlight-extent 'highlight-extent)
146 (defalias 'bbdb-highlight-extent 'ignore)) ; XXX noop
148 (if (fboundp 'extent-start-position)
149 (defalias 'bbdb-extent-start-position 'extent-start-position)
150 (defalias 'bbdb-extent-start-position 'overlay-start))
152 (if (fboundp 'extent-end-position)
153 (defalias 'bbdb-extent-end-position 'extent-end-position)
154 (defalias 'bbdb-extent-end-position 'overlay-end))
156 (if (fboundp 'extent-face)
157 (defalias 'bbdb-extent-face 'extent-face)
158 (defun bbdb-extent-face (extent)
159 (overlay-get extent 'face)))
161 (if (fboundp 'set-extent-face)
162 (defalias 'bbdb-set-extent-face 'set-extent-face)
163 (defun bbdb-set-extent-face (extent face) "set the face for an overlay"
164 (overlay-put extent 'face face)))
166 (if (fboundp 'set-extent-begin-glyph)
167 (defalias 'bbdb-set-extent-begin-glyph 'set-extent-begin-glyph)
168 (defalias 'bbdb-set-extent-begin-glyph 'ignore)) ; XXX noop
170 (if (fboundp 'set-extent-end-glyph)
171 (defalias 'bbdb-set-extent-end-glyph 'set-extent-end-glyph)
172 (defalias 'bbdb-set-extent-end-glyph 'ignore))) ; XXX noop
175 (eval-when-compile (defvar scrollbar-height))
177 (defun bbdb-fontify-buffer (&optional records)
180 (set-buffer bbdb-buffer-name)
181 (if (featurep 'scrollbar)
182 (bbdb-set-specifier scrollbar-height (cons (current-buffer) 0)))
184 (let ((rest (or records bbdb-records))
192 (setq record (car (car rest))
193 multi-line-p (string-match "multi-line"
194 (symbol-name (nth 1 (car rest))))
195 face (and multi-line-p (bbdb-record-getprop record 'face))
196 start (marker-position (nth 2 (car rest)))
197 end (1- (or (nth 2 (car (cdr rest))) (point-max))))
199 (if (< start (point-min)) (setq start (point-min)))
200 (if (> end (point-max)) (setq end (point-max)))
202 (mapcar (function (lambda(o)
204 (eq (bbdb-extent-property o 'data)
206 (bbdb-delete-extent o))))
207 (bbdb-extents-in start end))
209 (setq extent (bbdb-make-extent start end))
210 (bbdb-set-extent-property extent 'highlight t)
211 (bbdb-set-extent-property extent 'data 'bbdb)
212 ;; note that on GNU Emacs, once you hit the main overlay, you
213 ;; have to move off the record and back on again before it'll
214 ;; notice that you're on a more specific overlay. This is
215 ;; bogus, like most GNU Emacs GUI stuff.
216 (bbdb-set-extent-property extent 'priority 3)
217 (if face (bbdb-hack-x-face face extent))
220 (setq property (cadr (member 'bbdb-field (text-properties-at s))))
221 (while (and s (< s end))
222 (setq e (or (next-single-property-change (1+ s) 'bbdb-field)
224 (cond ((equal property '(name))
225 (setq extent (bbdb-make-extent s e))
226 (bbdb-set-extent-property extent 'priority 2)
227 (bbdb-set-extent-property extent 'data 'bbdb)
228 (bbdb-set-extent-face extent 'bbdb-name))
229 ((equal property '(company))
230 (setq extent (bbdb-make-extent s e))
231 (bbdb-set-extent-property extent 'priority 2)
232 (bbdb-set-extent-property extent 'data 'bbdb)
233 (bbdb-set-extent-face extent 'bbdb-company))
234 ((member 'field-name property)
236 (setq extent (bbdb-make-extent s e))
237 (bbdb-set-extent-property extent 'priority 2)
238 (bbdb-set-extent-property extent 'data 'bbdb)
239 (bbdb-set-extent-face extent 'bbdb-field-name))
241 (setq extent (bbdb-make-extent start e))
242 (bbdb-set-extent-property extent 'priority 2)
243 (bbdb-set-extent-property extent 'data 'bbdb)
244 (bbdb-set-extent-face extent 'bbdb-field-value)))
246 (while (and s (null (setq property
247 (cadr (member 'bbdb-field
248 (text-properties-at s))))))
249 (setq s (next-single-property-change s 'bbdb-field))))
251 (setq rest (cdr rest))
252 (if (null (caar rest))
255 ;;; share the xface cache data with VM if it's around
256 (defvar vm-xface-cache (make-vector 29 0))
257 (eval-when-compile (defvar highlight-headers-hack-x-face-p))
259 ;; In Emacs 21, this could use the x-face support from Gnus.
260 (defun bbdb-hack-x-face (face extent)
261 "Process a face property of a record and honour it.
262 Not done for GNU Emacs just yet, since it doesn't have image support
263 as of GNU Emacs 20.7"
264 (if (not (or (and (fboundp 'highlight-headers-hack-x-face-p)
265 (symbol-value (intern ;; compiler
266 "highlight-headers-hack-x-face-p"))) ;; ick.
267 (and (featurep 'xemacs)
268 (string-match "^21\\." emacs-version)))) ;; XXX
270 (setq face (bbdb-split face "\n"))
274 ;; ripped pretty much verbatim from VM; X Faces for recent XEmacsen.
275 ((string-match "^21\\." emacs-version) ;; XXX how far back can I go?
277 (let* ((h (concat "X-Face: " (car face))) ;; from vm-display-xface
278 (g (intern h vm-xface-cache)))
279 (if (bbdb-find-face 'vm-xface) ;; use the same face as VM
281 (make-face 'vm-xface)
282 (set-face-background 'vm-xface "white")
283 (set-face-foreground 'vm-xface "black"))
285 (setq g (symbol-value g))
286 (set g (bbdb-make-glyph
288 (vector 'xface ':data h)))) ;; XXX use API
289 (setq g (symbol-value g))
290 (bbdb-set-glyph-face g 'vm-xface))
291 (bbdb-set-extent-property extent 'vm-xface t)
292 (bbdb-set-extent-begin-glyph extent g))
293 (error nil))) ;; looks like you don't have xface support, d00d
295 ;; requires lemacs 19.10 version of highlight-headers.el
296 ((fboundp 'highlight-headers-x-face) ; the 19.10 way
297 (bbdb-highlight-headers-x-face (car face) extent)
298 (let ((b (bbdb-extent-property extent 'begin-glyph)))
299 (cond (b ; I'd like this to be an end-glyph instead
300 (bbdb-set-extent-property extent 'begin-glyph nil)
301 (bbdb-set-extent-property extent 'end-glyph b)))))
303 ((fboundp 'highlight-headers-x-face-to-pixmap) ; the 19.13 way
305 (set-buffer (get-buffer-create " *tmp*"))
306 (buffer-disable-undo (current-buffer))
309 (bbdb-set-extent-begin-glyph extent nil)
310 (bbdb-set-extent-end-glyph extent
311 (bbdb-highlight-headers-x-face-to-pixmap
312 (point-min) (point-max)))
316 (setq face (cdr face))
317 (cond (face ; there are more, so clone the extent
318 (setq extent (bbdb-make-extent
319 (bbdb-extent-start-position extent)
320 (bbdb-extent-end-position extent)))
321 (bbdb-set-extent-property extent 'data 'bbdb))))))
324 (defcustom bbdb-user-menu-commands nil
325 "User defined menu entries which should be appended to the BBDB menu.
326 This should be a list of menu entries.
327 When set to a fucntion the function gets called with two arguments the
328 RECORD and the FIELD and it should either return nil or a list of menu
330 :group 'bbdb-database
333 (defun build-bbdb-finger-menu (record)
334 (let ((addrs (bbdb-record-finger-host record)))
338 (mapcar (lambda (addr)
339 (vector addr (list 'bbdb-finger record addr)
343 (vector "Finger all addresses"
344 (list 'bbdb-finger record ''(4)) t))))
345 (vector (concat "Finger " (car addrs))
346 (list 'bbdb-finger record (car addrs)) t))))
348 (defun build-bbdb-sendmail-menu (record)
349 (let ((addrs (bbdb-record-net record)))
352 (mapcar (lambda (addr)
353 (vector addr (list 'bbdb-send-mail-internal
354 (bbdb-dwim-net-address record addr))
357 (vector (concat "Send mail to " (car addrs))
358 (list 'bbdb-send-mail-internal
359 (bbdb-dwim-net-address record (car addrs)))
363 (defun build-bbdb-field-menu (record field)
364 (let ((type (car field)))
367 (concat "Commands for "
368 (cond ((eq type 'property)
370 (symbol-name (if (consp (car (cdr field)))
371 (car (car (cdr field)))
374 ((eq type 'name) "Name field:")
375 ((eq type 'company) "Company field:")
376 ((eq type 'net) "Network Addresses field:")
377 ((eq type 'aka) "Alternate Names field:")
379 (concat "\"" (aref (nth 1 field) 0) "\" "
380 (capitalize (symbol-name type)) " field:"))))
382 ["Edit Field" bbdb-edit-current-field t]
384 (if (memq type '(name company))
386 (list ["Delete Field" bbdb-delete-current-field-or-record t]))
387 (cond ((eq type 'phone)
388 (list (vector (concat "Dial " (bbdb-phone-string (car (cdr field))))
389 (list 'bbdb-dial (list 'quote field) nil) t)))
394 (defun build-bbdb-insert-field-menu (record)
395 (cons "Insert New Field..."
398 (let ((type (if (string= (car field) "AKA")
400 (intern (car field)))))
402 (list 'bbdb-insert-new-field
405 (list 'bbdb-prompt-for-new-field-value
408 (or (and (eq type 'net) (bbdb-record-net record))
409 (and (eq type 'aka) (bbdb-record-aka record))
410 (and (eq type 'notes) (bbdb-record-notes record))
411 (and (consp (bbdb-record-raw-notes record))
412 (assq type (bbdb-record-raw-notes record))))))))
413 (append '(("phone") ("address") ("net") ("AKA") ("notes"))
417 (defun build-bbdb-menu (record field)
421 '("bbdb-menu" "Global BBDB Commands" "-----")
423 ["Save BBDB" bbdb-save-db t]
424 ["Toggle All Records Display Layout"
425 bbdb-toggle-all-records-display-layout t]
426 ["Finger All Records" (bbdb-finger (mapcar 'car bbdb-records)) t]
427 ["BBDB Manual" bbdb-info t]
428 ["BBDB Quit" bbdb-bury-buffer t])
432 (concat "Commands for record \""
433 (bbdb-record-name record) "\":")
435 (vector "Delete Record"
436 (list 'bbdb-delete-current-record record) t)
437 ["Toggle Records Display Layout" bbdb-toggle-records-display-layout t]
438 (if (and (not (eq 'full-multi-line
439 (nth 1 (assq record bbdb-records))))
440 (bbdb-display-layout-get-option 'multi-line 'omit))
441 ["Fully Display Record" bbdb-display-record-completely t])
442 ["Omit Record" bbdb-omit-record t]
443 ["Refile (Merge) Record" bbdb-refile-record t]
446 (list (build-bbdb-finger-menu record)))
447 (if (bbdb-record-net record)
448 (list (build-bbdb-sendmail-menu record)))
450 (list (build-bbdb-insert-field-menu record)))
452 (cons "-----" (build-bbdb-field-menu record field)))
453 (if bbdb-user-menu-commands
454 (let ((menu (if (functionp bbdb-user-menu-commands)
455 (funcall bbdb-user-menu-commands record field)
456 bbdb-user-menu-commands)))
459 ["User Defined Commands"]
464 (if (fboundp 'popup-menu)
466 (fset 'bbdb-popup 'popup-menu)
467 (fset 'bbdb-desc-to-menu 'identity))
468 ;; This is really, REALLY ugly, but it saves me some coding and uses
469 ;; the correct keymap API instead of carnal knowledge of keymap
471 (defun bbdb-desc-to-menu(desc)
472 (let ((map (make-sparse-keymap (car desc)))
473 (desc (reverse (cdr desc))) ;; throw away header, reorient list
474 (txtcount 0) elt elt-name)
475 (while (setq elt (car desc))
476 ;; fake a key binding name
477 (setq elt-name (intern (format "fake%d" txtcount))
478 txtcount (+ 1 txtcount))
480 ;; non-active entries in the menu
482 (define-key map (vector elt-name) (list elt)))
484 ;; active entries in the menu
486 (define-key map (vector elt-name) (cons (aref elt 0) (aref elt 1))))
490 (define-key map (vector elt-name)
491 (cons (car elt) (bbdb-desc-to-menu elt))))
493 (setq desc (cdr desc)))
495 ;; this does the actual popping up & parsing nonsense
496 (defun bbdb-popup( desc &optional event )
497 (let ((map (bbdb-desc-to-menu desc)) result)
498 (setq result (x-popup-menu t map))
500 (let ((command (lookup-key map (vconcat result))))
501 ;; Clear out echoing, which perhaps shows a prefix arg.
504 (if (commandp command)
505 (command-execute command)
506 (funcall 'eval command)))))))))
509 (defun bbdb-menu (event)
511 (mouse-set-point event)
513 (save-window-excursion
515 (let ((extent (or (bbdb-extent-at (point) (current-buffer) 'highlight)
518 (or (eq (bbdb-extent-property extent 'data) 'bbdb)
519 (error "not a bbdb extent"))
520 (bbdb-highlight-extent extent t)
521 (setq record (bbdb-current-record)
522 field (get-text-property (point) 'bbdb-field))
523 (build-bbdb-menu record field))))))
525 ;; tell everyone else we're here.