--- /dev/null
+Return-Path: <markwalters1009@gmail.com>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 2052E429E5F\r
+ for <notmuch@notmuchmail.org>; Tue, 17 Jan 2012 15:44:29 -0800 (PST)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: 0.201\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=0.201 tagged_above=-999 required=5\r
+ tests=[DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1,\r
+ FREEMAIL_ENVFROM_END_DIGIT=1, FREEMAIL_FROM=0.001,\r
+ RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id 42+tg8PM2gTJ for <notmuch@notmuchmail.org>;\r
+ Tue, 17 Jan 2012 15:44:28 -0800 (PST)\r
+Received: from mail-we0-f181.google.com (mail-we0-f181.google.com\r
+ [74.125.82.181]) (using TLSv1 with cipher RC4-SHA (128/128 bits))\r
+ (No client certificate requested)\r
+ by olra.theworths.org (Postfix) with ESMTPS id 25645429E27\r
+ for <notmuch@notmuchmail.org>; Tue, 17 Jan 2012 15:44:28 -0800 (PST)\r
+Received: by werp13 with SMTP id p13so914905wer.26\r
+ for <notmuch@notmuchmail.org>; Tue, 17 Jan 2012 15:44:26 -0800 (PST)\r
+DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma;\r
+ h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references;\r
+ bh=byucpBqCV9QFDt6VbjaSco5/bHV0/Rhjg1zo4JAI/2Y=;\r
+ b=SHuvFSPsNV0BLvhYgFtBTWhUqIzeDMQNZlxniYSqwIhaL0if+zpV3DsclMKWOkmT+j\r
+ BuJE04ZbqZfhd3W20XN8LgDlveQ0iVn9U+x8GsiEYCWdeCwnQSJGaIJ3frHxny9QfnjA\r
+ eJ94swgRh2gcG8mlzE6qA+5g6y533UXgvWqk8=\r
+Received: by 10.216.133.71 with SMTP id p49mr306824wei.8.1326843866721;\r
+ Tue, 17 Jan 2012 15:44:26 -0800 (PST)\r
+Received: from localhost (94-192-233-223.zone6.bethere.co.uk.\r
+ [94.192.233.223]) by mx.google.com with ESMTPS id\r
+ d9sm19615881wiy.2.2012.01.17.15.44.25 (version=TLSv1/SSLv3 cipher=OTHER);\r
+ Tue, 17 Jan 2012 15:44:25 -0800 (PST)\r
+From: Mark Walters <markwalters1009@gmail.com>\r
+To: notmuch@notmuchmail.org\r
+Subject: [PATCH v3] Make buttons for attachments allow viewing as well as\r
+ saving\r
+Date: Tue, 17 Jan 2012 23:44:46 +0000\r
+Message-Id: <1326843886-18387-1-git-send-email-markwalters1009@gmail.com>\r
+X-Mailer: git-send-email 1.7.2.3\r
+In-Reply-To: <87lip5rj43.fsf@qmul.ac.uk>\r
+References: <87lip5rj43.fsf@qmul.ac.uk>\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Tue, 17 Jan 2012 23:44:29 -0000\r
+\r
+Define a keymap for attachment buttons to allow multiple actions.\r
+Define 3 possible actions:\r
+ save attachment: exactly as currently,\r
+ view attachment: uses mailcap entry,\r
+ view attachment with user chosen program\r
+\r
+Keymap on a button is: s for save, v for view and o for view with\r
+other program. Default (i.e. enter or mouse button) is save but this\r
+is configurable in notmuch customize.\r
+\r
+One implementation detail: the view attachment function forces all\r
+attachments to be "displayed" using mailcap even if emacs could\r
+display them itself. Thus, for example, text/html appears in a browser\r
+and text/plain asks whether to save (on a standard debian setup)\r
+---\r
+ emacs/notmuch-show.el | 106 ++++++++++++++++++++++++++++++++++++++-----------\r
+ 1 files changed, 82 insertions(+), 24 deletions(-)\r
+\r
+diff --git a/emacs/notmuch-show.el b/emacs/notmuch-show.el\r
+index 03c1f6b..0aaaf79 100644\r
+--- a/emacs/notmuch-show.el\r
++++ b/emacs/notmuch-show.el\r
+@@ -281,10 +281,21 @@ message at DEPTH in the current thread."\r
+ (run-hooks 'notmuch-show-markup-headers-hook)))))\r
+ \r
+ (define-button-type 'notmuch-show-part-button-type\r
+- 'action 'notmuch-show-part-button-action\r
++ 'action 'notmuch-show-part-button-default\r
++ 'keymap 'notmuch-show-part-button-map\r
+ 'follow-link t\r
+ 'face 'message-mml)\r
+ \r
++(defvar notmuch-show-part-button-map\r
++ (let ((map (make-sparse-keymap)))\r
++ (set-keymap-parent map button-map)\r
++ (define-key map "s" 'notmuch-show-part-button-save)\r
++ (define-key map "v" 'notmuch-show-part-button-view)\r
++ (define-key map "o" 'notmuch-show-part-button-interactively-view)\r
++ map)\r
++ "Submap for button commands")\r
++(fset 'notmuch-show-part-button-map notmuch-show-part-button-map)\r
++\r
+ (defun notmuch-show-insert-part-header (nth content-type declared-type &optional name comment)\r
+ (let ((button))\r
+ (setq button\r
+@@ -299,29 +310,48 @@ message at DEPTH in the current thread."\r
+ " ]")\r
+ :type 'notmuch-show-part-button-type\r
+ :notmuch-part nth\r
+- :notmuch-filename name))\r
++ :notmuch-filename name\r
++ :notmuch-content-type content-type))\r
+ (insert "\n")\r
+ ;; return button\r
+ button))\r
+ \r
+ ;; Functions handling particular MIME parts.\r
+ \r
+-(defun notmuch-show-save-part (message-id nth &optional filename)\r
+- (let ((process-crypto notmuch-show-process-crypto))\r
+- (with-temp-buffer\r
+- (setq notmuch-show-process-crypto process-crypto)\r
+- ;; Always acquires the part via `notmuch part', even if it is\r
+- ;; available in the JSON output.\r
+- (insert (notmuch-show-get-bodypart-internal message-id nth))\r
+- (let ((file (read-file-name\r
+- "Filename to save as: "\r
+- (or mailcap-download-directory "~/")\r
+- nil nil\r
+- filename)))\r
+- ;; Don't re-compress .gz & al. Arguably we should make\r
+- ;; `file-name-handler-alist' nil, but that would chop\r
+- ;; ange-ftp, which is reasonable to use here.\r
+- (mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t)))))\r
++(defmacro notmuch-with-temp-part-buffer (message-id nth &rest body)\r
++ (declare (indent 2))\r
++ (let ((process-crypto (make-symbol "process-crypto")))\r
++ `(let ((,process-crypto notmuch-show-process-crypto))\r
++ (with-temp-buffer\r
++ (setq notmuch-show-process-crypto ,process-crypto)\r
++ ;; Always acquires the part via `notmuch part', even if it is\r
++ ;; available in the JSON output.\r
++ (insert (notmuch-show-get-bodypart-internal ,message-id ,nth))\r
++ ,@body))))\r
++\r
++(defun notmuch-show-save-part (message-id nth &optional filename content-type)\r
++ (notmuch-with-temp-part-buffer message-id nth\r
++ (let ((file (read-file-name\r
++ "Filename to save as: "\r
++ (or mailcap-download-directory "~/")\r
++ nil nil\r
++ filename)))\r
++ ;; Don't re-compress .gz & al. Arguably we should make\r
++ ;; `file-name-handler-alist' nil, but that would chop\r
++ ;; ange-ftp, which is reasonable to use here.\r
++ (mm-write-region (point-min) (point-max) file nil nil nil 'no-conversion t))))\r
++\r
++(defun notmuch-show-view-part (message-id nth &optional filename content-type )\r
++ (notmuch-with-temp-part-buffer message-id nth\r
++ ;; set mm-inlined-types to nil to force an external viewer\r
++ (let ((handle (mm-make-handle (current-buffer) (list content-type)))\r
++ (mm-inlined-types nil))\r
++ (mm-display-part handle t))))\r
++\r
++(defun notmuch-show-interactively-view-part (message-id nth &optional filename content-type)\r
++ (notmuch-with-temp-part-buffer message-id nth\r
++ (let ((handle (mm-make-handle (current-buffer) (list content-type))))\r
++ (mm-interactively-view-part handle))))\r
+ \r
+ (defun notmuch-show-mm-display-part-inline (msg part nth content-type)\r
+ "Use the mm-decode/mm-view functions to display a part in the\r
+@@ -1502,12 +1532,40 @@ buffer."\r
+ \r
+ ;; Commands typically bound to buttons.\r
+ \r
+-(defun notmuch-show-part-button-action (button)\r
+- (let ((nth (button-get button :notmuch-part)))\r
+- (if nth\r
+- (notmuch-show-save-part (notmuch-show-get-message-id) nth\r
+- (button-get button :notmuch-filename))\r
+- (message "Not a valid part (is it a fake part?)."))))\r
++(defcustom notmuch-show-part-button-default-action 'notmuch-show-save-part\r
++ "Default part header button action (on ENTER or mouse click)."\r
++ :group 'notmuch\r
++ :type '(choice (const :tag "Save part"\r
++ notmuch-show-save-part)\r
++ (const :tag "View part"\r
++ notmuch-show-view-part)\r
++ (const :tag "View interactively"\r
++ notmuch-show-interactively-view-part)))\r
++\r
++(defun notmuch-show-part-button-default (&optional button)\r
++ (interactive)\r
++ (notmuch-show-part-button-internal button notmuch-show-part-button-default-action))\r
++\r
++(defun notmuch-show-part-button-save (&optional button)\r
++ (interactive)\r
++ (notmuch-show-part-button-internal button #'notmuch-show-save-part))\r
++\r
++(defun notmuch-show-part-button-view (&optional button)\r
++ (interactive)\r
++ (notmuch-show-part-button-internal button #'notmuch-show-view-part))\r
++\r
++(defun notmuch-show-part-button-interactively-view (&optional button)\r
++ (interactive)\r
++ (notmuch-show-part-button-internal button #'notmuch-show-interactively-view-part))\r
++\r
++(defun notmuch-show-part-button-internal (button handler)\r
++ (let ((button (or button (button-at (point)))))\r
++ (if button\r
++ (let ((nth (button-get button :notmuch-part)))\r
++ (if nth\r
++ (funcall handler (notmuch-show-get-message-id) nth\r
++ (button-get button :notmuch-filename)\r
++ (button-get button :notmuch-content-type)))))))\r
+ \r
+ ;;\r
+ \r
+-- \r
+1.7.2.3\r
+\r