1 ;;; emchat-buddy.el --- "Buddy" code for EMchat
3 ;; Copyright (C) 2007 - 2011 Steve Youngs
5 ;; Author: Steve Youngs <steve@emchat.org>
6 ;; Maintainer: Steve Youngs <steve@emchat.org>
8 ;; Homepage: http://www.emchat.org/
11 ;; This file is part of EMchat.
13 ;; Redistribution and use in source and binary forms, with or without
14 ;; modification, are permitted provided that the following conditions
17 ;; 1. Redistributions of source code must retain the above copyright
18 ;; notice, this list of conditions and the following disclaimer.
20 ;; 2. Redistributions in binary form must reproduce the above copyright
21 ;; notice, this list of conditions and the following disclaimer in the
22 ;; documentation and/or other materials provided with the distribution.
24 ;; 3. Neither the name of the author nor the names of any contributors
25 ;; may be used to endorse or promote products derived from this
26 ;; software without specific prior written permission.
28 ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
29 ;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30 ;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31 ;; DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33 ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35 ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
36 ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37 ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
38 ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 (require 'emchat-menu)
42 (require 'emchat-status)
43 (require 'emchat-world)
44 (require 'emchat-history))
50 (defgroup emchat-buddy nil
51 "Contact list preferences."
55 (defcustom emchat-buddy-window-width 20
56 "*Width of window for `emchat-buddy-buffer'."
57 :group 'emchat-interface)
59 (defcustom emchat-buddy-view
60 'emchat-connected-aliases
61 "*View of buddy buffer.
62 It determines what aliases to be display in buddy buffer. For example,
63 \(emchat-connected-aliases) means display all connected aliases.
65 See `emchat-buddy-view-all', `emchat-buddy-view-connected', and
66 `emchat-buddy-view-active'."
68 :type '(choice (item emchat-all-aliases)
69 (item emchat-connected-aliases)
70 (item emchat-active-aliases))
71 :initialize 'custom-initialize-default)
73 (defcustom emchat-buddy-show-xface nil
74 "*When non-nil, display XFace images in the buddy buffer.
76 The images come from BBDB. For an image to display in the buddy
77 buffer there has to be an existing BBDB entry for the contact that
78 has both a `face' field, for the image, and a `icqnick' field, to
79 match from the contact name in the buddy buffer."
84 (defcustom emchat-buddy-prefer-cface-to-xface (featurep 'png)
85 "*When non-nil, display colour faces instead of X-Face if available."
89 (defface emchat-face-selected
90 '((((background dark))
91 (:foreground "darkblue" :background "yellow"))
93 (:foreground "darkblue" :background "yellow")))
94 "Face for OFFLINE status."
97 ;;; Internal variables
100 (defvar emchat-buddy-buffer nil
101 "Buffer for contact list.")
103 (defun emchat-buddy-mode ()
104 "Major mode for contact list in emchat.
105 Commands: \\{emchat-buddy-mode-map}
107 Turning on `emchat-buddy-mode' runs the hook `emchat-buddy-mode-hook'."
109 (kill-all-local-variables)
110 (use-local-map emchat-buddy-mode-map)
111 (setq mode-name "emchat-buddy")
112 (setq major-mode 'emchat-buddy-mode)
113 ;; put easy-menu-add after set mode-name
114 (easy-menu-add emchat-main-easymenu)
115 (easy-menu-add emchat-buddy-menu)
116 (easy-menu-add emchat-log-menu)
117 (set-specifier has-modeline-p nil
118 (cons (current-buffer) nil))
119 (set-specifier horizontal-scrollbar-visible-p nil
120 (cons (current-buffer) nil))
121 ;(setq modeline-format "%b")
123 (run-hooks 'emchat-buddy-mode-hook))
125 (defun emchat-buddy-view-set (&optional symbol value)
126 "Set `emchat-buddy-view'."
127 (set-default symbol value)
128 (emchat-buddy-show-buffer 'new 'no-select))
130 (defun emchat-face-to-png (face)
131 "Base64 decode a Face header into a PNG.
135 (base64-decode-region (point-min) (point-max))
138 (defun emchat-buddy-show-xface (alias)
139 "Display an XFace image in the buddy buffer."
140 (unless (featurep '(and xface bbdb-autoloads))
141 (error 'unimplemented "X-Face and/or BBDB"))
143 (when (buffer-live-p emchat-buddy-buffer)
144 (set-buffer emchat-buddy-buffer)
145 (goto-char (point-min))
146 (when (search-forward-regexp (concat "^" (regexp-quote alias) "$") nil t)
147 (let ((ext (extent-at (point)))
148 (all-records (bbdb-records))
149 face cface nick record)
151 (setq record (car all-records)
152 nick (bbdb-record-getprop record 'icqnick)
153 face (bbdb-record-getprop record 'face)
154 cface (bbdb-record-getprop record 'cface))
156 (setq nick (split-string nick ",\\| " 'omitnulls)))
157 (when (and (member alias nick)
159 ;; put some whitespace between the image and the name
160 (set-extent-begin-glyph
161 (make-extent (point-at-bol) (point-at-eol))
165 (or (not emchat-buddy-prefer-cface-to-xface)
167 (set-extent-begin-glyph
169 (make-glyph (list (vector 'xface
170 :data (concat "X-Face: " face)
172 :background "white")))))
174 (when (and (featurep 'png)
176 emchat-buddy-prefer-cface-to-xface)
177 (set-extent-begin-glyph
179 (make-glyph (list (vector 'png
180 :data (emchat-face-to-png cface)))))))
181 (setq all-records (cdr all-records))))))))
184 (defun emchat-buddy-show-buffer (&optional new no-select)
185 "Switch to `emchat-buddy-buffer'.
186 Create buffer if buffer does not exists already or
188 Don't select buddy window if NO-SELECT is non-nil.
189 See `emchat-buddy-view' and `emchat-buddy-status-color-hint-flag'."
191 (when (or (not (buffer-live-p emchat-buddy-buffer))
193 (setq emchat-buddy-buffer (get-buffer-create "*emchat buddy*"))
194 (set-buffer emchat-buddy-buffer)
196 (loop for alias in (symbol-value emchat-buddy-view)
197 as status = (emchat-world-getf alias 'status)
198 as face = (emchat-status-face status)
199 do (insert-face (concat alias "\n") face)
200 do (when emchat-buddy-show-xface (emchat-buddy-show-xface alias))
201 do (emchat-buddy-update-face alias))
204 (switch-to-buffer emchat-buddy-buffer)))
206 (defun emchat-buddy-view-all ()
207 "Display all aliases in `emchat-world'.
208 See `emchat-buddy-view'."
210 (emchat-buddy-view-set 'emchat-buddy-view 'emchat-all-aliases))
212 (defun emchat-buddy-view-connected ()
213 "Display all connected aliases.
214 See `emchat-buddy-view' and `emchat-connected-aliases'."
216 (emchat-buddy-view-set 'emchat-buddy-view 'emchat-connected-aliases))
218 (defun emchat-buddy-view-active ()
219 "Display all active aliases.
220 See `emchat-buddy-view' and `emchat-active-aliases'."
222 (emchat-buddy-view-set 'emchat-buddy-view 'emchat-active-aliases))
224 (eval-when-compile (defvar emchat-history-directory))
226 (defun emchat-buddy-show-xface-in-balloon (alias)
227 "Display an XFace image in the balloon-help buffer."
228 (unless (featurep '(and xface bbdb-autoloads))
229 (error 'unimplemented "X-Face and/or BBDB"))
231 (let ((ext (or (extent-at (point))
232 (make-extent (point-min) (point-min))))
233 (all-records (bbdb-records))
234 face cface nick record)
236 (setq record (car all-records)
237 nick (bbdb-record-getprop record 'icqnick)
238 face (bbdb-record-getprop record 'face)
239 cface (bbdb-record-getprop record 'cface))
240 (when (and (equal nick alias)
242 ;; put some whitespace between the image and the name
243 (set-extent-begin-glyph
244 (make-extent (point-min) (point-min))
248 (or (not emchat-buddy-prefer-cface-to-xface)
250 (set-extent-begin-glyph
252 (make-glyph (list (vector 'xface
253 :data (concat "X-Face: " face)
255 :background "white")))))
257 (when (and (featurep 'png)
259 emchat-buddy-prefer-cface-to-xface)
260 (set-extent-begin-glyph
262 (make-glyph (list (vector 'png
263 :data (emchat-face-to-png cface)))))))
264 (setq all-records (cdr all-records))))))
266 (defadvice balloon-help-display-help (after emchat-balloon-xface (&rest args) activate)
267 "Display an X-Face or cface image in the balloon."
268 (when emchat-buddy-show-xface
270 (set-buffer balloon-help-buffer)
271 (goto-char (point-min))
272 (when (re-search-forward "\\(^.*\\) (" (point-at-eol) t)
273 (substring (match-string 1) 1)))))
275 (emchat-buddy-show-xface-in-balloon alias)))))
277 (defun emchat-buddy-update-face (alias &optional delete)
278 "Update face of ALIAS.
279 Non-nil DELETE means delete alias from buffer."
281 (when (buffer-live-p emchat-buddy-buffer)
282 (set-buffer emchat-buddy-buffer)
283 (goto-char (point-min))
285 (if (search-forward-regexp
286 ;; use "^" alias "$" so searching "foo" will not get "foobar"
288 ;; to allow funny characters in alias
296 ;; take care of last line
297 (min (1+ (point-at-eol)) (point-max))))
304 (let* ((ext (extent-at (point)))
306 "%s (%s)\n Status: %s\n Groups: %s\nHistory: %s\n\n\n"
308 (emchat-alias-uin alias)
309 (or (emchat-world-getf alias 'status)
311 (or (emchat-world-getf alias 'group)
313 (or (emchat-world-getf alias 'history)
315 (face (emchat-status-face (emchat-world-getf alias 'status))))
317 (set-extent-property ext 'face face)
318 (set-extent-property ext 'balloon-help bhelp))
320 (when (emchat-world-getf alias 'selected)
321 ;; highlight first char
323 (+ 0 (point-at-bol)) (+ 1 (point-at-bol))
324 'face 'emchat-face-selected)))))))
326 (defun emchat-buddy-select-all-in-view (state &optional predicate)
327 "Select all aliases in current view.
328 See `emchat-group-select-aliases' for STATE.
329 PREDICATE accepts an alias as an argument and limits the application.
330 Current view is `emchat-buddy-view'."
331 (loop for x in (symbol-value emchat-buddy-view)
332 if (or (null predicate)
333 (funcall predicate x))
334 do (emchat-group-select-aliases state x)))
336 (defun emchat-buddy-select-all-in-view-by-status (status)
337 "Toggle selections of all aliases with STATUS in current view."
339 (list (emchat-completing-read "status: " emchat-valid-statuses)))
340 (emchat-buddy-select-all-in-view
343 (equal (emchat-world-getf x 'status) status))))
345 (defun emchat-buddy-select-all-in-view-by-regexp (regexp)
346 "Toggle selections of all aliases matching REGEXP in current view."
347 ;; checked my screenshots? know why i use a symbol prefix now?
348 (interactive "sregexp: ")
349 (emchat-buddy-select-all-in-view
352 (string-match regexp x))))
354 (defun emchat-buddy-selected-in-view ()
355 "Return a list of all selected aliases in current view.
356 Selected means an alias has non-nil 'selected property.
357 Current view is `emchat-buddy-view'."
358 (loop for x in (symbol-value emchat-buddy-view)
359 if (emchat-world-getf x 'selected)
362 (provide 'emchat-buddy)
364 ;;; emchat-buddy.el ends here