3a5d678f8ee1df79619188f750bf6f9ef2b9100e
[claws.git] / src / mimeview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <gdk/gdkkeysyms.h>
28 #include <gtk/gtknotebook.h>
29 #include <gtk/gtkscrolledwindow.h>
30 #include <gtk/gtkctree.h>
31 #include <gtk/gtkvbox.h>
32 #include <gtk/gtkvpaned.h>
33 #include <gtk/gtktext.h>
34 #include <gtk/gtksignal.h>
35 #include <gtk/gtkmenu.h>
36 #include <gtk/gtkdnd.h>
37 #include <gtk/gtkselection.h>
38 #include <stdio.h>
39 #ifdef HAVE_FNMATCH_H
40 #include <fnmatch.h>
41 #endif
42
43 #include "intl.h"
44 #include "main.h"
45 #include "mimeview.h"
46 #include "textview.h"
47 #include "procmime.h"
48 #include "summaryview.h"
49 #include "menu.h"
50 #include "filesel.h"
51 #include "alertpanel.h"
52 #include "inputdialog.h"
53 #include "utils.h"
54 #include "gtkutils.h"
55 #include "prefs_common.h"
56 #include "rfc2015.h"
57
58 typedef enum
59 {
60         COL_MIMETYPE = 0,
61         COL_SIZE     = 1,
62         COL_NAME     = 2
63 } MimeViewColumnPos;
64
65 #define N_MIMEVIEW_COLS 3
66
67 static void mimeview_set_multipart_tree         (MimeView       *mimeview,
68                                                  MimeInfo       *mimeinfo,
69                                                  GtkCTreeNode   *parent);
70 static GtkCTreeNode *mimeview_append_part       (MimeView       *mimeview,
71                                                  MimeInfo       *partinfo,
72                                                  GtkCTreeNode   *parent);
73 static void mimeview_show_message_part          (MimeView       *mimeview,
74                                                  MimeInfo       *partinfo);
75 static void mimeview_change_view_type           (MimeView       *mimeview,
76                                                  MimeViewType    type);
77 static void mimeview_clear                      (MimeView       *mimeview);
78
79 static void mimeview_selected           (GtkCTree       *ctree,
80                                          GtkCTreeNode   *node,
81                                          gint            column,
82                                          MimeView       *mimeview);
83 static void mimeview_start_drag         (GtkWidget      *widget,
84                                          gint            button,
85                                          GdkEvent       *event,
86                                          MimeView       *mimeview);
87 static gint mimeview_button_pressed     (GtkWidget      *widget,
88                                          GdkEventButton *event,
89                                          MimeView       *mimeview);
90 static gint mimeview_key_pressed        (GtkWidget      *widget,
91                                          GdkEventKey    *event,
92                                          MimeView       *mimeview);
93
94 static void mimeview_drag_data_get      (GtkWidget        *widget,
95                                          GdkDragContext   *drag_context,
96                                          GtkSelectionData *selection_data,
97                                          guint             info,
98                                          guint             time,
99                                          MimeView         *mimeview);
100
101 static void mimeview_display_as_text    (MimeView       *mimeview);
102 static void mimeview_save_as            (MimeView       *mimeview);
103 static void mimeview_save_all           (MimeView       *mimeview);
104 static void mimeview_launch             (MimeView       *mimeview);
105 static void mimeview_open_with          (MimeView       *mimeview);
106 static void mimeview_view_file          (const gchar    *filename,
107                                          MimeInfo       *partinfo,
108                                          const gchar    *cmdline);
109
110 static GtkItemFactoryEntry mimeview_popup_entries[] =
111 {
112         {N_("/_Open"),            NULL, mimeview_launch,          0, NULL},
113         {N_("/Open _with..."),    NULL, mimeview_open_with,       0, NULL},
114         {N_("/_Display as text"), NULL, mimeview_display_as_text, 0, NULL},
115         {N_("/_Save as..."),      NULL, mimeview_save_as,         0, NULL},
116         {N_("/Save _all..."),     NULL, mimeview_save_all,        0, NULL}
117 #if USE_GPGME
118         ,
119         {N_("/_Check signature"), NULL, mimeview_check_signature, 0, NULL}
120 #endif
121 };
122
123 static GtkTargetEntry mimeview_mime_types[] =
124 {
125         {"text/uri-list", 0, 0}
126 };
127
128 GSList *mimeviewer_factories;
129 GSList *mimeviews;
130
131 MimeView *mimeview_create(void)
132 {
133         MimeView *mimeview;
134
135         GtkWidget *notebook;
136         GtkWidget *vbox;
137         GtkWidget *paned;
138         GtkWidget *scrolledwin;
139         GtkWidget *ctree;
140         GtkWidget *mime_vbox;
141         GtkWidget *popupmenu;
142         GtkItemFactory *popupfactory;
143         gchar *titles[N_MIMEVIEW_COLS];
144         gint n_entries;
145         gint i;
146
147         debug_print("Creating MIME view...\n");
148         mimeview = g_new0(MimeView, 1);
149
150         titles[COL_MIMETYPE] = _("MIME Type");
151         titles[COL_SIZE]     = _("Size");
152         titles[COL_NAME]     = _("Name");
153
154         notebook = gtk_notebook_new();
155         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
156
157         vbox = gtk_vbox_new(FALSE, 0);
158         gtk_container_add(GTK_CONTAINER(notebook), vbox);
159         gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(notebook), vbox,
160                                         _("Text"));
161
162         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
163         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
164                                        GTK_POLICY_AUTOMATIC,
165                                        GTK_POLICY_ALWAYS);
166         gtk_widget_set_usize(scrolledwin, -1, 80);
167
168         ctree = gtk_sctree_new_with_titles(N_MIMEVIEW_COLS, 0, titles);
169         gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE);
170         gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_NONE);
171         gtk_clist_set_column_justification(GTK_CLIST(ctree), COL_SIZE,
172                                            GTK_JUSTIFY_RIGHT);
173         gtk_clist_set_column_width(GTK_CLIST(ctree), COL_MIMETYPE, 240);
174         gtk_clist_set_column_width(GTK_CLIST(ctree), COL_SIZE, 64);
175         for (i = 0; i < N_MIMEVIEW_COLS; i++)
176                 GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button,
177                                        GTK_CAN_FOCUS);
178         gtk_container_add(GTK_CONTAINER(scrolledwin), ctree);
179
180         gtk_signal_connect(GTK_OBJECT(ctree), "tree_select_row",
181                            GTK_SIGNAL_FUNC(mimeview_selected), mimeview);
182         gtk_signal_connect(GTK_OBJECT(ctree), "button_press_event",
183                            GTK_SIGNAL_FUNC(mimeview_button_pressed), mimeview);
184         gtk_signal_connect(GTK_OBJECT(ctree), "key_press_event",
185                            GTK_SIGNAL_FUNC(mimeview_key_pressed), mimeview);
186         gtk_signal_connect(GTK_OBJECT (ctree),"start_drag",
187                            GTK_SIGNAL_FUNC (mimeview_start_drag), mimeview);
188         gtk_signal_connect(GTK_OBJECT(ctree), "drag_data_get",
189                            GTK_SIGNAL_FUNC(mimeview_drag_data_get), mimeview);
190     
191         mime_vbox = gtk_vbox_new(FALSE, 0);
192
193         paned = gtk_vpaned_new();
194         gtk_paned_add1(GTK_PANED(paned), scrolledwin);
195         gtk_paned_add2(GTK_PANED(paned), mime_vbox);
196         gtk_container_add(GTK_CONTAINER(notebook), paned);
197         gtk_notebook_set_tab_label_text(GTK_NOTEBOOK(notebook), paned,
198                                         _("Attachments"));
199
200         gtk_widget_show_all(notebook);
201
202         gtk_notebook_set_page(GTK_NOTEBOOK(notebook), 0);
203
204         n_entries = sizeof(mimeview_popup_entries) /
205                 sizeof(mimeview_popup_entries[0]);
206         popupmenu = menu_create_items(mimeview_popup_entries, n_entries,
207                                       "<MimeView>", &popupfactory, mimeview);
208
209         mimeview->notebook     = notebook;
210         mimeview->vbox         = vbox;
211         mimeview->paned        = paned;
212         mimeview->scrolledwin  = scrolledwin;
213         mimeview->ctree        = ctree;
214         mimeview->mime_vbox    = mime_vbox;
215         mimeview->popupmenu    = popupmenu;
216         mimeview->popupfactory = popupfactory;
217         mimeview->type         = -1;
218
219         mimeviews = g_slist_prepend(mimeviews, mimeview);
220
221         return mimeview;
222 }
223
224 void mimeview_init(MimeView *mimeview)
225 {
226 }
227
228 /* 
229  * Check whether the message is OpenPGP signed
230  */
231 #if USE_GPGME
232 static gboolean mimeview_is_signed(MimeView *mimeview)
233 {
234         MimeInfo *partinfo = NULL;
235
236         debug_print("mimeview_is signed of %p\n", mimeview);
237
238         if (!mimeview) return FALSE;
239         if (!mimeview->opened) return FALSE;
240
241         debug_print("mimeview_is_signed: open\n" );
242
243         if (!mimeview->file) return FALSE;
244
245         debug_print("mimeview_is_signed: file\n" );
246
247         partinfo = gtk_ctree_node_get_row_data
248                 (GTK_CTREE(mimeview->ctree), mimeview->opened);
249         g_return_val_if_fail(partinfo != NULL, FALSE);
250
251         /* walk the tree and see whether there is a signature somewhere */
252         do {
253                 if (rfc2015_has_signature(partinfo))
254                         return TRUE;
255         } while ((partinfo = partinfo->parent) != NULL);
256
257         debug_print("mimeview_is_signed: FALSE\n" );
258
259         return FALSE;
260 }
261
262 static void set_unchecked_signature(MimeInfo *mimeinfo)
263 {
264         MimeInfo *sig_partinfo;
265
266         sig_partinfo = rfc2015_find_signature(mimeinfo);
267         if (sig_partinfo == NULL) return;
268
269         g_free(sig_partinfo->sigstatus);
270         sig_partinfo->sigstatus =
271                 g_strdup(_("Right-click here to verify the signature"));
272
273         g_free(sig_partinfo->sigstatus_full);
274         sig_partinfo->sigstatus_full = NULL;
275 }
276 #endif /* USE_GPGME */
277
278 void mimeview_show_message(MimeView *mimeview, MimeInfo *mimeinfo,
279                            const gchar *file)
280 {
281         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
282         GtkCTreeNode *node;
283         FILE *fp;
284
285         mimeview_clear(mimeview);
286         textview_clear(mimeview->messageview->textview);
287
288         g_return_if_fail(file != NULL);
289         g_return_if_fail(mimeinfo != NULL);
290
291         mimeview->mimeinfo = mimeinfo;
292
293         mimeview->file = g_strdup(file);
294
295 #if USE_GPGME
296         if (prefs_common.auto_check_signatures && gpg_started) {
297                 if ((fp = fopen(file, "rb")) == NULL) {
298                         FILE_OP_ERROR(file, "fopen");
299                         return;
300                 }
301                 rfc2015_check_signature(mimeinfo, fp);
302                 fclose(fp);
303         } else
304                 set_unchecked_signature(mimeinfo);
305 #endif
306
307         gtk_signal_handler_block_by_func(GTK_OBJECT(ctree), mimeview_selected,
308                                          mimeview);
309
310         mimeview_set_multipart_tree(mimeview, mimeinfo, NULL);
311
312         gtk_signal_handler_unblock_by_func(GTK_OBJECT(ctree),
313                                            mimeview_selected, mimeview);
314
315         /* search first text part */
316         for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
317              node != NULL; node = GTK_CTREE_NODE_NEXT(node)) {
318                 MimeInfo *partinfo;
319
320                 partinfo = gtk_ctree_node_get_row_data(ctree, node);
321                 if (partinfo &&
322                     (partinfo->mime_type == MIME_TEXT ||
323                      partinfo->mime_type == MIME_TEXT_HTML))
324                         break;
325         }
326         textview_show_message(mimeview->messageview->textview, mimeinfo, file);
327
328         if (!node)
329                 node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
330
331         if (node) {
332                 gtk_ctree_select(ctree, node);
333                 gtkut_ctree_set_focus_row(ctree, node);
334                 gtk_widget_grab_focus(mimeview->ctree);
335         }
336 }
337
338 void mimeview_destroy(MimeView *mimeview)
339 {
340         GSList *cur;
341         
342         for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
343                 MimeViewer *viewer = (MimeViewer *) cur->data;
344                 viewer->destroy_viewer(viewer);
345         }
346         g_slist_free(mimeview->viewers);
347
348         procmime_mimeinfo_free_all(mimeview->mimeinfo);
349         g_free(mimeview->file);
350         g_free(mimeview);
351
352         mimeviews = g_slist_remove(mimeviews, mimeview);
353 }
354
355 MimeInfo *mimeview_get_selected_part(MimeView *mimeview)
356 {
357         if (gtk_notebook_get_current_page
358                 (GTK_NOTEBOOK(mimeview->notebook)) == 0)
359                 return NULL;
360
361         return gtk_ctree_node_get_row_data
362                 (GTK_CTREE(mimeview->ctree), mimeview->opened);
363 }
364
365 static void mimeview_set_multipart_tree(MimeView *mimeview,
366                                         MimeInfo *mimeinfo,
367                                         GtkCTreeNode *parent)
368 {
369         GtkCTreeNode *node;
370
371         g_return_if_fail(mimeinfo != NULL);
372
373         if (mimeinfo->children)
374                 mimeinfo = mimeinfo->children;
375
376         while (mimeinfo != NULL) {
377                 node = mimeview_append_part(mimeview, mimeinfo, parent);
378
379                 if (mimeinfo->children)
380                         mimeview_set_multipart_tree(mimeview, mimeinfo, node);
381                 else if (mimeinfo->sub &&
382                          mimeinfo->sub->mime_type != MIME_TEXT &&
383                          mimeinfo->sub->mime_type != MIME_TEXT_HTML)
384                         mimeview_set_multipart_tree(mimeview, mimeinfo->sub,
385                                                     node);
386                 mimeinfo = mimeinfo->next;
387         }
388 }
389
390 static gchar *get_part_name(MimeInfo *partinfo)
391 {
392 #if USE_GPGME
393         if (partinfo->sigstatus)
394                 return partinfo->sigstatus;
395         else
396 #endif
397         if (partinfo->name)
398                 return partinfo->name;
399         else if (partinfo->filename)
400                 return partinfo->filename;
401         else if (partinfo->description)
402                 return partinfo->description;
403         else
404                 return "";
405 }
406
407 static gchar *get_part_description(MimeInfo *partinfo)
408 {
409         if (partinfo->description)
410                 return partinfo->description;
411         else if (partinfo->name)
412                 return partinfo->name;
413         else if (partinfo->filename)
414                 return partinfo->filename;
415         else
416                 return "";
417 }
418
419 static GtkCTreeNode *mimeview_append_part(MimeView *mimeview,
420                                           MimeInfo *partinfo,
421                                           GtkCTreeNode *parent)
422 {
423         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
424         GtkCTreeNode *node;
425         gchar *str[N_MIMEVIEW_COLS];
426
427         str[COL_MIMETYPE] =
428                 partinfo->content_type ? partinfo->content_type : "";
429         str[COL_SIZE] = to_human_readable(partinfo->size);
430         if (prefs_common.attach_desc)
431                 str[COL_NAME] = get_part_description(partinfo);
432         else
433                 str[COL_NAME] = get_part_name(partinfo);
434
435         node = gtk_ctree_insert_node(ctree, parent, NULL, str, 0,
436                                      NULL, NULL, NULL, NULL,
437                                      FALSE, TRUE);
438         gtk_ctree_node_set_row_data(ctree, node, partinfo);
439
440         return node;
441 }
442
443 static void mimeview_show_message_part(MimeView *mimeview, MimeInfo *partinfo)
444 {
445         FILE *fp;
446         const gchar *fname;
447 #if USE_GPGME
448         MimeInfo *pi;
449 #endif
450
451         if (!partinfo) return;
452
453 #if USE_GPGME
454         for (pi = partinfo; pi && !pi->plaintextfile ; pi = pi->parent)
455                 ;
456         fname = pi ? pi->plaintextfile : mimeview->file;
457 #else
458         fname = mimeview->file;
459 #endif /* USE_GPGME */
460         if (!fname) return;
461
462         if ((fp = fopen(fname, "rb")) == NULL) {
463                 FILE_OP_ERROR(fname, "fopen");
464                 return;
465         }
466
467         if (fseek(fp, partinfo->fpos, SEEK_SET) < 0) {
468                 FILE_OP_ERROR(mimeview->file, "fseek");
469                 fclose(fp);
470                 return;
471         }
472
473         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
474         textview_show_part(mimeview->textview, partinfo, fp);
475
476         fclose(fp);
477 }
478
479 static MimeViewer *get_viewer_for_content_type(MimeView *mimeview, const gchar *content_type)
480 {
481         GSList *cur;
482         MimeViewerFactory *factory = NULL;
483         MimeViewer *viewer = NULL;
484
485 /*
486  * FNM_CASEFOLD is a GNU extension
487  * if its not defined copy the string to the stack and
488  * convert the copy to lower case
489  */
490 #ifndef FNM_CASEFOLD
491 #define FNM_CASEFOLD 0
492         Xstrdup_a(content_type, content_type, return NULL);
493         g_strdown((gchar *)content_type);
494 #endif
495         
496         for (cur = mimeviewer_factories; cur != NULL; cur = g_slist_next(cur)) {
497                 MimeViewerFactory *curfactory = cur->data;
498                 gint i = 0;
499
500                 while (curfactory->content_types[i] != NULL) {
501                         debug_print("%s\n", curfactory->content_types[i]);
502                         if(!fnmatch(curfactory->content_types[i], content_type, FNM_CASEFOLD)) {
503                                 factory = curfactory;
504                                 break;
505                         }
506                         i++;
507                 }
508                 if (factory != NULL)
509                         break;
510         }
511         if (factory == NULL)
512                 return NULL;
513
514         for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
515                 MimeViewer *curviewer = cur->data;
516                 
517                 if (curviewer->factory == factory)
518                         return curviewer;
519         }
520         viewer = factory->create_viewer();
521         mimeview->viewers = g_slist_append(mimeview->viewers, viewer);
522
523         return viewer;
524 }
525
526 static MimeViewer *get_viewer_for_mimeinfo(MimeView *mimeview, MimeInfo *partinfo)
527 {
528         gchar *content_type = NULL;
529         MimeViewer *viewer = NULL;
530
531         if ((partinfo->mime_type == MIME_APPLICATION_OCTET_STREAM) &&
532             (partinfo->name != NULL)) {
533                 content_type = procmime_get_mime_type(partinfo->name);
534         } else {
535                 content_type = g_strdup(partinfo->content_type);
536         }
537
538         if (content_type != NULL) {
539                 viewer = get_viewer_for_content_type(mimeview, content_type);
540                 g_free(content_type);
541         }
542
543         return viewer;
544 }
545
546 static gboolean mimeview_show_part(MimeView *mimeview, MimeInfo *partinfo)
547 {
548         MimeViewer *viewer;
549         
550         viewer = get_viewer_for_mimeinfo(mimeview, partinfo);
551         if (viewer == NULL) {
552                 if (mimeview->mimeviewer != NULL)
553                         mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
554                 mimeview->mimeviewer = NULL;
555                 return FALSE;
556         }
557
558         if (mimeview->mimeviewer != viewer) {
559                 if (mimeview->mimeviewer != NULL)
560                         mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
561                 mimeview->mimeviewer = viewer;
562                 mimeview_change_view_type(mimeview, MIMEVIEW_VIEWER);
563         }
564         viewer->show_mimepart(viewer, mimeview->file, partinfo);
565
566         return TRUE;
567 }
568
569 static void mimeview_change_view_type(MimeView *mimeview, MimeViewType type)
570 {
571         TextView  *textview  = mimeview->textview;
572         GList *children;
573
574         if ((mimeview->type != MIMEVIEW_VIEWER) && 
575             (mimeview->type == type)) return;
576
577         children = gtk_container_children(GTK_CONTAINER(mimeview->mime_vbox));
578         if (children) {
579                 gtkut_container_remove(GTK_CONTAINER(mimeview->mime_vbox),
580                                        GTK_WIDGET(children->data));
581                 g_list_free(children);
582         }
583
584         switch (type) {
585         case MIMEVIEW_TEXT:
586                 gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox),
587                                   GTK_WIDGET_PTR(textview));
588                 break;
589         case MIMEVIEW_VIEWER:
590                 gtk_container_add(GTK_CONTAINER(mimeview->mime_vbox),
591                                   GTK_WIDGET(mimeview->mimeviewer->get_widget(mimeview->mimeviewer)));
592                 break;
593         default:
594                 return;
595         }
596
597         mimeview->type = type;
598 }
599
600 static void mimeview_clear(MimeView *mimeview)
601 {
602         GtkCList *clist = GTK_CLIST(mimeview->ctree);
603
604         procmime_mimeinfo_free_all(mimeview->mimeinfo);
605         mimeview->mimeinfo = NULL;
606
607         gtk_clist_clear(clist);
608         textview_clear(mimeview->textview);
609         if (mimeview->mimeviewer != NULL)
610                 mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
611
612         mimeview->opened = NULL;
613
614         g_free(mimeview->file);
615         mimeview->file = NULL;
616
617         /* gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->notebook), 0); */
618 }
619
620 static void mimeview_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
621                               MimeView *mimeview)
622 {
623         MimeInfo *partinfo;
624
625         if (mimeview->opened == node) return;
626         mimeview->opened = node;
627         gtk_ctree_node_moveto(ctree, node, -1, 0.5, 0);
628
629         partinfo = gtk_ctree_node_get_row_data(ctree, node);
630         if (!partinfo) return;
631
632         /* ungrab the mouse event */
633         if (GTK_WIDGET_HAS_GRAB(ctree)) {
634                 gtk_grab_remove(GTK_WIDGET(ctree));
635                 if (gdk_pointer_is_grabbed())
636                         gdk_pointer_ungrab(GDK_CURRENT_TIME);
637         }
638         
639         mimeview->textview->default_text = FALSE;
640         
641         if (!mimeview_show_part(mimeview, partinfo)) {
642                 switch (partinfo->mime_type) {
643                 case MIME_TEXT:
644                 case MIME_TEXT_HTML:
645                 case MIME_TEXT_ENRICHED:
646                 case MIME_MESSAGE_RFC822:
647                 case MIME_MULTIPART:
648                         mimeview_show_message_part(mimeview, partinfo);
649                 
650                         break;
651                 default:
652                         mimeview->textview->default_text = TRUE;        
653                         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
654 #if USE_GPGME
655                         if (g_strcasecmp(partinfo->content_type,
656                                          "application/pgp-signature") == 0)
657                                 textview_show_signature_part(mimeview->textview,
658                                                              partinfo);
659                         else
660 #endif
661                                 textview_show_mime_part(mimeview->textview, partinfo);
662                         break;
663                 }
664         }
665 }
666
667 static void mimeview_start_drag(GtkWidget *widget, gint button,
668                                 GdkEvent *event, MimeView *mimeview)
669 {
670         GtkTargetList *list;
671         GdkDragContext *context;
672         MimeInfo *partinfo;
673
674         g_return_if_fail(mimeview != NULL);
675
676         partinfo = mimeview_get_selected_part(mimeview);
677         if (partinfo->filename == NULL && partinfo->name == NULL) return;
678
679         list = gtk_target_list_new(mimeview_mime_types, 1);
680         context = gtk_drag_begin(widget, list,
681                                  GDK_ACTION_COPY, button, event);
682         gtk_drag_set_icon_default(context);
683 }
684
685 static gint mimeview_button_pressed(GtkWidget *widget, GdkEventButton *event,
686                                     MimeView *mimeview)
687 {
688         GtkCList *clist = GTK_CLIST(widget);
689         MimeInfo *partinfo;
690         gint row, column;
691
692         if (!event) return FALSE;
693
694         if (event->button == 2 || event->button == 3) {
695                 if (!gtk_clist_get_selection_info(clist, event->x, event->y,
696                                                   &row, &column))
697                         return FALSE;
698                 gtk_clist_unselect_all(clist);
699                 gtk_clist_select_row(clist, row, column);
700                 gtkut_clist_set_focus_row(clist, row);
701         }
702
703         if (event->button == 2 ||
704             (event->button == 1 && event->type == GDK_2BUTTON_PRESS)) {
705                 /* call external program for image, audio or html */
706                 mimeview_launch(mimeview);
707         } else if (event->button == 3) {
708                 partinfo = mimeview_get_selected_part(mimeview);
709                 if (partinfo && (partinfo->mime_type == MIME_TEXT ||
710                                  partinfo->mime_type == MIME_TEXT_HTML ||
711                                  partinfo->mime_type == MIME_TEXT_ENRICHED ||
712                                  partinfo->mime_type == MIME_MESSAGE_RFC822 ||
713                                  partinfo->mime_type == MIME_IMAGE ||
714                                  partinfo->mime_type == MIME_MULTIPART))
715                         menu_set_sensitive(mimeview->popupfactory,
716                                            "/Display as text", FALSE);
717                 else
718                         menu_set_sensitive(mimeview->popupfactory,
719                                            "/Display as text", TRUE);
720                 if (partinfo &&
721                     partinfo->mime_type == MIME_APPLICATION_OCTET_STREAM)
722                         menu_set_sensitive(mimeview->popupfactory,
723                                            "/Open", FALSE);
724                 else
725                         menu_set_sensitive(mimeview->popupfactory,
726                                            "/Open", TRUE);
727
728 #if USE_GPGME
729                 menu_set_sensitive(mimeview->popupfactory,
730                                    "/Check signature",
731                                    mimeview_is_signed(mimeview));
732 #endif
733
734                 gtk_menu_popup(GTK_MENU(mimeview->popupmenu),
735                                NULL, NULL, NULL, NULL,
736                                event->button, event->time);
737         }
738
739         return TRUE;
740 }
741
742 void mimeview_pass_key_press_event(MimeView *mimeview, GdkEventKey *event)
743 {
744         mimeview_key_pressed(mimeview->ctree, event, mimeview);
745 }
746
747 #define BREAK_ON_MODIFIER_KEY() \
748         if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break
749
750 #define KEY_PRESS_EVENT_STOP() \
751         if (gtk_signal_n_emissions_by_name \
752                 (GTK_OBJECT(ctree), "key_press_event") > 0) { \
753                 gtk_signal_emit_stop_by_name(GTK_OBJECT(ctree), \
754                                              "key_press_event"); \
755         }
756
757 static gint mimeview_key_pressed(GtkWidget *widget, GdkEventKey *event,
758                                  MimeView *mimeview)
759 {
760         SummaryView *summaryview;
761         GtkCTree *ctree = GTK_CTREE(widget);
762         GtkCTreeNode *node;
763
764         if (!event) return FALSE;
765         if (!mimeview->opened) return FALSE;
766
767         switch (event->keyval) {
768         case GDK_space:
769                 if (textview_scroll_page(mimeview->textview, FALSE))
770                         return TRUE;
771
772                 node = GTK_CTREE_NODE_NEXT(mimeview->opened);
773                 if (node) {
774                         gtk_sctree_unselect_all(GTK_SCTREE(ctree));
775                         gtk_sctree_select(GTK_SCTREE(ctree), node);
776                         return TRUE;
777                 }
778                 break;
779         case GDK_BackSpace:
780                 textview_scroll_page(mimeview->textview, TRUE);
781                 return TRUE;
782         case GDK_Return:
783                 textview_scroll_one_line(mimeview->textview,
784                                          (event->state & GDK_MOD1_MASK) != 0);
785                 return TRUE;
786         case GDK_n:
787         case GDK_N:
788                 BREAK_ON_MODIFIER_KEY();
789                 if (!GTK_CTREE_NODE_NEXT(mimeview->opened)) break;
790                 KEY_PRESS_EVENT_STOP();
791
792                 gtk_signal_emit_by_name(GTK_OBJECT(ctree), "scroll_vertical",
793                                         GTK_SCROLL_STEP_FORWARD, 0.0);
794                 return TRUE;
795         case GDK_p:
796         case GDK_P:
797                 BREAK_ON_MODIFIER_KEY();
798                 if (!GTK_CTREE_NODE_PREV(mimeview->opened)) break;
799                 KEY_PRESS_EVENT_STOP();
800
801                 gtk_signal_emit_by_name(GTK_OBJECT(ctree), "scroll_vertical",
802                                         GTK_SCROLL_STEP_BACKWARD, 0.0);
803                 return TRUE;
804         case GDK_y:
805                 BREAK_ON_MODIFIER_KEY();
806                 KEY_PRESS_EVENT_STOP();
807                 mimeview_save_as(mimeview);
808                 return TRUE;
809         case GDK_t:
810                 BREAK_ON_MODIFIER_KEY();
811                 KEY_PRESS_EVENT_STOP();
812                 mimeview_display_as_text(mimeview);
813                 return TRUE;    
814         case GDK_l:
815                 BREAK_ON_MODIFIER_KEY();
816                 KEY_PRESS_EVENT_STOP();
817                 mimeview_launch(mimeview);
818                 return TRUE;
819         default:
820                 break;
821         }
822
823         if (!mimeview->messageview->mainwin) return FALSE;
824         summaryview = mimeview->messageview->mainwin->summaryview;
825         summary_pass_key_press_event(summaryview, event);
826         return TRUE;
827 }
828
829 static void mimeview_drag_data_get(GtkWidget        *widget,
830                                    GdkDragContext   *drag_context,
831                                    GtkSelectionData *selection_data,
832                                    guint             info,
833                                    guint             time,
834                                    MimeView         *mimeview)
835 {
836         gchar *filename, *uriname;
837         MimeInfo *partinfo;
838
839         if (!mimeview->opened) return;
840         if (!mimeview->file) return;
841
842         partinfo = mimeview_get_selected_part(mimeview);
843         if (!partinfo) return;
844         if (!partinfo->filename && !partinfo->name) return;
845
846         filename = partinfo->filename ? partinfo->filename : partinfo->name;
847         filename = g_basename(filename);
848         if (*filename == '\0') return;
849
850         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
851                                filename, NULL);
852
853         if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
854                 alertpanel_error
855                         (_("Can't save the part of multipart message."));
856
857         uriname = g_strconcat("file:/", filename, NULL);
858         gtk_selection_data_set(selection_data, selection_data->target, 8,
859                                uriname, strlen(uriname));
860
861         g_free(uriname);
862         g_free(filename);
863 }
864
865 static void mimeview_save_all(MimeView *mimeview)
866 {
867         gchar *dirname;
868         gchar *defname = NULL;
869         MimeInfo *partinfo;
870         MimeInfo *attachment;
871         gchar buf[1024];
872
873         if (!mimeview->opened) return;
874         if (!mimeview->file) return;
875
876         partinfo = mimeview_get_selected_part(mimeview);
877         g_return_if_fail(partinfo != NULL);
878
879         dirname = filesel_select_file(_("Save as"), defname);
880         if (!dirname) return;
881
882         /* return to first children */
883         if (!partinfo->parent->children) return;  /* multipart container? */
884         attachment = partinfo->parent->children->next;
885         /* for each attachment, extract it in the selected dir. */
886         while (attachment != NULL) {
887                 static guint subst_cnt = 1;
888                 gchar *attachdir;
889                 gchar *attachname = g_strdup(get_part_name(attachment));
890                 AlertValue aval = G_ALERTDEFAULT;
891                 gchar *res;
892
893                 if (!attachname || !strlen(attachname))
894                         attachname = g_strdup_printf("noname.%d",subst_cnt++);
895                 subst_chars(attachname, ":?*&|<>\t\r\n", '_');
896                 g_snprintf(buf, sizeof(buf), "%s%s",
897                            dirname,
898                            (attachname[0] == G_DIR_SEPARATOR)
899                            ? &attachname[1]
900                            : attachname);
901                 subst_chars(buf, "/\\", G_DIR_SEPARATOR);
902                 attachdir = g_dirname(buf);
903                 make_dir_hier(attachdir);
904                 g_free(attachdir);
905
906                 if (is_file_exist(buf)) {
907                         res = g_strdup_printf(_("Overwrite existing file '%s'?"),
908                                               attachname);
909                         aval = alertpanel(_("Overwrite"), res, _("OK"), 
910                                           _("Cancel"), NULL);
911                         g_free(res);                                      
912                 }
913                 g_free(attachname);
914
915                 if ((G_ALERTDEFAULT != aval) || (procmime_get_part(buf, mimeview->file, attachment) < 0))
916                         alertpanel_error(_("Can't save the part of multipart message."));
917                 attachment = attachment->next;
918         }
919 }
920
921 static void mimeview_display_as_text(MimeView *mimeview)
922 {
923         MimeInfo *partinfo;
924
925         if (!mimeview->opened) return;
926
927         partinfo = mimeview_get_selected_part(mimeview);
928         g_return_if_fail(partinfo != NULL);
929         mimeview_show_message_part(mimeview, partinfo);
930 }
931
932 static void mimeview_save_as(MimeView *mimeview)
933 {
934         gchar *filename;
935         gchar *defname = NULL;
936         MimeInfo *partinfo;
937         gchar *res;
938
939         if (!mimeview->opened) return;
940         if (!mimeview->file) return;
941
942         partinfo = mimeview_get_selected_part(mimeview);
943         g_return_if_fail(partinfo != NULL);
944
945         if (partinfo->filename)
946                 defname = partinfo->filename;
947         else if (partinfo->name) {
948                 Xstrdup_a(defname, partinfo->name, return);
949                 subst_for_filename(defname);
950         }
951
952         filename = filesel_select_file(_("Save as"), defname);
953         if (!filename) return;
954         if (is_file_exist(filename)) {
955                 AlertValue aval;
956                 res = g_strdup_printf(_("Overwrite existing file '%s'?"),
957                                       filename);
958                 aval = alertpanel(_("Overwrite"), res, _("OK"), 
959                                   _("Cancel"), NULL);
960                 g_free(res);                                      
961                 if (G_ALERTDEFAULT != aval) return;
962         }
963
964         if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
965                 alertpanel_error
966                         (_("Can't save the part of multipart message."));
967 }
968
969 static void mimeview_launch(MimeView *mimeview)
970 {
971         MimeInfo *partinfo;
972         gchar *filename;
973
974         if (!mimeview->opened) return;
975         if (!mimeview->file) return;
976
977         partinfo = mimeview_get_selected_part(mimeview);
978         g_return_if_fail(partinfo != NULL);
979
980         filename = procmime_get_tmp_file_name(partinfo);
981
982         if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
983                 alertpanel_error
984                         (_("Can't save the part of multipart message."));
985         else
986                 mimeview_view_file(filename, partinfo, NULL);
987
988         g_free(filename);
989 }
990
991 static void mimeview_open_with(MimeView *mimeview)
992 {
993         MimeInfo *partinfo;
994         gchar *filename;
995         gchar *cmd;
996
997         if (!mimeview->opened) return;
998         if (!mimeview->file) return;
999
1000         partinfo = mimeview_get_selected_part(mimeview);
1001         g_return_if_fail(partinfo != NULL);
1002
1003         filename = procmime_get_tmp_file_name(partinfo);
1004
1005         if (procmime_get_part(filename, mimeview->file, partinfo) < 0) {
1006                 alertpanel_error
1007                         (_("Can't save the part of multipart message."));
1008                 g_free(filename);
1009                 return;
1010         }
1011
1012         if (!prefs_common.mime_open_cmd_history)
1013                 prefs_common.mime_open_cmd_history =
1014                         add_history(NULL, prefs_common.mime_open_cmd);
1015
1016         cmd = input_dialog_combo
1017                 (_("Open with"),
1018                  _("Enter the command line to open file:\n"
1019                    "(`%s' will be replaced with file name)"),
1020                  prefs_common.mime_open_cmd,
1021                  prefs_common.mime_open_cmd_history,
1022                  TRUE);
1023         if (cmd) {
1024                 mimeview_view_file(filename, partinfo, cmd);
1025                 g_free(prefs_common.mime_open_cmd);
1026                 prefs_common.mime_open_cmd = cmd;
1027                 prefs_common.mime_open_cmd_history =
1028                         add_history(prefs_common.mime_open_cmd_history, cmd);
1029         }
1030
1031         g_free(filename);
1032 }
1033
1034 static void mimeview_view_file(const gchar *filename, MimeInfo *partinfo,
1035                                const gchar *cmdline)
1036 {
1037         static gchar *default_image_cmdline = "display '%s'";
1038         static gchar *default_audio_cmdline = "play '%s'";
1039         static gchar *default_html_cmdline = DEFAULT_BROWSER_CMD;
1040         static gchar *mime_cmdline = "metamail -d -b -x -c %s '%s'";
1041         gchar buf[1024];
1042         gchar m_buf[1024];
1043         const gchar *cmd;
1044         const gchar *def_cmd;
1045         const gchar *p;
1046
1047         if (cmdline) {
1048                 cmd = cmdline;
1049                 def_cmd = NULL;
1050         } else if (MIME_APPLICATION_OCTET_STREAM == partinfo->mime_type) {
1051                 return;
1052         } else if (MIME_IMAGE == partinfo->mime_type) {
1053                 cmd = prefs_common.mime_image_viewer;
1054                 def_cmd = default_image_cmdline;
1055         } else if (MIME_AUDIO == partinfo->mime_type) {
1056                 cmd = prefs_common.mime_audio_player;
1057                 def_cmd = default_audio_cmdline;
1058         } else if (MIME_TEXT_HTML == partinfo->mime_type) {
1059                 cmd = prefs_common.uri_cmd;
1060                 def_cmd = default_html_cmdline;
1061         } else {
1062                 g_snprintf(m_buf, sizeof(m_buf), mime_cmdline,
1063                            partinfo->content_type, "%s");
1064                 cmd = m_buf;
1065                 def_cmd = NULL;
1066         }
1067
1068         if (cmd && (p = strchr(cmd, '%')) && *(p + 1) == 's' &&
1069             !strchr(p + 2, '%'))
1070                 g_snprintf(buf, sizeof(buf), cmd, filename);
1071         else {
1072                 if (cmd)
1073                         g_warning("MIME viewer command line is invalid: `%s'", cmd);
1074                 if (def_cmd)
1075                         g_snprintf(buf, sizeof(buf), def_cmd, filename);
1076                 else
1077                         return;
1078         }
1079
1080         execute_command_line(buf, TRUE);
1081 }
1082
1083 #if USE_GPGME
1084 static void update_node_name(GtkCTree *ctree, GtkCTreeNode *node,
1085                              gpointer data)
1086 {
1087         MimeInfo *partinfo;
1088         gchar *part_name;
1089
1090         partinfo = gtk_ctree_node_get_row_data(ctree, node);
1091         g_return_if_fail(partinfo != NULL);
1092
1093         part_name = get_part_name(partinfo);
1094         gtk_ctree_node_set_text(ctree, node, COL_NAME, part_name);
1095 }
1096
1097 static void mimeview_update_names(MimeView *mimeview)
1098 {
1099         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
1100
1101         gtk_ctree_pre_recursive(ctree, NULL, update_node_name, NULL);
1102 }
1103
1104 static void mimeview_update_signature_info(MimeView *mimeview)
1105 {
1106         MimeInfo *partinfo;
1107
1108         if (!mimeview) return;
1109         if (!mimeview->opened) return;
1110
1111         partinfo = mimeview_get_selected_part(mimeview);
1112         if (!partinfo) return;
1113
1114         if (g_strcasecmp(partinfo->content_type,
1115                          "application/pgp-signature") == 0) {
1116                 mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1117                 textview_show_signature_part(mimeview->textview, partinfo);
1118         }
1119 }
1120
1121 void mimeview_check_signature(MimeView *mimeview)
1122 {
1123         MimeInfo *mimeinfo;
1124         FILE *fp;
1125
1126         g_return_if_fail (mimeview_is_signed(mimeview));
1127         g_return_if_fail (gpg_started);
1128
1129         mimeinfo = gtk_ctree_node_get_row_data
1130                 (GTK_CTREE(mimeview->ctree), mimeview->opened);
1131         g_return_if_fail(mimeinfo != NULL);
1132         g_return_if_fail(mimeview->file != NULL);
1133
1134         while (mimeinfo->parent)
1135                 mimeinfo = mimeinfo->parent;
1136
1137         if ((fp = fopen(mimeview->file, "rb")) == NULL) {
1138                 FILE_OP_ERROR(mimeview->file, "fopen");
1139                 return;
1140         }
1141
1142         rfc2015_check_signature(mimeinfo, fp);
1143         fclose(fp);
1144
1145         mimeview_update_names(mimeview);
1146         mimeview_update_signature_info(mimeview);
1147
1148         textview_show_message(mimeview->messageview->textview, mimeinfo,
1149                               mimeview->file);
1150 }
1151 #endif /* USE_GPGME */
1152
1153 void mimeview_register_viewer_factory(MimeViewerFactory *factory)
1154 {
1155         mimeviewer_factories = g_slist_append(mimeviewer_factories, factory);
1156 }
1157
1158 static gint cmp_viewer_by_factroy(gconstpointer a, gconstpointer b)
1159 {
1160         return ((MimeViewer *) a)->factory == (MimeViewerFactory *) b ? 0 : -1;
1161 }
1162
1163 void mimeview_unregister_viewer_factory(MimeViewerFactory *factory)
1164 {
1165         GSList *mimeview_list, *viewer_list;
1166
1167         for (mimeview_list = mimeviews; mimeview_list != NULL; mimeview_list = g_slist_next(mimeview_list)) {
1168                 MimeView *mimeview = (MimeView *) mimeview_list->data;
1169                 
1170                 if (mimeview->mimeviewer && mimeview->mimeviewer->factory == factory) {
1171                         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1172                         mimeview->mimeviewer = NULL;
1173                 }
1174
1175                 while ((viewer_list = g_slist_find_custom(mimeview->viewers, factory, cmp_viewer_by_factroy)) != NULL) {
1176                         MimeViewer *mimeviewer = (MimeViewer *) viewer_list->data;
1177
1178                         mimeviewer->destroy_viewer(mimeviewer);
1179                         mimeview->viewers = g_slist_remove(mimeview->viewers, mimeviewer);
1180                 }
1181         }
1182
1183         mimeviewer_factories = g_slist_remove(mimeviewer_factories, factory);
1184 }