1 /*
2  * This program is free software; you can redistribute it and/or modify
3  * it under the terms of the GNU General Public License as published by
4  * the Free Software Foundation; either version 2 of the License, or
5  * (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License along
13  * with this program; if not, write to the Free Software Foundation, Inc.,
14  * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
15  */
16 
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif
20 
21 #include "eog-file-chooser.h"
22 #include "eog-pixbuf-util.h"
23 
24 #include <stdlib.h>
25 
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <gio/gio.h>
29 #include <gtk/gtk.h>
30 
31 /* We must define GNOME_DESKTOP_USE_UNSTABLE_API to be able
32    to use GnomeDesktopThumbnail */
33 #ifndef GNOME_DESKTOP_USE_UNSTABLE_API
34 #define GNOME_DESKTOP_USE_UNSTABLE_API
35 #endif
36 #include <libgnome-desktop/gnome-desktop-thumbnail.h>
37 
38 static char *last_dir[] = { NULL, NULL, NULL, NULL };
39 
40 #define FILE_FORMAT_KEY "file-format"
41 
42 struct _EogFileChooserPrivate
43 {
44 	GnomeDesktopThumbnailFactory *thumb_factory;
45 
46 	GtkWidget *image;
47 	GtkWidget *size_label;
48 	GtkWidget *dim_label;
49 	GtkWidget *creator_label;
50 };
51 
G_DEFINE_TYPE_WITH_PRIVATE(EogFileChooser,eog_file_chooser,GTK_TYPE_FILE_CHOOSER_DIALOG)52 G_DEFINE_TYPE_WITH_PRIVATE (EogFileChooser, eog_file_chooser, GTK_TYPE_FILE_CHOOSER_DIALOG)
53 
54 static void
55 eog_file_chooser_finalize (GObject *object)
56 {
57 	EogFileChooserPrivate *priv;
58 
59 	priv = EOG_FILE_CHOOSER (object)->priv;
60 
61 	if (priv->thumb_factory != NULL)
62 		g_object_unref (priv->thumb_factory);
63 
64 	(* G_OBJECT_CLASS (eog_file_chooser_parent_class)->finalize) (object);
65 }
66 
67 static void
eog_file_chooser_class_init(EogFileChooserClass * klass)68 eog_file_chooser_class_init (EogFileChooserClass *klass)
69 {
70 	GObjectClass *object_class = (GObjectClass *) klass;
71 
72 	object_class->finalize = eog_file_chooser_finalize;
73 }
74 
75 static void
eog_file_chooser_init(EogFileChooser * chooser)76 eog_file_chooser_init (EogFileChooser *chooser)
77 {
78 	chooser->priv = eog_file_chooser_get_instance_private (chooser);
79 }
80 
81 static void
response_cb(GtkDialog * dlg,gint id,gpointer data)82 response_cb (GtkDialog *dlg, gint id, gpointer data)
83 {
84 	char *dir;
85 	GtkFileChooserAction action;
86 
87 	if (id == GTK_RESPONSE_OK) {
88 		dir = gtk_file_chooser_get_current_folder (GTK_FILE_CHOOSER (dlg));
89 		action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (dlg));
90 
91 		if (last_dir [action] != NULL)
92 			g_free (last_dir [action]);
93 
94 		last_dir [action] = dir;
95 	}
96 }
97 
98 static void
save_response_cb(GtkDialog * dlg,gint id,gpointer data)99 save_response_cb (GtkDialog *dlg, gint id, gpointer data)
100 {
101 	GFile *file;
102 	GdkPixbufFormat *format;
103 
104 	if (id != GTK_RESPONSE_OK)
105 		return;
106 
107 	file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dlg));
108 	format = eog_pixbuf_get_format (file);
109 	g_object_unref (file);
110 
111 	if (!format || !gdk_pixbuf_format_is_writable (format)) {
112 		GtkWidget *msg_dialog;
113 
114 		msg_dialog = gtk_message_dialog_new (
115 						     GTK_WINDOW (dlg),
116 						     GTK_DIALOG_MODAL,
117 						     GTK_MESSAGE_ERROR,
118 						     GTK_BUTTONS_OK,
119 						     _("File format is unknown or unsupported"));
120 
121 		gtk_message_dialog_format_secondary_text (
122 						GTK_MESSAGE_DIALOG (msg_dialog),
123 						"%s\n%s",
124 						_("Image Viewer could not determine a supported writable file format based on the filename."),
125 		  				_("Please try a different file extension like .png or .jpg."));
126 
127 		gtk_dialog_run (GTK_DIALOG (msg_dialog));
128 		gtk_widget_destroy (msg_dialog);
129 
130 		g_signal_stop_emission_by_name (dlg, "response");
131 	} else {
132 		response_cb (dlg, id, data);
133 	}
134 }
135 
136 static GSList*
_eog_file_chooser_prepare_save_file_filter(GtkFileFilter * all_img_filter)137 _eog_file_chooser_prepare_save_file_filter (GtkFileFilter *all_img_filter)
138 {
139 	GSList *filters = NULL;
140 	GSList *formats = NULL;
141 	GSList *it;
142 	GtkFileFilter *filter;
143 	gchar **mime_types, **pattern, *tmp;
144 	int i;
145 
146 	formats = eog_pixbuf_get_savable_formats ();
147 
148 	/* Image filters */
149 	for (it = formats; it != NULL; it = it->next) {
150 		char *filter_name;
151 		char *description, *extension;
152 		GdkPixbufFormat *format;
153 		filter = gtk_file_filter_new ();
154 
155 		format = (GdkPixbufFormat*) it->data;
156 		description = gdk_pixbuf_format_get_description (format);
157 		extension = gdk_pixbuf_format_get_name (format);
158 
159 		/* Filter name: First description then file extension, eg. "The PNG-Format (*.png)".*/
160 		filter_name = g_strdup_printf (_("%s (*.%s)"), description, extension);
161 		g_free (description);
162 		g_free (extension);
163 
164 		gtk_file_filter_set_name (filter, filter_name);
165 		g_free (filter_name);
166 
167 		mime_types = gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) it->data);
168 		for (i = 0; mime_types[i] != NULL; i++) {
169 			gtk_file_filter_add_mime_type (filter, mime_types[i]);
170 			gtk_file_filter_add_mime_type (all_img_filter, mime_types[i]);
171 		}
172 		g_strfreev (mime_types);
173 
174 		pattern = gdk_pixbuf_format_get_extensions ((GdkPixbufFormat *) it->data);
175 		for (i = 0; pattern[i] != NULL; i++) {
176 			tmp = g_strconcat ("*.", pattern[i], NULL);
177 			gtk_file_filter_add_pattern (filter, tmp);
178 			gtk_file_filter_add_pattern (all_img_filter, tmp);
179 			g_free (tmp);
180 		}
181 		g_strfreev (pattern);
182 
183 		/* attach GdkPixbufFormat to filter, see also
184 		 * eog_file_chooser_get_format. */
185 		g_object_set_data (G_OBJECT (filter),
186 				   FILE_FORMAT_KEY,
187 				   format);
188 
189 		filters = g_slist_prepend (filters, filter);
190 	}
191 	g_slist_free (formats);
192 
193 	return filters;
194 }
195 static void
eog_file_chooser_add_filter(EogFileChooser * chooser)196 eog_file_chooser_add_filter (EogFileChooser *chooser)
197 {
198 	GSList *it;
199  	GtkFileFilter *all_file_filter;
200 	GtkFileFilter *all_img_filter;
201 	GSList *filters = NULL;
202 	GtkFileChooserAction action;
203 
204 	action = gtk_file_chooser_get_action (GTK_FILE_CHOOSER (chooser));
205 
206 	if (action != GTK_FILE_CHOOSER_ACTION_SAVE && action != GTK_FILE_CHOOSER_ACTION_OPEN) {
207 		return;
208 	}
209 
210 	/* All Files Filter */
211 	all_file_filter = gtk_file_filter_new ();
212 	gtk_file_filter_set_name (all_file_filter, _("All files"));
213 	gtk_file_filter_add_pattern (all_file_filter, "*");
214 
215 	/* All Image Filter */
216 	all_img_filter = gtk_file_filter_new ();
217 	gtk_file_filter_set_name (all_img_filter, _("Supported image files"));
218 
219 	if (action == GTK_FILE_CHOOSER_ACTION_SAVE) {
220 		filters = _eog_file_chooser_prepare_save_file_filter(all_img_filter);
221 	}
222 	else {
223 		gtk_file_filter_add_pixbuf_formats(all_img_filter);
224 	}
225 
226 	/* Add filter to filechooser */
227 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), all_file_filter);
228 	gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), all_img_filter);
229 	gtk_file_chooser_set_filter (GTK_FILE_CHOOSER (chooser), all_img_filter);
230 
231 	for (it = filters; it != NULL; it = it->next) {
232 		gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (chooser), GTK_FILE_FILTER (it->data));
233 	}
234 	g_slist_free (filters);
235 }
236 
237 static void
set_preview_label(GtkWidget * label,const char * str)238 set_preview_label (GtkWidget *label, const char *str)
239 {
240 	if (str == NULL) {
241 		gtk_widget_hide (GTK_WIDGET (label));
242 	}
243 	else {
244 		gtk_label_set_text (GTK_LABEL (label), str);
245 		gtk_widget_show (GTK_WIDGET (label));
246 	}
247 }
248 
249 /* Sets the pixbuf as preview thumbnail and tries to read and display
250  * further information according to the thumbnail spec.
251  */
252 static void
set_preview_pixbuf(EogFileChooser * chooser,GdkPixbuf * pixbuf,goffset size)253 set_preview_pixbuf (EogFileChooser *chooser, GdkPixbuf *pixbuf, goffset size)
254 {
255 	EogFileChooserPrivate *priv;
256 	int bytes;
257 	int pixels;
258 	const char *bytes_str;
259 	const char *width;
260 	const char *height;
261 	const char *creator = NULL;
262 	char *size_str    = NULL;
263 	char *dim_str     = NULL;
264 
265 	g_return_if_fail (EOG_IS_FILE_CHOOSER (chooser));
266 
267 	priv = chooser->priv;
268 
269 	gtk_image_set_from_pixbuf (GTK_IMAGE (priv->image), pixbuf);
270 
271 	if (pixbuf != NULL) {
272 		/* try to read file size */
273 		bytes_str = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Size");
274 		if (bytes_str != NULL) {
275 			bytes = atoi (bytes_str);
276 			size_str = g_format_size (bytes);
277 		}
278 		else {
279 			size_str = g_format_size (size);
280 		}
281 
282 		/* try to read image dimensions */
283 		width  = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Width");
284 		height = gdk_pixbuf_get_option (pixbuf, "tEXt::Thumb::Image::Height");
285 
286 		if ((width != NULL) && (height != NULL)) {
287 			pixels = atoi (height);
288 			/* Pixel size of image: width x height in pixel */
289 			dim_str = g_strdup_printf ("%s x %s %s", width, height, ngettext ("pixel", "pixels", pixels));
290 		}
291 
292 #if 0
293 		/* Not sure, if this is really useful, therefore its commented out for now. */
294 
295 		/* try to read creator of the thumbnail */
296 		creator = gdk_pixbuf_get_option (pixbuf, "tEXt::Software");
297 
298 		/* stupid workaround to display nicer string if the
299 		 * thumbnail is created through the gnome libraries.
300 		 */
301 		if (g_ascii_strcasecmp (creator, "Gnome::ThumbnailFactory") == 0) {
302 			creator = "GNOME Libs";
303 		}
304 #endif
305 	}
306 
307 	set_preview_label (priv->size_label, size_str);
308 	set_preview_label (priv->dim_label, dim_str);
309 	set_preview_label (priv->creator_label, creator);
310 
311 	if (size_str != NULL) {
312 		g_free (size_str);
313 	}
314 
315 	if (dim_str != NULL) {
316 		g_free (dim_str);
317 	}
318 }
319 
320 static void
update_preview_cb(GtkFileChooser * file_chooser,gpointer data)321 update_preview_cb (GtkFileChooser *file_chooser, gpointer data)
322 {
323 	EogFileChooserPrivate *priv;
324 	char *uri;
325 	char *thumb_path = NULL;
326 	GFile *file;
327 	GFileInfo *file_info;
328 	GdkPixbuf *pixbuf = NULL;
329 	gboolean have_preview = FALSE;
330 
331 	priv = EOG_FILE_CHOOSER (file_chooser)->priv;
332 
333 	uri = gtk_file_chooser_get_preview_uri (file_chooser);
334 	if (uri == NULL) {
335 		gtk_file_chooser_set_preview_widget_active (file_chooser, FALSE);
336 		return;
337 	}
338 
339 	file = g_file_new_for_uri (uri);
340 	file_info = g_file_query_info (file,
341 				       G_FILE_ATTRIBUTE_TIME_MODIFIED ","
342 				       G_FILE_ATTRIBUTE_STANDARD_TYPE ","
343 				       G_FILE_ATTRIBUTE_STANDARD_SIZE ","
344 				       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
345 				       0, NULL, NULL);
346 	g_object_unref (file);
347 
348 	if ((file_info != NULL) && (priv->thumb_factory != NULL)
349 	    && g_file_info_get_file_type (file_info) != G_FILE_TYPE_SPECIAL) {
350 		guint64 mtime;
351 
352 		mtime = g_file_info_get_attribute_uint64 (file_info,
353 							  G_FILE_ATTRIBUTE_TIME_MODIFIED);
354 		thumb_path = gnome_desktop_thumbnail_factory_lookup (priv->thumb_factory, uri, mtime);
355 
356 		if (thumb_path != NULL && g_file_test (thumb_path, G_FILE_TEST_EXISTS)) {
357 			/* try to load and display preview thumbnail */
358 			pixbuf = gdk_pixbuf_new_from_file (thumb_path, NULL);
359 		} else if (g_file_info_get_size (file_info) <= 100000) {
360 			/* read files smaller than 100kb directly */
361 
362 			gchar *mime_type = g_content_type_get_mime_type (
363 						g_file_info_get_content_type (file_info));
364 
365 
366 			if (G_LIKELY (mime_type)) {
367 				gboolean can_thumbnail, has_failed;
368 
369 				can_thumbnail = gnome_desktop_thumbnail_factory_can_thumbnail (
370 							priv->thumb_factory,
371 							uri, mime_type, mtime);
372 				has_failed = gnome_desktop_thumbnail_factory_has_valid_failed_thumbnail (
373 							priv->thumb_factory,
374 							uri, mtime);
375 
376 				if (G_LIKELY (can_thumbnail && !has_failed))
377 					pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (
378 							priv->thumb_factory, uri, mime_type);
379 
380 				g_free (mime_type);
381 			}
382 		}
383 
384 		if (pixbuf != NULL) {
385 			have_preview = TRUE;
386 
387 			set_preview_pixbuf (EOG_FILE_CHOOSER (file_chooser), pixbuf,
388 					    g_file_info_get_size (file_info));
389 
390 			if (pixbuf != NULL) {
391 				g_object_unref (pixbuf);
392 			}
393 		}
394 	}
395 
396 	if (thumb_path != NULL) {
397 		g_free (thumb_path);
398 	}
399 
400 	g_free (uri);
401 	g_object_unref (file_info);
402 
403 	gtk_file_chooser_set_preview_widget_active (file_chooser, have_preview);
404 }
405 
406 static void
eog_file_chooser_add_preview(GtkWidget * widget)407 eog_file_chooser_add_preview (GtkWidget *widget)
408 {
409 	EogFileChooserPrivate *priv;
410 	GtkWidget *vbox;
411 
412 	priv = EOG_FILE_CHOOSER (widget)->priv;
413 
414 	vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
415 	gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
416 
417 	priv->image      = gtk_image_new ();
418 	/* 128x128 is maximum size of thumbnails */
419 	gtk_widget_set_size_request (priv->image, 128,128);
420 
421 	priv->dim_label  = gtk_label_new (NULL);
422 	priv->size_label = gtk_label_new (NULL);
423 	priv->creator_label = gtk_label_new (NULL);
424 
425 	gtk_box_pack_start (GTK_BOX (vbox), priv->image, FALSE, TRUE, 0);
426 	gtk_box_pack_start (GTK_BOX (vbox), priv->dim_label, FALSE, TRUE, 0);
427 	gtk_box_pack_start (GTK_BOX (vbox), priv->size_label, FALSE, TRUE, 0);
428 	gtk_box_pack_start (GTK_BOX (vbox), priv->creator_label, FALSE, TRUE, 0);
429 
430 	gtk_widget_show_all (vbox);
431 
432 	gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (widget), vbox);
433 	gtk_file_chooser_set_preview_widget_active (GTK_FILE_CHOOSER (widget), FALSE);
434 
435 	priv->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
436 
437 	g_signal_connect (widget, "update-preview",
438 			  G_CALLBACK (update_preview_cb), NULL);
439 }
440 
441 GtkWidget *
eog_file_chooser_new(GtkFileChooserAction action)442 eog_file_chooser_new (GtkFileChooserAction action)
443 {
444 	GtkWidget *chooser;
445 	gchar *title = NULL;
446 
447 	chooser = g_object_new (EOG_TYPE_FILE_CHOOSER,
448 				"action", action,
449 				"select-multiple", (action == GTK_FILE_CHOOSER_ACTION_OPEN),
450 				"local-only", FALSE,
451 				NULL);
452 
453 	switch (action) {
454 	case GTK_FILE_CHOOSER_ACTION_OPEN:
455 		gtk_dialog_add_buttons (GTK_DIALOG (chooser),
456 					_("_Cancel"), GTK_RESPONSE_CANCEL,
457 					_("_Open"), GTK_RESPONSE_OK,
458 					NULL);
459 		title = _("Open Image");
460 		break;
461 
462 	case GTK_FILE_CHOOSER_ACTION_SAVE:
463 		gtk_dialog_add_buttons (GTK_DIALOG (chooser),
464 					_("_Cancel"), GTK_RESPONSE_CANCEL,
465 					_("_Save"), GTK_RESPONSE_OK,
466 					NULL);
467 		title = _("Save Image");
468 		break;
469 
470 	case GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER:
471 		gtk_dialog_add_buttons (GTK_DIALOG (chooser),
472 					_("_Cancel"), GTK_RESPONSE_CANCEL,
473 					_("_Open"), GTK_RESPONSE_OK,
474 					NULL);
475 		title = _("Open Folder");
476 		break;
477 
478 	default:
479 		g_assert_not_reached ();
480 	}
481 
482 	if (action != GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER) {
483 		eog_file_chooser_add_filter (EOG_FILE_CHOOSER (chooser));
484 		eog_file_chooser_add_preview (chooser);
485 	}
486 
487 	if (last_dir[action] != NULL) {
488 		gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser), last_dir [action]);
489 	}
490 
491 	g_signal_connect (chooser, "response",
492 			  G_CALLBACK ((action == GTK_FILE_CHOOSER_ACTION_SAVE) ?
493 				      save_response_cb : response_cb),
494 			  NULL);
495 
496  	gtk_window_set_title (GTK_WINDOW (chooser), title);
497 	gtk_dialog_set_default_response (GTK_DIALOG (chooser), GTK_RESPONSE_OK);
498 
499 	gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (chooser), TRUE);
500 
501 	return chooser;
502 }
503 
504 GdkPixbufFormat *
eog_file_chooser_get_format(EogFileChooser * chooser)505 eog_file_chooser_get_format (EogFileChooser *chooser)
506 {
507 	GtkFileFilter *filter;
508 	GdkPixbufFormat* format;
509 
510 	g_return_val_if_fail (EOG_IS_FILE_CHOOSER (chooser), NULL);
511 
512 	filter = gtk_file_chooser_get_filter (GTK_FILE_CHOOSER (chooser));
513 	if (filter == NULL)
514 		return NULL;
515 
516 	format = g_object_get_data (G_OBJECT (filter), FILE_FORMAT_KEY);
517 
518 	return format;
519 }
520