Synch with Oort Gnus.
[elisp/gnus.git-] / lisp / mm-decode.el
index fe50694..b6fb1c4 100644 (file)
 
 ;;; Commentary:
 
+;; Jaap-Henk Hoepman (jhh@xs4all.nl): 
+;;
+;; Added support for delayed destroy of external MIME viewers. All external
+;; viewers for mime types in mm-keep-viewer-alive-types will remain active
+;; after switching articles or groups, and will only be removed when exiting
+;; gnus.
+;; 
+
 ;;; Code:
 
 (require 'mail-parse)
 (require 'gnus-mailcap)
 (require 'mm-bodies)
-(eval-when-compile (require 'cl))
+(eval-when-compile (require 'cl)
+                   (require 'term))
 
 (eval-and-compile
   (autoload 'mm-inline-partial "mm-partial")
   (autoload 'mm-inline-external-body "mm-extern")
   (autoload 'mm-insert-inline "mm-view"))
 
+(add-hook 'gnus-exit-gnus-hook 'mm-destroy-postponed-undisplay-list)
+
 (defgroup mime-display ()
   "Display of MIME in mail and news articles."
   :link '(custom-manual "(emacs-mime)Customization")
     ("application/pkcs7-signature" ignore identity)
     ("multipart/alternative" ignore identity)
     ("multipart/mixed" ignore identity)
-    ("multipart/related" ignore identity))
+    ("multipart/related" ignore identity)
+    ;; Default to displaying as text
+    (".*" mm-inline-text identity))
   "Alist of media types/tests saying whether types can be displayed inline."
   :type '(repeat (list (string :tag "MIME type")
                       (function :tag "Display function")
     "message/partial" "message/external-body" "application/emacs-lisp"
     "application/pgp-signature" "application/x-pkcs7-signature"
     "application/pkcs7-signature")
-  "List of media types that are to be displayed inline."
+  "List of media types that are to be displayed inline.
+See also `mm-inline-media-tests', which says how to display a media
+type inline."
+  :type '(repeat string)
+  :group 'mime-display)
+
+(defcustom mm-keep-viewer-alive-types
+  '("application/postscript" "application/msword" "application/vnd.ms-excel"
+    "application/pdf" "application/x-dvi")
+  "List of media types for which the external viewer will not be killed
+when selecting a different article."
   :type '(repeat string)
   :group 'mime-display)
  
@@ -222,22 +245,51 @@ to:
   :type '(repeat string)
   :group 'mime-display)
 
-(defvar mm-tmp-directory
+(defcustom mm-tmp-directory
   (cond ((fboundp 'temp-directory) (temp-directory))
        ((boundp 'temporary-file-directory) temporary-file-directory)
        ("/tmp/"))
-  "Where mm will store its temporary files.")
+  "Where mm will store its temporary files."
+  :type 'directory
+  :group 'mime-display)
 
 (defcustom mm-inline-large-images nil
   "If non-nil, then all images fit in the buffer."
   :type 'boolean
   :group 'mime-display)
 
+(defvar mm-file-name-rewrite-functions nil
+  "*List of functions used for rewriting file names of MIME parts.
+Each function takes a file name as input and returns a file name.
+
+Ready-made functions include
+`mm-file-name-delete-whitespace',
+`mm-file-name-trim-whitespace',
+`mm-file-name-collapse-whitespace',
+`mm-file-name-replace-whitespace',
+`capitalize', `downcase', `upcase', and
+`upcase-initials'.")
+
+(defvar mm-file-name-replace-whitespace nil
+  "String used for replacing whitespace characters; default is `\"_\"'.")
+
+(defcustom mm-default-directory nil
+  "The default directory where mm will save files.
+If not set, `default-directory' will be used."
+  :type 'directory
+  :group 'mime-display)
+
+(defcustom mm-external-terminal-program "xterm"
+  "The program to start an external terminal."
+  :type 'string
+  :group 'mime-display)
+
 ;;; Internal variables.
 
 (defvar mm-dissection-list nil)
 (defvar mm-last-shell-command "")
 (defvar mm-content-id-alist nil)
+(defvar mm-postponed-undisplay-list nil)
 
 ;; According to RFC2046, in particular, in a digest, the default
 ;; Content-Type value for a body part is changed from "text/plain" to
@@ -324,6 +376,30 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
       (setq alist (cdr alist)))
     (nreverse plist)))
 
+(defun mm-keep-viewer-alive-p (handle)
+  "Say whether external viewer for HANDLE should stay alive."
+  (let ((types mm-keep-viewer-alive-types)
+       (type (mm-handle-media-type handle))
+       ty)
+    (catch 'found
+      (while (setq ty (pop types))
+       (when (string-match ty type)
+         (throw 'found t))))))
+
+(defun mm-handle-set-external-undisplayer (handle function)
+ "Set the undisplayer for this handle; postpone undisplaying of viewers
+for types in mm-keep-viewer-alive-types."
+ (if (mm-keep-viewer-alive-p handle)
+     (let ((new-handle (copy-sequence handle)))
+       (mm-handle-set-undisplayer new-handle function)
+       (mm-handle-set-undisplayer handle nil)
+       (push new-handle mm-postponed-undisplay-list))
+   (mm-handle-set-undisplayer handle function)))
+
+(defun mm-destroy-postponed-undisplay-list ()
+  (message "Destroying external MIME viewers")
+  (mm-destroy-parts mm-postponed-undisplay-list))
+
 (defun mm-dissect-buffer (&optional no-strict-mime)
   "Dissect the current buffer and return a list of MIME handles."
   (save-excursion
@@ -337,9 +413,14 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
                cte (mail-fetch-field "content-transfer-encoding")
                cd (mail-fetch-field "content-disposition")
                description (mail-fetch-field "content-description")
-               from (cadr (mail-extract-address-components
-                           (or (mail-fetch-field "from") "")))
-               id (mail-fetch-field "content-id"))))
+               from (mail-fetch-field "from")
+               id (mail-fetch-field "content-id"))
+         ;; FIXME: In some circumstances, this code is running within
+         ;; an unibyte macro.  mail-extract-address-components
+         ;; creates unibyte buffers. This `if', though not a perfect
+         ;; solution, avoids most of them.
+         (if from
+             (setq from (cadr (mail-extract-address-components from))))))
       (when cte
        (setq cte (mail-header-strip cte)))
       (if (or (not ctl)
@@ -438,14 +519,18 @@ The original alist is not modified.  See also `destructive-alist-to-plist'."
 (defun mm-copy-to-buffer ()
   "Copy the contents of the current buffer to a fresh buffer."
   (save-excursion
-    (let ((obuf (current-buffer))
-         beg)
+    (let ((flag enable-multibyte-characters)
+         (new-buffer (generate-new-buffer " *mm*")))
       (goto-char (point-min))
       (search-forward-regexp "^\n" nil t)
-      (setq beg (point))
-      (set-buffer (generate-new-buffer " *mm*"))
-      (insert-buffer-substring obuf beg)
-      (current-buffer))))
+      (save-restriction
+       (narrow-to-region (point) (point-max))
+       (when flag
+         (set-buffer-multibyte nil))
+       (copy-to-buffer new-buffer (point-min) (point-max))
+       (when flag
+         (set-buffer-multibyte t)))
+      new-buffer)))
 
 (defun mm-display-parts (handle &optional no-default)
   (if (stringp (car handle))
@@ -467,7 +552,8 @@ external if displayed external."
        (mm-remove-part handle)
       (let* ((type (mm-handle-media-type handle))
             (method (mailcap-mime-info type)))
-       (if (mm-inlined-p handle)
+       (if (and (mm-inlinable-p handle)
+                (mm-inlined-p handle))
            (progn
              (forward-line 1)
              (mm-display-inline handle)
@@ -537,13 +623,34 @@ external if displayed external."
          (message "Viewing with %s" method)
          (cond (needsterm
                 (unwind-protect
-                    (start-process "*display*" nil
-                                   "xterm"
-                                   "-e" shell-file-name
-                                   shell-command-switch
-                                   (mm-mailcap-command
-                                    method file (mm-handle-type handle)))
-                  (mm-handle-set-undisplayer handle (cons file buffer)))
+                     (if window-system
+                         (start-process "*display*" nil
+                                        mm-external-terminal-program
+                                        "-e" shell-file-name
+                                        shell-command-switch
+                                        (mm-mailcap-command
+                                         method file (mm-handle-type handle)))
+                       (require 'term)
+                       (require 'gnus-win)
+                       (set-buffer
+                        (setq buffer
+                              (make-term "display"
+                                         shell-file-name
+                                         nil
+                                         shell-command-switch
+                                         (mm-mailcap-command
+                                          method file 
+                                          (mm-handle-type handle)))))
+                       (term-mode)
+                       (term-char-mode)
+                       (set-process-sentinel 
+                        (get-buffer-process buffer)
+                        `(lambda (process state)
+                           (if (eq 'exit (process-status process))
+                               (gnus-configure-windows 
+                                ',gnus-current-window-configuration))))
+                       (gnus-configure-windows 'display-term))
+                  (mm-handle-set-external-undisplayer handle (cons file buffer)))
                 (message "Displaying %s..." (format method file))
                 'external)
                (copiousoutput
@@ -579,7 +686,7 @@ external if displayed external."
                                    shell-command-switch
                                    (mm-mailcap-command
                                     method file (mm-handle-type handle)))
-                  (mm-handle-set-undisplayer handle (cons file buffer)))
+                  (mm-handle-set-external-undisplayer handle (cons file buffer)))
                 (message "Displaying %s..." (format method file))
                 'external)))))))
 
@@ -661,7 +768,7 @@ external if displayed external."
         ((consp object)
          (ignore-errors (delete-file (car object)))
          (ignore-errors (delete-directory (file-name-directory (car object))))
-         (ignore-errors (kill-buffer (cdr object))))
+         (ignore-errors (and (cdr object) (kill-buffer (cdr object)))))
         ((bufferp object)
          (when (buffer-live-p object)
            (kill-buffer object)))))
@@ -678,6 +785,18 @@ external if displayed external."
     (when (string-match (car elem) type)
       (return elem))))
 
+(defun mm-automatic-display-p (handle)
+  "Say whether the user wants HANDLE to be displayed automatically."
+  (let ((methods mm-automatic-display)
+       (type (mm-handle-media-type handle))
+       method result)
+    (while (setq method (pop methods))
+      (when (and (not (mm-inline-override-p handle))
+                (string-match method type))
+       (setq result t
+             methods nil)))
+    result))
+
 (defun mm-inlinable-p (handle)
   "Say whether HANDLE can be displayed inline."
   (let ((alist mm-inline-media-tests)
@@ -691,28 +810,14 @@ external if displayed external."
       (pop alist))
     test))
 
-(defun mm-automatic-display-p (handle)
-  "Say whether the user wants HANDLE to be displayed automatically."
-  (let ((methods mm-automatic-display)
-       (type (mm-handle-media-type handle))
-       method result)
-    (while (setq method (pop methods))
-      (when (and (not (mm-inline-override-p handle))
-                (string-match method type)
-                (mm-inlinable-p handle))
-       (setq result t
-             methods nil)))
-    result))
-
 (defun mm-inlined-p (handle)
-  "Say whether the user wants HANDLE to be displayed automatically."
+  "Say whether the user wants HANDLE to be displayed inline."
   (let ((methods mm-inlined-types)
        (type (mm-handle-media-type handle))
        method result)
     (while (setq method (pop methods))
       (when (and (not (mm-inline-override-p handle))
-                (string-match method type)
-                (mm-inlinable-p handle))
+                (string-match method type))
        (setq result t
              methods nil)))
     result))
@@ -796,7 +901,34 @@ external if displayed external."
            (set-buffer cur)
            (insert-buffer-substring temp)))))))
 
-(defvar mm-default-directory nil)
+(defun mm-file-name-delete-whitespace (file-name)
+  "Remove all whitespace characters from FILE-NAME."
+  (while (string-match "\\s-+" file-name)
+    (setq file-name (replace-match "" t t file-name)))
+  file-name)
+
+(defun mm-file-name-trim-whitespace (file-name)
+  "Remove leading and trailing whitespace characters from FILE-NAME."
+  (when (string-match "\\`\\s-+" file-name)
+    (setq file-name (substring file-name (match-end 0))))
+  (when (string-match "\\s-+\\'" file-name)
+    (setq file-name (substring file-name 0 (match-beginning 0))))
+  file-name)
+
+(defun mm-file-name-collapse-whitespace (file-name)
+  "Collapse multiple whitespace characters in FILE-NAME."
+  (while (string-match "\\s-\\s-+" file-name)
+    (setq file-name (replace-match " " t t file-name)))
+  file-name)
+
+(defun mm-file-name-replace-whitespace (file-name)
+  "Replace whitespace characters in FILE-NAME with underscores.
+Set `mm-file-name-replace-whitespace' to any other string if you do not
+like underscores."
+  (let ((s (or mm-file-name-replace-whitespace "_")))
+    (while (string-match "\\s-" file-name)
+      (setq file-name (replace-match s t t file-name))))
+  file-name)
 
 (defun mm-save-part (handle)
   "Write HANDLE to a file."
@@ -805,7 +937,8 @@ external if displayed external."
                    (mm-handle-disposition handle) 'filename))
         file)
     (when filename
-      (setq filename (file-name-nondirectory filename)))
+      (setq filename (gnus-map-function mm-file-name-rewrite-functions
+                                       (file-name-nondirectory filename))))
     (setq file
          (read-file-name "Save MIME part to: "
                          (expand-file-name
@@ -838,7 +971,8 @@ external if displayed external."
          (read-string "Shell command on MIME part: " mm-last-shell-command)))
     (mm-with-unibyte-buffer
       (mm-insert-part handle)
-      (shell-command-on-region (point-min) (point-max) command nil))))
+      (let ((coding-system-for-write 'binary))
+       (shell-command-on-region (point-min) (point-max) command nil)))))
 
 (defun mm-interactively-view-part (handle)
   "Display HANDLE using METHOD."
@@ -1111,6 +1245,19 @@ If RECURSIVE, search recursively."
      (t nil))
     parts))
 
+(defun mm-multiple-handles (handles)
+   (and (listp (car handles)) 
+       (> (length handles) 1)))
+
+(defun mm-merge-handles (handles1 handles2) 
+  (append
+   (if (listp (car handles1)) 
+       handles1
+     (list handles1))
+   (if (listp (car handles2))
+       handles2
+     (list handles2))))
+
 (provide 'mm-decode)
 
 ;;; mm-decode.el ends here