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