PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lisp/gnus/gnus-picon.el

https://gitlab.com/freesoftware/emacs
Emacs Lisp | 319 lines | 234 code | 39 blank | 46 comment | 3 complexity | 7965eb001cd33f170d0787def6dc3855 MD5 | raw file
  1. ;;; gnus-picon.el --- displaying pretty icons in Gnus -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 1996-2022 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 <https://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-lib))
  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 'plist
  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. :type '(choice (const inline)
  68. (const right))
  69. :group 'gnus-picon)
  70. (defcustom gnus-picon-inhibit-top-level-domains t
  71. "If non-nil, don't piconify top-level domains.
  72. These are often not very interesting."
  73. :version "24.1"
  74. :type 'boolean
  75. :group 'gnus-picon)
  76. ;;; Internal variables:
  77. (defvar gnus-picon-glyph-alist nil
  78. "Picon glyphs cache.
  79. List of pairs (KEY . GLYPH) where KEY is either a filename or an URL.")
  80. (defvar gnus-picon-cache nil)
  81. ;;; Functions:
  82. (defsubst gnus-picon-split-address (address)
  83. (setq address (split-string address "@"))
  84. (if (stringp (cadr address))
  85. (cons (car address) (split-string (cadr address) "\\."))
  86. (if (stringp (car address))
  87. (split-string (car address) "\\."))))
  88. (defun gnus-picon-find-face (address directories &optional exact)
  89. (let* ((address (gnus-picon-split-address address))
  90. (user (pop address))
  91. (faddress address)
  92. result base) ;; database directory instance
  93. (catch 'found
  94. (dolist (database gnus-picon-databases)
  95. (dolist (directory directories)
  96. (setq address faddress
  97. base (expand-file-name directory database))
  98. (while address
  99. (when (setq result (gnus-picon-find-image
  100. (concat base "/" (mapconcat #'downcase
  101. (reverse address)
  102. "/")
  103. "/" (downcase user) "/")))
  104. (throw 'found result))
  105. (if exact
  106. (setq address nil)
  107. (pop address)))
  108. ;; Kludge to search MISC as well. But not in "news".
  109. (unless (string= directory "news")
  110. (when (setq result (gnus-picon-find-image
  111. (concat base "/MISC/" user "/")))
  112. (throw 'found result))))))))
  113. (defun gnus-picon-find-image (directory)
  114. (let ((types gnus-picon-file-types)
  115. found type file)
  116. (while (and (not found)
  117. (setq type (pop types)))
  118. (setq found (file-exists-p (setq file (concat directory "face." type)))))
  119. (if found
  120. file
  121. nil)))
  122. (defun gnus-picon-insert-glyph (glyph category &optional nostring)
  123. "Insert GLYPH into the buffer.
  124. GLYPH can be either a glyph or a string. When NOSTRING, no textual
  125. replacement is added."
  126. ;; Using NOSTRING prevents wrong BBDB entries with `gnus-picon-style' set to
  127. ;; 'right.
  128. (if (stringp glyph)
  129. (insert glyph)
  130. (gnus-add-wash-type category)
  131. (gnus-add-image category (car glyph))
  132. (gnus-put-image (car glyph) (unless nostring (cdr glyph)) category)))
  133. (defun gnus-picon-create-glyph (file)
  134. (or (cdr (assoc file gnus-picon-glyph-alist))
  135. (cdar (push (cons file (apply #'gnus-create-image
  136. file nil nil
  137. gnus-picon-properties))
  138. gnus-picon-glyph-alist))))
  139. ;;; Functions that does picon transformations:
  140. (declare-function image-size "image.c" (spec &optional pixels frame))
  141. (defun gnus-picon-transform-address (header category)
  142. (gnus-with-article-headers
  143. (let ((addresses
  144. (mail-header-parse-addresses
  145. ;; mail-header-parse-addresses does not work (reliably) on
  146. ;; decoded headers.
  147. (or
  148. (ignore-errors
  149. (mail-encode-encoded-word-string
  150. (or (mail-fetch-field header) "")))
  151. (mail-fetch-field header))))
  152. spec file point cache len)
  153. (dolist (address addresses)
  154. (setq address (car address))
  155. (when (and (stringp address)
  156. (setq spec (gnus-picon-split-address address)))
  157. (if (setq cache (cdr (assoc address gnus-picon-cache)))
  158. (setq spec cache)
  159. (when (setq file (or (gnus-picon-find-face
  160. address gnus-picon-user-directories)
  161. (gnus-picon-find-face
  162. (concat "unknown@"
  163. (mapconcat
  164. #'identity (cdr spec) "."))
  165. gnus-picon-user-directories)))
  166. (setcar spec (cons (gnus-picon-create-glyph file)
  167. (car spec))))
  168. (dotimes (i (- (length spec)
  169. (if gnus-picon-inhibit-top-level-domains
  170. 2 1)))
  171. (when (setq file (gnus-picon-find-face
  172. (concat "unknown@"
  173. (mapconcat
  174. #'identity (nthcdr (1+ i) spec) "."))
  175. gnus-picon-domain-directories t))
  176. (setcar (nthcdr (1+ i) spec)
  177. (cons (gnus-picon-create-glyph file)
  178. (nth (1+ i) spec)))))
  179. (setq spec (nreverse spec))
  180. (push (cons address spec) gnus-picon-cache))
  181. (gnus-article-goto-header header)
  182. (mail-header-narrow-to-field)
  183. (cl-case gnus-picon-style
  184. (right
  185. (when (= (length addresses) 1)
  186. (setq len (apply #'+ (mapcar (lambda (x)
  187. (condition-case nil
  188. (car (image-size (car x)))
  189. (error 0)))
  190. 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 nil gnus-article-mode gnus-summary-mode)
  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 nil gnus-article-mode gnus-summary-mode)
  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 nil gnus-article-mode gnus-summary-mode)
  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 nil gnus-article-mode gnus-summary-mode)
  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