Blob Blame History Raw
/*
 * e-picture-gallery.c
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms 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, see <http://www.gnu.org/licenses/>.
 *
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#include "evolution-config.h"

#include "e-picture-gallery.h"

#include "e-icon-factory.h"
#include "e-misc-utils.h"

#define E_PICTURE_GALLERY_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), E_TYPE_PICTURE_GALLERY, EPictureGalleryPrivate))

struct _EPictureGalleryPrivate {
	gboolean initialized;
	gchar *path;
	GFileMonitor *monitor;
};

enum {
	PROP_0,
	PROP_PATH
};

enum {
	COL_PIXBUF = 0,
	COL_URI,
	COL_FILENAME_TEXT
};

G_DEFINE_TYPE (EPictureGallery, e_picture_gallery, GTK_TYPE_ICON_VIEW)

static gboolean
update_file_iter (GtkListStore *list_store,
                  GtkTreeIter *iter,
                  GFile *file,
                  gboolean force_thumbnail_update)
{
	GFileInfo *file_info;
	gchar *uri;
	gboolean res = FALSE;

	g_return_val_if_fail (list_store != NULL, FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);
	g_return_val_if_fail (file != NULL, FALSE);

	uri = g_file_get_uri (file);

	file_info = g_file_query_info (
		file,
		G_FILE_ATTRIBUTE_THUMBNAIL_PATH ","
		G_FILE_ATTRIBUTE_THUMBNAILING_FAILED ","
		G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
		G_FILE_ATTRIBUTE_STANDARD_SIZE,
		G_FILE_QUERY_INFO_NONE,
		NULL,
		NULL);

	if (file_info != NULL) {
		const gchar *existing_thumb = g_file_info_get_attribute_byte_string (file_info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
		gchar *new_thumb = NULL;

		if (!existing_thumb || force_thumbnail_update) {
			gchar *filename;

			filename = g_file_get_path (file);
			if (filename) {
				new_thumb = e_icon_factory_create_thumbnail (filename);
				if (new_thumb)
					existing_thumb = new_thumb;
				g_free (filename);
			}
		}

		if (existing_thumb && !g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_THUMBNAILING_FAILED)) {
			GdkPixbuf * pixbuf;

			pixbuf = gdk_pixbuf_new_from_file (existing_thumb, NULL);

			if (pixbuf) {
				const gchar *filename;
				gchar *filename_text = NULL;
				guint64 filesize;

				filename = g_file_info_get_attribute_string (file_info, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME);
				if (filename) {
					filesize = g_file_info_get_attribute_uint64 (file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
					if (filesize) {
						gchar *tmp = g_format_size ((goffset) filesize);
						filename_text = g_strdup_printf ("%s (%s)", filename, tmp);
						g_free (tmp);
					}

					res = TRUE;
					gtk_list_store_set (
						list_store, iter,
						COL_PIXBUF, pixbuf,
						COL_URI, uri,
						COL_FILENAME_TEXT, filename_text ? filename_text : filename,
						-1);
				}

				g_object_unref (pixbuf);
				g_free (filename_text);
			}
		}

		g_free (new_thumb);
	}

	g_free (uri);

	return res;
}

static void
add_file (GtkListStore *list_store,
          GFile *file)
{
	GtkTreeIter iter;

	g_return_if_fail (list_store != NULL);
	g_return_if_fail (file != NULL);

	gtk_list_store_append (list_store, &iter);
	if (!update_file_iter (list_store, &iter, file, FALSE))
		gtk_list_store_remove (list_store, &iter);
}

static gboolean
find_file_uri (GtkListStore *list_store,
               const gchar *uri,
               GtkTreeIter *iter)
{
	GtkTreeModel *model;

	g_return_val_if_fail (list_store != NULL, FALSE);
	g_return_val_if_fail (uri != NULL, FALSE);
	g_return_val_if_fail (iter != NULL, FALSE);

	model = GTK_TREE_MODEL (list_store);
	g_return_val_if_fail (model != NULL, FALSE);

	if (!gtk_tree_model_get_iter_first (model, iter))
		return FALSE;

	do {
		gchar *iter_uri = NULL;

		gtk_tree_model_get (
			model, iter,
			COL_URI, &iter_uri,
			-1);

		if (iter_uri && g_ascii_strcasecmp (uri, iter_uri) == 0) {
			g_free (iter_uri);
			return TRUE;
		}

		g_free (iter_uri);
	} while (gtk_tree_model_iter_next (model, iter));

	return FALSE;
}

static void
picture_gallery_dir_changed_cb (GFileMonitor *monitor,
                                GFile *file,
                                GFile *other_file,
                                GFileMonitorEvent event_type,
                                EPictureGallery *gallery)
{
	gchar *uri;
	GtkListStore *list_store;
	GtkTreeIter iter;

	g_return_if_fail (file != NULL);

	list_store = GTK_LIST_STORE (gtk_icon_view_get_model (GTK_ICON_VIEW (gallery)));
	g_return_if_fail (list_store != NULL);

	uri = g_file_get_uri (file);
	if (!uri)
		return;

	switch (event_type) {
	case G_FILE_MONITOR_EVENT_CREATED:
		if (find_file_uri (list_store, uri, &iter)) {
			if (!update_file_iter (list_store, &iter, file, TRUE))
				gtk_list_store_remove (list_store, &iter);
		} else {
			add_file (list_store, file);
		}
		break;
	case G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT:
		if (find_file_uri (list_store, uri, &iter)) {
			if (!update_file_iter (list_store, &iter, file, TRUE))
				gtk_list_store_remove (list_store, &iter);
		}
		break;
	case G_FILE_MONITOR_EVENT_DELETED:
		if (find_file_uri (list_store, uri, &iter))
			gtk_list_store_remove (list_store, &iter);
		break;
	default:
		break;
	}

	g_free (uri);
}

static gboolean
picture_gallery_start_loading_cb (EPictureGallery *gallery)
{
	GtkIconView *icon_view;
	GtkListStore *list_store;
	GDir *dir;
	const gchar *dirname;

	icon_view = GTK_ICON_VIEW (gallery);
	list_store = GTK_LIST_STORE (gtk_icon_view_get_model (icon_view));
	g_return_val_if_fail (list_store != NULL, FALSE);

	dirname = e_picture_gallery_get_path (gallery);
	if (!dirname)
		return FALSE;

	dir = g_dir_open (dirname, 0, NULL);
	if (dir) {
		GFile *file;
		const gchar *basename;

		while ((basename = g_dir_read_name (dir)) != NULL) {
			gchar *filename;

			filename = g_build_filename (dirname, basename, NULL);
			file = g_file_new_for_path (filename);

			add_file (list_store, file);

			g_free (filename);
			g_object_unref (file);
		}

		g_dir_close (dir);

		file = g_file_new_for_path (dirname);
		gallery->priv->monitor = g_file_monitor_directory (file, G_FILE_MONITOR_NONE, NULL, NULL);
		g_object_unref (file);

		if (gallery->priv->monitor)
			g_signal_connect (
				gallery->priv->monitor, "changed",
				G_CALLBACK (picture_gallery_dir_changed_cb),
				gallery);
	}

	g_object_unref (icon_view);

	return FALSE;
}

const gchar *
e_picture_gallery_get_path (EPictureGallery *gallery)
{
	g_return_val_if_fail (gallery != NULL, NULL);
	g_return_val_if_fail (E_IS_PICTURE_GALLERY (gallery), NULL);
	g_return_val_if_fail (gallery->priv != NULL, NULL);

	return gallery->priv->path;
}

static void
picture_gallery_set_path (EPictureGallery *gallery,
                          const gchar *path)
{
	g_return_if_fail (E_IS_PICTURE_GALLERY (gallery));
	g_return_if_fail (gallery->priv != NULL);

	g_free (gallery->priv->path);

	if (!path || !*path || !g_file_test (path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
		gallery->priv->path = g_strdup (g_get_user_special_dir (G_USER_DIRECTORY_PICTURES));
	else
		gallery->priv->path = g_strdup (path);
}

static void
picture_gallery_get_property (GObject *object,
                              guint property_id,
                              GValue *value,
                              GParamSpec *pspec)
{
	switch (property_id) {
	case PROP_PATH:
		g_value_set_string (value, e_picture_gallery_get_path (E_PICTURE_GALLERY (object)));
		return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
picture_gallery_set_property (GObject *object,
                              guint property_id,
                              const GValue *value,
                              GParamSpec *pspec)
{
	switch (property_id) {
	case PROP_PATH:
		picture_gallery_set_path (E_PICTURE_GALLERY (object), g_value_get_string (value));
		return;
	}

	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}

static void
visible_cb (EPictureGallery *gallery)
{
	if (!gallery->priv->initialized && gtk_widget_get_visible (GTK_WIDGET (gallery))) {
		gallery->priv->initialized = TRUE;

		g_idle_add ((GSourceFunc) picture_gallery_start_loading_cb, gallery);
	}
}

static void
picture_gallery_constructed (GObject *object)
{
	GtkIconView *icon_view;
	GtkListStore *list_store;
	GtkTargetEntry *targets;
	GtkTargetList *list;
	gint n_targets;

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

	icon_view = GTK_ICON_VIEW (object);

	list_store = gtk_list_store_new (3, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING);
	gtk_icon_view_set_model (icon_view, GTK_TREE_MODEL (list_store));
	g_object_unref (list_store);

	gtk_icon_view_set_pixbuf_column (icon_view, COL_PIXBUF);
	gtk_icon_view_set_text_column (icon_view, COL_FILENAME_TEXT);
	gtk_icon_view_set_tooltip_column (icon_view, -1);

	/* Fit more icons by letting text wrap, especially with long filenames.
	 * The thumbnail is usually 128x128, so match it, as the text won't wrap
	 * smaller than the thumbnail width anyway. */
	gtk_icon_view_set_item_width (icon_view, 128);

	list = gtk_target_list_new (NULL, 0);
	gtk_target_list_add_uri_targets (list, 0);
	targets = gtk_target_table_new_from_list (list, &n_targets);

	gtk_icon_view_enable_model_drag_source (
		icon_view, GDK_BUTTON1_MASK,
		targets, n_targets, GDK_ACTION_COPY);

	gtk_target_table_free (targets, n_targets);
	gtk_target_list_unref (list);

	e_signal_connect_notify (object, "notify::visible", G_CALLBACK (visible_cb), NULL);
}

static void
picture_gallery_dispose (GObject *object)
{
	EPictureGallery *gallery;

	gallery = E_PICTURE_GALLERY (object);

	if (gallery->priv->monitor) {
		g_object_unref (gallery->priv->monitor);
		gallery->priv->monitor = NULL;
	}

	g_free (gallery->priv->path);
	gallery->priv->path = NULL;

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

static void
e_picture_gallery_class_init (EPictureGalleryClass *class)
{
	GObjectClass *object_class;

	g_type_class_add_private (class, sizeof (EPictureGalleryPrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->get_property = picture_gallery_get_property;
	object_class->set_property = picture_gallery_set_property;
	object_class->constructed = picture_gallery_constructed;
	object_class->dispose = picture_gallery_dispose;

	g_object_class_install_property (
		object_class,
		PROP_PATH,
		g_param_spec_string (
			"path",
			"Gallery path",
			NULL,
			NULL,
			G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}

static void
e_picture_gallery_init (EPictureGallery *gallery)
{
	gallery->priv = E_PICTURE_GALLERY_GET_PRIVATE (gallery);
	gallery->priv->initialized = FALSE;
	gallery->priv->monitor = NULL;
	picture_gallery_set_path (gallery, NULL);
}

GtkWidget *
e_picture_gallery_new (const gchar *path)
{
	return g_object_new (E_TYPE_PICTURE_GALLERY, "path", path, NULL);
}