1 ;;; emchat-buddy.el --- "Buddy" code for EMchat
3 ;; Copyright (C) 2007 - 2010 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))
155 (when (and (equal nick alias)
157 ;; put some whitespace between the image and the name
158 (set-extent-begin-glyph
159 (make-extent (point-at-bol) (point-at-eol))
163 (or (not emchat-buddy-prefer-cface-to-xface)
165 (set-extent-begin-glyph
167 (make-glyph (list (vector 'xface
168 :data (concat "X-Face: " face)
170 :background "white")))))
172 (when (and (featurep 'png)
174 emchat-buddy-prefer-cface-to-xface)
175 (set-extent-begin-glyph
177 (make-glyph (list (vector 'png
178 :data (emchat-face-to-png cface)))))))
179 (setq all-records (cdr all-records))))))))
182 (defun emchat-buddy-show-buffer (&optional new no-select)
183 "Switch to `emchat-buddy-buffer'.
184 Create buffer if buffer does not exists already or
186 Don't select buddy window if NO-SELECT is non-nil.
187 See `emchat-buddy-view' and `emchat-buddy-status-color-hint-flag'."
189 (when (or (not (buffer-live-p emchat-buddy-buffer))
191 (setq emchat-buddy-buffer (get-buffer-create "*emchat buddy*"))
192 (set-buffer emchat-buddy-buffer)
194 (loop for alias in (symbol-value emchat-buddy-view)
195 as status = (emchat-world-getf alias 'status)
196 as face = (emchat-status-face status)
197 do (insert-face (concat alias "\n") face)
198 do (when emchat-buddy-show-xface (emchat-buddy-show-xface alias))
199 do (emchat-buddy-update-face alias))
202 (switch-to-buffer emchat-buddy-buffer)))
204 (defun emchat-buddy-view-all ()
205 "Display all aliases in `emchat-world'.
206 See `emchat-buddy-view'."
208 (emchat-buddy-view-set 'emchat-buddy-view 'emchat-all-aliases))
210 (defun emchat-buddy-view-connected ()
211 "Display all connected aliases.
212 See `emchat-buddy-view' and `emchat-connected-aliases'."
214 (emchat-buddy-view-set 'emchat-buddy-view 'emchat-connected-aliases))
216 (defun emchat-buddy-view-active ()
217 "Display all active aliases.
218 See `emchat-buddy-view' and `emchat-active-aliases'."
220 (emchat-buddy-view-set 'emchat-buddy-view 'emchat-active-aliases))
222 (eval-when-compile (defvar emchat-history-directory))
224 (defun emchat-buddy-show-xface-in-balloon (alias)
225 "Display an XFace image in the balloon-help buffer."
226 (unless (featurep '(and xface bbdb-autoloads))
227 (error 'unimplemented "X-Face and/or BBDB"))
229 (let ((ext (or (extent-at (point))
230 (make-extent (point-min) (point-min))))
231 (all-records (bbdb-records))
232 face cface nick record)
234 (setq record (car all-records)
235 nick (bbdb-record-getprop record 'icqnick)
236 face (bbdb-record-getprop record 'face)
237 cface (bbdb-record-getprop record 'cface))
238 (when (and (equal nick alias)
240 ;; put some whitespace between the image and the name
241 (set-extent-begin-glyph
242 (make-extent (point-min) (point-min))
246 (or (not emchat-buddy-prefer-cface-to-xface)
248 (set-extent-begin-glyph
250 (make-glyph (list (vector 'xface
251 :data (concat "X-Face: " face)
253 :background "white")))))
255 (when (and (featurep 'png)
257 emchat-buddy-prefer-cface-to-xface)
258 (set-extent-begin-glyph
260 (make-glyph (list (vector 'png
261 :data (emchat-face-to-png cface)))))))
262 (setq all-records (cdr all-records))))))
264 (defadvice balloon-help-display-help (after emchat-balloon-xface (&rest args) activate)
265 "Display an X-Face or cface image in the balloon."
266 (when emchat-buddy-show-xface
268 (set-buffer balloon-help-buffer)
269 (goto-char (point-min))
270 (when (re-search-forward "\\(^.*\\) (" (eolp) t)
271 (substring (match-string 1) 1)))))
273 (emchat-buddy-show-xface-in-balloon alias)))))
275 (defun emchat-buddy-update-face (alias &optional delete)
276 "Update face of ALIAS.
277 Non-nil DELETE means delete alias from buffer."
279 (when (buffer-live-p emchat-buddy-buffer)
280 (set-buffer emchat-buddy-buffer)
281 (goto-char (point-min))
283 (if (search-forward-regexp
284 ;; use "^" alias "$" so searching "foo" will not get "foobar"
286 ;; to allow funny characters in alias
294 ;; take care of last line
295 (min (1+ (point-at-eol)) (point-max))))
302 (let* ((ext (extent-at (point)))
304 "%s (%s)\n Status: %s\n Groups: %s\nHistory: %s\n\n\n"
306 (emchat-alias-uin alias)
307 (or (emchat-world-getf alias 'status)
309 (or (emchat-world-getf alias 'group)
311 (or (emchat-world-getf alias 'history)
313 (face (emchat-status-face (emchat-world-getf alias 'status))))
315 (set-extent-property ext 'face face)
316 (set-extent-property ext 'balloon-help bhelp))
318 (when (emchat-world-getf alias 'selected)
319 ;; highlight first char
321 (+ 0 (point-at-bol)) (+ 1 (point-at-bol))
322 'face 'emchat-face-selected)))))))
324 (defun emchat-buddy-select-all-in-view (state &optional predicate)
325 "Select all aliases in current view.
326 See `emchat-group-select-aliases' for STATE.
327 PREDICATE accepts an alias as an argument and limits the application.
328 Current view is `emchat-buddy-view'."
329 (loop for x in (symbol-value emchat-buddy-view)
330 if (or (null predicate)
331 (funcall predicate x))
332 do (emchat-group-select-aliases state x)))
334 (defun emchat-buddy-select-all-in-view-by-status (status)
335 "Toggle selections of all aliases with STATUS in current view."
337 (list (emchat-completing-read "status: " emchat-valid-statuses)))
338 (emchat-buddy-select-all-in-view
341 (equal (emchat-world-getf x 'status) status))))
343 (defun emchat-buddy-select-all-in-view-by-regexp (regexp)
344 "Toggle selections of all aliases matching REGEXP in current view."
345 ;; checked my screenshots? know why i use a symbol prefix now?
346 (interactive "sregexp: ")
347 (emchat-buddy-select-all-in-view
350 (string-match regexp x))))
352 (defun emchat-buddy-selected-in-view ()
353 "Return a list of all selected aliases in current view.
354 Selected means an alias has non-nil 'selected property.
355 Current view is `emchat-buddy-view'."
356 (loop for x in (symbol-value emchat-buddy-view)
357 if (emchat-world-getf x 'selected)
360 (provide 'emchat-buddy)
362 ;;; emchat-buddy.el ends here