Blob Blame History Raw
/*
 * e-image-chooser-dialog.c
 *
 * Copyright (C) 2012 Dan Vrátil <dvratil@redhat.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU Lesser General Public
 * License as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "evolution-config.h"

#include <glib/gi18n-lib.h>

#include "e-image-chooser-dialog.h"

#define E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_IMAGE_CHOOSER_DIALOG, EImageChooserDialogPrivate))

#define PREVIEW_WIDTH	256
#define PREVIEW_HEIGHT	256

typedef struct _Context Context;

struct _EImageChooserDialogPrivate {
	GCancellable *cancellable;
};

struct _Context {
	GtkFileChooser *file_chooser;
	GCancellable *cancellable;
};

G_DEFINE_TYPE (
	EImageChooserDialog,
	e_image_chooser_dialog,
	GTK_TYPE_FILE_CHOOSER_DIALOG)

static void
context_free (Context *context)
{
	g_object_unref (context->file_chooser);
	g_object_unref (context->cancellable);

	g_slice_free (Context, context);
}

static void
image_chooser_dialog_read_cb (GFile *preview_file,
                              GAsyncResult *result,
                              Context *context)
{
	GdkPixbuf *pixbuf;
	GtkWidget *preview_widget;
	GFileInputStream *input_stream;

	input_stream = g_file_read_finish (preview_file, result, NULL);

	/* FIXME Handle errors better, but remember
	 *       to ignore G_IO_ERROR_CANCELLED. */
	if (input_stream == NULL)
		goto exit;

	/* XXX This blocks, but GDK-PixBuf offers no asynchronous
	 *     alternative and I don't want to deal with making GDK
	 *     calls from threads and all the crap that goes with it. */
	pixbuf = gdk_pixbuf_new_from_stream_at_scale (
		G_INPUT_STREAM (input_stream),
		PREVIEW_WIDTH, PREVIEW_HEIGHT, TRUE,
		context->cancellable, NULL);

	preview_widget = gtk_file_chooser_get_preview_widget (
		context->file_chooser);

	gtk_file_chooser_set_preview_widget_active (
		context->file_chooser, pixbuf != NULL);

	gtk_image_set_from_pixbuf (GTK_IMAGE (preview_widget), pixbuf);

	if (pixbuf != NULL)
		g_object_unref (pixbuf);

	g_object_unref (input_stream);

exit:
	context_free (context);
}

static void
image_chooser_dialog_update_preview (GtkFileChooser *file_chooser)
{
	EImageChooserDialogPrivate *priv;
	GtkWidget *preview_widget;
	GFile *preview_file;
	Context *context;

	priv = E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE (file_chooser);
	preview_file = gtk_file_chooser_get_preview_file (file_chooser);
	preview_widget = gtk_file_chooser_get_preview_widget (file_chooser);

	if (priv->cancellable != NULL) {
		g_cancellable_cancel (priv->cancellable);
		g_object_unref (priv->cancellable);
		priv->cancellable = NULL;
	}

	gtk_image_clear (GTK_IMAGE (preview_widget));
	gtk_file_chooser_set_preview_widget_active (file_chooser, FALSE);

	if (preview_file == NULL)
		return;

	priv->cancellable = g_cancellable_new ();

	context = g_slice_new0 (Context);
	context->file_chooser = g_object_ref (file_chooser);
	context->cancellable = g_object_ref (priv->cancellable);

	g_file_read_async (
		preview_file, G_PRIORITY_LOW,
		priv->cancellable, (GAsyncReadyCallback)
		image_chooser_dialog_read_cb, context);

	g_object_unref (preview_file);
}

static void
image_chooser_dialog_dispose (GObject *object)
{
	EImageChooserDialogPrivate *priv;

	priv = E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE (object);

	if (priv->cancellable != NULL) {
		g_cancellable_cancel (priv->cancellable);
		g_object_unref (priv->cancellable);
		priv->cancellable = NULL;
	}

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (e_image_chooser_dialog_parent_class)->dispose (object);
}

static void
image_chooser_dialog_constructed (GObject *object)
{
	GtkFileChooser *file_chooser;
	GtkFileFilter *file_filter;

	/* Chain up to parent's method. */
	G_OBJECT_CLASS (e_image_chooser_dialog_parent_class)->constructed (object);

	file_chooser = GTK_FILE_CHOOSER (object);
	gtk_file_chooser_set_local_only (file_chooser, FALSE);

	gtk_dialog_add_button (
		GTK_DIALOG (file_chooser),
		_("_Cancel"), GTK_RESPONSE_CANCEL);
	gtk_dialog_add_button (
		GTK_DIALOG (file_chooser),
		_("_Open"), GTK_RESPONSE_ACCEPT);
	gtk_dialog_set_default_response (
		GTK_DIALOG (file_chooser), GTK_RESPONSE_ACCEPT);

	file_filter = gtk_file_filter_new ();
	gtk_file_filter_add_pixbuf_formats (file_filter);
	gtk_file_chooser_set_filter (file_chooser, file_filter);

	gtk_file_chooser_set_preview_widget (file_chooser, gtk_image_new ());
}

static void
e_image_chooser_dialog_class_init (EImageChooserDialogClass *class)
{
	GObjectClass *object_class;

	g_type_class_add_private (
		class, sizeof (EImageChooserDialogPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->dispose = image_chooser_dialog_dispose;
	object_class->constructed = image_chooser_dialog_constructed;
}

static void
e_image_chooser_dialog_init (EImageChooserDialog *dialog)
{
	dialog->priv = E_IMAGE_CHOOSER_DIALOG_GET_PRIVATE (dialog);

	g_signal_connect (
		dialog, "update-preview",
		G_CALLBACK (image_chooser_dialog_update_preview), NULL);
}

GtkWidget *
e_image_chooser_dialog_new (const gchar *title,
                            GtkWindow *parent)
{
	return g_object_new (
		E_TYPE_IMAGE_CHOOSER_DIALOG,
		"action", GTK_FILE_CHOOSER_ACTION_OPEN,
		"title", title,
		"transient-for", parent, NULL);
}

GFile *
e_image_chooser_dialog_run (EImageChooserDialog *dialog)
{
	GtkFileChooser *file_chooser;

	g_return_val_if_fail (E_IS_IMAGE_CHOOSER_DIALOG (dialog), NULL);

	file_chooser = GTK_FILE_CHOOSER (dialog);

	if (gtk_dialog_run (GTK_DIALOG (dialog)) != GTK_RESPONSE_ACCEPT)
		return NULL;

	return gtk_file_chooser_get_file (file_chooser);
}