talons

Fork of Claws Mail https://www.claws-mail
Log | Files | Refs | README | LICENSE

mimeview.c (60509B)


      1 /*
      2  * Claws Mail -- a GTK based, lightweight, and fast e-mail client
      3  * Copyright (C) 1999-2024 the Claws Mail team and Hiroyuki Yamamoto
      4  *
      5  * This program is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published by
      7  * the Free Software Foundation; either version 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 #include "defs.h"
     20 
     21 #include <glib.h>
     22 #include <glib/gi18n.h>
     23 #include <gdk/gdkkeysyms.h>
     24 #include <gtk/gtk.h>
     25 #include <stdio.h>
     26 #include <sys/types.h>
     27 #include <sys/stat.h>
     28 
     29 #include "main.h"
     30 #include "mimeview.h"
     31 #include "textview.h"
     32 #include "procmime.h"
     33 #include "summaryview.h"
     34 #include "menu.h"
     35 #include "filesel.h"
     36 #include "alertpanel.h"
     37 #include "inputdialog.h"
     38 #include "utils.h"
     39 #include "gtkutils.h"
     40 #include "prefs_common.h"
     41 #include "procheader.h"
     42 #include "stock_pixmap.h"
     43 #include "gtk/gtkvscrollbutton.h"
     44 #include "gtk/logwindow.h"
     45 #include "manage_window.h"
     46 #include "file-utils.h"
     47 
     48 typedef enum
     49 {
     50 	COL_MIMETYPE = 0,
     51 	COL_SIZE     = 1,
     52 	COL_NAME     = 2,
     53 	COL_DATA     = 3,
     54 	N_MIMEVIEW_COLUMNS
     55 } MimeViewColumnPos;
     56 
     57 #define N_MIMEVIEW_COLS	(N_MIMEVIEW_COLUMNS)
     58 
     59 static void mimeview_set_multipart_tree		(MimeView	*mimeview,
     60 						 MimeInfo	*mimeinfo,
     61 						 GtkTreeIter	*parent);
     62 static void mimeview_show_message_part		(MimeView	*mimeview,
     63 						 MimeInfo	*partinfo);
     64 static void mimeview_change_view_type		(MimeView	*mimeview,
     65 						 MimeViewType	 type);
     66 static gchar *mimeview_get_filename_for_part	(MimeInfo	*partinfo,
     67 						 const gchar	*basedir,
     68 						 gint		 number);
     69 static gint mimeview_write_part		(const gchar	*filename,
     70 					 MimeInfo	*partinfo,
     71 					 gboolean	 handle_error);
     72 
     73 static void mimeview_selected		(GtkTreeSelection	*selection,
     74 					 MimeView	*mimeview);
     75 static gint mimeview_button_pressed	(GtkWidget	*widget,
     76 					 GdkEventButton	*event,
     77 					 MimeView	*mimeview);
     78 
     79 static gboolean mimeview_scrolled	(GtkWidget	*widget,
     80 					 GdkEventScroll	*event,
     81 					 MimeView	*mimeview);
     82 
     83 static void mimeview_save_all		(MimeView	*mimeview,
     84 					 gboolean	 attachments_only);
     85 static void mimeview_open_part_with	(MimeView	*mimeview,
     86 					 MimeInfo	*partinfo,
     87 					 gboolean	 automatic);
     88 static void mimeview_send_to		(MimeView	*mimeview,
     89 					 MimeInfo	*partinfo);
     90 static void mimeview_view_file		(const gchar	*filename,
     91 					 MimeInfo	*partinfo,
     92 					 const gchar	*cmd,
     93 					 MimeView  	*mimeview);
     94 static gboolean icon_clicked_cb		(GtkWidget 	*button,
     95 					 GdkEventButton	*event,
     96 					 MimeView 	*mimeview);
     97 static void icon_selected               (MimeView       *mimeview,
     98 					 gint            num,
     99 					 MimeInfo       *partinfo);
    100 static gint icon_key_pressed            (GtkWidget      *button,
    101 					 GdkEventKey    *event,
    102 					 MimeView       *mimeview);
    103 static void icon_list_append_icon 	(MimeView 	*mimeview,
    104 					 MimeInfo 	*mimeinfo);
    105 static void icon_list_create		(MimeView 	*mimeview,
    106 					 MimeInfo 	*mimeinfo);
    107 static void icon_list_clear		(MimeView	*mimeview);
    108 static void icon_list_toggle_by_mime_info (MimeView	*mimeview,
    109 					   MimeInfo	*mimeinfo);
    110 static void ctree_size_allocate_cb	(GtkWidget	*widget,
    111 					 GtkAllocation	*allocation,
    112 					 MimeView	*mimeview);
    113 static gint mime_toggle_button_cb(GtkWidget *button, GdkEventButton *event,
    114 				    MimeView *mimeview);
    115 static gboolean part_button_pressed	(MimeView 	*mimeview,
    116 					 GdkEventButton *event,
    117 					 MimeInfo 	*partinfo);
    118 static void icon_scroll_size_allocate_cb(GtkWidget 	*widget,
    119 					 GtkAllocation  *layout_size,
    120 					 MimeView 	*mimeview);
    121 static MimeInfo *mimeview_get_part_to_use(MimeView *mimeview);
    122 static const gchar *get_part_name(MimeInfo *partinfo);
    123 static const gchar *get_part_description(MimeInfo *partinfo);
    124 
    125 static void mimeview_launch_cb(GtkAction *action, gpointer data)
    126 {
    127 	MimeView *mimeview = (MimeView *)data;
    128 	mimeview_launch(mimeview, mimeview_get_part_to_use(mimeview));
    129 }
    130 
    131 static void mimeview_open_with_cb(GtkAction *action, gpointer data)
    132 {
    133 	mimeview_open_with((MimeView *)data);
    134 }
    135 
    136 static void mimeview_copy_cb(GtkAction *action, gpointer data)
    137 {
    138 	MimeView *mimeview = (MimeView *)data;
    139 	MimeInfo *mimeinfo = mimeview_get_part_to_use(mimeview);
    140 
    141 	if (mimeinfo == NULL)
    142 		return;
    143 
    144 	if (mimeinfo->type == MIMETYPE_IMAGE) {
    145 		GError *error = NULL;
    146 		GdkPixbuf *pixbuf = procmime_get_part_as_pixbuf(mimeinfo, &error);
    147 		if (error != NULL) {
    148 			g_warning("could not copy: %s", error->message);
    149 			g_error_free(error);
    150 		} else {
    151 			gtk_clipboard_set_image(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
    152 						pixbuf);
    153 		}
    154 		g_object_unref(pixbuf);
    155 	} else {
    156 		void *data = procmime_get_part_as_string(mimeinfo, FALSE);
    157 		gtk_clipboard_set_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
    158 				       data, (gint)mimeinfo->length);
    159 		g_free(data);
    160 	}
    161 }
    162 
    163 static void mimeview_send_to_cb(GtkAction *action, gpointer data)
    164 {
    165 	MimeView *mimeview = (MimeView *)data;
    166 	mimeview_send_to(mimeview, mimeview_get_part_to_use(mimeview));
    167 }
    168 
    169 static void mimeview_display_as_text_cb(GtkAction *action, gpointer data)
    170 {
    171 	mimeview_display_as_text((MimeView *)data);
    172 }
    173 
    174 static void mimeview_save_as_cb(GtkAction *action, gpointer data)
    175 {
    176 	mimeview_save_as((MimeView *)data);
    177 }
    178 
    179 static void mimeview_save_all_cb(GtkAction *action, gpointer data)
    180 {
    181 	mimeview_save_all((MimeView *)data, FALSE);
    182 }
    183 
    184 static void mimeview_save_all_attachments_cb(GtkAction *action, gpointer data)
    185 {
    186 	mimeview_save_all((MimeView *)data, TRUE);
    187 }
    188 
    189 static void mimeview_select_next_part_cb(GtkAction *action, gpointer data)
    190 {
    191 	mimeview_select_next_part((MimeView *)data);
    192 }
    193 
    194 static void mimeview_select_prev_part_cb(GtkAction *action, gpointer data)
    195 {
    196 	mimeview_select_prev_part((MimeView *)data);
    197 }
    198 
    199 static GtkActionEntry mimeview_menu_actions[] = {
    200 	{ "MimeView", NULL, "MimeView", NULL, NULL, NULL },
    201 	{ "MimeView/Open", NULL, N_("_Open"), NULL, "Open MIME part", G_CALLBACK(mimeview_launch_cb) },
    202 	{ "MimeView/OpenWith", NULL, N_("Open _with..."), NULL, "Open MIME part with...", G_CALLBACK(mimeview_open_with_cb) },
    203 	{ "MimeView/Copy", NULL, N_("Copy"), NULL, "Copy", G_CALLBACK(mimeview_copy_cb) },
    204 	{ "MimeView/SendTo", NULL, N_("Send to..."), NULL, "Send to", G_CALLBACK(mimeview_send_to_cb) },
    205 	{ "MimeView/DisplayAsText", NULL, N_("_Display as text"), NULL, "Display as text", G_CALLBACK(mimeview_display_as_text_cb) },
    206 	{ "MimeView/SaveAs", NULL, N_("_Save as..."), NULL, "Save as", G_CALLBACK(mimeview_save_as_cb) },
    207 	{ "MimeView/SaveAll", NULL, N_("Save _all..."), NULL, "Save all parts", G_CALLBACK(mimeview_save_all_cb) },
    208 	{ "MimeView/SaveAllAttachments", NULL, N_("Save all attachments..."), NULL, "Save all attachments", G_CALLBACK(mimeview_save_all_attachments_cb) },
    209 	{ "MimeView/NextPart", NULL, N_("Next part"), NULL, "Next part", G_CALLBACK(mimeview_select_next_part_cb) },
    210 	{ "MimeView/PrevPart", NULL, N_("Previous part"), NULL, "Previous part", G_CALLBACK(mimeview_select_prev_part_cb) }
    211 };
    212 
    213 static GtkTargetEntry mimeview_mime_types[] =
    214 {
    215 	{"text/uri-list", 0, 0}
    216 };
    217 
    218 GSList *mimeviewer_factories;
    219 GSList *mimeviews;
    220 
    221 MimeView *mimeview_create(MainWindow *mainwin)
    222 {
    223 	MimeView *mimeview;
    224 
    225 	GtkWidget *paned;
    226 	GtkWidget *scrolledwin;
    227 	GtkWidget *ctree;
    228 	GtkWidget *mime_notebook;
    229 	GtkWidget *popupmenu;
    230 	GtkWidget *ctree_mainbox;
    231 	GtkWidget *vbox;
    232 	GtkWidget *mime_toggle;
    233 	GtkWidget *icon_mainbox;
    234 	GtkWidget *icon_scroll;
    235 	GtkWidget *icon_grid;
    236 	GtkWidget *arrow;
    237 	GtkWidget *scrollbutton;
    238 	GtkWidget *hbox;
    239 	GtkTreeStore *model;
    240 	GtkCellRenderer *renderer;
    241 	GtkTreeViewColumn *column;
    242 	GtkTreeSelection *selection;
    243 
    244 	gchar *titles[N_MIMEVIEW_COLS];
    245 	gint cols;
    246 
    247 	mimeview = g_new0(MimeView, 1);
    248 
    249 	titles[COL_MIMETYPE] = _("MIME Type");
    250 	titles[COL_SIZE]     = _("Size");
    251 	titles[COL_NAME]     = _("Name");
    252 
    253 	scrolledwin = gtk_scrolled_window_new(NULL, NULL);
    254 	gtk_widget_show(scrolledwin);
    255 	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwin),
    256 				       GTK_POLICY_AUTOMATIC,
    257 				       GTK_POLICY_AUTOMATIC);
    258 
    259 	model = gtk_tree_store_new(N_MIMEVIEW_COLS,
    260 				   G_TYPE_STRING,
    261 				   G_TYPE_STRING,
    262 				   G_TYPE_STRING,
    263 				   G_TYPE_POINTER);
    264 
    265 	ctree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
    266 	g_object_unref(model);
    267 	gtk_tree_view_set_enable_search(GTK_TREE_VIEW(ctree), FALSE);
    268 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(ctree),
    269 					prefs_common.show_col_headers);
    270 
    271 	renderer = gtk_cell_renderer_text_new();
    272 	cols = gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(ctree),
    273 					-1, titles[COL_MIMETYPE],renderer,
    274 					"text", COL_MIMETYPE, NULL);
    275 	column = gtk_tree_view_get_column(GTK_TREE_VIEW(ctree), cols-1);
    276 
    277 	renderer = gtk_cell_renderer_text_new();
    278 	gtk_cell_renderer_set_alignment(renderer, 1, 0.5);
    279 	cols = gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(ctree),
    280 					-1, titles[COL_SIZE], renderer,
    281 					"text", COL_SIZE, NULL);
    282 	column = gtk_tree_view_get_column(GTK_TREE_VIEW(ctree), cols-1);
    283 	gtk_tree_view_column_set_alignment(GTK_TREE_VIEW_COLUMN(column), 1);
    284 
    285 	renderer = gtk_cell_renderer_text_new();
    286 	cols = gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(ctree),
    287 					-1, titles[COL_NAME], renderer,
    288 					"text", COL_NAME, NULL);
    289 	column = gtk_tree_view_get_column(GTK_TREE_VIEW(ctree), cols-1);
    290 	gtk_tree_view_column_set_expand(GTK_TREE_VIEW_COLUMN(column), TRUE);
    291 
    292 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(ctree));
    293 	gtk_tree_selection_set_mode(selection, GTK_SELECTION_BROWSE);
    294 
    295 	gtk_widget_show(ctree);
    296 	gtk_container_add(GTK_CONTAINER(scrolledwin), ctree);
    297 
    298 	g_signal_connect(G_OBJECT(selection), "changed",
    299 			 G_CALLBACK(mimeview_selected), mimeview);
    300 	g_signal_connect(G_OBJECT(ctree), "button_release_event",
    301 			 G_CALLBACK(mimeview_button_pressed), mimeview);
    302 
    303 	mime_notebook = gtk_notebook_new();
    304 	gtk_widget_set_name(GTK_WIDGET(mime_notebook), "mime_notebook");
    305 	gtk_widget_show(mime_notebook);
    306 	gtk_widget_set_can_focus(mime_notebook, FALSE);
    307 	gtk_notebook_set_show_tabs(GTK_NOTEBOOK(mime_notebook), FALSE);
    308 	gtk_notebook_set_show_border(GTK_NOTEBOOK(mime_notebook), FALSE);
    309 
    310 	icon_grid = gtk_grid_new();
    311 	gtk_orientable_set_orientation(GTK_ORIENTABLE(icon_grid),
    312 			GTK_ORIENTATION_VERTICAL);
    313 	gtk_grid_set_row_spacing(GTK_GRID(icon_grid), 0);
    314 	gtk_widget_show(icon_grid);
    315 	icon_scroll = gtk_layout_new(NULL, NULL);
    316 	gtk_widget_show(icon_scroll);
    317 	gtk_layout_put(GTK_LAYOUT(icon_scroll), icon_grid, 0, 0);
    318 	scrollbutton = gtk_vscrollbutton_new(gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(icon_scroll)));
    319 	gtk_widget_show(scrollbutton);
    320 
    321 	g_signal_connect(G_OBJECT(icon_scroll), "scroll_event",
    322 			 G_CALLBACK(mimeview_scrolled), mimeview);
    323 
    324     	mime_toggle = gtk_event_box_new();
    325 	gtk_event_box_set_visible_window(GTK_EVENT_BOX(mime_toggle), FALSE);
    326 
    327 	gtk_container_set_border_width(GTK_CONTAINER(mime_toggle), 2);
    328 	gtk_widget_show(mime_toggle);
    329 	mimeview->ctree_mode = FALSE;
    330 	arrow = gtk_image_new_from_icon_name("pan-start-symbolic", GTK_ICON_SIZE_MENU);
    331 	gtk_widget_show(arrow);
    332 	gtk_container_add(GTK_CONTAINER(mime_toggle), arrow);
    333 	g_signal_connect(G_OBJECT(mime_toggle), "button_release_event",
    334 			 G_CALLBACK(mime_toggle_button_cb), mimeview);
    335 
    336 	icon_mainbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    337 	gtk_widget_set_name(GTK_WIDGET(icon_mainbox), "mimeview_icon_mainbox");
    338 	gtk_widget_show(icon_mainbox);
    339 	gtk_widget_set_size_request(icon_mainbox, 32, -1);
    340 	gtk_box_pack_start(GTK_BOX(icon_mainbox), mime_toggle, FALSE, FALSE, 0);
    341 	gtk_box_pack_start(GTK_BOX(icon_mainbox), icon_scroll, TRUE, TRUE, 3);
    342 	gtk_box_pack_end(GTK_BOX(icon_mainbox), scrollbutton, FALSE, FALSE, 0);
    343 	g_signal_connect(G_OBJECT(icon_mainbox), "size_allocate",
    344 			 G_CALLBACK(icon_scroll_size_allocate_cb), mimeview);
    345 
    346 	ctree_mainbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    347 	gtk_widget_set_name(GTK_WIDGET(ctree_mainbox), "mimeview_ctree_mainbox");
    348 	gtk_box_pack_start(GTK_BOX(ctree_mainbox), scrolledwin, TRUE, TRUE, 0);
    349 	g_signal_connect(G_OBJECT(ctree_mainbox), "size_allocate",
    350 			 G_CALLBACK(ctree_size_allocate_cb), mimeview);
    351 
    352 	mimeview->ui_manager = gtk_ui_manager_new();
    353 	mimeview->action_group = cm_menu_create_action_group_full(mimeview->ui_manager,
    354 			"MimeView", mimeview_menu_actions,
    355 			G_N_ELEMENTS(mimeview_menu_actions), (gpointer)mimeview);
    356 
    357 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager, "/", "Menus", "Menus", GTK_UI_MANAGER_MENUBAR)
    358 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    359 			"/Menus/", "MimeView", "MimeView", GTK_UI_MANAGER_MENU);
    360 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    361 			"/Menus/MimeView/", "Open", "MimeView/Open",
    362 			GTK_UI_MANAGER_MENUITEM);
    363 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    364 			"/Menus/MimeView/", "OpenWith", "MimeView/OpenWith",
    365 			GTK_UI_MANAGER_MENUITEM);
    366 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    367 			"/Menus/MimeView/", "Copy", "MimeView/Copy",
    368 			GTK_UI_MANAGER_MENUITEM);
    369 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    370 			"/Menus/MimeView/", "SendTo", "MimeView/SendTo",
    371 			GTK_UI_MANAGER_MENUITEM);
    372 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    373 			"/Menus/MimeView/", "DisplayAsText", "MimeView/DisplayAsText",
    374 			GTK_UI_MANAGER_MENUITEM);
    375 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    376 			"/Menus/MimeView/", "SaveAs", "MimeView/SaveAs",
    377 			GTK_UI_MANAGER_MENUITEM);
    378 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    379 			"/Menus/MimeView/", "SaveAll", "MimeView/SaveAll",
    380 			GTK_UI_MANAGER_MENUITEM);
    381 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    382 			"/Menus/MimeView/", "SaveAllAttachments", "MimeView/SaveAllAttachments",
    383 			GTK_UI_MANAGER_MENUITEM);
    384 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    385 			"/Menus/MimeView/", "NextPart", "MimeView/NextPart",
    386 			GTK_UI_MANAGER_MENUITEM);
    387 	MENUITEM_ADDUI_MANAGER(mimeview->ui_manager,
    388 			"/Menus/MimeView/", "PrevPart", "MimeView/PrevPart",
    389 			GTK_UI_MANAGER_MENUITEM);
    390 
    391 	popupmenu = gtk_menu_item_get_submenu(GTK_MENU_ITEM(
    392 				gtk_ui_manager_get_widget(mimeview->ui_manager, "/Menus/MimeView")) );
    393 
    394 
    395 	vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
    396 	gtk_widget_show(vbox);
    397 	gtk_box_pack_start(GTK_BOX(vbox), mime_notebook, TRUE, TRUE, 0);
    398 
    399 	paned = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
    400 	gtk_widget_show(paned);
    401 	gtk_paned_pack1(GTK_PANED(paned), ctree_mainbox, FALSE, TRUE);
    402 	gtk_paned_pack2(GTK_PANED(paned), vbox, TRUE, TRUE);
    403 
    404 	hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    405 	gtk_widget_set_name(GTK_WIDGET(hbox), "mimeview");
    406 	gtk_box_pack_start(GTK_BOX(hbox), paned, TRUE, TRUE, 0);
    407 	gtk_box_pack_start(GTK_BOX(hbox), icon_mainbox, FALSE, FALSE, 0);
    408 
    409 	gtk_widget_show(hbox);
    410 	gtk_widget_hide(ctree_mainbox);
    411 	mimeview->hbox          = hbox;
    412 	mimeview->paned         = paned;
    413 	mimeview->scrolledwin   = scrolledwin;
    414 	mimeview->ctree         = ctree;
    415 	mimeview->mime_notebook = mime_notebook;
    416 	mimeview->popupmenu     = popupmenu;
    417 	mimeview->type          = -1;
    418 	mimeview->ctree_mainbox = ctree_mainbox;
    419 	mimeview->icon_scroll   = icon_scroll;
    420 	mimeview->icon_grid     = icon_grid;
    421 	mimeview->icon_mainbox  = icon_mainbox;
    422 	mimeview->icon_count    = 0;
    423 	mimeview->mainwin       = mainwin;
    424 	mimeview->mime_toggle   = mime_toggle;
    425 	mimeview->scrollbutton  = scrollbutton;
    426 	mimeview->arrow		= arrow;
    427 	mimeview->target_list	= gtk_target_list_new(mimeview_mime_types, 1);
    428 
    429 	mimeviews = g_slist_prepend(mimeviews, mimeview);
    430 
    431 	return mimeview;
    432 }
    433 
    434 void mimeview_init(MimeView *mimeview)
    435 {
    436 	textview_init(mimeview->textview);
    437 
    438 	gtk_container_add(GTK_CONTAINER(mimeview->mime_notebook),
    439 		GTK_WIDGET_PTR(mimeview->textview));
    440 }
    441 
    442 void mimeview_show_message(MimeView *mimeview, MimeInfo *mimeinfo,
    443 			   const gchar *file)
    444 {
    445 	GtkTreeView *ctree = GTK_TREE_VIEW(mimeview->ctree);
    446 
    447 	mimeview_clear(mimeview);
    448 
    449 	mimeview->mimeinfo = mimeinfo;
    450 	mimeview->file = strdup(file);
    451 
    452 	g_signal_handlers_block_by_func(G_OBJECT(ctree), mimeview_selected, mimeview);
    453 
    454 	mimeview_set_multipart_tree(mimeview, mimeinfo, NULL);
    455 	gtk_tree_view_expand_all(ctree);
    456 	icon_list_create(mimeview, mimeinfo);
    457 
    458 	g_signal_handlers_unblock_by_func(G_OBJECT(ctree), mimeview_selected, mimeview);
    459 }
    460 
    461 static void mimeview_free_mimeinfo(MimeView *mimeview)
    462 {
    463 	if (mimeview->mimeinfo != NULL) {
    464 		procmime_mimeinfo_free_all(&mimeview->mimeinfo);
    465 		mimeview->mimeinfo = NULL;
    466 	}
    467 }
    468 
    469 void mimeview_destroy(MimeView *mimeview)
    470 {
    471 	GSList *cur;
    472 
    473 	for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
    474 		MimeViewer *viewer = (MimeViewer *) cur->data;
    475 		viewer->destroy_viewer(viewer);
    476 	}
    477 	g_slist_free(mimeview->viewers);
    478 	gtk_target_list_unref(mimeview->target_list);
    479 
    480 	mimeview_free_mimeinfo(mimeview);
    481 	gtk_tree_path_free(mimeview->opened);
    482 	g_free(mimeview->file);
    483 	mimeviews = g_slist_remove(mimeviews, mimeview);
    484 	g_free(mimeview);
    485 }
    486 
    487 MimeInfo *mimeview_get_selected_part(MimeView *mimeview)
    488 {
    489 	return gtkut_tree_view_get_selected_pointer(
    490 			GTK_TREE_VIEW(mimeview->ctree), COL_DATA,
    491 			NULL, NULL, NULL);
    492 }
    493 
    494 MimeInfo *mimeview_get_node_part(MimeView *mimeview, GtkTreePath *path)
    495 {
    496 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(mimeview->ctree));
    497 	GtkTreeIter iter;
    498 	MimeInfo *partinfo;
    499 
    500 	gtk_tree_model_get_iter(model, &iter, path);
    501 	gtk_tree_model_get(model, &iter, COL_DATA, &partinfo, -1);
    502 	return partinfo;
    503 }
    504 
    505 gboolean mimeview_tree_is_empty(MimeView *mimeview)
    506 {
    507 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(mimeview->ctree));
    508 	GtkTreeIter iter;
    509 	return !gtk_tree_model_get_iter_first(model, &iter);
    510 }
    511 
    512 static gboolean mimeview_tree_next(GtkTreeModel *model, GtkTreePath *path)
    513 {
    514 	GtkTreeIter iter, parent;
    515 	gboolean has_parent;
    516 
    517 	gtk_tree_model_get_iter(model, &iter, path);
    518 
    519 	if (gtk_tree_model_iter_has_child(model, &iter)) {
    520 		gtk_tree_path_down(path);
    521 		return TRUE;
    522 	}
    523 
    524 	has_parent = gtk_tree_model_iter_parent(model, &parent, &iter);
    525 
    526 	if (!gtk_tree_model_iter_next(model, &iter)) {
    527 		while (has_parent) {
    528 			GtkTreeIter saved_parent = parent;
    529 			gtk_tree_path_up(path);
    530 			if (gtk_tree_model_iter_next(model, &parent)) {
    531 				gtk_tree_path_next(path);
    532 				return TRUE;
    533 			} else {
    534 				has_parent = gtk_tree_model_iter_parent(model, &parent, &saved_parent);
    535 			}
    536 		}
    537 
    538 	} else {
    539 		gtk_tree_path_next(path);
    540 		return TRUE;
    541 	}
    542 
    543 	return FALSE;
    544 }
    545 
    546 static gboolean mimeview_tree_prev(MimeView *mimeview, GtkTreePath *path)
    547 {
    548 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(mimeview->ctree));
    549 	GtkTreeIter iter, child, parent;
    550 	gboolean has_parent;
    551 
    552 	gtk_tree_model_get_iter(model, &iter, path);
    553 	has_parent = gtk_tree_model_iter_parent(model, &parent, &iter);
    554 
    555 	if (gtk_tree_path_prev(path)) {
    556 		gtk_tree_model_get_iter(model, &iter, path);
    557 
    558 		if (gtk_tree_model_iter_nth_child(model, &child, &iter, 0)) {
    559 			gtk_tree_path_down(path);
    560 
    561 			while (gtk_tree_model_iter_next(model, &child))
    562 				gtk_tree_path_next(path);
    563 		}
    564 
    565 		return TRUE;
    566 	}
    567 
    568 	if (has_parent) {
    569 		gtk_tree_path_up(path);
    570 		return TRUE;
    571 	}
    572 
    573 	return FALSE;
    574 }
    575 
    576 gint mimeview_get_selected_part_num(MimeView *mimeview)
    577 {
    578 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(mimeview->ctree));
    579 	GtkTreeIter iter;
    580 	GtkTreePath *path;
    581 	gint i = 0;
    582 
    583 	if (!gtk_tree_model_get_iter_first(model, &iter))
    584 		return -1;
    585 	path = gtk_tree_model_get_path(model, &iter);
    586 
    587 	do {
    588 		if (!gtk_tree_path_compare(mimeview->opened, path)) {
    589 			gtk_tree_path_free(path);
    590 			return i;
    591 		}
    592 
    593 		i++;
    594 	} while (mimeview_tree_next(model, path));
    595 
    596 	gtk_tree_path_free(path);
    597 
    598 	return -1;
    599 }
    600 
    601 void mimeview_select_part_num(MimeView *mimeview, gint i)
    602 {
    603 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(mimeview->ctree));
    604 	GtkTreeIter iter;
    605 	GtkTreePath *path;
    606 	GtkTreeSelection *selection;
    607 	gint x = 0;
    608 
    609 	if (i < 0)
    610 		return;
    611 
    612 	if (!gtk_tree_model_get_iter_first(model, &iter))
    613 		return;
    614 	path = gtk_tree_model_get_path(model, &iter);
    615 
    616 	while (x != i) {
    617 		if (!mimeview_tree_next(model, path)) {
    618 			gtk_tree_path_free(path);
    619 			return;
    620 		}
    621 		x++;
    622 	}
    623 
    624 	selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(mimeview->ctree));
    625 	gtk_tree_selection_select_path(selection, path);
    626 	gtk_tree_path_free(path);
    627 }
    628 
    629 static void mimeview_set_multipart_tree(MimeView *mimeview,
    630 					MimeInfo *mimeinfo,
    631 					GtkTreeIter *parent)
    632 {
    633 	GtkTreeStore *model = GTK_TREE_STORE(gtk_tree_view_get_model(
    634 					GTK_TREE_VIEW(mimeview->ctree)));
    635 	GtkTreeIter iter;
    636 	gchar *content_type, *length, *name;
    637 
    638 	cm_return_if_fail(mimeinfo != NULL);
    639 
    640 	while (mimeinfo != NULL) {
    641 		if (mimeinfo->type != MIMETYPE_UNKNOWN && mimeinfo->subtype)
    642 			content_type = g_strdup_printf("%s/%s",
    643 					procmime_get_media_type_str(mimeinfo->type),
    644 					mimeinfo->subtype);
    645 		else
    646 			content_type = g_strdup("UNKNOWN");
    647 
    648 		length = g_strdup(to_human_readable((goffset) mimeinfo->length));
    649 
    650 		if (prefs_common.attach_desc)
    651 			name = g_strdup(get_part_description(mimeinfo));
    652 		else
    653 			name = g_strdup(get_part_name(mimeinfo));
    654 
    655 		gtk_tree_store_append(model, &iter, parent);
    656 		gtk_tree_store_set(model, &iter,
    657 				COL_MIMETYPE, content_type,
    658 				COL_SIZE, length,
    659 				COL_NAME, name,
    660 				COL_DATA, mimeinfo, -1);
    661 		g_free(content_type);
    662 		g_free(length);
    663 		g_free(name);
    664 
    665 		if (mimeinfo->node->children)
    666 			mimeview_set_multipart_tree(mimeview,
    667 				(MimeInfo *) mimeinfo->node->children->data, &iter);
    668 		mimeinfo = mimeinfo->node->next != NULL ?
    669 				(MimeInfo *) mimeinfo->node->next->data : NULL;
    670 	}
    671 }
    672 
    673 static const gchar *get_real_part_name(MimeInfo *partinfo)
    674 {
    675 	const gchar *name = NULL;
    676 
    677 	name = procmime_mimeinfo_get_parameter(partinfo, "filename");
    678 	if (name == NULL)
    679 		name = procmime_mimeinfo_get_parameter(partinfo, "name");
    680 
    681 	return name;
    682 }
    683 
    684 static const gchar *get_part_name(MimeInfo *partinfo)
    685 {
    686 	const gchar *name;
    687 
    688 	name = get_real_part_name(partinfo);
    689 	if (name == NULL)
    690 		name = "";
    691 
    692 	return name;
    693 }
    694 
    695 static const gchar *get_part_description(MimeInfo *partinfo)
    696 {
    697 	if (partinfo->description)
    698 		return partinfo->description;
    699 	else
    700 		return get_part_name(partinfo);
    701 }
    702 
    703 static void mimeview_show_message_part(MimeView *mimeview, MimeInfo *partinfo)
    704 {
    705 	if (!partinfo) return;
    706 
    707 	FILE *fp;
    708 	if ((fp = fopen(mimeview->file, "r")) == NULL) {
    709 		warn("open %s", mimeview->file);
    710 		return;
    711 	}
    712 
    713 	mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
    714 	textview_show_part(mimeview->textview, partinfo, fp);
    715 
    716 	fclose(fp);
    717 }
    718 
    719 static MimeViewer *get_viewer_for_content_type(MimeView *mimeview, const gchar *content_type)
    720 {
    721 	GSList *cur;
    722 	MimeViewerFactory *factory = NULL;
    723 	MimeViewer *viewer = NULL;
    724 	gchar *real_contenttype = NULL, *tmp;
    725 
    726 	real_contenttype = g_utf8_strdown((gchar *)content_type, -1);
    727 
    728 	for (cur = mimeviewer_factories; cur != NULL; cur = g_slist_next(cur)) {
    729 		MimeViewerFactory *curfactory = cur->data;
    730 		gint i = 0;
    731 
    732 		while (curfactory->content_types[i] != NULL) {
    733 			tmp = g_utf8_strdown(curfactory->content_types[i], -1);
    734 			if (g_pattern_match_simple(tmp, real_contenttype)) {
    735 				debug_print("%s\n", curfactory->content_types[i]);
    736 				factory = curfactory;
    737 				g_free(tmp);
    738 				break;
    739 			}
    740 			g_free(tmp);
    741 			i++;
    742 		}
    743 		if (factory != NULL)
    744 			break;
    745 	}
    746 	g_free(real_contenttype);
    747 	if (factory == NULL)
    748 		return NULL;
    749 
    750 	for (cur = mimeview->viewers; cur != NULL; cur = g_slist_next(cur)) {
    751 		MimeViewer *curviewer = cur->data;
    752 
    753 		if (curviewer->factory == factory)
    754 			return curviewer;
    755 	}
    756 	viewer = factory->create_viewer();
    757 	gtk_container_add(GTK_CONTAINER(mimeview->mime_notebook),
    758 		GTK_WIDGET(viewer->get_widget(viewer)));
    759 
    760 	mimeview->viewers = g_slist_append(mimeview->viewers, viewer);
    761 
    762 	return viewer;
    763 }
    764 
    765 gboolean mimeview_has_viewer_for_content_type(MimeView *mimeview, const gchar *content_type)
    766 {
    767 	return (get_viewer_for_content_type(mimeview, content_type) != NULL);
    768 }
    769 
    770 static MimeViewer *get_viewer_for_mimeinfo(MimeView *mimeview, MimeInfo *partinfo)
    771 {
    772 	gchar *content_type = NULL;
    773 	MimeViewer *viewer = NULL;
    774 
    775 	if ((partinfo->type == MIMETYPE_APPLICATION) &&
    776             (!g_ascii_strcasecmp(partinfo->subtype, "octet-stream"))) {
    777 		const gchar *filename;
    778 
    779 		filename = procmime_mimeinfo_get_parameter(partinfo, "filename");
    780 		if (filename == NULL)
    781 			filename = procmime_mimeinfo_get_parameter(partinfo, "name");
    782 		if (filename != NULL)
    783 			content_type = procmime_get_mime_type(filename);
    784 	} else {
    785 		content_type = procmime_get_content_type_str(partinfo->type, partinfo->subtype);
    786 	}
    787 
    788 	if (content_type != NULL) {
    789 		viewer = get_viewer_for_content_type(mimeview, content_type);
    790 		g_free(content_type);
    791 	}
    792 
    793 	return viewer;
    794 }
    795 
    796 gboolean mimeview_show_part(MimeView *mimeview, MimeInfo *partinfo)
    797 {
    798 	MimeViewer *viewer;
    799 
    800 	viewer = get_viewer_for_mimeinfo(mimeview, partinfo);
    801 	if (viewer == NULL) {
    802 		if (mimeview->mimeviewer != NULL)
    803 			mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
    804 		mimeview->mimeviewer = NULL;
    805 		return FALSE;
    806 	}
    807 
    808 	if (mimeview->mimeviewer != NULL)
    809 		mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
    810 
    811 	if (mimeview->mimeviewer != viewer)
    812 		mimeview->mimeviewer = viewer;
    813 
    814 	mimeview_change_view_type(mimeview, MIMEVIEW_VIEWER);
    815 	viewer->mimeview = mimeview;
    816 	viewer->show_mimepart(viewer, mimeview->file, partinfo);
    817 
    818 	return TRUE;
    819 }
    820 
    821 static void mimeview_change_view_type(MimeView *mimeview, MimeViewType type)
    822 {
    823 	TextView  *textview  = mimeview->textview;
    824 	GtkWidget *focused = NULL;
    825 
    826 	if (mainwindow_get_mainwindow())
    827 		focused = gtkut_get_focused_child(
    828 				GTK_CONTAINER(mainwindow_get_mainwindow()->window));
    829 
    830 	if ((mimeview->type != MIMEVIEW_VIEWER) &&
    831 	    (mimeview->type == type)) return;
    832 
    833 	switch (type) {
    834 	case MIMEVIEW_TEXT:
    835 		gtk_notebook_set_current_page(GTK_NOTEBOOK(mimeview->mime_notebook),
    836 			gtk_notebook_page_num(GTK_NOTEBOOK(mimeview->mime_notebook),
    837 			GTK_WIDGET_PTR(textview)));
    838 		break;
    839 	case MIMEVIEW_VIEWER:
    840 		gtk_notebook_set_current_page(GTK_NOTEBOOK(mimeview->mime_notebook),
    841 			gtk_notebook_page_num(GTK_NOTEBOOK(mimeview->mime_notebook),
    842 			GTK_WIDGET(mimeview->mimeviewer->get_widget(mimeview->mimeviewer))));
    843 		break;
    844 	default:
    845 		return;
    846 	}
    847 	if (focused)
    848 		gtk_widget_grab_focus(focused);
    849 	mimeview->type = type;
    850 }
    851 
    852 void mimeview_clear(MimeView *mimeview)
    853 {
    854 	GtkTreeModel *model;
    855 
    856 	if (!mimeview)
    857 		return;
    858 	if (g_slist_find(mimeviews, mimeview) == NULL)
    859 		return;
    860 
    861 	model = gtk_tree_view_get_model(GTK_TREE_VIEW(mimeview->ctree));
    862 	gtk_tree_store_clear(GTK_TREE_STORE(model));
    863 
    864 	textview_clear(mimeview->textview);
    865 	if (mimeview->mimeviewer != NULL)
    866 		mimeview->mimeviewer->clear_viewer(mimeview->mimeviewer);
    867 
    868 	mimeview_free_mimeinfo(mimeview);
    869 
    870 	mimeview->mimeinfo = NULL;
    871 
    872 	gtk_tree_path_free(mimeview->opened);
    873 	mimeview->opened = NULL;
    874 
    875 	g_free(mimeview->file);
    876 	mimeview->file = NULL;
    877 
    878 	icon_list_clear(mimeview);
    879 	mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
    880 }
    881 
    882 static void mimeview_selected(GtkTreeSelection *selection, MimeView *mimeview)
    883 {
    884 	GtkTreeView *ctree = GTK_TREE_VIEW(mimeview->ctree);
    885 	GtkTreeModel *model;
    886 	GtkTreeIter iter;
    887 	GtkTreePath *path;
    888 	MimeInfo *partinfo;
    889 	MainWindow *mainwin;
    890 	GdkDisplay *display;
    891 	GdkSeat *seat;
    892 	GdkDevice *device;
    893 
    894 	selection = gtk_tree_view_get_selection(ctree);
    895 	if (!gtk_tree_selection_get_selected(selection, &model, &iter))
    896 		return;
    897 
    898 	path = gtk_tree_model_get_path(model, &iter);
    899 
    900 	if (mimeview->opened && !gtk_tree_path_compare(mimeview->opened, path)) {
    901 		gtk_tree_path_free(path);
    902 		return;
    903 	}
    904 
    905 	gtk_tree_path_free(mimeview->opened);
    906 	mimeview->opened = path;
    907 	mimeview->spec_part = NULL;
    908 	gtk_tree_view_scroll_to_cell(ctree, path, NULL, TRUE, 0.5, 0);
    909 
    910 	partinfo = mimeview_get_node_part(mimeview, path);
    911 	if (!partinfo)
    912 		return;
    913 
    914 	/* ungrab the mouse event */
    915 	if (gtk_widget_has_grab(GTK_WIDGET(ctree))) {
    916 		gtk_grab_remove(GTK_WIDGET(ctree));
    917 		display = gdk_window_get_display(gtk_widget_get_window(GTK_WIDGET(ctree)));
    918 		seat = gdk_display_get_default_seat(display);
    919 		device = gdk_seat_get_pointer(seat);
    920 		if (gdk_display_device_is_grabbed(display, device))
    921 			gdk_seat_ungrab(seat);
    922 	}
    923 
    924 	mimeview->textview->default_text = FALSE;
    925 
    926 	if (!mimeview_show_part(mimeview, partinfo)) {
    927 		switch (partinfo->type) {
    928 		case MIMETYPE_TEXT:
    929 		case MIMETYPE_MESSAGE:
    930 		case MIMETYPE_MULTIPART:
    931 			mimeview_show_message_part(mimeview, partinfo);
    932 			break;
    933 		default:
    934 			mimeview->textview->default_text = TRUE;
    935 			mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
    936 			textview_clear(mimeview->textview);
    937 			textview_show_mime_part(mimeview->textview, partinfo);
    938 			break;
    939 		}
    940 	}
    941 	mainwin = mainwindow_get_mainwindow();
    942 	if (mainwin)
    943 		main_window_set_menu_sensitive(mainwin);
    944 }
    945 
    946 static gint mimeview_button_pressed(GtkWidget *widget, GdkEventButton *event,
    947 				    MimeView *mimeview)
    948 {
    949 	GtkTreeSelection *selection;
    950 	GtkTreeIter iter;
    951 
    952 	if (!event) return FALSE;
    953 
    954 	if (event->button == 2 || event->button == 3) {
    955 		selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(widget));
    956 		if (!gtk_tree_selection_get_selected(selection, NULL, &iter))
    957 			return FALSE;
    958 
    959 		gtk_tree_selection_select_iter(selection, &iter);
    960 	}
    961 	part_button_pressed(mimeview, event, mimeview_get_selected_part(mimeview));
    962 
    963 	return FALSE;
    964 }
    965 
    966 static gboolean mimeview_scrolled(GtkWidget *widget, GdkEventScroll *event,
    967 				    MimeView *mimeview)
    968 {
    969 	GtkVScrollbutton *scrollbutton = (GtkVScrollbutton *)mimeview->scrollbutton;
    970 	if (event->direction == GDK_SCROLL_UP) {
    971 		scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD;
    972 	} else if (event->direction == GDK_SCROLL_DOWN) {
    973 		scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD;
    974 	} else {
    975 		gdouble x, y;
    976 
    977 		if ((event->direction == GDK_SCROLL_SMOOTH) &&
    978 				gdk_event_get_scroll_deltas((GdkEvent*)event, &x, &y)) {
    979 			if (y < 0)
    980 				scrollbutton->scroll_type = GTK_SCROLL_STEP_BACKWARD;
    981 			else
    982 				if (y >0)
    983 					scrollbutton->scroll_type = GTK_SCROLL_STEP_FORWARD;
    984 		} else
    985 			return FALSE; /* Scrolling left or right */
    986 	}
    987 	gtk_vscrollbutton_scroll(scrollbutton);
    988 	return TRUE;
    989 }
    990 
    991 static gboolean part_button_pressed(MimeView *mimeview, GdkEventButton *event,
    992 				    MimeInfo *partinfo)
    993 {
    994 	static MimeInfo *lastinfo;
    995 	static guint32 lasttime;
    996 
    997 	gint double_click_time;
    998 	g_object_get(gtk_settings_get_default(), "gtk-double-click-time", &double_click_time, NULL);
    999 
   1000 	if (event->button == 2 ||
   1001 	    (event->button == 1 && (event->time - lasttime) < double_click_time && lastinfo == partinfo)) {
   1002 		/* call external program for image, audio or html */
   1003 		mimeview_launch(mimeview, partinfo);
   1004 		return TRUE;
   1005 	} else if (event->button == 3) {
   1006 		MainWindow *mainwin = mainwindow_get_mainwindow();
   1007 
   1008 		if (partinfo && (partinfo->type == MIMETYPE_MESSAGE ||
   1009 				 partinfo->type == MIMETYPE_IMAGE ||
   1010 				 partinfo->type == MIMETYPE_MULTIPART))
   1011 			cm_menu_set_sensitive_full(mimeview->ui_manager, "Menus/MimeView/DisplayAsText", FALSE);
   1012 		else
   1013 			cm_menu_set_sensitive_full(mimeview->ui_manager, "Menus/MimeView/DisplayAsText", TRUE);
   1014 
   1015 		if (partinfo && (partinfo->type == MIMETYPE_MESSAGE ||
   1016 				 partinfo->type == MIMETYPE_IMAGE ||
   1017 				 partinfo->type == MIMETYPE_TEXT))
   1018 			cm_menu_set_sensitive_full(mimeview->ui_manager, "Menus/MimeView/Copy", TRUE);
   1019 		else
   1020 			cm_menu_set_sensitive_full(mimeview->ui_manager, "Menus/MimeView/Copy", FALSE);
   1021 		if (partinfo &&
   1022 		    partinfo->type == MIMETYPE_APPLICATION &&
   1023 		    !g_ascii_strcasecmp(partinfo->subtype, "octet-stream"))
   1024 			cm_menu_set_sensitive_full(mimeview->ui_manager, "Menus/MimeView/Open", FALSE);
   1025 		else
   1026 			cm_menu_set_sensitive_full(mimeview->ui_manager, "Menus/MimeView/Open", TRUE);
   1027 		if (mainwin)
   1028 			main_window_set_menu_sensitive(mainwin);
   1029 		g_object_set_data(G_OBJECT(mimeview->popupmenu),
   1030 				  "pop_partinfo", partinfo);
   1031 
   1032 		gtk_menu_popup_at_pointer(GTK_MENU(mimeview->popupmenu),
   1033 				(GdkEvent *)event);
   1034 		return TRUE;
   1035 	}
   1036 
   1037 	lastinfo = partinfo;
   1038 	lasttime = event->time;
   1039 	return FALSE;
   1040 }
   1041 
   1042 void mimeview_select_next_part(MimeView *mimeview)
   1043 {
   1044 	GtkTreeView *ctree = GTK_TREE_VIEW(mimeview->ctree);
   1045 	GtkTreeModel *model = gtk_tree_view_get_model(ctree);
   1046 	GtkTreeSelection *selection;
   1047 	GtkTreeIter iter;
   1048 	GtkTreePath *path;
   1049 	MimeInfo *partinfo = NULL;
   1050 	gboolean has_next;
   1051 
   1052 	if (!mimeview->opened) return;
   1053 
   1054 	gtk_tree_model_get_iter(model, &iter, mimeview->opened);
   1055 	path = gtk_tree_model_get_path(model, &iter);
   1056 skip:
   1057 	has_next = mimeview_tree_next(model, path);
   1058 	if (!has_next) {
   1059 		has_next = gtk_tree_model_get_iter_first(model, &iter);
   1060 		gtk_tree_path_free(path);
   1061 		path = gtk_tree_model_get_path(model, &iter);
   1062 	}
   1063 
   1064 	if (has_next) {
   1065 		partinfo = mimeview_get_node_part(mimeview, path);
   1066 		if (partinfo->type == MIMETYPE_MULTIPART ||
   1067 		    (!prefs_common.show_inline_attachments && partinfo->id))
   1068 			goto skip;
   1069 		selection = gtk_tree_view_get_selection(ctree);
   1070 		gtk_tree_selection_select_path(selection, path);
   1071 		icon_list_toggle_by_mime_info(mimeview, partinfo);
   1072 	}
   1073 
   1074 	gtk_tree_path_free(path);
   1075 }
   1076 
   1077 void mimeview_select_prev_part(MimeView *mimeview)
   1078 {
   1079 	GtkTreeView *ctree = GTK_TREE_VIEW(mimeview->ctree);
   1080 	GtkTreeModel *model = gtk_tree_view_get_model(ctree);
   1081 	GtkTreeSelection *selection;
   1082 	GtkTreeIter iter;
   1083 	GtkTreePath *path;
   1084 	MimeInfo *partinfo = NULL;
   1085 	gboolean has_prev;
   1086 
   1087 	if (!mimeview->opened) return;
   1088 
   1089 	gtk_tree_model_get_iter(model, &iter, mimeview->opened);
   1090 	path = gtk_tree_model_get_path(model, &iter);
   1091 skip:
   1092 	has_prev = mimeview_tree_prev(mimeview, path);
   1093 	if (!has_prev) {
   1094 		while (mimeview_tree_next(model, path)) {}
   1095 		has_prev = TRUE;
   1096 	}
   1097 
   1098 	if (has_prev) {
   1099 		partinfo = mimeview_get_node_part(mimeview, path);
   1100 		if (partinfo->type == MIMETYPE_MULTIPART ||
   1101 		    (!prefs_common.show_inline_attachments && partinfo->id))
   1102 			goto skip;
   1103 		selection = gtk_tree_view_get_selection(ctree);
   1104 		gtk_tree_selection_select_path(selection, path);
   1105 		icon_list_toggle_by_mime_info(mimeview, partinfo);
   1106 	}
   1107 
   1108 	gtk_tree_path_free(path);
   1109 }
   1110 
   1111 #define BREAK_ON_MODIFIER_KEY() \
   1112 	if ((event->state & (GDK_MOD1_MASK|GDK_CONTROL_MASK)) != 0) break
   1113 
   1114 /**
   1115  * Returns a filename (with path) for an attachment
   1116  * \param partinfo The attachment to save
   1117  * \param basedir The target directory
   1118  * \param number Used for dummy filename if attachment is unnamed
   1119  */
   1120 static gchar *mimeview_get_filename_for_part(MimeInfo *partinfo,
   1121 				      const gchar *basedir,
   1122 				      gint number)
   1123 {
   1124 	gchar *fullname;
   1125 	gchar *filename;
   1126 
   1127 	filename = g_strdup(get_part_name(partinfo));
   1128 	if (!filename || !*filename) {
   1129 		g_free(filename);
   1130 		filename = g_strdup_printf("noname.%d", number);
   1131 	}
   1132 
   1133 	if (!g_utf8_validate(filename, -1, NULL)) {
   1134 		gchar *tmp = conv_filename_to_utf8(filename);
   1135 		g_free(filename);
   1136 		filename = tmp;
   1137 	}
   1138 
   1139 	subst_for_filename(filename);
   1140 
   1141 	fullname = g_strconcat
   1142 		(basedir, G_DIR_SEPARATOR_S, (g_path_is_absolute(filename))
   1143 		 ? &filename[1] : filename, NULL);
   1144 
   1145 	g_free(filename);
   1146 	filename = conv_filename_from_utf8(fullname);
   1147 	g_free(fullname);
   1148 	return filename;
   1149 }
   1150 
   1151 /**
   1152  * Write a single attachment to file
   1153  * \param filename Filename with path
   1154  * \param partinfo Attachment to save
   1155  */
   1156 static gint mimeview_write_part(const gchar *filename,
   1157 				MimeInfo *partinfo,
   1158 				gboolean handle_error)
   1159 {
   1160 	gchar *dir;
   1161 	gint err;
   1162 
   1163 	dir= g_path_get_dirname(filename);
   1164 	if (!is_dir_exist(dir))
   1165 		make_dir_hier(dir);
   1166 	g_free(dir);
   1167 
   1168 	if (is_file_exist(filename)) {
   1169 		AlertValue aval;
   1170 		gchar *res;
   1171 		gchar *tmp;
   1172 
   1173 		if (!g_utf8_validate(filename, -1, NULL))
   1174 			tmp = conv_filename_to_utf8(filename);
   1175 		else
   1176 			tmp = g_strdup(filename);
   1177 
   1178 		res = g_strdup_printf(_("Overwrite existing file '%s'?"),
   1179 				      tmp);
   1180 		g_free(tmp);
   1181 		aval = alertpanel(_("Overwrite"), res, NULL, _("_No"),
   1182 				  NULL, _("_Yes"), NULL, NULL, ALERTFOCUS_FIRST);
   1183 		g_free(res);
   1184 		if (G_ALERTALTERNATE != aval)
   1185 			return 2;
   1186 	}
   1187 
   1188 	if ((err = procmime_get_part(filename, partinfo)) < 0) {
   1189 		debug_print("error saving MIME part: %d\n", err);
   1190 		if (handle_error)
   1191 			alertpanel_error
   1192 				(_("Couldn't save the part of multipart message: %s"),
   1193 				 g_strerror(-err));
   1194 		return 0;
   1195 	}
   1196 	return 1;
   1197 }
   1198 
   1199 static AlertValue mimeview_save_all_error_ask(gint n)
   1200 {
   1201 	gchar *message = g_strdup_printf(
   1202 		_("An error has occurred while saving message part %d. "
   1203 		"Do you want to cancel saving or ignore error and "
   1204 		"continue?"), n);
   1205 	AlertValue av = alertpanel_full(_("Error saving message part"),
   1206 		message, NULL, _("_Cancel"), NULL, _("Ignore"), NULL, _("Ignore all"),
   1207 		ALERTFOCUS_FIRST, FALSE, NULL, ALERT_WARNING);
   1208 	g_free(message);
   1209 	return av;
   1210 }
   1211 
   1212 static void mimeview_save_all_info(gint errors, gint total)
   1213 {
   1214 	AlertValue aval;
   1215 	char msg[BUFSIZ];
   1216 
   1217 	if (!errors && prefs_common.show_save_all_success) {
   1218 		snprintf(msg, sizeof(msg), "%d saved.", total);
   1219 		aval = alertpanel_full("Notice", msg, NULL, "_Close",
   1220 				       NULL, NULL, NULL, NULL, ALERTFOCUS_FIRST,
   1221 				       TRUE, NULL, ALERT_NOTICE);
   1222 		if (aval & G_ALERTDISABLE)
   1223 			prefs_common.show_save_all_success = FALSE;
   1224 	} else if (prefs_common.show_save_all_failure) {
   1225 		snprintf(msg, sizeof(msg), "%d saved, %d failed.", total-errors, errors);
   1226 		aval = alertpanel_full("Warning", msg, NULL, "_Close",
   1227 				       NULL, NULL, NULL, NULL, ALERTFOCUS_FIRST,
   1228 				       TRUE, NULL, ALERT_WARNING);
   1229 		if (aval & G_ALERTDISABLE)
   1230 			prefs_common.show_save_all_failure = FALSE;
   1231 	}
   1232 }
   1233 
   1234 /**
   1235  * Menu callback: Save all attached files
   1236  * \param mimeview Current display
   1237  */
   1238 static void mimeview_save_all(MimeView *mimeview, gboolean attachments_only)
   1239 {
   1240 	MimeInfo *partinfo;
   1241 	gchar *dirname;
   1242 	gchar *startdir = NULL;
   1243 	gint number = 0, errors = 0;
   1244 	gboolean skip_errors = FALSE;
   1245 
   1246 	if (!mimeview->opened) return;
   1247 	if (!mimeview->file) return;
   1248 	if (!mimeview->mimeinfo) return;
   1249 
   1250 	partinfo = mimeview->mimeinfo;
   1251 	if (prefs_common.attach_save_dir && *prefs_common.attach_save_dir)
   1252 		startdir = g_strconcat(prefs_common.attach_save_dir, G_DIR_SEPARATOR_S, NULL);
   1253 	else
   1254 		startdir = g_strdup(get_home_dir());
   1255 
   1256 	manage_window_focus_in(gtk_widget_get_ancestor(mimeview->hbox, GTK_TYPE_WINDOW), NULL, NULL);
   1257 	dirname = filesel_select_file_save_folder(_("Select destination folder"), startdir);
   1258 	if (!dirname) {
   1259 		g_free(startdir);
   1260 		return;
   1261 	}
   1262 
   1263 	if (!is_dir_exist (dirname)) {
   1264 		alertpanel_error(_("'%s' is not a directory."), dirname);
   1265 		g_free(startdir);
   1266 		g_free(dirname);
   1267 		return;
   1268 	}
   1269 
   1270 	if (dirname[strlen(dirname)-1] == G_DIR_SEPARATOR)
   1271 		dirname[strlen(dirname)-1] = '\0';
   1272 
   1273 	/* Skip the first part, that is sometimes DISPOSITIONTYPE_UNKNOWN */
   1274 	if (partinfo && partinfo->type == MIMETYPE_MESSAGE)
   1275 		partinfo = procmime_mimeinfo_next(partinfo);
   1276 	if (partinfo && partinfo->type == MIMETYPE_MULTIPART) {
   1277 		partinfo = procmime_mimeinfo_next(partinfo);
   1278 		if (partinfo && partinfo->type == MIMETYPE_TEXT)
   1279 			partinfo = procmime_mimeinfo_next(partinfo);
   1280 	}
   1281 
   1282 	while (partinfo != NULL) {
   1283 		if ((!attachments_only &&
   1284 		    (partinfo->type != MIMETYPE_MESSAGE &&
   1285 		     partinfo->type != MIMETYPE_MULTIPART &&
   1286 		     (partinfo->disposition != DISPOSITIONTYPE_INLINE ||
   1287 		      get_real_part_name(partinfo) != NULL))) ||
   1288 		    (attachments_only &&
   1289 		     (partinfo->disposition == DISPOSITIONTYPE_ATTACHMENT &&
   1290 		      get_real_part_name(partinfo) != NULL))) {
   1291 			gchar *filename = mimeview_get_filename_for_part(
   1292 				partinfo, dirname, number++);
   1293 
   1294 			gint ok = mimeview_write_part(filename, partinfo, FALSE);
   1295 			g_free(filename);
   1296 			if (ok < 1) {
   1297 				++errors;
   1298 				if (!skip_errors) {
   1299 					AlertValue av = mimeview_save_all_error_ask(number);
   1300 					skip_errors = (av == G_ALERTOTHER);
   1301 					if (av == G_ALERTDEFAULT) /* cancel */
   1302 						break;
   1303 				}
   1304 			} else if (ok == 2)
   1305 				number--;
   1306 		}
   1307 		partinfo = procmime_mimeinfo_next(partinfo);
   1308 	}
   1309 
   1310 	g_free(prefs_common.attach_save_dir);
   1311 	g_free(startdir);
   1312 	prefs_common.attach_save_dir = g_filename_to_utf8(dirname,
   1313 					-1, NULL, NULL, NULL);
   1314 	g_free(dirname);
   1315 
   1316 	mimeview_save_all_info(errors, number);
   1317 }
   1318 
   1319 static MimeInfo *mimeview_get_part_to_use(MimeView *mimeview)
   1320 {
   1321 	MimeInfo *partinfo = NULL;
   1322 	if (mimeview->spec_part) {
   1323 		partinfo = mimeview->spec_part;
   1324 		mimeview->spec_part = NULL;
   1325 	} else {
   1326 		partinfo = (MimeInfo *) g_object_get_data
   1327 			 (G_OBJECT(mimeview->popupmenu),
   1328 			 "pop_partinfo");
   1329 		g_object_set_data(G_OBJECT(mimeview->popupmenu),
   1330 				  "pop_partinfo", NULL);
   1331 		if (!partinfo) {
   1332 			partinfo = mimeview_get_selected_part(mimeview);
   1333 		}
   1334 	}
   1335 
   1336 	return partinfo;
   1337 }
   1338 /**
   1339  * Menu callback: Save the selected attachment
   1340  * \param mimeview Current display
   1341  */
   1342 void mimeview_save_as(MimeView *mimeview)
   1343 {
   1344 	gchar *filename;
   1345 	gchar *filepath = NULL;
   1346 	gchar *filedir = NULL;
   1347 	MimeInfo *partinfo;
   1348 	gchar *partname = NULL;
   1349 
   1350 	if (!mimeview->opened) return;
   1351 	if (!mimeview->file) return;
   1352 
   1353 	partinfo = mimeview_get_part_to_use(mimeview);
   1354 
   1355 	cm_return_if_fail(partinfo != NULL);
   1356 
   1357 	if (get_part_name(partinfo) == NULL) {
   1358 		return;
   1359 	}
   1360 	partname = g_strdup(get_part_name(partinfo));
   1361 
   1362 	if (!g_utf8_validate(partname, -1, NULL)) {
   1363 		gchar *tmp = conv_filename_to_utf8(partname);
   1364 		if (!tmp) {
   1365 			tmp = conv_codeset_strdup(partname,
   1366 				conv_get_locale_charset_str(),
   1367 				CS_UTF_8);
   1368 		}
   1369 		if (tmp) {
   1370 			g_free(partname);
   1371 			partname = tmp;
   1372 		}
   1373 	}
   1374 
   1375 	subst_for_filename(partname);
   1376 
   1377 	if (prefs_common.attach_save_dir && *prefs_common.attach_save_dir)
   1378 		filepath = g_strconcat(prefs_common.attach_save_dir,
   1379 				       G_DIR_SEPARATOR_S, partname, NULL);
   1380 	else
   1381 		filepath = g_strdup(partname);
   1382 
   1383 	g_free(partname);
   1384 
   1385 	manage_window_focus_in(gtk_widget_get_ancestor(mimeview->hbox, GTK_TYPE_WINDOW), NULL, NULL);
   1386 	filename = filesel_select_file_save(_("Save as"), filepath);
   1387 	if (!filename) {
   1388 		g_free(filepath);
   1389 		return;
   1390 	}
   1391 
   1392 	mimeview_write_part(filename, partinfo, TRUE);
   1393 
   1394 	filedir = g_path_get_dirname(filename);
   1395 	if (filedir && strcmp(filedir, ".")) {
   1396 		g_free(prefs_common.attach_save_dir);
   1397 		prefs_common.attach_save_dir = g_filename_to_utf8(filedir, -1, NULL, NULL, NULL);
   1398 	}
   1399 
   1400 	g_free(filedir);
   1401 	g_free(filepath);
   1402 }
   1403 
   1404 void mimeview_display_as_text(MimeView *mimeview)
   1405 {
   1406 	MimeInfo *partinfo;
   1407 
   1408 	if (!mimeview->opened) return;
   1409 
   1410 	partinfo = mimeview_get_part_to_use(mimeview);
   1411 	mimeview_select_mimepart_icon(mimeview, partinfo);
   1412 	cm_return_if_fail(partinfo != NULL);
   1413 	mimeview_show_message_part(mimeview, partinfo);
   1414 }
   1415 
   1416 void mimeview_launch(MimeView *mimeview, MimeInfo *partinfo)
   1417 {
   1418 	gchar *filename;
   1419 	gint err;
   1420 
   1421 	if (!mimeview->opened) return;
   1422 	if (!mimeview->file) return;
   1423 
   1424 	if (!partinfo)
   1425 		partinfo = mimeview_get_part_to_use(mimeview);
   1426 
   1427 	cm_return_if_fail(partinfo != NULL);
   1428 
   1429 	filename = procmime_get_tmp_file_name(partinfo);
   1430 
   1431 	if ((err = procmime_get_part(filename, partinfo)) < 0)
   1432 		alertpanel_error
   1433 			(_("Couldn't save the part of multipart message: %s"),
   1434 				g_strerror(-err));
   1435 	else
   1436 		mimeview_view_file(filename, partinfo, NULL, mimeview);
   1437 
   1438 	g_free(filename);
   1439 }
   1440 
   1441 void mimeview_open_with(MimeView *mimeview)
   1442 {
   1443 	MimeInfo *partinfo;
   1444 
   1445 	if (!mimeview) return;
   1446 	if (!mimeview->opened) return;
   1447 	if (!mimeview->file) return;
   1448 
   1449 	partinfo = mimeview_get_part_to_use(mimeview);
   1450 
   1451 	mimeview_open_part_with(mimeview, partinfo, FALSE);
   1452 }
   1453 
   1454 static void mimeview_open_part_with(MimeView *mimeview, MimeInfo *partinfo, gboolean automatic)
   1455 {
   1456 	gchar *filename;
   1457 	gchar *cmd;
   1458 	gchar *mime_command = NULL;
   1459 	gchar *content_type = NULL;
   1460 	gint err;
   1461 
   1462 	cm_return_if_fail(partinfo != NULL);
   1463 
   1464 	filename = procmime_get_tmp_file_name(partinfo);
   1465 
   1466 	if ((err = procmime_get_part(filename, partinfo)) < 0) {
   1467 		alertpanel_error
   1468 			(_("Couldn't save the part of multipart message: %s"),
   1469 				g_strerror(-err));
   1470 		g_free(filename);
   1471 		return;
   1472 	}
   1473 
   1474 	if (!prefs_common.mime_open_cmd_history)
   1475 		prefs_common.mime_open_cmd_history =
   1476 			add_history(NULL, prefs_common.mime_open_cmd);
   1477 
   1478 	if ((partinfo->type == MIMETYPE_APPLICATION) &&
   1479             (!g_ascii_strcasecmp(partinfo->subtype, "octet-stream"))) {
   1480 	    	/* guess content-type from filename */
   1481 	    	content_type = procmime_get_mime_type(filename);
   1482 	}
   1483 	if (content_type == NULL) {
   1484 		content_type = procmime_get_content_type_str(partinfo->type,
   1485 			partinfo->subtype);
   1486 	}
   1487 
   1488 	if ((partinfo->type == MIMETYPE_TEXT && !strcmp(partinfo->subtype, "html"))
   1489 	&& prefs_common_get_uri_cmd() && prefs_common.uri_cmd[0]) {
   1490 		mime_command = g_strdup(prefs_common_get_uri_cmd());
   1491 		g_free(content_type);
   1492 		content_type = NULL;
   1493 	} else if (partinfo->type != MIMETYPE_TEXT || !prefs_common_get_ext_editor_cmd()
   1494 	||  !prefs_common_get_ext_editor_cmd()[0]) {
   1495 		mime_command = mailcap_get_command_for_type(content_type, filename);
   1496 	} else {
   1497 		mime_command = g_strdup(prefs_common_get_ext_editor_cmd());
   1498 		g_free(content_type);
   1499 		content_type = NULL;
   1500 	}
   1501 	if (mime_command == NULL) {
   1502 		/* try with extension this time */
   1503 		g_free(content_type);
   1504 		content_type = procmime_get_mime_type(filename);
   1505 		mime_command = mailcap_get_command_for_type(content_type, filename);
   1506 	}
   1507 
   1508 	if (mime_command == NULL)
   1509 		automatic = FALSE;
   1510 
   1511 	if (!automatic) {
   1512 		gboolean remember = FALSE;
   1513 		if (content_type != NULL)
   1514 			cmd = input_dialog_combo_remember
   1515 				(_("Open with"),
   1516 				 _("Enter the command-line to open file:\n"
   1517 				   "('%s' will be replaced with file name)"),
   1518 				 mime_command ? mime_command : prefs_common.mime_open_cmd,
   1519 				 prefs_common.mime_open_cmd_history, &remember);
   1520 		else
   1521 			cmd = input_dialog_combo
   1522 				(_("Open with"),
   1523 				 _("Enter the command-line to open file:\n"
   1524 				   "('%s' will be replaced with file name)"),
   1525 				 mime_command ? mime_command : prefs_common.mime_open_cmd,
   1526 				 prefs_common.mime_open_cmd_history);
   1527 		if (cmd && remember) {
   1528 			mailcap_update_default(content_type, cmd);
   1529 		}
   1530 		g_free(mime_command);
   1531 	} else {
   1532 		cmd = mime_command;
   1533 	}
   1534 	if (cmd) {
   1535 		mimeview_view_file(filename, partinfo, cmd, mimeview);
   1536 		g_free(prefs_common.mime_open_cmd);
   1537 		prefs_common.mime_open_cmd = cmd;
   1538 		prefs_common.mime_open_cmd_history =
   1539 			add_history(prefs_common.mime_open_cmd_history, cmd);
   1540 	}
   1541 
   1542 	g_free(content_type);
   1543 	g_free(filename);
   1544 }
   1545 
   1546 static void mimeview_send_to(MimeView *mimeview, MimeInfo *partinfo)
   1547 {
   1548 	GList *attach_file = NULL;
   1549 	AttachInfo *ainfo = NULL;
   1550 	gchar *filename;
   1551 	gint err;
   1552 
   1553 	if (!mimeview->opened) return;
   1554 	if (!mimeview->file) return;
   1555 
   1556 	cm_return_if_fail(partinfo != NULL);
   1557 
   1558 	filename = procmime_get_tmp_file_name(partinfo);
   1559 
   1560 	if (!(err = procmime_get_part(filename, partinfo))) {
   1561 		ainfo = g_new0(AttachInfo, 1);
   1562 		ainfo->file = filename;
   1563 		ainfo->name = g_strdup(get_part_name(partinfo));
   1564 		ainfo->content_type = procmime_get_content_type_str(
   1565 					partinfo->type, partinfo->subtype);
   1566 		ainfo->charset = g_strdup(procmime_mimeinfo_get_parameter(
   1567 					partinfo, "charset"));
   1568 		attach_file = g_list_append(attach_file, ainfo);
   1569 
   1570 		compose_new(NULL, NULL, attach_file);
   1571 
   1572 		g_free(ainfo->name);
   1573 		g_free(ainfo->content_type);
   1574 		g_free(ainfo->charset);
   1575 		g_free(ainfo);
   1576 		g_list_free(attach_file);
   1577 	} else
   1578 		alertpanel_error
   1579 			(_("Couldn't save the part of multipart message: %s"),
   1580 				g_strerror(-err));
   1581 	g_free(filename);
   1582 }
   1583 
   1584 static void mimeview_view_file(const gchar *filename, MimeInfo *partinfo,
   1585 			       const gchar *cmd, MimeView *mimeview)
   1586 {
   1587 	gchar *p;
   1588 	gchar buf[BUFFSIZE];
   1589 
   1590 	if (cmd == NULL)
   1591 		mimeview_open_part_with(mimeview, partinfo, TRUE);
   1592 	else {
   1593 		if ((p = strchr(cmd, '%')) && *(p + 1) == 's' &&
   1594 		    !strchr(p + 2, '%')) {
   1595 			g_snprintf(buf, sizeof(buf), cmd, filename);
   1596 			if (!prefs_common.save_parts_readwrite)
   1597 				g_chmod(filename, S_IRUSR);
   1598 			else
   1599 				g_chmod(filename, S_IRUSR|S_IWUSR);
   1600  		} else {
   1601 			g_warning("MIME viewer command-line is invalid: '%s'", cmd);
   1602 			cmd = NULL;
   1603 			mimeview_open_part_with(mimeview, partinfo, FALSE);
   1604  		}
   1605 		if (cmd != NULL && execute_command_line(buf, TRUE, NULL) != 0) {
   1606 			if (!prefs_common.save_parts_readwrite)
   1607 				g_chmod(filename, S_IRUSR|S_IWUSR);
   1608 			mimeview_open_part_with(mimeview, partinfo, FALSE);
   1609 		}
   1610 	}
   1611 }
   1612 
   1613 void mimeview_register_viewer_factory(MimeViewerFactory *factory)
   1614 {
   1615 	mimeviewer_factories = g_slist_append(mimeviewer_factories, factory);
   1616 }
   1617 
   1618 static gint cmp_viewer_by_factroy(gconstpointer a, gconstpointer b)
   1619 {
   1620 	return ((MimeViewer *) a)->factory == (MimeViewerFactory *) b ? 0 : -1;
   1621 }
   1622 
   1623 void mimeview_unregister_viewer_factory(MimeViewerFactory *factory)
   1624 {
   1625 	GSList *mimeview_list, *viewer_list;
   1626 
   1627 	for (mimeview_list = mimeviews; mimeview_list != NULL; mimeview_list = g_slist_next(mimeview_list)) {
   1628 		MimeView *mimeview = (MimeView *) mimeview_list->data;
   1629 
   1630 		if (mimeview->mimeviewer && mimeview->mimeviewer->factory == factory) {
   1631 			mimeview_change_view_type(mimeview, MIMEVIEW_TEXT);
   1632 			mimeview->mimeviewer = NULL;
   1633 		}
   1634 
   1635 		while ((viewer_list = g_slist_find_custom(mimeview->viewers, factory, cmp_viewer_by_factroy)) != NULL) {
   1636 			MimeViewer *mimeviewer = (MimeViewer *) viewer_list->data;
   1637 
   1638 			mimeviewer->destroy_viewer(mimeviewer);
   1639 			mimeview->viewers = g_slist_remove(mimeview->viewers, mimeviewer);
   1640 		}
   1641 	}
   1642 
   1643 	mimeviewer_factories = g_slist_remove(mimeviewer_factories, factory);
   1644 }
   1645 
   1646 static gboolean icon_clicked_cb (GtkWidget *button, GdkEventButton *event, MimeView *mimeview)
   1647 {
   1648 	gint      num;
   1649 	MimeInfo *partinfo;
   1650 
   1651 	num      = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(button), "icon_number"));
   1652 	partinfo = g_object_get_data(G_OBJECT(button), "partinfo");
   1653 
   1654 	if (event->button == 1) {
   1655 		icon_selected(mimeview, num, partinfo);
   1656 		gtk_widget_grab_focus(button);
   1657 		icon_list_toggle_by_mime_info(mimeview, partinfo);
   1658 	}
   1659 	part_button_pressed(mimeview, event, partinfo);
   1660 
   1661 	return FALSE;
   1662 }
   1663 
   1664 static void icon_selected (MimeView *mimeview, gint num, MimeInfo *partinfo)
   1665 {
   1666 	GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(mimeview->ctree));
   1667 	GtkTreeIter iter;
   1668 	GtkTreePath *path;
   1669 	MimeInfo *curr = NULL;
   1670 
   1671 	if (!gtk_tree_model_get_iter_first(model, &iter))
   1672 		return;
   1673 	path = gtk_tree_model_get_path(model, &iter);
   1674 
   1675 	do {
   1676 		gtk_tree_model_get_iter(model, &iter, path);
   1677 		gtk_tree_model_get(model, &iter, COL_DATA, &curr, -1);
   1678 		if (curr == partinfo) {
   1679 			GtkTreeSelection *sel = gtk_tree_view_get_selection(
   1680 						GTK_TREE_VIEW(mimeview->ctree));
   1681 			gtk_tree_selection_select_iter(sel, &iter);
   1682 			gtk_tree_path_free(path);
   1683 			return;
   1684 		}
   1685 	} while (mimeview_tree_next(model, path));
   1686 
   1687 	gtk_tree_path_free(path);
   1688 }
   1689 
   1690 void mimeview_select_mimepart_icon(MimeView *mimeview, MimeInfo *partinfo)
   1691 {
   1692 	icon_list_toggle_by_mime_info(mimeview, partinfo);
   1693 	icon_selected(mimeview, -1, partinfo);
   1694 }
   1695 
   1696 static gint icon_key_pressed(GtkWidget *button, GdkEventKey *event,
   1697 			     MimeView *mimeview)
   1698 {
   1699 	SummaryView  *summaryview;
   1700 
   1701 	if (!event) return FALSE;
   1702 
   1703 	switch (event->keyval) {
   1704 	case GDK_KEY_y:
   1705 		BREAK_ON_MODIFIER_KEY();
   1706 		mimeview_save_as(mimeview);
   1707 		return TRUE;
   1708 	case GDK_KEY_t:
   1709 		BREAK_ON_MODIFIER_KEY();
   1710 		mimeview_display_as_text(mimeview);
   1711 		return TRUE;
   1712 	case GDK_KEY_l:
   1713 		BREAK_ON_MODIFIER_KEY();
   1714 		mimeview_launch(mimeview, NULL);
   1715 		return TRUE;
   1716 	case GDK_KEY_o:
   1717 		BREAK_ON_MODIFIER_KEY();
   1718 		mimeview_open_with(mimeview);
   1719 		return TRUE;
   1720 	case GDK_KEY_a:
   1721 		BREAK_ON_MODIFIER_KEY();
   1722 		mimeview_select_next_part(mimeview);
   1723 		return TRUE;
   1724 	default:
   1725 		break;
   1726 	}
   1727 
   1728 	if (!mimeview->messageview->mainwin) return FALSE;
   1729 	summaryview = mimeview->messageview->mainwin->summaryview;
   1730 	return summary_pass_key_press_event(summaryview, event);
   1731 }
   1732 
   1733 static gboolean icon_popup_menu(GtkWidget *widget, gpointer data)
   1734 {
   1735 	MimeView *mimeview = (MimeView *)data;
   1736 	MimeInfo *partinfo = g_object_get_data(G_OBJECT(widget), "partinfo");
   1737 
   1738 	g_object_set_data(G_OBJECT(mimeview->popupmenu),
   1739 			  "pop_partinfo", partinfo);
   1740 	gtk_menu_popup_at_pointer(GTK_MENU(mimeview->popupmenu), NULL);
   1741 	return TRUE;
   1742 }
   1743 
   1744 static void icon_list_append_icon (MimeView *mimeview, MimeInfo *mimeinfo)
   1745 {
   1746 	GtkWidget *pixmap = NULL;
   1747 	GtkWidget *grid;
   1748 	GtkWidget *button;
   1749 	gchar *tip;
   1750 	gchar *tiptmp;
   1751 	const gchar *desc = NULL;
   1752 	gchar *sigshort = NULL;
   1753 	gchar *content_type;
   1754 	StockPixmap stockp;
   1755 	MimeInfo *partinfo;
   1756 
   1757 	if (!prefs_common.show_inline_attachments && mimeinfo->id)
   1758 		return;
   1759 
   1760 	grid = mimeview->icon_grid;
   1761 	mimeview->icon_count++;
   1762 	button = gtk_event_box_new();
   1763 
   1764 	gtk_container_set_border_width(GTK_CONTAINER(button), 2);
   1765 	g_object_set_data(G_OBJECT(button), "icon_number",
   1766 			  GINT_TO_POINTER(mimeview->icon_count));
   1767 	g_object_set_data(G_OBJECT(button), "partinfo",
   1768 		          mimeinfo);
   1769 
   1770 	switch (mimeinfo->type) {
   1771 
   1772 	case MIMETYPE_TEXT:
   1773 		if (mimeinfo->subtype && !g_ascii_strcasecmp(mimeinfo->subtype, "html"))
   1774 			stockp = STOCK_PIXMAP_MIME_TEXT_HTML;
   1775 		else if  (mimeinfo->subtype && !g_ascii_strcasecmp(mimeinfo->subtype, "calendar"))
   1776 			stockp = STOCK_PIXMAP_MIME_TEXT_CALENDAR;
   1777 		else if  (mimeinfo->subtype && (!g_ascii_strcasecmp(mimeinfo->subtype, "x-patch")
   1778 				|| !g_ascii_strcasecmp(mimeinfo->subtype, "x-diff")))
   1779 			stockp = STOCK_PIXMAP_MIME_TEXT_PATCH;
   1780 		else
   1781 			stockp = STOCK_PIXMAP_MIME_TEXT_PLAIN;
   1782 		break;
   1783 	case MIMETYPE_MESSAGE:
   1784 		stockp = STOCK_PIXMAP_MIME_MESSAGE;
   1785 		break;
   1786 	case MIMETYPE_APPLICATION:
   1787 		if (mimeinfo->subtype && (!g_ascii_strcasecmp(mimeinfo->subtype, "pgp-signature")
   1788 		    || !g_ascii_strcasecmp(mimeinfo->subtype, "x-pkcs7-signature")
   1789 		    || !g_ascii_strcasecmp(mimeinfo->subtype, "pkcs7-signature")))
   1790 			stockp = STOCK_PIXMAP_MIME_PGP_SIG;
   1791 		else if (mimeinfo->subtype && !g_ascii_strcasecmp(mimeinfo->subtype, "pdf"))
   1792 			stockp = STOCK_PIXMAP_MIME_PDF;
   1793 		else if  (mimeinfo->subtype && !g_ascii_strcasecmp(mimeinfo->subtype, "postscript"))
   1794 			stockp = STOCK_PIXMAP_MIME_PS;
   1795 		else
   1796 			stockp = STOCK_PIXMAP_MIME_APPLICATION;
   1797 		break;
   1798 	case MIMETYPE_IMAGE:
   1799 		stockp = STOCK_PIXMAP_MIME_IMAGE;
   1800 		break;
   1801 	case MIMETYPE_AUDIO:
   1802 		stockp = STOCK_PIXMAP_MIME_AUDIO;
   1803 		break;
   1804 	default:
   1805 		stockp = STOCK_PIXMAP_MIME_UNKNOWN;
   1806 		break;
   1807 	}
   1808 
   1809 	partinfo = mimeinfo;
   1810 	while (partinfo != NULL) {
   1811 		partinfo = procmime_mimeinfo_parent(partinfo);
   1812 	}
   1813 	pixmap = stock_pixmap_widget_with_overlay(stockp, 0, OVERLAY_NONE, 6, 3);
   1814 
   1815 	gtk_container_add(GTK_CONTAINER(button), pixmap);
   1816 	if (!desc) {
   1817 		if (prefs_common.attach_desc)
   1818 			desc = get_part_description(mimeinfo);
   1819 		else
   1820 			desc = get_part_name(mimeinfo);
   1821 	}
   1822 
   1823 	content_type = procmime_get_content_type_str(mimeinfo->type,
   1824 						     mimeinfo->subtype);
   1825 
   1826 	tip = g_strconcat("<b>", _("Type:"), "  </b>", content_type,
   1827 			  "\n<b>", _("Size:"), " </b>",
   1828 			  to_human_readable((goffset)mimeinfo->length), NULL);
   1829 	g_free(content_type);
   1830 	if (desc && *desc) {
   1831 		gchar *tmp = NULL, *escaped = NULL;
   1832 		if (!g_utf8_validate(desc, -1, NULL)) {
   1833 			tmp = conv_filename_to_utf8(desc);
   1834 		} else {
   1835 			tmp = g_strdup(desc);
   1836 		}
   1837 		escaped = g_markup_escape_text(tmp,-1);
   1838 
   1839 		tiptmp = g_strconcat(tip, "\n<b>",
   1840 				prefs_common.attach_desc && mimeinfo->description ?
   1841 				_("Description:") : _("Filename:"),
   1842 				" </b>", escaped, NULL);
   1843 		g_free(tip);
   1844 		tip = tiptmp;
   1845 		g_free(tmp);
   1846 		g_free(escaped);
   1847 	}
   1848 	if (sigshort && *sigshort) {
   1849 		gchar *sigshort_escaped =
   1850 			g_markup_escape_text(sigshort, -1);
   1851 
   1852 		tiptmp = g_strjoin("\n", tip, sigshort_escaped, NULL);
   1853 		g_free(tip);
   1854 		g_free(sigshort_escaped);
   1855 		tip = tiptmp;
   1856 	}
   1857 
   1858 	gtk_widget_set_tooltip_markup(button, tip);
   1859 	g_free(tip);
   1860 	gtk_widget_show_all(button);
   1861 
   1862 	g_signal_connect(G_OBJECT(button), "popup-menu",
   1863 			 G_CALLBACK(icon_popup_menu), mimeview);
   1864 	g_signal_connect(G_OBJECT(button), "button_release_event",
   1865 			 G_CALLBACK(icon_clicked_cb), mimeview);
   1866 	g_signal_connect(G_OBJECT(button), "key_press_event",
   1867 			 G_CALLBACK(icon_key_pressed), mimeview);
   1868 	gtk_container_add(GTK_CONTAINER(grid), button);
   1869 }
   1870 
   1871 static void icon_list_clear (MimeView *mimeview)
   1872 {
   1873 	GList     *child, *orig;
   1874 	GtkAdjustment *adj;
   1875 
   1876 	orig = gtk_container_get_children(GTK_CONTAINER(mimeview->icon_grid));
   1877 	for (child = orig; child != NULL; child = g_list_next(child)) {
   1878 		gtk_container_remove(GTK_CONTAINER(mimeview->icon_grid),
   1879 				     GTK_WIDGET(child->data));
   1880 	}
   1881 	g_list_free(orig);
   1882 	mimeview->icon_count = 0;
   1883 	adj  = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(mimeview->icon_scroll));
   1884 	gtk_adjustment_set_value(adj, gtk_adjustment_get_lower(adj));
   1885 }
   1886 
   1887 /*!
   1888  *\brief        Used to 'click' the next or previous icon.
   1889  *
   1890  *\return       true if the icon 'number' exists and was selected.
   1891  */
   1892 static void icon_scroll_size_allocate_cb(GtkWidget *widget,
   1893 					 GtkAllocation *size, MimeView *mimeview)
   1894 {
   1895 	GtkAllocation grid_size;
   1896 	GtkAllocation layout_size;
   1897 	GtkAdjustment *adj;
   1898 	guint width;
   1899 	guint height;
   1900 
   1901 	adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(mimeview->icon_scroll));
   1902 
   1903 	gtk_widget_get_allocation(mimeview->icon_grid, &grid_size);
   1904 	gtk_widget_get_allocation(mimeview->icon_scroll, &layout_size);
   1905 
   1906 	gtk_layout_get_size(GTK_LAYOUT(mimeview->icon_scroll), &width, &height);
   1907 	gtk_layout_set_size(GTK_LAYOUT(mimeview->icon_scroll),
   1908 			    MIN(grid_size.width, layout_size.width),
   1909 			    MAX(grid_size.height, layout_size.height));
   1910 	gtk_adjustment_set_step_increment(adj, 10);
   1911 }
   1912 
   1913 static void icon_list_create(MimeView *mimeview, MimeInfo *mimeinfo)
   1914 {
   1915 	icon_list_clear(mimeview);
   1916 	while (mimeinfo != NULL) {
   1917 		if (mimeinfo->type != MIMETYPE_MULTIPART)
   1918 			icon_list_append_icon(mimeview, mimeinfo);
   1919 		if (mimeinfo->node->children != NULL)
   1920 			icon_list_create(mimeview,
   1921 				(MimeInfo *) mimeinfo->node->children->data);
   1922 		mimeinfo = mimeinfo->node->next != NULL
   1923 			 ? (MimeInfo *) mimeinfo->node->next->data
   1924 			 : NULL;
   1925 	}
   1926 
   1927 	gint min_width, width;
   1928 	gtk_widget_get_preferred_width(mimeview->icon_mainbox, &min_width, &width);
   1929 	if (min_width < width) {
   1930 		gtk_widget_set_size_request(mimeview->icon_mainbox,
   1931 					    min_width, -1);
   1932 	}
   1933 	if (mimeview->opened)
   1934 		icon_list_toggle_by_mime_info(mimeview,
   1935 			mimeview_get_node_part(mimeview, mimeview->opened));
   1936 }
   1937 
   1938 static void icon_list_toggle_by_mime_info (MimeView	*mimeview,
   1939 					   MimeInfo	*mimeinfo)
   1940 {
   1941 	GList *children, *child;
   1942 
   1943 	children = gtk_container_get_children(GTK_CONTAINER(mimeview->icon_grid));
   1944 	for (child = children; child != NULL; child = g_list_next(child)) {
   1945 		gboolean *highlight = NULL;
   1946 		GtkWidget *icon = gtk_bin_get_child(GTK_BIN(child->data));
   1947 
   1948 		if (!GTK_IS_EVENT_BOX(child->data))
   1949 			continue;
   1950 
   1951 		highlight = g_object_get_data(G_OBJECT(icon), "highlight");
   1952 		*highlight = (g_object_get_data(G_OBJECT(child->data),
   1953 			 	      "partinfo") == (gpointer)mimeinfo);
   1954 
   1955 		gtk_widget_queue_draw(icon);
   1956 	}
   1957 	g_list_free(children);
   1958 }
   1959 
   1960 static void ctree_size_allocate_cb(GtkWidget *widget, GtkAllocation *allocation,
   1961 				    MimeView *mimeview)
   1962 {
   1963 	prefs_common.mimeview_tree_height = allocation->height;
   1964 }
   1965 
   1966 static gint mime_toggle_button_cb(GtkWidget *button, GdkEventButton *event,
   1967 				    MimeView *mimeview)
   1968 {
   1969 	g_object_ref(button);
   1970 
   1971 	mimeview->ctree_mode = !mimeview->ctree_mode;
   1972 	if (mimeview->ctree_mode) {
   1973 		gtk_image_set_from_icon_name(GTK_IMAGE(mimeview->arrow),
   1974 					      "pan-end-symbolic", GTK_ICON_SIZE_MENU);
   1975 		gtk_widget_hide(mimeview->icon_mainbox);
   1976 		gtk_widget_show(mimeview->ctree_mainbox);
   1977 		gtk_paned_set_position(GTK_PANED(mimeview->paned),
   1978 					prefs_common.mimeview_tree_height);
   1979 
   1980 		gtk_container_remove(GTK_CONTAINER(mimeview->icon_mainbox),
   1981 				     button);
   1982 		gtk_box_pack_end(GTK_BOX(mimeview->ctree_mainbox),
   1983 				   button, FALSE, FALSE, 0);
   1984 	} else {
   1985 		gtk_image_set_from_icon_name(GTK_IMAGE(mimeview->arrow),
   1986 					      "pan-start-symbolic", GTK_ICON_SIZE_MENU);
   1987 		gtk_widget_hide(mimeview->ctree_mainbox);
   1988 		gtk_widget_show(mimeview->icon_mainbox);
   1989 		gtk_paned_set_position(GTK_PANED(mimeview->paned), 0);
   1990 
   1991 		gtk_container_remove(GTK_CONTAINER(mimeview->ctree_mainbox),
   1992 				     button);
   1993 		gtk_box_pack_start(GTK_BOX(mimeview->icon_mainbox),
   1994 				   button, FALSE, FALSE, 0);
   1995 		gtk_box_reorder_child(GTK_BOX(gtk_widget_get_parent(button)), button, 0);
   1996 		if (mimeview->opened)
   1997 			icon_list_toggle_by_mime_info(mimeview,
   1998 					mimeview_get_node_part(mimeview, mimeview->opened));
   1999 		summary_grab_focus(mimeview->mainwin->summaryview);
   2000 	}
   2001 	g_object_unref(button);
   2002 	return TRUE;
   2003 }
   2004 
   2005 void mimeview_update (MimeView *mimeview)
   2006 {
   2007 	if (mimeview && mimeview->mimeinfo) {
   2008 		icon_list_create(mimeview, mimeview->mimeinfo);
   2009 	}
   2010 }
   2011 
   2012 void mimeview_handle_cmd(MimeView *mimeview, const gchar *cmd, GdkEventButton *event, gpointer data)
   2013 {
   2014 	MessageView *msgview = NULL;
   2015 	MainWindow *mainwin = NULL;
   2016 
   2017 	if (!cmd)
   2018 		return;
   2019 
   2020 	msgview = mimeview->messageview;
   2021 	if (!msgview)
   2022 		return;
   2023 
   2024 	mainwin = msgview->mainwin;
   2025 	if (!mainwin)
   2026 		return;
   2027 
   2028 	g_object_set_data(G_OBJECT(mimeview->popupmenu),
   2029 			  "pop_partinfo", NULL);
   2030 
   2031 	if (!strcmp(cmd, "cm://view_log"))
   2032 		log_window_show(mainwin->logwin);
   2033 	else if (!strcmp(cmd, "cm://save_as"))
   2034 		mimeview_save_as(mimeview);
   2035 	else if (!strcmp(cmd, "cm://display_as_text"))
   2036 		mimeview_display_as_text(mimeview);
   2037 	else if (!strcmp(cmd, "cm://open_with"))
   2038 		mimeview_open_with(mimeview);
   2039 	else if (!strcmp(cmd, "cm://open"))
   2040 		mimeview_launch(mimeview, NULL);
   2041 	else if (!strcmp(cmd, "cm://select_attachment") && data != NULL) {
   2042 		icon_list_toggle_by_mime_info(mimeview, (MimeInfo *)data);
   2043 		icon_selected(mimeview, -1, (MimeInfo *)data);
   2044 	} else if (!strcmp(cmd, "cm://open_attachment") && data != NULL) {
   2045 		mimeview_launch(mimeview, (MimeInfo *)data);
   2046 	} else if (!strcmp(cmd, "cm://menu_attachment") && data != NULL) {
   2047 		mimeview->spec_part = (MimeInfo *)data;
   2048 		part_button_pressed(mimeview, event, (MimeInfo *)data);
   2049 	}
   2050 }