2006-09-04 [colin] 2.4.0cvs145
[claws.git] / src / mimeview.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2006 Hiroyuki Yamamoto and the Sylpheed-Claws team
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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 <glib/gi18n.h>
28 #include <gdk/gdkkeysyms.h>
29 #include <gtk/gtknotebook.h>
30 #include <gtk/gtkscrolledwindow.h>
31 #include <gtk/gtkctree.h>
32 #include <gtk/gtkvbox.h>
33 #include <gtk/gtkhbox.h>
34 #include <gtk/gtkvpaned.h>
35 #include <gtk/gtktext.h>
36 #include <gtk/gtksignal.h>
37 #include <gtk/gtkmenu.h>
38 #include <gtk/gtkdnd.h>
39 #include <gtk/gtkselection.h>
40 #include <gtk/gtktooltips.h>
41 #include <gtk/gtkcontainer.h>
42 #include <gtk/gtkbutton.h>
43 #include <stdio.h>
44
45 #ifndef HAVE_APACHE_FNMATCH
46 /* kludge: apache's fnmatch clashes with <regex.h>, don't include
47  * fnmatch.h */
48 #include <fnmatch.h>
49 #endif
50
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 "procheader.h"
64 #include "stock_pixmap.h"
65 #include "gtk/gtkvscrollbutton.h"
66 #include "gtk/logwindow.h"
67 #include "timing.h"
68
69 typedef enum
70 {
71         COL_MIMETYPE = 0,
72         COL_SIZE     = 1,
73         COL_NAME     = 2
74 } MimeViewColumnPos;
75
76 #define N_MIMEVIEW_COLS 3
77
78 static void mimeview_set_multipart_tree         (MimeView       *mimeview,
79                                                  MimeInfo       *mimeinfo,
80                                                  GtkCTreeNode   *parent);
81 static GtkCTreeNode *mimeview_append_part       (MimeView       *mimeview,
82                                                  MimeInfo       *partinfo,
83                                                  GtkCTreeNode   *parent);
84 static void mimeview_show_message_part          (MimeView       *mimeview,
85                                                  MimeInfo       *partinfo);
86 static void mimeview_change_view_type           (MimeView       *mimeview,
87                                                  MimeViewType    type);
88 gchar *mimeview_get_filename_for_part           (MimeInfo       *partinfo,
89                                                  const gchar    *basedir,
90                                                  gint            number);
91 static gboolean mimeview_write_part             (const gchar    *filename,
92                                                  MimeInfo       *partinfo);
93
94 static void mimeview_selected           (GtkCTree       *ctree,
95                                          GtkCTreeNode   *node,
96                                          gint            column,
97                                          MimeView       *mimeview);
98 static void mimeview_start_drag         (GtkWidget      *widget,
99                                          gint            button,
100                                          GdkEvent       *event,
101                                          MimeView       *mimeview);
102 static gint mimeview_button_pressed     (GtkWidget      *widget,
103                                          GdkEventButton *event,
104                                          MimeView       *mimeview);
105 static gint mimeview_key_pressed        (GtkWidget      *widget,
106                                          GdkEventKey    *event,
107                                          MimeView       *mimeview);
108
109 static void mimeview_drag_data_get      (GtkWidget        *widget,
110                                          GdkDragContext   *drag_context,
111                                          GtkSelectionData *selection_data,
112                                          guint             info,
113                                          guint             time,
114                                          MimeView         *mimeview);
115
116 static gboolean mimeview_scrolled       (GtkWidget      *widget,
117                                          GdkEventScroll *event,
118                                          MimeView       *mimeview);
119 static void mimeview_display_as_text    (MimeView       *mimeview);
120 static void mimeview_save_as            (MimeView       *mimeview);
121 static void mimeview_save_all           (MimeView       *mimeview);
122 static void mimeview_launch             (MimeView       *mimeview);
123 static void mimeview_open_with          (MimeView       *mimeview);
124 static void mimeview_open_part_with     (MimeView       *mimeview,
125                                          MimeInfo       *partinfo,
126                                          gboolean        automatic);
127 static void mimeview_view_file          (const gchar    *filename,
128                                          MimeInfo       *partinfo,
129                                          const gchar    *cmd,
130                                          MimeView       *mimeview);
131 static gboolean icon_clicked_cb         (GtkWidget      *button, 
132                                          GdkEventButton *event, 
133                                          MimeView       *mimeview);
134 static void icon_selected               (MimeView       *mimeview, 
135                                          gint            num, 
136                                          MimeInfo       *partinfo);
137 static gint icon_key_pressed            (GtkWidget      *button, 
138                                          GdkEventKey    *event,
139                                          MimeView       *mimeview);
140 static void toggle_icon                 (GtkToggleButton *button,
141                                          MimeView       *mimeview);
142 static void icon_list_append_icon       (MimeView       *mimeview, 
143                                          MimeInfo       *mimeinfo);
144 static void icon_list_create            (MimeView       *mimeview, 
145                                          MimeInfo       *mimeinfo);
146 static void icon_list_clear             (MimeView       *mimeview);
147 static void icon_list_toggle_by_mime_info (MimeView     *mimeview,
148                                            MimeInfo     *mimeinfo);
149 static gboolean icon_list_select_by_number(MimeView     *mimeview,
150                                            gint          number);
151 static void mime_toggle_button_cb       (GtkWidget      *button,
152                                          MimeView       *mimeview);
153 static gboolean part_button_pressed     (MimeView       *mimeview, 
154                                          GdkEventButton *event, 
155                                          MimeInfo       *partinfo);
156 static void icon_scroll_size_allocate_cb(GtkWidget      *widget, 
157                                          GtkAllocation  *layout_size, 
158                                          MimeView       *mimeview);
159
160 static GtkItemFactoryEntry mimeview_popup_entries[] =
161 {
162         {N_("/_Open"),            NULL, mimeview_launch,          0, NULL},
163         {N_("/Open _with..."),    NULL, mimeview_open_with,       0, NULL},
164         {N_("/_Display as text"), NULL, mimeview_display_as_text, 0, NULL},
165         {N_("/_Save as..."),      NULL, mimeview_save_as,         0, NULL},
166         {N_("/Save _all..."),     NULL, mimeview_save_all,        0, NULL},
167 };
168
169 static GtkTargetEntry mimeview_mime_types[] =
170 {
171         {"text/uri-list", 0, 0}
172 };
173
174 GSList *mimeviewer_factories;
175 GSList *mimeviews;
176
177 MimeView *mimeview_create(MainWindow *mainwin)
178 {
179         MimeView *mimeview;
180
181         GtkWidget *paned;
182         GtkWidget *scrolledwin;
183         GtkWidget *ctree;
184         GtkWidget *mime_notebook;
185         GtkWidget *popupmenu;
186         GtkWidget *ctree_mainbox;
187         GtkWidget *vbox;
188         GtkWidget *mime_toggle;
189         GtkWidget *icon_mainbox;
190         GtkWidget *icon_scroll;
191         GtkWidget *icon_vbox;
192         GtkWidget *arrow;
193         GtkWidget *scrollbutton;
194         GtkWidget *hbox;
195         GtkTooltips *tooltips;
196         GtkItemFactory *popupfactory;
197         NoticeView *siginfoview;
198         gchar *titles[N_MIMEVIEW_COLS];
199         gint n_entries;
200         gint i;
201
202         debug_print("Creating MIME view...\n");
203         mimeview = g_new0(MimeView, 1);
204
205         titles[COL_MIMETYPE] = _("MIME Type");
206         titles[COL_SIZE]     = _("Size");
207         titles[COL_NAME]     = _("Name");
208
209         scrolledwin = gtk_scrolled_window_new(NULL, NULL);
210         gtk_widget_show(scrolledwin);
211         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
212                                        GTK_POLICY_AUTOMATIC,
213                                        GTK_POLICY_AUTOMATIC);
214
215         ctree = gtk_sctree_new_with_titles(N_MIMEVIEW_COLS, 0, titles);
216         gtk_widget_show(ctree);
217         gtk_clist_set_selection_mode(GTK_CLIST(ctree), GTK_SELECTION_BROWSE);
218         gtk_ctree_set_line_style(GTK_CTREE(ctree), GTK_CTREE_LINES_NONE);
219         gtk_clist_set_column_justification(GTK_CLIST(ctree), COL_SIZE,
220                                            GTK_JUSTIFY_RIGHT);
221         gtk_clist_set_column_width(GTK_CLIST(ctree), COL_MIMETYPE, 240);
222         gtk_clist_set_column_width(GTK_CLIST(ctree), COL_SIZE, 90);
223         for (i = 0; i < N_MIMEVIEW_COLS; i++)
224                 GTK_WIDGET_UNSET_FLAGS(GTK_CLIST(ctree)->column[i].button,
225                                        GTK_CAN_FOCUS);
226         gtk_container_add(GTK_CONTAINER(scrolledwin), ctree);
227
228         g_signal_connect(G_OBJECT(ctree), "tree_select_row",
229                          G_CALLBACK(mimeview_selected), mimeview);
230         g_signal_connect(G_OBJECT(ctree), "button_release_event",
231                          G_CALLBACK(mimeview_button_pressed), mimeview);
232         g_signal_connect(G_OBJECT(ctree), "key_press_event",
233                          G_CALLBACK(mimeview_key_pressed), mimeview);
234         g_signal_connect(G_OBJECT (ctree),"start_drag",
235                          G_CALLBACK (mimeview_start_drag), mimeview);
236         g_signal_connect(G_OBJECT(ctree), "drag_data_get",
237                          G_CALLBACK(mimeview_drag_data_get), mimeview);
238
239         mime_notebook = gtk_notebook_new();
240         gtk_widget_show(mime_notebook);
241         GTK_WIDGET_UNSET_FLAGS(mime_notebook, GTK_CAN_FOCUS);
242         gtk_notebook_set_show_tabs(GTK_NOTEBOOK(mime_notebook), FALSE);
243         gtk_notebook_set_show_border(GTK_NOTEBOOK(mime_notebook), FALSE);
244         
245         icon_vbox = gtk_vbox_new(FALSE, 2);
246         gtk_widget_show(icon_vbox);
247         icon_scroll = gtk_layout_new(NULL, NULL);
248         gtk_widget_show(icon_scroll);
249         gtk_layout_put(GTK_LAYOUT(icon_scroll), icon_vbox, 0, 0);
250         scrollbutton = gtk_vscrollbutton_new(gtk_layout_get_vadjustment(GTK_LAYOUT(icon_scroll)));
251         gtk_widget_show(scrollbutton);
252
253         g_signal_connect(G_OBJECT(icon_scroll), "scroll_event",
254                          G_CALLBACK(mimeview_scrolled), mimeview);
255
256         mime_toggle = gtk_toggle_button_new();
257         gtk_widget_show(mime_toggle);
258         arrow = gtk_arrow_new(GTK_ARROW_LEFT, GTK_SHADOW_NONE);
259         gtk_widget_show(arrow);
260         gtk_container_add(GTK_CONTAINER(mime_toggle), arrow);
261         g_signal_connect(G_OBJECT(mime_toggle), "toggled", 
262                          G_CALLBACK(mime_toggle_button_cb), mimeview);
263
264         icon_mainbox = gtk_vbox_new(FALSE, 0);
265         gtk_widget_show(icon_mainbox);
266         gtk_box_pack_start(GTK_BOX(icon_mainbox), mime_toggle, FALSE, FALSE, 0);
267         gtk_box_pack_start(GTK_BOX(icon_mainbox), icon_scroll, TRUE, TRUE, 3);
268         gtk_box_pack_end(GTK_BOX(icon_mainbox), scrollbutton, FALSE, FALSE, 0);
269         g_signal_connect(G_OBJECT(icon_mainbox), "size_allocate", 
270                          G_CALLBACK(icon_scroll_size_allocate_cb), mimeview);
271         
272         ctree_mainbox = gtk_hbox_new(FALSE, 0); 
273         gtk_box_pack_start(GTK_BOX(ctree_mainbox), scrolledwin, TRUE, TRUE, 0);
274
275         n_entries = sizeof(mimeview_popup_entries) /
276                 sizeof(mimeview_popup_entries[0]);
277         popupmenu = menu_create_items(mimeview_popup_entries, n_entries,
278                                       "<MimeView>", &popupfactory, mimeview);
279         tooltips = gtk_tooltips_new();
280         gtk_tooltips_set_delay(tooltips, 0); 
281
282         vbox = gtk_vbox_new(FALSE, 0);
283         gtk_widget_show(vbox);
284         siginfoview = noticeview_create(mainwin);
285         noticeview_hide(siginfoview);
286         noticeview_set_icon_clickable(siginfoview, TRUE);
287         gtk_box_pack_start(GTK_BOX(vbox), mime_notebook, TRUE, TRUE, 0);
288         gtk_box_pack_end(GTK_BOX(vbox), GTK_WIDGET_PTR(siginfoview), FALSE, FALSE, 0);
289
290         paned = gtk_vpaned_new();
291         gtk_widget_show(paned);
292         gtk_paned_set_gutter_size(GTK_PANED(paned), 0);
293         gtk_paned_pack1(GTK_PANED(paned), ctree_mainbox, FALSE, TRUE);
294         gtk_paned_pack2(GTK_PANED(paned), vbox, TRUE, TRUE);
295         
296         hbox = gtk_hbox_new(FALSE, 0);
297         gtk_box_pack_start(GTK_BOX(hbox), paned, TRUE, TRUE, 0);
298         gtk_box_pack_start(GTK_BOX(hbox), icon_mainbox, FALSE, FALSE, 0);
299
300         gtk_widget_show(hbox);
301         gtk_widget_hide(ctree_mainbox);
302
303         mimeview->hbox          = hbox;
304         mimeview->paned         = paned;
305         mimeview->scrolledwin   = scrolledwin;
306         mimeview->ctree         = ctree;
307         mimeview->mime_notebook = mime_notebook;
308         mimeview->popupmenu     = popupmenu;
309         mimeview->popupfactory  = popupfactory;
310         mimeview->type          = -1;
311         mimeview->ctree_mainbox = ctree_mainbox;
312         mimeview->icon_scroll   = icon_scroll;
313         mimeview->icon_vbox     = icon_vbox;
314         mimeview->icon_mainbox  = icon_mainbox;
315         mimeview->icon_count    = 0;
316         mimeview->mainwin       = mainwin;
317         mimeview->tooltips      = tooltips;
318         mimeview->oldsize       = 60;
319         mimeview->mime_toggle   = mime_toggle;
320         mimeview->siginfoview   = siginfoview;
321         mimeview->scrollbutton  = scrollbutton;
322         mimeview->target_list   = gtk_target_list_new(mimeview_mime_types, 1); 
323         
324         mimeviews = g_slist_prepend(mimeviews, mimeview);
325
326         return mimeview;
327 }
328
329 void mimeview_init(MimeView *mimeview)
330 {
331         textview_init(mimeview->textview);
332
333         gtk_container_add(GTK_CONTAINER(mimeview->mime_notebook),
334                 GTK_WIDGET_PTR(mimeview->textview));
335 }
336
337 void mimeview_show_message(MimeView *mimeview, MimeInfo *mimeinfo,
338                            const gchar *file)
339 {
340         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
341         GtkCTreeNode *node;
342
343         mimeview_clear(mimeview);
344
345         g_return_if_fail(file != NULL);
346         g_return_if_fail(mimeinfo != NULL);
347
348         mimeview->mimeinfo = mimeinfo;
349
350         mimeview->file = g_strdup(file);
351
352         g_signal_handlers_block_by_func(G_OBJECT(ctree), mimeview_selected,
353                                         mimeview);
354
355         mimeview_set_multipart_tree(mimeview, mimeinfo, NULL);
356         icon_list_create(mimeview, mimeinfo);
357
358         g_signal_handlers_unblock_by_func(G_OBJECT(ctree),
359                                           mimeview_selected, mimeview);
360
361         node = GTK_CTREE_NODE(GTK_CLIST(ctree)->row_list);
362         if (node) {
363                 gtk_ctree_select(ctree, node);
364                 icon_list_toggle_by_mime_info
365                         (mimeview, gtk_ctree_node_get_row_data(ctree, node));
366                 gtkut_ctree_set_focus_row(ctree, node);
367         }
368 }
369
370 #ifdef USE_PTHREAD
371 static void mimeview_check_sig_cancel_now(MimeView *mimeview);
372 #endif
373
374 static void mimeview_free_mimeinfo(MimeView *mimeview)
375 {
376         gboolean defer = FALSE;
377 #ifdef USE_PTHREAD
378         defer = (mimeview->check_data != NULL);
379         if (defer)
380                 mimeview->check_data->free_after_use = TRUE;
381 #endif
382         if (mimeview->mimeinfo != NULL && !defer) {
383                 procmime_mimeinfo_free_all(mimeview->mimeinfo);
384                 mimeview->mimeinfo = NULL;
385         } else if (defer) {
386 #ifdef USE_PTHREAD
387                 debug_print("deferring free(mimeinfo) and cancelling check\n");
388                 mimeview_check_sig_cancel_now(mimeview);
389 #endif
390         }
391 }
392
393 void mimeview_destroy(MimeView *mimeview)
394 {
395         GSList *cur;
396         
397         for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
398                 MimeViewer *viewer = (MimeViewer *) cur->data;
399                 viewer->destroy_viewer(viewer);
400         }
401         g_slist_free(mimeview->viewers);
402         gtk_target_list_unref(mimeview->target_list);
403
404         mimeview_free_mimeinfo(mimeview);
405 #ifdef USE_PTHREAD
406         if (mimeview->check_data) {
407                 mimeview->check_data->destroy_mimeview = TRUE;
408                 debug_print("deferring destroy\n");
409         } else 
410 #endif
411         {
412                 g_free(mimeview->file);
413                 g_free(mimeview);
414                 mimeviews = g_slist_remove(mimeviews, mimeview);
415         }
416         
417 }
418
419 MimeInfo *mimeview_get_selected_part(MimeView *mimeview)
420 {
421         return gtk_ctree_node_get_row_data
422                 (GTK_CTREE(mimeview->ctree), mimeview->opened);
423 }
424
425 static void mimeview_set_multipart_tree(MimeView *mimeview,
426                                         MimeInfo *mimeinfo,
427                                         GtkCTreeNode *parent)
428 {
429         GtkCTreeNode *node;
430
431         g_return_if_fail(mimeinfo != NULL);
432
433         while (mimeinfo != NULL) {
434                 node = mimeview_append_part(mimeview, mimeinfo, parent);
435
436                 if (mimeinfo->node->children)
437                         mimeview_set_multipart_tree(mimeview, (MimeInfo *) mimeinfo->node->children->data, node);
438                 mimeinfo = mimeinfo->node->next != NULL ? (MimeInfo *) mimeinfo->node->next->data : NULL;
439         }
440 }
441
442 static const gchar *get_real_part_name(MimeInfo *partinfo)
443 {
444         const gchar *name = NULL;
445
446         name = procmime_mimeinfo_get_parameter(partinfo, "filename");
447         if (name == NULL)
448                 name = procmime_mimeinfo_get_parameter(partinfo, "name");
449
450         return name;
451 }
452
453 static const gchar *get_part_name(MimeInfo *partinfo)
454 {
455         const gchar *name;
456
457         name = get_real_part_name(partinfo);
458         if (name == NULL)
459                 name = "";
460
461         return name;
462 }
463
464 static const gchar *get_part_description(MimeInfo *partinfo)
465 {
466         if (partinfo->description)
467                 return partinfo->description;
468         else
469                 return get_part_name(partinfo);
470 }
471
472 static GtkCTreeNode *mimeview_append_part(MimeView *mimeview,
473                                           MimeInfo *partinfo,
474                                           GtkCTreeNode *parent)
475 {
476         GtkCTree *ctree = GTK_CTREE(mimeview->ctree);
477         GtkCTreeNode *node;
478         static gchar content_type[64];
479         gchar *str[N_MIMEVIEW_COLS];
480
481         if (partinfo->type != MIMETYPE_UNKNOWN && partinfo->subtype) {
482                 g_snprintf(content_type, 64, "%s/%s", procmime_get_media_type_str(partinfo->type), partinfo->subtype);
483         } else {
484                 g_snprintf(content_type, 64, "UNKNOWN");
485         }
486
487         str[COL_MIMETYPE] = content_type;
488         str[COL_SIZE] = to_human_readable(partinfo->length);
489         if (prefs_common.attach_desc)
490                 str[COL_NAME] = (gchar *) get_part_description(partinfo);
491         else
492                 str[COL_NAME] = (gchar *) get_part_name(partinfo);
493
494         node = gtk_sctree_insert_node(ctree, parent, NULL, str, 0,
495                                      NULL, NULL, NULL, NULL,
496                                      FALSE, TRUE);
497         gtk_ctree_node_set_row_data(ctree, node, partinfo);
498
499         return node;
500 }
501
502 static void mimeview_show_message_part(MimeView *mimeview, MimeInfo *partinfo)
503 {
504         FILE *fp;
505         const gchar *fname;
506
507         if (!partinfo) return;
508         if (partinfo == mimeview->opened)
509                 return;
510
511         fname = mimeview->file;
512         if (!fname) return;
513
514         if ((fp = g_fopen(fname, "rb")) == NULL) {
515                 FILE_OP_ERROR(fname, "fopen");
516                 return;
517         }
518
519         if (fseek(fp, partinfo->offset, SEEK_SET) < 0) {
520                 FILE_OP_ERROR(mimeview->file, "fseek");
521                 fclose(fp);
522                 return;
523         }
524
525         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
526         textview_show_part(mimeview->textview, partinfo, fp);
527
528         fclose(fp);
529 }
530
531 static MimeViewer *get_viewer_for_content_type(MimeView *mimeview, const gchar *content_type)
532 {
533         GSList *cur;
534         MimeViewerFactory *factory = NULL;
535         MimeViewer *viewer = NULL;
536
537 /*
538  * FNM_CASEFOLD is a GNU extension
539  * if its not defined copy the string to the stack and
540  * convert the copy to lower case
541  */
542 #ifndef FNM_CASEFOLD
543 #define FNM_CASEFOLD 0
544         Xstrdup_a(content_type, content_type, return NULL);
545         g_strdown((gchar *)content_type);
546 #endif
547         
548         for (cur = mimeviewer_factories; cur != NULL; cur = g_slist_next(cur)) {
549                 MimeViewerFactory *curfactory = cur->data;
550                 gint i = 0;
551
552                 while (curfactory->content_types[i] != NULL) {
553                         if(!fnmatch(curfactory->content_types[i], content_type, FNM_CASEFOLD)) {
554                                 debug_print("%s\n", curfactory->content_types[i]);
555                                 factory = curfactory;
556                                 break;
557                         }
558                         i++;
559                 }
560                 if (factory != NULL)
561                         break;
562         }
563         if (factory == NULL)
564                 return NULL;
565
566         for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
567                 MimeViewer *curviewer = cur->data;
568                 
569                 if (curviewer->factory == factory)
570                         return curviewer;
571         }
572         viewer = factory->create_viewer();
573         gtk_container_add(GTK_CONTAINER(mimeview->mime_notebook),
574                 GTK_WIDGET(viewer->get_widget(viewer)));
575                 
576         mimeview->viewers = g_slist_append(mimeview->viewers, viewer);
577
578         return viewer;
579 }
580
581 static MimeViewer *get_viewer_for_mimeinfo(MimeView *mimeview, MimeInfo *partinfo)
582 {
583         gchar *content_type = NULL;
584         MimeViewer *viewer = NULL;
585
586         if ((partinfo->type == MIMETYPE_APPLICATION) &&
587             (!g_ascii_strcasecmp(partinfo->subtype, "octet-stream"))) {
588                 const gchar *filename;
589
590                 filename = procmime_mimeinfo_get_parameter(partinfo, "filename");
591                 if (filename == NULL)
592                         filename = procmime_mimeinfo_get_parameter(partinfo, "name");
593                 if (filename != NULL)
594                         content_type = procmime_get_mime_type(filename);
595         } else {
596                 content_type = procmime_get_content_type_str(partinfo->type, partinfo->subtype);
597         }
598
599         if (content_type != NULL) {
600                 viewer = get_viewer_for_content_type(mimeview, content_type);
601                 g_free(content_type);
602         }
603
604         return viewer;
605 }
606
607 gboolean mimeview_show_part(MimeView *mimeview, MimeInfo *partinfo)
608 {
609         MimeViewer *viewer;
610         
611         viewer = get_viewer_for_mimeinfo(mimeview, partinfo);
612         if (viewer == NULL) {
613                 if (mimeview->mimeviewer != NULL)
614                         mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
615                 mimeview->mimeviewer = NULL;
616                 return FALSE;
617         }
618
619         if (mimeview->mimeviewer != NULL)
620                 mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
621
622         if (mimeview->mimeviewer != viewer) {
623                 mimeview->mimeviewer = viewer;
624                 mimeview_change_view_type(mimeview, MIMEVIEW_VIEWER);
625         }
626         viewer->mimeview = mimeview;
627         viewer->show_mimepart(viewer, mimeview->file, partinfo);
628
629         return TRUE;
630 }
631
632 static void mimeview_change_view_type(MimeView *mimeview, MimeViewType type)
633 {
634         TextView  *textview  = mimeview->textview;
635
636         if ((mimeview->type != MIMEVIEW_VIEWER) && 
637             (mimeview->type == type)) return;
638
639         switch (type) {
640         case MIMEVIEW_TEXT:
641                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mimeview->mime_notebook),
642                         gtk_notebook_page_num(GTK_NOTEBOOK(mimeview->mime_notebook), 
643                         GTK_WIDGET_PTR(textview)));
644                 break;
645         case MIMEVIEW_VIEWER:
646                 gtk_notebook_set_current_page(GTK_NOTEBOOK(mimeview->mime_notebook),
647                         gtk_notebook_page_num(GTK_NOTEBOOK(mimeview->mime_notebook), 
648                         GTK_WIDGET(mimeview->mimeviewer->get_widget(mimeview->mimeviewer))));
649                 break;
650         default:
651                 return;
652         }
653
654         mimeview->type = type;
655 }
656
657 void mimeview_clear(MimeView *mimeview)
658 {
659         GtkCList *clist = NULL;
660         
661         if (!mimeview)
662                 return;
663
664         if (g_slist_find(mimeviews, mimeview) == NULL)
665                 return;
666         
667         clist = GTK_CLIST(mimeview->ctree);
668         
669         noticeview_hide(mimeview->siginfoview);
670
671         gtk_clist_clear(clist);
672         textview_clear(mimeview->textview);
673         if (mimeview->mimeviewer != NULL)
674                 mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
675
676         mimeview_free_mimeinfo(mimeview);
677
678         mimeview->mimeinfo = NULL;
679
680         mimeview->opened = NULL;
681
682         g_free(mimeview->file);
683         mimeview->file = NULL;
684
685         icon_list_clear(mimeview);
686         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
687 }
688
689 static void check_signature_cb(GtkWidget *widget, gpointer user_data);
690 void mimeview_check_signature(MimeView *mimeview);
691 static void display_full_info_cb(GtkWidget *widget, gpointer user_data);
692
693 static void update_signature_noticeview(MimeView *mimeview, MimeInfo *mimeinfo, 
694                                         gboolean special, SignatureStatus code)
695 {
696         gchar *text = NULL, *button_text = NULL;
697         void  *func = NULL;
698         StockPixmap icon = STOCK_PIXMAP_PRIVACY_SIGNED;
699         SignatureStatus mycode = SIGNATURE_UNCHECKED;
700         
701         g_return_if_fail(mimeview != NULL);
702         g_return_if_fail(mimeinfo != NULL);
703         
704         if (special)
705                 mycode = code;
706         else 
707                 mycode = privacy_mimeinfo_get_sig_status(mimeinfo);
708
709         switch (mycode) {
710         case SIGNATURE_UNCHECKED:
711                 button_text = _("Check signature");
712                 func = check_signature_cb;
713                 icon = STOCK_PIXMAP_PRIVACY_SIGNED;
714                 break;
715         case SIGNATURE_OK:
716                 button_text = _("View full information");
717                 func = display_full_info_cb;
718                 icon = STOCK_PIXMAP_PRIVACY_PASSED;
719                 break;
720         case SIGNATURE_WARN:
721                 button_text = _("View full information");
722                 func = display_full_info_cb;
723                 icon = STOCK_PIXMAP_PRIVACY_WARN;
724                 break;
725         case SIGNATURE_INVALID:
726                 button_text = _("View full information");
727                 func = display_full_info_cb;
728                 icon = STOCK_PIXMAP_PRIVACY_FAILED;
729                 break;
730         case SIGNATURE_CHECK_FAILED:
731                 button_text = _("Check again");
732                 func = check_signature_cb;
733                 icon = STOCK_PIXMAP_PRIVACY_UNKNOWN;
734         case SIGNATURE_CHECK_TIMEOUT:
735                 button_text = _("Check again");
736                 func = check_signature_cb;
737                 icon = STOCK_PIXMAP_PRIVACY_UNKNOWN;
738         default:
739                 break;
740         }
741         if (mycode == SIGNATURE_UNCHECKED) {
742                 gchar *tmp = privacy_mimeinfo_sig_info_short(mimeinfo);
743                 text = g_strdup_printf("%s %s",
744                         tmp, _("Click the icon or hit 'C' to check it."));
745                 g_free(tmp);
746         } else if (mycode != SIGNATURE_CHECK_TIMEOUT) {
747                 text = privacy_mimeinfo_sig_info_short(mimeinfo);
748         } else if (mycode == SIGNATURE_CHECK_TIMEOUT) {
749                 text = g_strdup(_("Timeout checking the signature. Click the icon or hit 'C' to try again."));
750         }
751
752         noticeview_set_text(mimeview->siginfoview, text);
753         gtk_label_set_selectable(GTK_LABEL(mimeview->siginfoview->text), TRUE);
754
755         g_free(text);
756         noticeview_set_button_text(mimeview->siginfoview, NULL);
757         noticeview_set_button_press_callback(
758                 mimeview->siginfoview,
759                 G_CALLBACK(func),
760                 (gpointer) mimeview);
761         noticeview_set_icon(mimeview->siginfoview, icon);
762         noticeview_set_tooltip(mimeview->siginfoview, button_text);
763 }
764
765 #ifdef USE_PTHREAD
766
767 /* reset all thread stuff, and do the cleanups we've been left to do */
768 static void mimeview_check_data_reset(MimeView *mimeview)
769 {
770         if (!mimeview->check_data)
771                 return;
772
773         if (mimeview->check_data->free_after_use) {
774                 debug_print("freeing deferred mimeinfo\n");
775                 procmime_mimeinfo_free_all(mimeview->check_data->siginfo);
776         }
777         if (mimeview->check_data->destroy_mimeview) {
778                 debug_print("freeing deferred mimeview\n");
779                 g_free(mimeview->file);
780                 g_free(mimeview);
781                 mimeviews = g_slist_remove(mimeviews, mimeview);
782         }
783
784         g_free(mimeview->check_data);
785         mimeview->check_data = NULL;
786 }
787
788 /* GUI update once the checker thread is done or killed */
789 static gboolean mimeview_check_sig_thread_cb(void *data)
790 {
791         MimeView *mimeview = (MimeView *) data;
792         MimeInfo *mimeinfo = mimeview->siginfo;
793
794         debug_print("mimeview_check_sig_thread_cb\n");
795         
796         if (mimeinfo == NULL) {
797                 /* message changed !? */
798                 g_warning("no more siginfo!\n");
799                 goto end;
800         }
801         
802         if (!mimeview->check_data) {
803                 g_warning("nothing to check\n");
804                 return FALSE;
805         }
806
807         if (mimeview->check_data->siginfo != mimeinfo) {
808                 /* message changed !? */
809                 g_warning("different siginfo!\n");
810                 goto end;
811         }
812
813         if (mimeview->check_data->destroy_mimeview ||
814             mimeview->check_data->free_after_use) {
815                 debug_print("not bothering, we're changing message\n"); 
816                 goto end;
817         }
818         
819         /* update status */
820         if (mimeview->check_data->timeout) 
821                 update_signature_noticeview(mimeview, mimeview->siginfo, 
822                         TRUE, SIGNATURE_CHECK_TIMEOUT);
823         else
824                 update_signature_noticeview(mimeview, mimeview->siginfo, 
825                         FALSE, 0);
826         icon_list_clear(mimeview);
827         icon_list_create(mimeview, mimeview->mimeinfo);
828         
829 end:
830         mimeview_check_data_reset(mimeview);
831         return FALSE;
832 }
833
834 /* sig checker thread */
835 static void *mimeview_check_sig_worker_thread(void *data)
836 {
837         MimeView *mimeview = (MimeView *)data;
838         MimeInfo *mimeinfo = mimeview->siginfo;
839         
840         debug_print("checking...\n");
841
842         if (!mimeview->check_data)
843                 return NULL;
844
845         if (mimeinfo && mimeinfo == mimeview->check_data->siginfo)
846                 privacy_mimeinfo_check_signature(mimeinfo);
847         else {
848                 /* that's strange! we changed message without 
849                  * getting killed. */
850                 g_warning("different siginfo!\n");
851                 mimeview_check_data_reset(mimeview);
852                 return NULL;
853         }
854
855         /* use g_timeout so that GUI updates is done from the
856          * correct thread */
857         g_timeout_add(0,mimeview_check_sig_thread_cb,mimeview);
858         
859         return NULL;
860 }
861
862 /* killer thread - acts when the checker didn't work fast
863  * enough. */
864 static void *mimeview_check_sig_cancel_thread(void *data)
865 {
866         MimeView *mimeview = (MimeView *)data;
867         
868         if (!mimeview->check_data)
869                 return NULL; /* nothing to kill ! */
870
871         /* wait for a few seconds... */
872         debug_print("waiting a while\n");
873
874         sleep(5);
875         
876         if (!mimeview->check_data)
877                 return NULL; /* nothing to kill, it's done in time :) */
878         
879         /* too late, go away checker thread */
880         debug_print("killing checker thread\n");
881         pthread_cancel(mimeview->check_data->th);
882         
883         /* tell upstream it was a timeout */
884         mimeview->check_data->timeout = TRUE;
885         /* use g_timeout so that GUI updates is done from the
886          * correct thread */
887         g_timeout_add(0,mimeview_check_sig_thread_cb,mimeview);
888
889         return NULL;
890 }
891
892 /* get rid of the checker thread right now - used when changing the
893  * displayed message for example. */
894 static void mimeview_check_sig_cancel_now(MimeView *mimeview)
895 {
896         if (!mimeview->check_data)
897                 return;
898         debug_print("killing checker thread NOW\n");
899         pthread_cancel(mimeview->check_data->th);
900
901         /* tell upstream it was a timeout */
902         mimeview->check_data->timeout = TRUE;
903         mimeview_check_sig_thread_cb(mimeview);
904         return;
905 }
906
907 /* creates a thread to check the signature, and a second one
908  * to kill the first one after a timeout */
909 static void mimeview_check_sig_in_thread(MimeView *mimeview)
910 {
911         pthread_t th, th2;
912         pthread_attr_t detach, detach2;
913         
914         if (mimeview->check_data) {
915                 g_warning("already checking it");
916                 return;
917         }
918         
919         mimeview->check_data = g_new0(SigCheckData, 1);
920         mimeview->check_data->siginfo = mimeview->siginfo;
921         debug_print("creating thread\n");
922
923         pthread_attr_init(&detach);
924         pthread_attr_setdetachstate(&detach, TRUE);
925
926         pthread_attr_init(&detach2);
927         pthread_attr_setdetachstate(&detach2, TRUE);
928
929         /* create the checker thread */
930         if (pthread_create(&th, &detach, 
931                         mimeview_check_sig_worker_thread, 
932                         mimeview) != 0) {
933                 /* arh. We'll do it synchronously. */
934                 g_warning("can't create thread");
935                 g_free(mimeview->check_data);
936                 mimeview->check_data = NULL;
937                 return;
938         } else 
939                 mimeview->check_data->th = th;
940
941         /* create the killer thread */
942         pthread_create(&th2, &detach2, 
943                         mimeview_check_sig_cancel_thread, 
944                         mimeview);
945 }
946 #endif
947
948 static void check_signature_cb(GtkWidget *widget, gpointer user_data)
949 {
950         MimeView *mimeview = (MimeView *) user_data;
951         MimeInfo *mimeinfo = mimeview->siginfo;
952         
953         if (mimeinfo == NULL)
954                 return;
955 #ifdef USE_PTHREAD
956         if (mimeview->check_data)
957                 return;
958 #endif
959         noticeview_set_text(mimeview->siginfoview, _("Checking signature..."));
960         GTK_EVENTS_FLUSH();
961 #if (defined USE_PTHREAD && defined __GLIBC__ && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 3)))
962         /* let's do it non-blocking */
963         mimeview_check_sig_in_thread(mimeview);
964         if (!mimeview->check_data) /* let's check syncronously */
965 #endif
966         {
967                 debug_print("checking without thread\n");
968                 privacy_mimeinfo_check_signature(mimeinfo);
969                 update_signature_noticeview(mimeview, mimeview->siginfo, FALSE, 0);
970                 icon_list_clear(mimeview);
971                 icon_list_create(mimeview, mimeview->mimeinfo);
972         }
973 }
974
975 void mimeview_check_signature(MimeView *mimeview)
976 {
977         check_signature_cb(NULL, mimeview);     
978 }
979
980 static void redisplay_email(GtkWidget *widget, gpointer user_data)
981 {
982         MimeView *mimeview = (MimeView *) user_data;
983         GtkCTreeNode *node = mimeview->opened;
984         mimeview->opened = NULL;
985         mimeview_selected(GTK_CTREE(mimeview->ctree), node, 0, mimeview);
986 }
987
988 static void display_full_info_cb(GtkWidget *widget, gpointer user_data)
989 {
990         MimeView *mimeview = (MimeView *) user_data;
991         gchar *siginfo;
992
993         siginfo = privacy_mimeinfo_sig_info_full(mimeview->siginfo);
994         textview_set_text(mimeview->textview, siginfo);
995         g_free(siginfo);
996         noticeview_set_button_text(mimeview->siginfoview, NULL);
997         noticeview_set_button_press_callback(
998                 mimeview->siginfoview,
999                 G_CALLBACK(redisplay_email),
1000                 (gpointer) mimeview);
1001         noticeview_set_tooltip(mimeview->siginfoview, _("Go back to email"));
1002 }
1003
1004 static void update_signature_info(MimeView *mimeview, MimeInfo *selected)
1005 {
1006         MimeInfo *siginfo;
1007         MimeInfo *first_text;
1008         
1009         g_return_if_fail(mimeview != NULL);
1010         g_return_if_fail(selected != NULL);
1011         
1012         if (selected->type == MIMETYPE_MESSAGE 
1013         &&  !g_ascii_strcasecmp(selected->subtype, "rfc822")) {
1014                 /* if the first text part is signed, check that */
1015                 first_text = selected;
1016                 while (first_text && first_text->type != MIMETYPE_TEXT) {
1017                         first_text = procmime_mimeinfo_next(first_text);
1018                 }
1019                 if (first_text) {
1020                         update_signature_info(mimeview, first_text);
1021                         return;
1022                 }       
1023         }
1024
1025         siginfo = selected;
1026         while (siginfo != NULL) {
1027                 if (privacy_mimeinfo_is_signed(siginfo))
1028                         break;
1029                 siginfo = procmime_mimeinfo_parent(siginfo);
1030         }
1031         mimeview->siginfo = siginfo;
1032         
1033         if (siginfo == NULL) {
1034                 noticeview_hide(mimeview->siginfoview);
1035                 return;
1036         }
1037         
1038         update_signature_noticeview(mimeview, siginfo, FALSE, 0);
1039         noticeview_show(mimeview->siginfoview);
1040 }
1041
1042 static void mimeview_selected(GtkCTree *ctree, GtkCTreeNode *node, gint column,
1043                               MimeView *mimeview)
1044 {
1045         MimeInfo *partinfo;
1046         if (mimeview->opened == node) return;
1047         mimeview->opened = node;
1048         mimeview->spec_part = NULL;
1049         gtk_ctree_node_moveto(ctree, node, -1, 0.5, 0);
1050
1051         partinfo = gtk_ctree_node_get_row_data(ctree, node);
1052         if (!partinfo) return;
1053
1054         /* ungrab the mouse event */
1055         if (GTK_WIDGET_HAS_GRAB(ctree)) {
1056                 gtk_grab_remove(GTK_WIDGET(ctree));
1057                 if (gdk_pointer_is_grabbed())
1058                         gdk_pointer_ungrab(GDK_CURRENT_TIME);
1059         }
1060         
1061         mimeview->textview->default_text = FALSE;
1062
1063         update_signature_info(mimeview, partinfo);
1064
1065         if (!mimeview_show_part(mimeview, partinfo)) {
1066                 switch (partinfo->type) {
1067                 case MIMETYPE_TEXT:
1068                 case MIMETYPE_MESSAGE:
1069                 case MIMETYPE_MULTIPART:
1070                         mimeview_show_message_part(mimeview, partinfo);
1071                 
1072                         break;
1073                 default:
1074                         mimeview->textview->default_text = TRUE;
1075                         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1076                         textview_clear(mimeview->textview);
1077                         textview_show_mime_part(mimeview->textview, partinfo);
1078                         break;
1079                 }
1080         }
1081 }
1082
1083 static void mimeview_start_drag(GtkWidget *widget, gint button,
1084                                 GdkEvent *event, MimeView *mimeview)
1085 {
1086         GdkDragContext *context;
1087         MimeInfo *partinfo;
1088
1089         g_return_if_fail(mimeview != NULL);
1090
1091         partinfo = mimeview_get_selected_part(mimeview);
1092         if (partinfo->disposition == DISPOSITIONTYPE_INLINE) return;
1093
1094         context = gtk_drag_begin(widget, mimeview->target_list,
1095                                  GDK_ACTION_COPY, button, event);
1096         gtk_drag_set_icon_default(context);
1097 }
1098
1099 static gint mimeview_button_pressed(GtkWidget *widget, GdkEventButton *event,
1100                                     MimeView *mimeview)
1101 {
1102         GtkCList *clist = GTK_CLIST(widget);
1103         gint row, column;
1104
1105         if (!event) return FALSE;
1106
1107         if (event->button == 2 || event->button == 3) {
1108                 if (!gtk_clist_get_selection_info(clist, event->x, event->y,
1109                                                   &row, &column))
1110                         return FALSE;
1111                 gtk_clist_unselect_all(clist);
1112                 gtk_clist_select_row(clist, row, column);
1113                 gtkut_clist_set_focus_row(clist, row);
1114         }
1115         part_button_pressed(mimeview, event, mimeview_get_selected_part(mimeview));
1116
1117         return FALSE;
1118 }
1119
1120 static gboolean mimeview_scrolled(GtkWidget *widget, GdkEventScroll *event,
1121                                     MimeView *mimeview)
1122 {
1123         GtkVScrollbutton *scrollbutton = (GtkVScrollbutton *)mimeview->scrollbutton;
1124         if (event->direction == GDK_SCROLL_UP) {
1125                 scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD;
1126         } else {
1127                 scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD;
1128         }
1129         gtk_vscrollbutton_scroll(scrollbutton);
1130         return TRUE;
1131 }
1132
1133 /* from gdkevents.c */
1134 #define DOUBLE_CLICK_TIME 250
1135
1136 static gboolean part_button_pressed(MimeView *mimeview, GdkEventButton *event, 
1137                                     MimeInfo *partinfo)
1138 {
1139         static MimeInfo *lastinfo;
1140         static guint32 lasttime;
1141
1142         if (event->button == 2 ||
1143             (event->button == 1 && (event->time - lasttime) < DOUBLE_CLICK_TIME && lastinfo == partinfo)) {
1144                 /* call external program for image, audio or html */
1145                 mimeview_launch(mimeview);
1146                 return TRUE;
1147         } else if (event->button == 3) {
1148                 if (partinfo && (partinfo->type == MIMETYPE_MESSAGE ||
1149                                  partinfo->type == MIMETYPE_IMAGE ||
1150                                  partinfo->type == MIMETYPE_MULTIPART))
1151                         menu_set_sensitive(mimeview->popupfactory,
1152                                            "/Display as text", FALSE);
1153                 else
1154                         menu_set_sensitive(mimeview->popupfactory,
1155                                            "/Display as text", TRUE);
1156                 if (partinfo &&
1157                     partinfo->type == MIMETYPE_APPLICATION &&
1158                     !g_ascii_strcasecmp(partinfo->subtype, "octet-stream"))
1159                         menu_set_sensitive(mimeview->popupfactory,
1160                                            "/Open", FALSE);
1161                 else
1162                         menu_set_sensitive(mimeview->popupfactory,
1163                                            "/Open", TRUE);
1164
1165                 g_object_set_data(G_OBJECT(mimeview->popupmenu),
1166                                   "pop_partinfo", partinfo);
1167                                     
1168                 gtk_menu_popup(GTK_MENU(mimeview->popupmenu),
1169                                NULL, NULL, NULL, NULL,
1170                                event->button, event->time);
1171                 return TRUE;
1172         }
1173
1174         lastinfo = partinfo;
1175         lasttime = event->time;
1176         return FALSE;
1177 }
1178
1179
1180 void mimeview_pass_key_press_event(MimeView *mimeview, GdkEventKey *event)
1181 {
1182         mimeview_key_pressed(mimeview->ctree, event, mimeview);
1183 }
1184
1185 #define BREAK_ON_MODIFIER_KEY() \
1186         if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break
1187
1188 #define KEY_PRESS_EVENT_STOP() \
1189         g_signal_stop_emission_by_name(G_OBJECT(ctree), \
1190                                        "key_press_event");
1191
1192 static gint mimeview_key_pressed(GtkWidget *widget, GdkEventKey *event,
1193                                  MimeView *mimeview)
1194 {
1195         SummaryView *summaryview;
1196         GtkCTree *ctree = GTK_CTREE(widget);
1197         GtkCTreeNode *node;
1198
1199         if (!event) return FALSE;
1200         if (!mimeview->opened) return FALSE;
1201
1202         summaryview = mimeview->messageview->mainwin->summaryview;
1203         
1204         if (summaryview && quicksearch_has_focus(summaryview->quicksearch))
1205                 return FALSE;
1206                 
1207         switch (event->keyval) {
1208         case GDK_space:
1209                 if (mimeview_scroll_page(mimeview, FALSE))
1210                         return TRUE;
1211
1212                 node = GTK_CTREE_NODE_NEXT(mimeview->opened);
1213                 if (node) {
1214                         gtk_sctree_unselect_all(GTK_SCTREE(ctree));
1215                         gtk_sctree_select(GTK_SCTREE(ctree), node);
1216                         return TRUE;
1217                 }
1218                 break;
1219         case GDK_BackSpace:
1220                 mimeview_scroll_page(mimeview, TRUE);
1221                 return TRUE;
1222         case GDK_Return:
1223                 mimeview_scroll_one_line(mimeview,
1224                                          (event->state & GDK_MOD1_MASK) != 0);
1225                 return TRUE;
1226         case GDK_n:
1227         case GDK_N:
1228                 BREAK_ON_MODIFIER_KEY();
1229                 if (!GTK_CTREE_NODE_NEXT(mimeview->opened)) break;
1230                 KEY_PRESS_EVENT_STOP();
1231                 g_signal_emit_by_name(G_OBJECT(ctree), "scroll_vertical",
1232                                         GTK_SCROLL_STEP_FORWARD, 0.0);
1233                 return TRUE;
1234         case GDK_p:
1235         case GDK_P:
1236                 BREAK_ON_MODIFIER_KEY();
1237                 if (!GTK_CTREE_NODE_PREV(mimeview->opened)) break;
1238                 KEY_PRESS_EVENT_STOP();
1239                 g_signal_emit_by_name(G_OBJECT(ctree), "scroll_vertical",
1240                                         GTK_SCROLL_STEP_BACKWARD, 0.0);
1241                 return TRUE;
1242         case GDK_y:
1243                 BREAK_ON_MODIFIER_KEY();
1244                 KEY_PRESS_EVENT_STOP();
1245                 mimeview_save_as(mimeview);
1246                 return TRUE;
1247         case GDK_t:
1248                 BREAK_ON_MODIFIER_KEY();
1249                 KEY_PRESS_EVENT_STOP();
1250                 mimeview_display_as_text(mimeview);
1251                 return TRUE;    
1252         case GDK_l:
1253                 BREAK_ON_MODIFIER_KEY();
1254                 KEY_PRESS_EVENT_STOP();
1255                 mimeview_launch(mimeview);
1256                 return TRUE;
1257         case GDK_o:
1258                 BREAK_ON_MODIFIER_KEY();
1259                 KEY_PRESS_EVENT_STOP();
1260                 mimeview_open_with(mimeview);
1261                 return TRUE;
1262         case GDK_c:
1263                 BREAK_ON_MODIFIER_KEY();
1264                 KEY_PRESS_EVENT_STOP();
1265                 mimeview_check_signature(mimeview);
1266                 return TRUE;
1267         default:
1268                 break;
1269         }
1270
1271         if (!mimeview->messageview->mainwin) return FALSE;
1272
1273         summary_pass_key_press_event(summaryview, event);
1274         return TRUE;
1275 }
1276
1277 static void mimeview_drag_data_get(GtkWidget        *widget,
1278                                    GdkDragContext   *drag_context,
1279                                    GtkSelectionData *selection_data,
1280                                    guint             info,
1281                                    guint             time,
1282                                    MimeView         *mimeview)
1283 {
1284         gchar *filename = NULL, *uriname, *tmp;
1285         MimeInfo *partinfo;
1286
1287         if (!mimeview->opened) return;
1288         if (!mimeview->file) return;
1289
1290         partinfo = mimeview_get_selected_part(mimeview);
1291         if (!partinfo) return;
1292
1293         if (strlen(get_part_name(partinfo)) > 0) {
1294                 filename = g_path_get_basename(get_part_name(partinfo));
1295                 if (*filename == '\0') return;
1296         } else if (partinfo->type == MIMETYPE_MESSAGE 
1297                    && !g_ascii_strcasecmp(partinfo->subtype, "rfc822")) {
1298                 gchar *name = NULL;
1299                 GPtrArray *headers = NULL;
1300                 FILE *fp;
1301
1302                 fp = g_fopen(partinfo->data.filename, "rb");
1303                 fseek(fp, partinfo->offset, SEEK_SET);
1304                 headers = procheader_get_header_array_asis(fp);
1305                 if (headers) {
1306                         gint i;
1307                         for (i = 0; i < headers->len; i++) {
1308                                 Header *header = g_ptr_array_index(headers, i);
1309                                 if (procheader_headername_equal(header->name, "Subject")) {
1310                                         unfold_line(header->body);
1311                                         name = g_strconcat(header->body, ".txt", NULL);
1312                                         subst_for_filename(name);
1313                                 }
1314                         }
1315                         procheader_header_array_destroy(headers);
1316                 }
1317                 fclose(fp);
1318                 if (name)
1319                         filename = g_path_get_basename(name);
1320                 g_free(name);
1321         }
1322         if (filename == NULL)
1323                 filename = g_path_get_basename("Unnamed part");
1324                 
1325
1326         tmp = g_filename_from_utf8(filename, -1, NULL, NULL, NULL);
1327         
1328         if (tmp == NULL) {
1329                 g_warning("filename not in UTF-8");
1330                 tmp = g_strdup("Unnamed part");
1331         }
1332         filename = g_strconcat(get_mime_tmp_dir(), G_DIR_SEPARATOR_S,
1333                                tmp, NULL);
1334
1335         g_free(tmp);
1336
1337         if (procmime_get_part(filename, partinfo) < 0)
1338                 alertpanel_error
1339                         (_("Can't save the part of multipart message."));
1340         uriname = g_strconcat("file://", filename, "\r\n", NULL);
1341
1342         gtk_selection_data_set(selection_data, selection_data->target, 8,
1343                                (guchar *)uriname, strlen(uriname));
1344
1345         g_free(uriname);
1346         g_free(filename);
1347 }
1348
1349 /**
1350  * Returns a filename (with path) for an attachment
1351  * \param partinfo The attachment to save
1352  * \param basedir The target directory
1353  * \param number Used for dummy filename if attachment is unnamed
1354  */
1355 gchar *mimeview_get_filename_for_part(MimeInfo *partinfo,
1356                                       const gchar *basedir,
1357                                       gint number)
1358 {
1359         gchar *fullname;
1360         gchar *filename;
1361
1362         filename = g_strdup(get_part_name(partinfo));
1363         if (!filename || !*filename)
1364                 filename = g_strdup_printf("noname.%d", number);
1365
1366         if (!g_utf8_validate(filename, -1, NULL)) {
1367                 gchar *tmp = conv_filename_to_utf8(filename);
1368                 g_free(filename);
1369                 filename = tmp;
1370         }
1371         
1372         subst_for_filename(filename);
1373
1374         fullname = g_strconcat
1375                 (basedir, G_DIR_SEPARATOR_S, (filename[0] == G_DIR_SEPARATOR)
1376                  ? &filename[1] : filename, NULL);
1377
1378         g_free(filename);
1379         filename = conv_filename_from_utf8(fullname);
1380         g_free(fullname);
1381         return filename;
1382 }
1383
1384 /**
1385  * Write a single attachment to file
1386  * \param filename Filename with path
1387  * \param partinfo Attachment to save
1388  */
1389 static gboolean mimeview_write_part(const gchar *filename,
1390                                     MimeInfo *partinfo)
1391 {
1392         gchar *dir;
1393         
1394         dir= g_path_get_dirname(filename);
1395         if (!is_dir_exist(dir))
1396                 make_dir_hier(dir);
1397         g_free(dir);
1398
1399         if (is_file_exist(filename)) {
1400                 AlertValue aval;
1401                 gchar *res;
1402                 gchar *tmp;
1403                 
1404                 if (!g_utf8_validate(filename, -1, NULL))
1405                         tmp = conv_filename_to_utf8(filename);
1406                 else 
1407                         tmp = g_strdup(filename);
1408                 
1409                 res = g_strdup_printf(_("Overwrite existing file '%s'?"),
1410                                       tmp);
1411                 g_free(tmp);
1412                 aval = alertpanel(_("Overwrite"), res, GTK_STOCK_CANCEL, 
1413                                   GTK_STOCK_OK, NULL);
1414                 g_free(res);                                      
1415                 if (G_ALERTALTERNATE != aval) return FALSE;
1416         }
1417
1418         if (procmime_get_part(filename, partinfo) < 0) {
1419                 alertpanel_error
1420                         (_("Can't save the part of multipart message."));
1421                 return FALSE;
1422         }
1423
1424         return TRUE;
1425 }
1426
1427 /**
1428  * Menu callback: Save all attached files
1429  * \param mimeview Current display
1430  */
1431 static void mimeview_save_all(MimeView *mimeview)
1432 {
1433         MimeInfo *partinfo;
1434         gchar *dirname;
1435         gchar *startdir = NULL;
1436         gint number = 1;
1437
1438         if (!mimeview->opened) return;
1439         if (!mimeview->file) return;
1440         if (!mimeview->mimeinfo) return;
1441
1442         partinfo = mimeview->mimeinfo;
1443         if (prefs_common.attach_save_dir)
1444                 startdir = g_strconcat(prefs_common.attach_save_dir,
1445                                        G_DIR_SEPARATOR_S, NULL);
1446
1447         dirname = filesel_select_file_save_folder(_("Select destination folder"), startdir);
1448         if (!dirname) {
1449                 g_free(startdir);
1450                 return;
1451         }
1452
1453         if (!is_dir_exist (dirname)) {
1454                 alertpanel_error(_("'%s' is not a directory."),
1455                                  dirname);
1456                 g_free(startdir);
1457                 return;
1458         }
1459
1460         if (dirname[strlen(dirname)-1] == G_DIR_SEPARATOR)
1461                 dirname[strlen(dirname)-1] = '\0';
1462
1463         /* Skip the first part, that is sometimes DISPOSITIONTYPE_UNKNOWN */
1464         if (partinfo && partinfo->type == MIMETYPE_MESSAGE)
1465                 partinfo = procmime_mimeinfo_next(partinfo);
1466         if (partinfo && partinfo->type == MIMETYPE_MULTIPART) {
1467                 partinfo = procmime_mimeinfo_next(partinfo);
1468                 if (partinfo && partinfo->type == MIMETYPE_TEXT)
1469                         partinfo = procmime_mimeinfo_next(partinfo);
1470         }
1471                 
1472         while (partinfo != NULL) {
1473                 if (partinfo->type != MIMETYPE_MESSAGE &&
1474                     partinfo->type != MIMETYPE_MULTIPART &&
1475                     (partinfo->disposition != DISPOSITIONTYPE_INLINE
1476                      || get_real_part_name(partinfo) != NULL)) {
1477                         gchar *filename = mimeview_get_filename_for_part
1478                                 (partinfo, dirname, number++);
1479
1480                         mimeview_write_part(filename, partinfo);
1481                         g_free(filename);
1482                 }
1483                 partinfo = procmime_mimeinfo_next(partinfo);
1484         }
1485
1486         g_free(prefs_common.attach_save_dir);
1487         g_free(startdir);
1488         prefs_common.attach_save_dir = g_strdup(dirname);
1489 }
1490
1491 static MimeInfo *mimeview_get_part_to_use(MimeView *mimeview)
1492 {
1493         MimeInfo *partinfo = NULL;
1494         if (mimeview->spec_part) {
1495                 partinfo = mimeview->spec_part;
1496                 mimeview->spec_part = NULL;
1497         } else {
1498                 partinfo = mimeview_get_selected_part(mimeview);
1499                 if (!partinfo) { 
1500                         partinfo = (MimeInfo *) g_object_get_data
1501                                  (G_OBJECT(mimeview->popupmenu),
1502                                  "pop_partinfo");
1503                         g_object_set_data(G_OBJECT(mimeview->popupmenu),
1504                                           "pop_partinfo", NULL);
1505                 }                        
1506         }
1507
1508         return partinfo;
1509 }
1510 /**
1511  * Menu callback: Save the selected attachment
1512  * \param mimeview Current display
1513  */
1514 static void mimeview_save_as(MimeView *mimeview)
1515 {
1516         gchar *filename;
1517         gchar *filepath = NULL;
1518         gchar *filedir = NULL;
1519         MimeInfo *partinfo;
1520         gchar *partname = NULL;
1521
1522         if (!mimeview->opened) return;
1523         if (!mimeview->file) return;
1524
1525         partinfo = mimeview_get_part_to_use(mimeview);
1526
1527         g_return_if_fail(partinfo != NULL);
1528         
1529         if (get_part_name(partinfo) == NULL) {
1530                 return;
1531         }
1532         partname = g_strdup(get_part_name(partinfo));
1533         
1534         if (!g_utf8_validate(partname, -1, NULL)) {
1535                 gchar *tmp = conv_filename_to_utf8(partname);
1536                 g_free(partname);
1537                 partname = tmp;
1538         }
1539
1540         subst_for_filename(partname);
1541         
1542         if (prefs_common.attach_save_dir)
1543                 filepath = g_strconcat(prefs_common.attach_save_dir,
1544                                        G_DIR_SEPARATOR_S, partname, NULL);
1545         else
1546                 filepath = g_strdup(partname);
1547
1548         g_free(partname);
1549
1550         filename = filesel_select_file_save(_("Save as"), filepath);
1551         if (!filename) {
1552                 g_free(filepath);
1553                 return;
1554         }
1555
1556         mimeview_write_part(filename, partinfo);
1557
1558         filedir = g_path_get_dirname(filename);
1559         if (filedir && strcmp(filedir, ".")) {
1560                 g_free(prefs_common.attach_save_dir);
1561                 prefs_common.attach_save_dir = g_strdup(filedir);
1562         }
1563
1564         g_free(filedir);
1565         g_free(filepath);
1566 }
1567
1568 static void mimeview_display_as_text(MimeView *mimeview)
1569 {
1570         MimeInfo *partinfo;
1571
1572         if (!mimeview->opened) return;
1573
1574         partinfo = mimeview_get_part_to_use(mimeview);
1575
1576         g_return_if_fail(partinfo != NULL);
1577         mimeview_show_message_part(mimeview, partinfo);
1578 }
1579
1580 static void mimeview_launch(MimeView *mimeview)
1581 {
1582         MimeInfo *partinfo;
1583         gchar *filename;
1584
1585         if (!mimeview->opened) return;
1586         if (!mimeview->file) return;
1587
1588         partinfo = mimeview_get_part_to_use(mimeview);
1589
1590         g_return_if_fail(partinfo != NULL);
1591
1592         filename = procmime_get_tmp_file_name(partinfo);
1593
1594         if (procmime_get_part(filename, partinfo) < 0)
1595                 alertpanel_error
1596                         (_("Can't save the part of multipart message."));
1597         else
1598                 mimeview_view_file(filename, partinfo, NULL, mimeview);
1599
1600         g_free(filename);
1601 }
1602
1603 static void mimeview_open_with(MimeView *mimeview)
1604 {
1605         MimeInfo *partinfo;
1606
1607         if (!mimeview) return;
1608         if (!mimeview->opened) return;
1609         if (!mimeview->file) return;
1610
1611         partinfo = mimeview_get_part_to_use(mimeview);
1612
1613         mimeview_open_part_with(mimeview, partinfo, FALSE);
1614 }
1615
1616 static void mimeview_open_part_with(MimeView *mimeview, MimeInfo *partinfo, gboolean automatic)
1617 {
1618         gchar *filename;
1619         gchar *cmd;
1620         gchar *mime_command = NULL;
1621         gchar *content_type = NULL;
1622
1623         g_return_if_fail(partinfo != NULL);
1624
1625         filename = procmime_get_tmp_file_name(partinfo);
1626
1627         if (procmime_get_part(filename, partinfo) < 0) {
1628                 alertpanel_error
1629                         (_("Can't save the part of multipart message."));
1630                 g_free(filename);
1631                 return;
1632         }
1633
1634         if (!prefs_common.mime_open_cmd_history)
1635                 prefs_common.mime_open_cmd_history =
1636                         add_history(NULL, prefs_common.mime_open_cmd);
1637
1638         if ((partinfo->type == MIMETYPE_APPLICATION) &&
1639             (!g_ascii_strcasecmp(partinfo->subtype, "octet-stream"))) {
1640                 /* guess content-type from filename */
1641                 content_type = procmime_get_mime_type(filename);
1642         } 
1643         if (content_type == NULL) {
1644                 content_type = procmime_get_content_type_str(partinfo->type,
1645                         partinfo->subtype);
1646         }
1647         
1648         if ((partinfo->type == MIMETYPE_TEXT && !strcmp(partinfo->subtype, "html"))
1649         && prefs_common.uri_cmd && prefs_common.uri_cmd[0]) {
1650                 mime_command = g_strdup(prefs_common.uri_cmd);
1651                 g_free(content_type);
1652                 content_type = NULL;
1653         } else if (partinfo->type != MIMETYPE_TEXT || !prefs_common.ext_editor_cmd
1654         ||  !prefs_common.ext_editor_cmd[0]) {
1655                 mime_command = mailcap_get_command_for_type(content_type, filename);
1656         } else {
1657                 mime_command = g_strdup(prefs_common.ext_editor_cmd);
1658                 g_free(content_type);
1659                 content_type = NULL;
1660         }
1661         if (mime_command == NULL) {
1662                 /* try with extension this time */
1663                 g_free(content_type);
1664                 content_type = procmime_get_mime_type(filename);
1665                 mime_command = mailcap_get_command_for_type(content_type, filename);
1666         }
1667
1668         if (mime_command == NULL)
1669                 automatic = FALSE;
1670         
1671         if (!automatic) {
1672                 gboolean remember = FALSE;
1673                 if (content_type != NULL)
1674                         cmd = input_dialog_combo_remember
1675                                 (_("Open with"),
1676                                  _("Enter the command line to open file:\n"
1677                                    "('%s' will be replaced with file name)"),
1678                                  mime_command ? mime_command : prefs_common.mime_open_cmd,
1679                                  prefs_common.mime_open_cmd_history,
1680                                  TRUE, &remember);
1681                 else
1682                         cmd = input_dialog_combo
1683                                 (_("Open with"),
1684                                  _("Enter the command line to open file:\n"
1685                                    "('%s' will be replaced with file name)"),
1686                                  mime_command ? mime_command : prefs_common.mime_open_cmd,
1687                                  prefs_common.mime_open_cmd_history,
1688                                  TRUE);
1689                 if (cmd && remember) {
1690                         mailcap_update_default(content_type, cmd);
1691                 }
1692                 g_free(mime_command);
1693         } else {
1694                 cmd = mime_command;
1695         }
1696         if (cmd) {
1697                 mimeview_view_file(filename, partinfo, cmd, mimeview);
1698                 g_free(prefs_common.mime_open_cmd);
1699                 prefs_common.mime_open_cmd = cmd;
1700                 prefs_common.mime_open_cmd_history =
1701                         add_history(prefs_common.mime_open_cmd_history, cmd);
1702         }
1703         g_free(content_type);
1704         g_free(filename);
1705 }
1706
1707 static void mimeview_view_file(const gchar *filename, MimeInfo *partinfo,
1708                                const gchar *cmd, MimeView *mimeview)
1709 {
1710         gchar *p;
1711         gchar buf[BUFFSIZE];
1712         if (cmd == NULL)
1713                 mimeview_open_part_with(mimeview, partinfo, TRUE);
1714         else {
1715                 if ((p = strchr(cmd, '%')) && *(p + 1) == 's' &&
1716                     !strchr(p + 2, '%'))
1717                         g_snprintf(buf, sizeof(buf), cmd, filename);
1718                 else {
1719                         g_warning("MIME viewer command line is invalid: '%s'", cmd);
1720                         mimeview_open_part_with(mimeview, partinfo, FALSE);
1721                 }
1722                 if (execute_command_line(buf, TRUE) != 0)
1723                         mimeview_open_part_with(mimeview, partinfo, FALSE);
1724         }
1725 }
1726
1727 void mimeview_register_viewer_factory(MimeViewerFactory *factory)
1728 {
1729         mimeviewer_factories = g_slist_append(mimeviewer_factories, factory);
1730 }
1731
1732 static gint cmp_viewer_by_factroy(gconstpointer a, gconstpointer b)
1733 {
1734         return ((MimeViewer *) a)->factory == (MimeViewerFactory *) b ? 0 : -1;
1735 }
1736
1737 void mimeview_unregister_viewer_factory(MimeViewerFactory *factory)
1738 {
1739         GSList *mimeview_list, *viewer_list;
1740
1741         for (mimeview_list = mimeviews; mimeview_list != NULL; mimeview_list = g_slist_next(mimeview_list)) {
1742                 MimeView *mimeview = (MimeView *) mimeview_list->data;
1743                 
1744                 if (mimeview->mimeviewer && mimeview->mimeviewer->factory == factory) {
1745                         mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
1746                         mimeview->mimeviewer = NULL;
1747                 }
1748
1749                 while ((viewer_list = g_slist_find_custom(mimeview->viewers, factory, cmp_viewer_by_factroy)) != NULL) {
1750                         MimeViewer *mimeviewer = (MimeViewer *) viewer_list->data;
1751
1752                         mimeviewer->destroy_viewer(mimeviewer);
1753                         mimeview->viewers = g_slist_remove(mimeview->viewers, mimeviewer);
1754                 }
1755         }
1756
1757         mimeviewer_factories = g_slist_remove(mimeviewer_factories, factory);
1758 }
1759
1760 static gboolean icon_clicked_cb (GtkWidget *button, GdkEventButton *event, MimeView *mimeview)
1761 {
1762         gint      num;
1763         MimeInfo *partinfo;
1764
1765         num      = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "icon_number"));
1766         partinfo = g_object_get_data(G_OBJECT(button), "partinfo");
1767
1768         icon_selected(mimeview, num, partinfo);
1769         gtk_widget_grab_focus(button);
1770         if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
1771                 toggle_icon(GTK_TOGGLE_BUTTON(button), mimeview);
1772                 if (event->button == 2 || event->button == 3)
1773                         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
1774                                                      TRUE);
1775         }
1776
1777         part_button_pressed(mimeview, event, partinfo);
1778
1779         return FALSE;
1780 }
1781
1782 static void icon_selected (MimeView *mimeview, gint num, MimeInfo *partinfo)
1783 {
1784         GtkCTreeNode *node;
1785         node = gtk_ctree_find_by_row_data(GTK_CTREE(mimeview->ctree), NULL, partinfo);
1786         if (node)
1787                 gtk_ctree_select(GTK_CTREE(mimeview->ctree), node);
1788 }               
1789
1790 void mimeview_select_mimepart_icon(MimeView *mimeview, MimeInfo *partinfo)
1791 {
1792         icon_list_toggle_by_mime_info(mimeview, partinfo);
1793         icon_selected(mimeview, -1, partinfo);
1794 }
1795
1796 #undef  KEY_PRESS_EVENT_STOP
1797 #define KEY_PRESS_EVENT_STOP() \
1798         g_signal_stop_emission_by_name(G_OBJECT(button), \
1799                                        "key_press_event");
1800
1801 static gint icon_key_pressed(GtkWidget *button, GdkEventKey *event,
1802                              MimeView *mimeview)
1803 {
1804         gint          num;
1805         MimeInfo     *partinfo;
1806         SummaryView  *summaryview;
1807         TextView     *textview;
1808
1809         num      = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "icon_number"));
1810         partinfo = g_object_get_data(G_OBJECT(button), "partinfo");
1811         
1812         if (!event) return FALSE;
1813
1814         textview = mimeview->textview;
1815
1816         switch (event->keyval) {
1817         case GDK_space:
1818                 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
1819                         /* stop the button being untoggled */
1820                         KEY_PRESS_EVENT_STOP();
1821                         if (mimeview_scroll_page(mimeview, FALSE))
1822                                 return TRUE;
1823
1824                         if (icon_list_select_by_number(mimeview, num + 1))
1825                                 return TRUE;
1826                 } else {
1827                         icon_selected(mimeview, num, partinfo);
1828                         toggle_icon(GTK_TOGGLE_BUTTON(button), mimeview);
1829                         return TRUE;
1830                 }
1831
1832                 break;
1833         case GDK_BackSpace:
1834                 mimeview_scroll_page(mimeview, TRUE);
1835                 return TRUE;
1836         case GDK_Return:
1837                 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
1838                         KEY_PRESS_EVENT_STOP();
1839                         mimeview_scroll_one_line(mimeview,
1840                                                  (event->state & GDK_MOD1_MASK) != 0);
1841                         return TRUE;
1842                 } else {
1843                         icon_selected(mimeview, num, partinfo);
1844                         toggle_icon(GTK_TOGGLE_BUTTON(button), mimeview);
1845                         return TRUE;
1846                 }
1847
1848         case GDK_n:
1849         case GDK_N:
1850                 BREAK_ON_MODIFIER_KEY();
1851                 if (icon_list_select_by_number(mimeview, num + 1)) {
1852                         KEY_PRESS_EVENT_STOP();
1853                         return TRUE;
1854                 }
1855                 break;
1856                 
1857         case GDK_p:
1858         case GDK_P:
1859                 BREAK_ON_MODIFIER_KEY();
1860                 if (icon_list_select_by_number(mimeview, num - 1)) {
1861                         KEY_PRESS_EVENT_STOP();
1862                         return TRUE;
1863                 }
1864                 break;
1865
1866         case GDK_y:
1867                 BREAK_ON_MODIFIER_KEY();
1868                 KEY_PRESS_EVENT_STOP();
1869                 mimeview_save_as(mimeview);
1870                 return TRUE;
1871         case GDK_t:
1872                 BREAK_ON_MODIFIER_KEY();
1873                 KEY_PRESS_EVENT_STOP();
1874                 mimeview_display_as_text(mimeview);
1875                 return TRUE;    
1876         case GDK_l:
1877                 BREAK_ON_MODIFIER_KEY();
1878                 KEY_PRESS_EVENT_STOP();
1879                 mimeview_launch(mimeview);
1880                 return TRUE;
1881         case GDK_o:
1882                 BREAK_ON_MODIFIER_KEY();
1883                 KEY_PRESS_EVENT_STOP();
1884                 mimeview_open_with(mimeview);
1885                 return TRUE;
1886         case GDK_c:
1887                 BREAK_ON_MODIFIER_KEY();
1888                 KEY_PRESS_EVENT_STOP();
1889                 mimeview_check_signature(mimeview);
1890                 return TRUE;
1891         default:
1892                 break;
1893         }
1894
1895         if (!mimeview->messageview->mainwin) return FALSE;
1896         summaryview = mimeview->messageview->mainwin->summaryview;
1897         return summary_pass_key_press_event(summaryview, event);
1898 }
1899
1900 static void toggle_icon(GtkToggleButton *button, MimeView *mimeview)
1901 {
1902         GList *child;
1903         
1904         child = gtk_container_get_children(GTK_CONTAINER(mimeview->icon_vbox));
1905         for (; child != NULL; child = g_list_next(child)) {
1906                 if (GTK_IS_TOGGLE_BUTTON(child->data) && 
1907                     GTK_TOGGLE_BUTTON(child->data) != button &&
1908                     gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(child->data)))
1909                         gtk_toggle_button_set_active
1910                                 (GTK_TOGGLE_BUTTON(child->data),
1911                                  FALSE);
1912         }
1913 }
1914
1915 static void icon_list_append_icon (MimeView *mimeview, MimeInfo *mimeinfo) 
1916 {
1917         GtkWidget *pixmap = NULL;
1918         GtkWidget *vbox;
1919         GtkWidget *button;
1920         gchar *tip;
1921         gchar *tiptmp;
1922         const gchar *desc = NULL; 
1923         gchar *sigshort = NULL;
1924         gchar *content_type;
1925         StockPixmap stockp;
1926         MimeInfo *partinfo;
1927         MimeInfo *siginfo = NULL;
1928         MimeInfo *encrypted = NULL;
1929         
1930         vbox = mimeview->icon_vbox;
1931         mimeview->icon_count++;
1932         button = gtk_toggle_button_new();
1933         gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE);
1934         g_object_set_data(G_OBJECT(button), "icon_number", 
1935                           GINT_TO_POINTER(mimeview->icon_count));
1936         g_object_set_data(G_OBJECT(button), "partinfo", 
1937                           mimeinfo);
1938         
1939         switch (mimeinfo->type) {
1940                 
1941         case MIMETYPE_TEXT:
1942                 if (mimeinfo->subtype && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
1943                         stockp = STOCK_PIXMAP_MIME_TEXT_HTML;
1944                 else if  (mimeinfo->subtype && !g_ascii_strcasecmp(mimeinfo->subtype, "enriched"))
1945                         stockp = STOCK_PIXMAP_MIME_TEXT_ENRICHED;
1946                 else
1947                         stockp = STOCK_PIXMAP_MIME_TEXT_PLAIN;
1948                 break;
1949         case MIMETYPE_MESSAGE:
1950                 stockp = STOCK_PIXMAP_MIME_MESSAGE;
1951                 break;
1952         case MIMETYPE_APPLICATION:
1953                 stockp = STOCK_PIXMAP_MIME_APPLICATION;
1954                 break;
1955         case MIMETYPE_IMAGE:
1956                 stockp = STOCK_PIXMAP_MIME_IMAGE;
1957                 break;
1958         case MIMETYPE_AUDIO:
1959                 stockp = STOCK_PIXMAP_MIME_AUDIO;
1960                 break;
1961         default:
1962                 stockp = STOCK_PIXMAP_MIME_UNKNOWN;
1963                 break;
1964         }
1965         
1966         partinfo = mimeinfo;
1967         while (partinfo != NULL) {
1968                 if (privacy_mimeinfo_is_signed(partinfo)) {
1969                         siginfo = partinfo;
1970                         break;
1971                 }
1972                 if (privacy_mimeinfo_is_encrypted(partinfo)) {
1973                         encrypted = partinfo;
1974                         break;
1975                 }
1976                 partinfo = procmime_mimeinfo_parent(partinfo);
1977         }       
1978
1979         if (siginfo != NULL) {
1980                 switch (privacy_mimeinfo_get_sig_status(siginfo)) {
1981                 case SIGNATURE_UNCHECKED:
1982                 case SIGNATURE_CHECK_FAILED:
1983                 case SIGNATURE_CHECK_TIMEOUT:
1984                         pixmap = stock_pixmap_widget_with_overlay(mimeview->mainwin->window, stockp,
1985                             STOCK_PIXMAP_PRIVACY_EMBLEM_SIGNED, OVERLAY_BOTTOM_RIGHT, 6, 3);
1986                         break;
1987                 case SIGNATURE_OK:
1988                         pixmap = stock_pixmap_widget_with_overlay(mimeview->mainwin->window, stockp,
1989                             STOCK_PIXMAP_PRIVACY_EMBLEM_PASSED, OVERLAY_BOTTOM_RIGHT, 6, 3);
1990                         break;
1991                 case SIGNATURE_WARN:
1992                         pixmap = stock_pixmap_widget_with_overlay(mimeview->mainwin->window, stockp,
1993                             STOCK_PIXMAP_PRIVACY_EMBLEM_WARN, OVERLAY_BOTTOM_RIGHT, 6, 3);
1994                         break;
1995                 case SIGNATURE_INVALID:
1996                         pixmap = stock_pixmap_widget_with_overlay(mimeview->mainwin->window, stockp,
1997                             STOCK_PIXMAP_PRIVACY_EMBLEM_FAILED, OVERLAY_BOTTOM_RIGHT, 6, 3);
1998                         break;
1999                 }
2000                 sigshort = privacy_mimeinfo_sig_info_short(siginfo);
2001         } else if (encrypted != NULL) {
2002                         pixmap = stock_pixmap_widget_with_overlay(mimeview->mainwin->window, stockp,
2003                             STOCK_PIXMAP_PRIVACY_EMBLEM_ENCRYPTED, OVERLAY_BOTTOM_RIGHT, 6, 3);         
2004         } else {
2005                 pixmap = stock_pixmap_widget_with_overlay(mimeview->mainwin->window, stockp, 0,
2006                                                           OVERLAY_NONE, 6, 3);
2007         }
2008         gtk_container_add(GTK_CONTAINER(button), pixmap);
2009         
2010         if (!desc) {
2011                 if (prefs_common.attach_desc)
2012                         desc = get_part_description(mimeinfo);
2013                 else
2014                         desc = get_part_name(mimeinfo);
2015         }
2016
2017         content_type = procmime_get_content_type_str(mimeinfo->type,
2018                                                      mimeinfo->subtype);
2019
2020         tip = g_strjoin("\n", content_type,
2021                         to_human_readable(mimeinfo->length), NULL);
2022         g_free(content_type);
2023         if (desc && *desc) {
2024                 gchar *tmp = NULL;
2025                 if (!g_utf8_validate(desc, -1, NULL)) {
2026                         tmp = conv_filename_to_utf8(desc);
2027                 } else {
2028                         tmp = g_strdup(desc);
2029                 }
2030                 tiptmp = g_strjoin("\n", tmp, tip, NULL);
2031                 g_free(tip);
2032                 tip = tiptmp;
2033                 g_free(tmp);
2034         }
2035         if (sigshort && *sigshort) {
2036                 tiptmp = g_strjoin("\n", tip, sigshort, NULL);
2037                 g_free(tip);
2038                 tip = tiptmp;
2039         }
2040         g_free(sigshort);
2041
2042         gtk_tooltips_set_tip(mimeview->tooltips, button, tip, NULL);
2043         g_free(tip);
2044         gtk_widget_show_all(button);
2045         gtk_drag_source_set(button, GDK_BUTTON1_MASK|GDK_BUTTON3_MASK, 
2046                             mimeview_mime_types, 1, GDK_ACTION_COPY);
2047         g_signal_connect(G_OBJECT(button), "button_release_event", 
2048                          G_CALLBACK(icon_clicked_cb), mimeview);
2049         g_signal_connect(G_OBJECT(button), "key_press_event", 
2050                          G_CALLBACK(icon_key_pressed), mimeview);
2051         g_signal_connect(G_OBJECT(button), "drag_data_get",
2052                          G_CALLBACK(mimeview_drag_data_get), mimeview);
2053         gtk_box_pack_start(GTK_BOX(vbox), button, FALSE, FALSE, 0);
2054
2055 }
2056
2057 static void icon_list_clear (MimeView *mimeview)
2058 {
2059         GList     *child;
2060         GtkAdjustment *adj;
2061         
2062         child = gtk_container_children(GTK_CONTAINER(mimeview->icon_vbox));
2063         for (; child != NULL; child = g_list_next(child)) {
2064                 gtkut_container_remove(GTK_CONTAINER(mimeview->icon_vbox), 
2065                                        GTK_WIDGET(child->data));
2066                 gtk_widget_destroy(GTK_WIDGET(child->data));
2067         }
2068         g_list_free(child);
2069         mimeview->icon_count = 0;
2070         adj  = gtk_layout_get_vadjustment(GTK_LAYOUT(mimeview->icon_scroll));
2071         gtk_adjustment_set_value(adj, adj->lower);
2072 }
2073
2074 static void icon_list_toggle_by_mime_info(MimeView      *mimeview,
2075                                           MimeInfo      *mimeinfo)
2076 {
2077         GList *child;
2078         
2079         child = gtk_container_children(GTK_CONTAINER(mimeview->icon_vbox));
2080         for (; child != NULL; child = g_list_next(child)) {
2081                 if (GTK_IS_TOGGLE_BUTTON(child->data) &&  
2082                     g_object_get_data(G_OBJECT(child->data),
2083                                       "partinfo") == (gpointer)mimeinfo) {
2084                         toggle_icon(GTK_TOGGLE_BUTTON(child->data), mimeview);
2085                         gtk_toggle_button_set_active
2086                                 (GTK_TOGGLE_BUTTON(child->data), TRUE);
2087                 }                                
2088         }
2089 }
2090
2091 /*!
2092  *\brief        Used to 'click' the next or previous icon.
2093  *
2094  *\return       true if the icon 'number' exists and was selected.
2095  */
2096 static gboolean icon_list_select_by_number(MimeView     *mimeview,
2097                                            gint          number)
2098 {
2099         GList *child;
2100
2101         if (number == 0) return FALSE;
2102         child = gtk_container_children(GTK_CONTAINER(mimeview->icon_vbox));
2103         for (; child != NULL; child = g_list_next(child)) {
2104                 if (GTK_IS_TOGGLE_BUTTON(child->data) &&  
2105                     GPOINTER_TO_INT(g_object_get_data(G_OBJECT(child->data),
2106                                         "icon_number")) == number) {
2107                         icon_selected(mimeview, number,
2108                                       (MimeInfo*)g_object_get_data(G_OBJECT(child->data),
2109                                                                    "partinfo"));
2110                         toggle_icon(GTK_TOGGLE_BUTTON(child->data), mimeview);
2111                         gtk_toggle_button_set_active
2112                                 (GTK_TOGGLE_BUTTON(child->data), TRUE);
2113                         gtk_widget_grab_focus(GTK_WIDGET(child->data));
2114                 
2115                         return TRUE;
2116                 }                                
2117         }
2118         return FALSE;
2119 }
2120
2121 static void icon_scroll_size_allocate_cb(GtkWidget *widget, 
2122                                          GtkAllocation *size, MimeView *mimeview)
2123 {
2124         GtkAllocation *mainbox_size;
2125         GtkAllocation *vbox_size;
2126         GtkAllocation *layout_size;
2127         GtkAdjustment *adj;
2128         
2129         adj = gtk_layout_get_vadjustment(GTK_LAYOUT(mimeview->icon_scroll));
2130
2131         mainbox_size = &mimeview->icon_mainbox->allocation;
2132         vbox_size = &mimeview->icon_vbox->allocation;
2133         layout_size = &mimeview->icon_scroll->allocation;
2134                 
2135         gtk_layout_set_size(GTK_LAYOUT(mimeview->icon_scroll), 
2136                             GTK_LAYOUT(mimeview->icon_scroll)->width, 
2137                             MAX(vbox_size->height, layout_size->height));
2138         adj->step_increment = 10;
2139 }
2140
2141 static void icon_list_create(MimeView *mimeview, MimeInfo *mimeinfo)
2142 {
2143         GtkRequisition size;
2144
2145         g_return_if_fail(mimeinfo != NULL);
2146
2147         while (mimeinfo != NULL) {
2148                 if (mimeinfo->type != MIMETYPE_MULTIPART)
2149                         icon_list_append_icon(mimeview, mimeinfo);
2150                 if (mimeinfo->node->children != NULL)
2151                         icon_list_create(mimeview, 
2152                                 (MimeInfo *) mimeinfo->node->children->data);
2153                 mimeinfo = mimeinfo->node->next != NULL 
2154                          ? (MimeInfo *) mimeinfo->node->next->data 
2155                          : NULL;
2156         }
2157         gtk_widget_size_request(mimeview->icon_vbox, &size);
2158         if (size.width > mimeview->icon_mainbox->requisition.width) {
2159                 gtk_widget_set_size_request(mimeview->icon_mainbox, 
2160                                             size.width, -1);
2161         }
2162
2163 }
2164
2165 static void mime_toggle_button_cb (GtkWidget *button, MimeView *mimeview) 
2166 {
2167         gtk_widget_ref(button); 
2168
2169         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button))) {
2170                 gtk_arrow_set(GTK_ARROW(GTK_BIN(button)->child), GTK_ARROW_RIGHT, 
2171                                         GTK_SHADOW_NONE);
2172                 gtk_widget_hide(mimeview->icon_mainbox);
2173                 gtk_widget_show(mimeview->ctree_mainbox);
2174                 gtk_paned_set_position(GTK_PANED(mimeview->paned), mimeview->oldsize);
2175
2176                 gtkut_container_remove(GTK_CONTAINER(mimeview->icon_mainbox), 
2177                                         button);
2178                 gtk_box_pack_end(GTK_BOX(mimeview->ctree_mainbox), 
2179                                    button, FALSE, FALSE, 0);
2180                 gtk_paned_set_gutter_size(GTK_PANED(mimeview->paned), 6);
2181         } else {
2182                 gtk_arrow_set(GTK_ARROW(GTK_BIN(button)->child), GTK_ARROW_LEFT, 
2183                               GTK_SHADOW_NONE);
2184                 mimeview->oldsize = mimeview->ctree_mainbox->allocation.height;
2185                 gtk_widget_hide(mimeview->ctree_mainbox);
2186                 gtk_widget_show(mimeview->icon_mainbox);
2187                 gtk_paned_set_position(GTK_PANED(mimeview->paned), 0);
2188
2189                 gtkut_container_remove(GTK_CONTAINER(mimeview->ctree_mainbox), 
2190                                         button);
2191                 gtk_box_pack_start(GTK_BOX(mimeview->icon_mainbox), 
2192                                    button, FALSE, FALSE, 0);
2193                 gtk_box_reorder_child(GTK_BOX(button->parent), button, 0);
2194                 if (mimeview->opened)
2195                         icon_list_toggle_by_mime_info
2196                                 (mimeview, gtk_ctree_node_get_row_data(GTK_CTREE(mimeview->ctree), 
2197                                                                        mimeview->opened));
2198
2199                 gtk_paned_set_gutter_size(GTK_PANED(mimeview->paned), 0);
2200         }
2201         gtk_widget_grab_focus(button);
2202         gtk_widget_unref(button);
2203
2204 }
2205
2206 void mimeview_update (MimeView *mimeview) {
2207         if (mimeview && mimeview->mimeinfo) {
2208                 icon_list_clear(mimeview);
2209                 icon_list_create(mimeview, mimeview->mimeinfo);
2210         }
2211 }
2212
2213 void mimeview_handle_cmd(MimeView *mimeview, const gchar *cmd, GdkEventButton *event, gpointer data)
2214 {
2215         MessageView *msgview = NULL;
2216         MainWindow *mainwin = NULL;
2217         
2218         if (!cmd)
2219                 return;
2220         
2221         msgview = mimeview->messageview;
2222         if (!msgview)
2223                 return;
2224                 
2225         mainwin = msgview->mainwin;
2226         if (!mainwin)
2227                 return;
2228                 
2229         else if (!strcmp(cmd, "sc://view_log"))
2230                 log_window_show(mainwin->logwin);
2231         else if (!strcmp(cmd, "sc://save_as"))
2232                 mimeview_save_as(mimeview);
2233         else if (!strcmp(cmd, "sc://display_as_text"))
2234                 mimeview_display_as_text(mimeview);
2235         else if (!strcmp(cmd, "sc://open_with"))
2236                 mimeview_open_with(mimeview);
2237         else if (!strcmp(cmd, "sc://open"))
2238                 mimeview_launch(mimeview);
2239         else if (!strcmp(cmd, "sc://select_attachment") && data != NULL) {
2240                 icon_list_toggle_by_mime_info(mimeview, (MimeInfo *)data);
2241                 icon_selected(mimeview, -1, (MimeInfo *)data);
2242         } else if (!strcmp(cmd, "sc://open_attachment") && data != NULL) {
2243                 icon_list_toggle_by_mime_info(mimeview, (MimeInfo *)data);
2244                 icon_selected(mimeview, -1, (MimeInfo *)data);
2245                 mimeview_launch(mimeview);
2246         } else if (!strcmp(cmd, "sc://menu_attachment") && data != NULL) {
2247                 mimeview->spec_part = (MimeInfo *)data;
2248                 part_button_pressed(mimeview, event, (MimeInfo *)data);
2249         }
2250 }
2251
2252 gboolean mimeview_scroll_page(MimeView *mimeview, gboolean up)
2253 {
2254         if (mimeview->type == MIMEVIEW_TEXT)
2255                 return textview_scroll_page(mimeview->textview, up);
2256         else if (mimeview->mimeviewer) {
2257                 MimeViewer *mimeviewer = mimeview->mimeviewer;
2258                 if (mimeviewer->scroll_page)
2259                         return mimeviewer->scroll_page(mimeviewer, up);
2260         }
2261         return TRUE;
2262 }
2263
2264 void mimeview_scroll_one_line(MimeView *mimeview, gboolean up)
2265 {
2266         if (mimeview->type == MIMEVIEW_TEXT)
2267                 textview_scroll_one_line(mimeview->textview, up);
2268         else if (mimeview->mimeviewer) {
2269                 MimeViewer *mimeviewer = mimeview->mimeviewer;
2270                 if (mimeviewer->scroll_one_line)
2271                         mimeviewer->scroll_one_line(mimeviewer, up);
2272         }
2273 }