comparison lisp/gnus/gnus-picon.el @ 142:1856695b1fa9 r20-2b5

Import from CVS: tag r20-2b5
author cvs
date Mon, 13 Aug 2007 09:33:18 +0200
parents 585fb297b004
children 59463afc5666
comparison
equal deleted inserted replaced
141:ea67ad3963dc 142:1856695b1fa9
20 ;; along with GNU Emacs; see the file COPYING. If not, write to the 20 ;; along with GNU Emacs; see the file COPYING. If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, 21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA. 22 ;; Boston, MA 02111-1307, USA.
23 23
24 ;;; Commentary: 24 ;;; Commentary:
25
26 ;;; TODO:
27 ;; See the comment in gnus-picons-remove
25 28
26 ;;; Code: 29 ;;; Code:
27 30
28 (require 'gnus) 31 (require 'gnus)
29 (require 'xpm) 32 (require 'xpm)
30 (require 'annotations) 33 (require 'annotations)
31 (require 'custom) 34 (require 'custom)
32 (require 'gnus-art) 35 (require 'gnus-art)
33 (require 'gnus-win) 36 (require 'gnus-win)
37
38 ;;; User variables:
34 39
35 (defgroup picons nil 40 (defgroup picons nil
36 "Show pictures of people, domains, and newsgroups (XEmacs). 41 "Show pictures of people, domains, and newsgroups (XEmacs).
37 For this to work, you must add gnus-group-display-picons to the 42 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 43 gnus-summary-display-hook or to the gnus-article-display-hook
111 "*Whether to move point to first empty line when displaying picons. 116 "*Whether to move point to first empty line when displaying picons.
112 This has only an effect if `gnus-picons-display-where' has value `article'." 117 This has only an effect if `gnus-picons-display-where' has value `article'."
113 :type 'boolean 118 :type 'boolean
114 :group 'picons) 119 :group 'picons)
115 120
116 (defvar gnus-picons-map (make-sparse-keymap "gnus-picons-keys") 121 (defcustom gnus-picons-clear-cache-on-shutdown t
117 "keymap to hide/show picon glyphs") 122 "*Whether to clear the picons cache when exiting gnus.
118 123 Gnus caches every picons it finds while it is running. This saves
119 (define-key gnus-picons-map [(button2)] 'gnus-picons-toggle-extent) 124 some time in the search process but eats some memory. If this
120 125 variable is set to nil, Gnus will never clear the cache itself; you
121 ;;; Internal variables. 126 will have to manually call `gnus-picons-clear-cache' to clear it.
127 Otherwise the cache will be cleared every time you exit Gnus."
128 :type 'boolean
129 :group 'picons)
130
131 (defcustom gnus-picons-piconsearch-url nil
132 "*The url to query for picons. Setting this to nil will disable it.
133 The only plublicly available address currently known is
134 http://www.cs.indiana.edu:800/piconsearch. If you know of any other,
135 please tell me so that we can list it."
136 :type '(choice (const :tag "Disable" :value nil)
137 (const :tag "www.cs.indiana.edu"
138 :value "http://www.cs.indiana.edu:800/piconsearch")
139 (string))
140 :group 'picons)
141
142 ;;; Internal variables:
143
144 (defvar gnus-picons-processes-alist nil
145 "Picons processes currently running and their environment.")
146 (defvar gnus-picons-glyph-alist nil
147 "Picons glyphs cache.
148 List of pairs (KEY . GLYPH) where KEY is either a filename or an URL.")
149 (defvar gnus-picons-url-alist nil
150 "Picons file names cache.
151 List of pairs (KEY . NAME) where KEY is (USER HOST DBS) and NAME is an URL.")
122 152
123 (defvar gnus-group-annotations nil 153 (defvar gnus-group-annotations nil
124 "List of annotations added/removed when selecting/exiting a group") 154 "List of annotations added/removed when selecting/exiting a group")
155 (defvar gnus-group-annotations-lock nil)
125 (defvar gnus-article-annotations nil 156 (defvar gnus-article-annotations nil
126 "List of annotations added/removed when selecting an article") 157 "List of annotations added/removed when selecting an article")
158 (defvar gnus-article-annotations-lock nil)
127 (defvar gnus-x-face-annotations nil 159 (defvar gnus-x-face-annotations nil
128 "List of annotations added/removed when selecting an article with an X-Face.") 160 "List of annotations added/removed when selecting an article with an
161 X-Face.")
162 (defvar gnus-x-face-annotations-lock nil)
163
164 (defvar gnus-picons-jobs-alist nil
165 "List of jobs that still need be done.
166 This is a list of (SYM-ANN TAG ARGS...) where SYM-ANN three annotations list,
167 TAG is one of `picon' or `search' indicating that the job should query a
168 picon or do a search for picons file names, and ARGS is some additionnal
169 arguments necessary for the job.")
170
171 (defvar gnus-picons-job-already-running nil
172 "Lock to ensure only one stream of http requests is running.")
173
174 ;;; Functions:
175
176 (defsubst gnus-picons-lock (symbol)
177 (intern (concat (symbol-name symbol) "-lock")))
129 178
130 (defun gnus-picons-remove (symbol) 179 (defun gnus-picons-remove (symbol)
131 "Remove all annotations/processes in variable named SYMBOL. 180 "Remove all annotations in variable named SYMBOL.
132 This function is careful to set it to nil before removing anything so that 181 This function is careful to set it to nil before removing anything so that
133 asynchronous process don't get crazy." 182 asynchronous process don't get crazy."
134 (let ((listitems (symbol-value symbol))) 183 ;; clear the lock
135 (set symbol nil) 184 (set (gnus-picons-lock symbol) nil)
136 (while listitems 185 ;; clear all annotations
137 (let ((item (pop listitems))) 186 (mapc (function (lambda (item)
138 (cond ((annotationp item) 187 (if (annotationp item)
139 (delete-annotation item)) 188 (delete-annotation item))))
140 ((processp item) 189 (prog1 (symbol-value symbol)
141 ;; kill the process, ignore any output. 190 (set symbol nil)))
142 (set-process-sentinel item (function (lambda (p e)))) 191 ;; FIXME: there's a race condition here. If a job is already
143 (delete-process item))))))) 192 ;; running, it has already removed itself from this queue... But
193 ;; will still display its picon.
194 ;; TODO: push a request to clear an annotation. Then
195 ;; gnus-picons-next-job will be able to clean up when it gets the
196 ;; hand
197 (setq gnus-picons-jobs-alist (remassq symbol gnus-picons-jobs-alist)))
144 198
145 (defun gnus-picons-remove-all () 199 (defun gnus-picons-remove-all ()
146 "Removes all picons from the Gnus display(s)." 200 "Removes all picons from the Gnus display(s)."
147 (interactive) 201 (interactive)
148 (gnus-picons-remove 'gnus-article-annotations) 202 (gnus-picons-remove 'gnus-article-annotations)
151 (when (bufferp gnus-picons-buffer) 205 (when (bufferp gnus-picons-buffer)
152 (kill-buffer gnus-picons-buffer))) 206 (kill-buffer gnus-picons-buffer)))
153 207
154 (defun gnus-get-buffer-name (variable) 208 (defun gnus-get-buffer-name (variable)
155 "Returns the buffer name associated with the contents of a variable." 209 "Returns the buffer name associated with the contents of a variable."
156 (cond ((symbolp variable) 210 (cond ((symbolp variable) (let ((newvar (cdr (assq variable
157 (let ((newvar (cdr (assq variable gnus-window-to-buffer)))) 211 gnus-window-to-buffer))))
158 (cond ((symbolp newvar) 212 (cond ((symbolp newvar)
159 (symbol-value newvar)) 213 (symbol-value newvar))
160 ((stringp newvar) newvar)))) 214 ((stringp newvar) newvar))))
161 ((stringp variable) 215 ((stringp variable) variable)))
162 variable)))
163 216
164 (defun gnus-picons-prepare-for-annotations (annotations) 217 (defun gnus-picons-prepare-for-annotations (annotations)
165 "Prepare picons buffer for puting annotations memorized in ANNOTATIONS. 218 "Prepare picons buffer for puting annotations memorized in ANNOTATIONS.
166 ANNOTATIONS should be a symbol naming a variable wich contains a list of 219 ANNOTATIONS should be a symbol naming a variable wich contains a list of
167 annotations. Sets buffer to `gnus-picons-display-where'." 220 annotations. Sets buffer to `gnus-picons-display-where'."
173 (gnus-add-current-to-buffer-list) 226 (gnus-add-current-to-buffer-list)
174 (goto-char (point-min)) 227 (goto-char (point-min))
175 (if (and (eq gnus-picons-display-where 'article) 228 (if (and (eq gnus-picons-display-where 'article)
176 gnus-picons-display-article-move-p) 229 gnus-picons-display-article-move-p)
177 (when (search-forward "\n\n" nil t) 230 (when (search-forward "\n\n" nil t)
178 (forward-line -1))) 231 (forward-line -1))
232 (make-local-variable 'inhibit-read-only)
233 (setq buffer-read-only t
234 inhibit-read-only nil))
179 (gnus-picons-remove annotations)) 235 (gnus-picons-remove annotations))
180 236
181 (defun gnus-picons-article-display-x-face () 237 (defun gnus-picons-article-display-x-face ()
182 "Display the x-face header bitmap in the 'gnus-picons-display-where buffer." 238 "Display the x-face header bitmap in the 'gnus-picons-display-where buffer."
183 ;; delete any old ones. 239 ;; delete any old ones.
187 ;; display the new one. 243 ;; display the new one.
188 (let ((gnus-article-x-face-command 'gnus-picons-display-x-face)) 244 (let ((gnus-article-x-face-command 'gnus-picons-display-x-face))
189 (gnus-article-display-x-face))) 245 (gnus-article-display-x-face)))
190 246
191 (defun gnus-picons-x-face-sentinel (process event) 247 (defun gnus-picons-x-face-sentinel (process event)
192 ;; don't call gnus-picons-prepare-for-annotations, it would reset 248 (let* ((env (assq process gnus-picons-processes-alist))
193 ;; gnus-x-face-annotations. 249 (annot (cdr env)))
194 (set-buffer (get-buffer-create 250 (setq gnus-picons-processes-alist (remassq process
195 (gnus-get-buffer-name gnus-picons-display-where))) 251 gnus-picons-processes-alist))
196 (gnus-add-current-to-buffer-list) 252 (when annot
197 (goto-char (point-min)) 253 (set-annotation-glyph annot
198 (if (and (eq gnus-picons-display-where 'article) 254 (make-glyph gnus-picons-x-face-file-name))
199 gnus-picons-display-article-move-p) 255 (if (memq annot gnus-x-face-annotations)
200 (when (search-forward "\n\n" nil t) 256 (delete-file gnus-picons-x-face-file-name)))))
201 (forward-line -1)))
202 ;; If the process is still in the list, insert this icon
203 (let ((myself (member process gnus-x-face-annotations)))
204 (when myself
205 (setcar myself
206 (make-annotation gnus-picons-x-face-file-name nil 'text))
207 (delete-file gnus-picons-x-face-file-name))))
208 257
209 (defun gnus-picons-display-x-face (beg end) 258 (defun gnus-picons-display-x-face (beg end)
210 "Function to display the x-face header in the picons window. 259 "Function to display the x-face header in the picons window.
211 To use: (setq gnus-article-x-face-command 'gnus-picons-display-x-face)" 260 To use: (setq gnus-article-x-face-command 'gnus-picons-display-x-face)"
212 (interactive) 261 (interactive)
214 ;; Use builtin support 263 ;; Use builtin support
215 (let ((buf (current-buffer))) 264 (let ((buf (current-buffer)))
216 (save-excursion 265 (save-excursion
217 (gnus-picons-prepare-for-annotations 'gnus-x-face-annotations) 266 (gnus-picons-prepare-for-annotations 'gnus-x-face-annotations)
218 (setq gnus-x-face-annotations 267 (setq gnus-x-face-annotations
219 (cons (make-annotation (concat "X-Face: " 268 (cons (make-annotation
220 (buffer-substring beg end buf)) 269 (vector 'xface
270 :data (concat "X-Face: "
271 (buffer-substring beg end buf)))
221 nil 'text) 272 nil 'text)
222 gnus-x-face-annotations)))) 273 gnus-x-face-annotations))))
223 ;; convert the x-face header to a .xbm file 274 ;; convert the x-face header to a .xbm file
224 (let* ((process-connection-type nil) 275 (let* ((process-connection-type nil)
225 (process (start-process "gnus-x-face" nil 276 (annot (save-excursion
226 shell-file-name shell-command-switch 277 (gnus-picons-prepare-for-annotations
227 gnus-picons-convert-x-face))) 278 'gnus-x-face-annotations)
279 (make-annotation nil nil 'text)))
280 (process (start-process-shell-command "gnus-x-face" nil
281 gnus-picons-convert-x-face)))
282 (push annot gnus-x-face-annotations)
283 (push (cons process annot) gnus-picons-processes-alist)
228 (process-kill-without-query process) 284 (process-kill-without-query process)
229 (setq gnus-x-face-annotations (list process))
230 (set-process-sentinel process 'gnus-picons-x-face-sentinel) 285 (set-process-sentinel process 'gnus-picons-x-face-sentinel)
231 (process-send-region process beg end) 286 (process-send-region process beg end)
232 (process-send-eof process)))) 287 (process-send-eof process))))
233 288
234 (defun gnus-article-display-picons () 289 (defun gnus-article-display-picons ()
236 (interactive) 291 (interactive)
237 (let (from at-idx) 292 (let (from at-idx)
238 (when (and (featurep 'xpm) 293 (when (and (featurep 'xpm)
239 (or (not (fboundp 'device-type)) (equal (device-type) 'x)) 294 (or (not (fboundp 'device-type)) (equal (device-type) 'x))
240 (setq from (mail-fetch-field "from")) 295 (setq from (mail-fetch-field "from"))
241 (setq from (downcase 296 (setq from (downcase (or (cadr (mail-extract-address-components
242 (or (cadr (mail-extract-address-components from)) 297 from))
243 ""))) 298 "")))
244 (or (setq at-idx (string-match "@" from)) 299 (or (setq at-idx (string-match "@" from))
245 (setq at-idx (length from)))) 300 (setq at-idx (length from))))
246 (save-excursion 301 (save-excursion
247 (let ((username (substring from 0 at-idx)) 302 (let ((username (downcase (substring from 0 at-idx)))
248 (addrs (if (eq at-idx (length from)) 303 (addrs (if (eq at-idx (length from))
249 (if gnus-local-domain 304 (if gnus-local-domain
250 (message-tokenize-header gnus-local-domain ".") 305 (message-tokenize-header gnus-local-domain "."))
251 nil)
252 (message-tokenize-header (substring from (1+ at-idx)) 306 (message-tokenize-header (substring from (1+ at-idx))
253 ".")))) 307 "."))))
254 (gnus-picons-prepare-for-annotations 'gnus-article-annotations) 308 (gnus-picons-prepare-for-annotations 'gnus-article-annotations)
255 (setq gnus-article-annotations 309 (if (null gnus-picons-piconsearch-url)
256 (nconc gnus-article-annotations 310 (setq gnus-article-annotations
257 ;; look for domain paths. 311 (nconc gnus-article-annotations
258 (gnus-picons-display-pairs 312 (gnus-picons-display-pairs
259 (gnus-picons-lookup-pairs addrs 313 (gnus-picons-lookup-pairs
260 gnus-picons-domain-directories) 314 addrs gnus-picons-domain-directories)
261 (not (or gnus-picons-display-as-address 315 (not (or gnus-picons-display-as-address
262 gnus-article-annotations)) 316 gnus-article-annotations))
263 nil "." t) 317 "." t)
264 ;; add an '@' if displaying as address 318 (if (and gnus-picons-display-as-address addrs)
265 (if (and gnus-picons-display-as-address addrs) 319 (list (make-annotation [string :data "@"] nil
266 (list (make-annotation "@" nil 'text nil nil nil t))) 320 'text nil nil nil t)))
267 ;; then do user directories, 321 (gnus-picons-display-picon-or-name
268 (gnus-picons-display-picon-or-name 322 (gnus-picons-lookup-user username addrs)
269 (gnus-picons-lookup-user (downcase username) addrs) 323 username t)))
270 username nil t))) 324 (push (list 'gnus-article-annotations 'search username addrs
325 gnus-picons-domain-directories t)
326 gnus-picons-jobs-alist)
327 (gnus-picons-next-job))
271 328
272 (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all)))))) 329 (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all))))))
273 330
274 (defun gnus-group-display-picons () 331 (defun gnus-group-display-picons ()
275 "Display icons for the group in the gnus-picons-display-where buffer." 332 "Display icons for the group in the gnus-picons-display-where buffer."
276 (interactive) 333 (interactive)
277 (when (and (featurep 'xpm) 334 (when (and (featurep 'xpm)
278 (or (not (fboundp 'device-type)) (equal (device-type) 'x))) 335 (or (not (fboundp 'device-type)) (equal (device-type) 'x)))
279 (save-excursion 336 (save-excursion
280 (gnus-picons-prepare-for-annotations 'gnus-group-annotations) 337 (gnus-picons-prepare-for-annotations 'gnus-group-annotations)
281 (setq gnus-group-annotations 338 (if (null gnus-picons-piconsearch-url)
282 (gnus-picons-display-pairs 339 (setq gnus-group-annotations
283 (gnus-picons-lookup-pairs (reverse (message-tokenize-header 340 (gnus-picons-display-pairs
284 gnus-newsgroup-name ".")) 341 (gnus-picons-lookup-pairs (reverse (message-tokenize-header
285 gnus-picons-news-directory) 342 gnus-newsgroup-name "."))
286 t nil ".")) 343 gnus-picons-news-directory)
344 t "."))
345 (push (list 'gnus-group-annotations 'search nil
346 (message-tokenize-header gnus-newsgroup-name ".")
347 (if (listp gnus-picons-news-directory)
348 gnus-picons-news-directory
349 (list gnus-picons-news-directory))
350 nil)
351 gnus-picons-jobs-alist)
352 (gnus-picons-next-job))
353
287 (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all)))) 354 (add-hook 'gnus-summary-exit-hook 'gnus-picons-remove-all))))
288 355
289 (defun gnus-picons-make-path (dir subdirs) 356 (defsubst gnus-picons-lookup-internal (addrs dir)
290 "Make a directory name from a base DIR and a list of SUBDIRS. 357 (setq dir (expand-file-name dir gnus-picons-database))
291 Returns a directory name build by concatenating DIR and all elements of 358 (gnus-picons-try-face (dolist (part (reverse addrs) dir)
292 SUBDIRS with \"/\" between elements." 359 (setq dir (expand-file-name part dir)))))
293 (while subdirs
294 (setq dir (file-name-as-directory (concat dir (pop subdirs)))))
295 dir)
296
297 (defsubst gnus-picons-try-suffixes (file)
298 (let ((suffixes gnus-picons-file-suffixes)
299 f)
300 (while (and suffixes
301 (not (file-exists-p (setq f (concat file (pop suffixes))))))
302 (setq f nil))
303 f))
304 360
305 (defun gnus-picons-lookup (addrs dirs) 361 (defun gnus-picons-lookup (addrs dirs)
306 "Lookup the picon for ADDRS in databases DIRS. 362 "Lookup the picon for ADDRS in databases DIRS.
307 Returns the picon filename or NIL if none found." 363 Returns the picon filename or NIL if none found."
308 (let (result) 364 (let (result)
309 (while (and dirs (null result)) 365 (while (and dirs (null result))
310 (setq result 366 (setq result (gnus-picons-lookup-internal addrs (pop dirs))))
311 (gnus-picons-try-suffixes
312 (expand-file-name "face."
313 (gnus-picons-make-path
314 (file-name-as-directory
315 (concat
316 (file-name-as-directory gnus-picons-database)
317 (pop dirs)))
318 (reverse addrs))))))
319 result)) 367 result))
320 368
321 (defun gnus-picons-lookup-user-internal (user domains) 369 (defun gnus-picons-lookup-user-internal (user domains)
322 (let ((dirs gnus-picons-user-directories) 370 (let ((dirs gnus-picons-user-directories)
323 picon) 371 domains-tmp dir picon)
324 (while (and dirs (null picon)) 372 (while (and dirs (null picon))
325 (let ((dir (list (pop dirs))) 373 (setq domains-tmp domains
326 (domains domains)) 374 dir (pop dirs))
327 (while (and domains (null picon)) 375 (while (and domains-tmp
328 (setq picon (gnus-picons-lookup (cons user domains) dir)) 376 (null (setq picon (gnus-picons-lookup-internal
329 (pop domains)) 377 (cons user domains-tmp) dir))))
330 ;; Also make a try MISC subdir 378 (pop domains-tmp))
331 (unless picon 379 ;; Also make a try in MISC subdir
332 (setq picon (gnus-picons-lookup (list user "MISC") dir))))) 380 (unless picon
333 381 (setq picon (gnus-picons-lookup-internal (list user "MISC") dir))))
334 picon)) 382 picon))
335 383
336 (defun gnus-picons-lookup-user (user domains) 384 (defun gnus-picons-lookup-user (user domains)
337 "Lookup the picon for USER at DOMAINS. 385 "Lookup the picon for USER at DOMAINS.
338 USER is a string containing a name. 386 USER is a string containing a name.
343 (defun gnus-picons-lookup-pairs (domains directories) 391 (defun gnus-picons-lookup-pairs (domains directories)
344 "Lookup picons for DOMAINS and all its parents in DIRECTORIES. 392 "Lookup picons for DOMAINS and all its parents in DIRECTORIES.
345 Returns a list of PAIRS whose CAR is the picon filename or NIL if 393 Returns a list of PAIRS whose CAR is the picon filename or NIL if
346 none, and whose CDR is the corresponding element of DOMAINS." 394 none, and whose CDR is the corresponding element of DOMAINS."
347 (let (picons) 395 (let (picons)
396 (setq directories (if (listp directories)
397 directories
398 (list directories)))
348 (while domains 399 (while domains
349 (push (list (gnus-picons-lookup (cons "unknown" domains) 400 (push (list (gnus-picons-lookup (cons "unknown" domains) directories)
350 (if (listp directories)
351 directories
352 (list directories)))
353 (pop domains)) 401 (pop domains))
354 picons)) 402 picons))
355 picons)) 403 picons))
356 404
357 (defun gnus-picons-display-picon-or-name (picon name &optional xface-p right-p) 405 (defun gnus-picons-display-picon-or-name (picon name &optional right-p)
358 (if picon 406 (cond (picon (gnus-picons-display-glyph picon name right-p))
359 (gnus-picons-try-to-find-face picon xface-p name right-p) 407 (gnus-picons-display-as-address (list (make-annotation
360 (list (make-annotation name nil 'text nil nil nil right-p)))) 408 (vector 'string :data name)
361 409 nil 'text
362 (defun gnus-picons-display-pairs (pairs &optional bar-p xface-p dot-p right-p) 410 nil nil nil right-p)))))
411
412 (defun gnus-picons-display-pairs (pairs &optional bar-p dot-p right-p)
363 "Display picons in list PAIRS." 413 "Display picons in list PAIRS."
364 (let ((bar (and bar-p (or gnus-picons-display-as-address 414 (let ((bar (and bar-p (or gnus-picons-display-as-address
365 (annotations-in-region (point) 415 (annotations-in-region (point)
366 (min (point-max) (1+ (point))) 416 (min (point-max)
367 (current-buffer))))) 417 (1+ (point)))
418 (current-buffer)))))
368 (domain-p (and gnus-picons-display-as-address dot-p)) 419 (domain-p (and gnus-picons-display-as-address dot-p))
369 picons) 420 pair picons)
370 (while pairs 421 (while pairs
371 (let ((pair (pop pairs))) 422 (setq pair (pop pairs)
372 (setq picons (nconc (if (and domain-p picons (not right-p)) 423 picons (nconc (if (and domain-p picons (not right-p))
373 (list (make-annotation 424 (list (make-annotation
374 dot-p nil 'text nil nil nil right-p))) 425 (vector 'string :data dot-p)
375 (gnus-picons-display-picon-or-name (car pair) 426 nil 'text nil nil nil right-p)))
376 (cadr pair) 427 (gnus-picons-display-picon-or-name (car pair)
377 xface-p 428 (cadr pair)
378 right-p) 429 right-p)
379 (if (and domain-p pairs right-p) 430 (if (and domain-p pairs right-p)
380 (list (make-annotation 431 (list (make-annotation
381 dot-p nil 'text nil nil nil right-p))) 432 (vector 'string :data dot-p)
382 (when (and bar domain-p) 433 nil 'text nil nil nil right-p)))
383 (setq bar nil) 434 (when (and bar domain-p)
384 (gnus-picons-try-to-find-face 435 (setq bar nil)
385 (expand-file-name "bar.xbm" 436 (gnus-picons-display-glyph
386 gnus-xmas-glyph-directory) 437 (gnus-picons-try-face gnus-xmas-glyph-directory
387 nil nil t)) 438 "bar.")
388 picons)))) 439 nil t))
440 picons)))
389 picons)) 441 picons))
390 442
391 (defvar gnus-picons-glyph-alist nil) 443 (defun gnus-picons-try-face (dir &optional filebase)
392 444 (let* ((dir (file-name-as-directory dir))
393 (defun gnus-picons-try-to-find-face (path &optional xface-p part rightp) 445 (filebase (or filebase "face."))
394 "If PATH exists, display it as a bitmap. Returns t if succeeded." 446 (key (concat dir filebase))
395 (let ((glyph (and (not xface-p) 447 (glyph (cdr (assoc key gnus-picons-glyph-alist)))
396 (cdr (assoc path gnus-picons-glyph-alist))))) 448 (suffixes gnus-picons-file-suffixes)
397 (when (or glyph (file-exists-p path)) 449 f)
398 (unless glyph 450 (while (and suffixes (null glyph))
399 (setq glyph (make-glyph path)) 451 (when (file-exists-p (setq f (expand-file-name (concat filebase
400 (unless xface-p 452 (pop suffixes))
401 (push (cons path glyph) gnus-picons-glyph-alist)) 453 dir)))
402 (set-glyph-face glyph 'default)) 454 (setq glyph (make-glyph f))
403 (let ((new (make-annotation glyph (point) 'text nil nil nil rightp))) 455 (push (cons key glyph) gnus-picons-glyph-alist)))
404 (nconc 456 glyph))
405 (list new) 457
406 (when (and (eq major-mode 'gnus-article-mode) 458 (defun gnus-picons-display-glyph (glyph &optional part rightp)
407 (not gnus-picons-display-as-address) 459 (let ((new (make-annotation glyph (point) 'text nil nil nil rightp)))
408 (not part)) 460 (when (and part gnus-picons-display-as-address)
409 (list (make-annotation " " (point) 'text nil nil nil rightp))) 461 (set-annotation-data new (cons new
410 (when (and part gnus-picons-display-as-address) 462 (make-glyph (vector 'string :data part))))
411 (let ((txt (make-annotation part (point) 'text nil nil nil rightp))) 463 (set-annotation-action new 'gnus-picons-action-toggle))
412 (hide-annotation txt) 464 (nconc
413 (set-extent-property txt 'its-partner new) 465 (list new)
414 (set-extent-property txt 'keymap gnus-picons-map) 466 (if (and (eq major-mode 'gnus-article-mode)
415 (set-extent-property txt 'mouse-face gnus-article-mouse-face) 467 (not gnus-picons-display-as-address)
416 (set-extent-property new 'its-partner txt) 468 (not part))
417 (set-extent-property new 'keymap gnus-picons-map) 469 (list (make-annotation [string :data " "]
418 (list txt)))))))) 470 (point) 'text nil nil nil rightp))))))
419 471
420 (defun gnus-picons-toggle-extent (event) 472 (defun gnus-picons-action-toggle (data)
421 "Toggle picon glyph at given point" 473 "Toggle annotation"
422 (interactive "e") 474 (interactive "e")
423 (let* ((ant1 (event-glyph-extent event)) 475 (let* ((annot (car data))
424 (ant2 (extent-property ant1 'its-partner))) 476 (glyph (annotation-glyph annot)))
425 (when (and (annotationp ant1) (annotationp ant2)) 477 (set-annotation-glyph annot (cdr data))
426 (reveal-annotation ant2) 478 (set-annotation-data annot (cons annot glyph))))
427 (hide-annotation ant1)))) 479
480 (defun gnus-picons-clear-cache ()
481 "Clear the picons cache"
482 (interactive)
483 (setq gnus-picons-glyph-alist nil))
428 484
429 (gnus-add-shutdown 'gnus-picons-close 'gnus) 485 (gnus-add-shutdown 'gnus-picons-close 'gnus)
430 486
431 (defun gnus-picons-close () 487 (defun gnus-picons-close ()
432 "Shut down the picons." 488 "Shut down the picons."
433 (setq gnus-picons-glyph-alist nil)) 489 (if gnus-picons-clear-cache-on-shutdown
490 (gnus-picons-clear-cache)))
491
492 ;;; Query a remote DB. This requires some stuff from w3 !
493
494 (require 'url)
495 (require 'w3-forms)
496
497 (defun gnus-picons-url-retrieve (url fn arg)
498 (let ((old-asynch (default-value 'url-be-asynchronous))
499 (url-working-buffer (generate-new-buffer " *picons*"))
500 (url-request-method nil)
501 (url-package-name "Gnus")
502 (url-package-version gnus-version-number))
503 (setq-default url-be-asynchronous t)
504 (save-excursion
505 (set-buffer url-working-buffer)
506 (setq url-be-asynchronous t
507 url-show-status nil
508 url-current-callback-data arg
509 url-current-callback-func fn)
510 (url-retrieve url t))
511 (setq-default url-be-asynchronous old-asynch)))
512
513 (defun gnus-picons-make-glyph (type)
514 "Make a TYPE glyph using current buffer as data. Handles xbm nicely."
515 (cond ((null type) nil)
516 ((eq type 'xbm) (let ((fname (make-temp-name "/tmp/picon")))
517 (write-region (point-min) (point-max) fname
518 nil 'quiet)
519 (prog1 (make-glyph (vector 'xbm :file fname))
520 (delete-file fname))))
521 (t (make-glyph (vector type :data (buffer-string))))))
522
523 ;;; Parsing of piconsearch result page.
524
525 ;; Assumes:
526 ;; 1 - each value field has the form: "<strong>key</strong> = <kbd>value</kbd>"
527 ;; 2 - a "<p>" separates the keywords from the results
528 ;; 3 - every results begins by the path within the database at the beginning
529 ;; of the line in raw text.
530 ;; 3b - and the href following it is the preferred image type.
531
532 ;; if 1 or 2 is not met, it will probably cause an error. The other
533 ;; will go undetected
534
535 (defun gnus-picons-parse-value (name)
536 (goto-char (point-min))
537 (re-search-forward (concat "<strong>"
538 (regexp-quote name)
539 "</strong> *= *<kbd> *\\([^ <][^<]*\\) *</kbd>"))
540 (buffer-substring (match-beginning 1) (match-end 1)))
541
542 (defun gnus-picons-parse-filenames ()
543 ;; returns an alist of ((USER ADDRS DB) . URL)
544 (let* ((case-fold-search t)
545 (user (gnus-picons-parse-value "user"))
546 (host (gnus-picons-parse-value "host"))
547 (dbs (message-tokenize-header (gnus-picons-parse-value "db") " "))
548 (start-re
549 (concat
550 ;; dbs
551 "^\\(" (mapconcat 'identity dbs "\\|") "\\)/"
552 ;; host
553 "\\(\\(" (replace-in-string host "\\." "/\\|" t) "/\\|MISC/\\)*\\)"
554 ;; user
555 "\\(" (regexp-quote user) "\\|unknown\\)/"
556 "face\\."))
557 cur-db cur-host cur-user types res)
558 ;; now point will be somewhere in the header. Find beginning of
559 ;; entries
560 (re-search-forward "<p>[ \t\n]*")
561 (while (re-search-forward start-re nil t)
562 (setq cur-db (buffer-substring (match-beginning 1) (match-end 1))
563 cur-host (buffer-substring (match-beginning 2) (match-end 2))
564 cur-user (buffer-substring (match-beginning 4) (match-end 4))
565 cur-host (nreverse (message-tokenize-header cur-host "/")))
566 ;; XXX - KLUDGE: there is a blank picon in news/MISC/unknown
567 (unless (and (string-equal cur-db "news")
568 (string-equal cur-user "unknown")
569 (equal cur-host '("MISC")))
570 ;; ok now we have found an entry (USER HOST DB), find the
571 ;; corresponding picon URL
572 (save-restriction
573 ;; restrict region to this entry
574 (narrow-to-region (point) (search-forward "<br>"))
575 (goto-char (point-min))
576 (setq types gnus-picons-file-suffixes)
577 (while (and types
578 (not (re-search-forward
579 (concat "<a[ \t\n]+href=\"\\([^\"]*\\."
580 (regexp-quote (car types)) "\\)\"")
581 nil t)))
582 (pop types))
583 (push (cons (list cur-user cur-host cur-db)
584 (buffer-substring (match-beginning 1) (match-end 1)))
585 res))))
586 (nreverse res)))
587
588 ;;; picon network display functions :
589
590 (defun gnus-picons-network-display-internal (sym-ann glyph part right-p)
591 (set-buffer
592 (get-buffer-create (gnus-get-buffer-name gnus-picons-display-where)))
593 (set sym-ann (nconc (symbol-value sym-ann)
594 (gnus-picons-display-picon-or-name glyph part right-p)))
595 (gnus-picons-next-job-internal))
596
597 (defun gnus-picons-network-display-callback (url part sym-ann right-p)
598 (let ((glyph (gnus-picons-make-glyph (cdr (assoc url-current-mime-type
599 w3-image-mappings)))))
600 (kill-buffer (current-buffer))
601 (push (cons url glyph) gnus-picons-glyph-alist)
602 (gnus-picons-network-display-internal sym-ann glyph part right-p)))
603
604 (defun gnus-picons-network-display (url part sym-ann right-p)
605 (let ((cache (assoc url gnus-picons-glyph-alist)))
606 (if (or cache (null url))
607 (gnus-picons-network-display-internal sym-ann (cdr cache) part right-p)
608 (gnus-picons-url-retrieve url 'gnus-picons-network-display-callback
609 (list url part sym-ann right-p)))))
610
611 ;;; search job functions
612
613 (defun gnus-picons-network-search-internal (user addrs dbs sym-ann right-p
614 &optional fnames)
615 (let (curkey dom pfx url dbs-tmp cache new-jobs)
616 ;; First do the domain search
617 (dolist (part (if right-p
618 (reverse addrs)
619 addrs))
620 (setq pfx (nconc (list part) pfx)
621 dom (cond ((and dom right-p) (concat part "." dom))
622 (dom (concat dom "." part))
623 (t part))
624 curkey (list "unknown" dom dbs))
625 (when (null (setq cache (assoc curkey gnus-picons-url-alist)))
626 ;; This one is not yet in the cache, create a new entry
627 ;; Search for an entry
628 (setq dbs-tmp dbs
629 url nil)
630 (while (and dbs-tmp (null url))
631 (setq url (or (cdr (assoc (list "unknown" pfx (car dbs-tmp)) fnames))
632 (and (eq dom part)
633 ;; This is the first component. Try the
634 ;; catch-all MISC component
635 (cdr (assoc (list "unknown"
636 '("MISC")
637 (car dbs-tmp))
638 fnames)))))
639 (pop dbs-tmp))
640 (push (setq cache (cons curkey url)) gnus-picons-url-alist))
641 ;; Put this glyph in the job list
642 (if (and (not (eq dom part)) gnus-picons-display-as-address)
643 (push (list sym-ann "." right-p) new-jobs))
644 (push (list sym-ann 'picon (cdr cache) part right-p) new-jobs))
645 ;; next, the user search
646 (when user
647 (setq curkey (list user dom gnus-picons-user-directories))
648 (if (null (setq cache (assoc curkey gnus-picons-url-alist)))
649 (let ((users (list user "unknown"))
650 dirs usr domains-tmp dir picon)
651 (while (and users (null picon))
652 (setq dirs gnus-picons-user-directories
653 usr (pop users))
654 (while (and dirs (null picon))
655 (setq domains-tmp addrs
656 dir (pop dirs))
657 (while (and domains-tmp
658 (null (setq picon (assoc (list usr domains-tmp dir)
659 fnames))))
660 (pop domains-tmp))
661 (unless picon
662 (setq picon (assoc (list usr '("MISC") dir) fnames)))))
663 (push (setq cache (cons curkey (cdr picon)))
664 gnus-picons-url-alist)))
665 (if (and gnus-picons-display-as-address new-jobs)
666 (push (list sym-ann "@" right-p) new-jobs))
667 (push (list sym-ann 'picon (cdr cache) user right-p) new-jobs))
668 (setq gnus-picons-jobs-alist (nconc (nreverse new-jobs)
669 gnus-picons-jobs-alist))
670 (gnus-picons-next-job-internal)))
671
672 (defun gnus-picons-network-search-callback (user addrs dbs sym-ann right-p)
673 (gnus-picons-network-search-internal user addrs dbs sym-ann right-p
674 (prog1 (gnus-picons-parse-filenames)
675 (kill-buffer (current-buffer)))))
676
677 (defun gnus-picons-network-search (user addrs dbs sym-ann right-p)
678 (let* ((host (mapconcat 'identity addrs "."))
679 (key (list (or user "unknown") host (if user
680 gnus-picons-user-directories
681 dbs)))
682 (cache (assoc key gnus-picons-url-alist)))
683 (if (null cache)
684 (gnus-picons-url-retrieve
685 (concat gnus-picons-piconsearch-url
686 "?user=" (w3-form-encode-xwfu (or user "unknown"))
687 "&host=" (w3-form-encode-xwfu host)
688 "&db=" (mapconcat 'w3-form-encode-xwfu
689 (if user
690 (append dbs
691 gnus-picons-user-directories)
692 dbs)
693 "+"))
694 'gnus-picons-network-search-callback
695 (list user addrs dbs sym-ann right-p))
696 (gnus-picons-network-search-internal user addrs dbs sym-ann right-p))))
697
698 ;;; Main jobs dispatcher function
699 ;; Given that XEmacs is not really multi threaded, this locking should
700 ;; be sufficient
701
702 (defun gnus-picons-next-job-internal ()
703 (if gnus-picons-jobs-alist
704 (let* ((job (pop gnus-picons-jobs-alist))
705 (sym-ann (pop job))
706 (tag (pop job)))
707 (if tag
708 (cond ((stringp tag);; (SYM-ANN "..." RIGHT-P)
709 (gnus-picons-network-display-internal sym-ann nil tag
710 (pop job)))
711 ((eq 'search tag);; (SYM-ANN 'search USER ADDRS DBS RIGHT-P)
712 (gnus-picons-network-search
713 (pop job) (pop job) (pop job) sym-ann (pop job)))
714 ((eq 'picon tag);; (SYM-ANN 'picon URL PART RIGHT-P)
715 (gnus-picons-network-display
716 (pop job) (pop job) sym-ann (pop job)))
717 (t (error "Unknown picon job tag %s" tag)))))
718 (setq gnus-picons-job-already-running nil)))
719
720 (defun gnus-picons-next-job ()
721 "Start processing the job queue."
722 (unless gnus-picons-job-already-running
723 (setq gnus-picons-job-already-running t)
724 (gnus-picons-next-job-internal)))
434 725
435 (provide 'gnus-picon) 726 (provide 'gnus-picon)
436 727
437 ;;; gnus-picon.el ends here 728 ;;; gnus-picon.el ends here