Initial Commit
[packages] / xemacs-packages / bbdb / lisp / bbdb-com.el
1 ;;; -*- Mode:Emacs-Lisp -*-
2
3 ;;; This file is part of the Insidious Big Brother Database (aka BBDB),
4 ;;; copyright (c) 1991, 1992, 1993 Jamie Zawinski <jwz@netscape.com>.
5 ;;; It contains most of the user-level interactive commands for BBDB.
6 ;;; See bbdb.texinfo.
7
8 ;;; The Insidious Big Brother Database is free software; you can redistribute
9 ;;; it and/or modify it under the terms of the GNU General Public License as
10 ;;; published by the Free Software Foundation; either version 2, or (at your
11 ;;; option) any later version.
12 ;;;
13 ;;; BBDB is distributed in the hope that it will be useful, but WITHOUT ANY
14 ;;; WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 ;;; FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
16 ;;; details.
17 ;;;
18 ;;; You should have received a copy of the GNU General Public License
19 ;;; along with GNU Emacs; see the file COPYING.  If not, write to
20 ;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 ;;
23 ;; $Id: bbdb-com.el,v 1.10 2007-02-23 20:24:06 fenk Exp $
24 ;;
25
26 (require 'cl)
27 (require 'bbdb)
28 ;;(require 'bbdb-snarf) causes recursive compile, which I should fix.
29
30 ;; ARGH. fmh, dammit.
31 (require
32  (eval-and-compile
33    (if (locate-library "mailabbrev")
34        (quote mailabbrev)
35      (quote mail-abbrevs))))
36
37 ;; compiler placating.
38 ;; not sure BBDB runs on anything old enough to use auto-fill-hook, mind.
39 (eval-and-compile
40   (if (boundp 'auto-fill-function)
41       (fset 'bbdb-auto-fill-function 'auto-fill-function)
42     (fset 'bbdb-auto-fill-function 'auto-fill-hook))
43
44   (autoload 'mh-send "mh-e")
45   (autoload 'vm-session-initialization "vm-startup")
46   (autoload 'vm-mail-internal "vm-reply")
47   (autoload 'mew-send "mew")
48   (autoload 'bbdb-header-start "bbdb-hooks")
49   (autoload 'bbdb-extract-field-value "bbdb-hooks")
50   (autoload 'bbdb-fontify-buffer "bbdb-gui")
51   (autoload 'Info-goto-node "info")
52   ;; this is very unpleasant, but saves me doing a lot of rewriting
53   ;; for now. a big cleanup will happen for the next release, maybe.
54   ;; NB if emacs 21 or older emacsen or even things you bolt on have
55   ;; any of these functions, bad things will happen. Again, FITNR.
56   (if (featurep 'xemacs)
57       (progn
58         (fset 'bbdb-extent-string 'extent-string)
59         (fset 'bbdb-display-message 'display-message)
60         (fset 'bbdb-event-to-character 'event-to-character))
61     (fset 'bbdb-extent-string 'ignore)
62     (fset 'bbdb-display-message 'ignore)
63     (fset 'bbdb-event-to-character 'ignore)))
64
65 (defvar bbdb-define-all-aliases-needs-rebuilt nil)
66
67 (defcustom bbdb-extract-address-components-func
68   'bbdb-rfc822-addresses
69   "Function called to parse one or more email addresses.
70 See bbdb-extract-address-components for an example."
71   :group 'bbdb-noticing-records
72   :type 'function)
73
74 (defcustom bbdb-default-country
75   '"Emacs";; what do you mean, it's not a country?
76   "*Default country to use if none is specified."
77   :group 'bbdb-record-creation
78   :type 'string) ;; wonder if there's a smart place to get this? TZ, maybe?
79
80 (defmacro bbdb-grovel-elide-arg (arg)
81   (list 'if arg
82         (list 'not (list 'eq arg 0))
83         'bbdb-display-layout))
84
85 (defvar bbdb-search-invert nil
86   "Bind this variable to t in order to invert the result of `bbdb-search'.
87
88 \(let ((bbdb-search-invert t))
89    \(bbdb-search records foo foo))")
90
91 (defun bbdb-search-invert-p ()
92   "Return `bbdb-search-invert' and set it to nil.
93 To set it on again, use `bbdb-search-invert-set'."
94   (let ((result bbdb-search-invert))
95     (setq bbdb-search-invert nil)
96     result))
97
98 ;;;###autoload
99 (defun bbdb-search-invert-set ()
100   "Typing \\<bbdb-mode-map>\\[bbdb-search-invert-set] inverts the meaning of the next search command.
101 Sets `bbdb-search-invert' to t.
102 You will have to call this function again, if you want to
103 do repeated inverted searches."
104   (interactive)
105   (setq bbdb-search-invert t)
106   (message (substitute-command-keys
107             "\\<bbdb-mode-map>\\[bbdb-search-invert-set] - ")))
108
109 (defmacro bbdb-search (records &optional name company net notes phone)
110   "Search RECORDS for optional arguments NAME, COMPANY, NET, NOTES, PHONE.
111 This macro only emits code for those things being searched for;
112 literal nils at compile-time cause no code to be emitted.
113
114 If you want to reverse the search, bind `bbdb-search-invert' to t."
115   (let (clauses)
116     ;; I didn't protect these vars from multiple evaluation because that
117     ;; actually generates *less efficient code* in elisp, because the extra
118     ;; bindings can't easily be optimized away without lexical scope.  fmh.
119     (or (stringp name) (symbolp name) (error "name must be atomic"))
120     (or (stringp company) (symbolp company) (error "company must be atomic"))
121     (or (stringp net) (symbolp net) (error "net must be atomic"))
122     (or (stringp notes) (symbolp notes) (error "notes must be atomic"))
123     (or (stringp phone) (symbolp phone) (error "phone must be atomic"))
124     (if phone
125         (setq clauses
126               (cons
127                (` (let ((rest-of-phones (bbdb-record-phones record))
128                         (done nil))
129                     (if rest-of-phones
130                         (while (and rest-of-phones (not done))
131                           (setq done (string-match (, phone)
132                                                    ;; way way wasteful...
133                                                    (bbdb-phone-string
134                                                     (car rest-of-phones)))
135                                 rest-of-phones (cdr rest-of-phones)))
136                       ;; so that "^$" can be used to find entries that
137                       ;; have no phones
138                       (setq done (string-match (, phone) "")))
139                     done))
140                clauses)))
141     (if notes
142         (setq clauses
143               (cons
144                (` (if (stringp (, notes))
145                       (string-match (, notes)
146                                     (or (bbdb-record-notes record) ""))
147                     (if (eq (car (, notes)) '*)
148                         (let ((fields all-fields) done tmp)
149                           (if (bbdb-record-raw-notes record)
150                               (while (and (not done) fields)
151                                 (setq tmp (bbdb-record-getprop
152                                            record (car fields))
153                                       done (and tmp (string-match
154                                                      (cdr (, notes))
155                                                      tmp))
156                                       fields (cdr fields)))
157                             ;; so that "^$" can be used to find entries that
158                             ;; have no notes
159                             (setq done (string-match (cdr (, notes)) "")))
160                           done)
161                       (string-match (cdr (, notes))
162                                     (or (bbdb-record-getprop
163                                          record (car (, notes))) "")))))
164                clauses)))
165     (if name
166         (setq clauses
167               (append
168                (` ((string-match (, name) (or (bbdb-record-name record) ""))
169                    (let ((rest-of-aka (bbdb-record-aka record))
170                          (done nil))
171                      (while (and rest-of-aka (not done))
172                        (setq done (string-match (, name) (car rest-of-aka))
173                              rest-of-aka (cdr rest-of-aka)))
174                      done)))
175                clauses)))
176     (if net
177         (setq clauses
178               (cons
179                (` (let ((rest-of-nets (bbdb-record-net record))
180                         (done nil))
181                     (if rest-of-nets
182                         (while (and rest-of-nets (not done))
183                           (setq done (string-match (, net) (car rest-of-nets))
184                                 rest-of-nets (cdr rest-of-nets)))
185                       ;; so that "^$" can be used to find entries that
186                       ;; have no net addresses.
187                       (setq done (string-match (, net) "")))
188                     done))
189                clauses)))
190     (if company
191         (setq clauses
192               (cons
193                (` (string-match (, company)
194                                 (or (bbdb-record-company record) "")))
195                clauses)))
196
197     (` (let ((matches '())
198              (,@ (if notes
199                      '((all-fields (cons 'notes
200                                          (mapcar (lambda (x) (intern (car x)))
201                                                  (bbdb-propnames)))))
202                    nil))
203              (case-fold-search bbdb-case-fold-search)
204              (records (, records))
205          (invert (bbdb-search-invert-p))
206              record)
207          (while records
208            (setq record (car records))
209        (if (or (and invert
210             (not (or (,@ clauses))))
211            (and (not invert)
212             (or (,@ clauses))))
213            (setq matches (cons record matches)))
214        (setq records (cdr records)))
215          (nreverse matches)))))
216
217 (defun bbdb-search-prompt (prompt &rest rest)
218   (if (string-match "%m" prompt)
219       (setq prompt (replace-match (if bbdb-search-invert
220                                       "not matching"
221                                     "matching")
222                                   nil nil prompt)))
223   (read-string (apply 'format prompt rest)))
224
225 ;;;###autoload
226 (defun bbdb (string elidep)
227   "Display all entries in the BBDB matching the regexp STRING
228 in either the name(s), company, network address, or notes."
229   (interactive
230    (list (bbdb-search-prompt "Search records %m regexp: ")
231          current-prefix-arg))
232   (let* ((bbdb-display-layout (bbdb-grovel-elide-arg elidep))
233          (notes (cons '* string))
234          (records
235           (bbdb-search (bbdb-records) string string string notes
236                        nil)))
237     (if records
238         (bbdb-display-records records)
239       ;; we could use error here, but it's not really an error.
240       (message "No records matching '%s'" string))))
241
242 ;;;###autoload
243 (defun bbdb-name (string elidep)
244   "Display all entries in the BBDB matching the regexp STRING in the name
245 \(or ``alternate'' names\)."
246   (interactive
247    (list (bbdb-search-prompt "Search records with names %m regexp: ")
248          current-prefix-arg))
249    (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
250     (bbdb-display-records (bbdb-search (bbdb-records) string))))
251
252 ;;;###autoload
253 (defun bbdb-company (string elidep)
254   "Display all entries in BBDB matching STRING in the company field."
255   (interactive
256    (list (bbdb-search-prompt "Search records with company %m regexp: ")
257          current-prefix-arg))
258   (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
259     (bbdb-display-records (bbdb-search (bbdb-records) nil string))))
260
261 ;;;###autoload
262 (defun bbdb-net (string elidep)
263   "Display all entries in BBDB matching regexp STRING in the network address."
264   (interactive
265    (list (bbdb-search-prompt "Search records with net address %m regexp: ")
266          current-prefix-arg))
267   (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
268     (bbdb-display-records (bbdb-search (bbdb-records) nil nil string))))
269
270 ;;;###autoload
271 (defun bbdb-notes (which string elidep)
272   "Display all entries in BBDB matching STRING in the named notes field."
273   (interactive
274    (let (field)
275      (list (setq field (completing-read "Notes field to search (RET for all): "
276                                         (append '(("notes")) (bbdb-propnames))
277                                         nil t))
278            (if (featurep 'gmhist)
279                (read-with-history-in 'bbdb-notes-field "Regular expression: ")
280              (bbdb-search-prompt "Search records with %s %m regexp: "
281                                  (if (string= field "")
282                                      "one field"
283                                    field)))
284            current-prefix-arg)))
285   (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep))
286         (notes (if (string= which "")
287                    (cons '* string)
288                  (cons (intern which) string))))
289     (bbdb-display-records (bbdb-search (bbdb-records) nil nil nil notes))))
290
291 (defun bbdb-phones (string elidep)
292   "Display all entries in BBDB matching the regexp STRING in the phones field."
293   (interactive
294    (list (bbdb-search-prompt "Search records with phone %m regexp: ")
295          current-prefix-arg))
296   (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep)))
297     (bbdb-display-records
298      (bbdb-search (bbdb-records) nil nil nil nil string))))
299
300 ;;;###autoload
301 (defun bbdb-changed (elidep)
302   "Display all entries in the bbdb database which have been changed since
303 the database was last saved."
304   (interactive "P")
305   (let ((bbdb-display-layout (bbdb-grovel-elide-arg elidep))
306         (changed-records  (bbdb-with-db-buffer bbdb-changed-records)))
307     (if (bbdb-search-invert-p)
308         (let ((recs (bbdb-records))
309               unchanged-records
310               r)
311           (while recs
312             (setq r (car recs)
313                   recs (cdr recs))
314             (when (not (member r changed-records))
315               (setq changed-records (delete r changed-records)
316                     unchanged-records (cons r unchanged-records))))
317           (bbdb-display-records unchanged-records))
318       (bbdb-display-records changed-records))))
319
320 (defun bbdb-display (records)
321   "Prompts for and displays a single record (this is faster than searching.)"
322   (interactive (list (bbdb-completing-read-record "Display record of: ")))
323   (bbdb-display-records records))
324
325 (defun bbdb-display-some (function)
326   "Display records according to FUNCTION.  FUNCTION is called with one
327 argument, the record, and should return nil if the record is not to be
328 displayed.  If the record is to be displayed, it (the record) should
329 be returned."
330   (bbdb-display-records (delq nil (mapcar function (bbdb-records)))))
331
332 ;;; fancy redisplay
333
334 ;;;###autoload
335 (defun bbdb-redisplay-records ()
336   "Regrinds the contents of the *BBDB* buffer, without scrolling.
337 If possible, you should call `bbdb-redisplay-one-record' instead."
338   (let ((p (point))
339         (m (condition-case nil (mark) (error nil))))
340     (goto-char (window-start))
341     (let ((p2 (point)))
342       (bbdb-display-records-1 bbdb-records)
343       (goto-char p2)
344       (if m (set-mark m)))
345     (recenter 0)
346     (goto-char p)
347     (save-excursion
348       (run-hooks 'bbdb-list-hook))))
349
350 (defun bbdb-redisplay-one-record (record &optional record-cons next-record-cons
351                                          delete-p)
352   "Regrind one record. The *BBDB* buffer must be current when this is called."
353   (bbdb-debug (if (not (eq (not (not delete-p))
354                            (not (not (bbdb-record-deleted-p record)))))
355                   (error "splorch.")))
356   (if (null record-cons) (setq record-cons (assq record bbdb-records)))
357   (if (null next-record-cons)
358       (setq next-record-cons (car (cdr (memq record-cons bbdb-records)))))
359   (if (null record-cons)
360       (bbdb-display-records (list record) nil t)
361     (let ((position (point))
362           (marker (nth 2 record-cons))
363           next-marker
364           (buffer-read-only nil))
365       (bbdb-debug
366        (if (null record-cons) (error "doubleplus ungood: record unexists!"))
367        (if (null marker) (error "doubleplus ungood: marker unexists!")))
368       (beginning-of-line)
369       (goto-char marker)
370       (remove-text-properties marker (or (nth 2 next-record-cons) (point-max))
371                               '(bbdb-field nil))
372       (if delete-p nil
373         (bbdb-format-record (car record-cons) (car (cdr record-cons))))
374       (setq next-marker (or (nth 2 next-record-cons) (point-max)))
375       (delete-region (point) next-marker)
376       (if (< position next-marker)
377           (goto-char position))
378
379       (if (and bbdb-gui (not delete-p))
380           (bbdb-fontify-buffer (list record-cons
381                                      ;; the record ends here
382                                      (list nil nil next-marker))))
383       (save-excursion
384         (run-hooks 'bbdb-list-hook)))))
385
386 ;;; Parsing phone numbers
387 ;;; XXX this needs expansion to handle international prefixes properly
388 ;;; i.e. +353-number without discarding the +353 part. Problem being
389 ;;; that this will necessitate yet another change in the database
390 ;;; format for people who are using north american numbers.
391
392
393 (defconst bbdb-phone-area-regexp "(?[ \t]*\\+?1?[ \t]*[-\(]?[ \t]*[-\(]?[ \t]*\\([2-9][0-9][0-9]\\)[ \t]*)?[-./ \t]*")
394 (defconst bbdb-phone-main-regexp "\\([1-9][0-9][0-9]\\)[ \t]*[-.]?[ \t]*\\([0-9][0-9][0-9][0-9]\\)[ \t]*")
395
396 (defconst bbdb-phone-ext-regexp  "x?[ \t]*\\([0-9]+\\)[ \t]*")
397
398 (defconst bbdb-phone-regexp-1 (concat "^[ \t]*" bbdb-phone-area-regexp bbdb-phone-main-regexp bbdb-phone-ext-regexp "$"))
399 (defconst bbdb-phone-regexp-2 (concat "^[ \t]*" bbdb-phone-area-regexp bbdb-phone-main-regexp "$"))
400 (defconst bbdb-phone-regexp-3 (concat "^[ \t]*" bbdb-phone-main-regexp bbdb-phone-ext-regexp "$"))
401 (defconst bbdb-phone-regexp-4 (concat "^[ \t]*" bbdb-phone-main-regexp "$"))
402 (defconst bbdb-phone-regexp-5 (concat "^[ \t]*" bbdb-phone-ext-regexp "$"))
403
404 (defun bbdb-parse-phone-number (string &optional number-type)
405   "Parse a phone number from STRING and return a list of integers the form
406 \(area-code exchange number) or (area-code exchange number extension).
407 This is both lenient and strict in what it will parse - whitespace may
408 appear (or not) between any of the groups of digits, parentheses around the
409 area code are optional, as is a dash between the exchange and number, and
410 a '1' preceeding the area code; but there must be three digits in the area
411 code and exchange, and four in the number (if they are present).  An error
412 will be signalled if unparsable.  All of these are unambigously parsable:
413
414   ( 415 ) 555 - 1212 x123   -> (415 555 1212 123)
415   (415)555-1212 123         -> (415 555 1212 123)
416   (1-415) 555-1212 123      -> (415 555 1212 123)
417   1 (415)-555-1212 123      -> (415 555 1212 123)
418   555-1212 123              -> (0 555 1212 123)
419   555 1212                  -> (0 555 1212)
420   415 555 1212              -> (415 555 1212)
421   1 415 555 1212            -> (415 555 1212)
422   5551212                   -> (0 555 1212)
423   4155551212                -> (415 555 1212)
424   4155551212123             -> (415 555 1212 123)
425   5551212x123               -> (0 555 1212 123)
426   1234                      -> (0 0 0 1234)
427
428 Note that \"4151212123\" is ambiguous; it could be interpreted either as
429 \"(415) 121-2123\" or as \"415-1212 x123\".
430
431 \(And uh, oh yeah, this does little if `bbdb-north-american-phone-numbers-p'
432 is nil...\)"
433
434   (cond ((if number-type
435              (eq number-type 'euro)
436            (not bbdb-north-american-phone-numbers-p))
437          (list (bbdb-string-trim string)))
438         ((string-match bbdb-phone-regexp-1 string)
439          ;; (415) 555-1212 x123
440          (list (bbdb-subint string 1) (bbdb-subint string 2)
441                (bbdb-subint string 3) (bbdb-subint string 4)))
442         ((string-match bbdb-phone-regexp-2 string)
443          ;; (415) 555-1212
444          (list (bbdb-subint string 1) (bbdb-subint string 2)
445                (bbdb-subint string 3)))
446         ((string-match bbdb-phone-regexp-3 string)
447          ;; 555-1212 x123
448          (list 0 (bbdb-subint string 1) (bbdb-subint string 2)
449                (bbdb-subint string 3)))
450         ((string-match bbdb-phone-regexp-4 string)
451          ;; 555-1212
452          (list 0 (bbdb-subint string 1) (bbdb-subint string 2)))
453         ((string-match bbdb-phone-regexp-5 string)
454          ;; x123
455          (list 0 0 0 (bbdb-subint string 1)))
456         (t (error "phone number unparsable."))))
457
458 ;;; Parsing other things
459
460 (defcustom bbdb-expand-mail-aliases t
461   "If non-nil, expand mail aliases in `bbdb-complete-name'."
462   :group 'bbdb-record-use
463   :type 'boolean)
464
465 (defcustom bbdb-check-zip-codes-p t
466   "If non-nil, require legal zip codes when entering an address.
467 The format of legal zip codes is determined by the variable
468 `bbdb-legal-zip-codes'."
469   :group 'bbdb-record-creation
470   :type 'boolean)
471
472 (defcustom bbdb-legal-zip-codes
473   '(;; empty string
474     "^$"
475     ;; Matches 1 to 6 digits.
476     "^[ \t\n]*[0-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?[ \t\n]*$"
477     ;; Matches 5 digits and 3 or 4 digits.
478     "^[ \t\n]*\\([0-9][0-9][0-9][0-9][0-9]\\)[ \t\n]*-?[ \t\n]*\\([0-9][0-9][0-9][0-9]?\\)[ \t\n]*$"
479     ;; Match zip codes for Canada, UK, etc. (result is ("LL47" "U4B")).
480     "^[ \t\n]*\\([A-Za-z0-9]+\\)[ \t\n]+\\([A-Za-z0-9]+\\)[ \t\n]*$"
481     ;; Match zip codes for continental Europe.  Examples "CH-8057"
482     ;; or "F - 83320" (result is ("CH" "8057") or ("F" "83320")).
483     ;; Support for "NL-2300RA" added at request from Carsten Dominik
484     ;; <dominik@astro.uva.nl>
485     "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+ ?[A-Z]*\\)[ \t\n]*$"
486     ;; Match zip codes from Sweden where the five digits are grouped 3+2
487     ;; at the request from Mats Lofdahl <MLofdahl@solar.stanford.edu>.
488     ;; (result is ("SE" (133 36)))
489     "^[ \t\n]*\\([A-Z]+\\)[ \t\n]*-?[ \t\n]*\\([0-9]+\\)[ \t\n]+\\([0-9]+\\)[ \t\n]*$")
490   "List of regexps that match legal zip codes.
491 Whether this is used at all depends on the variable `bbdb-check-zip-codes-p'."
492   :group 'bbdb-record-creation
493   :type '(repeat regexp))
494
495 (defun bbdb-parse-zip-string (string)
496   "Check whether STRING is a legal zip code.
497 Do this only if `bbdb-check-zip-codes-p' is non-nil."
498   (if (and bbdb-check-zip-codes-p
499            (not (memq t (mapcar (lambda (regexp)
500                                   ;; if it matches, (not (not index-of-match)) returns t
501                                   (not (not (string-match regexp string))))
502                                 bbdb-legal-zip-codes))))
503       (error "not a valid zip code.")
504     string))
505
506 (defun bbdb-read-new-record ()
507   "Prompt for and return a completely new BBDB record.
508 Doesn't insert it in to the database or update the hashtables, but does
509 ensure that there will not be name collisions."
510   (bbdb-records)                        ; make sure database is loaded
511   (if bbdb-readonly-p
512       (error "The Insidious Big Brother Database is read-only."))
513   (let (firstname lastname)
514     (bbdb-error-retry
515      (progn
516        (if current-prefix-arg
517            (setq firstname (bbdb-read-string "First Name: ")
518                  lastname (bbdb-read-string "Last Name: "))
519          (let ((names (bbdb-divide-name (bbdb-read-string "Name: "))))
520            (setq firstname (car names)
521                  lastname (nth 1 names))))
522        (if (string= firstname "") (setq firstname nil))
523        (if (string= lastname "") (setq lastname nil))
524        (if (and bbdb-no-duplicates-p
525                 (bbdb-gethash (bbdb-build-name firstname lastname)))
526            (error "%s %s is already in the database"
527                   (or firstname "") (or lastname "")))))
528     (let ((company (bbdb-read-string "Company: "))
529           (net (bbdb-split (bbdb-read-string "Network Address: ") ","))
530           (addrs
531            (let (L L-tail str addr)
532              (while (not (string=
533                           ""
534                           (setq str
535                                 (bbdb-read-string
536                                  "Address Description [RET when no more]: "
537                                  ""
538                                  (mapcar (function (lambda(x) (list x)))
539                                          (bbdb-label-completion-list
540                                           "addresses"))))))
541                (setq addr (make-vector bbdb-address-length nil))
542                (bbdb-record-edit-address addr str)
543                (if L
544                    (progn (setcdr L-tail (cons addr nil))
545                           (setq L-tail (cdr L-tail)))
546                  (setq L (cons addr nil)
547                        L-tail L)))
548              L))
549           (phones
550            (let (L L-tail str)
551              (while (not (string=
552                           ""
553                           (setq str
554                                 (bbdb-read-string
555                                  "Phone Location [RET when no more]: "
556                                  ""
557                                  (mapcar (function (lambda(x) (list x)))
558                                          (bbdb-label-completion-list
559                                           "phones"))))))
560                (let* ((phonelist
561                        (bbdb-error-retry
562                         (bbdb-parse-phone-number
563                          (read-string "Phone: "
564                                       (and (integerp bbdb-default-area-code)
565                                            (format "(%03d) "
566                                                    bbdb-default-area-code))))))
567                       (phone (apply 'vector str
568                                     (if (= 3 (length phonelist))
569                                         (nconc phonelist '(0))
570                                       phonelist))))
571                  (if L
572                      (progn (setcdr L-tail (cons phone nil))
573                             (setq L-tail (cdr L-tail)))
574                           (setq L (cons phone nil)
575                                 L-tail L))))
576                     L))
577           (notes (bbdb-read-string "Additional Comments: ")))
578       (if (string= company "") (setq company nil))
579       (if (string= notes "") (setq notes nil))
580       (let ((record
581              (vector firstname lastname nil company phones addrs net notes
582                      (make-vector bbdb-cache-length nil))))
583         record))))
584
585 ;;;###autoload
586 (defun bbdb-create (record)
587   "Add a new entry to the bbdb database ; prompts for all relevant info
588 using the echo area, inserts the new record in the db, sorted alphabetically,
589 and offers to save the db file.  DO NOT call this from a program.  Call
590 bbdb-create-internal instead."
591   (interactive (list (bbdb-read-new-record)))
592   (bbdb-invoke-hook 'bbdb-create-hook record)
593   (bbdb-change-record record t)
594   (bbdb-display-records (list record)))
595
596
597 (defmacro bbdb-check-type (place predicate)
598   (list 'while (list 'not (list predicate place))
599         (nconc (cond ((eq (car-safe place) 'aref)
600                       (list 'aset (nth 1 place) (nth 2 place)))
601                      ((eq (car-safe place) 'car)
602                       (list 'setcar (nth 1 place)))
603                      ((eq (car-safe place) 'cdr)
604                       (list 'setcdr (nth 1 place)))
605                      (t (list 'setq place)))
606                (list
607                 (list 'signal ''wrong-type-argument
608                       (list 'list (list 'quote predicate) place))))))
609
610 (defun bbdb-create-internal (name company net addrs phones notes)
611   "Adds a record to the database; this function does a fair amount of
612 error-checking on the passed in values, so it's safe to call this from
613 other programs.
614
615 NAME is a string, the name of the person to add.  An error is signalled
616 if that name is already in use and `bbdb-no-duplicates-p' is t.
617 COMPANY is a string or nil.
618 NET is a comma-separated list of email addresses, or a list of strings.
619 An error is signalled if that name is already in use.
620 ADDRS is a list of address objects.  An address is a vector of the form
621   [\"location\" (\"line1\" \"line2\" ... ) \"City\" \"State\" \"Zip\" \"Country\"].
622 PHONES is a list of phone-number objects.  A phone-number is a vector of
623 the form
624   [\"location\" areacode prefix suffix extension-or-nil]
625 or
626   [\"location\" \"phone-number\"]
627 NOTES is a string, or an alist associating symbols with strings."
628   (let (firstname lastname aka)
629     (while (and (progn
630                   (setq name      (and name (bbdb-divide-name name))
631                         firstname (car name)
632                         lastname  (nth 1 name))
633                   (bbdb-gethash (bbdb-build-name firstname lastname)))
634                 bbdb-no-duplicates-p)
635       (setq name (signal 'error
636                          (list (format "%s %s is already in the database"
637                                        (or firstname "") (or lastname ""))))))
638     (and company (bbdb-check-type company stringp))
639     (if (stringp net)
640         (setq net (bbdb-split net ",")))
641     (if bbdb-no-duplicates-p
642         (let ((rest net))
643           (while rest
644             (while (bbdb-gethash (downcase (car rest)))
645               (setcar rest
646                       (signal 'error (list (format
647                                             "%s is already in the database"
648                                             (car rest))))))
649             (setq rest (cdr rest)))))
650     (setq addrs
651           (mapcar
652            (lambda (addr)
653              (while (or (not (vectorp addr))
654                         (/= (length addr) bbdb-address-length))
655                (setq addr (signal 'wrong-type-argument (list 'vectorp addr))))
656              (bbdb-check-type (aref addr 0) stringp) ;;; XXX use bbdb-addresses
657              (bbdb-check-type (aref addr 1) listp)
658              (bbdb-check-type (aref addr 2) stringp)
659              (bbdb-check-type (aref addr 3) stringp)
660              (bbdb-check-type (aref addr 4) stringp)
661              (bbdb-check-type (aref addr 5) stringp)
662              addr)
663            addrs))
664     (setq phones
665           (mapcar
666            (lambda (phone)
667              (while (or (not (vectorp phone))
668                         (and (/= (length phone) 2)
669                              (/= (length phone) bbdb-phone-length)))
670                (setq phone
671                      (signal 'wrong-type-argument (list 'vectorp phone))))
672              (bbdb-check-type (aref phone 0) stringp)
673              (if (= 2 (length phone))
674                  (bbdb-check-type (aref phone 1) stringp)
675                (bbdb-check-type (aref phone 1) integerp)
676                (bbdb-check-type (aref phone 2) integerp)
677                (bbdb-check-type (aref phone 3) integerp)
678                (and (aref phone 4) (bbdb-check-type (aref phone 4) integerp))
679                (if (eq 0 (aref phone 4)) (aset phone 4 nil)))
680              phone)
681            phones))
682     (or (stringp notes)
683         (setq notes
684               (mapcar (lambda (note)
685                         (bbdb-check-type note consp)
686                         (bbdb-check-type (car note) symbolp)
687                         (if (consp (cdr note))
688                             (setq note (cons (car note) (car (cdr note)))))
689                         (bbdb-check-type (cdr note) stringp)
690                         note)
691                       notes)))
692     (let ((record
693            (vector firstname lastname aka company phones addrs net notes
694                    (make-vector bbdb-cache-length nil))))
695       (bbdb-invoke-hook 'bbdb-create-hook record)
696       (bbdb-change-record record t)
697       record)))
698
699
700 ;;; bbdb-mode stuff
701
702 (defun bbdb-current-record (&optional planning-on-modifying)
703   "Returns the record which the point is point at.  In linear time, man..."
704   (if (and planning-on-modifying bbdb-readonly-p)
705       (error "The Insidious Big Brother Database is read-only."))
706   (if (not (equal bbdb-buffer-name (buffer-name (current-buffer))))
707       (error "this command only works while in the \"%s\" buffer."
708              bbdb-buffer-name))
709   (let ((p (point))
710         (rest bbdb-records)
711         (rec nil))
712     (while (and (cdr rest) (not rec))
713       (if (> (nth 2 (car (cdr rest))) p)
714           (setq rec (car (car rest))))
715       (setq rest (cdr rest)))
716     (or rec (car (car rest)))))
717
718
719 ;; yow, are we object oriented yet?
720 (defun bbdb-record-get-field-internal (record field)
721   (cond ((eq field 'name)   (bbdb-record-name record))
722         ((eq field 'net)    (bbdb-record-net record))
723         ((eq field 'aka)    (bbdb-record-aka record))
724         ((eq field 'phone)  (bbdb-record-phones record))
725         ((eq field 'address)    (bbdb-record-addresses record))
726         ((eq field 'property)   (bbdb-record-raw-notes record))
727         (t (error "doubleplus ungood: unknown field type %s" field))))
728
729 (defun bbdb-record-store-field-internal (record field value)
730   (cond ((eq field 'name)   (error "doesn't work on names"))
731         ((eq field 'net)    (bbdb-record-set-net record value))
732         ((eq field 'aka)    (bbdb-record-set-aka record value))
733         ((eq field 'phone)  (bbdb-record-set-phones record value))
734         ((eq field 'address)    (bbdb-record-set-addresses record value))
735         ((eq field 'property)   (bbdb-record-set-raw-notes record value))
736         (t (error "doubleplus ungood: unknown field type %s" field))))
737
738 (defun bbdb-record-edit-field-internal (record field &optional which location)
739   (cond ((eq field 'name)     (bbdb-record-edit-name record))
740         ((eq field 'company)  (bbdb-record-edit-company record))
741         ((eq field 'net)      (bbdb-record-edit-net record))
742         ((eq field 'aka)      (bbdb-record-edit-aka record))
743         ((eq field 'phone)    (bbdb-record-edit-phone which location))
744         ((eq field 'address)  (bbdb-record-edit-address which location))
745         ((eq field 'property) (bbdb-record-edit-property record (car which)))
746         (t (error "doubleplus ungood: unknown field type %s" field))))
747
748
749 (defun bbdb-current-field (&optional planning-on-modifying)
750   (or (bbdb-current-record planning-on-modifying)
751       (error "unperson"))
752   (delete 'field-name (get-text-property (point) 'bbdb-field)))
753
754 ;;;###autoload
755 (defun bbdb-apply-next-command-to-all-records ()
756   "Typing \\<bbdb-mode-map>\\[bbdb-apply-next-command-to-all-records] \
757 in the *BBDB* buffer makes the next command operate on all
758 of the records currently displayed.  \(Note that this only works for
759 certain commands.\)"
760   (interactive)
761   (message (substitute-command-keys
762             "\\<bbdb-mode-map>\\[bbdb-apply-next-command-to-all-records] - "))
763   (setq prefix-arg current-prefix-arg
764         last-command this-command)
765   nil)
766
767 (defmacro bbdb-do-all-records-p ()
768   "Whether the last command was `bbdb-apply-next-command-to-all-records'."
769   '(eq last-command 'bbdb-apply-next-command-to-all-records))
770
771
772 (defvar bbdb-append-records nil)
773
774 ;;;###autoload
775 (defun bbdb-append-records-p ()
776   (cond ((eq t bbdb-append-records))
777         ((numberp bbdb-append-records)
778          (setq bbdb-append-records
779                (1- bbdb-append-records))
780          (when (= 0 bbdb-append-records)
781            (when (not bbdb-silent-running)
782              (message "No further search results will be appended.")
783              (sit-for 2))
784            (setq bbdb-append-records nil))
785          t)
786         (bbdb-append-records
787          (setq bbdb-append-records nil)
788          t)
789         (t nil)))
790
791 ;;;###autoload
792 (defun bbdb-append-records (arg)
793   "Typing \\<bbdb-mode-map>\\[bbdb-append-records] \
794 in the *BBDB* buffer makes the next search/display command to append
795 new records to those in the *BBDB* buffer.
796
797 With an prefix arg (C-u) toggle between always append and no append.
798 With an prefix arg that is a positive number append will be enabled for that
799 many times.
800 With any other argument append will be enabled once."
801   (interactive "P")
802   (message (substitute-command-keys
803             "\\<bbdb-mode-map>\\[bbdb-append-records] - "))
804   (setq bbdb-append-records
805         (cond ((and arg (listp arg))
806                (if (not bbdb-silent-running)
807                    (if (not bbdb-append-records)
808                        (message "Always append records.")
809                      (message "Do not append records.")))
810                (not bbdb-append-records))
811               ((and (numberp arg) (< 1 arg))
812                (if (not bbdb-silent-running)
813                    (message "Append records for the next %d times." arg))
814                arg)
815               (t 'once))))
816
817 ;;;###autoload
818 (defun bbdb-insert-new-field (record name contents)
819   "Add a new field to the current record; the field type and contents
820 are prompted for if not supplied.
821
822 If you are inserting a new phone-number field, you can control whether
823 it is a north american or european phone number by providing a prefix
824 argument.  A prefix arg of ^U means it's to be a euronumber, and any
825 other prefix arg means it's to be a a structured north american number.
826 Otherwise, which style is used is controlled by the variable
827 `bbdb-north-american-phone-numbers-p'.
828
829 If you are inserting a new net address, you can have BBDB append a
830 default domain to any net address that does not contain one.  Set
831 `bbdb-default-domain' to a string such as \"mycompany.com\" (or,
832 depending on your environment, (getenv \"DOMAINNAME\")), and
833 \"@mycompany.com\" will be appended to an address that is entered as
834 just a username.  A prefix arg of ^U (or a `bbdb-default-domain'
835 value of \"\", the default) means do not alter the address."
836   (interactive (let ((record (or (bbdb-current-record t)
837                                  (error "current record unexists!")))
838                      (name "")
839                      (completion-ignore-case t))
840                  (while (string= name "")
841                    (setq name
842                          (downcase
843                           (completing-read "Insert Field: "
844                                            (append '(("phone") ("address")
845                                                      ("net") ("AKA") ("notes"))
846                                                    (bbdb-propnames))
847                                            nil
848                                            nil ; used to be t
849                                            nil))))
850                  (setq name (intern name))
851                  (list record name (bbdb-prompt-for-new-field-value name))))
852   (if (null contents)
853       (setq contents (bbdb-prompt-for-new-field-value name)))
854
855   (cond ((eq name 'phone)
856          (bbdb-record-set-phones record
857                                  (nconc (bbdb-record-phones record)
858                                         (list contents))))
859         ((eq name 'address)
860          (bbdb-record-set-addresses record
861                                     (nconc (bbdb-record-addresses record)
862                                            (list contents))))
863         ((eq name 'net)
864          (if (bbdb-record-net record)
865              (error "There already are net addresses!"))
866          (if (stringp contents)
867              (setq contents (bbdb-split contents ",")))
868          ;; first detect any conflicts....
869          (if bbdb-no-duplicates-p
870              (let ((nets contents))
871                (while nets
872                  (let ((old (bbdb-gethash (downcase (car nets)))))
873                    (if (and old (not (eq old record)))
874                        (error "net address \"%s\" is used by \"%s\""
875                               (car nets)
876                               (or (bbdb-record-name old)
877                                   (car (bbdb-record-net old))))))
878                  (setq nets (cdr nets)))))
879          ;; then store.
880          (let ((nets contents))
881            (while nets
882              (bbdb-puthash (downcase (car nets)) record)
883              (setq nets (cdr nets))))
884          (bbdb-record-set-net record contents))
885         ((eq name 'aka)
886         (if (bbdb-record-aka record)
887             (error "there already are alternate names!"))
888         (if (stringp contents)
889             (setq contents (bbdb-split contents ";")))
890         ;; first detect any conflicts....
891         (if bbdb-no-duplicates-p
892             (let ((aka contents))
893               (while aka
894                 (let ((old (bbdb-gethash (downcase (car aka)))))
895                   (if (and old (not (eq old record)))
896                       (error "alternate name \"%s\" is used by \"%s\""
897                              (car aka)
898                              (or (bbdb-record-name old)
899                                  (car (bbdb-record-net old))))))
900                 (setq aka (cdr aka)))))
901         ;; then store.
902         (let ((aka contents))
903           (while aka
904             (bbdb-puthash (downcase (car aka)) record)
905             (setq aka (cdr aka))))
906         (bbdb-record-set-aka record contents))
907         ((eq name 'notes)
908         (if (bbdb-record-notes record) (error "there already are notes!"))
909         (bbdb-record-set-notes record contents))
910         ((assoc (symbol-name name) (bbdb-propnames))
911         (if (and (consp (bbdb-record-raw-notes record))
912                  (assq name (bbdb-record-raw-notes record)))
913             (error "there is already a \"%s\" note!" name))
914         (bbdb-record-putprop record name contents))
915         (t (error "doubleplus ungood: unknow how to set slot %s" name)))
916   (bbdb-change-record record nil)
917 ;    (bbdb-offer-save)
918   (let ((bbdb-display-layout nil))
919     (bbdb-redisplay-one-record record)))
920
921 (defun bbdb-prompt-for-new-field-value (name)
922   (cond ((eq name 'net)
923          (let
924              ((n (bbdb-read-string "Net: ")))
925            (if (string-match "^mailto:" n)
926                (setq n (substring n (match-end 0))))
927            (if (or (eq nil bbdb-default-domain)
928                    current-prefix-arg (string-match "[@%!]" n))
929                n
930              (concat n "@" bbdb-default-domain))))
931         ((eq name 'aka) (bbdb-read-string "Alternate Names: "))
932         ((eq name 'phone)
933          (let ((p (make-vector
934                    (if (if current-prefix-arg
935                            (numberp current-prefix-arg)
936                          bbdb-north-american-phone-numbers-p)
937                        bbdb-phone-length
938                      2)
939                    0)))
940            (aset p 0 nil)
941            (aset p 1
942                  (if (= bbdb-phone-length (length p))
943                      (if (integerp bbdb-default-area-code)
944                          bbdb-default-area-code
945                        0)
946                    nil))
947            (bbdb-record-edit-phone p)
948            p))
949         ((eq name 'address)
950          (let ((a (make-vector bbdb-address-length nil)))
951            (bbdb-record-edit-address a)
952            a))
953         ((eq name 'notes) (bbdb-read-string "Notes: "))
954         ((assoc (symbol-name name) (bbdb-propnames))
955          (bbdb-read-string (format "%s: " name)))
956         (t
957          (if (bbdb-y-or-n-p
958               (format "\"%s\" is an unknown field name.  Define it? " name))
959              (bbdb-set-propnames
960               (append (bbdb-propnames) (list (list (symbol-name name)))))
961            (error "unknown field \"%s\"" name))
962          (bbdb-read-string (format "%s: " name)))))
963
964 (defun bbdb-add-new-field (name)
965   "Programmatically add a new field called NAME. Returns the list of propnames."
966   ;; check that we don't have one already; if we do, return quietly.
967   (if (assoc (symbol-name name) (append '(("phone") ("address") ("net")
968                                           ("AKA") ("notes"))
969                                         (bbdb-propnames)))
970       bbdb-propnames
971     (bbdb-set-propnames (append (bbdb-propnames)
972                                 (list (list (symbol-name name)))))))
973
974 ;;;###autoload
975 (defun bbdb-edit-current-field ()
976   "Edit the contents of the Insidious Big Brother Database field displayed on
977 the current line (this is only meaningful in the \"*BBDB*\" buffer.)   If the
978 cursor is in the middle of a multi-line field, such as an address or comments
979 section, then the entire field is edited, not just the current line."
980   (interactive)
981   ;; when at the end of the line take care of it
982   (if (and (eolp) (not (bobp)) (not (bbdb-current-field t)))
983       (backward-char 1))
984
985   (let* ((record (bbdb-current-record t))
986          (field (bbdb-current-field t))
987          need-to-sort)
988     (or field (error "on an unfield"))
989     (setq need-to-sort
990           (apply 'bbdb-record-edit-field-internal record field))
991     (bbdb-change-record record need-to-sort)
992     (bbdb-redisplay-one-record record)
993     ;; (bbdb-offer-save)
994     (if (and (eq 'property (car field))
995              (or (eq 'mail-alias (caadr field))
996                  (eq 'net (caadr field))))
997         (setq bbdb-define-all-aliases-needs-rebuilt 'edit))
998     ))
999
1000 (defun bbdb-record-edit-name (bbdb-record)
1001   (let (fn ln co need-to-sort new-name old-name)
1002     (bbdb-error-retry
1003      (progn
1004        (if current-prefix-arg
1005            (setq fn (bbdb-read-string "First Name: "
1006                                       (bbdb-record-firstname bbdb-record))
1007                  ln (bbdb-read-string "Last Name: "
1008                                       (bbdb-record-lastname bbdb-record)))
1009          (let ((names (bbdb-divide-name
1010                        (bbdb-read-string "Name: "
1011                                          (bbdb-record-name bbdb-record)))))
1012            (setq fn (car names)
1013                  ln (nth 1 names))))
1014        (setq need-to-sort
1015              (or (not (string= fn
1016                                (or (bbdb-record-firstname bbdb-record) "")))
1017                  (not (string= ln
1018                                (or (bbdb-record-lastname bbdb-record) "")))))
1019        (if (string= "" fn) (setq fn nil))
1020        (if (string= "" ln) (setq ln nil))
1021        ;; check for collisions
1022        (setq new-name (if (and fn ln) (concat fn " " ln)
1023                         (or fn ln))
1024              old-name (bbdb-record-name bbdb-record))
1025        (if (and bbdb-no-duplicates-p
1026                 new-name
1027                 (not (and old-name (string= (downcase new-name)
1028                                             (downcase old-name))))
1029                 (bbdb-gethash (downcase new-name)))
1030            (error "%s is already in the database!" new-name))))
1031     (setq co (bbdb-read-string "Company: "
1032                                (bbdb-record-company bbdb-record)))
1033     (if (string= "" co) (setq co nil))
1034     (setq need-to-sort
1035           (or need-to-sort
1036               (not (equal (if co (downcase co) "")
1037                           (downcase (or (bbdb-record-company bbdb-record)
1038                                         ""))))))
1039     ;;
1040     ;; delete the old hash entry
1041     (let ((name    (bbdb-record-name    bbdb-record))
1042           (lastname    (bbdb-record-lastname    bbdb-record))
1043           (company (bbdb-record-company bbdb-record)))
1044       (if (> (length name) 0)
1045           (bbdb-remhash (downcase name) bbdb-record))
1046       (if (> (length lastname) 0)
1047           (bbdb-remhash (downcase lastname) bbdb-record))
1048       (if (> (length company) 0)
1049           (bbdb-remhash (downcase company) bbdb-record)))
1050     (bbdb-record-set-namecache bbdb-record nil)
1051     (bbdb-record-set-firstname bbdb-record fn)
1052     (bbdb-record-set-lastname bbdb-record ln)
1053     (bbdb-record-set-company bbdb-record co)
1054     ;; add a new hash entry
1055     (and (or fn ln)
1056          (bbdb-puthash (downcase (bbdb-record-name bbdb-record))
1057                        bbdb-record))
1058     need-to-sort))
1059
1060 (defun bbdb-record-edit-company (bbdb-record)
1061   (let ((co (bbdb-read-string "Company: " (bbdb-record-company bbdb-record)))
1062         need-to-sort)
1063
1064     (if (string= "" co) (setq co nil))
1065     (setq need-to-sort
1066           (or need-to-sort
1067               (not (equal (if co (downcase co) "")
1068                           (downcase (or (bbdb-record-company bbdb-record)
1069                                         ""))))))
1070
1071     ;; delete the old hash entry
1072     (let ((company (bbdb-record-company bbdb-record)))
1073       (if (> (length company) 0)
1074           (bbdb-remhash (downcase company) bbdb-record)))
1075
1076     (bbdb-record-set-company bbdb-record co)
1077     ;; add a new hash entry
1078     (bbdb-puthash (downcase (bbdb-record-name bbdb-record))
1079                   bbdb-record)
1080
1081     need-to-sort))
1082
1083 (defun bbdb-address-edit-default (addr)
1084   "Function to use for address editing.
1085 The sub-fields are queried using the default order and using the
1086 default names.  Set `bbdb-address-editing-function' to an alternate
1087 address editing function if you don't like this function.  It is
1088 mostly used for US style addresses.
1089
1090 The sub-fields and the prompts used are:
1091 Street, line n:  (nth n street)
1092 City:            city
1093 State:           state
1094 Zip Code:        zip
1095 Country:         country"
1096   (let* ((str (let ((l) (s) (n 0))
1097                 (while (not (string= "" (setq s (bbdb-read-string
1098                                                  (format "Street, line %d: " (+ 1 n))
1099                                                  (nth n (bbdb-address-streets addr))))))
1100                   (setq l (append l (list s)))
1101                   (setq n (1+ n)))
1102                 l))
1103          (cty (bbdb-read-string "City: " (bbdb-address-city addr)))
1104          (ste (bbdb-read-string "State: " (bbdb-address-state addr)))
1105          (zip (bbdb-error-retry
1106                (bbdb-parse-zip-string
1107                 (bbdb-read-string "Zip Code: " (bbdb-address-zip-string addr)))))
1108          (country (bbdb-read-string "Country: " (or (bbdb-address-country addr)
1109                                                     bbdb-default-country))))
1110     (bbdb-address-set-streets addr str)
1111     (bbdb-address-set-city addr cty)
1112     (bbdb-address-set-state addr ste)
1113     (bbdb-address-set-zip addr zip)
1114     (if (string= "" (concat cty ste zip country (mapconcat 'identity str "")))
1115         ;; user didn't enter anything. this causes a display bug. this
1116         ;; is a temporary fix. Ideally, we'd simply discard the entire
1117         ;; address entry, but that's going to require bigger hacking.
1118         (bbdb-address-set-country addr "Emacs")
1119       (bbdb-address-set-country addr country))
1120     nil))
1121
1122 (defun bbdb-address-edit-continental (addr)
1123   "Function to use for address editing.
1124 The sub-fields are queried using the default order and using the
1125 default names.  Set `bbdb-address-editing-function' to an alternate
1126 address editing function if you don't like this function.  It is
1127 mostly used for US style addresses.
1128
1129 The sub-fields and the prompts used are:
1130 Street, line n:  (nth n street)
1131 City:            city
1132 State:           state
1133 Zip Code:        zip
1134 Country:         country"
1135   (let* ((str (let ((l) (s) (n 0))
1136                 (while (not (string= "" (setq s (bbdb-read-string
1137                                                  (format "Street, line %d: " (+ 1 n))
1138                                                  (nth n (bbdb-address-streets addr))))))
1139                   (setq l (append l (list s)))
1140                   (setq n (1+ n)))
1141                 l))
1142          (zip (bbdb-error-retry
1143                (bbdb-parse-zip-string
1144                 (bbdb-read-string "Zip Code: " (bbdb-address-zip-string addr)))))
1145          (cty (bbdb-read-string "City: " (bbdb-address-city addr)))
1146          (ste "")
1147          (country (bbdb-read-string "Country: " (or (bbdb-address-country addr)
1148                                                     bbdb-default-country))))
1149     (bbdb-address-set-streets addr str)
1150     (bbdb-address-set-city addr cty)
1151     (bbdb-address-set-state addr ste)
1152     (bbdb-address-set-zip addr zip)
1153     (if (string= "" (concat cty ste zip country (mapconcat 'identity str "")))
1154         ;; user didn't enter anything. this causes a display bug. this
1155         ;; is a temporary fix. Ideally, we'd simply discard the entire
1156         ;; address entry, but that's going to require bigger hacking.
1157         (bbdb-address-set-country addr "Emacs")
1158       (bbdb-address-set-country addr country))
1159     nil))
1160
1161 (defcustom bbdb-address-editing-function 'bbdb-address-edit-default
1162   "Function to use for address editing.
1163 The function must accept a BBDB address as parameter and allow the
1164 user to edit it.  This variable is called from `bbdb-record-edit-address'.
1165 The default value is the symbol `bbdb-address-edit-default'."
1166   :group 'bbdb-record-creation
1167   :type 'function)
1168
1169 (defun bbdb-record-edit-address (addr &optional location)
1170   "Edit an address ADDR.
1171 If optional parameter LOCATION is nil, edit the location sub-field
1172 of the address as well.  The address itself is edited using the editing
1173 function in `bbdb-address-editing-function'."
1174   (let ((loc
1175          (or location (bbdb-read-string "Location: "
1176                                         (or (bbdb-address-location addr)
1177                                             (bbdb-label-completion-default
1178                                              "addresses"))
1179                                         (mapcar (function (lambda(x) (list x)))
1180                                                 (bbdb-label-completion-list
1181                                                  "addresses"))))))
1182     (bbdb-address-set-location addr loc))
1183   (if current-prefix-arg
1184       (bbdb-address-edit-default addr)
1185     (funcall bbdb-address-editing-function addr)))
1186
1187 (defun bbdb-record-edit-phone (phone-number &optional location)
1188   (let ((newl (or location
1189                   (bbdb-read-string "Location: "
1190                                     (or (bbdb-phone-location phone-number)
1191                                         (bbdb-label-completion-default
1192                                          "phones"))
1193                                     (mapcar (function (lambda(x) (list x)))
1194                                             (bbdb-label-completion-list
1195                                              "phones")))))
1196         (newp (let ((bbdb-north-american-phone-numbers-p
1197                      (= (length phone-number) bbdb-phone-length)))
1198                 (bbdb-error-retry
1199                  (bbdb-parse-phone-number
1200                   (read-string "Phone: " (bbdb-phone-string phone-number)))))))
1201     (bbdb-phone-set-location phone-number newl)
1202     (bbdb-phone-set-area phone-number (nth 0 newp)) ; euronumbers too.
1203     (if (= (length phone-number) 2)
1204         nil
1205       (bbdb-phone-set-exchange phone-number (nth 1 newp))
1206       (bbdb-phone-set-suffix phone-number (nth 2 newp))
1207       (bbdb-phone-set-extension phone-number (or (nth 3 newp) 0))))
1208   nil)
1209
1210 (defun bbdb-record-edit-net (bbdb-record)
1211   (let ((str (bbdb-read-string "Net: "
1212                                (mapconcat (function identity)
1213                                           (bbdb-record-net bbdb-record)
1214                                           ", "))))
1215     (let ((oldnets (bbdb-record-net bbdb-record))
1216           (newnets (bbdb-split str ",")))
1217       ;; first check for any conflicts...
1218       (if bbdb-no-duplicates-p
1219           (let ((rest newnets))
1220             (while rest
1221               (let ((old (delete bbdb-record (bbdb-gethash (downcase (car rest))))))
1222                 (if old
1223                     (error "net address \"%s\" is used by \"%s\""
1224                            (car rest) (mapconcat (lambda (r) (bbdb-record-name r))
1225                                                  old ", "))))
1226               (setq rest (cdr rest)))))
1227       ;; then update.
1228       (let ((rest oldnets))
1229         (while rest
1230           (bbdb-remhash (downcase (car rest)) bbdb-record)
1231           (setq rest (cdr rest))))
1232       (let ((nets newnets))
1233         (while nets
1234           (bbdb-puthash (downcase (car nets)) bbdb-record)
1235           (setq nets (cdr nets))))
1236       (bbdb-record-set-net bbdb-record newnets)))
1237   nil)
1238
1239 (defun bbdb-record-edit-aka (bbdb-record)
1240   (let ((str (bbdb-read-string "AKA: "
1241                                (mapconcat (function identity)
1242                                           (bbdb-record-aka bbdb-record)
1243                                           "; "))))
1244     (let ((oldaka (bbdb-record-aka bbdb-record))
1245           (newaka (bbdb-split str ";")))
1246       ;; first check for any conflicts...
1247       (if bbdb-no-duplicates-p
1248           (let ((rest newaka))
1249             (while rest
1250               (let ((old (bbdb-gethash (downcase (car rest)))))
1251                 (if (and old (not (eq old bbdb-record)))
1252                     (error "alternate name address \"%s\" is used by \"%s\""
1253                            (car rest) (bbdb-record-name old))))
1254               (setq rest (cdr rest)))))
1255       ;; then update.
1256       (let ((rest oldaka))
1257         (while rest
1258           (bbdb-remhash (downcase (car rest)) bbdb-record)
1259           (setq rest (cdr rest))))
1260       (let ((aka newaka))
1261         (while aka
1262           (bbdb-puthash (downcase (car aka)) bbdb-record)
1263           (setq aka (cdr aka))))
1264       (bbdb-record-set-aka bbdb-record newaka)))
1265   nil)
1266
1267 ;;;###autoload
1268 (defun bbdb-record-edit-notes (bbdb-record &optional regrind)
1269   (interactive (list (bbdb-current-record t) t))
1270   (let ((notes (bbdb-read-string "Notes: " (bbdb-record-notes bbdb-record))))
1271     (bbdb-record-set-notes bbdb-record (if (string= "" notes) nil notes)))
1272   (if regrind
1273       (save-excursion
1274         (set-buffer bbdb-buffer-name)
1275         (bbdb-redisplay-one-record bbdb-record)))
1276   nil)
1277
1278 ;;;###autoload
1279 (defun bbdb-record-edit-property (bbdb-record &optional prop regrind)
1280   (interactive (list (bbdb-current-record t) nil t))
1281   (let* ((propnames (bbdb-propnames))
1282          (propname (if prop (symbol-name prop)
1283                      (completing-read
1284                       (format "Edit property of %s: "
1285                               (bbdb-record-name bbdb-record))
1286                       (cons '("notes") propnames))))
1287          (propsym (or prop (if (equal "" propname) 'notes (intern propname))))
1288          (string (bbdb-read-string (format "%s: " propname)
1289                                    (bbdb-record-getprop bbdb-record propsym))))
1290     (bbdb-record-putprop bbdb-record propsym
1291                          (if (string= "" string) nil string)))
1292   (if regrind
1293       (save-excursion
1294         (set-buffer bbdb-buffer-name)
1295         (bbdb-redisplay-one-record bbdb-record)))
1296   nil)
1297
1298
1299 (defsubst bbdb-field-equal (x y)
1300   (if (and (consp x) (consp y))
1301       (and (eq (car x) (car y))
1302            (eq (car (cdr x)) (car (cdr y)))
1303            (eq (car (cdr (cdr x))) (car (cdr (cdr y)))))
1304     (eq x y)))
1305
1306 (defun bbdb-next-field (&optional count planning-on-modifying)
1307   (or count (setq count 1))
1308   (beginning-of-line)
1309   (let* ((record (bbdb-current-record planning-on-modifying))
1310          (field (bbdb-current-field planning-on-modifying))
1311          (next-record record)
1312          (next-field field)
1313          (signum (if (< count 0) -1 1))
1314          (i 0))
1315     (if (< count 0) (setq count (- count)))
1316     (if field
1317         (while (and next-field (< i count))
1318           (while (bbdb-field-equal next-field field)
1319             (forward-line signum)
1320             (setq next-record (bbdb-current-record planning-on-modifying)
1321                   next-field (bbdb-current-field planning-on-modifying))
1322             (or (eq next-record record)
1323                 (setq next-field nil)))
1324           (setq i (1+ i))
1325           (setq field next-field)))
1326     next-field))
1327
1328 ;;;###autoload
1329 (defun bbdb-transpose-fields (&optional arg)
1330   "This is like the `transpose-lines' command, but it is for BBDB fields.
1331 If the cursor is on a field of a BBDB record, that field and the previous
1332 field will be transposed.
1333
1334 With argument ARG, takes previous line and moves it past ARG fields.
1335 With argument 0, interchanges field point is in with field mark is in.
1336
1337 Both fields must be in the same record, and must be of the same basic type
1338 \(that is, you can use this command to change the order in which phone-number
1339 fields are listed, but you can't use it to make an address appear before a
1340 phone number; the order of field types is fixed.\)"
1341   (interactive "p")
1342   (let ((record (bbdb-current-record t))
1343         moving-field position-after position-before
1344         swap-p type list)
1345     (if (/= arg 0)
1346         (setq moving-field (or (bbdb-next-field -1 t)
1347                                (error "no previous field"))
1348               position-after (bbdb-next-field arg t)
1349               position-before (bbdb-next-field (if (< arg 0) -1 1) t))
1350       ;; if arg is 0, swap fields at point and mark
1351       (setq swap-p t)
1352       (setq position-after (bbdb-current-field))
1353       (save-excursion
1354         (goto-char (mark))
1355         (setq moving-field (bbdb-current-field))
1356         (or (eq record (bbdb-current-record)) (error "not in the same record"))))
1357     (if (< arg 0)
1358         (let ((x position-after))
1359           (setq position-after position-before
1360                 position-before x)
1361           (forward-line 2)))
1362     (setq type (car moving-field))
1363     (or position-after position-before
1364         (error "that would be out of the record!"))
1365     (or (eq type (car position-after))
1366         (eq type (car position-before))
1367         (error "can't transpose fields of different types (%s and %s)"
1368                type (if (eq type (car position-after))
1369                         (car position-before) (car position-after))))
1370     (or (eq type (car position-after)) (setq position-after nil))
1371     (or (eq type (car position-before)) (setq position-before nil))
1372     (setq moving-field (nth 1 moving-field)
1373           position-after (nth 1 position-after)
1374           position-before (nth 1 position-before))
1375     (cond ((memq type '(name aka net))
1376            (error "there is only one %s field, so you can't transpose it"
1377                   type))
1378           ((memq type '(phone address property))
1379            (setq list (bbdb-record-get-field-internal record type)))
1380           (t (error "doubleplus ungood: unknown field %s" type)))
1381     (if swap-p
1382         (let ((rest list))
1383           (while rest
1384             (cond ((eq (car rest) moving-field) (setcar rest position-after))
1385                   ((eq (car rest) position-after) (setcar rest moving-field)))
1386             (setq rest (cdr rest))))
1387       (if (eq position-before (car list))
1388           (setq list (cons moving-field (delq moving-field list)))
1389         (let ((rest list))
1390           (while (and rest (not (eq position-after (car rest))))
1391             (setq rest (cdr rest)))
1392           (or rest (error "doubleplus ungood: couldn't reorder list"))
1393           (let ((inhibit-quit t))
1394             (setq list (delq moving-field list))
1395             (setcdr rest (cons moving-field (cdr rest)))))))
1396     (bbdb-record-store-field-internal record type list)
1397     (bbdb-change-record record nil)
1398     (bbdb-redisplay-one-record record)))
1399
1400
1401 ;;;###autoload
1402 (defun bbdb-delete-current-field-or-record (&optional records noprompt)
1403   "Delete the line which the cursor is on; actually, delete the field which
1404 that line represents from the database.  If the cursor is on the first line
1405 of a database entry (the name/company line) then the entire entry will be
1406 deleted."
1407   (interactive (list (if (bbdb-do-all-records-p)
1408                          (mapcar 'car bbdb-records)
1409                        (list (bbdb-current-record)))
1410                      current-prefix-arg))
1411   (let* ((field (bbdb-current-field t))
1412          (type (car field))
1413          record
1414          (name (cond ((null field) (error "on an unfield"))
1415                      ((eq type 'property) (symbol-name (car (nth 1 field))))
1416                      (t (symbol-name type)))))
1417     (while records
1418       (setq record (car records))
1419       (if (eq type 'name)
1420           (bbdb-delete-current-record record noprompt)
1421         (if (not (or noprompt
1422                      (bbdb-y-or-n-p (format "delete this %s field (of %s)? "
1423                                             name
1424                                             (bbdb-record-name record)))))
1425             nil
1426           (cond ((memq type '(phone address))
1427                  (bbdb-record-store-field-internal
1428                   record type
1429                   (delq (nth 1 field)
1430                         (bbdb-record-get-field-internal record type))))
1431                 ((memq type '(net aka))
1432                  (let ((rest (bbdb-record-get-field-internal record type)))
1433                    (while rest
1434                      (bbdb-remhash (downcase (car rest)) record)
1435                      (setq rest (cdr rest))))
1436                  (bbdb-record-store-field-internal record type nil))
1437                 ((eq type 'property)
1438                  (bbdb-record-putprop record (car (nth 1 field)) nil))
1439                 (t (error "doubleplus ungood: unknown field type")))
1440           (bbdb-change-record record nil)
1441           (bbdb-redisplay-one-record record)))
1442       (setq records (cdr records)))))
1443
1444 ;;;###autoload
1445 (defun bbdb-delete-current-record (recs &optional noprompt)
1446   "Delete the entire bbdb database entry which the cursor is within.
1447 Pressing \\<bbdb-mode-map>\\[bbdb-apply-next-command-to-all-records] will
1448 delete all records listed in the BBDB buffer."
1449   (interactive (list (if (bbdb-do-all-records-p)
1450                          (mapcar 'car bbdb-records)
1451                        (list (bbdb-current-record t)))
1452                      current-prefix-arg))
1453   (if (not (listp recs))
1454       (setq recs (list recs)))
1455   (while recs
1456     (let ((r (car recs)))
1457       (setq recs (cdr recs))
1458       (bbdb-debug (if (bbdb-record-deleted-p r)
1459                       (error "deleting deleted record")))
1460       (if (or noprompt
1461               (bbdb-y-or-n-p (format "delete the entire db entry of %s? "
1462                                      (or (bbdb-record-name r)
1463                                          (bbdb-record-company r)
1464                                          (car (bbdb-record-net r))))))
1465           (let* ((record-cons (assq r bbdb-records))
1466                  (next-record-cons (car (cdr (memq record-cons
1467                                                    bbdb-records)))))
1468             (bbdb-debug (if (bbdb-record-deleted-p r)
1469                             (error "deleting deleted record")))
1470             (bbdb-record-set-deleted-p r t)
1471             (bbdb-delete-record-internal r)
1472             (if (eq record-cons (car bbdb-records))
1473                 (setq bbdb-records (cdr bbdb-records))
1474               (let ((rest bbdb-records))
1475                 (while (cdr rest)
1476                   (if (eq record-cons (car (cdr rest)))
1477                       (progn
1478                         (setcdr rest (cdr (cdr rest)))
1479                         (setq rest nil)))
1480                   (setq rest (cdr rest)))))
1481             (bbdb-redisplay-one-record r record-cons next-record-cons t)
1482             (bbdb-with-db-buffer
1483              (setq bbdb-changed-records (delq r bbdb-changed-records)))
1484             ;; (bbdb-offer-save)
1485             )))))
1486
1487 (defun bbdb-change-records-state-and-redisplay (desired-state records)
1488   (let (rec)
1489     (while records
1490       (setq rec (car records))
1491       (unless (eq desired-state (nth 1 rec))
1492         (setcar (cdr rec) desired-state)
1493         (bbdb-redisplay-one-record (car rec) rec))
1494       (setq records (cdr records)))))
1495
1496 ;;;###autoload
1497 (defun bbdb-toggle-all-records-display-layout (arg &optional records)
1498   "Show all the fields of all visible records.
1499 Like `bbdb-toggle-records-display-layout' but for all visible records."
1500   (interactive "P")
1501   (if (null records)
1502       (setq records bbdb-records))
1503   (let* ((record (bbdb-current-record))
1504          (cons (assq record bbdb-records))
1505          (current-state (nth 1 cons))
1506          (layout-alist
1507           (or (delete nil (mapcar (lambda (l)
1508                                     (if (and (assoc 'toggle l)
1509                                              (cdr (assoc 'toggle l)))
1510                                         l))
1511                                   bbdb-display-layout-alist))
1512               bbdb-display-layout-alist))
1513          (desired-state (assoc current-state layout-alist)))
1514     (setq desired-state
1515           (cond ((eq arg 0)
1516                  'one-line)
1517                 ((null current-state)
1518                  'multi-line)
1519                 ((null (cdr (memq desired-state layout-alist)))
1520                  (caar layout-alist))
1521                 (t
1522                  (caadr (memq desired-state layout-alist)))))
1523     (message "Using %S layout" desired-state)
1524     (bbdb-change-records-state-and-redisplay desired-state records)))
1525
1526 ;;;###autoload
1527 (defun bbdb-toggle-records-display-layout (arg)
1528   "Toggle whether the current record is displayed expanded or elided
1529 \(multi-line or one-line display.\)  With a numeric argument of 0, the
1530 current record will unconditionally be made elided; with any other argument,
1531 the current record will unconditionally be shown expanded.
1532 \\<bbdb-mode-map>
1533 If \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-toggle-records-display-layout]\" is \
1534 used instead of simply \"\\[bbdb-toggle-records-display-layout]\", then the state of all \
1535 records will
1536 be changed instead of just the one at point.  In this case, an argument
1537 of 0 means that all records will unconditionally be made elided; any other
1538 numeric argument means that all of the records will unconditionally be shown
1539 expanded; and no numeric argument means that the records are made to be in
1540 the opposite state of the record under point."
1541   (interactive "P")
1542   (bbdb-toggle-all-records-display-layout
1543    arg
1544    (if (not (bbdb-do-all-records-p))
1545        (list (assq (bbdb-current-record) bbdb-records)))))
1546
1547 ;;;###autoload
1548 (defun bbdb-display-all-records-completely
1549   (arg &optional records)
1550   "Show all the fields of all currently displayed records.
1551 The display layout `full-multi-line' is used for this."
1552   (interactive "P")
1553   (if (null records)
1554       (setq records bbdb-records))
1555   (let* ((record (bbdb-current-record))
1556          (cons (assq record bbdb-records))
1557          (current-state (nth 1 cons))
1558          (desired-state
1559           (cond ((not (eq current-state 'full-multi-line))
1560                  'full-multi-line)
1561                 (t
1562                  'multi-line))))
1563     (bbdb-change-records-state-and-redisplay desired-state records)))
1564
1565 ;;;###autoload
1566 (defun bbdb-display-record-completely (arg)
1567   "Show all the fields of the current record.
1568 The display layout `full-multi-line' is used for this."
1569   (interactive "P")
1570   (bbdb-display-all-records-completely
1571    arg
1572    (if (not (bbdb-do-all-records-p))
1573        (list (assq (bbdb-current-record) bbdb-records)))))
1574
1575 ;;;###autoload
1576 (defun bbdb-display-record-with-layout (layout &optional records)
1577   "Show all the fields of the current record using LAYOUT."
1578   (interactive (list (completing-read "Layout: "
1579                       (mapcar (lambda (i)
1580                         (list (symbol-name (car i))))
1581                           bbdb-display-layout-alist))))
1582   (when (stringp layout)
1583     (setq layout (intern layout)))
1584   (when (null records)
1585     (setq records bbdb-records))
1586   (bbdb-change-records-state-and-redisplay layout records))
1587
1588 ;;;###autoload
1589 (defun bbdb-omit-record (n)
1590   "Remove the current record from the display without deleting it from the
1591 database.  With a prefix argument, omit the next N records.  If negative,
1592 omit backwards."
1593   (interactive "p")
1594   (while (not (= n 0))
1595     (if (< n 0) (bbdb-prev-record 1))
1596     (let* ((record (or (bbdb-current-record) (error "no records")))
1597            (rest bbdb-records)
1598            cons next prev-tail)
1599       (while rest
1600         (if (eq (car (car rest)) record)
1601             (setq cons (car rest)
1602                   next (car (cdr rest))
1603                   rest nil)
1604           (setq prev-tail rest
1605                 rest (cdr rest))))
1606       (or record (error "can't find current record"))
1607       (let ((buffer-read-only nil))
1608         (delete-region (nth 2 cons) (if next (nth 2 next) (point-max))))
1609       (if prev-tail
1610           (setcdr prev-tail (cdr (cdr prev-tail)))
1611         (setq bbdb-records (cdr bbdb-records)))
1612       (setq n (if (> n 0) (1- n) (1+ n)))))
1613   (bbdb-frob-mode-line (length bbdb-records)))
1614
1615 ;;; Fixing up bogus entries
1616
1617 (defcustom bbdb-refile-notes-generate-alist '((creation-date . bbdb-refile-notes-string-least) (timestamp . bbdb-refile-notes-string-most))
1618   "*An alist defining specific merging function, based on notes field."
1619   :group 'bbdb-noticing-records
1620   :type '(repeat (cons
1621                   (symbol :tag "Notes filed")
1622                   (hook :tag "Generating function"))))
1623
1624 (defcustom bbdb-refile-notes-default-merge-function 'bbdb-refile-notes-default-merge-function
1625   "*Default function to use for merging BBDB notes records.
1626
1627 If the note field has an entry in `bbdb-refile-notes-generate-alist',
1628 that function will be used instead."
1629   :group 'bbdb-noticing-records
1630   :type 'function)
1631
1632
1633 (defun bbdb-refile-notes-default-merge-function (string1 string2)
1634   "Returns the concatenation of STRING1 and STRING2"
1635   (concat string1 "\n" string2))
1636
1637 (defun bbdb-refile-notes-remove-duplicates (string1 string2)
1638   "Concatenate STRING1 and STRING2, but remove duplicate lines."
1639   (let ((note1 (split-string string1 "\n"))
1640         (note2 (split-string string2 "\n")))
1641     (while note2
1642       (if (not (member (car note2) note1))
1643           (setq note1 (cons (car note2) note1)))
1644       (setq note2 (cdr note2)))
1645     (mapconcat 'identity note1 "\n")))
1646
1647 (defun bbdb-refile-notes-string-least (string1 string2)
1648   "Returns the string that is lessp."
1649   (if (string-lessp string1 string2)
1650       string1
1651     string2))
1652
1653 (defun bbdb-refile-notes-string-most (string1 string2)
1654   "Returns the string that is not lessp."
1655   (if (string-lessp string1 string2)
1656       string2
1657     string1))
1658
1659 (defun bbdb-merge-lists! (l1 l2 cmp &optional mod)
1660   "Merge two lists l1 l2 (modifies l1) only adds elements from l2
1661 if cmp returns false for all elements of l1.  If optional mod
1662 is provided it is applied to each element of l1 and l2 prior to cmp"
1663   (if (null l1)
1664       l2
1665     (let ((end (last l1))
1666           (src2 l2)
1667           (chk (if mod (mapcar mod l1) (append l1 '()))))
1668       (while src2
1669         (let ((fail '())
1670               (src1 chk)
1671               (val  (if mod (apply mod (car src2) '()) (car src2))))
1672           (while src1
1673             (if (apply cmp (car src1) val '())
1674                 (setq src1 '()
1675                       fail 't)
1676               (setq src1 (cdr src1))))
1677           (if fail '()
1678             (setcdr end (cons (car src2) '()))
1679             (setq end (cdr end)))
1680           (setq src2 (cdr src2))))
1681       l1)))
1682
1683 (defun bbdb-merge-records (old-record new-record)
1684   "Merge the contents of old-record into new-record, old-record
1685 remains unchanged.  For name and company it queries about which to use
1686 if they differ.  All other fields are concatenated.  Idealy this would
1687 be better about checking for duplicate entries in other fields, as
1688 well as possibly querying about differing values.
1689
1690 This function does nothing to ensure the integrity of the rest of the
1691 database, that is somebody elses problem (something like
1692 `bbdb-refile-record')."
1693   (if (or (null new-record) (eq old-record new-record))
1694       (error "those are the same"))
1695   (let ((new-name (bbdb-record-name    new-record))
1696         (new-co   (bbdb-record-company new-record))
1697         (old-name (bbdb-record-name    old-record))
1698         (old-co   (bbdb-record-company old-record))
1699         (old-nets (bbdb-record-net     old-record))
1700         (old-aka  (bbdb-record-aka     old-record))
1701         extra-name)
1702     (let ((name
1703            (cond ((= 0 (length old-name))
1704                   (cons (bbdb-record-firstname new-record)
1705                         (bbdb-record-lastname new-record)))
1706                  ((= 0 (length new-name))
1707                   (cons (bbdb-record-firstname old-record)
1708                         (bbdb-record-lastname old-record)))
1709                  ((string-equal (downcase old-name) (downcase new-name))
1710                   (cons (bbdb-record-firstname new-record)
1711                         (bbdb-record-lastname new-record)))
1712                  (t (prog1
1713                         (if (bbdb-y-or-n-p
1714                              (format "Use name \"%s\" instead of \"%s\"? "
1715                                      old-name  new-name))
1716                             (progn
1717                               (setq extra-name new-record)
1718                               (cons (bbdb-record-firstname old-record)
1719                                     (bbdb-record-lastname old-record)))
1720                           (setq extra-name old-record)
1721                           (cons (bbdb-record-firstname new-record)
1722                                 (bbdb-record-lastname new-record)))
1723                       (or (and bbdb-use-alternate-names
1724                                (bbdb-y-or-n-p
1725                                 (format "Keep \"%s\" as an alternate name? "
1726                                         (bbdb-record-name extra-name))))
1727                           (setq extra-name nil))))))
1728           (comp (cond ((= 0 (length old-co)) new-co)
1729                       ((= 0 (length new-co)) old-co)
1730                       ((string-equal old-co new-co) new-co)
1731                       (t (if (bbdb-y-or-n-p
1732                               (format "Use company \"%s\" instead of \"%s\"? "
1733                                       old-co new-co))
1734                              old-co new-co)))))
1735
1736       (if extra-name
1737           (setq old-aka (cons (bbdb-record-name extra-name) old-aka)))
1738
1739       (bbdb-record-set-phones new-record
1740                               (bbdb-merge-lists!
1741                                (bbdb-record-phones new-record)
1742                                (bbdb-record-phones old-record)
1743                                'equal))
1744       (bbdb-record-set-addresses new-record
1745                                  (bbdb-merge-lists!
1746                                   (bbdb-record-addresses new-record)
1747                                   (bbdb-record-addresses old-record)
1748                                   'equal))
1749       (bbdb-record-set-company new-record comp)
1750
1751       (let ((n1 (bbdb-record-raw-notes new-record))
1752             (n2 (bbdb-record-raw-notes old-record))
1753             tmp)
1754         (or (equal n1 n2)
1755             (progn
1756               (or (listp n1) (setq n1 (list (cons 'notes n1))))
1757               (or (listp n2) (setq n2 (list (cons 'notes n2))))
1758               (while n2
1759                 (if (setq tmp (assq (car (car n2)) n1))
1760                     (setcdr tmp
1761                             (funcall
1762                              (or (cdr (assq (car (car n2))
1763                                             bbdb-refile-notes-generate-alist))
1764                                  bbdb-refile-notes-default-merge-function)
1765                              (cdr tmp) (cdr (car n2))))
1766                   (setq n1 (nconc n1 (list (car n2)))))
1767                 (setq n2 (cdr n2)))
1768               (bbdb-record-set-raw-notes new-record n1))))
1769
1770       (bbdb-record-set-firstname new-record (car name))
1771       (bbdb-record-set-lastname  new-record (cdr name))
1772       (bbdb-record-set-namecache new-record nil)
1773
1774       (bbdb-record-set-net new-record
1775                            (bbdb-merge-lists!
1776                             (bbdb-record-net new-record) old-nets
1777                             'string= 'downcase))
1778       (bbdb-record-set-aka new-record
1779                            (bbdb-merge-lists!
1780                             (bbdb-record-aka new-record) old-aka
1781                             'string= 'downcase))
1782       new-record)))
1783
1784 ;;;###autoload
1785 (defun bbdb-refile-record (old-record new-record)
1786   "Merge the current record into some other record; that is, delete the
1787 record under point after copying all of the data within it into some other
1788 record.  this is useful if you realize that somehow a redundant record has
1789 gotten into the database, and you want to merge it with another.
1790
1791 If both records have names and/or companies, you are asked which to use.
1792 Phone numbers, addresses, and network addresses are simply concatenated.
1793 The first record is the record under the point; the second is prompted for.
1794 Completion behaviour is as dictated by the variable `bbdb-completion-type'."
1795   (interactive
1796    (let ((r (bbdb-current-record))
1797          name)
1798      (setq name (bbdb-record-name r))
1799      (list r
1800            (if current-prefix-arg
1801                (car (delq r (bbdb-search (bbdb-records) name nil)))
1802              (bbdb-completing-read-one-record
1803               (format "merge record \"%s\" into: "
1804                       (or (bbdb-record-name r) (car (bbdb-record-net r))
1805                           "???")) (list r))))))
1806
1807   (if (or (null new-record) (eq old-record new-record))
1808       (error "those are the same"))
1809   (setq new-record (bbdb-merge-records old-record new-record))
1810
1811   (bbdb-delete-current-record old-record 'noprompt)
1812   (bbdb-change-record new-record t)     ; don't always need-to-sort...
1813   (let ((bbdb-display-layout nil))
1814     (if (assq new-record bbdb-records)
1815         (bbdb-redisplay-one-record new-record))
1816     (bbdb-with-db-buffer
1817      (if (not (memq new-record bbdb-changed-records))
1818          (setq bbdb-changed-records
1819                (cons new-record bbdb-changed-records))))
1820     (if (null bbdb-records)             ; nothing displayed, display something.
1821         (bbdb-display-records (list new-record))))
1822   (message "records merged."))
1823
1824 ;; sort the notes
1825 (defcustom bbdb-notes-sort-order
1826   '((notes . 0) (www . 1) (ftp . 2) (gopher . 3) (telnet . 4) (mail-alias . 5)
1827     (mail-folder . 6) (lpr . 7) (creation-date . 1000) (timestamp . 1001))
1828   "*The order for sorting the notes.
1829 If a note is not in the alist, it is assigned weight 100, so all notes
1830 with weights less then 100 will be in the beginning, and all notes with
1831 weights more than 100 will be in the end."
1832   :group 'bbdb-noticing-records
1833   :type 'list)
1834
1835 ;;;###autoload
1836 (defun bbdb-sort-notes (rec)
1837   "Sort the notes in the record according to `bbdb-notes-sort-order'.
1838 Can be used in `bbdb-change-hook'."
1839   (flet ((kk (nt) (or (cdr (assq (car nt) bbdb-notes-sort-order)) 100)))
1840     (bbdb-record-set-raw-notes
1841      rec (sort (bbdb-record-raw-notes rec)
1842                (lambda (aa bb) (< (kk aa) (kk bb)))))))
1843
1844 ;;;###autoload
1845 (defun bbdb-sort-phones (rec)
1846   "Sort the phones in the record according to the location.
1847 Can be used in `bbdb-change-hook'."
1848   (bbdb-record-set-phones
1849    rec (sort (bbdb-record-phones rec)
1850              (lambda (xx yy) (string< (aref xx 0) (aref yy 0))))))
1851
1852 ;;;###autoload
1853 (defun bbdb-sort-addresses (rec)
1854   "Sort the addresses in the record according to the location.
1855 Can be used in `bbdb-change-hook'."
1856   (bbdb-record-set-addresses
1857    rec (sort (bbdb-record-addresses rec)
1858              (lambda (xx yy) (string< (aref xx 0) (aref yy 0))))))
1859
1860
1861 ;;; Send-Mail interface
1862
1863 (defcustom bbdb-dwim-net-address-allow-redundancy nil
1864   "*Non-nil means always use full name when sending mail, even if same as net."
1865   :group 'bbdb
1866   :type '(choice (const :tag "Disallow redundancy" nil)
1867                  (const :tag "Return only the net" 'netonly)
1868                  (const :tag "Allow redundancy" t)))
1869
1870 ;;;###autoload
1871 (defun bbdb-dwim-net-address (record &optional net)
1872   "Returns a string to use as the email address of the given record.  The
1873 given address is the address the mail is destined to; this is formatted like
1874 \"Firstname Lastname <addr>\" unless both the first name and last name are
1875 constituents of the address, as in John.Doe@SomeHost, or the address is
1876 already in the form \"Name <foo>\" or \"foo (Name)\", in which case the
1877 address is used as-is. If `bbdb-dwim-net-address-allow-redundancy' is non-nil,
1878 the name is always included.  If `bbdb-dwim-net-address-allow-redundancy' is
1879 'netonly the name is never included!"
1880   (or net (setq net (car (bbdb-record-net record))))
1881   (or net (error "record unhas network addresses"))
1882   (let* ((override (bbdb-record-getprop record 'mail-name))
1883          (name (or override (bbdb-record-name record)))
1884          fn ln (i 0))
1885     (if override
1886         (let ((both (bbdb-divide-name override)))
1887           (setq fn (car both)
1888                 ln (car (cdr both)))
1889           (if (equal fn "") (setq fn nil))
1890           (if (equal ln "") (setq ln nil)))
1891       (setq fn (bbdb-record-firstname record)
1892             ln (bbdb-record-lastname record)))
1893     ;; if the name contains backslashes or double-quotes, backslash them.
1894     (if name
1895         (while (setq i (string-match "[\\\"]" name i))
1896           (setq name (concat (substring name 0 i) "\\" (substring name i))
1897                 i (+ i 2))))
1898     (cond ((eq 'netonly bbdb-dwim-net-address-allow-redundancy)
1899            net)
1900           ((or (null name)
1901                (if (not bbdb-dwim-net-address-allow-redundancy)
1902                    (cond ((and fn ln)
1903                           (or (string-match
1904                                (concat "\\`[^!@%]*\\b" (regexp-quote fn)
1905                                        "\\b[^!%@]+\\b" (regexp-quote ln) "\\b")
1906                                net)
1907                               (string-match
1908                                (concat "\\`[^!@%]*\\b" (regexp-quote ln)
1909                                        "\\b[^!%@]+\\b" (regexp-quote fn) "\\b")
1910                                net)))
1911                          ((or fn ln)
1912                           (string-match
1913                            (concat "\\`[^!@%]*\\b" (regexp-quote (or fn ln)) "\\b")
1914                            net))))
1915                ;; already in "foo <bar>" or "bar <foo>" format.
1916                (string-match "\\`[ \t]*[^<]+[ \t]*<" net)
1917                (string-match "\\`[ \t]*[^(]+[ \t]*(" net))
1918            net)
1919           ;; if the name contains control chars or RFC822 specials, it needs
1920           ;; to be enclosed in quotes.  Double-quotes and backslashes have
1921           ;; already been escaped.  This quotes a few extra characters as
1922           ;; well (!,%, and $) just for common sense.
1923           ((string-match "[][\000-\037\177()<>@,;:.!$%]" name)
1924            (format "\"%s\" <%s>" name net))
1925           (t
1926            (format "%s <%s>" name net)))))
1927
1928
1929 (defun bbdb-send-mail-internal (&optional to subj records)
1930   (let ((type (or bbdb-send-mail-style
1931           ;; In Emacs, `compose-mail' gets whatever you've
1932           ;; customized as your preferred `mail-user-agent'.
1933                   (cond ((fboundp 'compose-mail) 'compose-mail)
1934                         ((featurep 'mh-e) 'mh)
1935                         ((featurep 'vm) 'vm)
1936                         ((featurep 'message) 'message)
1937                         ((featurep 'mew) 'mew)
1938                         ((featurep 'gnus) 'gnus)
1939                         (t 'mail)))))
1940     (cond
1941      ((eq type 'mh)
1942       (or (fboundp 'mh-send) (autoload 'mh-send "mh-e"))
1943       (mh-send to "" (or subj "")))
1944      ((eq type 'vm)
1945       (cond ((not (fboundp 'vm-mail-internal))
1946              (load-library "vm")        ; 5.32 or later
1947              (or (fboundp 'vm-mail-internal)
1948                  (load-library "vm-reply")))) ; 5.31 or earlier
1949       (vm-session-initialization)
1950       (if (not subj)
1951           (vm-mail to)
1952         (vm-mail-internal nil to subj)
1953         (run-hooks 'vm-mail-hook)
1954         (run-hooks 'vm-mail-mode-hook)))
1955      ((eq type 'message)
1956       (or (fboundp 'message-mail) (autoload 'message-mail "message"))
1957       (message-mail to subj))
1958      ((or (eq type 'mail) (eq type 'rmail))
1959       (mail nil to subj))
1960      ((eq type 'mew)
1961       (or (fboundp 'mew-send) (load-library "mew"))
1962       (mew-send to nil subj))
1963      ((eq type 'compose-mail)
1964       (compose-mail to subj))
1965      ((eq type 'gnus)
1966       (gnus-msg-mail to subj))
1967      (t
1968       (error "bbdb-send-mail-style must be vm, mh, message, compose-mail, or rmail")))))
1969
1970 ;;;###autoload
1971 (defun bbdb-send-mail (bbdb-record &optional subject)
1972   "Compose a mail message to the person indicated by the current bbdb record.
1973 The first (most-recently-added) address is used if there are more than one.
1974 \\<bbdb-mode-map>
1975 If \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-send-mail]\" is \
1976 used instead of simply \"\\[bbdb-send-mail]\", then mail will be sent to \
1977 all of the
1978 folks listed in the *BBDB* buffer instead of just the person at point."
1979   (interactive (list (if (bbdb-do-all-records-p)
1980                          (mapcar 'car bbdb-records)
1981                        (bbdb-current-record))))
1982   (if (consp bbdb-record)
1983       (bbdb-send-mail-many bbdb-record subject)
1984     (bbdb-send-mail-1 bbdb-record subject)))
1985
1986
1987 (defun bbdb-send-mail-1 (bbdb-record &optional subject)
1988   (if bbdb-inside-electric-display
1989       (bbdb-electric-throw-to-execute
1990        (list 'bbdb-send-mail bbdb-record subject)))
1991   ;; else...
1992
1993   (cond ((null bbdb-record) (error "record unexists"))
1994         ((null (bbdb-record-net bbdb-record))
1995          (error "Current record unhas a network addresses."))
1996         (t (bbdb-send-mail-internal (bbdb-dwim-net-address bbdb-record)
1997                                     subject (list bbdb-record))
1998            (if (re-search-backward "^Subject: $" nil t) (end-of-line)))))
1999
2000
2001 (defun bbdb-send-mail-many (records &optional subject)
2002   (if bbdb-inside-electric-display
2003       (bbdb-electric-throw-to-execute
2004        (list 'bbdb-send-mail (list 'quote records) subject)))
2005   ;; else...
2006
2007   (let ((good '()) (bad '())
2008         (orec records))
2009     (while records
2010       (if (bbdb-record-net (car records))
2011           (setq good (cons (car records) good))
2012         (setq bad (cons (car records) bad)))
2013       (setq records (cdr records)))
2014     (bbdb-send-mail-internal
2015      (mapconcat (lambda (x) (bbdb-dwim-net-address x))
2016                 (nreverse good) ",\n    ")
2017      subject orec)
2018     (if (not bad) nil
2019       (goto-char (point-max))
2020       (let ((p (point))
2021             (fill-prefix "    ")
2022             (fill-column 70))
2023         (insert "*** Warning: No net addresses for "
2024                 (mapconcat (lambda (x) (bbdb-record-name x))
2025                            (nreverse bad) ", ") ".")
2026         (fill-region-as-paragraph p (point))
2027         (goto-char p))))
2028   (if (re-search-backward "^Subject: $" nil t) (end-of-line)))
2029
2030
2031 (defun bbdb-yank-addresses ()
2032   "CC the people displayed in the *BBDB* buffer on this message.
2033 The primary net-address of each of the records currently listed in the
2034 *BBDB* buffer (whether it is visible or not) will be appended to the
2035 CC: field of the current buffer (assuming the current buffer is a mail
2036 composition buffer.)"
2037   (interactive)
2038   (let ((addrs (save-excursion
2039                  (set-buffer bbdb-buffer-name)
2040                  (delq nil
2041                        (mapcar (lambda (x)
2042                                  (if (bbdb-record-net (car x))
2043                                      (bbdb-dwim-net-address (car x))
2044                                    nil))
2045                                bbdb-records)))))
2046     (goto-char (point-min))
2047     ;; If there's a CC field, move to the end of it, inserting a comma if
2048     ;;  there are already addresses present.
2049     ;; Otherwise, if there's an empty To: field, move to the end of it.
2050     ;; Otherwise, insert an empty CC: field.
2051     (if (re-search-forward "^CC:[ \t]*" nil t)
2052         (if (eolp)
2053             nil
2054           (end-of-line)
2055           (while (looking-at "\n[ \t]")
2056             (forward-char) (end-of-line))
2057           (insert ",\n")
2058           (indent-relative))
2059       (re-search-forward "^To:[ \t]*")
2060       (if (eolp)
2061           nil
2062         (end-of-line)
2063         (while (looking-at "\n[ \t]")
2064           (forward-char) (end-of-line))
2065         (insert ",\n")
2066         (indent-relative))
2067       (if (eolp)
2068           nil
2069         (end-of-line)
2070         (while (looking-at "\n[ \t]")
2071           (forward-char) (end-of-line))
2072         (insert "\nCC:")
2073         (indent-relative)))
2074     ;; Now insert each of the addresses on its own line.
2075     (while addrs
2076       (insert (car addrs))
2077       (if (cdr addrs) (progn (insert ",\n") (indent-relative)))
2078       (setq addrs (cdr addrs)))))
2079
2080 ;;;###autoload
2081 (defun bbdb-show-all-recipients ()
2082   "*Display BBDB records for all recipients of the message in this buffer."
2083   (interactive)
2084   (let ((marker (bbdb-header-start))
2085         (fields '("from" "sender" "to" "cc" "bcc"
2086                   "resent-from" "resent-to" "resent-cc" "resent-bcc"))
2087         addrs)
2088     (message "Searching...")
2089     (save-excursion
2090       (set-buffer (marker-buffer marker))
2091       (while fields
2092         (goto-char marker)
2093         (setq addrs (append (bbdb-split (or (bbdb-extract-field-value
2094                                              (car fields))
2095                                             "")
2096                                         ",")
2097                             addrs)
2098               fields (cdr fields))))
2099     (let ((rest addrs)
2100           (records '())
2101           record)
2102       (while rest
2103         (setq record (bbdb-annotate-message-sender (car rest) t t t))
2104         (if record (setq records (cons record records)))
2105         (setq rest (cdr rest)))
2106       (message "Sorting...")
2107       (setq records (sort records (lambda (x y) (bbdb-record-lessp x y))))
2108       (bbdb-display-records records))))
2109
2110
2111 ;;; completion
2112
2113 ;;;###autoload
2114 (defun bbdb-completion-check-record (sym rec)
2115   (let ((name (or (bbdb-record-name rec)
2116                   (bbdb-record-company rec)
2117                   ""))
2118         (nets (bbdb-record-net rec))
2119         ok)
2120
2121     (if (null bbdb-completion-type)
2122         (setq ok 't)
2123
2124       (if (memq bbdb-completion-type
2125                 '(name primary-or-name name-or-primary))
2126           (setq ok (string= sym (downcase name))))
2127
2128       ;; #### handle AKA, mail-name or mail-alias here?
2129       (if ok '()
2130         (when (eq bbdb-completion-type 'net)
2131           (while (and nets (not ok))
2132             (setq ok (string= sym (downcase (car nets)))
2133                   nets (cdr nets))))
2134         (when (and nets (memq bbdb-completion-type
2135                               '(primary primary-or-name name-or-primary)))
2136           (setq ok (string= sym (downcase (car nets)))))))
2137     ok))
2138
2139
2140 ;;;###autoload
2141 (defun bbdb-completion-predicate (symbol)
2142   "For use as the third argument to `completing-read'.
2143 Obey the semantics of `bbdb-completion-type'."
2144   (cond ((null bbdb-completion-type)
2145          t)
2146         ((not (boundp symbol))
2147          nil)
2148         (t
2149          (let ((sym  (symbol-name symbol))
2150                (recs (symbol-value symbol))
2151                ok)
2152            (while (and recs (not ok))
2153              (setq ok   (bbdb-completion-check-record sym (car recs))
2154                    recs (cdr recs)))
2155            ok))))
2156
2157 (defun bbdb-completing-read-record (prompt &optional omit-records)
2158   "Prompt for and return a record from the bbdb.
2159 Completion is done according to `bbdb-completion-type'.  If the user
2160 just hits return, nil is returned.  Otherwise, a valid response is forced."
2161   (let* ((ht (bbdb-hashtable))
2162          (completion-ignore-case 't)
2163          (string (completing-read prompt ht 'bbdb-completion-predicate t))
2164          (symbol (and (not (= 0 (length string)))
2165                       (intern-soft string ht))))
2166     (if symbol
2167         (if (and (boundp symbol) (symbol-value symbol))
2168             (let ((recs (symbol-value symbol)) ret)
2169               (while recs
2170                 (if (and (not (memq (car recs) omit-records))
2171                          (bbdb-completion-check-record (symbol-name symbol)
2172                                                        (car recs)))
2173                     (setq ret (cons (car recs) ret)))
2174                 (setq recs (cdr recs)))
2175               ret)
2176           (error "selecting deleted (unhashed) record \"%s\"!" symbol))
2177       nil)))
2178
2179 (defun bbdb-completing-read-one-record (prompt &optional omit-records)
2180   "Prompt for and return a single record from the bbdb;
2181 completion is done according to `bbdb-completion-type'.  If the user
2182 just hits return, nil is returned. Otherwise, a valid response is forced.
2183 if omit-records is non-nil it should be a list of records to dis-allow
2184 completion with."
2185   (let ((records (bbdb-remove-memq-duplicates
2186                   (bbdb-completing-read-record prompt omit-records))))
2187     (cond
2188      ((eq (length records) 1)
2189       (car records))
2190      ((> (length records) 1)
2191       (let ((count (length records))
2192             prompts result)
2193         (bbdb-display-records records)
2194         (while (> count 0)
2195           (setq prompts (cons (list (number-to-string count) count) prompts)
2196                 count (1- count)))
2197         (setq result
2198               (completing-read (format "Which duplicate record (1-%s): "
2199                                        (length records))
2200                                prompts nil t "1"))
2201         (nth (1- (string-to-number result)) records)))
2202      (t
2203       nil))))
2204
2205 (defvar bbdb-read-addresses-with-completion-map
2206   (let ((map (copy-keymap minibuffer-local-completion-map)))
2207     (define-key map " " 'self-insert-command)
2208     (define-key map "\t" 'bbdb-complete-name)
2209     (define-key map "\M-\t" 'bbdb-complete-name)
2210     map))
2211
2212 ;;;###autoload
2213 (defun bbdb-read-addresses-with-completion (prompt &optional default)
2214   "Like `read-string', but allows `bbdb-complete-name' style completion."
2215   (read-from-minibuffer prompt default
2216                         bbdb-read-addresses-with-completion-map))
2217
2218
2219 ;; Internal use. Store the window configuration before we pop up the
2220 ;; completion buffer.
2221 (defvar bbdb-complete-name-saved-window-config nil)
2222
2223 ;; Restore the saved window configuration
2224 (defun bbdb-complete-name-cleanup ()
2225   (if bbdb-complete-name-saved-window-config
2226       (progn
2227         (if (get-buffer-window "*Completions*")
2228             (progn
2229               (set-window-configuration
2230                bbdb-complete-name-saved-window-config)
2231               (bury-buffer "*Completions*"))
2232           )
2233         (setq bbdb-complete-name-saved-window-config nil))))
2234
2235 (defvar bbdb-complete-name-callback-data nil
2236   "Stores the buffer and region start and end of the completed string.
2237 This is set in the *Completions* buffer.
2238 It is set in `bbdb-display-completion-list' and used in the advice
2239 `choose-completion-string'.")
2240
2241 (make-variable-buffer-local 'bbdb-complete-name-callback-data)
2242
2243 (defun bbdb-display-completion-list (list &optional callback data)
2244   "Wrapper for `display-completion-list'.
2245 GNU Emacs requires DATA to be in a specific format, viz. (nth 1 data) should
2246 be a marker for the start of the region being completed."
2247   ;; disgusting hack to make GNU Emacs nuke the bit you've typed
2248   ;; when it inserts the completion.
2249   (setq bbdb-complete-name-callback-data data)
2250   (if (featurep 'xemacs)
2251       (display-completion-list list :activate-callback callback
2252                                :user-data data)
2253     (display-completion-list list)))
2254
2255 (defadvice choose-completion-string (before bbdb-complete-fix activate)
2256   "Deletes the completed string before replacing.
2257 We need to do this as we are abusing completion and it was not meant to work
2258 in buffer other than the mini buffer."
2259   (when bbdb-complete-name-callback-data
2260     (save-excursion
2261       (set-buffer (car bbdb-complete-name-callback-data))
2262       (apply 'delete-region (cdr  bbdb-complete-name-callback-data)))))
2263
2264 (defun bbdb-complete-clicked-name (event extent user-data)
2265   "Find the record for a name clicked in a completion buffer.
2266 Currently only used by XEmacs."
2267   (let ((buffer (nth 0 user-data))
2268         (bbdb-complete-name-allow-cycling nil)
2269         (beg (nth 1 user-data))
2270         (end (nth 2 user-data)))
2271     (bbdb-complete-name-cleanup)
2272     (set-buffer buffer)
2273     (goto-char beg)
2274     (delete-region beg end)
2275     (insert (bbdb-extent-string extent))
2276     (bbdb-complete-name beg)))
2277
2278
2279 (defun bbdb-list-overlap (l1 l2)
2280   (let (ok)
2281     (while (and (not ok) l1)
2282       (if (memq (car l1) l2) (setq ok t l1 '())
2283         (setq l1 (cdr l1))))
2284     ok))
2285
2286 (defun bbdb-remove-assoc-duplicates (l)
2287   (if (null l) '()
2288     (if (assoc (car (car l)) (cdr l))
2289         (bbdb-remove-assoc-duplicates (cdr l))
2290       (cons (car l) (bbdb-remove-assoc-duplicates (cdr l))))))
2291
2292 (defcustom bbdb-complete-name-allow-cycling nil
2293   "Whether to allow cycling of email addresses when calling
2294 `bbdb-complete-name' on a completed address in a composition buffer."
2295   :group 'bbdb-mua-specific
2296   :type 'boolean)
2297
2298 (defcustom bbdb-complete-name-hooks nil
2299   "List of functions called after a sucessful completion."
2300   :group 'bbdb-mua-specific
2301   :type 'boolean)
2302
2303 (eval-when-compile (defvar auto-fill-hook))
2304
2305 ;;;###autoload
2306 (defun bbdb-complete-name (&optional start-pos)
2307   "Complete the user full-name or net-address before point (up to the
2308 preceeding newline, colon, or comma, or the value of START-POS).  If
2309 what has been typed is unique, insert an entry of the form \"User Name
2310 <net-addr>\" (although see documentation for
2311 bbdb-dwim-net-address-allow-redundancy).  If it is a valid completion
2312 but not unique, a list of completions is displayed.
2313
2314 If the completion is done and `bbdb-complete-name-allow-cycling' is
2315 true then cycle through the nets for the matching record.
2316
2317 When called with a prefix arg then display a list of all nets.
2318
2319 Completion behaviour can be controlled with `bbdb-completion-type'."
2320   (interactive)
2321
2322   (let* ((end (point))
2323          (beg (or start-pos
2324                   (save-excursion
2325                     (re-search-backward "\\(\\`\\|[\n:,]\\)[ \t]*")
2326                     (goto-char (match-end 0))
2327                     (point))))
2328          (orig (buffer-substring beg end))
2329          (typed (downcase orig))
2330          (pattern (bbdb-string-trim typed))
2331          (ht (bbdb-hashtable))
2332          ;; make a list of possible completion strings
2333          ;; (all-the-completions), and a flag to indicate if there's a
2334          ;; single matching record or not (only-one-p)
2335          (only-one-p t)
2336          (all-the-completions nil)
2337          (pred
2338           (lambda (sym)
2339             (when (bbdb-completion-predicate sym)
2340               (if (and only-one-p
2341                        all-the-completions
2342                        (or
2343                         ;; not sure about this. more than one record
2344                         ;; attached to the symbol? does that happen?
2345                         (> (length (symbol-value sym)) 1)
2346                         ;; this is the doozy, though. multiple syms
2347                         ;; which all match the same record
2348                         (delete t (mapcar (lambda(x)
2349                                             (equal (symbol-value x)
2350                                                    (symbol-value sym)))
2351                                           all-the-completions))))
2352                   (setq only-one-p nil))
2353               (if (not (memq sym all-the-completions))
2354                   (setq all-the-completions (cons sym all-the-completions))))))
2355          (completion (progn (all-completions pattern ht pred) (try-completion pattern ht)))
2356          (exact-match (eq completion t)))
2357
2358     (cond
2359      ;; No matches found OR you're trying completion on an
2360      ;; already-completed record. In the latter case, we might have to
2361      ;; cycle through the nets for that record.
2362      ((or (null completion)
2363           (and bbdb-complete-name-allow-cycling
2364                exact-match ;; which is a net of the record
2365                (member orig
2366                        (bbdb-record-net
2367                         (car (symbol-value (intern-soft pattern ht)))))))
2368       ;; Clean up the completion buffer, if it exists
2369       (bbdb-complete-name-cleanup)
2370       ;; Check for cycling
2371       (or (catch 'bbdb-cycling-exit
2372             ;; jump straight out if we're not cycling
2373             (or bbdb-complete-name-allow-cycling
2374                 (throw 'bbdb-cycling-exit nil))
2375
2376             ;; find the record we're working on.
2377             (let* ((addr (funcall bbdb-extract-address-components-func orig))
2378                    (rec
2379                     (if (listp addr)
2380                         ;; for now, we're ignoring the case where this
2381                         ;; returns more than one record. Ideally, the
2382                         ;; last expansion would be stored in a
2383                         ;; buffer-local variable, perhaps.
2384                         (car (bbdb-search-intertwingle (caar addr)
2385                                                        (cadar addr)))
2386                       nil)))
2387               (or rec
2388                   (throw 'bbdb-cycling-exit nil))
2389
2390               (if current-prefix-arg
2391                   ;; use completion buffer
2392                   (let ((standard-output (get-buffer-create "*Completions*")))
2393                     ;; a previously existing buffer has to be cleaned first
2394                     (save-excursion (set-buffer standard-output)
2395                                     (setq buffer-read-only nil)
2396                                     (erase-buffer))
2397                     (display-completion-list
2398                      (mapcar (lambda (n) (bbdb-dwim-net-address rec n))
2399                              (bbdb-record-net rec)))
2400                     (delete-region beg end)
2401                     (switch-to-buffer standard-output))
2402                 ;; use next address
2403                 (let* ((addrs (bbdb-record-net rec))
2404                        (this-addr (or (cadr (member (car (cdar addr)) addrs))
2405                                       (nth 0 addrs))))
2406                   (if (= (length addrs) 1)
2407                       ;; no alternatives. don't signal an error.
2408                       (throw 'bbdb-cycling-exit t)
2409                     ;; replace with new mail address
2410                     (delete-region beg end)
2411                     (insert (bbdb-dwim-net-address rec this-addr))
2412                     (run-hooks 'bbdb-complete-name-hooks)
2413                     (throw 'bbdb-cycling-exit t))))))
2414
2415           ;; FALL THROUGH
2416           ;; Check mail aliases
2417           (if (and bbdb-expand-mail-aliases (expand-abbrev))
2418               ()
2419             (when bbdb-complete-name-hooks
2420               (message "completion for \"%s\" unfound." pattern)
2421               (ding)))));; no matches, sorry!
2422
2423      ;; Match for a single record. If cycling is enabled then we don't
2424      ;; care too much about the exact-match part.
2425      ((and only-one-p (or exact-match bbdb-complete-name-allow-cycling))
2426       (let* ((sym (if exact-match (intern-soft pattern ht) (car all-the-completions)))
2427              (recs (symbol-value sym))
2428              the-net match-recs lst primary matched)
2429
2430         (while recs
2431           (when (bbdb-record-net (car recs))
2432
2433             ;; Did we match on name?
2434             (let ((b-r-name (or (bbdb-record-name (car recs)) "")))
2435               (if (string= pattern
2436                            (substring (downcase b-r-name) 0
2437                                       (min (length b-r-name)
2438                                            (length pattern))))
2439                   (setq match-recs (cons (car recs) match-recs)
2440                         matched t)))
2441             
2442             ;; Did we match on lastname?
2443             (let ((b-r-name (or (bbdb-record-lastname (car recs)) "")))
2444               (if (string= pattern
2445                            (substring (downcase b-r-name) 0
2446                                       (min (length b-r-name)
2447                                            (length pattern))))
2448                   (setq match-recs (cons (car recs) match-recs)
2449                         matched t)))
2450
2451             ;; Did we match on aka?
2452             (when (not matched)
2453               (setq lst (bbdb-record-aka (car recs)))
2454               (while lst
2455                 (if (string= pattern (substring (downcase (car lst)) 0
2456                                                 (min (length (downcase
2457                                                               (car
2458                                                                lst)))
2459                                                      (length pattern))))
2460                     (setq match-recs (append match-recs (list (car recs)))
2461                           matched t
2462                           lst '())
2463                   (setq lst (cdr lst)))))
2464
2465             ;; Name didn't match name so check net matching
2466             (when (not matched)
2467               (setq lst (bbdb-record-net (car recs)))
2468               (setq primary t) ;; primary wins over secondary...
2469               (while lst
2470                 (if (string= pattern (substring (downcase (car lst))
2471                                                 0 (min (length
2472                                                         (downcase (car
2473                                                                    lst)))
2474                                                        (length pattern))))
2475                     (setq the-net (car lst)
2476                           lst     nil
2477                           match-recs
2478                           (if primary (cons (car recs) match-recs)
2479                             (append match-recs (list (car recs))))))
2480                 (setq lst     (cdr lst)
2481                       primary nil))))
2482
2483           ;; loop to next rec
2484           (setq recs    (cdr recs)
2485                 matched nil))
2486
2487         (unless match-recs
2488           (error "only exact matching record unhas net field"))
2489
2490         ;; now replace the text with the expansion
2491         (delete-region beg end)
2492         (insert (bbdb-dwim-net-address (car match-recs) the-net))
2493
2494         ;; if we're past fill-column, wrap at the previous comma.
2495         (if (and
2496              (bbdb-auto-fill-function)
2497              (>= (current-column) fill-column))
2498             (let ((p (point))
2499                   bol)
2500               (save-excursion
2501                 (beginning-of-line)
2502                 (setq bol (point))
2503                 (goto-char p)
2504                 (if (search-backward "," bol t)
2505                     (progn
2506                       (forward-char 1)
2507                       (insert "\n   "))))))
2508
2509         ;; Update the *BBDB* buffer if desired.
2510         (if bbdb-completion-display-record
2511             (let ((bbdb-gag-messages t))
2512               (bbdb-pop-up-bbdb-buffer)
2513               (bbdb-display-records-1 match-recs t)))
2514         (bbdb-complete-name-cleanup)
2515
2516         ;; call the exact-completion hook
2517         (run-hooks 'bbdb-complete-name-hooks)))
2518
2519      ;; Partial match
2520      ;; note, we can't use the trimmed version of the pattern here or
2521      ;; we'll recurse infinitely on e.g. common first names
2522      ((and (stringp completion) (not (string= typed completion)))
2523       (delete-region beg end)
2524       (insert completion)
2525       (setq end (point))
2526       (let ((last "")
2527             (bbdb-complete-name-allow-cycling nil))
2528         (while (and (stringp completion)
2529                     (not (string= completion last))
2530                     (setq last completion
2531                           pattern (downcase orig)
2532                           completion (progn (all-completions pattern ht pred) (try-completion pattern ht))))
2533           (if (stringp completion)
2534               (progn (delete-region beg end)
2535                      (insert completion))))
2536         (bbdb-complete-name beg)))
2537
2538      ;; Exact match, but more than one record
2539      (t
2540       (or (eq (selected-window) (minibuffer-window))
2541           (message "Making completion list..."))
2542
2543       (let (dwim-completions
2544             uniq nets net name akas)
2545         ;; Now collect all the dwim-addresses for each completion, but only
2546         ;; once for each record!  Add it if the net is part of the completions
2547         (bbdb-mapc
2548          (lambda (sym)
2549            (bbdb-mapc
2550             (lambda (rec)
2551               (when (not (member rec uniq))
2552                 (setq uniq (cons rec uniq)
2553                       nets (bbdb-record-net rec)
2554                       name (downcase (or (bbdb-record-name rec) ""))
2555                       akas (mapcar 'downcase (bbdb-record-aka rec)))
2556                 (while nets
2557                   (setq net (car nets))
2558                   (when (cond
2559                          ;; primary
2560                          ((and (member bbdb-completion-type
2561                                        '(primary primary-or-name))
2562                                (member (intern-soft (downcase net) ht)
2563                                        all-the-completions))
2564                           (setq nets nil)
2565                           t)
2566                          ;; name
2567                          ((and name (member bbdb-completion-type
2568                                             '(nil name primary-or-name))
2569                                (let ((cname (symbol-name sym)))
2570                                  (or (string= cname name)
2571                                      (member cname akas))))
2572                           (setq name nil)
2573                           t)
2574                          ;; net
2575                          ((and (member bbdb-completion-type
2576                                        '(nil net))
2577                                (member (intern-soft (downcase net) ht)
2578                                        all-the-completions)))
2579                          ;; (name-or-)primary
2580                          ((and (member bbdb-completion-type
2581                                        '(name-or-primary))
2582                                (let ((cname (symbol-name sym)))
2583                                  (or (string= cname name)
2584                                      (member cname akas))))
2585                           (setq nets nil)
2586                           t)
2587                          )
2588                     (setq dwim-completions
2589                           (cons (bbdb-dwim-net-address rec net)
2590                                 dwim-completions))
2591                     (if exact-match (setq nets nil)))
2592                   (setq nets (cdr nets)))))
2593             (symbol-value sym)))
2594          all-the-completions)
2595
2596         ;; if, after all that, we've only got one matching record...
2597         (if (and dwim-completions (null (cdr dwim-completions)))
2598             (progn
2599               (delete-region beg end)
2600               (insert (car dwim-completions))
2601               (message ""))
2602           ;; otherwise, pop up a completions window
2603           (if (not (get-buffer-window "*Completions*"))
2604               (setq bbdb-complete-name-saved-window-config
2605                     (current-window-configuration)))
2606           (let ((arg (list (current-buffer)
2607                            (set-marker (make-marker) beg)
2608                            (set-marker (make-marker) end))))
2609             (with-output-to-temp-buffer "*Completions*"
2610               (bbdb-display-completion-list
2611                dwim-completions
2612                'bbdb-complete-clicked-name
2613                arg)))
2614           (or (eq (selected-window) (minibuffer-window))
2615               (message "Making completion list...done"))))))))
2616
2617 ;;;###autoload
2618 (defun bbdb-yank ()
2619   "Insert the current contents of the *BBDB* buffer at point."
2620   (interactive)
2621   (insert (let ((b (current-buffer)))
2622             (set-buffer bbdb-buffer-name)
2623             (prog1 (buffer-string) (set-buffer b)))))
2624
2625
2626 ;;; interface to mail-abbrevs.el.
2627
2628 (defcustom bbdb-define-all-aliases-field 'mail-alias
2629   "*The field which `bbdb-define-all-aliases' searches for."
2630   :group 'bbdb
2631   :type 'symbol)
2632
2633 (defcustom bbdb-define-all-aliases-mode 'first
2634   "*The type of alias which are created.
2635 first: Default is to generate an abbrev which is \"alias\" and expands to the
2636        primary net.
2637 star:  Generate an extra alias \"<alias>*\" which expands to all nets of an
2638        record.
2639 all:   Generate an alias all nets (as for 'star) and an alias for each net
2640        as \"<alias>n\" where n is the position of the net in the nets of the
2641        record."
2642   :group 'bbdb
2643   :type '(choice (symbol :tag "Only first" first)
2644                  (symbol :tag "<alias>* for all nets" star)
2645                  (symbol :tag "All aliases" all)))
2646
2647 ;;;###autoload
2648 (defun bbdb-define-all-aliases ()
2649   "Define mail aliases for some of the records in the database.
2650 Every record which has a `mail-alias' field will have a mail alias
2651 defined for it which is the contents of that field.  If there are
2652 multiple comma-separated words in the `mail-alias' field, then all
2653 of those words will be defined as aliases for that person.
2654
2655 If multiple entries in the database have the same mail alias, then
2656 that alias expands to a comma-separated list of the network addresses
2657 of all of those people."
2658   (interactive "")
2659   (let* ((target (cons bbdb-define-all-aliases-field "."))
2660          (use-abbrev-p (fboundp 'define-mail-abbrev))
2661          (mail-alias-separator-string (if (boundp 'mail-alias-separator-string)
2662                                           mail-alias-separator-string
2663                                         ", "))
2664          (records (bbdb-search (bbdb-records) nil nil nil target))
2665          result record aliases match)
2666
2667     (if use-abbrev-p
2668         nil
2669       ;; clear abbrev-table
2670       (setq mail-aliases nil)
2671       ;; arrange rebuilt if necessary, this should be done by
2672       ;; mail-pre-abbrev-expand-hook, but there is none!
2673       (defadvice sendmail-pre-abbrev-expand-hook
2674         (before bbdb-rebuilt-all-aliases activate)
2675         (bbdb-rebuilt-all-aliases)))
2676
2677     ;; collect an alist of (alias rec1 [rec2 ...])
2678     (while records
2679       (setq record (car records))
2680       (if (bbdb-record-net record)
2681           (setq aliases (bbdb-split
2682                          (bbdb-record-getprop record
2683                                               bbdb-define-all-aliases-field)
2684                          ","))
2685         (if (not bbdb-silent-running)
2686             (bbdb-warn "record %S has no network address, but the aliases: %s"
2687                         (bbdb-record-name record)
2688                         (bbdb-record-getprop record
2689                                              bbdb-define-all-aliases-field)))
2690         (setq aliases nil))
2691
2692       (while aliases
2693         (if (setq match (assoc (car aliases) result))
2694             (nconc match (cons record nil))
2695           (setq result (cons (list (car aliases) record) result)))
2696         (setq aliases (cdr aliases)))
2697       (setq records (cdr records)))
2698
2699     ;; iterate over the results and create the aliases
2700     (while result
2701       (let* ((aliasstem (caar result))
2702              (rec (cadar result))
2703              (group-alias-p (cddar result))
2704              (nets (if (not group-alias-p) (bbdb-record-net rec)))
2705              (expansions
2706               (if group-alias-p
2707                   (mapcar (lambda (r) (bbdb-dwim-net-address r)) (cdar result))
2708                 (mapcar (lambda (net) (bbdb-dwim-net-address rec net))
2709                         (if (eq 'all bbdb-define-all-aliases-mode)
2710                             nets
2711                           (list (car nets))))))
2712              (count 1)
2713              alias expansion)
2714
2715         (if group-alias-p
2716             ;; for group aliases we just take all the primary nets and define
2717             ;; just one expansion!
2718             (setq expansions (list (mapconcat 'identity expansions
2719                                               mail-alias-separator-string)))
2720           ;; this is an alias for a single person so deal with it according to
2721           ;; the bbdb-define-all-aliases-mode
2722           (when (or (not (eq 'first bbdb-define-all-aliases-mode))
2723                     (setq expansions
2724                           (cons (mapconcat 'identity
2725                                            (mapcar (lambda (net)
2726                                                      (bbdb-dwim-net-address
2727                                                       rec net))
2728                                                    nets)
2729                                            mail-alias-separator-string)
2730                                 expansions)
2731                           count 0))))
2732
2733         ;; create the aliases for each expansion
2734         (while expansions
2735           (cond ((= count 0);; all the nets of a record
2736                  (setq alias (concat aliasstem "*")))
2737                 ((= count 1);; expansion as usual
2738                  (setq alias aliasstem))
2739                 (t;; alias# for each net of a record
2740                  (setq alias (format "%s%s" aliasstem count))))
2741           (setq count (1+ count))
2742           (setq expansion (car expansions))
2743
2744           (if use-abbrev-p
2745               (define-mail-abbrev alias expansion)
2746             (define-mail-alias alias expansion))
2747           (setq alias (or (intern-soft (downcase alias)
2748                                        (if use-abbrev-p
2749                                            mail-abbrevs mail-aliases))
2750                           (error "couldn't find the alias we just defined!")))
2751
2752           (or (eq (symbol-function alias) 'mail-abbrev-expand-hook)
2753               (error "mail-aliases contains unexpected hook %s"
2754                      (symbol-function alias)))
2755           ;; The abbrev-hook is called with network addresses instead of bbdb
2756           ;; records to avoid keeping pointers to records, which would lose if
2757           ;; the database was reverted.  It uses -search-simple to convert
2758           ;; these to records, which is plenty fast.
2759           (fset alias (list 'lambda '()
2760                             (list 'bbdb-mail-abbrev-expand-hook
2761                                   alias
2762                                   (list 'quote
2763                                         (mapcar (lambda (x)
2764                                                   (car (bbdb-record-net x)))
2765                                                 (cdr (car result)))))))
2766           (setq expansions (cdr expansions))))
2767       (setq result (cdr result)))
2768
2769     (when (not use-abbrev-p)
2770       (if (boundp 'mail-mode-header-syntax-table)
2771           (modify-syntax-entry ?* "w" mail-mode-header-syntax-table))
2772       (sendmail-pre-abbrev-expand-hook))))
2773
2774 ;; We should be cleverer here and instead of rebuilding all aliases we should
2775 ;; just do what's necessary, i.e. remove deleted records and add new records
2776 (defun bbdb-rebuilt-all-aliases ()
2777   (let ((needs-rebuilt bbdb-define-all-aliases-needs-rebuilt))
2778     (when needs-rebuilt
2779       (if (not bbdb-silent-running)
2780           (message "Rebuilding aliases due to %s aliases." needs-rebuilt))
2781       (setq bbdb-define-all-aliases-needs-rebuilt nil)
2782       (bbdb-define-all-aliases))))
2783
2784 (defcustom bbdb-mail-abbrev-expand-hook nil
2785   "*Hook or hooks invoked each time an alias is expanded.
2786 The hook is called with two arguments the alias and the list of records."
2787   :group 'bbdb-hooks
2788   :type 'hook)
2789
2790 (defun bbdb-mail-abbrev-expand-hook (alias records)
2791   (run-hook-with-args 'bbdb-mail-abbrev-expand-hook alias records)
2792   (mail-abbrev-expand-hook)
2793   (when bbdb-completion-display-record
2794     (if bbdb-use-pop-up
2795         (bbdb-pop-up-bbdb-buffer))
2796     (let ((bbdb-gag-messages t))
2797       (bbdb-display-records-1
2798        (mapcar (lambda (x) (bbdb-search-simple nil x)) records)
2799        t))))
2800
2801 (defun bbdb-get-mail-aliases ()
2802   "Return a list of mail aliases used in the BBDB.
2803 The format is suitable for `completing-read'."
2804   (let* ((target (cons bbdb-define-all-aliases-field "."))
2805          (records (bbdb-search (bbdb-records) nil nil nil target))
2806          result aliases)
2807     (while records
2808       (setq aliases (bbdb-split
2809                      (bbdb-record-getprop (car records)
2810                                           bbdb-define-all-aliases-field)
2811                      ","))
2812       (while aliases
2813         (add-to-list 'result (list (car aliases)))
2814         (setq aliases (cdr aliases)))
2815       (setq records (cdr records)))
2816     result))
2817
2818 ;;;###autoload
2819 (defun bbdb-add-or-remove-mail-alias (&optional records newalias delete)
2820   "Add NEWALIAS in all RECORDS or remove it if DELETE it t.
2821 When called with prefix argument it will remove the alias.
2822 We honor `bbdb-apply-next-command-to-all-records'!
2823 The new alias will only be added if it isn't there yet."
2824   (interactive (list (if (bbdb-do-all-records-p) 'all 'one)
2825                      (completing-read
2826                       (format "%s mail alias: " (if current-prefix-arg "Remove" "Add"))
2827                       (bbdb-get-mail-aliases))
2828                      current-prefix-arg))
2829   (setq newalias (bbdb-string-trim newalias))
2830   (setq newalias (if (string= "" newalias) nil newalias))
2831   (let* ((propsym bbdb-define-all-aliases-field)
2832          (do-all-p (if (equal records 'one) nil t))
2833          (records (cond ((equal records 'all) (mapcar 'car bbdb-records))
2834                         ((equal records 'one) (list (bbdb-current-record t)))
2835                         (t records))))
2836     (while records
2837       (let* ((record (car records))
2838              (oldaliases (bbdb-record-getprop record propsym)))
2839         (if oldaliases (setq oldaliases (bbdb-split oldaliases ",")))
2840         (if delete (setq oldaliases (delete newalias oldaliases))
2841           (add-to-list 'oldaliases newalias))
2842         (setq oldaliases (bbdb-join oldaliases ", "))
2843         (bbdb-record-putprop record propsym oldaliases))
2844       (setq records (cdr records)))
2845     (if do-all-p
2846         (bbdb-redisplay-records)
2847       (bbdb-redisplay-one-record (bbdb-current-record))))
2848   (setq bbdb-define-all-aliases-needs-rebuilt
2849         (if delete
2850             'deleted
2851           (if (bbdb-record-net (bbdb-current-record))
2852               'new
2853             nil))))
2854 \f
2855 ;;; Dialing numbers from BBDB
2856 (defcustom bbdb-dial-local-prefix-alist
2857   '(((if (integerp bbdb-default-area-code)
2858          (format "(%03d)" bbdb-default-area-code)
2859        (or bbdb-default-area-code ""))
2860      ""))
2861   "Mapping to remove local prefixes from numbers.
2862 If this is non-nil, it should be an alist of
2863 (PREFIX REPLACEMENT) elements. The first part of a phone number
2864 matching the regexp returned by evaluating PREFIX will be replaced by
2865 the corresponding REPLACEMENT when dialing."
2866   :group 'bbdb-phone-dialing
2867   :type 'sexp)
2868
2869 (defcustom bbdb-dial-local-prefix nil
2870   "Local prefix digits.
2871 If this is non-nil, it should be a string of digits which your phone
2872 system requires before making local calls (for example, if your phone system
2873 requires you to dial 9 before making outside calls.) In BBDB's
2874 opinion, you're dialing a local number if it starts with a 0 after
2875 processing bbdb-dial-local-prefix-alist."
2876   :group 'bbdb-phone-dialing
2877   :type '(choice (const :tag "No digits required" nil)
2878                  (string :tag "Dial this first" "9")))
2879
2880 (defcustom bbdb-dial-long-distance-prefix nil
2881   "Long distance prefix digits.
2882 If this is non-nil, it should be a string of digits which your phone
2883 system requires before making a long distance call (one not in your local
2884 area code).  For example, in some areas you must dial 1 before an area
2885 code. Note that this is used to replace the + sign in phone numbers
2886 when dialling (international dialing prefix.)"
2887   :group 'bbdb-phone-dialing
2888   :type '(choice (const :tag "No digits required" nil)
2889                  (string :tag "Dial this first" "1")))
2890
2891 (defcustom bbdb-sound-player nil
2892   "The program to be used to play the sounds for the touch-tone digits."
2893   :group 'bbdb-phone-dialing
2894   :type '(choice (const :tag "No External Player" nil)
2895                  (file :tag "Sound Player" "/usr/local/bin/play")))
2896
2897 (defcustom bbdb-sound-files
2898   '["/usr/demo/SOUND/sounds/touchtone.0.au"
2899     "/usr/demo/SOUND/sounds/touchtone.1.au"
2900     "/usr/demo/SOUND/sounds/touchtone.2.au"
2901     "/usr/demo/SOUND/sounds/touchtone.3.au"
2902     "/usr/demo/SOUND/sounds/touchtone.4.au"
2903     "/usr/demo/SOUND/sounds/touchtone.5.au"
2904     "/usr/demo/SOUND/sounds/touchtone.6.au"
2905     "/usr/demo/SOUND/sounds/touchtone.7.au"
2906     "/usr/demo/SOUND/sounds/touchtone.8.au"
2907     "/usr/demo/SOUND/sounds/touchtone.9.au"
2908     "/usr/demo/SOUND/sounds/touchtone.pound.au"
2909     "/usr/demo/SOUND/sounds/touchtone.star.au"]
2910   "A vector of ten sound files to be used for dialing.  They
2911 correspond to the 0, 1, 2, ... 9 digits, pound and star, respectively."
2912   :group 'bbdb-phone-dialing
2913   :type 'vector)
2914
2915 (defcustom bbdb-modem-dial nil
2916   "Type of dialing to use.
2917 If this value is nil, the audio device is used for dialing. Otherwise,
2918 this string is fed to the modem before the phone number digits."
2919   :group 'bbdb-phone-dialing
2920   :type '(choice (const  :tag "audio" nil)
2921                  (string :tag "tone dialing" "ATDT ")
2922                  (string :tag "pulse dialing" "ATDP ")))
2923
2924 (defcustom bbdb-modem-device "/dev/modem"
2925   "The name of the modem device.
2926 This is only used if bbdb-modem-dial is set to something other than nil."
2927   :group 'bbdb-phone-dialing
2928   :type 'string)
2929
2930 (defcustom bbdb-sound-volume 50
2931   "The volume to play back dial tones at. The range is 0 to 100.
2932 This is only used if bbdb-modem-dial is set to nil."
2933   :group 'bbdb-phone-dialing
2934   :type 'integer)
2935
2936 (defun bbdb-play-sound (num &optional volume)
2937   "Play the specified touchtone number NUM at VOLUME.
2938 Uses external program `bbdb-sound-player' if set, otherwise
2939 try to use internal sound if available."
2940   (if (and (not bbdb-sound-player) (featurep 'native-sound))
2941       ;; This requires the sound files to be loaded via bbdb-xemacs.
2942       (funcall 'play-sound (intern (format "touchtone%d" num))
2943                bbdb-sound-volume)
2944     (if (and (not (featurep 'xemacs))
2945              ;; We can't tell a priori if Emacs 21 facility will
2946              ;; actually work.
2947              (condition-case nil
2948                  (play-sound (list 'sound
2949                                    :file (aref bbdb-sound-files
2950                                                (string-to-number num))
2951                                    :volume (or volume bbdb-sound-volume)))
2952                (error nil)))
2953         (if (and bbdb-sound-player
2954                  (file-exists-p bbdb-sound-player))
2955             (call-process bbdb-sound-player nil nil nil
2956                           (aref bbdb-sound-files num))
2957           (error "BBDB has no means of playing sound.")))))
2958
2959 (eval-and-compile
2960   (if (fboundp 'next-event)
2961       (fset 'bbdb-next-event 'next-event)
2962     (fset 'bbdb-next-event 'read-event)))
2963
2964 (defun bbdb-dial-number (phone-string)
2965   "Dial the number specified by PHONE-STRING.
2966 The number is dialed either by playing touchtones through the audio
2967 device using bbdb-sound-player, or by sending a dial sequence to
2968 bbdb-modem-device. # and * are dialed as-is, and a space is treated as
2969 a pause in the dial sequence."
2970   (interactive "sDial number: ")
2971   (let ((dialed ""))
2972     (mapcar
2973      (lambda(d)
2974        (if bbdb-modem-dial
2975            (setq dialed
2976                  (concat dialed
2977                          (cond ((eq ?  d) ",")
2978                                ((memq d '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?* ?#))
2979                                 (format "%c" d))
2980                                (t ""))))
2981          (cond
2982           ((eq ?# d)
2983            (bbdb-play-sound 10))
2984           ((eq ?* d)
2985            (bbdb-play-sound 11))
2986           ((eq ?  d)
2987            ;; if we use sit-for, the user can interrupt!
2988            (sleep-for 1)) ;; configurable?
2989           ((memq d '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9))
2990            (bbdb-play-sound (- (char-int d) (char-int ?0))))
2991           (t)))) phone-string)
2992
2993     ;; tell the user that we're dialed, if we're using the modem
2994     (if bbdb-modem-dial
2995         (with-temp-buffer
2996           (insert bbdb-modem-dial dialed ";\r\n")
2997           (write-region (point-min) (point-max) bbdb-modem-device t)
2998           (message "%s dialed. Pick up the phone now and hit any key ..."
2999                    phone-string)
3000           (bbdb-next-event)
3001           (erase-buffer)
3002           (insert "ATH\r\n")
3003           (write-region (point-min) (point-max) bbdb-modem-device t)))))
3004
3005 ;;;###autoload
3006 (defun bbdb-dial (phone force-area-code)
3007   "Dial the number at point.
3008 If the point is at the beginning of a record, dial the first
3009 phone number.  Does not dial the extension.  Does not apply the
3010 transformations from bbdb-dial-local-prefix-alist if a prefix arg
3011 is given."
3012   (interactive (list (bbdb-current-field)
3013                      current-prefix-arg))
3014   (if (eq (car-safe phone) 'name)
3015       (setq phone (car (bbdb-record-phones (bbdb-current-record)))))
3016   (if (eq (car-safe phone) 'phone)
3017       (setq phone (car (cdr phone))))
3018   (or (vectorp phone) (error "not on a phone field"))
3019
3020   (let* ((number (bbdb-phone-string phone)) shortnumber)
3021     (when (not force-area-code)
3022       (let ((alist bbdb-dial-local-prefix-alist))
3023         (while alist
3024           (if (string-match (concat "^" (eval (caar alist))) number)
3025               (setq shortnumber (concat (car (cdar alist))
3026                                         (substring number (match-end 0)))
3027                     alist nil))
3028           (setq alist (cdr alist)))))
3029
3030     ;; cut off the extension
3031     (if (string-match "x[0-9]+$" number)
3032         (setq number (substring number 0 (match-beginning 0))))
3033
3034     ;; This is terrifically Americanized...
3035     ;; Leading 0 => local number (?)
3036     (if (and (not shortnumber) bbdb-dial-local-prefix
3037              (string-match "^0" number))
3038         (setq number (concat bbdb-dial-local-prefix number)))
3039
3040     ;; Leading + => long distance/international number
3041     (if (and (not shortnumber) bbdb-dial-long-distance-prefix
3042              (string-match "^\+" number))
3043         (setq number (concat bbdb-dial-long-distance-prefix " "
3044                              (substring number 1))))
3045
3046     ;; use the short number if it's available
3047     (setq number (or shortnumber number))
3048     (if (not bbdb-silent-running)
3049         (message "Dialing %s" number))
3050     (bbdb-dial-number number)))
3051
3052 \f
3053 ;; not sure what this is doing here...
3054 (defun bbdb-get-record (prompt)
3055   "Get the current record or ask the user.
3056 To be used in `interactive' like this:
3057 (interactive (list (bbdb-get-record \"look up ...\")))"
3058   (if (and (boundp 'bbdb-buffer-name)
3059 (string= bbdb-buffer-name (buffer-name)))
3060 (bbdb-current-record)
3061 (let (re (pr ""))
3062   (while (not re)
3063     (setq re (bbdb-completing-read-record (concat pr prompt)))
3064     (unless re (ding)) (setq pr "Invalid response! ")) re)))
3065
3066 ;;; Finger, based on code by Sam Cramer <cramer@sun.com>.
3067 ;;; Note that process-death bugs in 18.57 may make this eat up all the cpu...
3068
3069 (defcustom bbdb-finger-buffer-name "*finger*"
3070   "The buffer into which finger output should be directed."
3071   :group 'bbdb-utilities-finger
3072   :type 'string)
3073
3074 (defun bbdb-finger-internal (address)
3075   (message "Fingering %s..." address)
3076   (condition-case condition
3077       (let* ((@ (string-match "@" address))
3078              (stream (open-network-stream
3079                       "finger" bbdb-finger-buffer-name
3080                       (if @ (substring address (1+ @)) "localhost")
3081                       "finger")))
3082         (set-process-sentinel stream 'bbdb-finger-process-sentinel)
3083         (princ (concat "finger " address "\n"))
3084         (process-send-string
3085          stream (concat;;"/W " ; cs.stanford.edu doesn't like this...
3086                  (if @ (substring address 0 @) address) "\n"))
3087         (process-send-eof stream))
3088     (error
3089      (princ (format "error fingering %s: %s\n" address
3090                     (if (stringp condition) condition
3091                       (concat "\n" (nth 1 condition)
3092                               (if (cdr (cdr condition)) ": ")
3093                               (mapconcat '(lambda (x)
3094                                             (if (stringp x) x
3095                                               (prin1-to-string x)))
3096                                          (cdr (cdr condition)) ", ")))))
3097      (bbdb-finger-process-sentinel nil nil)))) ; hackaroonie
3098
3099 (defvar bbdb-remaining-addrs-to-finger)
3100 (defun bbdb-finger-process-sentinel (process s)
3101   (save-excursion
3102     (set-buffer bbdb-finger-buffer-name)
3103     (goto-char (point-min))
3104     (while (search-forward "\r" nil t)
3105       (delete-char -1))
3106     (if (and (boundp 'bbdb-remaining-addrs-to-finger)
3107              bbdb-remaining-addrs-to-finger)
3108         (let ((addr (car bbdb-remaining-addrs-to-finger)))
3109           (setq bbdb-remaining-addrs-to-finger
3110                 (cdr bbdb-remaining-addrs-to-finger))
3111           (goto-char (point-max))
3112           (let ((standard-output (current-buffer)))
3113             (princ "\n\n\^L\n")
3114             (bbdb-finger-internal addr)))
3115       (goto-char (point-max))
3116       (message "Finger done."))))
3117
3118 (defcustom bbdb-finger-host-field 'finger-host
3119   "*The field for special net addresses used by \"\\[bbdb-finger]\"."
3120   :group 'bbdb-utilities-finger
3121   :type 'symbol)
3122
3123 (defun bbdb-record-finger-host (record)
3124   (let ((finger-host (and bbdb-finger-host-field
3125                           (bbdb-record-getprop record bbdb-finger-host-field))))
3126     (if finger-host
3127         (bbdb-split finger-host ",")
3128       (bbdb-record-net record))))
3129
3130 ;;;###autoload
3131 (defun bbdb-finger (record &optional which-address)
3132   "Finger the network address of a BBDB record.
3133 If this command is executed from the *BBDB* buffer, finger the network
3134 address of the record at point; otherwise, it prompts for a user.
3135 With a numeric prefix argument, finger the Nth network address of the
3136 current record\; with a prefix argument of ^U, finger all of them.
3137 The *finger* buffer is filled asynchronously, meaning that you don't
3138 have to wait around for it to finish\; but fingering another user before
3139 the first finger has finished could have unpredictable results.
3140 \\<bbdb-mode-map>
3141 If this command is executed from the *BBDB* buffer, it may be prefixed
3142 with \"\\[bbdb-apply-next-command-to-all-records]\" \(as in \
3143 \"\\[bbdb-apply-next-command-to-all-records]\\[bbdb-finger]\" instead of \
3144 simply \"\\[bbdb-finger]\"\), meaning to finger all of
3145 the users currently listed in the *BBDB* buffer instead of just the one
3146 at point.  The numeric prefix argument has the same interpretation.
3147
3148 You can define a special network address to \"finger\" by defining a
3149 field `finger-host' (default value of `bbdb-finger-host-field')."
3150   (interactive (list (bbdb-get-record "BBDB Finger: ")
3151                      current-prefix-arg))
3152   (if (not (consp record)) (setq record (list record)))
3153   (let ((addrs nil))
3154     (while record
3155       (cond ((null which-address)
3156              (setq addrs
3157                    (nconc addrs
3158                           (list (car (bbdb-record-finger-host (car record)))))))
3159             ((stringp which-address)
3160              (setq addrs (nconc addrs (list which-address))))
3161             ((numberp which-address)
3162              (setq addrs
3163                    (nconc addrs
3164                           (list (nth which-address
3165                                      (bbdb-record-finger-host (car record)))))))
3166             (t
3167              (setq addrs
3168                    (nconc addrs
3169                           (copy-sequence (bbdb-record-finger-host
3170                                           (car record)))))))
3171       (setq record (cdr record)))
3172     (if (car addrs)
3173         (save-excursion
3174           (with-output-to-temp-buffer bbdb-finger-buffer-name
3175             (set-buffer bbdb-finger-buffer-name)
3176             (make-local-variable 'bbdb-remaining-addrs-to-finger)
3177             (setq bbdb-remaining-addrs-to-finger (cdr addrs))
3178             (bbdb-finger-internal (car addrs))))
3179       (error "Nothing to finger!"))))
3180
3181
3182 (defun bbdb-remove-duplicate-nets (records)
3183   "*Remove duplicate nets from a record."
3184   (interactive (if (bbdb-do-all-records-p)
3185                    (mapcar 'car bbdb-records)
3186                  (bbdb-current-record)))
3187   (let (nets cnets)
3188     (while records
3189       (setq nets (bbdb-record-net (car records))
3190             cnets nil)
3191       (while nets
3192         (add-to-list 'cnets (car nets))
3193         (setq nets (cdr nets)))
3194       (bbdb-record-set-net (car records) cnets)
3195       (setq records (cdr records)))))
3196
3197 (defun bbdb-find-duplicates (&optional fields)
3198   "Find all records that have duplicate entries for given FIELDS.
3199 FIELDS should be a list of the symbols `name', `net', and/or `aka'.
3200 Note that overlap between these fields is noted if either is selected,
3201 most common case `aka' and `name'.  If FIELDS is not given it
3202 defaults to all of them.
3203
3204 The results of the search is returned as a list of records."
3205   (setq fields (or fields '(name net aka)))
3206   (let ((records (bbdb-records))
3207         rec hash ret)
3208     (while records
3209       (setq rec (car records))
3210
3211       (when (and (memq 'name fields)
3212                  (bbdb-record-name rec)
3213                  (setq hash (bbdb-gethash (downcase (bbdb-record-name rec))))
3214                  (> (length hash) 1))
3215         (setq ret (append hash ret))
3216         (message "BBDB record `%s' causes duplicates, maybe it is equal to a company name."
3217                  (bbdb-record-name rec))
3218         (sit-for 0))
3219
3220       (if (memq 'net fields)
3221           (let ((nets (bbdb-record-net rec)))
3222             (while nets
3223               (setq hash (bbdb-gethash (downcase (car nets))))
3224               (when (> (length hash) 1)
3225                 (setq ret (append hash ret))
3226                 (message "BBDB record `%s' has duplicate net `%s'."
3227                          (bbdb-record-name rec) (car nets))
3228                 (sit-for 0))
3229               (setq nets (cdr nets)))))
3230
3231       (if (memq 'aka fields)
3232           (let ((aka (bbdb-record-aka rec)))
3233             (while aka
3234               (setq hash (bbdb-gethash (downcase (car aka))))
3235               (when (> (length hash) 1)
3236                 (setq ret (append hash ret))
3237                 (message "BBDB record `%s' has duplicate aka `%s'"
3238                          (bbdb-record-name rec) (car aka))
3239                 (sit-for 0))
3240               (setq aka (cdr aka)))))
3241
3242       (setq records (cdr records)))
3243     (reverse (bbdb-remove-memq-duplicates ret))))
3244
3245 (defun bbdb-show-duplicates (&optional fields)
3246   "*Find all records that have duplicate entries for given FIELDS.
3247 FIELDS should be a list of the symbols `name', `net', and/or `aka'.
3248 Note that overlap between these fields is noted if either is selected
3249 (most common case `aka' and `name').  If FIELDS is not given it
3250 defaults to all of them.
3251
3252 The results are displayed in the bbdb buffer."
3253   (interactive)
3254   (setq fields (or fields '(name net aka)))
3255   (bbdb-display-records (bbdb-find-duplicates fields)))
3256
3257 ;;; Time-based functions
3258 (defun bbdb-kill-older (date &optional compare function)
3259   "*Apply FUNCTION to all records with timestamps older than DATE.
3260 The comparison is done with COMPARE.  If FUNCTION is not specified, the
3261 selected records are deleted.  If COMPARE is not specified,
3262 `string-lessp' is used.
3263
3264 Example:
3265         (bbdb-kill-older \"1997-01-01\")
3266 will delete all records with timestamps older than Jan 1 1997.
3267
3268 Notes:  1. Records without timestamp fields will be ignored
3269 2. DATE must be in yyyy-mm-dd format."
3270   (interactive "sKill records with timestamp older than (yyyy-mm-dd): \n")
3271   (let ((records (bbdb-records)) timestamp
3272         (fun (or function 'bbdb-delete-record-internal))
3273         (cmp (or compare 'string-lessp)))
3274     (while records
3275       (if (and (setq timestamp (bbdb-record-getprop (car records) 'timestamp))
3276                (funcall cmp timestamp date))
3277           (funcall fun (car records)))
3278       (setq records (cdr records)))))
3279
3280 (defmacro bbdb-compare-records (cmpval field compare)
3281   "Builds a lambda comparison function that takes one argument, REC.
3282 REC is returned if
3283 (COMPARE VALUE CMPVAL)
3284 is true, where VALUE is the value of the FIELD field of REC."
3285   `(lambda (rec)
3286 (let ((val (bbdb-record-getprop rec ,field)))
3287   (if (and val (,compare val ,cmpval))
3288       rec nil))))
3289
3290 ;;;###autoload
3291 (defun bbdb-timestamp-older (date)
3292   "*Display records with timestamp older than DATE.
3293 DATE must be in yyyy-mm-dd format."
3294   (interactive "sOlder than date (yyyy-mm-dd): ")
3295   (bbdb-display-some (bbdb-compare-records date 'timestamp string<)))
3296
3297 ;;;###autoload
3298 (defun bbdb-timestamp-newer (date)
3299   "*Display records with timestamp newer than DATE.
3300 DATE must be in yyyy-mm-dd format."
3301   (interactive "sNewer than date (yyyy-mm-dd): ")
3302   (bbdb-display-some (bbdb-compare-records date 'timestamp string>)))
3303
3304 ;;;###autoload
3305 (defun bbdb-creation-older (date)
3306   "*Display records with creation-date older than DATE.
3307 DATE must be in yyyy-mm-dd format."
3308   (interactive "sOlder than date (yyyy-mm-dd): ")
3309   (bbdb-display-some (bbdb-compare-records date 'creation-date string<)))
3310
3311 ;;;###autoload
3312 (defun bbdb-creation-newer (date)
3313   "*Display records with creation-date newer than DATE.
3314 DATE must be in yyyy-mm-dd format."
3315   (interactive "sNewer than date (yyyy-mm-dd): ")
3316   (bbdb-display-some (bbdb-compare-records date 'creation-date string>)))
3317
3318 ;;;###autoload
3319 (defun bbdb-creation-no-change ()
3320   "*Display records that have the same timestamp and creation-date."
3321   (interactive)
3322   (bbdb-display-some
3323    (bbdb-compare-records (bbdb-record-getprop rec 'timestamp)
3324                          'creation-date string=)))
3325
3326 ;;; Help and documentation
3327
3328 (defcustom bbdb-info-file nil
3329   "*Set this to the location of the bbdb info file, if it's not in the
3330 standard place."
3331   :group 'bbdb
3332   :type '(choice (const :tag "Standard location" nil)
3333                  (file :tag "New location")))
3334
3335 ;;;###autoload
3336 (defun bbdb-info ()
3337   (interactive)
3338   (require 'info)
3339   (if bbdb-inside-electric-display
3340       (bbdb-electric-throw-to-execute '(bbdb-info))
3341     (let ((file (or bbdb-info-file "bbdb")))
3342       (Info-goto-node (format "(%s)Top" file)))))
3343
3344 ;;;###autoload
3345 (defun bbdb-help ()
3346   (interactive)
3347   (message (substitute-command-keys "\\<bbdb-mode-map>\
3348 new field: \\[bbdb-insert-new-field]; \
3349 edit field: \\[bbdb-edit-current-field]; \
3350 delete field: \\[bbdb-delete-current-field-or-record]; \
3351 mode help: \\[describe-mode]; \
3352 info: \\[bbdb-info]")))
3353
3354
3355 (or (fboundp 'member);; v18 lossage
3356     (defun member (item list)
3357       (while (and list (not (equal item (car list)))) (setq list (cdr list)))
3358       list))
3359
3360
3361 ;;; If Sebastian Kremer's minibuffer history package is around, use it.
3362 (if (and (fboundp 'gmhist-make-magic)
3363          (string-lessp emacs-version "19")) ; v19 has history built in
3364     (mapcar 'gmhist-make-magic
3365             '(bbdb bbdb-name bbdb-company bbdb-net bbdb-changed)))
3366
3367 ;;;###autoload
3368 (defcustom bbdb-update-records-mode 'annotating
3369   "Controls how `bbdb-update-records' processes email addresses.
3370 Set this to an expression which evaluates either to 'searching or
3371 'annotating.  When set to 'annotating email addresses will be fed to
3372 `bbdb-annotate-message-sender' in order to update existing records or create
3373 new ones.  A value of 'searching will search just for existing records having
3374 the right net.
3375
3376 There is a version of this variable for each MUA, which overrides this variable
3377 when set!
3378
3379 This variable is also used for inter-function communication between the
3380 functions `bbdb-update-records' and `bbdb-prompt-for-create'."
3381   :group 'bbdb-mua-specific
3382   :type '(choice (const :tag "annotating all messages"
3383                         annotating)
3384                  (const :tag "annotating no messages"
3385                         searching)
3386                  (sexp   :tag "user defined")))
3387
3388 (defvar bbdb-offer-to-create nil
3389   "Used for inter-function communication between the functions
3390 `bbdb-update-records' and `bbdb-prompt-for-create'.")
3391 (defvar bbdb-address nil
3392   "Used for inter-function communication between the functions
3393 `bbdb-update-records' and `bbdb-prompt-for-create'.")
3394
3395 (defvar bbdb-update-address-class nil
3396   "Class of currently processed address as in `bbdb-get-addresses-headers'.
3397 The `bbdb-notice-hook' and `bbdb-create-hook' functions may utilize this to
3398 treat updates in the right way.")
3399
3400 (defvar bbdb-update-address-header nil
3401   "Header the currently processed address was extracted from.
3402 The `bbdb-notice-hook' and `bbdb-create-hook' functions may utilize this to
3403 treat updates in the right way.")
3404
3405 ;;;###autoload
3406 (defun bbdb-update-records (addrs auto-create-p offer-to-create)
3407   "Returns the records corresponding to the list of addresses ADDRS,
3408 creating or modifying them as necessary.  A record will be created if
3409 AUTO-CREATE-P is non-nil or if OFFER-TO-CREATE is true and the user
3410 confirms the creation.
3411
3412 The variable `bbdb/gnus-update-records-mode' controls what actions
3413 are performed and it might override `bbdb-update-records-mode'.
3414
3415 When hitting C-g once you will not be asked any more for new people listed
3416 in this message, but it will search only for existing records.  When hitting
3417 C-g again it will stop scanning."
3418   (setq auto-create-p (bbdb-invoke-hook-for-value auto-create-p))
3419
3420   (let ((bbdb-records (bbdb-records))
3421         (processed-addresses 0)
3422         (bbdb-offer-to-create (or offer-to-create (eq 'prompt auto-create-p)))
3423         (bbdb-update-records-mode
3424          (if offer-to-create 'annotating
3425            (if (listp bbdb-update-records-mode)
3426                (eval bbdb-update-records-mode)
3427              bbdb-update-records-mode)))
3428         (addrslen (length addrs))
3429         (bbdb-update-address-class nil)
3430         (bbdb-update-address-header nil)
3431         records hits)
3432
3433     (while addrs
3434
3435       (setq bbdb-address (car addrs)
3436             bbdb-update-address-class (car bbdb-address)
3437             bbdb-update-address-header (cadr bbdb-address)
3438             bbdb-address (caddr bbdb-address))
3439
3440       (condition-case nil
3441           (progn
3442             (setq hits
3443                   (cond ((null (cadr bbdb-address))
3444                          ;; ignore emtpy addrs, e.g. (??? nil)
3445                          nil)
3446                         ((eq bbdb-update-records-mode 'annotating)
3447                          (list;; search might return a list
3448                           (bbdb-annotate-message-sender
3449                            bbdb-address t
3450                            (or offer-to-create;; force create
3451                                auto-create-p)
3452                            'bbdb-prompt-for-create)))
3453                         ((eq bbdb-update-records-mode 'searching)
3454                          ;; search for records having this net
3455                          (let ((net (concat "^"
3456                                             (regexp-quote
3457                                              (cadr bbdb-address))
3458                                             "$"))
3459                                ;; there is no case for nets
3460                                (bbdb-case-fold-search t))
3461                            (bbdb-search bbdb-records nil nil net))))
3462                   processed-addresses (+ processed-addresses 1))
3463
3464             (when (and (not bbdb-silent-running)
3465                        (not bbdb-gag-messages)
3466                        (not (eq bbdb-offer-to-create 'quit))
3467                        (= 0 (% processed-addresses 5)))
3468               (let ((mess (format "Hit C-g to stop BBDB from %s.  %d of %d addresses processed."
3469                                   bbdb-update-records-mode processed-addresses addrslen)))
3470                 (if (featurep 'xemacs)
3471                     (bbdb-display-message 'progress mess)
3472                   (message mess)))
3473               (sit-for 0)))
3474
3475         ;; o.k. there was a quit signal so how should we proceed now?
3476         (quit (cond ((eq bbdb-update-records-mode 'annotating)
3477                      (setq bbdb-update-records-mode 'searching))
3478                     ((eq bbdb-update-records-mode 'searching)
3479                      nil)
3480                     ((eq bbdb-update-records-mode 'next)
3481                      (setq bbdb-update-records-mode 'annotating))
3482                     (t
3483                      (setq bbdb-update-records-mode 'quit)))
3484               nil))
3485
3486       (while hits
3487         ;; people should be listed only once so we use add-to-list
3488         (if (car hits) (add-to-list 'records (car hits)))
3489         (setq hits (cdr hits)))
3490
3491       (setq addrs (cdr addrs)))
3492
3493     ;; add-to-list adds at the front so we have to reverse the list in order
3494     ;; to reflect the order of the records as they appear in the headers.
3495     (setq records (nreverse records))
3496
3497     records))
3498
3499 (defun bbdb-get-help-window (message)
3500   "Display MESSAGE in a new window which is the last one in the current frame."
3501   (let ((b (get-buffer-create " *BBDB Help*"))
3502         (w (or (get-buffer-window " *BBDB Help*")
3503                (get-lru-window)))
3504         (lines (let ((l 2) (s 0))
3505                  (while (setq s (string-match "\n" message s))
3506                    (setq s (1+ s) l (1+ l)))
3507                  l)))
3508
3509     (setq w (split-window w))
3510     (select-window w)
3511     (switch-to-buffer b)
3512     (erase-buffer)
3513     (insert message)
3514     (goto-char (point-min))
3515     (let ((window-min-height 1))
3516       (enlarge-window (- lines (window-height w))))
3517     w))
3518
3519 (defun bbdb-kill-help-window (window)
3520   "Kill the buffer corresponding to WINDOW and delete the WINDOW."
3521   (kill-buffer (window-buffer window))
3522   (delete-window window))
3523
3524 ;; This is a hack.  The function is called by bbdb-annotate-message-sender and
3525 ;; uses the above variable in order to manipulate bbdb-update-records.
3526 ;; Some cases are handled with signals in order to keep the changes in
3527 ;; bbdb-annotate-message-sender as minimal as possible.
3528
3529 (defun bbdb-prompt-for-create ()
3530   "This function is used by `bbdb-update-records' to ask the user how to
3531 proceed the processing of records.
3532
3533 It is called from `bbdb-annotate-message-sender' (PROMPT-FOR-CREATE arg) and
3534 returns `t' if the record should be created or `nil' otherwise.  It honors a
3535 previous answer, e.g. \"!\" add all ..."
3536   (let ((old-offer-to-create bbdb-offer-to-create)
3537         event prompt)
3538     (when bbdb-offer-to-create
3539       (when (not (integerp bbdb-offer-to-create))
3540         (setq prompt (format "%s is not in the db; add? (y,!,n,s,q,?) "
3541                              (or (car bbdb-address) (cadr bbdb-address))))
3542         (while (not event)
3543           (setq event (read-key-sequence prompt))
3544           (if (featurep 'xemacs)
3545               (setq event (bbdb-event-to-character (aref event 0)))
3546             (setq event (if (stringp event) (aref event 0)))))
3547
3548         (setq bbdb-offer-to-create event))
3549       (message "");; clear the message buffer
3550
3551       (cond ((eq bbdb-offer-to-create ?y)
3552              (setq bbdb-offer-to-create old-offer-to-create)
3553              t)
3554             ((eq bbdb-offer-to-create  ?!)
3555              t)
3556             ((or (eq bbdb-offer-to-create  ?n)
3557                  (eq bbdb-offer-to-create  ? ))
3558              (setq bbdb-update-records-mode 'next
3559                    bbdb-offer-to-create old-offer-to-create)
3560              (signal 'quit nil))
3561             ((eq bbdb-offer-to-create  ?q)
3562              (setq bbdb-update-records-mode 'quit)
3563              (signal 'quit nil))
3564             ((eq bbdb-offer-to-create  ?s)
3565              (setq bbdb-update-records-mode 'searching)
3566              (signal 'quit nil))
3567             (t
3568              (let ((w (bbdb-get-help-window
3569                        "Your answer controls how BBDB updates/searches for records.
3570
3571 Type ?  for this help.
3572 Type y  to add the current record.
3573 Type !  to add all remaining records.
3574 Type n  to skip the current record. (You might also type space)
3575 Type s  to switch from annotate to search mode.
3576 Type q  to quit updating records.  No more search or annotation is done.")))
3577                (setq bbdb-offer-to-create nil)
3578                (condition-case error
3579                    (progn (bbdb-prompt-for-create)
3580                           (bbdb-kill-help-window w))
3581                  (t
3582                   (bbdb-kill-help-window w)
3583                   (apply 'signal error)))))))))
3584
3585 ;;;###autoload
3586 (defcustom bbdb-get-addresses-headers
3587   '((authors    . ("From" "Resent-From" "Reply-To"))
3588     (recipients . ("Resent-To" "Resent-CC" "To" "CC" "BCC")))
3589   "*List of headers to search for senders and recipients email addresses.
3590 The headers are grouped into two classes, the authors and the senders headers."
3591   :group 'bbdb-mua-specific
3592   :type 'list)
3593
3594 ;;;###autoload
3595 (defcustom bbdb-get-only-first-address-p
3596   t
3597   "*If t `bbdb-update-records' will return only the first one.
3598 Changing this variable will show its effect only after clearing the
3599 `bbdb-message-cache' of a folder or closing and visiting it again."
3600   :group 'bbdb-mua-specific
3601   :type 'boolean)
3602
3603 (defun bbdb-get-addresses (only-first-address
3604                            uninteresting-senders
3605                            get-header-content-function
3606                            &rest get-header-content-function-args)
3607   ""
3608   (let ((headers bbdb-get-addresses-headers)
3609         (ignore-senders (or bbdb-user-mail-names uninteresting-senders))
3610         addrlist adlist fn ad
3611         header-type header-fields header-content)
3612     (while headers
3613       (setq header-type (caar headers)
3614             header-fields (cdar headers))
3615       (while header-fields
3616         (setq header-content (apply get-header-content-function
3617                                     (car header-fields)
3618                                     get-header-content-function-args))
3619         (when header-content
3620           (setq adlist (funcall bbdb-extract-address-components-func
3621                                 header-content))
3622           (while adlist
3623             (setq fn (caar adlist)
3624                   ad (car (cdar adlist)))
3625
3626             ;; ignore uninteresting addresses, this is kinda gross!
3627             (if (or (not (stringp ignore-senders))
3628                     (not (or (and fn (string-match ignore-senders fn))
3629                              (and ad (string-match ignore-senders ad)))))
3630                 (add-to-list 'addrlist
3631                              (list header-type
3632                                    (car header-fields)
3633                                    (car adlist))))
3634
3635             (if (and only-first-address addrlist)
3636                 (setq adlist nil headers nil)
3637               (setq adlist (cdr adlist)))))
3638         (setq header-fields (cdr header-fields)))
3639       (setq headers (cdr headers)))
3640     (nreverse addrlist)))
3641
3642 (provide 'bbdb-com)