*** empty log message ***
[elisp/wanderlust.git] / utils / bbdb-wl.el
1 ;;; bbdb-wl.el -- BBDB interface to Wanderlust
2
3 ;; Copyright (C) 1998,1999,2000 Yuuichi Teranishi <teranisi@gohome.org>
4
5 ;; Author: Yuuichi Teranishi <teranisi@gohome.org>
6 ;; Keywords: mail, news, database
7
8 ;;; Commentary:
9 ;;
10 ;;  Insert the following lines in your ~/.wl
11 ;;
12 ;;  (require 'bbdb-wl)
13 ;;  (bbdb-wl-setup)
14
15 ;;; Code:
16 ;;
17
18 ;; bbdb setup.
19 (eval-when-compile
20   (require 'mime-setup)
21   (require 'elmo-vars)
22   (require 'elmo-util)
23   (require 'wl-summary)
24   (require 'wl-message)
25   (require 'wl-draft)
26   (require 'wl-address)
27   (require 'bbdb-com)
28   (defvar bbdb-pop-up-elided-display nil))
29
30 (require 'bbdb)
31
32 (defvar bbdb-wl-get-update-record-hook nil)
33 (defvar bbdb-wl-folder-regexp nil)
34
35 ;;;###autoload
36 (defun bbdb-wl-setup ()
37   (add-hook 'wl-message-redisplay-hook 'bbdb-wl-get-update-record)
38   (add-hook 'wl-summary-exit-hook 'bbdb-wl-hide-bbdb-buffer)
39   (add-hook 'wl-message-window-deleted-hook 'bbdb-wl-hide-bbdb-buffer)
40   (add-hook 'wl-exit-hook 'bbdb-wl-exit)
41   (add-hook 'wl-summary-toggle-disp-off-hook 'bbdb-wl-hide-bbdb-buffer)
42   (add-hook 'wl-summary-toggle-disp-folder-on-hook 'bbdb-wl-hide-bbdb-buffer)
43   (add-hook 'wl-summary-toggle-disp-folder-off-hook 'bbdb-wl-hide-bbdb-buffer)
44   (add-hook 'wl-summary-toggle-disp-folder-message-resumed-hook
45             'bbdb-wl-show-bbdb-buffer)
46   (add-hook 'wl-summary-mode-hook
47             (function
48              (lambda ()
49                (define-key (current-local-map) ":" 'bbdb-wl-show-sender)
50                (define-key (current-local-map) ";" 'bbdb-wl-edit-notes))))
51   (add-hook 'wl-summary-exit-hook 'bbdb-flush-all-caches)
52   (add-hook 'wl-summary-exec-hook 'bbdb-flush-all-caches)
53   (add-hook 'wl-mail-setup-hook
54             (function
55              (lambda ()
56 ;;;            (local-set-key "\M-\t" 'bbdb-complete-name)
57                (define-key (current-local-map) "\M-\t" 'bbdb-complete-name))))
58   ;; BBDB 2.00.06 or earlier:
59   ;;  auto-autoloads.el includes (provide 'bbdb-autoloads)
60   ;;  Don't exist bbdb-autoloads.el
61   (when (and (not (featurep 'bbdb-autoloads))
62              (module-installed-p 'bbdb-autoloads))
63     ;; BBDB 2.20: bbdb-autoloads.el NOT includes (provide 'bbdb-autoloads)
64     (load "bbdb-autoloads")))
65
66 (defun bbdb-wl-exit ()
67   (let (bbdb-buf)
68     (if (setq bbdb-buf (get-buffer bbdb-buffer-name))
69         (kill-buffer bbdb-buf)))
70   (bbdb-offer-save))
71
72 (defun bbdb-wl-get-update-record ()
73   (if (or (null bbdb-wl-folder-regexp)
74           (string-match
75            bbdb-wl-folder-regexp
76            (with-current-buffer
77                wl-message-buffer-cur-summary-buffer
78              (wl-summary-buffer-folder-name))))
79       (with-current-buffer (wl-message-get-original-buffer)
80         (bbdb-wl-update-record)
81         (run-hooks 'bbdb-wl-get-update-record-hook))))
82
83 (defun bbdb-wl-hide-bbdb-buffer ()
84   (let (bbdb-buf bbdb-win)
85     (if (setq bbdb-buf (get-buffer bbdb-buffer-name))
86         (if (setq bbdb-win (get-buffer-window bbdb-buf))
87             (delete-window bbdb-win)))))
88
89 (defun bbdb-wl-show-bbdb-buffer ()
90   (save-selected-window
91     (if (get-buffer-window bbdb-buffer-name)
92         nil
93       (let ((mes-win (get-buffer-window
94                       (save-excursion
95                         (if (buffer-live-p  wl-current-summary-buffer)
96                             (set-buffer wl-current-summary-buffer))
97                         wl-message-buffer)))
98             (cur-win (selected-window))
99             (b (current-buffer)))
100         (and mes-win (select-window mes-win))
101         (let ((size (min
102                      (- (window-height mes-win)
103                         window-min-height 1)
104                      (- (window-height mes-win)
105                         (max window-min-height
106                              (1+ bbdb-pop-up-target-lines))))))
107           (split-window mes-win (if (> size 0) size window-min-height)))
108         ;; goto the bottom of the two...
109         (select-window (next-window))
110         ;; make it display *BBDB*...
111         (let ((pop-up-windows nil))
112           (switch-to-buffer (get-buffer-create bbdb-buffer-name)))))))
113
114 (defun bbdb-wl-get-petname (from)
115   "For `wl-summary-get-petname-function'."
116   (let* ((address (wl-address-header-extract-address from))
117          (record (bbdb-search-simple nil address)))
118     (and record
119          (or (bbdb-record-name record)
120              (car (bbdb-record-name record))))))
121
122 (defun bbdb-wl-from-func (string)
123   "A candidate From field STRING.  For `wl-summary-from-function'."
124   (let ((hit (bbdb-search-simple nil (wl-address-header-extract-address
125                                       string)))
126         first-name last-name from-str)
127     (if hit
128         (progn
129           (setq first-name (aref hit 0))
130           (setq last-name (aref hit 1))
131           (cond ((and (null first-name)
132                       (null last-name))
133                  (setq from-str string))
134                 ((and first-name last-name)
135                  (setq from-str (concat first-name " " last-name)))
136                 ((or first-name last-name)
137                  (setq from-str (or first-name last-name))))
138           from-str)
139       string)))
140
141 (if (not (boundp 'bbdb-get-addresses-from-headers))
142     (defvar bbdb-get-addresses-from-headers
143       '("From" "Resent-From" "Reply-To")))
144
145 (if (not (boundp 'bbdb-get-addresses-to-headers))
146     (defvar bbdb-get-addresses-to-headers
147       '("Resent-To" "Resent-CC" "To" "CC" "BCC")))
148
149 (if (not (boundp 'bbdb-get-addresses-headers))
150     (defvar bbdb-get-addresses-headers
151       (append bbdb-get-addresses-from-headers bbdb-get-addresses-to-headers)))
152
153 (defun bbdb-wl-get-addresses (&optional only-first-address)
154   "Return real name and email address of sender respectively recipients.
155 If an address matches `bbdb-user-mail-names' it will be ignored.
156 The headers to search can be configured by `bbdb-get-addresses-headers'."
157   (save-excursion
158     (save-restriction
159       (std11-narrow-to-header)
160       (let ((headers bbdb-get-addresses-headers)
161             (uninteresting-senders bbdb-user-mail-names)
162             addrlist header structures structure fn ad)
163         (while headers
164           (setq header (std11-fetch-field (car headers)))
165           (when header
166             (setq structures (std11-parse-addresses-string
167                               (std11-unfold-string header)))
168             (while (and (setq structure (car structures))
169                         (eq (car structure) 'mailbox))
170               (setq fn (std11-full-name-string structure)
171                     fn (and fn
172                             (with-temp-buffer ; to keep raw buffer unibyte.
173                               (elmo-set-buffer-multibyte
174                                default-enable-multibyte-characters)
175                               (eword-decode-string
176                                (decode-mime-charset-string
177                                 fn wl-mime-charset))))
178                     ad (std11-address-string structure))
179
180               ;; ignore uninteresting addresses, this is kinda gross!
181               (when (or (not (stringp uninteresting-senders))
182                         (not (or
183                               (and fn (string-match uninteresting-senders fn))
184                               (and ad (string-match uninteresting-senders ad)))))
185                 (add-to-list 'addrlist (list fn ad)))
186
187               (if (and only-first-address addrlist)
188                   (setq structures nil headers nil)
189                 (setq structures (cdr structures)))))
190           (setq headers (cdr headers)))
191         (nreverse addrlist)))))
192
193 (defun bbdb-wl-update-record (&optional offer-to-create)
194   "Returns the record corresponding to the current WL message,
195 creating or modifying it as necessary.  A record will be created if
196 bbdb/mail-auto-create-p is non-nil, or if OFFER-TO-CREATE is true and
197 the user confirms the creation."
198   (let* ((bbdb-get-only-first-address-p t)
199          (records (bbdb-wl-update-records offer-to-create)))
200     (if (and records (listp records))
201         (car records)
202       records)))
203
204 (defun bbdb-wl-update-records (&optional offer-to-create)
205   "Returns the records corresponding to the current WL message,
206 creating or modifying it as necessary.  A record will be created if
207 bbdb/mail-auto-create-p is non-nil, or if OFFER-TO-CREATE is true and
208 the user confirms the creation."
209   (save-excursion
210     (if bbdb-use-pop-up
211         (bbdb-wl-pop-up-bbdb-buffer offer-to-create)
212       (let ((key
213              (save-excursion
214                (set-buffer
215                 (save-excursion
216                   (if (buffer-live-p wl-current-summary-buffer)
217                       (set-buffer wl-current-summary-buffer))
218                   wl-message-buffer))
219                (intern (format
220                         "%s-%d"
221                         wl-current-summary-buffer
222                         wl-message-buffer-cur-number))))
223             record)
224         (or (progn (setq record (bbdb-message-cache-lookup key))
225                    (if (listp record) (nth 1 record) record))
226             (static-if (not (fboundp 'bbdb-update-records))
227                 (let* ((from (or (std11-field-body "From") ""))
228                        (addr (and from
229                                   (nth 1 (std11-extract-address-components
230                                           from)))))
231                   (if (or (null from)
232                           (null addr)
233                           (string-match (bbdb-user-mail-names) addr))
234                       (setq from (or (std11-field-body "To") from)))
235                   (with-temp-buffer ; to keep raw buffer unibyte.
236                     (elmo-set-buffer-multibyte
237                      default-enable-multibyte-characters)
238                     (setq from (eword-decode-string
239                                 (decode-mime-charset-string
240                                  from
241                                  wl-mime-charset))))
242                   (if from
243                       (bbdb-encache-message
244                        key
245                        (bbdb-annotate-message-sender
246                         from t
247                         (or (bbdb-invoke-hook-for-value
248                              bbdb/mail-auto-create-p)
249                             offer-to-create)
250                         offer-to-create))))
251               (bbdb-encache-message
252                key
253                (bbdb-update-records (bbdb-wl-get-addresses
254                                      bbdb-get-only-first-address-p)
255                                     (or (bbdb-invoke-hook-for-value
256                                          bbdb/mail-auto-create-p)
257                                         offer-to-create)
258                                     offer-to-create))))))))
259
260 (defun bbdb-wl-annotate-sender (string)
261   "Add a line to the end of the Notes field of the BBDB record
262 corresponding to the sender of this message."
263   (interactive (list (if bbdb-readonly-p
264                          (error "The Insidious Big Brother Database is read-only")
265                        (read-string "Comments: "))))
266   (set-buffer (wl-message-get-original-buffer))
267   (bbdb-annotate-notes (bbdb-wl-update-record t) string))
268
269 (defun bbdb-wl-edit-notes (&optional arg)
270   "Edit the notes field or (with a prefix arg) a user-defined field
271 of the BBDB record corresponding to the sender of this message."
272   (interactive "P")
273   (wl-summary-set-message-buffer-or-redisplay)
274   (set-buffer (wl-message-get-original-buffer))
275   (let ((record (or (bbdb-wl-update-record t) (error ""))))
276     (bbdb-display-records (list record))
277     (if arg
278         (bbdb-record-edit-property record nil t)
279       (bbdb-record-edit-notes record t))))
280
281 (defun bbdb-wl-show-records (&optional headers)
282   "Display the contents of the BBDB for the sender of this message.
283 This buffer will be in `bbdb-mode', with associated keybindings."
284   (interactive)
285   (wl-summary-set-message-buffer-or-redisplay)
286   (set-buffer (wl-message-get-original-buffer))
287   (let ((bbdb-get-addresses-headers (or headers bbdb-get-addresses-headers))
288         (bbdb-update-records-mode 'annotating)
289         (bbdb-message-cache nil)
290         (bbdb-user-mail-names nil)
291         records bbdb-win)
292     (setq records (bbdb-wl-update-records t))
293     (if records
294         (progn
295           (bbdb-wl-pop-up-bbdb-buffer)
296           (bbdb-display-records (if (listp records) records
297                                   (list records))))
298       (bbdb-undisplay-records))
299     (setq bbdb-win (get-buffer-window (get-buffer bbdb-buffer-name)))
300     (and bbdb-win
301          (select-window bbdb-win))
302     records))
303
304 (defun bbdb-wl-show-all-recipients ()
305   "Show all recipients of this message. Counterpart to `bbdb/vm-show-sender'."
306   (interactive)
307   (bbdb-wl-show-records  bbdb-get-addresses-to-headers))
308
309 (defun bbdb-wl-show-sender (&optional show-recipients)
310   "Display the contents of the BBDB for the senders of this message.
311 With a prefix argument show the recipients instead,
312 with two prefix arguments show all records.
313 This buffer will be in `bbdb-mode', with associated keybindings."
314   (interactive "p")
315   (cond ((= 4 show-recipients)
316          (bbdb-wl-show-all-recipients))
317         ((= 16 show-recipients)
318          (bbdb-wl-show-records))
319         (t
320          (if (null (bbdb-wl-show-records bbdb-get-addresses-from-headers))
321              (bbdb-wl-show-all-recipients)))))
322
323 (defun bbdb-wl-pop-up-bbdb-buffer (&optional offer-to-create)
324   "Make the *BBDB* buffer be displayed along with the WL window(s),
325 displaying the record corresponding to the sender of the current message."
326   (if (get-buffer-window bbdb-buffer-name)
327       nil
328     (let ((mes-win (get-buffer-window
329                     (save-excursion
330                       (if (buffer-live-p  wl-current-summary-buffer)
331                           (set-buffer wl-current-summary-buffer))
332                       wl-message-buffer)))
333           (cur-win (selected-window))
334           (b (current-buffer)))
335       (and mes-win
336            (select-window mes-win))
337       (let ((size (min
338                    (- (window-height mes-win)
339                       window-min-height 1)
340                    (- (window-height mes-win)
341                       (max window-min-height
342                            (1+ bbdb-pop-up-target-lines))))))
343         (split-window mes-win (if (> size 0) size window-min-height)))
344       ;; goto the bottom of the two...
345       (select-window (next-window))
346       ;; make it display *BBDB*...
347       (let ((pop-up-windows nil))
348         (switch-to-buffer (get-buffer-create bbdb-buffer-name)))
349       ;; select the original window we were in...
350       (select-window cur-win)
351       ;; and make sure the current buffer is correct as well.
352       (set-buffer b)))
353   (let ((bbdb-gag-messages t)
354         (bbdb-use-pop-up nil)
355         (bbdb-electric-p nil))
356     (let* ((records (static-if (fboundp 'bbdb-update-records)
357                         (bbdb-wl-update-records offer-to-create)
358                       (bbdb-wl-update-record offer-to-create)))
359            ;; BBDB versions v2.33 and later.
360            (bbdb-display-layout
361             (cond ((boundp 'bbdb-pop-up-display-layout)
362                    (symbol-value 'bbdb-pop-up-display-layout))
363                   ((boundp 'bbdb-pop-up-elided-display)
364                    (symbol-value 'bbdb-pop-up-elided-display))))
365            ;; BBDB versions prior to v2.33,
366            (bbdb-elided-display bbdb-display-layout)
367            (b (current-buffer)))
368       (bbdb-display-records (if (listp records) records
369                               (list records)))
370       (set-buffer b)
371       records)))
372
373 (defun bbdb-wl-send-mail-internal (&optional to subj records)
374   (unwind-protect
375       (wl-draft (wl-address-header-extract-address to) "" (or subj ""))
376     (condition-case nil (delete-other-windows) (error))))
377
378 ;;; @ bbdb-extract-field-value -- stolen from tm-bbdb.
379 ;;;
380 (eval-and-compile
381   (if (fboundp 'bbdb-wl-extract-field-value-internal)
382 ;;(if (fboundp 'PLEASE_REPLACE_WITH_SEMI-BASED_MIME-BBDB)) ;; mime-bbdb
383       nil
384     (if (and (string< bbdb-version "1.58")
385              ;;(not (fboundp 'bbdb-extract-field-value) ;; defined as autoload
386              (not (fboundp 'bbdb-header-start)))
387         (load "bbdb-hooks")
388       (require 'bbdb-hooks))
389     (fset 'bbdb-wl-extract-field-value-internal
390           (cond
391            ((fboundp 'tm:bbdb-extract-field-value)
392             (symbol-function 'tm:bbdb-extract-field-value))
393            (t (symbol-function 'bbdb-extract-field-value))))
394     (defun bbdb-extract-field-value (field)
395       (let ((value (bbdb-wl-extract-field-value-internal field)))
396         (with-temp-buffer ; to keep raw buffer unibyte.
397           (elmo-set-buffer-multibyte
398            default-enable-multibyte-characters)
399           (and value
400                (eword-decode-string value)))))
401     ))
402
403
404 (provide 'bbdb-wl)
405
406 ;;; bbdb-wl.el ends here