gnus-picon.el 10 KB

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