gnus-picon.el 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. ;;; gnus-picon.el --- displaying pretty icons in Gnus
  2. ;; Copyright (C) 1996-2012 Free Software Foundation, Inc.
  3. ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
  4. ;; Keywords: news xpm annotation glyph faces
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; There are three picon types relevant to Gnus:
  18. ;;
  19. ;; Persons: person@subdomain.dom
  20. ;; users/dom/subdomain/person/face.gif
  21. ;; usenix/dom/subdomain/person/face.gif
  22. ;; misc/MISC/person/face.gif
  23. ;; Domains: subdomain.dom
  24. ;; domain/dom/subdomain/unknown/face.gif
  25. ;; Groups: comp.lang.lisp
  26. ;; news/comp/lang/lisp/unknown/face.gif
  27. ;;
  28. ;; Original implementation by Wes Hardaker <hardaker@ece.ucdavis.edu>.
  29. ;;
  30. ;;; Code:
  31. ;; For Emacs <22.2 and XEmacs.
  32. (eval-and-compile
  33. (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
  34. (eval-when-compile (require 'cl))
  35. (require 'gnus)
  36. (require 'gnus-art)
  37. ;;; User variables:
  38. (defcustom gnus-picon-news-directories '("news")
  39. "*List of directories to search for newsgroups faces."
  40. :type '(repeat string)
  41. :group 'gnus-picon)
  42. (defcustom gnus-picon-user-directories '("users" "usenix" "local" "misc")
  43. "*List of directories to search for user faces."
  44. :type '(repeat string)
  45. :group 'gnus-picon)
  46. (defcustom gnus-picon-domain-directories '("domains")
  47. "*List of directories to search for domain faces.
  48. Some people may want to add \"unknown\" to this list."
  49. :type '(repeat string)
  50. :group 'gnus-picon)
  51. (defcustom gnus-picon-file-types
  52. (let ((types (list "xbm")))
  53. (when (gnus-image-type-available-p 'gif)
  54. (push "gif" types))
  55. (when (gnus-image-type-available-p 'xpm)
  56. (push "xpm" types))
  57. types)
  58. "*List of suffixes on picon file names to try."
  59. :type '(repeat string)
  60. :group 'gnus-picon)
  61. (defcustom gnus-picon-style 'inline
  62. "How should picons be displayed.
  63. If `inline', the textual representation is replaced. If `right', picons are
  64. added right to the textual representation."
  65. ;; FIXME: `right' needs improvement for XEmacs.
  66. :type '(choice (const inline)
  67. (const right))
  68. :group 'gnus-picon)
  69. (defcustom gnus-picon-inhibit-top-level-domains t
  70. "If non-nil, don't piconify top-level domains.
  71. These are often not very interesting."
  72. :version "24.1"
  73. :type 'boolean
  74. :group 'gnus-picon)
  75. ;;; Internal variables:
  76. (defvar gnus-picon-glyph-alist nil
  77. "Picon glyphs cache.
  78. List of pairs (KEY . GLYPH) where KEY is either a filename or an URL.")
  79. (defvar gnus-picon-cache nil)
  80. ;;; Functions:
  81. (defsubst gnus-picon-split-address (address)
  82. (setq address (split-string address "@"))
  83. (if (stringp (cadr address))
  84. (cons (car address) (split-string (cadr address) "\\."))
  85. (if (stringp (car address))
  86. (split-string (car address) "\\."))))
  87. (defun gnus-picon-find-face (address directories &optional exact)
  88. (let* ((address (gnus-picon-split-address address))
  89. (user (pop address))
  90. (faddress address)
  91. database directory result instance base)
  92. (catch 'found
  93. (dolist (database gnus-picon-databases)
  94. (dolist (directory directories)
  95. (setq address faddress
  96. base (expand-file-name directory database))
  97. (while address
  98. (when (setq result (gnus-picon-find-image
  99. (concat base "/" (mapconcat 'downcase
  100. (reverse address)
  101. "/")
  102. "/" (downcase user) "/")))
  103. (throw 'found result))
  104. (if exact
  105. (setq address nil)
  106. (pop address)))
  107. ;; Kludge to search MISC as well. But not in "news".
  108. (unless (string= directory "news")
  109. (when (setq result (gnus-picon-find-image
  110. (concat base "/MISC/" user "/")))
  111. (throw 'found result))))))))
  112. (defun gnus-picon-find-image (directory)
  113. (let ((types gnus-picon-file-types)
  114. found type file)
  115. (while (and (not found)
  116. (setq type (pop types)))
  117. (setq found (file-exists-p (setq file (concat directory "face." type)))))
  118. (if found
  119. file
  120. nil)))
  121. (defun gnus-picon-insert-glyph (glyph category &optional nostring)
  122. "Insert GLYPH into the buffer.
  123. GLYPH can be either a glyph or a string. When NOSTRING, no textual
  124. replacement is added."
  125. ;; Using NOSTRING prevents wrong BBDB entries with `gnus-picon-style' set to
  126. ;; 'right.
  127. (if (stringp glyph)
  128. (insert glyph)
  129. (gnus-add-wash-type category)
  130. (gnus-add-image category (car glyph))
  131. (gnus-put-image (car glyph) (unless nostring (cdr glyph)) category)))
  132. (defun gnus-picon-create-glyph (file)
  133. (or (cdr (assoc file gnus-picon-glyph-alist))
  134. (cdar (push (cons file (gnus-create-image
  135. file nil nil
  136. :color-symbols '(("None" . "white"))))
  137. gnus-picon-glyph-alist))))
  138. ;;; Functions that does picon transformations:
  139. (declare-function image-size "image.c" (spec &optional pixels frame))
  140. (defun gnus-picon-transform-address (header category)
  141. (gnus-with-article-headers
  142. (let ((addresses
  143. (mail-header-parse-addresses
  144. ;; mail-header-parse-addresses does not work (reliably) on
  145. ;; decoded headers.
  146. (or
  147. (ignore-errors
  148. (mail-encode-encoded-word-string
  149. (or (mail-fetch-field header) "")))
  150. (mail-fetch-field header))))
  151. spec file point cache len)
  152. (dolist (address addresses)
  153. (setq address (car address))
  154. (when (and (stringp address)
  155. (setq spec (gnus-picon-split-address address)))
  156. (if (setq cache (cdr (assoc address gnus-picon-cache)))
  157. (setq spec cache)
  158. (when (setq file (or (gnus-picon-find-face
  159. address gnus-picon-user-directories)
  160. (gnus-picon-find-face
  161. (concat "unknown@"
  162. (mapconcat
  163. 'identity (cdr spec) "."))
  164. gnus-picon-user-directories)))
  165. (setcar spec (cons (gnus-picon-create-glyph file)
  166. (car spec))))
  167. (dotimes (i (- (length spec)
  168. (if gnus-picon-inhibit-top-level-domains
  169. 2 1)))
  170. (when (setq file (gnus-picon-find-face
  171. (concat "unknown@"
  172. (mapconcat
  173. 'identity (nthcdr (1+ i) spec) "."))
  174. gnus-picon-domain-directories t))
  175. (setcar (nthcdr (1+ i) spec)
  176. (cons (gnus-picon-create-glyph file)
  177. (nth (1+ i) spec)))))
  178. (setq spec (nreverse spec))
  179. (push (cons address spec) gnus-picon-cache))
  180. (gnus-article-goto-header header)
  181. (mail-header-narrow-to-field)
  182. (case gnus-picon-style
  183. (right
  184. (when (= (length addresses) 1)
  185. (setq len (apply '+ (mapcar (lambda (x)
  186. (condition-case nil
  187. (car (image-size (car x)))
  188. (error 0))) spec)))
  189. (when (> len 0)
  190. (goto-char (point-at-eol))
  191. (insert (propertize
  192. " " 'display
  193. (cons 'space
  194. (list :align-to (- (window-width) 1 len))))))
  195. (goto-char (point-at-eol))
  196. (setq point (point-at-eol))
  197. (dolist (image spec)
  198. (unless (stringp image)
  199. (goto-char point)
  200. (gnus-picon-insert-glyph image category 'nostring)))))
  201. (inline
  202. (when (search-forward address nil t)
  203. (delete-region (match-beginning 0) (match-end 0))
  204. (setq point (point))
  205. (while spec
  206. (goto-char point)
  207. (if (> (length spec) 2)
  208. (insert ".")
  209. (if (= (length spec) 2)
  210. (insert "@")))
  211. (gnus-picon-insert-glyph (pop spec) category))))))))))
  212. (defun gnus-picon-transform-newsgroups (header)
  213. (interactive)
  214. (gnus-with-article-headers
  215. (gnus-article-goto-header header)
  216. (mail-header-narrow-to-field)
  217. (let ((groups (message-tokenize-header (mail-fetch-field header)))
  218. spec file point)
  219. (dolist (group groups)
  220. (unless (setq spec (cdr (assoc group gnus-picon-cache)))
  221. (setq spec (nreverse (split-string group "[.]")))
  222. (dotimes (i (length spec))
  223. (when (setq file (gnus-picon-find-face
  224. (concat "unknown@"
  225. (mapconcat
  226. 'identity (nthcdr i spec) "."))
  227. gnus-picon-news-directories t))
  228. (setcar (nthcdr i spec)
  229. (cons (gnus-picon-create-glyph file)
  230. (nth i spec)))))
  231. (push (cons group spec) gnus-picon-cache))
  232. (when (search-forward group nil t)
  233. (delete-region (match-beginning 0) (match-end 0))
  234. (save-restriction
  235. (narrow-to-region (point) (point))
  236. (while spec
  237. (goto-char (point-min))
  238. (if (> (length spec) 1)
  239. (insert "."))
  240. (gnus-picon-insert-glyph (pop spec) 'newsgroups-picon))
  241. (goto-char (point-max))))))))
  242. ;;; Commands:
  243. ;; #### NOTE: the test for buffer-read-only is the same as in
  244. ;; article-display-[x-]face. See the comment up there.
  245. ;;;###autoload
  246. (defun gnus-treat-from-picon ()
  247. "Display picons in the From header.
  248. If picons are already displayed, remove them."
  249. (interactive)
  250. (let ((wash-picon-p buffer-read-only))
  251. (gnus-with-article-buffer
  252. (if (and wash-picon-p (memq 'from-picon gnus-article-wash-types))
  253. (gnus-delete-images 'from-picon)
  254. (gnus-picon-transform-address "from" 'from-picon)))))
  255. ;;;###autoload
  256. (defun gnus-treat-mail-picon ()
  257. "Display picons in the Cc and To headers.
  258. If picons are already displayed, remove them."
  259. (interactive)
  260. (let ((wash-picon-p buffer-read-only))
  261. (gnus-with-article-buffer
  262. (if (and wash-picon-p (memq 'mail-picon gnus-article-wash-types))
  263. (gnus-delete-images 'mail-picon)
  264. (gnus-picon-transform-address "cc" 'mail-picon)
  265. (gnus-picon-transform-address "to" 'mail-picon)))))
  266. ;;;###autoload
  267. (defun gnus-treat-newsgroups-picon ()
  268. "Display picons in the Newsgroups and Followup-To headers.
  269. If picons are already displayed, remove them."
  270. (interactive)
  271. (let ((wash-picon-p buffer-read-only))
  272. (gnus-with-article-buffer
  273. (if (and wash-picon-p (memq 'newsgroups-picon gnus-article-wash-types))
  274. (gnus-delete-images 'newsgroups-picon)
  275. (gnus-picon-transform-newsgroups "newsgroups")
  276. (gnus-picon-transform-newsgroups "followup-to")))))
  277. (provide 'gnus-picon)
  278. ;;; gnus-picon.el ends here