update.
[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   (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 (defcustom mime-preview-move-scroll nil
66   "*Decides whether to scroll when moving to next entity.
67 When t, scroll the buffer.  Non-nil but not t means scroll when
68 the next entity is within `next-screen-context-lines' from top or
69 buttom.  Nil means don't scroll at all."
70   :group 'mime-view
71   :type '(choice (const :tag "Off" nil)
72                  (const :tag "On" t)
73                  (sexp :tag "Situation" 1)))
74
75 (defcustom mime-preview-scroll-full-screen nil
76   "*When non-nil, always scroll full screen.
77 If nil, point will be moved to the next entity if exists."
78   :group 'mime-view
79   :type '(choice (const :tag "On" t)
80                  (const :tag "Off" nil)))
81
82 (defcustom mime-view-force-inline-types '(text multipart)
83   "*List of MIME types that \"attachment\" should be ignored.
84 The element can be type or type/subtype. When t, inline everything
85 if possible."
86   :group 'mime-view
87   :type '(choice (const :tag "Nothing" nil)
88                  (const :tag "All" t)
89                  (list (repeat symbol))))
90
91 (defcustom mime-view-button-place-alist
92   '((message . around)
93     (application . before)
94     (multipart/alternative . around))
95   "*Alist of MIME type or type/subtype vs. button place.
96 When around, button will be inserted before and after that part.
97 When after or before, button will be inserted that place.
98 If not specified, that type will not have button."
99   :group 'mime-view
100   :type '(choice (const :tag "Nothing" nil)
101                  (list (repeat symbol))))
102
103 ;; Rename this.
104 (defcustom mime-view-type-subtype-score-alist
105   '(((text . enriched) . 3)
106     ((text . richtext) . 2)
107     ((text . plain)    . 1)
108     (t . 0))
109   "Alist MEDIA-TYPE vs corresponding score.
110 MEDIA-TYPE must be (TYPE . SUBTYPE), TYPE or t.  t means default."
111   :group 'mime-view
112   :type '(repeat (cons (choice :tag "Media-Type"
113                                (cons :tag "Type/Subtype"
114                                      (symbol :tag "Primary-type")
115                                      (symbol :tag "Subtype"))
116                                (symbol :tag "Type")
117                                (const :tag "Default" t))
118                        integer)))
119
120 (defvar mime-view-automatic-conversion
121   (cond ((featurep 'xemacs)
122          'automatic-conversion)
123         ((boundp 'MULE)
124          '*autoconv*)
125         (t
126          'undecided)))
127
128 ;;; @ in raw-buffer (representation space)
129 ;;;
130
131 (defvar mime-preview-buffer nil
132   "MIME-preview buffer corresponding with the (raw) buffer.")
133 (make-variable-buffer-local 'mime-preview-buffer)
134
135
136 (defvar mime-raw-representation-type-alist
137   '((mime-show-message-mode     . binary)
138     (mime-temp-message-mode     . binary)
139     (t                          . cooked))
140   "Alist of `major-mode' vs. representation-type of mime-raw-buffer.
141 Each element looks like (SYMBOL . REPRESENTATION-TYPE).  SYMBOL is
142 `major-mode' or t.  t means default.  REPRESENTATION-TYPE must be
143 `binary' or `cooked'.")
144
145
146 ;; (defun mime-raw-find-entity-from-point (point &optional message-info)
147 ;;   "Return entity from POINT in mime-raw-buffer.
148 ;; If optional argument MESSAGE-INFO is not specified,
149 ;; `mime-message-structure' is used."
150 ;;   (or message-info
151 ;;       (setq message-info mime-message-structure))
152 ;;   (if (and (<= (mime-entity-point-min message-info) point)
153 ;;            (<= point (mime-entity-point-max message-info)))
154 ;;       (let ((children (mime-entity-children message-info)))
155 ;;         (catch 'tag
156 ;;           (while children
157 ;;             (let ((ret
158 ;;                    (mime-raw-find-entity-from-point point (car children))))
159 ;;               (if ret
160 ;;                   (throw 'tag ret)
161 ;;                 ))
162 ;;             (setq children (cdr children)))
163 ;;           message-info))))
164 ;; (make-obsolete 'mime-raw-find-entity-from-point "don't use it.")
165
166
167 ;;; @ in preview-buffer (presentation space)
168 ;;;
169
170 (defvar mime-mother-buffer nil
171   "Mother buffer corresponding with the (MIME-preview) buffer.
172 If current MIME-preview buffer is generated by other buffer, such as
173 message/partial, it is called `mother-buffer'.")
174 (make-variable-buffer-local 'mime-mother-buffer)
175
176 ;; (defvar mime-raw-buffer nil
177 ;;   "Raw buffer corresponding with the (MIME-preview) buffer.")
178 ;; (make-variable-buffer-local 'mime-raw-buffer)
179
180 (defvar mime-preview-original-window-configuration nil
181   "Window-configuration before `mime-view-mode' is called.")
182 (make-variable-buffer-local 'mime-preview-original-window-configuration)
183
184 (defun mime-preview-original-major-mode (&optional recursive point)
185   "Return major-mode of original buffer.
186 If optional argument RECURSIVE is non-nil and current buffer has
187 mime-mother-buffer, it returns original major-mode of the
188 mother-buffer."
189   (if (and recursive mime-mother-buffer)
190       (save-excursion
191         (set-buffer mime-mother-buffer)
192         (mime-preview-original-major-mode recursive))
193     (cdr (assq 'major-mode
194                (get-text-property (or point
195                                       (if (> (point) (buffer-size))
196                                           (max (1- (point-max)) (point-min))
197                                         (point)))
198                                   'mime-view-situation)))))
199
200
201 ;;; @ entity information
202 ;;;
203
204 (defun mime-entity-situation (entity &optional situation)
205   "Return situation of ENTITY."
206   (let (rest param name)
207     ;; Content-Type
208     (unless (assq 'type situation)
209       (setq rest (or (mime-entity-content-type entity)
210                      (make-mime-content-type 'text 'plain))
211             situation (cons (car rest) situation)
212             rest (cdr rest)))
213     (unless (assq 'subtype situation)
214       (or rest
215           (setq rest (or (cdr (mime-entity-content-type entity))
216                          '((subtype . plain)))))
217       (setq situation (cons (car rest) situation)
218             rest (cdr rest)))
219     (while rest
220       (setq param (car rest))
221       (or (assoc (car param) situation)
222           (setq situation (cons param situation)))
223       (setq rest (cdr rest)))
224     
225     ;; Content-Disposition
226     (setq rest nil)
227     (unless (assq 'disposition-type situation)
228       (setq rest (mime-entity-content-disposition entity))
229       (if rest
230           (setq situation (cons (cons 'disposition-type
231                                       (mime-content-disposition-type rest))
232                                 situation)
233                 rest (mime-content-disposition-parameters rest))))
234     (while rest
235       (setq param (car rest)
236             name (car param))
237       (if (cond ((string= name "filename")
238                  (if (assq 'filename situation)
239                      nil
240                    (setq name 'filename)))
241                 ((string= name "creation-date")
242                  (if (assq 'creation-date situation)
243                      nil
244                    (setq name 'creation-date)))
245                 ((string= name "modification-date")
246                  (if (assq 'modification-date situation)
247                      nil
248                    (setq name 'modification-date)))
249                 ((string= name "read-date")
250                  (if (assq 'read-date situation)
251                      nil
252                    (setq name 'read-date)))
253                 ((string= name "size")
254                  (if (assq 'size situation)
255                      nil
256                    (setq name 'size)))
257                 (t (setq name (cons 'disposition name))
258                    (if (assoc name situation)
259                        nil
260                      name)))
261           (setq situation
262                 (cons (cons name (cdr param))
263                       situation)))
264       (setq rest (cdr rest)))
265     
266     ;; Content-Transfer-Encoding
267     (or (assq 'encoding situation)
268         (setq situation
269               (cons (cons 'encoding (or (mime-entity-encoding entity)
270                                         "7bit"))
271                     situation)))
272     
273     situation))
274
275 (defun mime-view-entity-title (entity)
276   (or (mime-entity-read-field entity 'Content-Description)
277       (mime-entity-read-field entity 'Subject)
278       (mime-entity-filename entity)
279       ""))
280
281
282 ;; (defsubst mime-raw-point-to-entity-node-id (point &optional message-info)
283 ;;   "Return entity-node-id from POINT in mime-raw-buffer.
284 ;; If optional argument MESSAGE-INFO is not specified,
285 ;; `mime-message-structure' is used."
286 ;;   (mime-entity-node-id (mime-raw-find-entity-from-point point message-info)))
287
288 ;; (make-obsolete 'mime-raw-point-to-entity-node-id "don't use it.")
289
290 ;; (defsubst mime-raw-point-to-entity-number (point &optional message-info)
291 ;;   "Return entity-number from POINT in mime-raw-buffer.
292 ;; If optional argument MESSAGE-INFO is not specified,
293 ;; `mime-message-structure' is used."
294 ;;   (mime-entity-number (mime-raw-find-entity-from-point point message-info)))
295
296 ;; (make-obsolete 'mime-raw-point-to-entity-number "don't use it.")
297
298 ;; (defun mime-raw-flatten-message-info (&optional message-info)
299 ;;   "Return list of entity in mime-raw-buffer.
300 ;; If optional argument MESSAGE-INFO is not specified,
301 ;; `mime-message-structure' is used."
302 ;;   (or message-info
303 ;;       (setq message-info mime-message-structure))
304 ;;   (let ((dest (list message-info))
305 ;;         (rcl (mime-entity-children message-info)))
306 ;;     (while rcl
307 ;;       (setq dest (nconc dest (mime-raw-flatten-message-info (car rcl))))
308 ;;       (setq rcl (cdr rcl)))
309 ;;     dest))
310
311 (defmacro mime-view-header-is-visible (situation)
312   `(eq (cdr (or (assq '*header ,situation)
313                 (assq 'header ,situation)))
314        'visible))
315
316 (defmacro mime-view-body-is-visible (situation)
317   `(eq (cdr (or (assq '*body ,situation)
318                 (assq 'body ,situation)))
319        'visible))
320
321 (defmacro mime-view-children-is-invisible (situation)
322   `(eq (cdr (or (assq '*children ,situation)
323                 (assq 'children ,situation)))
324        'invisible))
325
326 (defmacro mime-view-button-is-visible (situation)
327   ;; Kludge.
328   `(or (eq (or (cdr (assq '*entity-button ,situation))
329                (cdr (assq 'entity-button ,situation)))
330            'visible)
331        (and (not (eq (or (cdr (assq '*entity-button ,situation))
332                          (cdr (assq 'entity-button ,situation)))
333                      'invisible))
334             (mime-view-entity-button-visible-p entity))))
335
336 ;;; @ presentation of preview
337 ;;;
338
339 ;;; @@ entity-button
340 ;;;
341
342 ;;; @@@ predicate function
343 ;;;
344
345 ;; #### fix flim
346 (defun mime-view-entity-type/subtype (entity)
347   (if (not (mime-entity-media-type entity))
348       'text/plain
349     (intern (format "%s/%s"
350                     (mime-entity-media-type entity)
351                     (mime-entity-media-subtype entity)))))
352
353 (defun mime-view-entity-button-visible-p (entity)
354   "Return non-nil if header of ENTITY is visible.
355 You can customize the visibility by changing `mime-view-button-place-alist'."
356   (or
357    ;; Check current entity
358    ;; type/subtype
359    (memq (cdr (assq (mime-view-entity-type/subtype entity)
360                     mime-view-button-place-alist))
361          '(around before))
362    ;; type
363    (memq (cdr (assq (mime-entity-media-type entity)
364                     mime-view-button-place-alist))
365          '(around before))
366    (and (mime-entity-parent entity)
367         (let ((prev-entity
368                (cadr (memq entity
369                            (reverse (mime-entity-children
370                                      (mime-entity-parent entity)))))))
371           ;; When previous entity exists
372           (and prev-entity
373                (or
374                 ;; Check previous entity
375                 ;; type/subtype
376                 (memq (cdr
377                        (assq
378                         (mime-view-entity-type/subtype prev-entity)
379                         mime-view-button-place-alist))
380                       '(around after))
381                 ;; type
382                 (memq (cdr
383                        (assq
384                         (mime-entity-media-type prev-entity)
385                         mime-view-button-place-alist))
386                       '(around after))))))
387    ;; default for everything.
388    (memq (cdr (assq t
389                     mime-view-button-place-alist))
390          '(around before))))
391
392 ;;; @@@ entity button generator
393 ;;;
394
395 (defun mime-view-insert-entity-button (entity &optional body-is-invisible)
396   "Insert entity-button of ENTITY."
397   (let ((entity-node-id (mime-entity-node-id entity))
398         (params (mime-entity-parameters entity))
399         (subject (mime-view-entity-title entity)))
400     (mime-insert-button
401      (concat
402       (let ((access-type (assoc "access-type" params))
403             (num (or (cdr (assoc "x-part-number" params))
404                      (if (consp entity-node-id)
405                          (mapconcat (function
406                                      (lambda (num)
407                                        (format "%s" (1+ num))))
408                                     (reverse entity-node-id) ".")
409                        "0"))))
410         (cond (access-type
411                (let ((server (assoc "server" params)))
412                  (setq access-type (cdr access-type))
413                  (if server
414                      (format "%s %s ([%s] %s)"
415                              num subject access-type (cdr server))
416                    (let ((site (cdr (assoc "site" params)))
417                          (dir (cdr (assoc "directory" params)))
418                          (url (cdr (assoc "url" params))))
419                      (if url
420                          (format "%s %s ([%s] %s)"
421                                  num subject access-type url)
422                        (format "%s %s ([%s] %s:%s)"
423                                num subject access-type site dir))))))
424               (t
425                (let ((media-type (mime-entity-media-type entity))
426                      (media-subtype (mime-entity-media-subtype entity))
427                      (charset (cdr (assoc "charset" params)))
428                      (encoding (mime-entity-encoding entity)))
429                  (concat
430                   num " " subject
431                   (let ((rest
432                          (format " <%s/%s%s%s>"
433                                  media-type media-subtype
434                                  (if charset
435                                      (concat "; " charset)
436                                    "")
437                                  (if encoding
438                                      (concat " (" encoding ")")
439                                    ""))))
440                     (if (>= (+ (current-column)(length rest))(window-width))
441                         "\n\t")
442                     rest))))))
443       (if body-is-invisible
444           " ..."
445         ""))
446      (function mime-preview-play-current-entity))))
447
448
449 ;;; @@ entity-header
450 ;;;
451
452 (defvar mime-header-presentation-method-alist nil
453   "Alist of major mode vs. corresponding header-presentation-method functions.
454 Each element looks like (SYMBOL . FUNCTION).
455 SYMBOL must be major mode in raw-buffer or t.  t means default.
456 Interface of FUNCTION must be (ENTITY SITUATION).")
457
458 (defvar mime-view-ignored-field-list
459   '(".*Received:" ".*Path:" ".*Id:" "^References:"
460     "^Replied:" "^Errors-To:"
461     "^Lines:" "^Sender:" ".*Host:" "^Xref:"
462     "^Content-Type:" "^Precedence:"
463     "^Status:" "^X-VM-.*:")
464   "All fields that match this list will be hidden in MIME preview buffer.
465 Each elements are regexp of field-name.")
466
467 (defvar mime-view-visible-field-list '("^Dnas.*:" "^Message-Id:")
468   "All fields that match this list will be displayed in MIME preview buffer.
469 Each elements are regexp of field-name.")
470
471
472 ;;; @@ entity-body
473 ;;;
474
475 ;;; @@@ predicate function
476 ;;;
477
478 (in-calist-package 'mime-view)
479
480 (defun mime-calist::field-match-method-as-default-rule (calist
481                                                         field-type field-value)
482   (let ((s-field (assq field-type calist)))
483     (cond ((null s-field)
484            (cons (cons field-type field-value) calist))
485           (t calist))))
486
487 (define-calist-field-match-method
488   'header #'mime-calist::field-match-method-as-default-rule)
489
490 (define-calist-field-match-method
491   'body #'mime-calist::field-match-method-as-default-rule)
492
493
494 (defvar mime-preview-condition nil
495   "Condition-tree about how to display entity.")
496
497 ;;(ctree-set-calist-strictly
498 ;; 'mime-preview-condition '((type . application)(subtype . octet-stream)
499 ;;                         (encoding . nil)
500 ;;                         (body . visible)))
501
502 (ctree-set-calist-strictly
503  'mime-preview-condition '((type . application)(subtype . t)
504                            (encoding . "7bit")
505                            (body . visible)))
506 (ctree-set-calist-strictly
507  'mime-preview-condition '((type . application)(subtype . t)
508                            (encoding . "8bit")
509                            (body . visible)))
510
511 (ctree-set-calist-strictly
512  'mime-preview-condition '((type . application)(subtype . pgp)
513                            (body . visible)))
514
515 (ctree-set-calist-strictly
516  'mime-preview-condition '((type . application)(subtype . x-latex)
517                            (body . visible)))
518
519 (ctree-set-calist-strictly
520  'mime-preview-condition '((type . application)(subtype . x-selection)
521                            (body . visible)))
522
523 (ctree-set-calist-strictly
524  'mime-preview-condition '((type . application)(subtype . x-comment)
525                            (body . visible)))
526
527 (ctree-set-calist-strictly
528  'mime-preview-condition '((type . message)(subtype . delivery-status)
529                            (body . visible)))
530
531 (ctree-set-calist-strictly
532  'mime-preview-condition
533  '((body . visible)
534    (body-presentation-method . mime-display-text/plain)))
535
536 (ctree-set-calist-strictly
537  'mime-preview-condition
538  '((type . nil)
539    (body . visible)
540    (body-presentation-method . mime-display-text/plain)))
541
542 (ctree-set-calist-strictly
543  'mime-preview-condition
544  '((type . text)(subtype . enriched)
545    (body . visible)
546    (body-presentation-method . mime-display-text/enriched)))
547
548 (ctree-set-calist-strictly
549  'mime-preview-condition
550  '((type . text)(subtype . richtext)
551    (body . visible)
552    (body-presentation-method . mime-display-text/richtext)))
553
554 (ctree-set-calist-strictly
555  'mime-preview-condition
556  '((type . application)(subtype . x-postpet)
557    (body . visible)
558    (body-presentation-method . mime-display-application/x-postpet)))
559
560 (ctree-set-calist-strictly
561  'mime-preview-condition '((type . application)(subtype . t)
562                            (encoding . t)
563                            (body . invisible)
564                            (body-presentation-method . mime-display-detect-application/octet-stream)))
565
566 (ctree-set-calist-strictly
567  'mime-preview-condition
568  '((type . text)(subtype . t)
569    (body . visible)
570    (body-presentation-method . mime-display-text/plain)))
571
572 (ctree-set-calist-strictly
573  'mime-preview-condition
574  '((type . text)(subtype . x-rot13-47-48)
575    (body . visible)
576    (body-presentation-method . mime-display-text/x-rot13-47-48)))
577
578 (ctree-set-calist-strictly
579  'mime-preview-condition
580  '((type . multipart)(subtype . alternative)
581    (body . visible)
582    (body-presentation-method . mime-display-multipart/alternative)))
583
584 (ctree-set-calist-strictly
585  'mime-preview-condition
586  '((type . multipart)(subtype . t)
587    (body . visible)
588    (body-presentation-method . mime-display-multipart/mixed)))
589
590 (ctree-set-calist-strictly
591  'mime-preview-condition
592  '((type . message)(subtype . partial)
593    (body . visible)
594    (body-presentation-method . mime-display-message/partial-button)))
595
596 (ctree-set-calist-strictly
597  'mime-preview-condition
598  '((type . message)(subtype . rfc822)
599    (body . visible)
600    (body-presentation-method . mime-display-multipart/mixed)
601    (childrens-situation (header . visible)
602                         (entity-button . invisible))))
603
604 (ctree-set-calist-strictly
605  'mime-preview-condition
606  '((type . message)(subtype . news)
607    (body . visible)
608    (body-presentation-method . mime-display-multipart/mixed)
609    (childrens-situation (header . visible)
610                         (entity-button . invisible))))
611
612 ;; message/external-body has only one child.
613 (ctree-set-calist-strictly
614  'mime-preview-condition
615  '((type . message)(subtype . external-body)
616    (body . visible)
617    (body-presentation-method . nil)
618    (childrens-situation (header . invisible)
619                         (body . invisible)
620                         (entity-button . visible))))
621
622
623 ;;; @@@ entity presentation
624 ;;;
625
626 (defun mime-display-text/plain (entity situation)
627   (save-restriction
628     (narrow-to-region (point-max)(point-max))
629     (mime-insert-text-content entity)
630     (run-hooks 'mime-text-decode-hook)
631     (goto-char (point-max))
632     (if (not (eq (char-after (1- (point))) ?\n))
633         (insert "\n"))
634     (mime-add-url-buttons)
635     (run-hooks 'mime-display-text/plain-hook)))
636
637 (defun mime-display-text (entity situation)
638   (save-restriction
639     (narrow-to-region (point-max) (point-max))
640     (insert
641      (decode-coding-string
642       (mime-decode-string
643        (if (fboundp 'mime-entity-body)
644            ;; FLIM 1.14
645            (mime-entity-body entity)
646          ;; #### This is wrong, but...
647          (mime-entity-content entity))
648        (or (cdr (assq 'encoding situation))
649            (if (fboundp 'mime-entity-body)
650                (mime-entity-encoding entity)
651              "7bit")))
652       (or (cdr (assq 'coding situation))
653           'binary)))))
654
655 (defun mime-display-text/richtext (entity situation)
656   (save-restriction
657     (narrow-to-region (point-max)(point-max))
658     (mime-insert-text-content entity)
659     (run-hooks 'mime-text-decode-hook)
660     (let ((beg (point-min)))
661       (remove-text-properties beg (point-max) '(face nil))
662       (richtext-decode beg (point-max)))))
663
664 (defun mime-display-text/enriched (entity situation)
665   (save-restriction
666     (narrow-to-region (point-max)(point-max))
667     (mime-insert-text-content entity)
668     (run-hooks 'mime-text-decode-hook)
669     (let ((beg (point-min)))
670       (remove-text-properties beg (point-max) '(face nil))
671       (enriched-decode beg (point-max)))))
672
673 (defun mime-display-text/x-rot13-47-48 (entity situation)
674   (save-restriction
675     (narrow-to-region (point-max)(point-max))
676     (mime-insert-text-content entity)
677     (goto-char (point-max))
678     (if (not (eq (char-after (1- (point))) ?\n))
679         (insert "\n"))
680     (mule-caesar-region (point-min) (point-max))
681     (mime-add-url-buttons)))
682
683 (put 'unpack 'lisp-indent-function 1)
684 (defmacro unpack (string &rest body)
685   `(let* ((*unpack*string* (string-as-unibyte ,string))
686           (*unpack*index* 0))
687      ,@body))
688
689 (defun unpack-skip (len)
690   (setq *unpack*index* (+ len *unpack*index*)))
691
692 (defun unpack-fixed (len)
693   (prog1
694       (substring *unpack*string* *unpack*index* (+ *unpack*index* len))
695     (unpack-skip len)))
696
697 (defun unpack-byte ()
698   (char-int (aref (unpack-fixed 1) 0)))
699
700 (defun unpack-short ()
701   (let* ((b0 (unpack-byte))
702          (b1 (unpack-byte)))
703     (+ (* 256 b0) b1)))
704
705 (defun unpack-long ()
706   (let* ((s0 (unpack-short))
707          (s1 (unpack-short)))
708     (+ (* 65536 s0) s1)))
709
710 (defun unpack-string ()
711   (let ((len (unpack-byte)))
712     (unpack-fixed len)))
713
714 (defun unpack-string-sjis ()
715   (decode-mime-charset-string (unpack-string) 'shift_jis))
716
717 (defun postpet-decode (string)
718   (condition-case nil
719       (unpack string
720         (let (res)
721           (unpack-skip 4)
722           (set-alist 'res 'carryingcount (unpack-long))
723           (unpack-skip 8)
724           (set-alist 'res 'sentyear (unpack-short))
725           (set-alist 'res 'sentmonth (unpack-short))
726           (set-alist 'res 'sentday (unpack-short))
727           (unpack-skip 8)
728           (set-alist 'res 'petname (unpack-string-sjis))
729           (set-alist 'res 'owner (unpack-string-sjis))
730           (set-alist 'res 'pettype (unpack-fixed 4))
731           (set-alist 'res 'health (unpack-short))
732           (unpack-skip 2)
733           (set-alist 'res 'sex (unpack-long))
734           (unpack-skip 1)
735           (set-alist 'res 'brain (unpack-byte))
736           (unpack-skip 39)
737           (set-alist 'res 'happiness (unpack-byte))
738           (unpack-skip 14)
739           (set-alist 'res 'petbirthyear (unpack-short))
740           (set-alist 'res 'petbirthmonth (unpack-short))
741           (set-alist 'res 'petbirthday (unpack-short))
742           (unpack-skip 8)
743           (set-alist 'res 'from (unpack-string))
744           (unpack-skip 5)
745           (unpack-skip 160)
746           (unpack-skip 4)
747           (unpack-skip 8)
748           (unpack-skip 8)
749           (unpack-skip 26)
750           (set-alist 'res 'treasure (unpack-short))
751           (set-alist 'res 'money (unpack-long))
752           res))
753     (error nil)))
754
755 (defun mime-display-application/x-postpet (entity situation)
756   (save-restriction
757     (narrow-to-region (point-max)(point-max))
758     (let ((pet (postpet-decode (mime-entity-content entity))))
759       (if pet
760           (insert "Petname: " (cdr (assq 'petname pet)) "\n"
761                   "Owner: " (cdr (assq 'owner pet)) "\n"
762                   "Pettype: " (cdr (assq 'pettype pet)) "\n"
763                   "From: " (cdr (assq 'from pet)) "\n"
764                   "CarryingCount: " (int-to-string (cdr (assq 'carryingcount pet))) "\n"
765                   "SentYear: " (int-to-string (cdr (assq 'sentyear pet))) "\n"
766                   "SentMonth: " (int-to-string (cdr (assq 'sentmonth pet))) "\n"
767                   "SentDay: " (int-to-string (cdr (assq 'sentday pet))) "\n"
768                   "PetbirthYear: " (int-to-string (cdr (assq 'petbirthyear pet))) "\n"
769                   "PetbirthMonth: " (int-to-string (cdr (assq 'petbirthmonth pet))) "\n"
770                   "PetbirthDay: " (int-to-string (cdr (assq 'petbirthday pet))) "\n"
771                   "Health: " (int-to-string (cdr (assq 'health pet))) "\n"
772                   "Sex: " (int-to-string (cdr (assq 'sex pet))) "\n"
773                   "Brain: " (int-to-string (cdr (assq 'brain pet))) "\n"
774                   "Happiness: " (int-to-string (cdr (assq 'happiness pet))) "\n"
775                   "Treasure: " (int-to-string (cdr (assq 'treasure pet))) "\n"
776                   "Money: " (int-to-string (cdr (assq 'money pet))) "\n")
777         (insert "Invalid format\n"))
778       (run-hooks 'mime-display-application/x-postpet-hook))))
779
780
781 (defvar mime-view-announcement-for-message/partial
782   (if (and (>= emacs-major-version 19) window-system)
783       "\
784 This is message/partial style split message.
785 Please press `v' key in this buffer or click here by mouse button-2."
786     "\
787 This is message/partial style split message.
788 Please press `v' key in this buffer."))
789
790 (defun mime-display-message/partial-button (&optional entity situation)
791   (save-restriction
792     (goto-char (point-max))
793     (if (not (search-backward "\n\n" nil t))
794         (insert "\n"))
795     (goto-char (point-max))
796     ;;(narrow-to-region (point-max)(point-max))
797     ;;(insert mime-view-announcement-for-message/partial)
798     ;; (mime-add-button (point-min)(point-max)
799     ;;                  #'mime-preview-play-current-entity)
800     (mime-insert-button mime-view-announcement-for-message/partial
801                         #'mime-preview-play-current-entity)))
802
803 (defun mime-display-multipart/mixed (entity situation)
804   (let ((children (mime-entity-children entity))
805         (original-major-mode-cell (assq 'major-mode situation))
806         (default-situation
807           (cdr (assq 'childrens-situation situation))))
808     (if original-major-mode-cell
809         (setq default-situation
810               (cons original-major-mode-cell default-situation)))
811     (while children
812       (mime-display-entity (car children) nil default-situation)
813       (setq children (cdr children)))))
814
815 (defun mime-display-multipart/alternative (entity situation)
816   (let* ((children (mime-entity-children entity))
817          (original-major-mode-cell (assq 'major-mode situation))
818          (default-situation
819            (cdr (assq 'childrens-situation situation)))
820          (i 0)
821          (p 0)
822          (max-score 0)
823          situations)
824     (if original-major-mode-cell
825         (setq default-situation
826               (cons original-major-mode-cell default-situation)))
827     (setq situations
828           (mapcar (function
829                    (lambda (child)
830                      (let ((situation
831                             (or (ctree-match-calist
832                                  mime-preview-condition
833                                  (append (mime-entity-situation child)
834                                          default-situation))
835                                 default-situation)))
836                        (if (cdr (assq 'body-presentation-method situation))
837                            (let ((score
838                                   (cdr
839                                    (or (assoc
840                                         (cons
841                                          (cdr (assq 'type situation))
842                                          (cdr (assq 'subtype situation)))
843                                         mime-view-type-subtype-score-alist)
844                                        (assq
845                                         (cdr (assq 'type situation))
846                                         mime-view-type-subtype-score-alist)
847                                        (assq
848                                         t
849                                         mime-view-type-subtype-score-alist)))))
850                              (if (> score max-score)
851                                  (setq p i
852                                        max-score score))))
853                        (setq i (1+ i))
854                        situation)))
855                   children))
856     (setq i 0)
857     (while children
858       (let ((child (car children))
859             (situation (car situations)))
860         (mime-display-entity child (if (= i p)
861                                        situation
862                                      (del-alist 'body-presentation-method
863                                                 (copy-alist situation)))))
864       (setq children (cdr children)
865             situations (cdr situations)
866             i (1+ i)))))
867
868 (defun mime-display-detect-application/octet-stream (entity situation)
869   "Detect unknown ENTITY and display it inline.
870 This can only handle gzipped contents."
871   (or (and (mime-entity-filename entity)
872            (string-match "\\.gz$" (mime-entity-filename entity))
873            (mime-display-gzipped entity situation))
874       (mime-display-text/plain entity situation)))
875
876 (defun mime-display-gzipped (entity situation)
877   "Ungzip gzipped part and display."
878     (insert
879      (decode-coding-string
880       (with-temp-buffer
881         ;; #### Kludge to make FSF Emacs happy.
882         (if (featurep 'xemacs)
883             (insert (mime-entity-content entity))
884           (let ((content (mime-entity-content entity)))
885             (if (not (multibyte-string-p content))
886                 ;; I really hate this brain-damaged function.
887                 (set-buffer-multibyte nil))
888             (insert content)))
889         (as-binary-process
890          (call-process-region (point-min) (point-max) "gzip" t t
891                               nil "-cd"))
892         ;; Oh my goodness.
893         (when (fboundp 'set-buffer-multibyte)
894           (set-buffer-multibyte t))
895         (buffer-string))
896       mime-view-automatic-conversion))
897      t)
898
899 (defun mime-preview-inline ()
900   "View part as text without code conversion."
901   (interactive)
902   (let ((inhibit-read-only t)
903         (entity (get-text-property (point) 'mime-view-entity))
904         (situation (get-text-property (point) 'mime-view-situation))
905         start)
906     (when (and entity
907                (not (get-text-property (point) 'mime-view-entity-header))
908                (not (memq (mime-entity-media-type entity)
909                           '(multipart message))))
910       (setq start (or (and (not (mime-entity-parent entity))
911                            (1+ (previous-single-property-change
912                                 (point)
913                                 'mime-view-entity-header)))
914                       (and (not (eq (point) (point-min)))
915                            (not (eq (get-text-property (1- (point))
916                                                        'mime-view-entity)
917                                     entity))
918                            (point))
919                       (previous-single-property-change (point)
920                                                    'mime-view-entity)
921                       (point)))
922       (delete-region start
923                      (1-
924                       (or (next-single-property-change (point)
925                                                        'mime-view-entity)
926                           (point-max))))
927       (setq start (point))
928       (if (mime-view-entity-button-visible-p entity)
929           (mime-view-insert-entity-button entity))
930       (insert (mime-entity-content entity))
931       (if (and (bolp) (eolp))
932           (delete-char 1)
933         (forward-char 1))
934       (add-text-properties start (point)
935                            (list 'mime-view-entity entity
936                                  'mime-view-situation situation))
937       (goto-char start))))
938
939 (defun mime-preview-text (&optional ask-coding)
940   "View part as text. MIME charset will be guessed automatically.
941 With prefix, it prompts for coding-system."
942   (interactive "P")
943   (let ((inhibit-read-only t)
944         (mime-view-force-inline-types t)
945         (position (mime-preview-entity-boundary))
946         (coding (if ask-coding
947                     (or (read-coding-system "Coding system: ")
948                         mime-view-automatic-conversion)
949                   mime-view-automatic-conversion))
950         (cte (if ask-coding
951                  (completing-read "Content Transfer Encoding: "
952                                   (mime-encoding-alist) nil t)))
953         entity situation)
954     (setq entity (get-text-property (car position) 'mime-view-entity)
955           situation (get-text-property (car position) 'mime-view-situation))
956     (setq situation
957           (put-alist
958            'encoding cte
959            (put-alist
960             'coding coding
961             (put-alist
962              'body-presentation-method 'mime-display-text
963              (put-alist '*body 'visible situation)))))
964     (save-excursion
965       (delete-region (car position) (cdr position))
966       (mime-display-entity entity situation))))
967
968 (defun mime-preview-type ()
969   "View part as text without code conversion."
970   (interactive)
971   (mime-preview-toggle-content t))
972
973 (defun mime-preview-buttonize ()
974   (interactive)
975   (save-excursion
976     (goto-char (point-min))
977     (let (point)
978       (while (setq point (next-single-property-change
979                           (point) 'mime-view-entity))
980         (goto-char point)
981         (unless (get-text-property (point) 'mime-button)
982           (mime-preview-toggle-button t))))))
983
984 (defun mime-preview-unbuttonize ()
985   (interactive)
986   (save-excursion
987     (goto-char (point-min))
988     (let (point)
989       (while (setq point (next-single-property-change
990                           (point) 'mime-view-entity))
991         (goto-char point)
992         (when (get-text-property (point) 'mime-button)
993           (mime-preview-toggle-button 'hide))))))
994           
995
996 ;;; @ acting-condition
997 ;;;
998
999 (defvar mime-acting-condition nil
1000   "Condition-tree about how to process entity.")
1001
1002 (if (file-readable-p mailcap-file)
1003     (let ((entries (mailcap-parse-file)))
1004       (while entries
1005         (let ((entry (car entries))
1006               view print shared)
1007           (while entry
1008             (let* ((field (car entry))
1009                    (field-type (car field)))
1010               (cond ((eq field-type 'view)  (setq view field))
1011                     ((eq field-type 'print) (setq print field))
1012                     ((memq field-type '(compose composetyped edit)))
1013                     (t (setq shared (cons field shared)))))
1014             (setq entry (cdr entry)))
1015           (setq shared (nreverse shared))
1016           (ctree-set-calist-with-default
1017            'mime-acting-condition
1018            (append shared (list '(mode . "play")(cons 'method (cdr view)))))
1019           (if print
1020               (ctree-set-calist-with-default
1021                'mime-acting-condition
1022                (append shared
1023                        (list '(mode . "print")(cons 'method (cdr view)))))))
1024         (setq entries (cdr entries)))))
1025
1026 (ctree-set-calist-strictly
1027  'mime-acting-condition
1028  '((type . application)(subtype . octet-stream)
1029    (mode . "play")
1030    (method . mime-detect-content)))
1031
1032 (ctree-set-calist-with-default
1033  'mime-acting-condition
1034  '((mode . "extract")
1035    (method . mime-save-content)))
1036
1037 (ctree-set-calist-strictly
1038  'mime-acting-condition
1039  '((type . text)(subtype . x-rot13-47)(mode . "play")
1040    (method . mime-view-caesar)))
1041 (ctree-set-calist-strictly
1042  'mime-acting-condition
1043  '((type . text)(subtype . x-rot13-47-48)(mode . "play")
1044    (method . mime-view-caesar)))
1045
1046 (ctree-set-calist-strictly
1047  'mime-acting-condition
1048  '((type . message)(subtype . rfc822)(mode . "play")
1049    (method . mime-view-message/rfc822)))
1050 (ctree-set-calist-strictly
1051  'mime-acting-condition
1052  '((type . message)(subtype . partial)(mode . "play")
1053    (method . mime-store-message/partial-piece)))
1054
1055 (ctree-set-calist-strictly
1056  'mime-acting-condition
1057  '((type . message)(subtype . external-body)
1058    ("access-type" . "anon-ftp")
1059    (method . mime-view-message/external-anon-ftp)))
1060
1061 (ctree-set-calist-strictly
1062  'mime-acting-condition
1063  '((type . message)(subtype . external-body)
1064    ("access-type" . "url")
1065    (method . mime-view-message/external-url)))
1066
1067 (ctree-set-calist-strictly
1068  'mime-acting-condition
1069  '((type . application)(subtype . octet-stream)
1070    (method . mime-save-content)))
1071
1072
1073 ;;; @ quitting method
1074 ;;;
1075
1076 (defvar mime-preview-quitting-method-alist
1077   '((mime-show-message-mode
1078      . mime-preview-quitting-method-for-mime-show-message-mode))
1079   "Alist of `major-mode' vs. quitting-method of mime-view.")
1080
1081 (defvar mime-preview-over-to-previous-method-alist nil
1082   "Alist of `major-mode' vs. over-to-previous-method of mime-view.")
1083
1084 (defvar mime-preview-over-to-next-method-alist nil
1085   "Alist of `major-mode' vs. over-to-next-method of mime-view.")
1086
1087
1088 ;;; @ following method
1089 ;;;
1090
1091 (defvar mime-preview-following-method-alist nil
1092   "Alist of `major-mode' vs. following-method of mime-view.")
1093
1094 (defvar mime-view-following-required-fields-list
1095   '("From"))
1096
1097
1098 ;;; @ buffer setup
1099 ;;;
1100
1101 (defun mime-display-entity (entity &optional situation
1102                                    default-situation preview-buffer)
1103   "Display mime-entity ENTITY."
1104   (or preview-buffer
1105       (setq preview-buffer (current-buffer)))
1106   (in-calist-package 'mime-view)
1107   (or situation
1108       (setq situation
1109             (or (ctree-match-calist mime-preview-condition
1110                                     (append (mime-entity-situation entity)
1111                                             default-situation))
1112                 default-situation)))
1113   (let ((button-is-visible (mime-view-button-is-visible situation))
1114         (header-is-visible
1115          (mime-view-header-is-visible situation))
1116         (header-presentation-method
1117          (or (cdr (assq '*header-presentation-method situation))
1118              (cdr (assq 'header-presentation-method situation))
1119              (cdr (assq (cdr (assq 'major-mode situation))
1120                         mime-header-presentation-method-alist))))
1121         (body-is-visible
1122          (mime-view-body-is-visible situation))
1123         (body-presentation-method
1124          (cdr (assq 'body-presentation-method situation)))
1125         (children (mime-entity-children entity))
1126         nb ne nhb nbb)
1127     ;; Check if attachment is specified.
1128     ;; if inline is forced or not.
1129     (unless (or (eq t mime-view-force-inline-types)
1130                 (memq (mime-entity-media-type entity)
1131                       mime-view-force-inline-types)
1132                 (memq (mime-view-entity-type/subtype entity)
1133                       mime-view-force-inline-types)
1134                 ;; whether Content-Disposition header exists.
1135                 (not (mime-entity-content-disposition entity))
1136                 (eq 'inline
1137                     (mime-content-disposition-type
1138                      (mime-entity-content-disposition entity))))
1139       ;; This is attachment.
1140       ;; But show header when this is root entity.
1141       (if (mime-root-entity-p entity)
1142           (progn (setq body-is-visible nil)
1143                  (put-alist 'body 'invisible situation))
1144         (setq header-is-visible nil)
1145         (put-alist 'header 'invisible situation)))
1146     (set-buffer preview-buffer)
1147     (setq nb (point))
1148     (save-restriction
1149       (narrow-to-region nb nb)
1150       (if button-is-visible
1151           (mime-view-insert-entity-button entity
1152                                           ;; work around composite type
1153                                           (not (or children
1154                                                    body-is-visible))))
1155       (when header-is-visible
1156         (setq nhb (point))
1157         (if header-presentation-method
1158             (funcall header-presentation-method entity situation)
1159           (mime-insert-header entity
1160                               mime-view-ignored-field-list
1161                               mime-view-visible-field-list))
1162         (run-hooks 'mime-display-header-hook)
1163         (put-text-property nhb (point-max) 'mime-view-entity-header entity)
1164         (goto-char (point-max))
1165         (insert "\n"))
1166       (setq nbb (point))
1167       (cond (children)
1168             ((and body-is-visible
1169                   (functionp body-presentation-method))
1170              (funcall body-presentation-method entity situation))
1171             (t
1172              ;; When both body and button is not displayed,
1173              ;; there should be a button to indicate there's a part.
1174              (unless button-is-visible
1175                (goto-char (point-max))
1176                (mime-view-insert-entity-button entity
1177                                                ;; work around composite type
1178                                                (not (or children
1179                                                         body-is-visible))))
1180              (unless header-is-visible
1181                (goto-char (point-max))
1182                (insert "\n"))))
1183       (setq ne (point-max)))
1184     (put-text-property nb ne 'mime-view-entity entity)
1185     (put-text-property nb ne 'mime-view-situation situation)
1186     (put-text-property nbb ne 'mime-view-entity-body entity)
1187     (goto-char ne)
1188     (if (and children body-is-visible)
1189         (if (functionp body-presentation-method)
1190             (funcall body-presentation-method entity situation)
1191           (mime-display-multipart/mixed entity situation)))))
1192
1193 ;;; @ MIME viewer mode
1194 ;;;
1195
1196 (defconst mime-view-menu-title "MIME-View")
1197 (defconst mime-view-menu-list
1198   '((up          "Move to upper entity"    mime-preview-move-to-upper)
1199     (previous    "Move to previous entity" mime-preview-move-to-previous)
1200     (next        "Move to next entity"     mime-preview-move-to-next)
1201     (scroll-down "Scroll-down"             mime-preview-scroll-down-entity)
1202     (scroll-up   "Scroll-up"               mime-preview-scroll-up-entity)
1203     (play        "Play current entity"     mime-preview-play-current-entity)
1204     (extract     "Extract current entity"  mime-preview-extract-current-entity)
1205     (print       "Print current entity"    mime-preview-print-current-entity)
1206     (raw "View text without code conversion" mime-preview-inline)
1207     (text "View text with code conversion" mime-preview-text)
1208     (type "View internally as type" mime-preview-type))
1209   "Menu for MIME Viewer.")
1210
1211 (cond ((featurep 'xemacs)
1212        (defvar mime-view-xemacs-popup-menu
1213          (cons mime-view-menu-title
1214                (mapcar (function
1215                         (lambda (item)
1216                           (vector (nth 1 item)(nth 2 item) t)))
1217                        mime-view-menu-list)))
1218        (defun mime-view-xemacs-popup-menu (event)
1219          "Popup the menu in the MIME Viewer buffer"
1220          (interactive "e")
1221          (select-window (event-window event))
1222          (set-buffer (event-buffer event))
1223          (popup-menu 'mime-view-xemacs-popup-menu))
1224        (defvar mouse-button-2 'button2))
1225       (t
1226        (defvar mouse-button-2 [mouse-2])))
1227
1228 (defun mime-view-define-keymap (&optional default)
1229   (let ((mime-view-mode-map (if (keymapp default)
1230                                 (copy-keymap default)
1231                               (make-sparse-keymap))))
1232     (define-key mime-view-mode-map
1233       "u"        (function mime-preview-move-to-upper))
1234     (define-key mime-view-mode-map
1235       "p"        (function mime-preview-move-to-previous))
1236     (define-key mime-view-mode-map
1237       "n"        (function mime-preview-move-to-next))
1238     (define-key mime-view-mode-map
1239       "\e\t"     (function mime-preview-move-to-previous))
1240     (define-key mime-view-mode-map
1241       "\t"       (function mime-preview-move-to-next))
1242     (define-key mime-view-mode-map
1243       " "        (function mime-preview-scroll-up-entity))
1244     (define-key mime-view-mode-map
1245       "\M- "     (function mime-preview-scroll-down-entity))
1246     (define-key mime-view-mode-map
1247       "\177"     (function mime-preview-scroll-down-entity))
1248     (define-key mime-view-mode-map
1249       "\C-m"     (function mime-preview-next-line-entity))
1250     (define-key mime-view-mode-map
1251       "\C-\M-m"  (function mime-preview-previous-line-entity))
1252     (define-key mime-view-mode-map
1253       "v"        (function mime-preview-play-current-entity))
1254     (define-key mime-view-mode-map
1255       "e"        (function mime-preview-extract-current-entity))
1256     (define-key mime-view-mode-map
1257       "\C-c\C-e"        (function mime-preview-extract-current-entity))
1258     (define-key mime-view-mode-map
1259       "i"        (function mime-preview-inline))
1260     (define-key mime-view-mode-map
1261       "c"        (function mime-preview-text))
1262     (define-key mime-view-mode-map
1263       "t"        (function mime-preview-type))
1264     (define-key mime-view-mode-map
1265       "b"        (function mime-preview-buttonize))
1266     (define-key mime-view-mode-map
1267       "B"        (function mime-preview-unbuttonize))
1268     (define-key mime-view-mode-map
1269       "\C-c\C-t\C-h" (function mime-preview-toggle-header))
1270     (define-key mime-view-mode-map
1271       "\C-c\C-th" (function mime-preview-toggle-header))
1272     (define-key mime-view-mode-map
1273       "\C-c\C-t\C-c" (function mime-preview-toggle-content))
1274     (define-key mime-view-mode-map
1275       "\C-c\C-tc" (function mime-preview-toggle-content))
1276     (define-key mime-view-mode-map
1277       "\C-c\C-tH" (function mime-preview-toggle-all-header))
1278     (define-key mime-view-mode-map
1279       "\C-c\C-tb" (function mime-preview-toggle-button))
1280     (define-key mime-view-mode-map
1281       "\C-c\C-p" (function mime-preview-print-current-entity))
1282     (define-key mime-view-mode-map
1283       "a"        (function mime-preview-follow-current-entity))
1284     (define-key mime-view-mode-map
1285       "q"        (function mime-preview-quit))
1286     (define-key mime-view-mode-map
1287       "\C-c\C-x" (function mime-preview-kill-buffer))
1288     ;; (define-key mime-view-mode-map
1289     ;;   "<"        (function beginning-of-buffer))
1290     ;; (define-key mime-view-mode-map
1291     ;;   ">"        (function end-of-buffer))
1292     (define-key mime-view-mode-map
1293       "?"        (function describe-mode))
1294     (define-key mime-view-mode-map
1295       [tab] (function mime-preview-move-to-next))
1296     (define-key mime-view-mode-map
1297       [delete] (function mime-preview-scroll-down-entity))
1298     (define-key mime-view-mode-map
1299       [backspace] (function mime-preview-scroll-down-entity))
1300     (if (functionp default)
1301         (cond ((featurep 'xemacs)
1302                (set-keymap-default-binding mime-view-mode-map default))
1303               (t
1304                (setq mime-view-mode-map
1305                      (append mime-view-mode-map (list (cons t default)))))))
1306     (if mouse-button-2
1307         (define-key mime-view-mode-map
1308           mouse-button-2 (function mime-button-dispatcher)))
1309     (cond ((featurep 'xemacs)
1310            (define-key mime-view-mode-map
1311              mouse-button-3 (function mime-view-xemacs-popup-menu)))
1312           ((>= emacs-major-version 19)
1313            (define-key mime-view-mode-map [menu-bar mime-view]
1314              (cons mime-view-menu-title
1315                    (make-sparse-keymap mime-view-menu-title)))
1316            (mapcar (function
1317                     (lambda (item)
1318                       (define-key mime-view-mode-map
1319                         (vector 'menu-bar 'mime-view (car item))
1320                         (cons (nth 1 item)(nth 2 item)))))
1321                    (reverse mime-view-menu-list))))
1322     (use-local-map mime-view-mode-map)
1323     (run-hooks 'mime-view-define-keymap-hook)))
1324
1325 (defsubst mime-maybe-hide-echo-buffer ()
1326   "Clear mime-echo buffer and delete window for it."
1327   (let ((buf (get-buffer mime-echo-buffer-name)))
1328     (if buf
1329         (save-excursion
1330           (set-buffer buf)
1331           (erase-buffer)
1332           (let ((win (get-buffer-window buf)))
1333             (if win
1334                 (delete-window win)))
1335           (bury-buffer buf)))))
1336
1337 (defvar mime-view-redisplay nil)
1338
1339 ;;;###autoload
1340 (defun mime-display-message (message &optional preview-buffer
1341                                      mother default-keymap-or-function
1342                                      original-major-mode)
1343   "View MESSAGE in MIME-View mode.
1344
1345 Optional argument PREVIEW-BUFFER specifies the buffer of the
1346 presentation.  It must be either nil or a name of preview buffer.
1347
1348 Optional argument MOTHER specifies mother-buffer of the preview-buffer.
1349
1350 Optional argument DEFAULT-KEYMAP-OR-FUNCTION is nil, keymap or
1351 function.  If it is a keymap, keymap of MIME-View mode will be added
1352 to it.  If it is a function, it will be bound as default binding of
1353 keymap of MIME-View mode."
1354   (mime-maybe-hide-echo-buffer)
1355   (let ((win-conf (current-window-configuration)))
1356     (or preview-buffer
1357         (setq preview-buffer
1358               (concat "*Preview-" (mime-entity-name message) "*")))
1359     (or original-major-mode
1360         (setq original-major-mode major-mode))
1361     (let ((inhibit-read-only t))
1362       (set-buffer (get-buffer-create preview-buffer))
1363       (widen)
1364       (erase-buffer)
1365       (if mother
1366           (setq mime-mother-buffer mother))
1367       (setq mime-preview-original-window-configuration win-conf)
1368       (setq major-mode 'mime-view-mode)
1369       (setq mode-name "MIME-View")
1370       (mime-display-entity message nil
1371                            (list (cons 'entity-button 'invisible)
1372                                  (cons 'header 'visible)
1373                                  (cons 'major-mode original-major-mode))
1374                            preview-buffer)
1375       (mime-view-define-keymap default-keymap-or-function)
1376       (set (make-local-variable 'line-move-ignore-invisible) t)
1377       (let ((point
1378              (next-single-property-change (point-min) 'mime-view-entity)))
1379         (if point
1380             (goto-char point)
1381           (goto-char (point-min))
1382           (search-forward "\n\n" nil t)))
1383       (run-hooks 'mime-view-mode-hook)
1384       (set-buffer-modified-p nil)
1385       (setq buffer-read-only t)
1386       preview-buffer)))
1387
1388 ;;;###autoload
1389 (defun mime-view-buffer (&optional raw-buffer preview-buffer mother
1390                                    default-keymap-or-function
1391                                    representation-type)
1392   "View RAW-BUFFER in MIME-View mode.
1393 Optional argument PREVIEW-BUFFER is either nil or a name of preview
1394 buffer.
1395 Optional argument DEFAULT-KEYMAP-OR-FUNCTION is nil, keymap or
1396 function.  If it is a keymap, keymap of MIME-View mode will be added
1397 to it.  If it is a function, it will be bound as default binding of
1398 keymap of MIME-View mode.
1399 Optional argument REPRESENTATION-TYPE is representation-type of
1400 message.  It must be nil, `binary' or `cooked'.  If it is nil,
1401 `cooked' is used as default."
1402   (interactive)
1403   (or raw-buffer
1404       (setq raw-buffer (current-buffer)))
1405   (or representation-type
1406       (setq representation-type
1407             (save-excursion
1408               (set-buffer raw-buffer)
1409               (cdr (or (assq major-mode mime-raw-representation-type-alist)
1410                        (assq t mime-raw-representation-type-alist))))))
1411   (if (eq representation-type 'binary)
1412       (setq representation-type 'buffer))
1413   (setq preview-buffer (mime-display-message
1414                         (mime-open-entity representation-type raw-buffer)
1415                         preview-buffer mother default-keymap-or-function))
1416   (or (get-buffer-window preview-buffer)
1417       (let ((r-win (get-buffer-window raw-buffer)))
1418         (if r-win
1419             (set-window-buffer r-win preview-buffer)
1420           (let ((m-win (and mother (get-buffer-window mother))))
1421             (if m-win
1422                 (set-window-buffer m-win preview-buffer)
1423               (switch-to-buffer preview-buffer)))))))
1424
1425 (defun mime-view-mode (&optional mother ctl encoding
1426                                  raw-buffer preview-buffer
1427                                  default-keymap-or-function)
1428   "Major mode for viewing MIME message.
1429
1430 Here is a list of the standard keys for mime-view-mode.
1431
1432 key             feature
1433 ---             -------
1434
1435 u               Move to upper content
1436 p or M-TAB      Move to previous content
1437 n or TAB        Move to next content
1438 SPC             Scroll up or move to next content
1439 M-SPC or DEL    Scroll down or move to previous content
1440 RET             Move to next line
1441 M-RET           Move to previous line
1442 v               Decode current content as `play mode'
1443 e               Decode current content as `extract mode'
1444 C-c C-p         Decode current content as `print mode'
1445 a               Followup to current content.
1446 q               Quit
1447 button-2        Move to point under the mouse cursor
1448                 and decode current content as `play mode'"
1449   (interactive)
1450   (unless mime-view-redisplay
1451     (save-excursion
1452       (if raw-buffer (set-buffer raw-buffer))
1453       (let ((type
1454              (cdr
1455               (or (assq major-mode mime-raw-representation-type-alist)
1456                   (assq t mime-raw-representation-type-alist)))))
1457         (if (eq type 'binary)
1458             (setq type 'buffer))
1459         (setq mime-message-structure (mime-open-entity type raw-buffer))
1460         (or (mime-entity-content-type mime-message-structure)
1461             (mime-entity-set-content-type-internal
1462              mime-message-structure ctl)))
1463       (or (mime-entity-encoding mime-message-structure)
1464           (mime-entity-set-encoding-internal mime-message-structure encoding))))
1465   (mime-display-message mime-message-structure preview-buffer
1466                         mother default-keymap-or-function))
1467
1468
1469 ;;; @@ playing
1470 ;;;
1471
1472 (autoload 'mime-preview-play-current-entity "mime-play"
1473   "Play current entity." t)
1474
1475 (defun mime-preview-extract-current-entity (&optional ignore-examples)
1476   "Extract current entity into file (maybe).
1477 It decodes current entity to call internal or external method as
1478 \"extract\" mode.  The method is selected from variable
1479 `mime-acting-condition'."
1480   (interactive "P")
1481   (mime-preview-play-current-entity ignore-examples "extract"))
1482
1483 (defun mime-preview-print-current-entity (&optional ignore-examples)
1484   "Print current entity (maybe).
1485 It decodes current entity to call internal or external method as
1486 \"print\" mode.  The method is selected from variable
1487 `mime-acting-condition'."
1488   (interactive "P")
1489   (mime-preview-play-current-entity ignore-examples "print"))
1490
1491
1492 ;;; @@ following
1493 ;;;
1494
1495 (defun mime-preview-follow-current-entity ()
1496   "Write follow message to current entity.
1497 It calls following-method selected from variable
1498 `mime-preview-following-method-alist'."
1499   (interactive)
1500   (let (entity position entity-node-id header-exists)
1501     (while (null (setq entity
1502                        (get-text-property (point) 'mime-view-entity)))
1503       (backward-char))
1504     (setq position (mime-preview-entity-boundary))
1505     (setq entity-node-id (mime-entity-node-id entity)
1506           header-exists
1507           ;; When on an invisible entity, there's no header.
1508           (or (mime-view-header-is-visible
1509                (get-text-property (car position) 'mime-view-situation))
1510               ;; We are on a rfc822 button.
1511               (and (eq 'message (mime-entity-media-type
1512                                  entity))
1513                    (eq 'rfc822 (mime-entity-media-subtype
1514                                 entity))
1515                    (get-text-property
1516                     (next-single-property-change
1517                      (car position) 'mime-button
1518                      nil (point-max))
1519                     'mime-view-entity-header))))
1520     (let* ((mode (mime-preview-original-major-mode 'recursive))
1521            (new-name
1522             (format "%s-%s" (buffer-name) (reverse entity-node-id)))
1523            new-buf
1524            (the-buf (current-buffer))
1525            fields)
1526       (save-excursion
1527         (set-buffer (setq new-buf (get-buffer-create new-name)))
1528         (erase-buffer)
1529         ;; Compatibility kludge.
1530         ;; FSF Emacs can only take substring of current-buffer.
1531         (insert
1532          (save-excursion
1533            (set-buffer the-buf)
1534            (buffer-substring-no-properties (car position)
1535                                            (cdr position))))
1536         (if header-exists
1537             (delete-region (goto-char (point-min))
1538                            (re-search-forward "^$"))
1539           (goto-char (point-min))
1540           (insert "\n"))
1541         (goto-char (point-min))
1542         (let ((current-entity
1543                (if (and (eq (mime-entity-media-type entity) 'message)
1544                         (eq (mime-entity-media-subtype entity) 'rfc822))
1545                    (car (mime-entity-children entity))
1546                  entity)))
1547           (while (and current-entity
1548                       (if (and (eq (mime-entity-media-type
1549                                     current-entity) 'message)
1550                                (eq (mime-entity-media-subtype
1551                                     current-entity) 'rfc822))
1552                           nil
1553                         (mime-insert-header current-entity fields)
1554                         t))
1555             (setq fields (std11-collect-field-names)
1556                   current-entity (mime-entity-parent current-entity))))
1557         (let ((rest mime-view-following-required-fields-list)
1558               field-name ret)
1559           (while rest
1560             (setq field-name (car rest))
1561             (or (std11-field-body field-name)
1562                 (progn
1563                   (save-excursion
1564                     (set-buffer the-buf)
1565                     (let ((entity (when mime-mother-buffer
1566                                     (set-buffer mime-mother-buffer)
1567                                     (get-text-property (point)
1568                                                        'mime-view-entity))))
1569                       (while (and entity
1570                                   (null (setq ret (mime-entity-fetch-field
1571                                                    entity field-name))))
1572                         (setq entity (mime-entity-parent entity)))))
1573                   (if ret
1574                       (insert (concat field-name ": " ret "\n")))))
1575             (setq rest (cdr rest))))
1576         (mime-decode-header-in-buffer))
1577       (let ((f (cdr (assq mode mime-preview-following-method-alist))))
1578         (if (functionp f)
1579             (funcall f new-buf)
1580           (message
1581            (format
1582             "Sorry, following method for %s is not implemented yet."
1583             mode)))))))
1584
1585
1586 ;;; @@ moving
1587 ;;;
1588
1589 (defun mime-preview-move-to-upper ()
1590   "Move to upper entity.
1591 If there is no upper entity, call function `mime-preview-quit'."
1592   (interactive)
1593   (let (cinfo)
1594     (while (null (setq cinfo
1595                        (get-text-property (point) 'mime-view-entity)))
1596       (backward-char))
1597     (let ((r (mime-entity-parent cinfo))
1598           point)
1599       (catch 'tag
1600         (while (setq point (previous-single-property-change
1601                             (point) 'mime-view-entity))
1602           (goto-char point)
1603           (when (eq r (get-text-property (point) 'mime-view-entity))
1604             (if (or (eq mime-preview-move-scroll t)
1605                     (and mime-preview-move-scroll
1606                          (>= point
1607                              (save-excursion
1608                                (move-to-window-line -1)
1609                                (forward-line (* -1 next-screen-context-lines))
1610                                (beginning-of-line)
1611                                (point)))))
1612                 (recenter next-screen-context-lines))
1613             (throw 'tag t)))
1614         (mime-preview-quit)))))
1615
1616 (defun mime-preview-move-to-previous ()
1617   "Move to previous entity.
1618 If there is no previous entity, it calls function registered in
1619 variable `mime-preview-over-to-previous-method-alist'."
1620   (interactive)
1621   (while (and (not (bobp))
1622               (null (get-text-property (point) 'mime-view-entity)))
1623     (backward-char))
1624   (let ((point (previous-single-property-change (point) 'mime-view-entity)))
1625     (if (and point
1626              (>= point (point-min)))
1627         (if (get-text-property (1- point) 'mime-view-entity)
1628             (progn (goto-char point)
1629                    (if
1630                     (or (eq mime-preview-move-scroll t)
1631                         (and mime-preview-move-scroll
1632                              (<= point
1633                                 (save-excursion
1634                                   (move-to-window-line 0)
1635                                   (forward-line next-screen-context-lines)
1636                                   (end-of-line)
1637                                   (point)))))
1638                         (recenter next-screen-context-lines)))
1639           (goto-char (1- point))
1640           (mime-preview-move-to-previous))
1641       (let ((f (assq (mime-preview-original-major-mode)
1642                      mime-preview-over-to-previous-method-alist)))
1643         (if f
1644             (funcall (cdr f)))))))
1645
1646 (defun mime-preview-move-to-next ()
1647   "Move to next entity.
1648 If there is no previous entity, it calls function registered in
1649 variable `mime-preview-over-to-next-method-alist'."
1650   (interactive)
1651   (while (and (not (eobp))
1652               (null (get-text-property (point) 'mime-view-entity)))
1653     (forward-char))
1654   (let ((point (next-single-property-change (point) 'mime-view-entity)))
1655     (if (and point
1656              (<= point (point-max)))
1657         (progn
1658           (goto-char point)
1659           (if (null (get-text-property point 'mime-view-entity))
1660               (mime-preview-move-to-next)
1661             (and
1662              (or (eq mime-preview-move-scroll t)
1663                  (and mime-preview-move-scroll
1664                       (>= point
1665                          (save-excursion
1666                            (move-to-window-line -1)
1667                            (forward-line
1668                             (* -1 next-screen-context-lines))
1669                            (beginning-of-line)
1670                            (point)))))
1671                  (recenter next-screen-context-lines))))
1672       (let ((f (assq (mime-preview-original-major-mode)
1673                      mime-preview-over-to-next-method-alist)))
1674         (if f
1675             (funcall (cdr f)))))))
1676
1677 (defun mime-preview-scroll-up-entity (&optional h)
1678   "Scroll up current entity.
1679 If reached to (point-max), it calls function registered in variable
1680 `mime-preview-over-to-next-method-alist'."
1681   (interactive)
1682   (if (eobp)
1683       (let ((f (assq (mime-preview-original-major-mode)
1684                      mime-preview-over-to-next-method-alist)))
1685         (if f
1686             (funcall (cdr f))))
1687     (let ((point
1688            (or (next-single-property-change (point) 'mime-view-entity)
1689                (point-max)))
1690           (bottom (window-end (selected-window))))
1691       (if (and (not h)
1692                (> bottom point)
1693                (not mime-preview-scroll-full-screen))
1694           (progn (goto-char point)
1695                  (recenter next-screen-context-lines))
1696         (condition-case nil
1697             (let (window-pixel-scroll-increment)
1698               (scroll-up h))
1699           (end-of-buffer
1700            (goto-char (point-max))))))))
1701
1702 (defun mime-preview-scroll-down-entity (&optional h)
1703   "Scroll down current entity.
1704 If reached to (point-min), it calls function registered in variable
1705 `mime-preview-over-to-previous-method-alist'."
1706   (interactive)
1707   (if (bobp)
1708       (let ((f (assq (mime-preview-original-major-mode)
1709                      mime-preview-over-to-previous-method-alist)))
1710         (if f
1711             (funcall (cdr f))))
1712     (let ((point
1713            (or (previous-single-property-change (point) 'mime-view-entity)
1714                (point-min)))
1715           (top (window-start (selected-window))))
1716       (if (and (not h)
1717                (< top point)
1718                (not mime-preview-scroll-full-screen))
1719           (progn (goto-char point)
1720                  (recenter (* -1 next-screen-context-lines)))
1721         (condition-case nil
1722             (let (window-pixel-scroll-increment)
1723               (scroll-down h))
1724           (beginning-of-buffer
1725            (goto-char (point-min))))))))
1726
1727 (defun mime-preview-next-line-entity (&optional lines)
1728   "Scroll up one line (or prefix LINES lines).
1729 If LINES is negative, scroll down LINES lines."
1730   (interactive "p")
1731   (mime-preview-scroll-up-entity (or lines 1)))
1732
1733 (defun mime-preview-previous-line-entity (&optional lines)
1734   "Scrroll down one line (or prefix LINES lines).
1735 If LINES is negative, scroll up LINES lines."
1736   (interactive "p")
1737   (mime-preview-scroll-down-entity (or lines 1)))
1738
1739 (defun mime-preview-entity-boundary (&optional point)
1740   (or point
1741       (setq point (point)))
1742   (and (eq point (point-max))
1743        (setq point (1- (point-max))))
1744   (let ((entity (get-text-property point 'mime-view-entity))
1745         (start (previous-single-property-change (1+ point) 'mime-view-entity
1746                                                 nil (point-min)))
1747         end done)
1748     (if (not (mime-entity-node-id entity))
1749         (setq end (point-max))
1750       (while (and (mime-entity-children entity)
1751                   (not done))
1752         (if (not (mime-view-body-is-visible
1753                   (get-text-property point 'mime-view-situation)))
1754             (setq done t)
1755           ;; If the part is shown, search the last part.
1756           (let* ((child (car (last (mime-entity-children entity))))
1757                  (node-id (mime-entity-node-id child))
1758                  (tmp-node-id (mime-entity-node-id
1759                                  (get-text-property point
1760                                                     'mime-view-entity))))
1761             (while (or (< (length tmp-node-id)
1762                           (length node-id))
1763                        (not (eq (nthcdr (- (length tmp-node-id)
1764                                            (length node-id))
1765                                         tmp-node-id)
1766                                 node-id)))
1767               (setq point
1768                     (next-single-property-change point 'mime-view-entity)
1769                     tmp-node-id (mime-entity-node-id
1770                                  (get-text-property point
1771                                                     'mime-view-entity))))
1772             (setq entity child))))
1773       (setq end (next-single-property-change
1774                  point 'mime-view-entity nil (point-max))))
1775     (cons start end)))
1776
1777 (defun mime-preview-toggle-header (&optional show)
1778   "Toggle display of entity header.
1779 When prefix is given, it always displays the header."
1780   (interactive "P")
1781   (let ((inhibit-read-only t)
1782         (mime-view-force-inline-types t)
1783         (position (mime-preview-entity-boundary))
1784         entity header-is-visible situation)
1785     (setq entity (get-text-property (car position) 'mime-view-entity)
1786           situation (get-text-property (car position) 'mime-view-situation))
1787     (setq header-is-visible (mime-view-header-is-visible situation))
1788     (save-excursion
1789       (delete-region (car position) (cdr position))
1790       (if (or show (not header-is-visible))
1791           (mime-display-entity
1792            entity
1793            (del-alist '*entity-button
1794                       (put-alist '*header 'visible
1795                                  situation)))
1796         (mime-display-entity
1797          entity
1798          (put-alist '*entity-button
1799                     'visible
1800                     (put-alist '*header 'invisible
1801                                situation)))))))
1802
1803 (defun mime-preview-toggle-all-header (&optional show)
1804   "Toggle display of entity header.
1805 When prefix is given, it always displays the header."
1806   (interactive "P")
1807   (let ((inhibit-read-only t)
1808         (mime-view-force-inline-types t)
1809         (position (mime-preview-entity-boundary))
1810         entity header-is-visible situation)
1811     (setq entity (get-text-property (car position) 'mime-view-entity)
1812           situation (get-text-property (car position) 'mime-view-situation))
1813     (setq header-is-visible (mime-view-header-is-visible situation))
1814     (save-excursion
1815       (delete-region (car position) (cdr position))
1816       (if (or show (not header-is-visible))
1817           (mime-display-entity
1818            entity
1819            (del-alist '*entity-button
1820                       (del-alist '*header
1821                                  (del-alist '*header-presentation-method
1822                                             situation))))
1823         (mime-display-entity
1824          entity
1825          (put-alist
1826           '*entity-button
1827           'visible
1828           (put-alist
1829            '*header 'invisible
1830            (put-alist '*header-presentation-method
1831                       #'(lambda (entity situation)
1832                           (mime-insert-header
1833                            entity nil '(".*")))
1834                       situation))))))))
1835
1836 (defun mime-preview-toggle-content (&optional show)
1837   "Toggle display of entity body.
1838 When prefix is given, it always displays the content."
1839   (interactive "P")
1840   (let ((inhibit-read-only t)
1841         (mime-view-force-inline-types t)
1842         (position (mime-preview-entity-boundary))
1843         entity situation)
1844     (setq entity (get-text-property (car position) 'mime-view-entity)
1845           situation (get-text-property (car position) 'mime-view-situation))
1846     (setq situation
1847           (if (or show (not (mime-view-body-is-visible situation)))
1848               (del-alist
1849                '*entity-button
1850                (put-alist '*body 'visible situation))
1851             (put-alist
1852              '*entity-button 'visible
1853              (put-alist '*body 'invisible situation))))
1854     (save-excursion
1855       (delete-region (car position) (cdr position))
1856       (mime-display-entity entity situation))))
1857
1858 (defun mime-preview-toggle-button (&optional condition)
1859   "Toggle display of entity button.
1860 When prefix is given, it always displays the content.
1861 If condition is 'hide, hide all buttons."
1862   (interactive "P")
1863   (let ((inhibit-read-only t)
1864         (mime-view-force-inline-types t)
1865         (position (mime-preview-entity-boundary))
1866         entity situation button-is-visible)
1867     (setq entity (get-text-property (car position) 'mime-view-entity)
1868           situation (get-text-property (car position) 'mime-view-situation)
1869           button-is-visible (mime-view-button-is-visible situation))
1870     (save-excursion
1871       (delete-region (car position) (cdr position))
1872       (if (or (eq condition 'hide)
1873               (and (not condition) button-is-visible))
1874           (mime-display-entity entity
1875                                (put-alist '*entity-button
1876                                           'invisible situation))
1877         (mime-display-entity entity
1878                              (put-alist '*entity-button
1879                                         'visible situation))))))
1880
1881 ;;; @@ quitting
1882 ;;;
1883
1884 (defun mime-preview-quit ()
1885   "Quit from MIME-preview buffer.
1886 It calls function registered in variable
1887 `mime-preview-quitting-method-alist'."
1888   (interactive)
1889   (let ((r (assq (mime-preview-original-major-mode)
1890                  mime-preview-quitting-method-alist)))
1891     (if r
1892         (funcall (cdr r))
1893       (kill-buffer (current-buffer)))))
1894
1895 (defun mime-preview-kill-buffer ()
1896   (interactive)
1897   (kill-buffer (current-buffer)))
1898
1899
1900 ;;; @ end
1901 ;;;
1902
1903 (provide 'mime-view)
1904
1905 (run-hooks 'mime-view-load-hook)
1906
1907 ;;; mime-view.el ends here