X-Git-Url: https://cgit.sxemacs.org/?p=gnus;a=blobdiff_plain;f=lisp%2Fgnus-picon.el;h=cf511bb43018a14d08d4c416c7a5cfdbc573f00d;hp=326af48529f11aaa3342fce9ddfc4ce59749b67a;hb=HEAD;hpb=7a7ce38efebb8cc43c3fd6a57bb3a5f65f5b93cf diff --git a/lisp/gnus-picon.el b/lisp/gnus-picon.el index 326af4852..c58059f27 100644 --- a/lisp/gnus-picon.el +++ b/lisp/gnus-picon.el @@ -1,319 +1,318 @@ -;;; gnus-picons.el: Icon hacks for displaying pretty icons in Gnus. -;; Copyright (C) 1996 Wes Hardaker +;;; gnus-picon.el --- displaying pretty icons in Gnus -;; Author: Wes Hardaker -;; Keywords: gnus xpm annotation glyph faces +;; Copyright (C) 1996-2016 Free Software Foundation, Inc. + +;; Author: Lars Magne Ingebrigtsen +;; Keywords: news xpm annotation glyph faces + +;; This file is part of GNU Emacs. + +;; GNU Emacs is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; GNU Emacs is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs. If not, see . ;;; Commentary: -;; Usage: -;; - You must have XEmacs (19.12 or above I think) to use this. -;; - Read the variable descriptions below. -;; -;; - chose a setup: -;; -;; 1) display the icons in its own buffer: -;; -;; (add-hook 'gnus-article-display-hook 'gnus-article-display-picons t) -;; (add-hook 'gnus-summary-prepare-hook 'gnus-group-display-picons t) -;; (setq gnus-picons-display-where 'picons) -;; -;; Then add the picons buffer to your display configuration: -;; The picons buffer needs to be at least 48 pixels high, -;; which for me is 5 lines: -;; -;; (gnus-add-configuration -;; '(article (vertical 1.0 -;; (group 6) -;; (picons 5) -;; (summary .25 point) -;; (article 1.0)))) -;; -;; (gnus-add-configuration -;; '(summary (vertical 1.0 (group 6) -;; (picons 5) -;; (summary 1.0 point)))) +;; There are three picon types relevant to Gnus: ;; -;; 2) display the icons in the summary buffer +;; Persons: person@subdomain.dom +;; users/dom/subdomain/person/face.gif +;; usenix/dom/subdomain/person/face.gif +;; misc/MISC/person/face.gif +;; Domains: subdomain.dom +;; domain/dom/subdomain/unknown/face.gif +;; Groups: comp.lang.lisp +;; news/comp/lang/lisp/unknown/face.gif ;; -;; (add-hook 'gnus-article-display-hook 'gnus-article-display-picons t) -;; (add-hook 'gnus-summary-prepare-hook 'gnus-group-display-picons t) -;; (setq gnus-picons-display-where 'summary) +;; Original implementation by Wes Hardaker . ;; -;; 3) display the icons in the article buffer -;; -;; (add-hook 'gnus-article-display-hook 'gnus-article-display-picons t) -;; (add-hook 'gnus-article-prepare-hook 'gnus-group-display-picons t) -;; (setq gnus-picons-display-where 'article) -;; -;; -;; Warnings: -;; - I'm not even close to being a lisp expert. -;; - The 't' (append) flag MUST be in the add-hook line -;; -;; TODO: -;; - Remove the TODO section in the headers. -;; - ;;; Code: -(require 'xpm) -(require 'annotations) (eval-when-compile (require 'cl)) -(defvar gnus-picons-buffer "*Icon Buffer*" - "Buffer name to display the icons in if gnus-picons-display-where is 'picons.") - -(defvar gnus-picons-display-where 'picons - "Where to display the group and article icons.") +(require 'gnus) +(require 'gnus-art) -(defvar gnus-picons-database "/usr/local/faces" - "Defines the location of the faces database. -For information on obtaining this database of pretty pictures, please -see http://www.cs.indiana.edu/picons/ftp/index.html" ) +;;; User variables: -(defvar gnus-picons-news-directory "news" - "Sub-directory of the faces database containing the icons for newsgroups." -) +(defcustom gnus-picon-news-directories '("news") + "*List of directories to search for newsgroups faces." + :type '(repeat string) + :group 'gnus-picon) -(defvar gnus-picons-user-directories '("local" "users" "usenix" "misc/MISC") - "List of directories to search for user faces." -) +(defcustom gnus-picon-user-directories '("users" "usenix" "local" "misc") + "*List of directories to search for user faces." + :type '(repeat string) + :group 'gnus-picon) -(defvar gnus-picons-domain-directories '("domains") - "List of directories to search for domain faces. +(defcustom gnus-picon-domain-directories '("domains") + "*List of directories to search for domain faces. Some people may want to add \"unknown\" to this list." -) - -(defvar gnus-group-annotations nil) -(defvar gnus-article-annotations nil) -(defvar gnus-x-face-annotations nil) - -(defun gnus-picons-remove (plist) - (let ((listitem (car plist))) - (while (setq listitem (car plist)) - (if (annotationp listitem) - (delete-annotation listitem)) - (setq plist (cdr plist)))) -) - -(defun gnus-picons-remove-all () - "Removes all picons from the Gnus display(s)." + :type '(repeat string) + :group 'gnus-picon) + +(defcustom gnus-picon-file-types + (let ((types (list "xbm"))) + (when (gnus-image-type-available-p 'gif) + (push "gif" types)) + (when (gnus-image-type-available-p 'xpm) + (push "xpm" types)) + types) + "*List of suffixes on picon file names to try." + :type '(repeat string) + :group 'gnus-picon) + +(defcustom gnus-picon-properties '(:color-symbols (("None" . "white"))) + "List of image properties applied to picons." + :type 'sexp + :version "24.3" + :group 'gnus-picon) + +(defcustom gnus-picon-style 'inline + "How should picons be displayed. +If `inline', the textual representation is replaced. If `right', picons are +added right to the textual representation." + ;; FIXME: `right' needs improvement for XEmacs. + :type '(choice (const inline) + (const right)) + :group 'gnus-picon) + +(defcustom gnus-picon-inhibit-top-level-domains t + "If non-nil, don't piconify top-level domains. +These are often not very interesting." + :version "24.1" + :type 'boolean + :group 'gnus-picon) + +;;; Internal variables: + +(defvar gnus-picon-glyph-alist nil + "Picon glyphs cache. +List of pairs (KEY . GLYPH) where KEY is either a filename or an URL.") +(defvar gnus-picon-cache nil) + +;;; Functions: + +(defsubst gnus-picon-split-address (address) + (setq address (split-string address "@")) + (if (stringp (cadr address)) + (cons (car address) (split-string (cadr address) "\\.")) + (if (stringp (car address)) + (split-string (car address) "\\.")))) + +(defun gnus-picon-find-face (address directories &optional exact) + (let* ((address (gnus-picon-split-address address)) + (user (pop address)) + (faddress address) + result base) + (catch 'found + (dolist (database gnus-picon-databases) + (dolist (directory directories) + (setq address faddress + base (expand-file-name directory database)) + (while address + (when (setq result (gnus-picon-find-image + (concat base "/" (mapconcat 'downcase + (reverse address) + "/") + "/" (downcase user) "/"))) + (throw 'found result)) + (if exact + (setq address nil) + (pop address))) + ;; Kludge to search MISC as well. But not in "news". + (unless (string= directory "news") + (when (setq result (gnus-picon-find-image + (concat base "/MISC/" user "/"))) + (throw 'found result)))))))) + +(defun gnus-picon-find-image (directory) + (let ((types gnus-picon-file-types) + found type file) + (while (and (not found) + (setq type (pop types))) + (setq found (file-exists-p (setq file (concat directory "face." type))))) + (if found + file + nil))) + +(defun gnus-picon-insert-glyph (glyph category &optional nostring) + "Insert GLYPH into the buffer. +GLYPH can be either a glyph or a string. When NOSTRING, no textual +replacement is added." + ;; Using NOSTRING prevents wrong BBDB entries with `gnus-picon-style' set to + ;; 'right. + (if (stringp glyph) + (insert glyph) + (gnus-add-wash-type category) + (gnus-add-image category (car glyph)) + (gnus-put-image (car glyph) (unless nostring (cdr glyph)) category))) + +(defun gnus-picon-create-glyph (file) + (or (cdr (assoc file gnus-picon-glyph-alist)) + (cdar (push (cons file (apply 'gnus-create-image + file nil nil + gnus-picon-properties)) + gnus-picon-glyph-alist)))) + +;;; Functions that does picon transformations: + +(declare-function image-size "image.c" (spec &optional pixels frame)) + +(defun gnus-picon-transform-address (header category) + (gnus-with-article-headers + (let ((addresses + (mail-header-parse-addresses + ;; mail-header-parse-addresses does not work (reliably) on + ;; decoded headers. + (or + (ignore-errors + (mail-encode-encoded-word-string + (or (mail-fetch-field header) ""))) + (mail-fetch-field header)))) + spec file point cache len) + (dolist (address addresses) + (setq address (car address)) + (when (and (stringp address) + (setq spec (gnus-picon-split-address address))) + (if (setq cache (cdr (assoc address gnus-picon-cache))) + (setq spec cache) + (when (setq file (or (gnus-picon-find-face + address gnus-picon-user-directories) + (gnus-picon-find-face + (concat "unknown@" + (mapconcat + 'identity (cdr spec) ".")) + gnus-picon-user-directories))) + (setcar spec (cons (gnus-picon-create-glyph file) + (car spec)))) + + (dotimes (i (- (length spec) + (if gnus-picon-inhibit-top-level-domains + 2 1))) + (when (setq file (gnus-picon-find-face + (concat "unknown@" + (mapconcat + 'identity (nthcdr (1+ i) spec) ".")) + gnus-picon-domain-directories t)) + (setcar (nthcdr (1+ i) spec) + (cons (gnus-picon-create-glyph file) + (nth (1+ i) spec))))) + (setq spec (nreverse spec)) + (push (cons address spec) gnus-picon-cache)) + + (gnus-article-goto-header header) + (mail-header-narrow-to-field) + (case gnus-picon-style + (right + (when (= (length addresses) 1) + (setq len (apply '+ (mapcar (lambda (x) + (condition-case nil + (car (image-size (car x))) + (error 0))) spec))) + (when (> len 0) + (goto-char (point-at-eol)) + (insert (propertize + " " 'display + (cons 'space + (list :align-to (- (window-width) 1 len)))))) + (goto-char (point-at-eol)) + (setq point (point-at-eol)) + (dolist (image spec) + (unless (stringp image) + (goto-char point) + (gnus-picon-insert-glyph image category 'nostring))))) + (inline + (when (search-forward address nil t) + (delete-region (match-beginning 0) (match-end 0)) + (setq point (point)) + (while spec + (goto-char point) + (if (> (length spec) 2) + (insert ".") + (if (= (length spec) 2) + (insert "@"))) + (gnus-picon-insert-glyph (pop spec) category)))))))))) + +(defun gnus-picon-transform-newsgroups (header) (interactive) - (gnus-picons-remove gnus-article-annotations) - (gnus-picons-remove gnus-group-annotations) - (gnus-picons-remove gnus-x-face-annotations) - (setq gnus-article-annotations nil - gnus-group-annotations nil - gnus-x-face-annotations nil) - (if (bufferp gnus-picons-buffer) - (kill-buffer gnus-picons-buffer)) -) - -(defun gnus-get-buffer-name (variable) - "Returns the buffer name associated with the contents of a variable." - (cond ((symbolp variable) - (let ((newvar (cdr (assq variable gnus-window-to-buffer)))) - (cond ((symbolp newvar) - (symbol-value newvar)) - ((stringp newvar) newvar)))) - ((stringp variable) - variable))) - -(defvar gnus-picons-x-face-file-name - (format "/tmp/picon-xface.%s.xbm" (user-login-name)) - "The name of the file in which to store the converted X-face header.") - -(defvar gnus-picons-convert-x-face (format "{ echo '/* Width=48, Height=48 */'; uncompface; } | icontopbm | pbmtoxbm > %s" gnus-picons-x-face-file-name) - "Command to convert the x-face header into a xbm file." -) - -(defun gnus-picons-article-display-x-face () - "Display the x-face header bitmap in the 'gnus-picons-display-where buffer." - ;; delete any old ones. - (gnus-picons-remove gnus-x-face-annotations) - (setq gnus-x-face-annotations nil) - ;; display the new one. - (let ((gnus-article-x-face-command 'gnus-picons-display-x-face)) - (gnus-article-display-x-face))) - -(defun gnus-picons-display-x-face (beg end) - "Function to display the x-face header in the picons window. -To use: (setq gnus-article-x-face-command 'gnus-picons-display-x-face)" + (gnus-with-article-headers + (gnus-article-goto-header header) + (mail-header-narrow-to-field) + (let ((groups (message-tokenize-header (mail-fetch-field header))) + spec file) + (dolist (group groups) + (unless (setq spec (cdr (assoc group gnus-picon-cache))) + (setq spec (nreverse (split-string group "[.]"))) + (dotimes (i (length spec)) + (when (setq file (gnus-picon-find-face + (concat "unknown@" + (mapconcat + 'identity (nthcdr i spec) ".")) + gnus-picon-news-directories t)) + (setcar (nthcdr i spec) + (cons (gnus-picon-create-glyph file) + (nth i spec))))) + (push (cons group spec) gnus-picon-cache)) + (when (search-forward group nil t) + (delete-region (match-beginning 0) (match-end 0)) + (save-restriction + (narrow-to-region (point) (point)) + (while spec + (goto-char (point-min)) + (if (> (length spec) 1) + (insert ".")) + (gnus-picon-insert-glyph (pop spec) 'newsgroups-picon)) + (goto-char (point-max)))))))) + +;;; Commands: + +;; #### NOTE: the test for buffer-read-only is the same as in +;; article-display-[x-]face. See the comment up there. + +;;;###autoload +(defun gnus-treat-from-picon () + "Display picons in the From header. +If picons are already displayed, remove them." (interactive) - ;; convert the x-face header to a .xbm file - (let ((process-connection-type nil) - (process nil)) - (process-kill-without-query - (setq process (start-process - "gnus-x-face" nil "sh" "-c" gnus-picons-convert-x-face))) - (process-send-region "gnus-x-face" beg end) - (process-send-eof "gnus-x-face") - ;; wait for it. - (while (not (equal (process-status process) 'exit)) - (sleep-for .1))) - ;; display it - (save-excursion - (set-buffer (get-buffer-create (gnus-get-buffer-name - gnus-picons-display-where))) - (gnus-add-current-to-buffer-list) - (beginning-of-buffer) - (let ((iconpoint (point))) - (if (not (looking-at "^$")) - (if buffer-read-only - (progn - (toggle-read-only) - (open-line 1) - (toggle-read-only) - ) - (open-line 1))) - (end-of-line) - ;; append the annotation to gnus-article-annotations for deletion. - (setq gnus-x-face-annotations - (append - (gnus-picons-try-to-find-face - gnus-picons-x-face-file-name iconpoint) - gnus-x-face-annotations))) - ;; delete the tmp file - (delete-file gnus-picons-x-face-file-name))) - -(defun gnus-article-display-picons () -"Display faces for an author and his/her domain in gnus-picons-display-where." + (let ((wash-picon-p buffer-read-only)) + (gnus-with-article-buffer + (if (and wash-picon-p (memq 'from-picon gnus-article-wash-types)) + (gnus-delete-images 'from-picon) + (gnus-picon-transform-address "from" 'from-picon))))) + +;;;###autoload +(defun gnus-treat-mail-picon () + "Display picons in the Cc and To headers. +If picons are already displayed, remove them." (interactive) - (if (and (featurep 'xpm) - (or (not (fboundp 'device-type)) (equal (device-type) 'x))) - (save-excursion - (let* ((iconpoint (point)) (from (mail-fetch-field "from")) - (username - (progn - (string-match "\\([-_a-zA-Z0-9]+\\)@" from) - (match-string 1 from))) - (hostpath - (concat (gnus-picons-reverse-domain-path - (replace-in-string - (replace-in-string from ".*@\\([_a-zA-Z0-9-.]+\\).*" - "\\1") - "\\." "/")) "/"))) - (switch-to-buffer (gnus-get-buffer-name gnus-picons-display-where)) - (gnus-add-current-to-buffer-list) - (beginning-of-buffer) - (setq iconpoint (point)) - (if (not (looking-at "^$")) - (if buffer-read-only - (progn - (toggle-read-only) - (open-line 1) - (toggle-read-only) - ) - (open-line 1))) - - (end-of-line) - (gnus-picons-remove gnus-article-annotations) - (setq gnus-article-annotations 'nil) - (if (equal username from) - (setq username (progn - (string-match "<\\([_a-zA-Z0-9-.]+\\)>" from) - (match-string 1 from)))) - (mapcar '(lambda (pathpart) - (setq gnus-article-annotations - (append - (gnus-picons-insert-face-if-exists - (concat - (file-name-as-directory - gnus-picons-database) pathpart) - (concat hostpath username) - iconpoint) - gnus-article-annotations))) - gnus-picons-user-directories) - (mapcar '(lambda (pathpart) - (setq gnus-article-annotations - (append - (gnus-picons-insert-face-if-exists - (concat (file-name-as-directory - gnus-picons-database) pathpart) - (concat hostpath "unknown") - iconpoint) - gnus-article-annotations))) - gnus-picons-domain-directories) - (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all) - )))) - -(defun gnus-group-display-picons () - "Display icons for the group in the gnus-picons-display-where buffer." + (let ((wash-picon-p buffer-read-only)) + (gnus-with-article-buffer + (if (and wash-picon-p (memq 'mail-picon gnus-article-wash-types)) + (gnus-delete-images 'mail-picon) + (gnus-picon-transform-address "cc" 'mail-picon) + (gnus-picon-transform-address "to" 'mail-picon))))) + +;;;###autoload +(defun gnus-treat-newsgroups-picon () + "Display picons in the Newsgroups and Followup-To headers. +If picons are already displayed, remove them." (interactive) - (if (and (featurep 'xpm) - (or (not (fboundp 'device-type)) (equal (device-type) 'x))) - (save-excursion - (let - ((iconpoint (point))) - (switch-to-buffer (gnus-get-buffer-name gnus-picons-display-where)) - (gnus-add-current-to-buffer-list) - (beginning-of-buffer) - (cond - ((listp gnus-group-annotations) - (mapcar 'delete-annotation gnus-group-annotations) - (setq gnus-group-annotations nil)) - ((annotationp gnus-group-annotations) - (delete-annotation gnus-group-annotations) - (setq gnus-group-annotations nil)) - ) - (setq iconpoint (point)) - (if (not (looking-at "^$")) - (open-line 1)) - (gnus-picons-remove gnus-group-annotations) - (setq gnus-group-annotations nil) - (setq gnus-group-annotations - (gnus-picons-insert-face-if-exists - (concat (file-name-as-directory gnus-picons-database) - gnus-picons-news-directory) - (concat (replace-in-string gnus-newsgroup-name "\\." "/") - "/unknown") - iconpoint t)) - (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all))))) - - -(defun gnus-picons-insert-face-if-exists (path filename ipoint &optional rev) - "Inserts a face at point if I can find one" - (let ((pathfile (concat path "/" filename "/face")) - (newfilename - (replace-in-string filename - "[_a-zA-Z0-9-]+/\\([_A-Za-z0-9-]+\\)$" "\\1")) - (annotations nil)) - (if (and rev - (not (equal filename newfilename))) - (setq annotations (append - (gnus-picons-insert-face-if-exists path newfilename ipoint rev) - annotations))) - (if (eq (length annotations) (length (setq annotations (append - (gnus-picons-try-to-find-face (concat pathfile ".xpm") ipoint) - annotations)))) - (setq annotations (append - (gnus-picons-try-to-find-face - (concat pathfile ".xbm") ipoint) - annotations))) - (if (and (not rev) - (not (equal filename newfilename))) - (setq annotations (append - (gnus-picons-insert-face-if-exists path newfilename ipoint rev) - annotations))) - annotations - ) - ) - -(defun gnus-picons-try-to-find-face (path ipoint) - "If PATH exists, display it as a bitmap. Returns t if succedded." - (when (file-exists-p path) - (let ((gl (make-glyph path))) - (set-glyph-face gl 'default) - (list (make-annotation gl ipoint 'text))))) - -(defun gnus-picons-reverse-domain-path (str) - "a/b/c/d -> d/c/b/a" - (if (equal (replace-in-string str "^[^/]*$" "") "") - str - (concat (replace-in-string str "^.*/\\([_a-zA-Z0-9-]+\\)$" "\\1") "/" - (gnus-picons-reverse-domain-path - (replace-in-string str "^\\(.*\\)/[_a-zA-Z0-9-]+$" "\\1"))))) + (let ((wash-picon-p buffer-read-only)) + (gnus-with-article-buffer + (if (and wash-picon-p (memq 'newsgroups-picon gnus-article-wash-types)) + (gnus-delete-images 'newsgroups-picon) + (gnus-picon-transform-newsgroups "newsgroups") + (gnus-picon-transform-newsgroups "followup-to"))))) (provide 'gnus-picon)