src/mimeview.c: increase the gutter size of paned so that the panes can be resized
[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/gtkhbox.h>
33 #include <gtk/gtkvpaned.h>
34 #include <gtk/gtktext.h>
35 #include <gtk/gtksignal.h>
36 #include <gtk/gtkmenu.h>
37 #include <gtk/gtkdnd.h>
38 #include <gtk/gtkselection.h>
39 #include <gtk/gtktooltips.h>
40 #include <gtk/gtkcontainer.h>
41 #include <gtk/gtkbutton.h>
42 #include <stdio.h>
43
44 #ifndef HAVE_APACHE_FNMATCH
45 /* kludge: apache's fnmatch clashes with <regex.h>, don't include
46  * fnmatch.h */
47 #include <fnmatch.h>
48 #endif
49
50 #include "intl.h"
51 #include "main.h"
52 #include "mimeview.h"
53 #include "textview.h"
54 #include "procmime.h"
55 #include "summaryview.h"
56 #include "menu.h"
57 #include "filesel.h"
58 #include "alertpanel.h"
59 #include "inputdialog.h"
60 #include "utils.h"
61 #include "gtkutils.h"
62 #include "prefs_common.h"
63 #include "rfc2015.h"
64 #include "stock_pixmap.h"
65 #include "gtk/gtkvscrollbutton.h"
66
67
68 typedef enum
69 {
70         COL_MIMETYPE = 0,
71         COL_SIZE     = 1,
72         COL_NAME     = 2
73 } MimeViewColumnPos;
74
75 #define N_MIMEVIEW_COLS 3
76
77 static void mimeview_set_multipart_tree         (MimeView       *mimeview,
78                                                  MimeInfo       *mimeinfo,
79                                                  GtkCTreeNode   *parent);
80 static GtkCTreeNode *mimeview_append_part       (MimeView       *mimeview,
81                                                  MimeInfo       *partinfo,
82                                                  GtkCTreeNode   *parent);
83 static void mimeview_show_message_part          (MimeView       *mimeview,
84                                                  MimeInfo       *partinfo);
85 static void mimeview_change_view_type           (MimeView       *mimeview,
86                                                  MimeViewType    type);
87 static void mimeview_clear                      (MimeView       *mimeview);
88
89 static void mimeview_selected           (GtkCTree       *ctree,
90                                          GtkCTreeNode   *node,
91                                          gint            column,
92                                          MimeView       *mimeview);
93 static void mimeview_start_drag         (GtkWidget      *widget,
94                                          gint            button,
95                                          GdkEvent       *event,
96                                          MimeView       *mimeview);
97 static gint mimeview_button_pressed     (GtkWidget      *widget,
98                                          GdkEventButton *event,
99                                          MimeView       *mimeview);
100 static gint mimeview_key_pressed        (GtkWidget      *widget,
101                                          GdkEventKey    *event,
102                                          MimeView       *mimeview);
103
104 static void mimeview_drag_data_get      (GtkWidget        *widget,
105                                          GdkDragContext   *drag_context,
106                                          GtkSelectionData *selection_data,
107                                          guint             info,
108                                          guint             time,
109                                          MimeView         *mimeview);
110
111 static void mimeview_display_as_text    (MimeView       *mimeview);
112 static void mimeview_save_as            (MimeView       *mimeview);
113 static void mimeview_save_all           (MimeView       *mimeview);
114 static void mimeview_launch             (MimeView       *mimeview);
115 static void mimeview_open_with          (MimeView       *mimeview);
116 static void mimeview_view_file          (const gchar    *filename,
117                                          MimeInfo       *partinfo,
118                                          const gchar    *cmdline);
119 static gboolean icon_clicked_cb         (GtkWidget      *button, 
120                                          GdkEventButton *event, 
121                                          MimeView       *mimeview);
122 static void icon_selected               (MimeView       *mimeview, 
123                                          gint            num, 
124                                          MimeInfo       *partinfo);
125 static gint icon_key_pressed            (GtkWidget      *button, 
126                                          GdkEventKey    *event,
127                                          MimeView       *mimeview);
128 static void icon_toggled_cb             (GtkToggleButton *button,
129                                          MimeView       *mimeview);
130 static void icon_list_append_icon       (MimeView       *mimeview, 
131                                          MimeInfo       *mimeinfo);
132 static void icon_list_create            (MimeView       *mimeview, 
133                                          MimeInfo       *mimeinfo);
134 static void icon_list_clear             (MimeView       *mimeview);
135 static void icon_list_toggle_by_mime_info (MimeView     *mimeview,
136                                            MimeInfo     *mimeinfo);
137 static void mime_toggle_button_cb       (GtkWidget      *button,
138                                          MimeView       *mimeview);
139 static void part_button_pressed         (MimeView       *mimeview, 
140                                          GdkEventButton *event, 
141                                          MimeInfo       *partinfo);
142 static void icon_scroll_size_allocate_cb(GtkWidget      *widget, 
143                                          GtkAllocation  *layout_size, 
144                                          MimeView       *mimeview);
145
146 static GtkItemFactoryEntry mimeview_popup_entries[] =
147 {
148         {N_("/_Open"),            NULL, mimeview_launch,          0, NULL},
149         {N_("/Open _with..."),    NULL, mimeview_open_with,       0, NULL},
150         {N_("/_Display as text"), NULL, mimeview_display_as_text, 0, NULL},
151         {N_("/_Save as..."),      NULL, mimeview_save_as,         0, NULL},
152         {N_("/Save _all..."),     NULL, mimeview_save_all,        0, NULL}
153 #if USE_GPGME
154         ,
155         {N_("/_Check signature"), NULL, mimeview_check_signature, 0, NULL}
156 #endif
157 };
158
159 static GtkTargetEntry mimeview_mime_types[] =
160 {
161         {"text/uri-list", 0, 0}
162 };
163
164 GSList *mimeviewer_factories;
165 GSList *mimeviews;
166
167 MimeView *mimeview_create(MainWindow *mainwin)
168 {
169         MimeView *mimeview;
170
171         GtkWidget *notebook;
172         GtkWidget *vbox;
173         GtkWidget *paned;
174         GtkWidget *scrolledwin;
175         GtkWidget *ctree;
176         GtkWidget *mime_notebook;
177         GtkWidget *popupmenu;
178         GtkWidget *ctree_mainbox;
179         GtkWidget *mime_toggle;
180         GtkWidget *icon_mainbox;
181         GtkWidget *icon_scroll;
182         GtkWidget *icon_vbox;
183         GtkWidget *arrow;
184         GtkWidget *scrollbutton;
185         GtkWidget *hbox;
186         GtkTooltips *tooltips;
187         GtkItemFactory *popupfactory;
188         gchar *titles[N_MIMEVIEW_COLS];
189         gint n_entries;
190         gint i;
191
192         debug_print("Creating MIME view...\n");
193         mimeview = g_new0(MimeView, 1);
194
195         titles[COL_MIMETYPE] = _("MIME Type");
196         titles[COL_SIZE]     = _("Size");
197         titles[COL_NAME]     = _("Name");
198
199         notebook = gtk_notebook_new();
200         gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), TRUE);
201         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), FALSE);
202         gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
203
204         vbox = gtk_vbox_new(FALSE, 0);
205         gtk_container_add(GTK_CONTAINER(notebook), vbox);
206         
207         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
208         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
209                                        GTK_POLICY_AUTOMATIC,
210                                        GTK_POLICY_ALWAYS);
211
212         ctree = gtk_sctree_new_with_titles(N_MIMEVIEW_COLS, 0, titles);
213         gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE);
214         gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_NONE);
215         gtk_clist_set_column_justification(GTK_CLIST(ctree), COL_SIZE,
216                                            GTK_JUSTIFY_RIGHT);
217         gtk_clist_set_column_width(GTK_CLIST(ctree), COL_MIMETYPE, 240);
218         gtk_clist_set_column_width(GTK_CLIST(ctree), COL_SIZE, 90);
219         for (i = 0; i < N_MIMEVIEW_COLS; i++)
220                 GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button,
221                                        GTK_CAN_FOCUS);
222         gtk_container_add(GTK_CONTAINER(scrolledwin), ctree);
223
224         gtk_signal_connect(GTK_OBJECT(ctree), "tree_select_row",
225                            GTK_SIGNAL_FUNC(mimeview_selected), mimeview);
226         gtk_signal_connect(GTK_OBJECT(ctree), "button_press_event",
227                            GTK_SIGNAL_FUNC(mimeview_button_pressed), mimeview);
228         gtk_signal_connect(GTK_OBJECT(ctree), "key_press_event",
229                            GTK_SIGNAL_FUNC(mimeview_key_pressed), mimeview);
230         gtk_signal_connect(GTK_OBJECT (ctree),"start_drag",
231                            GTK_SIGNAL_FUNC (mimeview_start_drag), mimeview);
232         gtk_signal_connect(GTK_OBJECT(ctree), "drag_data_get",
233                            GTK_SIGNAL_FUNC(mimeview_drag_data_get), mimeview);
234
235         mime_notebook = gtk_notebook_new();
236         gtk_widget_show(mime_notebook);
237         GTK_WIDGET_UNSET_FLAGS(mime_notebook, GTK_CAN_FOCUS);
238         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(mime_notebook), FALSE);
239         gtk_notebook_set_show_border(GTK_NOTEBOOK(mime_notebook), FALSE);
240         
241         gtk_notebook_set_page(GTK_NOTEBOOK(notebook), 0);
242                                 
243         icon_vbox = gtk_vbox_new(FALSE,0);
244         icon_scroll = gtk_layout_new(NULL, NULL);
245         gtk_layout_put(GTK_LAYOUT(icon_scroll), icon_vbox, 0, 0);
246         scrollbutton = gtk_vscrollbutton_new(gtk_layout_get_vadjustment(GTK_LAYOUT(icon_scroll)));
247
248         mime_toggle = gtk_toggle_button_new();
249         arrow = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_NONE);
250         gtk_widget_show(arrow);
251         gtk_container_add(GTK_CONTAINER(mime_toggle), arrow);
252         gtk_signal_connect(GTK_OBJECT(mime_toggle), "toggled", 
253                            GTK_SIGNAL_FUNC(mime_toggle_button_cb), mimeview);
254
255         icon_mainbox = gtk_vbox_new(FALSE, 0);
256         gtk_box_pack_start(GTK_BOX(icon_mainbox), mime_toggle, FALSE, FALSE, 0);
257         gtk_box_pack_start(GTK_BOX(icon_mainbox), icon_scroll, TRUE, TRUE, 3);
258         gtk_box_pack_end(GTK_BOX(icon_mainbox), scrollbutton, FALSE, FALSE, 0);
259         gtk_signal_connect(GTK_OBJECT(icon_mainbox), "size_allocate", 
260                         GTK_SIGNAL_FUNC(icon_scroll_size_allocate_cb), mimeview);
261         
262         ctree_mainbox = gtk_hbox_new(FALSE, 0); 
263         gtk_box_pack_start(GTK_BOX(ctree_mainbox), scrolledwin, TRUE, TRUE, 0);
264
265         n_entries = sizeof(mimeview_popup_entries) /
266                 sizeof(mimeview_popup_entries[0]);
267         popupmenu = menu_create_items(mimeview_popup_entries, n_entries,
268                                       "<MimeView>", &popupfactory, mimeview);
269         tooltips = gtk_tooltips_new();
270         gtk_tooltips_set_delay(tooltips, 0); 
271
272         paned = gtk_vpaned_new();
273         gtk_paned_set_gutter_size(GTK_PANED(paned), 6);
274         gtk_paned_pack1(GTK_PANED(paned), ctree_mainbox, FALSE, TRUE);
275         gtk_paned_pack2(GTK_PANED(paned), mime_notebook, TRUE, TRUE);
276         gtk_container_add(GTK_CONTAINER(notebook), paned);
277         
278         hbox = gtk_hbox_new(FALSE, 0);
279         gtk_box_pack_start(GTK_BOX(hbox), notebook, TRUE, TRUE, 0);
280         gtk_box_pack_start(GTK_BOX(hbox), icon_mainbox, FALSE, FALSE, 0);
281
282         gtk_widget_show_all(hbox);
283         gtk_widget_hide(ctree_mainbox);
284
285         mimeview->notebook      = notebook;
286         mimeview->vbox          = vbox;
287         mimeview->hbox          = hbox;
288         mimeview->paned         = paned;
289         mimeview->scrolledwin   = scrolledwin;
290         mimeview->ctree         = ctree;
291         mimeview->mime_notebook = mime_notebook;
292         mimeview->popupmenu     = popupmenu;
293         mimeview->popupfactory  = popupfactory;
294         mimeview->type          = -1;
295         mimeview->ctree_mainbox = ctree_mainbox;
296         mimeview->icon_scroll   = icon_scroll;
297         mimeview->icon_vbox     = icon_vbox;
298         mimeview->icon_mainbox  = icon_mainbox;
299         mimeview->icon_count    = 0;
300         mimeview->mainwin       = mainwin;
301         mimeview->tooltips      = tooltips;
302         mimeview->oldsize       = 60;
303         mimeview->mime_toggle   = mime_toggle;
304
305         mimeview->target_list   = gtk_target_list_new(mimeview_mime_types, 1); 
306         
307         mimeviews = g_slist_prepend(mimeviews, mimeview);
308
309         return mimeview;
310 }
311
312 void mimeview_init(MimeView *mimeview)
313 {
314         textview_init(mimeview->textview);
315
316         gtk_container_add(GTK_CONTAINER(mimeview->mime_notebook),
317                 GTK_WIDGET_PTR(mimeview->textview));
318 }
319
320 /* 
321  * Check whether the message is OpenPGP signed
322  */
323 #if USE_GPGME
324 static gboolean mimeview_is_signed(MimeView *mimeview)
325 {
326         MimeInfo *partinfo = NULL;
327
328         debug_print("mimeview_is signed of %p\n", mimeview);
329
330         if (!mimeview) return FALSE;
331         if (!mimeview->opened) return FALSE;
332
333         debug_print("mimeview_is_signed: open\n" );
334
335         if (!mimeview->file) return FALSE;
336
337         debug_print("mimeview_is_signed: file\n" );
338
339         partinfo = gtk_ctree_node_get_row_data
340                 (GTK_CTREE(mimeview->ctree), mimeview->opened);
341         g_return_val_if_fail(partinfo != NULL, FALSE);
342
343         /* walk the tree and see whether there is a signature somewhere */
344         do {
345                 if (rfc2015_has_signature(partinfo))
346                         return TRUE;
347         } while ((partinfo = partinfo->parent) != NULL);
348
349         debug_print("mimeview_is_signed: FALSE\n" );
350
351         return FALSE;
352 }
353
354 static void set_unchecked_signature(MimeInfo *mimeinfo)
355 {
356         MimeInfo *sig_partinfo;
357
358         sig_partinfo = rfc2015_find_signature(mimeinfo);
359         if (sig_partinfo == NULL) return;
360
361         g_free(sig_partinfo->sigstatus);
362         sig_partinfo->sigstatus =
363                 g_strdup(_("Right-click here to verify the signature"));
364
365         g_free(sig_partinfo->sigstatus_full);
366         sig_partinfo->sigstatus_full = NULL;
367 }
368 #endif /* USE_GPGME */
369
370 void mimeview_show_message(MimeView *mimeview, MimeInfo *mimeinfo,
371                            const gchar *file)
372 {
373         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
374         GtkCTreeNode *node;
375         FILE *fp;
376
377         mimeview_clear(mimeview);
378         textview_clear(mimeview->messageview->textview);
379
380         g_return_if_fail(file != NULL);
381         g_return_if_fail(mimeinfo != NULL);
382
383         mimeview->mimeinfo = mimeinfo;
384
385         mimeview->file = g_strdup(file);
386
387 #if USE_GPGME
388         if (prefs_common.auto_check_signatures && gpg_started) {
389                 if ((fp = fopen(file, "rb")) == NULL) {
390                         FILE_OP_ERROR(file, "fopen");
391                         return;
392                 }
393                 rfc2015_check_signature(mimeinfo, fp);
394                 fclose(fp);
395         } else
396                 set_unchecked_signature(mimeinfo);
397 #endif
398
399         gtk_signal_handler_block_by_func(GTK_OBJECT(ctree), mimeview_selected,
400                                          mimeview);
401
402         mimeview_set_multipart_tree(mimeview, mimeinfo, NULL);
403         icon_list_create(mimeview, mimeinfo);
404
405         gtk_signal_handler_unblock_by_func(GTK_OBJECT(ctree),
406                                            mimeview_selected, mimeview);
407
408         /* search first text part */
409         for (node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
410              node != NULL; node = GTK_CTREE_NODE_NEXT(node)) {
411                 MimeInfo *partinfo;
412
413                 partinfo = gtk_ctree_node_get_row_data(ctree, node);
414                 if (partinfo &&
415                     (partinfo->mime_type == MIME_TEXT ||
416                      partinfo->mime_type == MIME_TEXT_HTML))
417                         break;
418         }
419         textview_show_message(mimeview->messageview->textview, mimeinfo, file);
420
421         if (!node)
422                 node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
423
424         if (node) {
425                 gtk_ctree_select(ctree, node);
426                 icon_list_toggle_by_mime_info
427                         (mimeview, gtk_ctree_node_get_row_data(ctree, node));
428                 gtkut_ctree_set_focus_row(ctree, node);
429                 gtk_widget_grab_focus(mimeview->ctree);
430         }
431 }
432
433 void mimeview_destroy(MimeView *mimeview)
434 {
435         GSList *cur;
436         
437         for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
438                 MimeViewer *viewer = (MimeViewer *) cur->data;
439                 gtk_container_remove(GTK_CONTAINER(mimeview->mime_notebook),
440                         GTK_WIDGET(viewer->get_widget(viewer)));
441                 viewer->destroy_viewer(viewer);
442         }
443         g_slist_free(mimeview->viewers);
444         gtk_target_list_unref(mimeview->target_list);
445
446         procmime_mimeinfo_free_all(mimeview->mimeinfo);
447         g_free(mimeview->file);
448         g_free(mimeview);
449
450         mimeviews = g_slist_remove(mimeviews, mimeview);
451         
452 }
453
454 MimeInfo *mimeview_get_selected_part(MimeView *mimeview)
455 {
456         if (gtk_notebook_get_current_page
457                 (GTK_NOTEBOOK(mimeview->notebook)) == 0)
458                 return NULL;
459
460         return gtk_ctree_node_get_row_data
461                 (GTK_CTREE(mimeview->ctree), mimeview->opened);
462 }
463
464 static void mimeview_set_multipart_tree(MimeView *mimeview,
465                                         MimeInfo *mimeinfo,
466                                         GtkCTreeNode *parent)
467 {
468         GtkCTreeNode *node;
469
470         g_return_if_fail(mimeinfo != NULL);
471
472         if (mimeinfo->children)
473                 mimeinfo = mimeinfo->children;
474
475         while (mimeinfo != NULL) {
476                 node = mimeview_append_part(mimeview, mimeinfo, parent);
477
478                 if (mimeinfo->children)
479                         mimeview_set_multipart_tree(mimeview, mimeinfo, node);
480                 else if (mimeinfo->sub &&
481                          mimeinfo->sub->mime_type != MIME_TEXT &&
482                          mimeinfo->sub->mime_type != MIME_TEXT_HTML)
483                         mimeview_set_multipart_tree(mimeview, mimeinfo->sub,
484                                                     node);
485                 mimeinfo = mimeinfo->next;
486         }
487 }
488
489 static gchar *get_part_name(MimeInfo *partinfo)
490 {
491 #if USE_GPGME
492         if (partinfo->sigstatus)
493                 return partinfo->sigstatus;
494         else
495 #endif
496         if (partinfo->name)
497                 return partinfo->name;
498         else if (partinfo->filename)
499                 return partinfo->filename;
500         else if (partinfo->description)
501                 return partinfo->description;
502         else
503                 return "";
504 }
505
506 static gchar *get_part_description(MimeInfo *partinfo)
507 {
508         if (partinfo->description)
509                 return partinfo->description;
510         else if (partinfo->name)
511                 return partinfo->name;
512         else if (partinfo->filename)
513                 return partinfo->filename;
514         else
515                 return "";
516 }
517
518 static GtkCTreeNode *mimeview_append_part(MimeView *mimeview,
519                                           MimeInfo *partinfo,
520                                           GtkCTreeNode *parent)
521 {
522         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
523         GtkCTreeNode *node;
524         gchar *str[N_MIMEVIEW_COLS];
525
526         str[COL_MIMETYPE] =
527                 partinfo->content_type ? partinfo->content_type : "";
528         str[COL_SIZE] = to_human_readable(partinfo->size);
529         if (prefs_common.attach_desc)
530                 str[COL_NAME] = get_part_description(partinfo);
531         else
532                 str[COL_NAME] = get_part_name(partinfo);
533
534         node = gtk_ctree_insert_node(ctree, parent, NULL, str, 0,
535                                      NULL, NULL, NULL, NULL,
536                                      FALSE, TRUE);
537         gtk_ctree_node_set_row_data(ctree, node, partinfo);
538
539         return node;
540 }
541
542 static void mimeview_show_message_part(MimeView *mimeview, MimeInfo *partinfo)
543 {
544         FILE *fp;
545         const gchar *fname;
546 #if USE_GPGME
547         MimeInfo *pi;
548 #endif
549
550         if (!partinfo) return;
551
552 #if USE_GPGME
553         for (pi = partinfo; pi && !pi->plaintextfile ; pi = pi->parent)
554                 ;
555         fname = pi ? pi->plaintextfile : mimeview->file;
556 #else
557         fname = mimeview->file;
558 #endif /* USE_GPGME */
559         if (!fname) return;
560
561         if ((fp = fopen(fname, "rb")) == NULL) {
562                 FILE_OP_ERROR(fname, "fopen");
563                 return;
564         }
565
566         if (fseek(fp, partinfo->fpos, SEEK_SET) < 0) {
567                 FILE_OP_ERROR(mimeview->file, "fseek");
568                 fclose(fp);
569                 return;
570         }
571
572         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
573         textview_show_part(mimeview->textview, partinfo, fp);
574
575         fclose(fp);
576 }
577
578 static MimeViewer *get_viewer_for_content_type(MimeView *mimeview, const gchar *content_type)
579 {
580         GSList *cur;
581         MimeViewerFactory *factory = NULL;
582         MimeViewer *viewer = NULL;
583
584 /*
585  * FNM_CASEFOLD is a GNU extension
586  * if its not defined copy the string to the stack and
587  * convert the copy to lower case
588  */
589 #ifndef FNM_CASEFOLD
590 #define FNM_CASEFOLD 0
591         Xstrdup_a(content_type, content_type, return NULL);
592         g_strdown((gchar *)content_type);
593 #endif
594         
595         for (cur = mimeviewer_factories; cur != NULL; cur = g_slist_next(cur)) {
596                 MimeViewerFactory *curfactory = cur->data;
597                 gint i = 0;
598
599                 while (curfactory->content_types[i] != NULL) {
600                         debug_print("%s\n", curfactory->content_types[i]);
601                         if(!fnmatch(curfactory->content_types[i], content_type, FNM_CASEFOLD)) {
602                                 factory = curfactory;
603                                 break;
604                         }
605                         i++;
606                 }
607                 if (factory != NULL)
608                         break;
609         }
610         if (factory == NULL)
611                 return NULL;
612
613         for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
614                 MimeViewer *curviewer = cur->data;
615                 
616                 if (curviewer->factory == factory)
617                         return curviewer;
618         }
619         viewer = factory->create_viewer();
620         gtk_container_add(GTK_CONTAINER(mimeview->mime_notebook),
621                 GTK_WIDGET(viewer->get_widget(viewer)));
622                 
623         mimeview->viewers = g_slist_append(mimeview->viewers, viewer);
624
625         return viewer;
626 }
627
628 static MimeViewer *get_viewer_for_mimeinfo(MimeView *mimeview, MimeInfo *partinfo)
629 {
630         gchar *content_type = NULL;
631         MimeViewer *viewer = NULL;
632
633         if ((partinfo->mime_type == MIME_APPLICATION_OCTET_STREAM) &&
634             (partinfo->name != NULL)) {
635                 content_type = procmime_get_mime_type(partinfo->name);
636         } else {
637                 content_type = g_strdup(partinfo->content_type);
638         }
639
640         if (content_type != NULL) {
641                 viewer = get_viewer_for_content_type(mimeview, content_type);
642                 g_free(content_type);
643         }
644
645         return viewer;
646 }
647
648 static gboolean mimeview_show_part(MimeView *mimeview, MimeInfo *partinfo)
649 {
650         MimeViewer *viewer;
651         
652         viewer = get_viewer_for_mimeinfo(mimeview, partinfo);
653         if (viewer == NULL) {
654                 if (mimeview->mimeviewer != NULL)
655                         mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
656                 mimeview->mimeviewer = NULL;
657                 return FALSE;
658         }
659
660         if (mimeview->mimeviewer != viewer) {
661                 if (mimeview->mimeviewer != NULL)
662                         mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
663                 mimeview->mimeviewer = viewer;
664                 mimeview_change_view_type(mimeview, MIMEVIEW_VIEWER);
665         }
666         viewer->show_mimepart(viewer, mimeview->file, partinfo);
667
668         return TRUE;
669 }
670
671 static void mimeview_change_view_type(MimeView *mimeview, MimeViewType type)
672 {
673         TextView  *textview  = mimeview->textview;
674
675         if ((mimeview->type != MIMEVIEW_VIEWER) && 
676             (mimeview->type == type)) return;
677
678         switch (type) {
679         case MIMEVIEW_TEXT:
680                 gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->mime_notebook),
681                         gtk_notebook_page_num(GTK_NOTEBOOK(mimeview->mime_notebook), 
682                         GTK_WIDGET_PTR(textview)));
683                 break;
684         case MIMEVIEW_VIEWER:
685                 gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->mime_notebook),
686                         gtk_notebook_page_num(GTK_NOTEBOOK(mimeview->mime_notebook), 
687                         GTK_WIDGET(mimeview->mimeviewer->get_widget(mimeview->mimeviewer))));
688                 break;
689         default:
690                 return;
691         }
692
693         mimeview->type = type;
694 }
695
696 static void mimeview_clear(MimeView *mimeview)
697 {
698         GtkCList *clist = GTK_CLIST(mimeview->ctree);
699
700         procmime_mimeinfo_free_all(mimeview->mimeinfo);
701         mimeview->mimeinfo = NULL;
702
703         gtk_clist_clear(clist);
704         textview_clear(mimeview->textview);
705         if (mimeview->mimeviewer != NULL)
706                 mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
707
708         mimeview->opened = NULL;
709
710         g_free(mimeview->file);
711         mimeview->file = NULL;
712         icon_list_clear(mimeview);
713
714         if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(mimeview->mime_toggle)))
715                 gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->notebook), 0);
716 }
717
718 static void mimeview_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
719                               MimeView *mimeview)
720 {
721         MimeInfo *partinfo;
722
723         if (mimeview->opened == node) return;
724         mimeview->opened = node;
725         gtk_ctree_node_moveto(ctree, node, -1, 0.5, 0);
726
727         partinfo = gtk_ctree_node_get_row_data(ctree, node);
728         if (!partinfo) return;
729
730         /* ungrab the mouse event */
731         if (GTK_WIDGET_HAS_GRAB(ctree)) {
732                 gtk_grab_remove(GTK_WIDGET(ctree));
733                 if (gdk_pointer_is_grabbed())
734                         gdk_pointer_ungrab(GDK_CURRENT_TIME);
735         }
736         
737         mimeview->textview->default_text = FALSE;
738         
739         if (!mimeview_show_part(mimeview, partinfo)) {
740                 switch (partinfo->mime_type) {
741                 case MIME_TEXT:
742                 case MIME_TEXT_HTML:
743                 case MIME_TEXT_ENRICHED:
744                 case MIME_MESSAGE_RFC822:
745                 case MIME_MULTIPART:
746                         mimeview_show_message_part(mimeview, partinfo);
747                 
748                         break;
749                 default:
750                         mimeview->textview->default_text = TRUE;        
751                         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
752 #if USE_GPGME
753                         if (g_strcasecmp(partinfo->content_type,
754                                          "application/pgp-signature") == 0)
755                                 textview_show_signature_part(mimeview->textview,
756                                                              partinfo);
757                         else
758 #endif
759                                 textview_show_mime_part(mimeview->textview, partinfo);
760                         break;
761                 }
762         }
763 }
764
765 static void mimeview_start_drag(GtkWidget *widget, gint button,
766                                 GdkEvent *event, MimeView *mimeview)
767 {
768         GdkDragContext *context;
769         MimeInfo *partinfo;
770
771         g_return_if_fail(mimeview != NULL);
772
773         partinfo = mimeview_get_selected_part(mimeview);
774         if (partinfo->filename == NULL && partinfo->name == NULL) return;
775
776         context = gtk_drag_begin(widget, mimeview->target_list,
777                                  GDK_ACTION_COPY, button, event);
778         gtk_drag_set_icon_default(context);
779 }
780
781 static gint mimeview_button_pressed(GtkWidget *widget, GdkEventButton *event,
782                                     MimeView *mimeview)
783 {
784         GtkCList *clist = GTK_CLIST(widget);
785         gint row, column;
786
787         if (!event) return FALSE;
788
789         if (event->button == 2 || event->button == 3) {
790                 if (!gtk_clist_get_selection_info(clist, event->x, event->y,
791                                                   &row, &column))
792                         return FALSE;
793                 gtk_clist_unselect_all(clist);
794                 gtk_clist_select_row(clist, row, column);
795                 gtkut_clist_set_focus_row(clist, row);
796         }
797         part_button_pressed(mimeview, event, mimeview_get_selected_part(mimeview));
798
799         return TRUE;
800 }
801
802 static void part_button_pressed(MimeView *mimeview, GdkEventButton *event, 
803                                 MimeInfo *partinfo)
804 {
805         if (event->button == 2 ||
806             (event->button == 1 && event->type == GDK_2BUTTON_PRESS)) {
807                 /* call external program for image, audio or html */
808                 mimeview_launch(mimeview);
809         } else if (event->button == 3) {
810                 if (partinfo && (partinfo->mime_type == MIME_TEXT ||
811                                  partinfo->mime_type == MIME_TEXT_HTML ||
812                                  partinfo->mime_type == MIME_TEXT_ENRICHED ||
813                                  partinfo->mime_type == MIME_MESSAGE_RFC822 ||
814                                  partinfo->mime_type == MIME_IMAGE ||
815                                  partinfo->mime_type == MIME_MULTIPART))
816                         menu_set_sensitive(mimeview->popupfactory,
817                                            "/Display as text", FALSE);
818                 else
819                         menu_set_sensitive(mimeview->popupfactory,
820                                            "/Display as text", TRUE);
821                 if (partinfo &&
822                     partinfo->mime_type == MIME_APPLICATION_OCTET_STREAM)
823                         menu_set_sensitive(mimeview->popupfactory,
824                                            "/Open", FALSE);
825                 else
826                         menu_set_sensitive(mimeview->popupfactory,
827                                            "/Open", TRUE);
828
829 #if USE_GPGME
830                 menu_set_sensitive(mimeview->popupfactory,
831                                    "/Check signature",
832                                    mimeview_is_signed(mimeview));
833 #endif
834
835                 gtk_menu_popup(GTK_MENU(mimeview->popupmenu),
836                                NULL, NULL, NULL, NULL,
837                                event->button, event->time);
838         }
839
840 }
841
842
843 void mimeview_pass_key_press_event(MimeView *mimeview, GdkEventKey *event)
844 {
845         mimeview_key_pressed(mimeview->ctree, event, mimeview);
846 }
847
848 #define BREAK_ON_MODIFIER_KEY() \
849         if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break
850
851 #define KEY_PRESS_EVENT_STOP() \
852         if (gtk_signal_n_emissions_by_name \
853                 (GTK_OBJECT(ctree), "key_press_event") > 0) { \
854                 gtk_signal_emit_stop_by_name(GTK_OBJECT(ctree), \
855                                              "key_press_event"); \
856         }
857
858 static gint mimeview_key_pressed(GtkWidget *widget, GdkEventKey *event,
859                                  MimeView *mimeview)
860 {
861         SummaryView *summaryview;
862         GtkCTree *ctree = GTK_CTREE(widget);
863         GtkCTreeNode *node;
864
865         if (!event) return FALSE;
866         if (!mimeview->opened) return FALSE;
867
868         switch (event->keyval) {
869         case GDK_space:
870                 if (textview_scroll_page(mimeview->textview, FALSE))
871                         return TRUE;
872
873                 node = GTK_CTREE_NODE_NEXT(mimeview->opened);
874                 if (node) {
875                         gtk_sctree_unselect_all(GTK_SCTREE(ctree));
876                         gtk_sctree_select(GTK_SCTREE(ctree), node);
877                         return TRUE;
878                 }
879                 break;
880         case GDK_BackSpace:
881                 textview_scroll_page(mimeview->textview, TRUE);
882                 return TRUE;
883         case GDK_Return:
884                 textview_scroll_one_line(mimeview->textview,
885                                          (event->state & GDK_MOD1_MASK) != 0);
886                 return TRUE;
887         case GDK_n:
888         case GDK_N:
889                 BREAK_ON_MODIFIER_KEY();
890                 if (!GTK_CTREE_NODE_NEXT(mimeview->opened)) break;
891                 KEY_PRESS_EVENT_STOP();
892
893                 gtk_signal_emit_by_name(GTK_OBJECT(ctree), "scroll_vertical",
894                                         GTK_SCROLL_STEP_FORWARD, 0.0);
895                 return TRUE;
896         case GDK_p:
897         case GDK_P:
898                 BREAK_ON_MODIFIER_KEY();
899                 if (!GTK_CTREE_NODE_PREV(mimeview->opened)) break;
900                 KEY_PRESS_EVENT_STOP();
901
902                 gtk_signal_emit_by_name(GTK_OBJECT(ctree), "scroll_vertical",
903                                         GTK_SCROLL_STEP_BACKWARD, 0.0);
904                 return TRUE;
905         case GDK_y:
906                 BREAK_ON_MODIFIER_KEY();
907                 KEY_PRESS_EVENT_STOP();
908                 mimeview_save_as(mimeview);
909                 return TRUE;
910         case GDK_t:
911                 BREAK_ON_MODIFIER_KEY();
912                 KEY_PRESS_EVENT_STOP();
913                 mimeview_display_as_text(mimeview);
914                 return TRUE;    
915         case GDK_l:
916                 BREAK_ON_MODIFIER_KEY();
917                 KEY_PRESS_EVENT_STOP();
918                 mimeview_launch(mimeview);
919                 return TRUE;
920         default:
921                 break;
922         }
923
924         if (!mimeview->messageview->mainwin) return FALSE;
925         summaryview = mimeview->messageview->mainwin->summaryview;
926         summary_pass_key_press_event(summaryview, event);
927         return TRUE;
928 }
929
930 static void mimeview_drag_data_get(GtkWidget        *widget,
931                                    GdkDragContext   *drag_context,
932                                    GtkSelectionData *selection_data,
933                                    guint             info,
934                                    guint             time,
935                                    MimeView         *mimeview)
936 {
937         gchar *filename, *uriname;
938         MimeInfo *partinfo;
939
940         if (!mimeview->opened) return;
941         if (!mimeview->file) return;
942
943         partinfo = mimeview_get_selected_part(mimeview);
944         if (!partinfo) return;
945         if (!partinfo->filename && !partinfo->name) return;
946
947         filename = partinfo->filename ? partinfo->filename : partinfo->name;
948         filename = g_basename(filename);
949         if (*filename == '\0') return;
950
951         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
952                                filename, NULL);
953
954         if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
955                 alertpanel_error
956                         (_("Can't save the part of multipart message."));
957
958         uriname = g_strconcat("file:/", filename, NULL);
959         gtk_selection_data_set(selection_data, selection_data->target, 8,
960                                uriname, strlen(uriname));
961
962         g_free(uriname);
963         g_free(filename);
964 }
965
966 static void mimeview_save_all(MimeView *mimeview)
967 {
968         gchar *dirname;
969         gchar *defname = NULL;
970         MimeInfo *partinfo;
971         MimeInfo *attachment;
972         gchar buf[1024];
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         dirname = filesel_select_file(_("Save as"), defname);
981         if (!dirname) return;
982
983         /* return to first children */
984         if (!partinfo->parent->children) return;  /* multipart container? */
985         attachment = partinfo->parent->children->next;
986         /* for each attachment, extract it in the selected dir. */
987         while (attachment != NULL) {
988                 static guint subst_cnt = 1;
989                 gchar *attachdir;
990                 gchar *attachname = g_strdup(get_part_name(attachment));
991                 AlertValue aval = G_ALERTDEFAULT;
992                 gchar *res;
993
994                 if (!attachname || !strlen(attachname))
995                         attachname = g_strdup_printf("noname.%d",subst_cnt++);
996                 subst_chars(attachname, ":?*&|<>\t\r\n", '_');
997                 g_snprintf(buf, sizeof(buf), "%s%s",
998                            dirname,
999                            (attachname[0] == G_DIR_SEPARATOR)
1000                            ? &attachname[1]
1001                            : attachname);
1002                 subst_chars(buf, "/\\", G_DIR_SEPARATOR);
1003                 attachdir = g_dirname(buf);
1004                 make_dir_hier(attachdir);
1005                 g_free(attachdir);
1006
1007                 if (is_file_exist(buf)) {
1008                         res = g_strdup_printf(_("Overwrite existing file '%s'?"),
1009                                               attachname);
1010                         aval = alertpanel(_("Overwrite"), res, _("OK"), 
1011                                           _("Cancel"), NULL);
1012                         g_free(res);                                      
1013                 }
1014                 g_free(attachname);
1015
1016                 if ((G_ALERTDEFAULT != aval) || (procmime_get_part(buf, mimeview->file, attachment) < 0))
1017                         alertpanel_error(_("Can't save the part of multipart message."));
1018                 attachment = attachment->next;
1019         }
1020 }
1021
1022 static void mimeview_display_as_text(MimeView *mimeview)
1023 {
1024         MimeInfo *partinfo;
1025
1026         if (!mimeview->opened) return;
1027
1028         partinfo = mimeview_get_selected_part(mimeview);
1029         g_return_if_fail(partinfo != NULL);
1030         mimeview_show_message_part(mimeview, partinfo);
1031 }
1032
1033 static void mimeview_save_as(MimeView *mimeview)
1034 {
1035         gchar *filename;
1036         gchar *defname = NULL;
1037         MimeInfo *partinfo;
1038         gchar *res;
1039
1040         if (!mimeview->opened) return;
1041         if (!mimeview->file) return;
1042
1043         partinfo = mimeview_get_selected_part(mimeview);
1044         g_return_if_fail(partinfo != NULL);
1045
1046         if (partinfo->filename)
1047                 defname = partinfo->filename;
1048         else if (partinfo->name) {
1049                 Xstrdup_a(defname, partinfo->name, return);
1050                 subst_for_filename(defname);
1051         }
1052
1053         filename = filesel_select_file(_("Save as"), defname);
1054         if (!filename) return;
1055         if (is_file_exist(filename)) {
1056                 AlertValue aval;
1057                 res = g_strdup_printf(_("Overwrite existing file '%s'?"),
1058                                       filename);
1059                 aval = alertpanel(_("Overwrite"), res, _("OK"), 
1060                                   _("Cancel"), NULL);
1061                 g_free(res);                                      
1062                 if (G_ALERTDEFAULT != aval) return;
1063         }
1064
1065         if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
1066                 alertpanel_error
1067                         (_("Can't save the part of multipart message."));
1068 }
1069
1070 static void mimeview_launch(MimeView *mimeview)
1071 {
1072         MimeInfo *partinfo;
1073         gchar *filename;
1074
1075         if (!mimeview->opened) return;
1076         if (!mimeview->file) return;
1077
1078         partinfo = mimeview_get_selected_part(mimeview);
1079         g_return_if_fail(partinfo != NULL);
1080
1081         filename = procmime_get_tmp_file_name(partinfo);
1082
1083         if (procmime_get_part(filename, mimeview->file, partinfo) < 0)
1084                 alertpanel_error
1085                         (_("Can't save the part of multipart message."));
1086         else
1087                 mimeview_view_file(filename, partinfo, NULL);
1088
1089         g_free(filename);
1090 }
1091
1092 static void mimeview_open_with(MimeView *mimeview)
1093 {
1094         MimeInfo *partinfo;
1095         gchar *filename;
1096         gchar *cmd;
1097
1098         if (!mimeview->opened) return;
1099         if (!mimeview->file) return;
1100
1101         partinfo = mimeview_get_selected_part(mimeview);
1102         g_return_if_fail(partinfo != NULL);
1103
1104         filename = procmime_get_tmp_file_name(partinfo);
1105
1106         if (procmime_get_part(filename, mimeview->file, partinfo) < 0) {
1107                 alertpanel_error
1108                         (_("Can't save the part of multipart message."));
1109                 g_free(filename);
1110                 return;
1111         }
1112
1113         if (!prefs_common.mime_open_cmd_history)
1114                 prefs_common.mime_open_cmd_history =
1115                         add_history(NULL, prefs_common.mime_open_cmd);
1116
1117         cmd = input_dialog_combo
1118                 (_("Open with"),
1119                  _("Enter the command line to open file:\n"
1120                    "(`%s' will be replaced with file name)"),
1121                  prefs_common.mime_open_cmd,
1122                  prefs_common.mime_open_cmd_history,
1123                  TRUE);
1124         if (cmd) {
1125                 mimeview_view_file(filename, partinfo, cmd);
1126                 g_free(prefs_common.mime_open_cmd);
1127                 prefs_common.mime_open_cmd = cmd;
1128                 prefs_common.mime_open_cmd_history =
1129                         add_history(prefs_common.mime_open_cmd_history, cmd);
1130         }
1131
1132         g_free(filename);
1133 }
1134
1135 static void mimeview_view_file(const gchar *filename, MimeInfo *partinfo,
1136                                const gchar *cmdline)
1137 {
1138         static gchar *default_image_cmdline = "display '%s'";
1139         static gchar *default_audio_cmdline = "play '%s'";
1140         static gchar *default_html_cmdline = DEFAULT_BROWSER_CMD;
1141         static gchar *mime_cmdline = "metamail -d -b -x -c %s '%s'";
1142         gchar buf[1024];
1143         gchar m_buf[1024];
1144         const gchar *cmd;
1145         const gchar *def_cmd;
1146         const gchar *p;
1147
1148         if (cmdline) {
1149                 cmd = cmdline;
1150                 def_cmd = NULL;
1151         } else if (MIME_APPLICATION_OCTET_STREAM == partinfo->mime_type) {
1152                 return;
1153         } else if (MIME_IMAGE == partinfo->mime_type) {
1154                 cmd = prefs_common.mime_image_viewer;
1155                 def_cmd = default_image_cmdline;
1156         } else if (MIME_AUDIO == partinfo->mime_type) {
1157                 cmd = prefs_common.mime_audio_player;
1158                 def_cmd = default_audio_cmdline;
1159         } else if (MIME_TEXT_HTML == partinfo->mime_type) {
1160                 cmd = prefs_common.uri_cmd;
1161                 def_cmd = default_html_cmdline;
1162         } else {
1163                 g_snprintf(m_buf, sizeof(m_buf), mime_cmdline,
1164                            partinfo->content_type, "%s");
1165                 cmd = m_buf;
1166                 def_cmd = NULL;
1167         }
1168
1169         if (cmd && (p = strchr(cmd, '%')) && *(p + 1) == 's' &&
1170             !strchr(p + 2, '%'))
1171                 g_snprintf(buf, sizeof(buf), cmd, filename);
1172         else {
1173                 if (cmd)
1174                         g_warning("MIME viewer command line is invalid: `%s'", cmd);
1175                 if (def_cmd)
1176                         g_snprintf(buf, sizeof(buf), def_cmd, filename);
1177                 else
1178                         return;
1179         }
1180
1181         execute_command_line(buf, TRUE);
1182 }
1183
1184 #if USE_GPGME
1185 static void update_node_name(GtkCTree *ctree, GtkCTreeNode *node,
1186                              gpointer data)
1187 {
1188         MimeInfo *partinfo;
1189         gchar *part_name;
1190
1191         partinfo = gtk_ctree_node_get_row_data(ctree, node);
1192         g_return_if_fail(partinfo != NULL);
1193
1194         part_name = get_part_name(partinfo);
1195         gtk_ctree_node_set_text(ctree, node, COL_NAME, part_name);
1196 }
1197
1198 static void mimeview_update_names(MimeView *mimeview)
1199 {
1200         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
1201
1202         gtk_ctree_pre_recursive(ctree, NULL, update_node_name, NULL);
1203 }
1204
1205 static void mimeview_update_signature_info(MimeView *mimeview)
1206 {
1207         MimeInfo *partinfo;
1208
1209         if (!mimeview) return;
1210         if (!mimeview->opened) return;
1211
1212         partinfo = mimeview_get_selected_part(mimeview);
1213         if (!partinfo) return;
1214
1215         if (g_strcasecmp(partinfo->content_type,
1216                          "application/pgp-signature") == 0) {
1217                 mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1218                 textview_show_signature_part(mimeview->textview, partinfo);
1219         }
1220 }
1221
1222 void mimeview_check_signature(MimeView *mimeview)
1223 {
1224         MimeInfo *mimeinfo;
1225         FILE *fp;
1226
1227         g_return_if_fail (mimeview_is_signed(mimeview));
1228         g_return_if_fail (gpg_started);
1229
1230         mimeinfo = gtk_ctree_node_get_row_data
1231                 (GTK_CTREE(mimeview->ctree), mimeview->opened);
1232         g_return_if_fail(mimeinfo != NULL);
1233         g_return_if_fail(mimeview->file != NULL);
1234
1235         while (mimeinfo->parent)
1236                 mimeinfo = mimeinfo->parent;
1237
1238         if ((fp = fopen(mimeview->file, "rb")) == NULL) {
1239                 FILE_OP_ERROR(mimeview->file, "fopen");
1240                 return;
1241         }
1242
1243         rfc2015_check_signature(mimeinfo, fp);
1244         fclose(fp);
1245
1246         mimeview_update_names(mimeview);
1247         mimeview_update_signature_info(mimeview);
1248
1249         textview_show_message(mimeview->messageview->textview, mimeinfo,
1250                               mimeview->file);
1251                               
1252         mimeview_update(mimeview);
1253 }
1254 #endif /* USE_GPGME */
1255
1256 void mimeview_register_viewer_factory(MimeViewerFactory *factory)
1257 {
1258         mimeviewer_factories = g_slist_append(mimeviewer_factories, factory);
1259 }
1260
1261 static gint cmp_viewer_by_factroy(gconstpointer a, gconstpointer b)
1262 {
1263         return ((MimeViewer *) a)->factory == (MimeViewerFactory *) b ? 0 : -1;
1264 }
1265
1266 void mimeview_unregister_viewer_factory(MimeViewerFactory *factory)
1267 {
1268         GSList *mimeview_list, *viewer_list;
1269
1270         for (mimeview_list = mimeviews; mimeview_list != NULL; mimeview_list = g_slist_next(mimeview_list)) {
1271                 MimeView *mimeview = (MimeView *) mimeview_list->data;
1272                 
1273                 if (mimeview->mimeviewer && mimeview->mimeviewer->factory == factory) {
1274                         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1275                         mimeview->mimeviewer = NULL;
1276                 }
1277
1278                 while ((viewer_list = g_slist_find_custom(mimeview->viewers, factory, cmp_viewer_by_factroy)) != NULL) {
1279                         MimeViewer *mimeviewer = (MimeViewer *) viewer_list->data;
1280
1281                         mimeviewer->destroy_viewer(mimeviewer);
1282                         mimeview->viewers = g_slist_remove(mimeview->viewers, mimeviewer);
1283                 }
1284         }
1285
1286         mimeviewer_factories = g_slist_remove(mimeviewer_factories, factory);
1287 }
1288
1289 static gboolean icon_clicked_cb (GtkWidget *button, GdkEventButton *event, MimeView *mimeview)
1290 {
1291         gint      num;
1292         MimeInfo *partinfo;
1293
1294         num      = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(button), "icon_number"));
1295         partinfo = gtk_object_get_data(GTK_OBJECT(button), "partinfo");
1296         if (event->button == 1) 
1297                 icon_selected(mimeview, num, partinfo);
1298         part_button_pressed(mimeview, event, partinfo);
1299         return TRUE;
1300 }
1301
1302 static void icon_selected (MimeView *mimeview, gint num, MimeInfo *partinfo)
1303 {
1304         if (num == 1 && (partinfo->mime_type == MIME_TEXT ||  
1305                          partinfo->mime_type == MIME_TEXT_HTML))  {
1306                 gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->notebook), 0);
1307                 /* don't set the ctree, as it will unload the plugin, and
1308                  * we want to be able to switch quickly between the text
1309                  * part and the attachment */
1310         } else {
1311                 GtkCTreeNode *node;
1312                 gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->notebook), 1);
1313                 node = gtk_ctree_find_by_row_data(GTK_CTREE(mimeview->ctree), NULL, partinfo);
1314                 if (node)
1315                         gtk_ctree_select(GTK_CTREE(mimeview->ctree), node);
1316         }
1317 }               
1318
1319 static gint icon_key_pressed(GtkWidget *button, GdkEventKey *event,
1320                              MimeView *mimeview)
1321 {
1322         gint      num;
1323         MimeInfo *partinfo;
1324
1325         num      = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(button), "icon_number"));
1326         partinfo = gtk_object_get_data(GTK_OBJECT(button), "partinfo");
1327         
1328         if (!event) return FALSE;
1329
1330         switch (event->keyval) {
1331         case GDK_Return:
1332                 icon_selected(mimeview, num, partinfo);
1333                 return TRUE;
1334                 break;
1335         default:
1336                 break;
1337         }
1338         return FALSE;
1339 }
1340
1341 static void icon_toggled_cb(GtkToggleButton *button, MimeView *mimeview)
1342 {
1343         GList *child;
1344         
1345         if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)))
1346                 return;
1347         
1348         child = gtk_container_children(GTK_CONTAINER(mimeview->icon_vbox));
1349         for (; child != NULL; child = g_list_next(child)) {
1350                 if (GTK_IS_TOGGLE_BUTTON(child->data) &&  
1351                     GTK_TOGGLE_BUTTON(child->data) != button)
1352                         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(child->data)))
1353                                 gtk_toggle_button_set_active
1354                                         (GTK_TOGGLE_BUTTON(child->data),
1355                                          FALSE);
1356         }
1357 }
1358
1359 static void icon_list_append_icon (MimeView *mimeview, MimeInfo *mimeinfo) 
1360 {
1361         GtkWidget *pixmap;
1362         GtkWidget *vbox;
1363         GtkWidget *button;
1364         GtkWidget *sep;
1365         gchar *tip;
1366         gchar *desc = NULL;
1367         StockPixmap stockp;
1368         
1369         vbox = mimeview->icon_vbox;
1370         mimeview->icon_count++;
1371         if (mimeview->icon_count > 1) {
1372                 sep = gtk_hseparator_new();
1373                 gtk_widget_show(sep);
1374                 gtk_box_pack_start(GTK_BOX(vbox), sep, TRUE, TRUE, 3);
1375         }
1376         button = gtk_toggle_button_new();
1377         gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
1378         gtk_object_set_data(GTK_OBJECT(button), "icon_number", 
1379                 GINT_TO_POINTER(mimeview->icon_count));
1380         gtk_object_set_data(GTK_OBJECT(button), "partinfo", 
1381                 mimeinfo);
1382         
1383         switch (mimeinfo->mime_type) {
1384                 
1385         case MIME_TEXT:
1386         case MIME_MESSAGE_RFC822:
1387                 stockp = STOCK_PIXMAP_MIME_TEXT_PLAIN;
1388                 break;
1389         case MIME_TEXT_HTML:
1390                 stockp = STOCK_PIXMAP_MIME_TEXT_HTML;
1391                 break;
1392         case MIME_APPLICATION:
1393 #ifdef USE_GPGME        
1394                 if (mimeinfo->content_type
1395                         &&  g_strcasecmp(mimeinfo->content_type, 
1396                                          "application/pgp-signature")  == 0) {
1397                         if (mimeinfo->sigstatus_full) {
1398                                 desc = mimeinfo->sigstatus;
1399                                 if (mimeinfo->sig_ok)
1400                                         stockp = STOCK_PIXMAP_MIME_GPG_PASSED;
1401                                 else if (mimeinfo->sig_unknown) 
1402                                         stockp = STOCK_PIXMAP_MIME_GPG_UNKNOWN;
1403                                 else if (mimeinfo->sig_expired 
1404                                          || mimeinfo->key_expired) 
1405                                         stockp = STOCK_PIXMAP_MIME_GPG_EXPIRED;
1406                                 else
1407                                         stockp = STOCK_PIXMAP_MIME_GPG_FAILED;
1408                         } else
1409                                 stockp = STOCK_PIXMAP_MIME_GPG_SIGNED;
1410                 } else
1411 #endif  
1412                         stockp = STOCK_PIXMAP_MIME_APPLICATION;
1413                 break;
1414         case MIME_APPLICATION_OCTET_STREAM:
1415                 stockp = STOCK_PIXMAP_MIME_APPLICATION_OCTET_STREAM;
1416                 break;
1417         case MIME_IMAGE:
1418                 stockp = STOCK_PIXMAP_MIME_IMAGE;
1419                 break;
1420         case MIME_AUDIO:
1421                 stockp = STOCK_PIXMAP_MIME_AUDIO;
1422                 break;
1423         case MIME_TEXT_ENRICHED:
1424                 stockp = STOCK_PIXMAP_MIME_TEXT_ENRICHED;
1425                 break;
1426         default:
1427                 stockp = STOCK_PIXMAP_MIME_UNKNOWN;
1428                 break;
1429         }
1430         
1431         pixmap = stock_pixmap_widget(mimeview->mainwin->window, stockp);
1432         gtk_container_add(GTK_CONTAINER(button), pixmap);
1433         
1434         if (!desc) {
1435                 if (prefs_common.attach_desc)
1436                         desc = get_part_description(mimeinfo);
1437                 else
1438                         desc = get_part_name(mimeinfo);
1439         }
1440         if (desc && *desc)
1441                 tip = g_strdup_printf("%s\n%s\n%s", desc, mimeinfo->content_type, 
1442                                 to_human_readable(mimeinfo->size));
1443         else            
1444                 tip = g_strdup_printf("%s\n%s", mimeinfo->content_type, 
1445                                 to_human_readable(mimeinfo->size));
1446
1447         gtk_tooltips_set_tip(mimeview->tooltips, button, tip, NULL);
1448         g_free(tip);
1449         gtk_widget_show_all(button);
1450         gtk_signal_connect(GTK_OBJECT(button), "button_press_event", 
1451                            GTK_SIGNAL_FUNC(icon_clicked_cb), mimeview);
1452         gtk_signal_connect(GTK_OBJECT(button), "key_press_event", 
1453                            GTK_SIGNAL_FUNC(icon_key_pressed), mimeview);
1454         gtk_signal_connect(GTK_OBJECT(button), "toggled", 
1455                            GTK_SIGNAL_FUNC(icon_toggled_cb), mimeview);
1456         gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
1457
1458 }
1459
1460 static void icon_list_clear (MimeView *mimeview)
1461 {
1462         GList     *child;
1463         GtkAdjustment *adj;
1464         
1465         child = gtk_container_children(GTK_CONTAINER(mimeview->icon_vbox));
1466         for (; child != NULL; child = g_list_next(child)) {
1467                 gtkut_container_remove(GTK_CONTAINER(mimeview->icon_vbox), 
1468                                        GTK_WIDGET(child->data));
1469         }
1470         mimeview->icon_count = 0;
1471         adj  = gtk_layout_get_vadjustment(GTK_LAYOUT(mimeview->icon_scroll));
1472         adj->value = 0;
1473 }
1474
1475 static void icon_list_toggle_by_mime_info(MimeView      *mimeview,
1476                                           MimeInfo      *mimeinfo)
1477 {
1478         GList *child;
1479         
1480         child = gtk_container_children(GTK_CONTAINER(mimeview->icon_vbox));
1481         for (; child != NULL; child = g_list_next(child)) {
1482                 if (GTK_IS_TOGGLE_BUTTON(child->data) &&  
1483                     gtk_object_get_data(GTK_OBJECT(child->data),
1484                                         "partinfo") == (gpointer)mimeinfo) {
1485                         gtk_toggle_button_set_active
1486                                 (GTK_TOGGLE_BUTTON(child->data), TRUE);
1487                 }                                
1488         }
1489 }
1490
1491 static void icon_scroll_size_allocate_cb(GtkWidget *widget, 
1492                                         GtkAllocation *size, MimeView *mimeview)
1493 {
1494         GtkAllocation *mainbox_size;
1495         GtkAllocation *vbox_size;
1496         GtkAllocation *layout_size;
1497         GtkAdjustment *adj;
1498         
1499         adj = gtk_layout_get_vadjustment(GTK_LAYOUT(mimeview->icon_scroll));
1500
1501         mainbox_size = &mimeview->icon_mainbox->allocation;
1502         vbox_size = &mimeview->icon_vbox->allocation;
1503         layout_size = &mimeview->icon_scroll->allocation;
1504         
1505         /* centralise the vbox */
1506         gtk_layout_move(GTK_LAYOUT(mimeview->icon_scroll), mimeview->icon_vbox, 
1507                         (mainbox_size->width - vbox_size->width)/2, 0);
1508         
1509         gtk_layout_set_size(GTK_LAYOUT(mimeview->icon_scroll), 
1510                             GTK_LAYOUT(mimeview->icon_scroll)->width, 
1511                             MAX(vbox_size->height, layout_size->height));
1512         adj->step_increment = 5;
1513 }
1514
1515 static void icon_list_create(MimeView *mimeview, MimeInfo *mimeinfo)
1516 {
1517         GtkRequisition size;
1518         gint           width;
1519         g_return_if_fail(mimeinfo != NULL);
1520
1521         if (mimeinfo->children)
1522                 mimeinfo = mimeinfo->children;
1523
1524         while (mimeinfo != NULL) {
1525                 if (mimeinfo->children)
1526                         icon_list_create(mimeview, mimeinfo);
1527                 else if (mimeinfo->sub &&
1528                          mimeinfo->sub->mime_type != MIME_TEXT &&
1529                          mimeinfo->sub->mime_type != MIME_TEXT_HTML)
1530                         icon_list_create(mimeview, mimeinfo->sub);
1531                 else 
1532                         icon_list_append_icon(mimeview, mimeinfo);
1533                 mimeinfo = mimeinfo->next;
1534         }
1535         gtk_widget_size_request(mimeview->icon_vbox, &size);
1536         width = size.width + 4;
1537         if (width > mimeview->icon_mainbox->requisition.width) {
1538                 gtk_widget_set_usize(mimeview->icon_mainbox, 
1539                                      width, -1);
1540         }
1541
1542 }
1543
1544 static void mime_toggle_button_cb (GtkWidget *button, MimeView *mimeview) 
1545 {
1546         gtk_widget_ref(button); 
1547
1548         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
1549                 gtk_arrow_set(GTK_ARROW(GTK_BIN(button)->child), GTK_ARROW_RIGHT, 
1550                                         GTK_SHADOW_NONE);
1551                 gtk_widget_hide(mimeview->icon_mainbox);
1552                 gtk_widget_show(mimeview->ctree_mainbox);
1553                 gtk_paned_set_position(GTK_PANED(mimeview->paned), mimeview->oldsize);
1554
1555                 gtkut_container_remove(GTK_CONTAINER(mimeview->icon_mainbox), 
1556                                         button);
1557                 gtk_box_pack_end(GTK_BOX(mimeview->ctree_mainbox), 
1558                                    button, FALSE, FALSE, 0);
1559                 gtk_notebook_set_page(GTK_NOTEBOOK(mimeview->notebook), 1); 
1560         } else {
1561                 gtk_arrow_set(GTK_ARROW(GTK_BIN(button)->child), GTK_ARROW_LEFT, 
1562                               GTK_SHADOW_NONE);
1563                 mimeview->oldsize = mimeview->ctree_mainbox->allocation.height;
1564                 gtk_widget_hide(mimeview->ctree_mainbox);
1565                 gtk_widget_show(mimeview->icon_mainbox);
1566                 gtk_paned_set_position(GTK_PANED(mimeview->paned), 0);
1567
1568                 gtkut_container_remove(GTK_CONTAINER(mimeview->ctree_mainbox), 
1569                                         button);
1570                 gtk_box_pack_start(GTK_BOX(mimeview->icon_mainbox), 
1571                                    button, FALSE, FALSE, 0);
1572                 gtk_box_reorder_child(GTK_BOX(button->parent), button, 0);
1573
1574         }
1575         gtk_widget_grab_focus(button);
1576         gtk_widget_unref(button);
1577
1578 }
1579
1580 void mimeview_update (MimeView *mimeview) {
1581         if (mimeview && mimeview->mimeinfo) {
1582                 icon_list_clear(mimeview);
1583                 icon_list_create(mimeview, mimeview->mimeinfo);
1584         }
1585 }