1 ;;; gnus-picon.el --- displaying pretty icons in Gnus
2 ;; Copyright (C) 1996,97 Free Software Foundation, Inc.
4 ;; Author: Wes Hardaker <hardaker@ece.ucdavis.edu>
5 ;; Keywords: news xpm annotation glyph faces
7 ;; This file is part of GNU Emacs.
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING. If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
30 (require 'annotations)
36 "Show pictures of people, domains, and newsgroups (XEmacs).
37 For this to work, you must add gnus-group-display-picons to the
38 gnus-summary-display-hook or to the gnus-article-display-hook
39 depending on what gnus-picons-display-where is set to. You must
40 also add gnus-article-display-picons to gnus-article-display-hook."
43 (defcustom gnus-picons-buffer "*Icon Buffer*"
44 "Buffer name to display the icons in if gnus-picons-display-where is 'picons."
48 (defcustom gnus-picons-display-where 'picons
49 "Where to display the group and article icons.
50 Legal values are `article' and `picons'."
51 :type '(choice symbol string)
54 (defcustom gnus-picons-database "/usr/local/faces"
55 "Defines the location of the faces database.
56 For information on obtaining this database of pretty pictures, please
57 see http://www.cs.indiana.edu/picons/ftp/index.html"
61 (defcustom gnus-picons-news-directory "news"
62 "Sub-directory of the faces database containing the icons for newsgroups."
66 (defcustom gnus-picons-user-directories '("local" "users" "usenix" "misc/MISC")
67 "List of directories to search for user faces."
68 :type '(repeat string)
71 (defcustom gnus-picons-domain-directories '("domains")
72 "List of directories to search for domain faces.
73 Some people may want to add \"unknown\" to this list."
74 :type '(repeat string)
77 (defcustom gnus-picons-refresh-before-display nil
78 "If non-nil, display the article buffer before computing the picons."
82 (defcustom gnus-picons-x-face-file-name
83 (format "/tmp/picon-xface.%s.xbm" (user-login-name))
84 "The name of the file in which to store the converted X-face header."
88 (defcustom gnus-picons-convert-x-face (format "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | pbmtoxbm > %s" gnus-picons-x-face-file-name)
89 "Command to convert the x-face header into a xbm file."
93 (defcustom gnus-picons-display-as-address t
94 "*If t display textual email addresses along with pictures."
98 (defcustom gnus-picons-file-suffixes
100 (let ((types (list "xbm")))
101 (when (featurep 'gif)
103 (when (featurep 'xpm)
106 "List of suffixes on picon file names to try."
107 :type '(repeat string)
110 (defcustom gnus-picons-display-article-move-p t
111 "*Whether to move point to first empty line when displaying picons.
112 This has only an effect if `gnus-picons-display-where' hs value article."
116 (defvar gnus-picons-map (make-sparse-keymap "gnus-picons-keys")
117 "keymap to hide/show picon glyphs")
119 (define-key gnus-picons-map [(button2)] 'gnus-picons-toggle-extent)
121 ;;; Internal variables.
123 (defvar gnus-group-annotations nil)
124 (defvar gnus-article-annotations nil)
125 (defvar gnus-x-face-annotations nil)
127 (defun gnus-picons-remove (plist)
128 (let ((listitem (car plist)))
129 (while (setq listitem (car plist))
130 (when (annotationp listitem)
131 (delete-annotation listitem))
132 (setq plist (cdr plist)))))
134 (defun gnus-picons-remove-all ()
135 "Removes all picons from the Gnus display(s)."
137 (gnus-picons-remove gnus-article-annotations)
138 (gnus-picons-remove gnus-group-annotations)
139 (gnus-picons-remove gnus-x-face-annotations)
140 (setq gnus-article-annotations nil
141 gnus-group-annotations nil
142 gnus-x-face-annotations nil)
143 (when (bufferp gnus-picons-buffer)
144 (kill-buffer gnus-picons-buffer)))
146 (defun gnus-get-buffer-name (variable)
147 "Returns the buffer name associated with the contents of a variable."
148 (cond ((symbolp variable)
149 (let ((newvar (cdr (assq variable gnus-window-to-buffer))))
150 (cond ((symbolp newvar)
151 (symbol-value newvar))
152 ((stringp newvar) newvar))))
156 (defun gnus-picons-article-display-x-face ()
157 "Display the x-face header bitmap in the 'gnus-picons-display-where buffer."
158 ;; delete any old ones.
159 (gnus-picons-remove gnus-x-face-annotations)
160 (setq gnus-x-face-annotations nil)
161 ;; display the new one.
162 (let ((gnus-article-x-face-command 'gnus-picons-display-x-face))
163 (gnus-article-display-x-face)))
165 (defun gnus-picons-display-x-face (beg end)
166 "Function to display the x-face header in the picons window.
167 To use: (setq gnus-article-x-face-command 'gnus-picons-display-x-face)"
169 ;; convert the x-face header to a .xbm file
170 (let ((process-connection-type nil)
172 (process-kill-without-query
173 (setq process (start-process
174 "gnus-x-face" nil shell-file-name shell-command-switch
175 gnus-picons-convert-x-face)))
176 (process-send-region "gnus-x-face" beg end)
177 (process-send-eof "gnus-x-face")
179 (while (not (equal (process-status process) 'exit))
183 (set-buffer (get-buffer-create (gnus-get-buffer-name
184 gnus-picons-display-where)))
185 (gnus-add-current-to-buffer-list)
186 (goto-char (point-min))
187 (let (buffer-read-only)
189 (push (make-annotation "\n" (point) 'text)
190 gnus-x-face-annotations))
191 ;; append the annotation to gnus-article-annotations for deletion.
192 (setq gnus-x-face-annotations
194 (gnus-picons-try-to-find-face gnus-picons-x-face-file-name t)
195 gnus-x-face-annotations)))
196 ;; delete the tmp file
197 (delete-file gnus-picons-x-face-file-name)))
199 (defun gnus-article-display-picons ()
200 "Display faces for an author and his/her domain in gnus-picons-display-where."
202 ;; let drawing catch up
203 (when gnus-picons-refresh-before-display
206 from at-idx databases)
207 (when (and (featurep 'xpm)
208 (or (not (fboundp 'device-type)) (equal (device-type) 'x))
209 (setq from (mail-fetch-field "from"))
211 (or (cadr (mail-extract-address-components from))
213 (or (setq at-idx (string-match "@" from))
214 (setq at-idx (length from))))
216 (let ((username (substring from 0 at-idx))
217 (addrs (if (eq at-idx (length from))
218 (if gnus-local-domain
219 (nreverse (message-tokenize-header
220 gnus-local-domain "."))
222 (nreverse (message-tokenize-header
223 (substring from (1+ at-idx)) ".")))))
224 (set-buffer (get-buffer-create
225 (gnus-get-buffer-name gnus-picons-display-where)))
226 (gnus-add-current-to-buffer-list)
227 (goto-char (point-min))
228 (if (and (eq gnus-picons-display-where 'article)
229 gnus-picons-display-article-move-p)
230 (when (search-forward "\n\n" nil t)
233 (push (make-annotation "\n" (point) 'text)
234 gnus-article-annotations)))
236 (gnus-picons-remove gnus-article-annotations)
237 (setq gnus-article-annotations nil)
239 ;; look for domain paths.
240 (setq databases gnus-picons-domain-directories)
242 (setq gnus-article-annotations
243 (nconc (gnus-picons-insert-face-if-exists
246 "unknown" (or gnus-picons-display-as-address
247 gnus-article-annotations) t t)
248 gnus-article-annotations))
249 (setq databases (cdr databases)))
251 ;; add an '@' if displaying as address
252 (when gnus-picons-display-as-address
253 (setq gnus-article-annotations
254 (nconc gnus-article-annotations
256 (make-annotation "@" (point) 'text nil nil nil t)))))
258 ;; then do user directories,
260 (setq databases gnus-picons-user-directories)
261 (setq username (downcase username))
264 (nconc (gnus-picons-insert-face-if-exists
265 (car databases) addrs username
266 (or gnus-picons-display-as-address
267 gnus-article-annotations) nil t)
269 (setq databases (cdr databases)))
270 ;; add their name if no face exists
271 (when (and gnus-picons-display-as-address (not found))
274 (make-annotation username (point) 'text nil nil nil t))))
275 (setq gnus-article-annotations
276 (nconc found gnus-article-annotations)))
278 (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all))))))
280 (defun gnus-group-display-picons ()
281 "Display icons for the group in the gnus-picons-display-where buffer."
283 ;; let display catch up so far
284 (when gnus-picons-refresh-before-display
286 (when (and (featurep 'xpm)
287 (or (not (fboundp 'device-type)) (equal (device-type) 'x)))
289 (set-buffer (get-buffer-create
290 (gnus-get-buffer-name gnus-picons-display-where)))
291 (gnus-add-current-to-buffer-list)
292 (goto-char (point-min))
293 (if (and (eq gnus-picons-display-where 'article)
294 gnus-picons-display-article-move-p)
295 (when (search-forward "\n\n" nil t)
298 (push (make-annotation "\n" (point) 'text)
299 gnus-group-annotations)))
301 ((listp gnus-group-annotations)
302 (mapc #'(lambda (ext) (when (extent-live-p ext)
303 (delete-annotation ext)))
304 gnus-group-annotations)
305 (setq gnus-group-annotations nil))
306 ((annotationp gnus-group-annotations)
307 (delete-annotation gnus-group-annotations)
308 (setq gnus-group-annotations nil)))
309 (gnus-picons-remove gnus-group-annotations)
310 (setq gnus-group-annotations
311 (gnus-picons-insert-face-if-exists
312 gnus-picons-news-directory
313 (message-tokenize-header gnus-newsgroup-name ".")
315 (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all))))
317 (defsubst gnus-picons-try-suffixes (file)
318 (let ((suffixes gnus-picons-file-suffixes)
321 (not (file-exists-p (setq f (concat file (pop suffixes))))))
325 (defun gnus-picons-insert-face-if-exists (database addrs filename &optional
327 "Inserts a face at point if I can find one"
328 ;; '(gnus-picons-insert-face-if-exists
329 ;; "Database" '("edu" "indiana" "cs") "Name")
331 ;; 1. edu/indiana/cs/Name
332 ;; 2. edu/indiana/Name
334 ;; '(gnus-picons-insert-face-if-exists
335 ;; "Database/MISC" '("edu" "indiana" "cs") "Name")
338 ;; The special treatment of MISC doesn't conform with the conventions for
339 ;; picon databases, but otherwise we would always see the MISC/unknown face.
340 (let ((bar (and (not nobar-p)
341 (or gnus-picons-display-as-address
342 (annotations-in-region
343 (point) (min (point-max) (1+ (point)))
345 (path (concat (file-name-as-directory gnus-picons-database)
347 (domainp (and gnus-picons-display-as-address dots))
348 picons found bar-ann cur first)
349 (when (string-match "/MISC" database)
352 (file-accessible-directory-p path))
353 (setq cur (pop addrs)
354 path (concat path cur "/"))
356 (gnus-picons-try-suffixes (concat path filename "/face.")))
358 (setq picons (nconc (when (and domainp first rightp)
359 (list (make-annotation
363 (gnus-picons-try-to-find-face
364 found nil (if domainp cur filename) rightp)
365 (when (and domainp first (not rightp))
366 (list (make-annotation
373 (nconc (list (make-annotation
374 (if first (concat (if (not rightp) ".") cur
375 (if rightp ".")) cur)
376 (point) 'text nil nil nil rightp))
378 (when (and bar (or domainp found))
379 (setq bar-ann (gnus-picons-try-to-find-face
380 (concat gnus-xmas-glyph-directory "bar.xbm")
383 (setq picons (nconc picons bar-ann))
386 (when (and addrs domainp)
387 (let ((it (mapconcat 'downcase (nreverse addrs) ".")))
389 (if first (concat (if (not rightp) ".") it (if rightp ".")) it)
390 (point) 'text nil nil nil rightp)))
393 (defvar gnus-picons-glyph-alist nil)
395 (defun gnus-picons-try-to-find-face (path &optional xface-p part rightp)
396 "If PATH exists, display it as a bitmap. Returns t if succeeded."
397 (let ((glyph (and (not xface-p)
398 (cdr (assoc path gnus-picons-glyph-alist)))))
399 (when (or glyph (file-exists-p path))
401 (setq glyph (make-glyph path))
403 (push (cons path glyph) gnus-picons-glyph-alist))
404 (set-glyph-face glyph 'default))
405 (let ((new (make-annotation glyph (point) 'text nil nil nil rightp)))
408 (when (and (eq major-mode 'gnus-article-mode)
409 (not gnus-picons-display-as-address)
411 (list (make-annotation " " (point) 'text nil nil nil rightp)))
412 (when (and part gnus-picons-display-as-address)
413 (let ((txt (make-annotation part (point) 'text nil nil nil rightp)))
414 (hide-annotation txt)
415 (set-extent-property txt 'its-partner new)
416 (set-extent-property txt 'keymap gnus-picons-map)
417 (set-extent-property txt 'mouse-face gnus-article-mouse-face)
418 (set-extent-property new 'its-partner txt)
419 (set-extent-property new 'keymap gnus-picons-map))))))))
421 (defun gnus-picons-reverse-domain-path (str)
423 (mapconcat 'downcase (nreverse (message-tokenize-header str "/")) "/"))
425 (defun gnus-picons-toggle-extent (event)
426 "Toggle picon glyph at given point"
428 (let* ((ant1 (event-glyph-extent event))
429 (ant2 (extent-property ant1 'its-partner)))
430 (when (and (annotationp ant1) (annotationp ant2))
431 (reveal-annotation ant2)
432 (hide-annotation ant1))))
434 (gnus-add-shutdown 'gnus-picons-close 'gnus)
436 (defun gnus-picons-close ()
437 "Shut down the picons."
438 (setq gnus-picons-glyph-alist nil))
440 (provide 'gnus-picon)
442 ;;; gnus-picon.el ends here