1 /* ev-sidebar-attachments.c
2  *  this file is part of evince, a gnome document viewer
3  *
4  * Copyright (C) 2006 Carlos Garcia Campos
5  *
6  * Author:
7  *   Carlos Garcia Campos <carlosgc@gnome.org>
8  *
9  * Evince is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * Evince is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #include "config.h"
26 #endif
27 
28 #include <string.h>
29 
30 #include <glib/gi18n.h>
31 #include <glib/gstdio.h>
32 #include <gtk/gtk.h>
33 
34 #include "ev-document-attachments.h"
35 #include "ev-document-misc.h"
36 #include "ev-jobs.h"
37 #include "ev-job-scheduler.h"
38 #include "ev-file-helpers.h"
39 #include "ev-sidebar-attachments.h"
40 #include "ev-sidebar-page.h"
41 
42 enum {
43 	COLUMN_ICON,
44 	COLUMN_NAME,
45 	COLUMN_DESCRIPTION,
46 	COLUMN_ATTACHMENT,
47 	N_COLS
48 };
49 
50 enum {
51 	PROP_0,
52 	PROP_WIDGET,
53 };
54 
55 enum {
56 	SIGNAL_POPUP_MENU,
57 	SIGNAL_SAVE_ATTACHMENT,
58 	N_SIGNALS
59 };
60 
61 enum {
62 	EV_DND_TARGET_XDS,
63 	EV_DND_TARGET_TEXT_URI_LIST
64 };
65 static guint signals[N_SIGNALS];
66 
67 #define XDS_ATOM                gdk_atom_intern_static_string  ("XdndDirectSave0")
68 #define TEXT_ATOM               gdk_atom_intern_static_string  ("text/plain")
69 #define STRING_ATOM             gdk_atom_intern_static_string  ("STRING")
70 #define MAX_XDS_ATOM_VAL_LEN    4096
71 #define XDS_ERROR               'E'
72 #define XDS_SUCCESS             'S'
73 
74 struct _EvSidebarAttachmentsPrivate {
75 	GtkWidget      *icon_view;
76 	GtkListStore   *model;
77 
78 	/* Icons */
79 	GtkIconTheme   *icon_theme;
80 	GHashTable     *icon_cache;
81 };
82 
83 static void ev_sidebar_attachments_page_iface_init (EvSidebarPageInterface *iface);
84 
85 G_DEFINE_TYPE_EXTENDED (EvSidebarAttachments,
86                         ev_sidebar_attachments,
87                         GTK_TYPE_BOX,
88                         0,
89                         G_ADD_PRIVATE (EvSidebarAttachments)
90                         G_IMPLEMENT_INTERFACE (EV_TYPE_SIDEBAR_PAGE,
91 					       ev_sidebar_attachments_page_iface_init))
92 
93 /* Icon cache */
94 static void
ev_sidebar_attachments_icon_cache_add(EvSidebarAttachments * ev_attachbar,const gchar * mime_type,const GdkPixbuf * pixbuf)95 ev_sidebar_attachments_icon_cache_add (EvSidebarAttachments *ev_attachbar,
96 				       const gchar          *mime_type,
97 				       const GdkPixbuf      *pixbuf)
98 {
99 	g_assert (mime_type != NULL);
100 	g_assert (GDK_IS_PIXBUF (pixbuf));
101 
102 	g_hash_table_insert (ev_attachbar->priv->icon_cache,
103 			     (gpointer)g_strdup (mime_type),
104 			     (gpointer)pixbuf);
105 
106 }
107 
108 static GdkPixbuf *
icon_theme_get_pixbuf_from_mime_type(GtkIconTheme * icon_theme,const gchar * mime_type)109 icon_theme_get_pixbuf_from_mime_type (GtkIconTheme *icon_theme,
110 				      const gchar  *mime_type)
111 {
112 	const char *separator;
113 	GString *icon_name;
114 	GdkPixbuf *pixbuf;
115 
116 	separator = strchr (mime_type, '/');
117 	if (!separator)
118 		return NULL; /* maybe we should return a GError with "invalid MIME-type" */
119 
120 	icon_name = g_string_new ("gnome-mime-");
121 	g_string_append_len (icon_name, mime_type, separator - mime_type);
122 	g_string_append_c (icon_name, '-');
123 	g_string_append (icon_name, separator + 1);
124 	pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str, 48, 0, NULL);
125 	g_string_free (icon_name, TRUE);
126 	if (pixbuf)
127 		return pixbuf;
128 
129 	icon_name = g_string_new ("gnome-mime-");
130 	g_string_append_len (icon_name, mime_type, separator - mime_type);
131 	pixbuf = gtk_icon_theme_load_icon (icon_theme, icon_name->str, 48, 0, NULL);
132 	g_string_free (icon_name, TRUE);
133 
134 	return pixbuf;
135 }
136 
137 static GdkPixbuf *
ev_sidebar_attachments_icon_cache_get(EvSidebarAttachments * ev_attachbar,const gchar * mime_type)138 ev_sidebar_attachments_icon_cache_get (EvSidebarAttachments *ev_attachbar,
139 				       const gchar          *mime_type)
140 {
141 	GdkPixbuf *pixbuf = NULL;
142 
143 	g_assert (mime_type != NULL);
144 
145 	pixbuf = g_hash_table_lookup (ev_attachbar->priv->icon_cache,
146 				      mime_type);
147 
148 	if (GDK_IS_PIXBUF (pixbuf))
149 		return pixbuf;
150 
151 	pixbuf = icon_theme_get_pixbuf_from_mime_type (ev_attachbar->priv->icon_theme,
152 						       mime_type);
153 
154 	if (GDK_IS_PIXBUF (pixbuf))
155 		ev_sidebar_attachments_icon_cache_add (ev_attachbar,
156 						       mime_type,
157 						       pixbuf);
158 
159 	return pixbuf;
160 }
161 
162 static gboolean
icon_cache_update_icon(gchar * key,GdkPixbuf * value,EvSidebarAttachments * ev_attachbar)163 icon_cache_update_icon (gchar                *key,
164 			GdkPixbuf            *value,
165 			EvSidebarAttachments *ev_attachbar)
166 {
167 	GdkPixbuf *pixbuf = NULL;
168 
169 	pixbuf = icon_theme_get_pixbuf_from_mime_type (ev_attachbar->priv->icon_theme,
170 						       key);
171 
172 	ev_sidebar_attachments_icon_cache_add (ev_attachbar,
173 					       key,
174 					       pixbuf);
175 
176 	return FALSE;
177 }
178 
179 static void
ev_sidebar_attachments_icon_cache_refresh(EvSidebarAttachments * ev_attachbar)180 ev_sidebar_attachments_icon_cache_refresh (EvSidebarAttachments *ev_attachbar)
181 {
182 	g_hash_table_foreach_remove (ev_attachbar->priv->icon_cache,
183 				     (GHRFunc) icon_cache_update_icon,
184 				     ev_attachbar);
185 }
186 
187 static EvAttachment *
ev_sidebar_attachments_get_attachment_at_pos(EvSidebarAttachments * ev_attachbar,gint x,gint y)188 ev_sidebar_attachments_get_attachment_at_pos (EvSidebarAttachments *ev_attachbar,
189 					      gint                  x,
190 					      gint                  y)
191 {
192 	GtkTreePath  *path = NULL;
193 	GtkTreeIter   iter;
194 	EvAttachment *attachment = NULL;
195 
196 	path = gtk_icon_view_get_path_at_pos (GTK_ICON_VIEW (ev_attachbar->priv->icon_view),
197 					      x, y);
198 	if (!path) {
199 		return NULL;
200 	}
201 
202 	gtk_tree_model_get_iter (GTK_TREE_MODEL (ev_attachbar->priv->model),
203 				 &iter, path);
204 	gtk_tree_model_get (GTK_TREE_MODEL (ev_attachbar->priv->model), &iter,
205 			    COLUMN_ATTACHMENT, &attachment,
206 			    -1);
207 
208 	gtk_icon_view_select_path (GTK_ICON_VIEW (ev_attachbar->priv->icon_view),
209 				   path);
210 
211 	gtk_tree_path_free (path);
212 
213 	return attachment;
214 }
215 
216 static gboolean
ev_sidebar_attachments_popup_menu_show(EvSidebarAttachments * ev_attachbar,gint x,gint y)217 ev_sidebar_attachments_popup_menu_show (EvSidebarAttachments *ev_attachbar,
218 					gint                  x,
219 					gint                  y)
220 {
221 	GtkIconView *icon_view;
222 	GtkTreePath *path;
223 	GList       *selected = NULL, *l;
224 	GList       *attach_list = NULL;
225 
226 	icon_view = GTK_ICON_VIEW (ev_attachbar->priv->icon_view);
227 
228 	path = gtk_icon_view_get_path_at_pos (icon_view, x, y);
229 	if (!path)
230 		return FALSE;
231 
232 	if (!gtk_icon_view_path_is_selected (icon_view, path)) {
233 		gtk_icon_view_unselect_all (icon_view);
234 		gtk_icon_view_select_path (icon_view, path);
235 	}
236 
237 	gtk_tree_path_free (path);
238 
239 	selected = gtk_icon_view_get_selected_items (icon_view);
240 	if (!selected)
241 		return FALSE;
242 
243 	for (l = selected; l && l->data; l = g_list_next (l)) {
244 		GtkTreeIter   iter;
245 		EvAttachment *attachment = NULL;
246 
247 		path = (GtkTreePath *) l->data;
248 
249 		gtk_tree_model_get_iter (GTK_TREE_MODEL (ev_attachbar->priv->model),
250 					 &iter, path);
251 		gtk_tree_model_get (GTK_TREE_MODEL (ev_attachbar->priv->model), &iter,
252 				    COLUMN_ATTACHMENT, &attachment,
253 				    -1);
254 
255 		if (attachment)
256 			attach_list = g_list_prepend (attach_list, attachment);
257 
258 		gtk_tree_path_free (path);
259 	}
260 
261 	g_list_free (selected);
262 
263 	if (!attach_list)
264 		return FALSE;
265 
266 	g_signal_emit (ev_attachbar, signals[SIGNAL_POPUP_MENU], 0, attach_list);
267 
268 	return TRUE;
269 }
270 
271 static gboolean
ev_sidebar_attachments_popup_menu(GtkWidget * widget)272 ev_sidebar_attachments_popup_menu (GtkWidget *widget)
273 {
274 	EvSidebarAttachments *ev_attachbar = EV_SIDEBAR_ATTACHMENTS (widget);
275 	gint                  x, y;
276 
277 	ev_document_misc_get_pointer_position (widget, &x, &y);
278 
279 	return ev_sidebar_attachments_popup_menu_show (ev_attachbar, x, y);
280 }
281 
282 static gboolean
ev_sidebar_attachments_button_press(EvSidebarAttachments * ev_attachbar,GdkEventButton * event,GtkWidget * icon_view)283 ev_sidebar_attachments_button_press (EvSidebarAttachments *ev_attachbar,
284 				     GdkEventButton       *event,
285 				     GtkWidget            *icon_view)
286 {
287 	if (!gtk_widget_has_focus (icon_view)) {
288 		gtk_widget_grab_focus (icon_view);
289 	}
290 
291 	if (event->button == 2)
292 		return FALSE;
293 
294 	switch (event->button) {
295 	        case 1:
296 			if (event->type == GDK_2BUTTON_PRESS) {
297 				GError *error = NULL;
298 				EvAttachment *attachment;
299 
300 				attachment = ev_sidebar_attachments_get_attachment_at_pos (ev_attachbar,
301 											   event->x,
302 											   event->y);
303 				if (!attachment)
304 					return FALSE;
305 
306 				ev_attachment_open (attachment,
307 						    gtk_widget_get_screen (GTK_WIDGET (ev_attachbar)),
308 						    event->time,
309 						    &error);
310 
311 				if (error) {
312 					g_warning ("%s", error->message);
313 					g_error_free (error);
314 				}
315 
316 				g_object_unref (attachment);
317 
318 				return TRUE;
319 			}
320 			break;
321 	        case 3:
322 			return ev_sidebar_attachments_popup_menu_show (ev_attachbar, event->x, event->y);
323 	}
324 
325 	return FALSE;
326 }
327 
328 static void
ev_sidebar_attachments_update_icons(EvSidebarAttachments * ev_attachbar,gpointer user_data)329 ev_sidebar_attachments_update_icons (EvSidebarAttachments *ev_attachbar,
330 				     gpointer              user_data)
331 {
332 	GtkTreeIter iter;
333 	gboolean    valid;
334 
335 	ev_sidebar_attachments_icon_cache_refresh (ev_attachbar);
336 
337 	valid = gtk_tree_model_get_iter_first (
338 		GTK_TREE_MODEL (ev_attachbar->priv->model),
339 		&iter);
340 
341 	while (valid) {
342 		EvAttachment *attachment = NULL;
343 		GdkPixbuf    *pixbuf = NULL;
344 		const gchar  *mime_type;
345 
346 		gtk_tree_model_get (GTK_TREE_MODEL (ev_attachbar->priv->model), &iter,
347 				    COLUMN_ATTACHMENT, &attachment,
348 				    -1);
349 
350 		mime_type = ev_attachment_get_mime_type (attachment);
351 
352 		if (attachment)
353 			g_object_unref (attachment);
354 
355 		pixbuf = ev_sidebar_attachments_icon_cache_get (ev_attachbar,
356 								mime_type);
357 
358 		gtk_list_store_set (ev_attachbar->priv->model, &iter,
359 				    COLUMN_ICON, pixbuf,
360 				    -1);
361 
362 		valid = gtk_tree_model_iter_next (
363 			GTK_TREE_MODEL (ev_attachbar->priv->model),
364 			&iter);
365 	}
366 }
367 
368 static void
ev_sidebar_attachments_screen_changed(GtkWidget * widget,GdkScreen * old_screen)369 ev_sidebar_attachments_screen_changed (GtkWidget *widget,
370 				       GdkScreen *old_screen)
371 {
372 	EvSidebarAttachments *ev_attachbar = EV_SIDEBAR_ATTACHMENTS (widget);
373 	GdkScreen            *screen;
374 
375 	if (!ev_attachbar->priv->icon_theme)
376 		return;
377 
378 	screen = gtk_widget_get_screen (widget);
379 	if (screen == old_screen)
380 		return;
381 
382 	if (old_screen) {
383 		g_signal_handlers_disconnect_by_func (
384 			gtk_icon_theme_get_for_screen (old_screen),
385 			G_CALLBACK (ev_sidebar_attachments_update_icons),
386 			ev_attachbar);
387 	}
388 
389 	ev_attachbar->priv->icon_theme = gtk_icon_theme_get_for_screen (screen);
390 	g_signal_connect_swapped (ev_attachbar->priv->icon_theme,
391 				  "changed",
392 				  G_CALLBACK (ev_sidebar_attachments_update_icons),
393 				  (gpointer) ev_attachbar);
394 
395 	if (GTK_WIDGET_CLASS (ev_sidebar_attachments_parent_class)->screen_changed) {
396 		GTK_WIDGET_CLASS (ev_sidebar_attachments_parent_class)->screen_changed (widget, old_screen);
397 	}
398 }
399 
400 
401 static gchar *
read_xds_property(GdkDragContext * context)402 read_xds_property (GdkDragContext *context)
403 {
404 	guchar *prop_text;
405 	gint    length;
406 	gchar  *retval = NULL;
407 
408 	g_assert (context != NULL);
409 
410 	if (gdk_property_get (gdk_drag_context_get_source_window (context), XDS_ATOM, TEXT_ATOM,
411 	                      0, MAX_XDS_ATOM_VAL_LEN, FALSE,
412 	                      NULL, NULL, &length, &prop_text)
413 	    && prop_text) {
414 
415 		/* g_strndup will null terminate the string */
416 		retval = g_strndup ((const gchar *) prop_text, length);
417 		g_free (prop_text);
418 	}
419 
420 	return retval;
421 }
422 
423 static void
write_xds_property(GdkDragContext * context,const gchar * value)424 write_xds_property (GdkDragContext *context,
425                     const gchar *value)
426 {
427 	g_assert (context != NULL);
428 
429 	if (value)
430 		gdk_property_change (gdk_drag_context_get_source_window (context), XDS_ATOM,
431 		                     TEXT_ATOM, 8, GDK_PROP_MODE_REPLACE,
432 		                     (const guchar *) value, strlen (value));
433 	else
434 		gdk_property_delete (gdk_drag_context_get_source_window (context), XDS_ATOM);
435 }
436 
437 /*
438  * Copied from add_custom_button_to_dialog () in gtk+, from file
439  * gtkfilechooserwidget.c
440  */
441 static void
ev_add_custom_button_to_dialog(GtkDialog * dialog,const gchar * mnemonic_label,gint response_id)442 ev_add_custom_button_to_dialog (GtkDialog   *dialog,
443                                 const gchar *mnemonic_label,
444                                 gint         response_id)
445 {
446 	GtkWidget *button;
447 
448 	button = gtk_button_new_with_mnemonic (mnemonic_label);
449 	gtk_widget_set_can_default (button, TRUE);
450 	gtk_widget_show (button);
451 
452 	gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, response_id);
453 }
454 
455 /* Presents an overwrite confirmation dialog; returns whether the file
456  * should be overwritten.
457  * Taken from confirm_dialog_should_accept_filename () in gtk+, from file
458  * gtkfilechooserwidget.c
459  */
460 static gboolean
ev_sidebar_attachments_confirm_overwrite(EvSidebarAttachments * attachbar,const gchar * uri)461 ev_sidebar_attachments_confirm_overwrite (EvSidebarAttachments   *attachbar,
462                                           const gchar            *uri)
463 {
464 	GtkWidget *toplevel, *dialog;
465 	int        response;
466 	gchar     *filename, *basename;
467 	GFile     *file;
468 
469 	filename = g_filename_from_uri (uri, NULL, NULL);
470 	if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
471 		g_free (filename);
472 		return TRUE;
473 	}
474 	g_free (filename);
475 
476 	file = g_file_new_for_uri (uri);
477 	basename = g_file_get_basename (file);
478 	g_object_unref (file);
479 
480 	toplevel = gtk_widget_get_toplevel (GTK_WIDGET (attachbar));
481 	dialog = gtk_message_dialog_new (gtk_widget_is_toplevel (toplevel) ? GTK_WINDOW (toplevel) : NULL,
482 	                                 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
483 	                                 GTK_MESSAGE_QUESTION,
484 	                                 GTK_BUTTONS_NONE,
485 	                                 _("A file named “%s” already exists. Do you want to "
486 	                                   "replace it?"),
487 	                                 basename);
488 	gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
489 	                                          _("The file “%s” already exists. Replacing"
490 	                                            " it will overwrite its contents."),
491 	                                          uri);
492 
493 	gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Cancel"), GTK_RESPONSE_CANCEL);
494 	ev_add_custom_button_to_dialog (GTK_DIALOG (dialog), _("_Replace"),
495 	                                GTK_RESPONSE_ACCEPT);
496         /* We are mimicking GtkFileChooserWidget behaviour, hence we keep the
497          * code synced and act like that.
498          */
499 G_GNUC_BEGIN_IGNORE_DEPRECATIONS
500         gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
501                                                  GTK_RESPONSE_ACCEPT,
502                                                  GTK_RESPONSE_CANCEL,
503                                                  -1);
504 G_GNUC_END_IGNORE_DEPRECATIONS
505 	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
506 
507 	if (gtk_window_has_group (GTK_WINDOW (toplevel)))
508 		gtk_window_group_add_window (gtk_window_get_group (GTK_WINDOW (toplevel)),
509 		                             GTK_WINDOW (dialog));
510 
511 	response = gtk_dialog_run (GTK_DIALOG (dialog));
512 
513 	gtk_widget_destroy (dialog);
514 
515 	return (response == GTK_RESPONSE_ACCEPT);
516 }
517 
518 static EvAttachment *
ev_sidebar_attachments_get_selected_attachment(EvSidebarAttachments * ev_attachbar)519 ev_sidebar_attachments_get_selected_attachment (EvSidebarAttachments *ev_attachbar)
520 {
521 	EvAttachment *attachment;
522 	GList        *selected = NULL;
523 	GtkTreeIter   iter;
524 	GtkTreePath  *path;
525 
526 	selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (ev_attachbar->priv->icon_view));
527 
528 	if (!selected)
529 		return NULL;
530 
531 	if (!selected->data) {
532 		g_list_free (selected);
533 		return NULL;
534 	}
535 
536 	path = (GtkTreePath *) selected->data;
537 
538 	gtk_tree_model_get_iter (GTK_TREE_MODEL (ev_attachbar->priv->model),
539 	                         &iter, path);
540 	gtk_tree_model_get (GTK_TREE_MODEL (ev_attachbar->priv->model), &iter,
541 	                    COLUMN_ATTACHMENT, &attachment,
542 	                    -1);
543 
544 	gtk_tree_path_free (path);
545 	g_list_free (selected);
546 
547 	return attachment;
548 }
549 
550 static void
ev_sidebar_attachments_drag_begin(GtkWidget * widget,GdkDragContext * drag_context,EvSidebarAttachments * ev_attachbar)551 ev_sidebar_attachments_drag_begin (GtkWidget            *widget,
552                                    GdkDragContext       *drag_context,
553                                    EvSidebarAttachments *ev_attachbar)
554 {
555 	EvAttachment         *attachment;
556 	gchar                *filename;
557 
558 	attachment = ev_sidebar_attachments_get_selected_attachment (ev_attachbar);
559 
560         if (!attachment)
561                 return;
562 
563 	filename = g_build_filename (ev_attachment_get_name (attachment), NULL);
564 	write_xds_property (drag_context, filename);
565 
566 	g_free (filename);
567 	g_object_unref (attachment);
568 }
569 
570 static void
ev_sidebar_attachments_drag_data_get(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * data,guint info,guint time,EvSidebarAttachments * ev_attachbar)571 ev_sidebar_attachments_drag_data_get (GtkWidget            *widget,
572 				      GdkDragContext       *drag_context,
573 				      GtkSelectionData     *data,
574 				      guint                 info,
575 				      guint                 time,
576 				      EvSidebarAttachments *ev_attachbar)
577 {
578 	EvAttachment         *attachment;
579 
580 	attachment = ev_sidebar_attachments_get_selected_attachment (ev_attachbar);
581 
582         if (!attachment)
583                 return;
584 
585 	if (info == EV_DND_TARGET_XDS) {
586 		guchar to_send = XDS_ERROR;
587 		gchar *uri;
588 
589 		uri = read_xds_property (drag_context);
590 		if (!uri) {
591 			g_object_unref (attachment);
592 			return;
593 		}
594 
595 		if (ev_sidebar_attachments_confirm_overwrite (ev_attachbar, uri)) {
596 			gboolean success;
597 
598 			g_signal_emit (ev_attachbar,
599 			               signals[SIGNAL_SAVE_ATTACHMENT],
600 			               0,
601 			               attachment,
602 			               uri,
603 			               &success);
604 
605 			if (success)
606 				to_send = XDS_SUCCESS;
607 		}
608 		g_free (uri);
609 		gtk_selection_data_set (data, STRING_ATOM, 8, &to_send, sizeof (to_send));
610 	} else {
611 		GError *error = NULL;
612 		GFile  *file;
613 		gchar  *uri_list[2];
614 		gchar  *template;
615 
616                 /* FIXMEchpe: convert to filename encoding first! */
617                 template = g_strdup_printf ("%s.XXXXXX", ev_attachment_get_name (attachment));
618                 file = ev_mkstemp_file (template, &error);
619                 g_free (template);
620 
621 		if (file != NULL && ev_attachment_save (attachment, file, &error)) {
622 			uri_list[0] = g_file_get_uri (file);
623 			uri_list[1] = NULL; /* NULL-terminate */
624 			g_object_unref (file);
625 		}
626 
627 		if (error) {
628 			g_warning ("%s", error->message);
629 			g_error_free (error);
630 		}
631 		gtk_selection_data_set_uris (data, uri_list);
632 		g_free (uri_list[0]);
633 	}
634 	g_object_unref (attachment);
635 }
636 
637 static void
ev_sidebar_attachments_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)638 ev_sidebar_attachments_get_property (GObject    *object,
639 				     guint       prop_id,
640 			    	     GValue     *value,
641 		      	             GParamSpec *pspec)
642 {
643 	EvSidebarAttachments *ev_sidebar_attachments;
644 
645 	ev_sidebar_attachments = EV_SIDEBAR_ATTACHMENTS (object);
646 
647 	switch (prop_id) {
648 	        case PROP_WIDGET:
649 			g_value_set_object (value, ev_sidebar_attachments->priv->icon_view);
650 			break;
651 	        default:
652 			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
653 			break;
654 	}
655 }
656 
657 static void
ev_sidebar_attachments_dispose(GObject * object)658 ev_sidebar_attachments_dispose (GObject *object)
659 {
660 	EvSidebarAttachments *ev_attachbar = EV_SIDEBAR_ATTACHMENTS (object);
661 
662 	if (ev_attachbar->priv->icon_theme) {
663 		g_signal_handlers_disconnect_by_func (
664 			ev_attachbar->priv->icon_theme,
665 			G_CALLBACK (ev_sidebar_attachments_update_icons),
666 			ev_attachbar);
667 		ev_attachbar->priv->icon_theme = NULL;
668 	}
669 
670 	if (ev_attachbar->priv->model) {
671 		g_object_unref (ev_attachbar->priv->model);
672 		ev_attachbar->priv->model = NULL;
673 	}
674 
675 	if (ev_attachbar->priv->icon_cache) {
676 		g_hash_table_destroy (ev_attachbar->priv->icon_cache);
677 		ev_attachbar->priv->icon_cache = NULL;
678 	}
679 
680 	G_OBJECT_CLASS (ev_sidebar_attachments_parent_class)->dispose (object);
681 }
682 
683 static void
ev_sidebar_attachments_class_init(EvSidebarAttachmentsClass * ev_attachbar_class)684 ev_sidebar_attachments_class_init (EvSidebarAttachmentsClass *ev_attachbar_class)
685 {
686 	GObjectClass   *g_object_class;
687 	GtkWidgetClass *gtk_widget_class;
688 
689 	g_object_class = G_OBJECT_CLASS (ev_attachbar_class);
690 	gtk_widget_class = GTK_WIDGET_CLASS (ev_attachbar_class);
691 
692 	g_object_class->get_property = ev_sidebar_attachments_get_property;
693 	g_object_class->dispose = ev_sidebar_attachments_dispose;
694 	gtk_widget_class->popup_menu = ev_sidebar_attachments_popup_menu;
695 	gtk_widget_class->screen_changed = ev_sidebar_attachments_screen_changed;
696 
697 	/* Signals */
698 	signals[SIGNAL_POPUP_MENU] =
699 		g_signal_new ("popup",
700 			      G_TYPE_FROM_CLASS (g_object_class),
701 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
702 			      G_STRUCT_OFFSET (EvSidebarAttachmentsClass, popup_menu),
703 			      NULL, NULL,
704 			      g_cclosure_marshal_VOID__POINTER,
705 			      G_TYPE_NONE, 1,
706 			      G_TYPE_POINTER);
707 
708 	signals[SIGNAL_SAVE_ATTACHMENT] =
709 		g_signal_new ("save-attachment",
710 			      G_TYPE_FROM_CLASS (g_object_class),
711 			      G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
712 			      G_STRUCT_OFFSET (EvSidebarAttachmentsClass, save_attachment),
713 			      NULL, NULL,
714 			      g_cclosure_marshal_generic,
715 			      G_TYPE_BOOLEAN, 2,
716 			      G_TYPE_OBJECT,
717 		              G_TYPE_STRING);
718 
719 	g_object_class_override_property (g_object_class,
720 					  PROP_WIDGET,
721 					  "main-widget");
722 }
723 
724 static void
ev_sidebar_attachments_init(EvSidebarAttachments * ev_attachbar)725 ev_sidebar_attachments_init (EvSidebarAttachments *ev_attachbar)
726 {
727 	GtkWidget *swindow;
728 
729 	static const GtkTargetEntry targets[] = { {"text/uri-list", 0, EV_DND_TARGET_TEXT_URI_LIST},
730 	                                          {"XdndDirectSave0", 0, EV_DND_TARGET_XDS}};
731 
732 	ev_attachbar->priv = ev_sidebar_attachments_get_instance_private (ev_attachbar);
733 
734 	swindow = gtk_scrolled_window_new (NULL, NULL);
735 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow),
736 					GTK_POLICY_NEVER,
737 					GTK_POLICY_AUTOMATIC);
738 	/* Data Model */
739 	ev_attachbar->priv->model = gtk_list_store_new (N_COLS,
740 							GDK_TYPE_PIXBUF,
741 							G_TYPE_STRING,
742 							G_TYPE_STRING,
743 							EV_TYPE_ATTACHMENT);
744 
745 	/* Icon View */
746 	ev_attachbar->priv->icon_view =
747 		gtk_icon_view_new_with_model (GTK_TREE_MODEL (ev_attachbar->priv->model));
748 	gtk_icon_view_set_selection_mode (GTK_ICON_VIEW (ev_attachbar->priv->icon_view),
749 					  GTK_SELECTION_MULTIPLE);
750 	gtk_icon_view_set_columns (GTK_ICON_VIEW (ev_attachbar->priv->icon_view), -1);
751 	g_object_set (G_OBJECT (ev_attachbar->priv->icon_view),
752 		      "text-column", COLUMN_NAME,
753 		      "pixbuf-column", COLUMN_ICON,
754 		      "tooltip-column", COLUMN_DESCRIPTION,
755 		      NULL);
756 	g_signal_connect_swapped (ev_attachbar->priv->icon_view,
757 				  "button-press-event",
758 				  G_CALLBACK (ev_sidebar_attachments_button_press),
759 				  (gpointer) ev_attachbar);
760 
761 	gtk_container_add (GTK_CONTAINER (swindow),
762 			   ev_attachbar->priv->icon_view);
763 
764         gtk_box_pack_start (GTK_BOX (ev_attachbar), swindow, TRUE, TRUE, 0);
765 	gtk_widget_show_all (GTK_WIDGET (ev_attachbar));
766 
767 	/* Icon Theme */
768 	ev_attachbar->priv->icon_theme = NULL;
769 
770 	/* Icon Cache */
771 	ev_attachbar->priv->icon_cache = g_hash_table_new_full (g_str_hash,
772 								g_str_equal,
773 								g_free,
774 								g_object_unref);
775 
776 	/* Drag and Drop */
777 	gtk_icon_view_enable_model_drag_source (
778 		GTK_ICON_VIEW (ev_attachbar->priv->icon_view),
779 		GDK_BUTTON1_MASK,
780         	targets, G_N_ELEMENTS (targets),
781 		GDK_ACTION_MOVE);
782 
783 	g_signal_connect (ev_attachbar->priv->icon_view,
784 			  "drag-data-get",
785 			  G_CALLBACK (ev_sidebar_attachments_drag_data_get),
786 			  (gpointer) ev_attachbar);
787 
788 	g_signal_connect (ev_attachbar->priv->icon_view,
789 	                  "drag-begin",
790 	                  G_CALLBACK (ev_sidebar_attachments_drag_begin),
791 	                  (gpointer) ev_attachbar);
792 }
793 
794 GtkWidget *
ev_sidebar_attachments_new(void)795 ev_sidebar_attachments_new (void)
796 {
797 	GtkWidget *ev_attachbar;
798 
799 	ev_attachbar = g_object_new (EV_TYPE_SIDEBAR_ATTACHMENTS,
800                                      "orientation", GTK_ORIENTATION_VERTICAL,
801                                      NULL);
802 
803 	return ev_attachbar;
804 }
805 
806 static void
job_finished_callback(EvJobAttachments * job,EvSidebarAttachments * ev_attachbar)807 job_finished_callback (EvJobAttachments     *job,
808 		       EvSidebarAttachments *ev_attachbar)
809 {
810 	GList *l;
811 
812 	for (l = job->attachments; l && l->data; l = g_list_next (l)) {
813 		EvAttachment *attachment;
814 		GtkTreeIter   iter;
815 		GdkPixbuf    *pixbuf = NULL;
816 		const gchar  *mime_type;
817 		gchar        *description;
818 
819 		attachment = EV_ATTACHMENT (l->data);
820 
821 		mime_type = ev_attachment_get_mime_type (attachment);
822 		pixbuf = ev_sidebar_attachments_icon_cache_get (ev_attachbar,
823 								mime_type);
824 		description =  g_markup_printf_escaped ("%s",
825 							 ev_attachment_get_description (attachment));
826 
827 		gtk_list_store_append (ev_attachbar->priv->model, &iter);
828 		gtk_list_store_set (ev_attachbar->priv->model, &iter,
829 				    COLUMN_NAME, ev_attachment_get_name (attachment),
830 				    COLUMN_DESCRIPTION, description,
831 				    COLUMN_ICON, pixbuf,
832 				    COLUMN_ATTACHMENT, attachment,
833 				    -1);
834 		g_free (description);
835 	}
836 
837 	g_object_unref (job);
838 }
839 
840 
841 static void
ev_sidebar_attachments_document_changed_cb(EvDocumentModel * model,GParamSpec * pspec,EvSidebarAttachments * ev_attachbar)842 ev_sidebar_attachments_document_changed_cb (EvDocumentModel      *model,
843 					    GParamSpec           *pspec,
844 					    EvSidebarAttachments *ev_attachbar)
845 {
846 	EvDocument *document = ev_document_model_get_document (model);
847 	EvJob *job;
848 
849 	if (!EV_IS_DOCUMENT_ATTACHMENTS (document))
850 		return;
851 
852 	if (!ev_document_attachments_has_attachments (EV_DOCUMENT_ATTACHMENTS (document)))
853 		return;
854 
855 	if (!ev_attachbar->priv->icon_theme) {
856 		GdkScreen *screen;
857 
858 		screen = gtk_widget_get_screen (GTK_WIDGET (ev_attachbar));
859 		ev_attachbar->priv->icon_theme = gtk_icon_theme_get_for_screen (screen);
860 		g_signal_connect_swapped (ev_attachbar->priv->icon_theme,
861 					  "changed",
862 					  G_CALLBACK (ev_sidebar_attachments_update_icons),
863 					  (gpointer) ev_attachbar);
864 	}
865 
866 	gtk_list_store_clear (ev_attachbar->priv->model);
867 
868 	job = ev_job_attachments_new (document);
869 	g_signal_connect (job, "finished",
870 			  G_CALLBACK (job_finished_callback),
871 			  ev_attachbar);
872 	g_signal_connect (job, "cancelled",
873 			  G_CALLBACK (g_object_unref),
874 			  NULL);
875 	/* The priority doesn't matter for this job */
876 	ev_job_scheduler_push_job (job, EV_JOB_PRIORITY_NONE);
877 }
878 
879 static void
ev_sidebar_attachments_set_model(EvSidebarPage * page,EvDocumentModel * model)880 ev_sidebar_attachments_set_model (EvSidebarPage   *page,
881 				  EvDocumentModel *model)
882 {
883 	g_signal_connect (model, "notify::document",
884 			  G_CALLBACK (ev_sidebar_attachments_document_changed_cb),
885 			  page);
886 }
887 
888 static gboolean
ev_sidebar_attachments_support_document(EvSidebarPage * sidebar_page,EvDocument * document)889 ev_sidebar_attachments_support_document (EvSidebarPage   *sidebar_page,
890 					 EvDocument      *document)
891 {
892 	return (EV_IS_DOCUMENT_ATTACHMENTS (document) &&
893 		ev_document_attachments_has_attachments (EV_DOCUMENT_ATTACHMENTS (document)));
894 }
895 
896 static const gchar*
ev_sidebar_attachments_get_label(EvSidebarPage * sidebar_page)897 ev_sidebar_attachments_get_label (EvSidebarPage *sidebar_page)
898 {
899 	return _("Attachments");
900 }
901 
902 static void
ev_sidebar_attachments_page_iface_init(EvSidebarPageInterface * iface)903 ev_sidebar_attachments_page_iface_init (EvSidebarPageInterface *iface)
904 {
905 	iface->support_document = ev_sidebar_attachments_support_document;
906 	iface->set_model = ev_sidebar_attachments_set_model;
907 	iface->get_label = ev_sidebar_attachments_get_label;
908 }
909 
910