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