16b2dc7f4fbaf3b07e47afe12bd88df9da5926ed
[elisp/semi.git] / mime-view.el
1 ;;; mime-view.el --- interactive MIME viewer for GNU Emacs
2
3 ;; Copyright (C) 1995,1996,1997,1998,1999 Free Software Foundation, Inc.
4
5 ;; Author: MORIOKA Tomohiko <morioka@jaist.ac.jp>
6 ;; Created: 1994/07/13
7 ;;      Renamed: 1994/08/31 from tm-body.el
8 ;;      Renamed: 1997/02/19 from tm-view.el
9 ;; Keywords: MIME, multimedia, mail, news
10
11 ;; This file is part of SEMI (Sample of Elastic MIME Interfaces).
12
13 ;; This program is free software; you can redistribute it and/or
14 ;; modify it under the terms of the GNU General Public License as
15 ;; published by the Free Software Foundation; either version 2, or (at
16 ;; your option) any later version.
17
18 ;; This program is distributed in the hope that it will be useful, but
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 ;; General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
25 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26 ;; Boston, MA 02111-1307, USA.
27
28 ;;; Code:
29
30 (require 'emu)
31 (require 'mime)
32 (require 'semi-def)
33 (require 'calist)
34 (require 'alist)
35 (require 'mailcap)
36
37
38 ;;; @ version
39 ;;;
40
41 (defconst mime-view-version
42   (eval-when-compile
43     (concat (mime-product-name mime-user-interface-product) " MIME-View "
44             (mapconcat #'number-to-string
45                        (mime-product-version mime-user-interface-product) ".")
46             " (" (mime-product-code-name mime-user-interface-product) ")")))
47
48
49 ;;; @ variables
50 ;;;
51
52 (defgroup mime-view nil
53   "MIME view mode"
54   :group 'mime)
55
56 (defcustom mime-view-find-every-acting-situation t
57   "*Find every available acting-situation if non-nil."
58   :group 'mime-view
59   :type 'boolean)
60
61 (defcustom mime-acting-situation-examples-file "~/.mime-example"
62   "*File name of example about acting-situation demonstrated by user."
63   :group 'mime-view
64   :type 'file)
65
66
67 ;;; @ in raw-buffer (representation space)
68 ;;;
69
70 (defvar mime-preview-buffer nil
71   "MIME-preview buffer corresponding with the (raw) buffer.")
72 (make-variable-buffer-local 'mime-preview-buffer)
73
74
75 (defvar mime-raw-representation-type-alist
76   '((mime-show-message-mode     . binary)
77     (mime-temp-message-mode     . binary)
78     (t                          . cooked)
79     )
80   "Alist of major-mode vs. representation-type of mime-raw-buffer.
81 Each element looks like (SYMBOL . REPRESENTATION-TYPE).  SYMBOL is
82 major-mode or t.  t means default.  REPRESENTATION-TYPE must be
83 `binary' or `cooked'.")
84
85
86 (defun mime-raw-find-entity-from-point (point &optional message-info)
87   "Return entity from POINT in mime-raw-buffer.
88 If optional argument MESSAGE-INFO is not specified,
89 `mime-message-structure' is used."
90   (or message-info
91       (setq message-info mime-message-structure))
92   (if (and (<= (mime-entity-point-min message-info) point)
93            (<= point (mime-entity-point-max message-info)))
94       (let ((children (mime-entity-children message-info)))
95         (catch 'tag
96           (while children
97             (let ((ret
98                    (mime-raw-find-entity-from-point point (car children))))
99               (if ret
100                   (throw 'tag ret)
101                 ))
102             (setq children (cdr children)))
103           message-info))))
104
105
106 ;;; @ in preview-buffer (presentation space)
107 ;;;
108
109 (defvar mime-mother-buffer nil
110   "Mother buffer corresponding with the (MIME-preview) buffer.
111 If current MIME-preview buffer is generated by other buffer, such as
112 message/partial, it is called `mother-buffer'.")
113 (make-variable-buffer-local 'mime-mother-buffer)
114
115 (defvar mime-raw-buffer nil
116   "Raw buffer corresponding with the (MIME-preview) buffer.")
117 (make-variable-buffer-local 'mime-raw-buffer)
118
119 (defvar mime-preview-original-window-configuration nil
120   "Window-configuration before mime-view-mode is called.")
121 (make-variable-buffer-local 'mime-preview-original-window-configuration)
122
123 (defun mime-preview-original-major-mode (&optional recursive)
124   "Return major-mode of original buffer.
125 If optional argument RECURSIVE is non-nil and current buffer has
126 mime-mother-buffer, it returns original major-mode of the
127 mother-buffer."
128   (if (and recursive mime-mother-buffer)
129       (save-excursion
130         (set-buffer mime-mother-buffer)
131         (mime-preview-original-major-mode recursive)
132         )
133     (save-excursion
134       (set-buffer
135        (mime-entity-buffer
136         (get-text-property (point-min) 'mime-view-entity)))
137       major-mode)))
138
139
140 ;;; @ entity information
141 ;;;
142
143 (defun mime-entity-situation (entity)
144   "Return situation of ENTITY."
145   (append (or (mime-entity-content-type entity)
146               (make-mime-content-type 'text 'plain))
147           (let ((d (mime-entity-content-disposition entity)))
148             (cons (cons 'disposition-type
149                         (mime-content-disposition-type d))
150                   (mapcar (function
151                            (lambda (param)
152                              (let ((name (car param)))
153                                (cons (cond ((string= name "filename")
154                                             'filename)
155                                            ((string= name "creation-date")
156                                             'creation-date)
157                                            ((string= name "modification-date")
158                                             'modification-date)
159                                            ((string= name "read-date")
160                                             'read-date)
161                                            ((string= name "size")
162                                             'size)
163                                            (t (cons 'disposition (car param))))
164                                      (cdr param)))))
165                           (mime-content-disposition-parameters d))
166                   ))
167           (list (cons 'encoding (mime-entity-encoding entity))
168                 (cons 'major-mode
169                       (save-excursion
170                         (set-buffer (mime-entity-buffer entity))
171                         major-mode)))
172           ))
173
174
175 (defun mime-view-entity-title (entity)
176   (or (mime-read-field 'Content-Description entity)
177       (mime-read-field 'Subject entity)
178       (mime-entity-filename entity)
179       ""))
180
181
182 (defsubst mime-raw-point-to-entity-node-id (point &optional message-info)
183   "Return entity-node-id from POINT in mime-raw-buffer.
184 If optional argument MESSAGE-INFO is not specified,
185 `mime-message-structure' is used."
186   (mime-entity-node-id (mime-raw-find-entity-from-point point message-info)))
187
188 (defsubst mime-raw-point-to-entity-number (point &optional message-info)
189   "Return entity-number from POINT in mime-raw-buffer.
190 If optional argument MESSAGE-INFO is not specified,
191 `mime-message-structure' is used."
192   (mime-entity-number (mime-raw-find-entity-from-point point message-info)))
193
194 (defun mime-raw-flatten-message-info (&optional message-info)
195   "Return list of entity in mime-raw-buffer.
196 If optional argument MESSAGE-INFO is not specified,
197 `mime-message-structure' is used."
198   (or message-info
199       (setq message-info mime-message-structure))
200   (let ((dest (list message-info))
201         (rcl (mime-entity-children message-info)))
202     (while rcl
203       (setq dest (nconc dest (mime-raw-flatten-message-info (car rcl))))
204       (setq rcl (cdr rcl)))
205     dest))
206
207
208 ;;; @ presentation of preview
209 ;;;
210
211 ;;; @@ entity-button
212 ;;;
213
214 ;;; @@@ predicate function
215 ;;;
216
217 (defun mime-view-entity-button-visible-p (entity)
218   "Return non-nil if header of ENTITY is visible.
219 Please redefine this function if you want to change default setting."
220   (let ((media-type (mime-entity-media-type entity))
221         (media-subtype (mime-entity-media-subtype entity)))
222     (or (not (eq media-type 'application))
223         (and (not (eq media-subtype 'x-selection))
224              (or (not (eq media-subtype 'octet-stream))
225                  (let ((mother-entity (mime-entity-parent entity)))
226                    (or (not (eq (mime-entity-media-type mother-entity)
227                                 'multipart))
228                        (not (eq (mime-entity-media-subtype mother-entity)
229                                 'encrypted)))
230                    )
231                  )))))
232
233 ;;; @@@ entity button generator
234 ;;;
235
236 (defun mime-view-insert-entity-button (entity)
237   "Insert entity-button of ENTITY."
238   (let ((entity-node-id (mime-entity-node-id entity))
239         (params (mime-entity-parameters entity))
240         (subject (mime-view-entity-title entity)))
241     (mime-insert-button
242      (let ((access-type (assoc "access-type" params))
243            (num (or (cdr (assoc "x-part-number" params))
244                     (if (consp entity-node-id)
245                         (mapconcat (function
246                                     (lambda (num)
247                                       (format "%s" (1+ num))
248                                       ))
249                                    (reverse entity-node-id) ".")
250                       "0"))
251                 ))
252        (cond (access-type
253               (let ((server (assoc "server" params)))
254                 (setq access-type (cdr access-type))
255                 (if server
256                     (format "%s %s ([%s] %s)"
257                             num subject access-type (cdr server))
258                 (let ((site (cdr (assoc "site" params)))
259                       (dir (cdr (assoc "directory" params)))
260                       (url (cdr (assoc "url" params)))
261                       )
262                   (if url
263                       (format "%s %s ([%s] %s)"
264                               num subject access-type url)
265                     (format "%s %s ([%s] %s:%s)"
266                             num subject access-type site dir))
267                   )))
268             )
269            (t
270             (let ((media-type (mime-entity-media-type entity))
271                   (media-subtype (mime-entity-media-subtype entity))
272                   (charset (cdr (assoc "charset" params)))
273                   (encoding (mime-entity-encoding entity)))
274               (concat
275                num " " subject
276                (let ((rest
277                       (format " <%s/%s%s%s>"
278                               media-type media-subtype
279                               (if charset
280                                   (concat "; " charset)
281                                 "")
282                               (if encoding
283                                   (concat " (" encoding ")")
284                                 ""))))
285                  (if (>= (+ (current-column)(length rest))(window-width))
286                      "\n\t")
287                  rest)))
288             )))
289      (function mime-preview-play-current-entity))
290     ))
291
292
293 ;;; @@ entity-header
294 ;;;
295
296 (defvar mime-header-presentation-method-alist nil
297   "Alist of major mode vs. corresponding header-presentation-method functions.
298 Each element looks like (SYMBOL . FUNCTION).
299 SYMBOL must be major mode in raw-buffer or t.  t means default.
300 Interface of FUNCTION must be (ENTITY SITUATION).")
301
302 (defvar mime-view-ignored-field-list
303   '(".*Received:" ".*Path:" ".*Id:" "^References:"
304     "^Replied:" "^Errors-To:"
305     "^Lines:" "^Sender:" ".*Host:" "^Xref:"
306     "^Content-Type:" "^Precedence:"
307     "^Status:" "^X-VM-.*:")
308   "All fields that match this list will be hidden in MIME preview buffer.
309 Each elements are regexp of field-name.")
310
311 (defvar mime-view-visible-field-list '("^Dnas.*:" "^Message-Id:")
312   "All fields that match this list will be displayed in MIME preview buffer.
313 Each elements are regexp of field-name.")
314
315
316 ;;; @@ entity-body
317 ;;;
318
319 ;;; @@@ predicate function
320 ;;;
321
322 (defun mime-calist::field-match-method-as-default-rule (calist
323                                                         field-type field-value)
324   (let ((s-field (assq field-type calist)))
325     (cond ((null s-field)
326            (cons (cons field-type field-value) calist)
327            )
328           (t calist))))
329
330 (define-calist-field-match-method
331   'header #'mime-calist::field-match-method-as-default-rule)
332
333 (define-calist-field-match-method
334   'body #'mime-calist::field-match-method-as-default-rule)
335
336
337 (defvar mime-preview-condition nil
338   "Condition-tree about how to display entity.")
339
340 (ctree-set-calist-strictly
341  'mime-preview-condition '((type . application)(subtype . octet-stream)
342                            (encoding . nil)
343                            (body . visible)))
344 (ctree-set-calist-strictly
345  'mime-preview-condition '((type . application)(subtype . octet-stream)
346                            (encoding . "7bit")
347                            (body . visible)))
348 (ctree-set-calist-strictly
349  'mime-preview-condition '((type . application)(subtype . octet-stream)
350                            (encoding . "8bit")
351                            (body . visible)))
352
353 (ctree-set-calist-strictly
354  'mime-preview-condition '((type . application)(subtype . pgp)
355                            (body . visible)))
356
357 (ctree-set-calist-strictly
358  'mime-preview-condition '((type . application)(subtype . x-latex)
359                            (body . visible)))
360
361 (ctree-set-calist-strictly
362  'mime-preview-condition '((type . application)(subtype . x-selection)
363                            (body . visible)))
364
365 (ctree-set-calist-strictly
366  'mime-preview-condition '((type . application)(subtype . x-comment)
367                            (body . visible)))
368
369 (ctree-set-calist-strictly
370  'mime-preview-condition '((type . message)(subtype . delivery-status)
371                            (body . visible)))
372
373 (ctree-set-calist-strictly
374  'mime-preview-condition
375  '((body . visible)
376    (body-presentation-method . mime-display-text/plain)))
377
378 (ctree-set-calist-strictly
379  'mime-preview-condition
380  '((type . nil)
381    (body . visible)
382    (body-presentation-method . mime-display-text/plain)))
383
384 (ctree-set-calist-strictly
385  'mime-preview-condition
386  '((type . text)(subtype . enriched)
387    (body . visible)
388    (body-presentation-method . mime-display-text/enriched)))
389
390 (ctree-set-calist-strictly
391  'mime-preview-condition
392  '((type . text)(subtype . richtext)
393    (body . visible)
394    (body-presentation-method . mime-display-text/richtext)))
395
396 (ctree-set-calist-strictly
397  'mime-preview-condition
398  '((type . text)(subtype . x-vcard)
399    (body . visible)
400    (body-presentation-method . mime-display-text/x-vcard)))
401
402 (ctree-set-calist-strictly
403  'mime-preview-condition
404  '((type . text)(subtype . t)
405    (body . visible)
406    (body-presentation-method . mime-display-text/plain)))
407
408 (ctree-set-calist-strictly
409  'mime-preview-condition
410  '((type . multipart)(subtype . alternative)
411    (body . visible)
412    (body-presentation-method . mime-display-multipart/alternative)))
413
414 (ctree-set-calist-strictly
415  'mime-preview-condition '((type . message)(subtype . partial)
416                            (body-presentation-method
417                             . mime-display-message/partial-button)))
418
419 (ctree-set-calist-strictly
420  'mime-preview-condition '((type . message)(subtype . rfc822)
421                            (body-presentation-method . nil)
422                            (childrens-situation (header . visible)
423                                                 (entity-button . invisible))))
424
425 (ctree-set-calist-strictly
426  'mime-preview-condition '((type . message)(subtype . news)
427                            (body-presentation-method . nil)
428                            (childrens-situation (header . visible)
429                                                 (entity-button . invisible))))
430
431
432 ;;; @@@ entity presentation
433 ;;;
434
435 (defun mime-display-text/plain (entity situation)
436   (save-restriction
437     (narrow-to-region (point-max)(point-max))
438     (mime-insert-text-content entity)
439     (run-hooks 'mime-text-decode-hook)
440     (goto-char (point-max))
441     (if (not (eq (char-after (1- (point))) ?\n))
442         (insert "\n")
443       )
444     (mime-add-url-buttons)
445     (run-hooks 'mime-display-text/plain-hook)
446     ))
447
448 (defun mime-display-text/richtext (entity situation)
449   (save-restriction
450     (narrow-to-region (point-max)(point-max))
451     (mime-insert-text-content entity)
452     (run-hooks 'mime-text-decode-hook)
453     (let ((beg (point-min)))
454       (remove-text-properties beg (point-max) '(face nil))
455       (richtext-decode beg (point-max))
456       )))
457
458 (defun mime-display-text/enriched (entity situation)
459   (save-restriction
460     (narrow-to-region (point-max)(point-max))
461     (mime-insert-text-content entity)
462     (run-hooks 'mime-text-decode-hook)
463     (let ((beg (point-min)))
464       (remove-text-properties beg (point-max) '(face nil))
465       (enriched-decode beg (point-max))
466       )))
467
468 (defun mime-display-text/x-vcard (entity situation)
469   (save-restriction
470     (narrow-to-region (point-max)(point-max))
471     (insert (string-as-multibyte (mime-entity-content entity)))
472     (goto-char (point-min))
473     (while (re-search-forward
474             "\\(;\\(encoding=\\)?quoted-printable:\\)\\(\\(=[0-9A-F][0-9A-F]\\|=\r\n\\|[^\r\n]\\)*\\)"
475             nil t)
476       (replace-match
477        (concat
478         (buffer-substring (match-beginning 1) (match-end 1))
479         (string-as-multibyte
480          (mime-decode-string
481           (decode-coding-string
482            (buffer-substring (match-beginning 3) (match-end 3)) 'raw-text-dos)
483           "quoted-printable")))
484        t t))
485     (decode-coding-region (point-min) (point-max) 'undecided)
486     (goto-char (point-max))
487     (if (not (eq (char-after (1- (point))) ?\n))
488         (insert "\n"))
489     (mime-add-url-buttons)
490     (run-hooks 'mime-display-text/x-vcard-hook)
491     ))
492
493 (defvar mime-view-announcement-for-message/partial
494   (if (and (>= emacs-major-version 19) window-system)
495       "\
496 \[[ This is message/partial style split message. ]]
497 \[[ Please press `v' key in this buffer          ]]
498 \[[ or click here by mouse button-2.             ]]"
499     "\
500 \[[ This is message/partial style split message. ]]
501 \[[ Please press `v' key in this buffer.         ]]"
502     ))
503
504 (defun mime-display-message/partial-button (&optional entity situation)
505   (save-restriction
506     (goto-char (point-max))
507     (if (not (search-backward "\n\n" nil t))
508         (insert "\n")
509       )
510     (goto-char (point-max))
511     (narrow-to-region (point-max)(point-max))
512     (insert mime-view-announcement-for-message/partial)
513     (mime-add-button (point-min)(point-max)
514                      #'mime-preview-play-current-entity)
515     ))
516
517 (defun mime-display-multipart/mixed (entity situation)
518   (let ((children (mime-entity-children entity))
519         (default-situation
520           (cdr (assq 'childrens-situation situation))))
521     (while children
522       (mime-display-entity (car children) nil default-situation)
523       (setq children (cdr children))
524       )))
525
526 (defcustom mime-view-type-subtype-score-alist
527   '(((text . enriched) . 3)
528     ((text . richtext) . 2)
529     ((text . plain)    . 1)
530     (t . 0))
531   "Alist MEDIA-TYPE vs corresponding score.
532 MEDIA-TYPE must be (TYPE . SUBTYPE), TYPE or t.  t means default."
533   :group 'mime-view
534   :type '(repeat (cons (choice :tag "Media-Type"
535                                (cons :tag "Type/Subtype"
536                                      (symbol :tag "Primary-type")
537                                      (symbol :tag "Subtype"))
538                                (symbol :tag "Type")
539                                (const :tag "Default" t))
540                        integer)))
541
542 (defun mime-display-multipart/alternative (entity situation)
543   (let* ((children (mime-entity-children entity))
544          (default-situation
545            (cdr (assq 'childrens-situation situation)))
546          (i 0)
547          (p 0)
548          (max-score 0)
549          (situations
550           (mapcar (function
551                    (lambda (child)
552                      (let ((situation
553                             (or (ctree-match-calist
554                                  mime-preview-condition
555                                  (append (mime-entity-situation child)
556                                          default-situation))
557                                 default-situation)))
558                        (if (cdr (assq 'body-presentation-method situation))
559                            (let ((score
560                                   (cdr
561                                    (or (assoc
562                                         (cons
563                                          (cdr (assq 'type situation))
564                                          (cdr (assq 'subtype situation)))
565                                         mime-view-type-subtype-score-alist)
566                                        (assq
567                                         (cdr (assq 'type situation))
568                                         mime-view-type-subtype-score-alist)
569                                        (assq
570                                         t
571                                         mime-view-type-subtype-score-alist)
572                                        ))))
573                              (if (> score max-score)
574                                  (setq p i
575                                        max-score score)
576                                )))
577                        (setq i (1+ i))
578                        situation)
579                      ))
580                   children)))
581     (setq i 0)
582     (while children
583       (let ((child (car children))
584             (situation (car situations)))
585         (mime-display-entity child (if (= i p)
586                                        situation
587                                      (del-alist 'body-presentation-method
588                                                 (copy-alist situation))))
589         )
590       (setq children (cdr children)
591             situations (cdr situations)
592             i (1+ i))
593       )))
594
595
596 ;;; @ acting-condition
597 ;;;
598
599 (defvar mime-acting-condition nil
600   "Condition-tree about how to process entity.")
601
602 (if (file-readable-p mailcap-file)
603     (let ((entries (mailcap-parse-file)))
604       (while entries
605         (let ((entry (car entries))
606               view print shared)
607           (while entry
608             (let* ((field (car entry))
609                    (field-type (car field)))
610               (cond ((eq field-type 'view)  (setq view field))
611                     ((eq field-type 'print) (setq print field))
612                     ((memq field-type '(compose composetyped edit)))
613                     (t (setq shared (cons field shared))))
614               )
615             (setq entry (cdr entry))
616             )
617           (setq shared (nreverse shared))
618           (ctree-set-calist-with-default
619            'mime-acting-condition
620            (append shared (list '(mode . "play")(cons 'method (cdr view)))))
621           (if print
622               (ctree-set-calist-with-default
623                'mime-acting-condition
624                (append shared
625                        (list '(mode . "print")(cons 'method (cdr view))))
626                ))
627           )
628         (setq entries (cdr entries))
629         )))
630
631 (ctree-set-calist-strictly
632  'mime-acting-condition
633  '((type . application)(subtype . octet-stream)
634    (mode . "play")
635    (method . mime-detect-content)
636    ))
637
638 (ctree-set-calist-with-default
639  'mime-acting-condition
640  '((mode . "extract")
641    (method . mime-save-content)))
642
643 (ctree-set-calist-strictly
644  'mime-acting-condition
645  '((type . text)(subtype . x-rot13-47)(mode . "play")
646    (method . mime-view-caesar)
647    ))
648 (ctree-set-calist-strictly
649  'mime-acting-condition
650  '((type . text)(subtype . x-rot13-47-48)(mode . "play")
651    (method . mime-view-caesar)
652    ))
653
654 (ctree-set-calist-strictly
655  'mime-acting-condition
656  '((type . message)(subtype . rfc822)(mode . "play")
657    (method . mime-view-message/rfc822)
658    ))
659 (ctree-set-calist-strictly
660  'mime-acting-condition
661  '((type . message)(subtype . partial)(mode . "play")
662    (method . mime-store-message/partial-piece)
663    ))
664
665 (ctree-set-calist-strictly
666  'mime-acting-condition
667  '((type . message)(subtype . external-body)
668    ("access-type" . "anon-ftp")
669    (method . mime-view-message/external-anon-ftp)
670    ))
671
672 (ctree-set-calist-strictly
673  'mime-acting-condition
674  '((type . message)(subtype . external-body)
675    ("access-type" . "url")
676    (method . mime-view-message/external-url)
677    ))
678
679 (ctree-set-calist-strictly
680  'mime-acting-condition
681  '((type . application)(subtype . octet-stream)
682    (method . mime-save-content)
683    ))
684
685
686 ;;; @ quitting method
687 ;;;
688
689 (defvar mime-preview-quitting-method-alist
690   '((mime-show-message-mode
691      . mime-preview-quitting-method-for-mime-show-message-mode))
692   "Alist of major-mode vs. quitting-method of mime-view.")
693
694 (defvar mime-preview-over-to-previous-method-alist nil
695   "Alist of major-mode vs. over-to-previous-method of mime-view.")
696
697 (defvar mime-preview-over-to-next-method-alist nil
698   "Alist of major-mode vs. over-to-next-method of mime-view.")
699
700
701 ;;; @ following method
702 ;;;
703
704 (defvar mime-preview-following-method-alist nil
705   "Alist of major-mode vs. following-method of mime-view.")
706
707 (defvar mime-view-following-required-fields-list
708   '("From"))
709
710
711 ;;; @ buffer setup
712 ;;;
713
714 (defun mime-display-entity (entity &optional situation
715                                    default-situation preview-buffer)
716   (or preview-buffer
717       (setq preview-buffer (current-buffer)))
718   (let* ((raw-buffer (mime-entity-buffer entity))
719          (start (mime-entity-point-min entity))
720          e nb ne)
721     (set-buffer raw-buffer)
722     (goto-char start)
723     (or situation
724         (setq situation
725               (or (ctree-match-calist mime-preview-condition
726                                       (append (mime-entity-situation entity)
727                                               default-situation))
728                   default-situation)))
729     (let ((button-is-invisible
730            (eq (cdr (assq 'entity-button situation)) 'invisible))
731           (header-is-visible
732            (eq (cdr (assq 'header situation)) 'visible))
733           (header-presentation-method
734            (or (cdr (assq 'header-presentation-method situation))
735                (cdr (assq major-mode mime-header-presentation-method-alist))))
736           (body-presentation-method
737            (cdr (assq 'body-presentation-method situation)))
738           (children (mime-entity-children entity)))
739       (set-buffer preview-buffer)
740       (setq nb (point))
741       (narrow-to-region nb nb)
742       (or button-is-invisible
743           (if (mime-view-entity-button-visible-p entity)
744               (mime-view-insert-entity-button entity)
745             ))
746       (when header-is-visible
747         (if header-presentation-method
748             (funcall header-presentation-method entity situation)
749           (mime-insert-header entity
750                               mime-view-ignored-field-list
751                               mime-view-visible-field-list))
752         (goto-char (point-max))
753         (insert "\n")
754         (run-hooks 'mime-display-header-hook)
755         )
756       (cond (children)
757             ((functionp body-presentation-method)
758              (funcall body-presentation-method entity situation)
759              )
760             (t
761              (when button-is-invisible
762                (goto-char (point-max))
763                (mime-view-insert-entity-button entity)
764                )
765              (or header-is-visible
766                  (progn
767                    (goto-char (point-max))
768                    (insert "\n")
769                    ))
770              ))
771       (setq ne (point-max))
772       (widen)
773       (put-text-property nb ne 'mime-view-entity entity)
774       (goto-char ne)
775       (if children
776           (if (functionp body-presentation-method)
777               (funcall body-presentation-method entity situation)
778             (mime-display-multipart/mixed entity situation)
779             ))
780       )))
781
782
783 ;;; @ MIME viewer mode
784 ;;;
785
786 (defconst mime-view-menu-title "MIME-View")
787 (defconst mime-view-menu-list
788   '((up          "Move to upper entity"    mime-preview-move-to-upper)
789     (previous    "Move to previous entity" mime-preview-move-to-previous)
790     (next        "Move to next entity"     mime-preview-move-to-next)
791     (scroll-down "Scroll-down"             mime-preview-scroll-down-entity)
792     (scroll-up   "Scroll-up"               mime-preview-scroll-up-entity)
793     (play        "Play current entity"     mime-preview-play-current-entity)
794     (extract     "Extract current entity"  mime-preview-extract-current-entity)
795     (print       "Print current entity"    mime-preview-print-current-entity)
796     )
797   "Menu for MIME Viewer")
798
799 (cond ((featurep 'xemacs)
800        (defvar mime-view-xemacs-popup-menu
801          (cons mime-view-menu-title
802                (mapcar (function
803                         (lambda (item)
804                           (vector (nth 1 item)(nth 2 item) t)
805                           ))
806                        mime-view-menu-list)))
807        (defun mime-view-xemacs-popup-menu (event)
808          "Popup the menu in the MIME Viewer buffer"
809          (interactive "e")
810          (select-window (event-window event))
811          (set-buffer (event-buffer event))
812          (popup-menu 'mime-view-xemacs-popup-menu))
813        (defvar mouse-button-2 'button2)
814        )
815       (t
816        (defvar mouse-button-2 [mouse-2])
817        ))
818
819 (defun mime-view-define-keymap (&optional default)
820   (let ((mime-view-mode-map (if (keymapp default)
821                                 (copy-keymap default)
822                               (make-sparse-keymap)
823                               )))
824     (define-key mime-view-mode-map
825       "u"        (function mime-preview-move-to-upper))
826     (define-key mime-view-mode-map
827       "p"        (function mime-preview-move-to-previous))
828     (define-key mime-view-mode-map
829       "n"        (function mime-preview-move-to-next))
830     (define-key mime-view-mode-map
831       "\e\t"     (function mime-preview-move-to-previous))
832     (define-key mime-view-mode-map
833       "\t"       (function mime-preview-move-to-next))
834     (define-key mime-view-mode-map
835       " "        (function mime-preview-scroll-up-entity))
836     (define-key mime-view-mode-map
837       "\M- "     (function mime-preview-scroll-down-entity))
838     (define-key mime-view-mode-map
839       "\177"     (function mime-preview-scroll-down-entity))
840     (define-key mime-view-mode-map
841       "\C-m"     (function mime-preview-next-line-entity))
842     (define-key mime-view-mode-map
843       "\C-\M-m"  (function mime-preview-previous-line-entity))
844     (define-key mime-view-mode-map
845       "v"        (function mime-preview-play-current-entity))
846     (define-key mime-view-mode-map
847       "e"        (function mime-preview-extract-current-entity))
848     (define-key mime-view-mode-map
849       "\C-c\C-p" (function mime-preview-print-current-entity))
850     (define-key mime-view-mode-map
851       "a"        (function mime-preview-follow-current-entity))
852     (define-key mime-view-mode-map
853       "q"        (function mime-preview-quit))
854     (define-key mime-view-mode-map
855       "\C-c\C-x" (function mime-preview-kill-buffer))
856     ;; (define-key mime-view-mode-map
857     ;;   "<"        (function beginning-of-buffer))
858     ;; (define-key mime-view-mode-map
859     ;;   ">"        (function end-of-buffer))
860     (define-key mime-view-mode-map
861       "?"        (function describe-mode))
862     (define-key mime-view-mode-map
863       [tab] (function mime-preview-move-to-next))
864     (define-key mime-view-mode-map
865       [delete] (function mime-preview-scroll-down-entity))
866     (define-key mime-view-mode-map
867       [backspace] (function mime-preview-scroll-down-entity))
868     (if (functionp default)
869         (cond ((featurep 'xemacs)
870                (set-keymap-default-binding mime-view-mode-map default)
871                )
872               (t
873                (setq mime-view-mode-map
874                      (append mime-view-mode-map (list (cons t default))))
875                )))
876     (if mouse-button-2
877         (define-key mime-view-mode-map
878           mouse-button-2 (function mime-button-dispatcher))
879       )
880     (cond ((featurep 'xemacs)
881            (define-key mime-view-mode-map
882              mouse-button-3 (function mime-view-xemacs-popup-menu))
883            )
884           ((>= emacs-major-version 19)
885            (define-key mime-view-mode-map [menu-bar mime-view]
886              (cons mime-view-menu-title
887                    (make-sparse-keymap mime-view-menu-title)))
888            (mapcar (function
889                     (lambda (item)
890                       (define-key mime-view-mode-map
891                         (vector 'menu-bar 'mime-view (car item))
892                         (cons (nth 1 item)(nth 2 item))
893                         )
894                       ))
895                    (reverse mime-view-menu-list)
896                    )
897            ))
898     (use-local-map mime-view-mode-map)
899     (run-hooks 'mime-view-define-keymap-hook)
900     ))
901
902 (defsubst mime-maybe-hide-echo-buffer ()
903   "Clear mime-echo buffer and delete window for it."
904   (let ((buf (get-buffer mime-echo-buffer-name)))
905     (if buf
906         (save-excursion
907           (set-buffer buf)
908           (erase-buffer)
909           (let ((win (get-buffer-window buf)))
910             (if win
911                 (delete-window win)
912               ))
913           (bury-buffer buf)
914           ))))
915
916 (defvar mime-view-redisplay nil)
917
918 (defun mime-display-message (message &optional preview-buffer
919                                      mother default-keymap-or-function)
920   (mime-maybe-hide-echo-buffer)
921   (let ((win-conf (current-window-configuration))
922         (raw-buffer (mime-entity-buffer message)))
923     (or preview-buffer
924         (setq preview-buffer
925               (concat "*Preview-" (buffer-name raw-buffer) "*")))
926     (set-buffer raw-buffer)
927     (setq mime-preview-buffer preview-buffer)
928     (let ((inhibit-read-only t))
929       (set-buffer (get-buffer-create preview-buffer))
930       (widen)
931       (erase-buffer)
932       (setq mime-raw-buffer raw-buffer)
933       (if mother
934           (setq mime-mother-buffer mother)
935         )
936       (setq mime-preview-original-window-configuration win-conf)
937       (setq major-mode 'mime-view-mode)
938       (setq mode-name "MIME-View")
939       (mime-display-entity message nil
940                            '((entity-button . invisible)
941                              (header . visible))
942                            preview-buffer)
943       (mime-view-define-keymap default-keymap-or-function)
944       (let ((point
945              (next-single-property-change (point-min) 'mime-view-entity)))
946         (if point
947             (goto-char point)
948           (goto-char (point-min))
949           (search-forward "\n\n" nil t)
950           ))
951       (run-hooks 'mime-view-mode-hook)
952       (set-buffer-modified-p nil)
953       (setq buffer-read-only t)
954       (or (get-buffer-window preview-buffer)
955           (let ((r-win (get-buffer-window raw-buffer)))
956             (if r-win
957                 (set-window-buffer r-win preview-buffer)
958               (let ((m-win (and mother (get-buffer-window mother))))
959                 (if m-win
960                     (set-window-buffer m-win preview-buffer)
961                   (switch-to-buffer preview-buffer)
962                   )))))
963       )))
964
965 (defun mime-view-buffer (&optional raw-buffer preview-buffer mother
966                                    default-keymap-or-function
967                                    representation-type)
968   "View RAW-BUFFER in MIME-View mode.
969 Optional argument PREVIEW-BUFFER is either nil or a name of preview
970 buffer.
971 Optional argument DEFAULT-KEYMAP-OR-FUNCTION is nil, keymap or
972 function.  If it is a keymap, keymap of MIME-View mode will be added
973 to it.  If it is a function, it will be bound as default binding of
974 keymap of MIME-View mode.
975 Optional argument REPRESENTATION-TYPE is representation-type of
976 message.  It must be nil, `binary' or `cooked'.  If it is nil,
977 `cooked' is used as default."
978   (interactive)
979   (or raw-buffer
980       (setq raw-buffer (current-buffer)))
981   (or representation-type
982       (setq representation-type
983             (save-excursion
984               (set-buffer raw-buffer)
985               (cdr (or (assq major-mode mime-raw-representation-type-alist)
986                        (assq t mime-raw-representation-type-alist)))
987               )))
988   (if (eq representation-type 'binary)
989       (setq representation-type 'buffer)
990     )
991   (mime-display-message
992    (mime-open-entity representation-type raw-buffer)
993    preview-buffer mother default-keymap-or-function))
994
995 (defun mime-view-mode (&optional mother ctl encoding
996                                  raw-buffer preview-buffer
997                                  default-keymap-or-function)
998   "Major mode for viewing MIME message.
999
1000 Here is a list of the standard keys for mime-view-mode.
1001
1002 key             feature
1003 ---             -------
1004
1005 u               Move to upper content
1006 p or M-TAB      Move to previous content
1007 n or TAB        Move to next content
1008 SPC             Scroll up or move to next content
1009 M-SPC or DEL    Scroll down or move to previous content
1010 RET             Move to next line
1011 M-RET           Move to previous line
1012 v               Decode current content as `play mode'
1013 e               Decode current content as `extract mode'
1014 C-c C-p         Decode current content as `print mode'
1015 a               Followup to current content.
1016 q               Quit
1017 button-2        Move to point under the mouse cursor
1018                 and decode current content as `play mode'
1019 "
1020   (interactive)
1021   (unless mime-view-redisplay
1022     (save-excursion
1023       (if raw-buffer (set-buffer raw-buffer))
1024       (let ((type
1025              (cdr
1026               (or (assq major-mode mime-raw-representation-type-alist)
1027                   (assq t mime-raw-representation-type-alist)))))
1028         (if (eq type 'binary)
1029             (setq type 'buffer)
1030           )
1031         (setq mime-message-structure (mime-open-entity type raw-buffer))
1032         (or (mime-entity-content-type mime-message-structure)
1033             (mime-entity-set-content-type-internal
1034              mime-message-structure ctl))
1035         )
1036       (or (mime-entity-encoding mime-message-structure)
1037           (mime-entity-set-encoding-internal mime-message-structure encoding))
1038       ))
1039   (mime-display-message mime-message-structure preview-buffer
1040                         mother default-keymap-or-function)
1041   )
1042
1043
1044 ;;; @@ playing
1045 ;;;
1046
1047 (autoload 'mime-preview-play-current-entity "mime-play"
1048   "Play current entity." t)
1049
1050 (defun mime-preview-extract-current-entity (&optional ignore-examples)
1051   "Extract current entity into file (maybe).
1052 It decodes current entity to call internal or external method as
1053 \"extract\" mode.  The method is selected from variable
1054 `mime-acting-condition'."
1055   (interactive "P")
1056   (mime-preview-play-current-entity ignore-examples "extract")
1057   )
1058
1059 (defun mime-preview-print-current-entity (&optional ignore-examples)
1060   "Print current entity (maybe).
1061 It decodes current entity to call internal or external method as
1062 \"print\" mode.  The method is selected from variable
1063 `mime-acting-condition'."
1064   (interactive "P")
1065   (mime-preview-play-current-entity ignore-examples "print")
1066   )
1067
1068
1069 ;;; @@ following
1070 ;;;
1071
1072 (defun mime-preview-follow-current-entity ()
1073   "Write follow message to current entity.
1074 It calls following-method selected from variable
1075 `mime-preview-following-method-alist'."
1076   (interactive)
1077   (let (entity)
1078     (while (null (setq entity
1079                        (get-text-property (point) 'mime-view-entity)))
1080       (backward-char)
1081       )
1082     (let* ((p-beg
1083             (previous-single-property-change (point) 'mime-view-entity))
1084            p-end
1085            (entity-node-id (mime-entity-node-id entity))
1086            (len (length entity-node-id))
1087            )
1088       (cond ((null p-beg)
1089              (setq p-beg
1090                    (if (eq (next-single-property-change (point-min)
1091                                                         'mime-view-entity)
1092                            (point))
1093                        (point)
1094                      (point-min)))
1095              )
1096             ((eq (next-single-property-change p-beg 'mime-view-entity)
1097                  (point))
1098              (setq p-beg (point))
1099              ))
1100       (setq p-end (next-single-property-change p-beg 'mime-view-entity))
1101       (cond ((null p-end)
1102              (setq p-end (point-max))
1103              )
1104             ((null entity-node-id)
1105              (setq p-end (point-max))
1106              )
1107             (t
1108              (save-excursion
1109                (goto-char p-end)
1110                (catch 'tag
1111                  (let (e)
1112                    (while (setq e
1113                                 (next-single-property-change
1114                                  (point) 'mime-view-entity))
1115                      (goto-char e)
1116                      (let ((rc (mime-entity-node-id
1117                                 (get-text-property (point)
1118                                                    'mime-view-entity))))
1119                        (or (equal entity-node-id
1120                                   (nthcdr (- (length rc) len) rc))
1121                            (throw 'tag nil)
1122                            ))
1123                      (setq p-end e)
1124                      ))
1125                  (setq p-end (point-max))
1126                  ))
1127              ))
1128       (let* ((mode (mime-preview-original-major-mode 'recursive))
1129              (new-name
1130               (format "%s-%s" (buffer-name) (reverse entity-node-id)))
1131              new-buf
1132              (the-buf (current-buffer))
1133              (a-buf mime-raw-buffer)
1134              fields)
1135         (save-excursion
1136           (set-buffer (setq new-buf (get-buffer-create new-name)))
1137           (erase-buffer)
1138           (insert-buffer-substring the-buf p-beg p-end)
1139           (goto-char (point-min))
1140           (let ((entity-node-id (mime-entity-node-id entity)) ci str)
1141             (while (progn
1142                      (setq
1143                       str
1144                       (save-excursion
1145                         (set-buffer a-buf)
1146                         (setq ci
1147                               (mime-find-entity-from-node-id entity-node-id))
1148                         (save-restriction
1149                           (narrow-to-region
1150                            (mime-entity-point-min ci)
1151                            (mime-entity-point-max ci)
1152                            )
1153                           (std11-header-string-except
1154                            (concat "^"
1155                                    (apply (function regexp-or) fields)
1156                                    ":") ""))))
1157                      (if (and
1158                           (eq (mime-entity-media-type ci) 'message)
1159                           (eq (mime-entity-media-subtype ci) 'rfc822))
1160                          nil
1161                        (if str
1162                            (insert str)
1163                          )
1164                        entity-node-id))
1165               (setq fields (std11-collect-field-names)
1166                     entity-node-id (cdr entity-node-id))
1167               )
1168             )
1169           (let ((rest mime-view-following-required-fields-list))
1170             (while rest
1171               (let ((field-name (car rest)))
1172                 (or (std11-field-body field-name)
1173                     (insert
1174                      (format
1175                       (concat field-name
1176                               ": "
1177                               (save-excursion
1178                                 (set-buffer the-buf)
1179                                 (set-buffer mime-mother-buffer)
1180                                 (set-buffer mime-raw-buffer)
1181                                 (std11-field-body field-name)
1182                                 )
1183                               "\n")))
1184                     ))
1185               (setq rest (cdr rest))
1186               ))
1187           (mime-decode-header-in-buffer)
1188           )
1189         (let ((f (cdr (assq mode mime-preview-following-method-alist))))
1190           (if (functionp f)
1191               (funcall f new-buf)
1192             (message
1193              (format
1194               "Sorry, following method for %s is not implemented yet."
1195               mode))
1196             ))
1197         ))))
1198
1199
1200 ;;; @@ moving
1201 ;;;
1202
1203 (defun mime-preview-move-to-upper ()
1204   "Move to upper entity.
1205 If there is no upper entity, call function `mime-preview-quit'."
1206   (interactive)
1207   (let (cinfo)
1208     (while (null (setq cinfo
1209                        (get-text-property (point) 'mime-view-entity)))
1210       (backward-char)
1211       )
1212     (let ((r (mime-entity-parent cinfo))
1213           point)
1214       (catch 'tag
1215         (while (setq point (previous-single-property-change
1216                             (point) 'mime-view-entity))
1217           (goto-char point)
1218           (if (eq r (get-text-property (point) 'mime-view-entity))
1219               (throw 'tag t)
1220             )
1221           )
1222         (mime-preview-quit)
1223         ))))
1224
1225 (defun mime-preview-move-to-previous ()
1226   "Move to previous entity.
1227 If there is no previous entity, it calls function registered in
1228 variable `mime-preview-over-to-previous-method-alist'."
1229   (interactive)
1230   (while (null (get-text-property (point) 'mime-view-entity))
1231     (backward-char)
1232     )
1233   (let ((point (previous-single-property-change (point) 'mime-view-entity)))
1234     (if point
1235         (if (get-text-property (1- point) 'mime-view-entity)
1236             (goto-char point)
1237           (goto-char (1- point))
1238           (mime-preview-move-to-previous)
1239           )
1240       (let ((f (assq (mime-preview-original-major-mode)
1241                      mime-preview-over-to-previous-method-alist)))
1242         (if f
1243             (funcall (cdr f))
1244           ))
1245       )))
1246
1247 (defun mime-preview-move-to-next ()
1248   "Move to next entity.
1249 If there is no previous entity, it calls function registered in
1250 variable `mime-preview-over-to-next-method-alist'."
1251   (interactive)
1252   (while (and (not (eobp))
1253               (null (get-text-property (point) 'mime-view-entity)))
1254     (forward-char)
1255     )
1256   (let ((point (next-single-property-change (point) 'mime-view-entity)))
1257     (if point
1258         (progn
1259           (goto-char point)
1260           (if (null (get-text-property point 'mime-view-entity))
1261               (mime-preview-move-to-next)
1262             ))
1263       (let ((f (assq (mime-preview-original-major-mode)
1264                      mime-preview-over-to-next-method-alist)))
1265         (if f
1266             (funcall (cdr f))
1267           ))
1268       )))
1269
1270 (defun mime-preview-scroll-up-entity (&optional h)
1271   "Scroll up current entity.
1272 If reached to (point-max), it calls function registered in variable
1273 `mime-preview-over-to-next-method-alist'."
1274   (interactive)
1275   (or h
1276       (setq h (1- (window-height)))
1277       )
1278   (if (= (point) (point-max))
1279       (let ((f (assq (mime-preview-original-major-mode)
1280                      mime-preview-over-to-next-method-alist)))
1281         (if f
1282             (funcall (cdr f))
1283           ))
1284     (let ((point
1285            (or (next-single-property-change (point) 'mime-view-entity)
1286                (point-max))))
1287       (forward-line h)
1288       (if (> (point) point)
1289           (goto-char point)
1290         )
1291       )))
1292
1293 (defun mime-preview-scroll-down-entity (&optional h)
1294   "Scroll down current entity.
1295 If reached to (point-min), it calls function registered in variable
1296 `mime-preview-over-to-previous-method-alist'."
1297   (interactive)
1298   (or h
1299       (setq h (1- (window-height)))
1300       )
1301   (if (= (point) (point-min))
1302       (let ((f (assq (mime-preview-original-major-mode)
1303                      mime-preview-over-to-previous-method-alist)))
1304         (if f
1305             (funcall (cdr f))
1306           ))
1307     (let ((point
1308            (or (previous-single-property-change (point) 'mime-view-entity)
1309                (point-min))))
1310       (forward-line (- h))
1311       (if (< (point) point)
1312           (goto-char point)
1313         ))))
1314
1315 (defun mime-preview-next-line-entity ()
1316   (interactive)
1317   (mime-preview-scroll-up-entity 1)
1318   )
1319
1320 (defun mime-preview-previous-line-entity ()
1321   (interactive)
1322   (mime-preview-scroll-down-entity 1)
1323   )
1324
1325
1326 ;;; @@ quitting
1327 ;;;
1328
1329 (defun mime-preview-quit ()
1330   "Quit from MIME-preview buffer.
1331 It calls function registered in variable
1332 `mime-preview-quitting-method-alist'."
1333   (interactive)
1334   (let ((r (assq (mime-preview-original-major-mode)
1335                  mime-preview-quitting-method-alist)))
1336     (if r
1337         (funcall (cdr r))
1338       )))
1339
1340 (defun mime-preview-kill-buffer ()
1341   (interactive)
1342   (kill-buffer (current-buffer))
1343   )
1344
1345
1346 ;;; @ end
1347 ;;;
1348
1349 (provide 'mime-view)
1350
1351 (run-hooks 'mime-view-load-hook)
1352
1353 ;;; mime-view.el ends here