/* -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 8; tab-width: 8 -*-
 *
 * Copyright (C) 2005-2006 William Jon McCann <mccann@jhu.edu>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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 General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 *
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

#include <glib.h>
#include <gtk/gtk.h>

#include "gs-theme-engine.h"
#include "gste-slideshow.h"

static void     gste_slideshow_class_init (GSTESlideshowClass *klass);
static void     gste_slideshow_init       (GSTESlideshow      *engine);
static void     gste_slideshow_finalize   (GObject            *object);

struct GSTESlideshowPrivate
{
	/* Image at full opacity */
	cairo_pattern_t *pat1;
	/* Image at partial opacity */
	cairo_pattern_t *pat2;
	/* Alpha of pat2 */
	gdouble          alpha2;
	/* edges of pat2 */
	int              pat2top;
	int              pat2bottom;
	int              pat2left;
	int              pat2right;

	/* backbuffer that we do all the alpha drawing into (no round
	 * trips to the X server when the server doesn't support drawing
	 * pixmaps with alpha?) */
	cairo_surface_t *surf;

	gint64           fade_ticks;

	GThread         *load_thread;
	GAsyncQueue     *op_q;
	GAsyncQueue     *results_q;

	guint           results_pull_id;
	guint           update_image_id;

	GSList         *filename_list;
	char           *images_location;
	gboolean        sort_images;
	int             window_width;
	int             window_height;
	PangoColor     *background_color;
	gboolean        no_stretch_hint;

	guint           timeout_id;

	GTimer         *timer;
	gboolean        fade_disabled;
};

enum
{
    PROP_0,
    PROP_IMAGES_LOCATION,
    PROP_SORT_IMAGES,
    PROP_SOLID_BACKGROUND,
    PROP_NO_STRETCH_HINT
};

#define GSTE_SLIDESHOW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), GSTE_TYPE_SLIDESHOW, GSTESlideshowPrivate))

static GObjectClass *parent_class = NULL;

G_DEFINE_TYPE (GSTESlideshow, gste_slideshow, GS_TYPE_THEME_ENGINE)

#define N_FADE_TICKS 10
#define MINIMUM_FPS 3.0
#define DEFAULT_IMAGES_LOCATION DATADIR "/pixmaps/backgrounds"
#define IMAGE_LOAD_TIMEOUT 10000

typedef struct _Op
{
	char          *location;
	GSTESlideshow *slideshow;
} Op;

typedef struct _OpResult
{
	GdkPixbuf *pixbuf;
} OpResult;

static gboolean
push_load_image_func (GSTESlideshow *show)
{
	Op *op;

	gs_theme_engine_profile_msg ("Starting a new image load");

	op = g_new (Op, 1);

	op->location = g_strdup (show->priv->images_location);
	op->slideshow = g_object_ref (show);

	g_async_queue_push (show->priv->op_q, op);

	show->priv->update_image_id = 0;

	return FALSE;
}

static void
start_new_load (GSTESlideshow *show,
                guint          timeout)
{
	gs_theme_engine_profile_msg ("Scheduling a new image load");

	/* queue a new load */
	if (show->priv->update_image_id <= 0)
	{
		show->priv->update_image_id = g_timeout_add_full (G_PRIORITY_LOW, timeout,
		                              (GSourceFunc)push_load_image_func,
		                              show, NULL);
	}
}

static void
start_fade (GSTESlideshow *show,
            GdkPixbuf     *pixbuf)
{
	int      pw;
	int      ph;
	int      x;
	int      y;
	cairo_t *cr;
	int      window_width;
	int      window_height;

	gs_theme_engine_profile_start ("start");

	window_width = show->priv->window_width;
	window_height = show->priv->window_height;

	if (show->priv->pat2 != NULL)
	{
		cairo_pattern_destroy (show->priv->pat2);
	}

	pw = gdk_pixbuf_get_width (pixbuf);
	ph = gdk_pixbuf_get_height (pixbuf);
	x = (window_width - pw) / 2;
	y = (window_height - ph) / 2;

	if (gdk_pixbuf_get_has_alpha (pixbuf) && show->priv->background_color)
	{
		GdkPixbuf *colored;
		guint32    color;

		color = (show->priv->background_color->red << 16)
		        + (show->priv->background_color->green / 256 << 8)
		        + show->priv->background_color->blue / 256;
		colored = gdk_pixbuf_composite_color_simple (pixbuf,
		          pw, ph,
		          GDK_INTERP_BILINEAR,
		          255,
		          256,
		          color,
		          color);

		gdk_pixbuf_copy_area (colored, 0, 0,
		                      gdk_pixbuf_get_width (colored),
		                      gdk_pixbuf_get_height (colored),
		                      pixbuf, 0, 0);

		g_object_unref(colored);
	}

	cr = cairo_create (show->priv->surf);

	/* XXX Handle out of memory? */
	gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y);
	show->priv->pat2 = cairo_pattern_reference (cairo_get_source (cr));
	show->priv->pat2top = y;
	show->priv->pat2bottom = y + ph;
	show->priv->pat2left = x;
	show->priv->pat2right = x + pw;

	cairo_destroy (cr);

	show->priv->fade_ticks = 0;
	g_timer_start (show->priv->timer);

	gs_theme_engine_profile_end ("end");
}

static void
finish_fade (GSTESlideshow *show)
{
	gs_theme_engine_profile_start ("start");

	if (show->priv->pat1 != NULL)
	{
		cairo_pattern_destroy (show->priv->pat1);
	}

	show->priv->pat1 = show->priv->pat2;
	show->priv->pat2 = NULL;

	start_new_load (show, IMAGE_LOAD_TIMEOUT);

	gs_theme_engine_profile_end ("end");
}

static void
update_display (GSTESlideshow *show)
{
	int      window_width;
	int      window_height;
	cairo_t *cr;

	gs_theme_engine_profile_start ("start");

	cr = cairo_create (show->priv->surf);

	gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
	                                 &window_width,
	                                 &window_height);

	if (show->priv->pat2 != NULL)
	{
		/* fade out areas not covered by the new image */
		/* top */
		cairo_rectangle (cr, 0, 0, window_width, show->priv->pat2top);
		if (show->priv->background_color)
		{
			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
			                       show->priv->background_color->green / 65535.0,
			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
		}
		else
		{
			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
		}
		cairo_fill (cr);
		/* left (excluding what's covered by top and bottom) */
		cairo_rectangle (cr, 0, show->priv->pat2top,
		                 show->priv->pat2left,
		                 show->priv->pat2bottom - show->priv->pat2top);
		if (show->priv->background_color)
		{
			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
			                       show->priv->background_color->green / 65535.0,
			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
		}
		else
		{
			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
		}
		cairo_fill (cr);
		/* bottom */
		cairo_rectangle (cr, 0, show->priv->pat2bottom, window_width,
		                 window_height - show->priv->pat2bottom);
		if (show->priv->background_color)
		{
			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
			                       show->priv->background_color->green / 65535.0,
			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
		}
		else
		{
			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
		}
		cairo_fill (cr);
		/* right (excluding what's covered by top and bottom) */
		cairo_rectangle (cr, show->priv->pat2right,
		                 show->priv->pat2top,
		                 window_width - show->priv->pat2right,
		                 show->priv->pat2bottom - show->priv->pat2top);
		if (show->priv->background_color)
		{
			cairo_set_source_rgba (cr, show->priv->background_color->red / 65535.0,
			                       show->priv->background_color->green / 65535.0,
			                       show->priv->background_color->blue / 65535.0, show->priv->alpha2);
		}
		else
		{
			cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, show->priv->alpha2);
		}
		cairo_fill (cr);

		gs_theme_engine_profile_start ("paint pattern to surface");
		cairo_set_source (cr, show->priv->pat2);

		cairo_paint_with_alpha (cr, show->priv->alpha2);
		gs_theme_engine_profile_end ("paint pattern to surface");
	}
	else
	{
		if (show->priv->pat1 != NULL)
		{
			cairo_set_source (cr, show->priv->pat1);
			cairo_paint (cr);
		}
	}

	cairo_destroy (cr);

	gtk_widget_queue_draw (GTK_WIDGET (show));
}

static gboolean
draw_iter (GSTESlideshow *show)
{
	double old_opacity;
	double new_opacity;

	if (show->priv->pat2 != NULL)
	{
		gdouble fps;
		gdouble elapsed;

		if (show->priv->fade_disabled)
		{
			show->priv->alpha2 = 1.0;
			update_display (show);
			finish_fade (show);
			return TRUE;
		}

		/* we are in a fade */
		show->priv->fade_ticks++;

		/*
		 * We have currently drawn pat2 with old_opacity, and we
		 * want to set alpha2 so that drawing pat2 at alpha2
		 * yields it drawn with new_opacity
		 *
		 * Solving
		 *   new_opacity = 1 - (1 - alpha2) * (1 - old_opacity)
		 * yields
		 *   alpha2 = 1 - (1 - new_opacity) / (1 - old_opacity)
		 *
		 * XXX This assumes that cairo doesn't correct alpha for
		 * the color profile.  However, any error is guaranteed
		 * to be cleaned up by the last iteration, where alpha2
		 * becomes 1 because new_opacity is 1.
		 */
		old_opacity = (double) (show->priv->fade_ticks - 1) /
		              (double) N_FADE_TICKS;
		new_opacity = (double) show->priv->fade_ticks /
		              (double) N_FADE_TICKS;
		show->priv->alpha2 = 1.0 - (1.0 - new_opacity) /
		                     (1.0 - old_opacity);

		update_display (show);

		elapsed = g_timer_elapsed (show->priv->timer, NULL);
		fps = (gdouble)show->priv->fade_ticks / elapsed;
		if (fps < MINIMUM_FPS)
		{
			g_warning ("Getting less than %.2f frames per second, disabling fade", MINIMUM_FPS);
			show->priv->fade_ticks = N_FADE_TICKS - 1;
			show->priv->fade_disabled = TRUE;
		}

		if (show->priv->fade_ticks >= N_FADE_TICKS)
		{
			finish_fade (show);
		}
	}

	return TRUE;
}

static void
process_new_pixbuf (GSTESlideshow *show,
                    GdkPixbuf     *pixbuf)
{
	gs_theme_engine_profile_msg ("Processing a new image");

	if (pixbuf != NULL)
	{
		start_fade (show, pixbuf);
	}
	else
	{
		start_new_load (show, 10);
	}
}

static void
op_result_free (OpResult *result)
{
	if (result == NULL)
	{
		return;
	}

	if (result->pixbuf != NULL)
	{
		g_object_unref (result->pixbuf);
	}

	g_free (result);
}

static gboolean
results_pull_func (GSTESlideshow *show)
{
	OpResult *result;

	g_async_queue_lock (show->priv->results_q);

	result = g_async_queue_try_pop_unlocked (show->priv->results_q);
	g_assert (result);

	while (result != NULL)
	{
		process_new_pixbuf (show, result->pixbuf);
		op_result_free (result);

		result = g_async_queue_try_pop_unlocked (show->priv->results_q);
	}

	show->priv->results_pull_id = 0;

	g_async_queue_unlock (show->priv->results_q);

	return FALSE;
}

static GdkPixbuf *
scale_pixbuf (GdkPixbuf *pixbuf,
              int        max_width,
              int        max_height,
              gboolean   no_stretch_hint)
{
	int        pw;
	int        ph;
	float      scale_factor_x = 1.0;
	float      scale_factor_y = 1.0;
	float      scale_factor = 1.0;

	pw = gdk_pixbuf_get_width (pixbuf);
	ph = gdk_pixbuf_get_height (pixbuf);

	/* If the image is less than 256 wide or high then it
	   is probably a thumbnail and we should ignore it */
	if (pw < 256 || ph < 256)
	{
		return NULL;
	}

	/* Determine which dimension requires the smallest scale. */
	scale_factor_x = (float) max_width / (float) pw;
	scale_factor_y = (float) max_height / (float) ph;

	if (scale_factor_x > scale_factor_y)
	{
		scale_factor = scale_factor_y;
	}
	else
	{
		scale_factor = scale_factor_x;
	}

	/* always scale down, allow to disable scaling up */
	if (scale_factor < 1.0 || !no_stretch_hint)
	{
		int scale_x;
		int scale_y;

		scale_x = (int) (pw * scale_factor);
		scale_y = (int) (ph * scale_factor);
		return gdk_pixbuf_scale_simple (pixbuf,
		                                scale_x,
		                                scale_y,
		                                GDK_INTERP_BILINEAR);
	}
	else
	{
		return g_object_ref (pixbuf);
	}
}

static void
add_files_to_list (GSList    **list,
                   const char *base)
{
	GDir       *d;
	const char *d_name;

	d = g_dir_open (base, 0, NULL);
	if (d == NULL)
	{
		g_warning ("Could not open directory: %s", base);
		return;
	}

	while ((d_name = g_dir_read_name (d)) != NULL)
	{
		char *path;

		/* skip hidden files */
		if (d_name[0] == '.')
		{
			continue;
		}

		path = g_build_filename (base, d_name, NULL);
		if (g_file_test (path, G_FILE_TEST_IS_DIR))
		{
			add_files_to_list (list, path);
			g_free (path);
		}
		else
		{
			*list = g_slist_prepend (*list, path);
		}
	}

	g_dir_close (d);
}

static GSList *
build_filename_list_local_dir (const char *base)
{
	GSList *list = NULL;

	add_files_to_list (&list, base);

	return list;
}

static int
gste_strcmp_compare_func (gconstpointer string_a, gconstpointer string_b)
{
	return strcmp (string_a == NULL ? "" : string_a,
	               string_b == NULL ? "" : string_b);
}


static GdkPixbuf *
get_pixbuf_from_local_dir (GSTESlideshow *show,
                           const char    *location)
{
	GdkPixbuf *pixbuf, *transformed_pixbuf;
	char      *filename;
	int        i;
	GSList    *l;

	/* rebuild the cache */
	if (show->priv->filename_list == NULL)
	{
		show->priv->filename_list = build_filename_list_local_dir (location);
	}

	if (show->priv->filename_list == NULL)
	{
		return NULL;
	}
	else
	{
		if (show->priv->sort_images)
		{
			show->priv->filename_list = g_slist_sort (show->priv->filename_list, gste_strcmp_compare_func);
		}
	}

	/* get a random filename if needed */
	if (! show->priv->sort_images)
	{
		i = g_random_int_range (0, g_slist_length (show->priv->filename_list));
		l = g_slist_nth (show->priv->filename_list, i);
	}
	else
	{
		l = show->priv->filename_list;
	}
	filename = l->data;

	pixbuf = gdk_pixbuf_new_from_file (filename, NULL);

	if (pixbuf != NULL)
	{
		transformed_pixbuf = gdk_pixbuf_apply_embedded_orientation (pixbuf);
		g_object_unref (pixbuf);
	}
	else
	{
		transformed_pixbuf = NULL;
	}

	g_free (filename);
	show->priv->filename_list = g_slist_delete_link (show->priv->filename_list, l);

	return transformed_pixbuf;
}

static GdkPixbuf *
get_pixbuf_from_location (GSTESlideshow *show,
                          const char    *location)
{
	GdkPixbuf *pixbuf = NULL;
	gboolean   is_dir;

	if (location == NULL)
	{
		return NULL;
	}

	is_dir = g_file_test (location, G_FILE_TEST_IS_DIR);

	if (is_dir)
	{
		pixbuf = get_pixbuf_from_local_dir (show, location);
	}

	return pixbuf;
}

static GdkPixbuf *
get_pixbuf (GSTESlideshow *show,
            const char    *location,
            int            width,
            int            height)
{
	GdkPixbuf *pixbuf;
	GdkPixbuf *scaled = NULL;

	if (location == NULL)
	{
		return NULL;
	}

	pixbuf = get_pixbuf_from_location (show, location);

	if (pixbuf != NULL)
	{
		scaled = scale_pixbuf (pixbuf, width, height, show->priv->no_stretch_hint);
		g_object_unref (pixbuf);
	}

	return scaled;
}

static void
op_load_image (GSTESlideshow *show,
               const char    *location)
{
	OpResult *op_result;
	int       window_width;
	int       window_height;

	window_width = show->priv->window_width;
	window_height = show->priv->window_height;

	op_result = g_new0 (OpResult, 1);

	op_result->pixbuf = get_pixbuf (show,
	                                location,
	                                window_width,
	                                window_height);

	g_async_queue_lock (show->priv->results_q);
	g_async_queue_push_unlocked (show->priv->results_q, op_result);

	if (show->priv->results_pull_id == 0)
	{
		show->priv->results_pull_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE,
		                              (GSourceFunc)results_pull_func,
		                              show, NULL);
	}

	g_async_queue_unlock (show->priv->results_q);
}

static gpointer
load_threadfunc (GAsyncQueue *op_q)
{
	Op *op;

	op = g_async_queue_pop (op_q);
	while (op)
	{
		op_load_image (op->slideshow,
		               op->location);

		if (op->slideshow != NULL)
		{
			g_object_unref (op->slideshow);
		}
		g_free (op->location);
		g_free (op);

		op = g_async_queue_pop (op_q);
	}

	return NULL;
}

void
gste_slideshow_set_images_location (GSTESlideshow *show,
                                    const char    *location)
{
	g_return_if_fail (GSTE_IS_SLIDESHOW (show));

	g_free (show->priv->images_location);
	show->priv->images_location = g_strdup (location);
}


void
gste_slideshow_set_sort_images (GSTESlideshow *show,
                                gboolean       sort_images)
{
	g_return_if_fail (GSTE_IS_SLIDESHOW (show));

	show->priv->sort_images = sort_images;
}

void
gste_slideshow_set_no_stretch_hint (GSTESlideshow *show,
                                    gboolean       no_stretch_hint)
{
	g_return_if_fail (GSTE_IS_SLIDESHOW (show));

	show->priv->no_stretch_hint = no_stretch_hint;
}

void
gste_slideshow_set_background_color (GSTESlideshow *show,
                                     const char    *background_color)
{
	g_return_if_fail (GSTE_IS_SLIDESHOW (show));

	if (show->priv->background_color != NULL)
	{
		g_slice_free (PangoColor, show->priv->background_color);
		show->priv->background_color = NULL;
	}

	if (background_color != NULL)
	{
		show->priv->background_color = g_slice_new (PangoColor);

		if (pango_color_parse (show->priv->background_color, background_color) == FALSE)
		{
			g_slice_free (PangoColor, show->priv->background_color);
			show->priv->background_color = NULL;
		}
	}
}

static void
gste_slideshow_set_property (GObject            *object,
                             guint               prop_id,
                             const GValue       *value,
                             GParamSpec         *pspec)
{
	GSTESlideshow *self;

	self = GSTE_SLIDESHOW (object);

	switch (prop_id)
	{
	case PROP_IMAGES_LOCATION:
		gste_slideshow_set_images_location (self, g_value_get_string (value));
		break;
	case PROP_SORT_IMAGES:
		gste_slideshow_set_sort_images (self, g_value_get_boolean (value));
		break;
	case PROP_SOLID_BACKGROUND:
		gste_slideshow_set_background_color (self, g_value_get_string (value));
		break;
	case PROP_NO_STRETCH_HINT:
		gste_slideshow_set_no_stretch_hint (self, g_value_get_boolean (value));
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
gste_slideshow_get_property (GObject            *object,
                             guint               prop_id,
                             GValue             *value,
                             GParamSpec         *pspec)
{
	GSTESlideshow *self;

	self = GSTE_SLIDESHOW (object);

	switch (prop_id)
	{
	case PROP_IMAGES_LOCATION:
		g_value_set_string (value, self->priv->images_location);
		break;
	case PROP_SORT_IMAGES:
		g_value_set_boolean (value, self->priv->sort_images);
		break;
	case PROP_SOLID_BACKGROUND:
	{
		char *color = NULL;
		color = pango_color_to_string (self->priv->background_color);
		g_value_set_string (value, color);
		g_free (color);
		break;
	}
	case PROP_NO_STRETCH_HINT:
		g_value_set_boolean (value, self->priv->no_stretch_hint);
		break;
	default:
		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
		break;
	}
}

static void
gste_slideshow_real_show (GtkWidget *widget)
{
	GSTESlideshow *show = GSTE_SLIDESHOW (widget);
	int            delay;

	if (GTK_WIDGET_CLASS (parent_class)->show)
	{
		GTK_WIDGET_CLASS (parent_class)->show (widget);
	}

	start_new_load (show, 10);

	delay = 25;
	show->priv->timeout_id = g_timeout_add (delay, (GSourceFunc)draw_iter, show);

	if (show->priv->timer != NULL)
	{
		g_timer_destroy (show->priv->timer);
	}
	show->priv->timer = g_timer_new ();
}

static gboolean
gste_slideshow_real_draw (GtkWidget *widget,
                          cairo_t   *cr)
{
	GSTESlideshow *show = GSTE_SLIDESHOW (widget);

	if (GTK_WIDGET_CLASS (parent_class)->draw) {
		GTK_WIDGET_CLASS (parent_class)->draw (widget, cr);
	}

	cairo_set_source_surface (cr, show->priv->surf, 0, 0);

	gs_theme_engine_profile_start ("paint surface to window");
	cairo_paint (cr);
	gs_theme_engine_profile_end ("paint surface to window");

	return TRUE;
}

static gboolean
gste_slideshow_real_configure (GtkWidget         *widget,
                               GdkEventConfigure *event)
{
	GSTESlideshow *show = GSTE_SLIDESHOW (widget);
	gboolean       handled = FALSE;
	cairo_t       *cr;

	/* resize */
	gs_theme_engine_get_window_size (GS_THEME_ENGINE (show),
	                                 &show->priv->window_width,
	                                 &show->priv->window_height);

	gs_theme_engine_profile_msg ("Resize to x:%d y:%d",
	                             show->priv->window_width,
	                             show->priv->window_height);

	if (show->priv->surf != NULL)
	{
		cairo_surface_destroy (show->priv->surf);
	}

	cr = gdk_cairo_create (gtk_widget_get_window (widget));
	show->priv->surf = cairo_surface_create_similar (cairo_get_target (cr),
	                   CAIRO_CONTENT_COLOR,
	                   show->priv->window_width,
	                   show->priv->window_height);
	cairo_destroy (cr);

	/* schedule a redraw */
	gtk_widget_queue_draw (widget);

	if (GTK_WIDGET_CLASS (parent_class)->configure_event)
	{
		handled = GTK_WIDGET_CLASS (parent_class)->configure_event (widget, event);
	}

	return handled;
}

static void
gste_slideshow_class_init (GSTESlideshowClass *klass)
{
	GObjectClass   *object_class = G_OBJECT_CLASS (klass);
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

	parent_class = g_type_class_peek_parent (klass);

	object_class->finalize = gste_slideshow_finalize;
	object_class->get_property = gste_slideshow_get_property;
	object_class->set_property = gste_slideshow_set_property;

	widget_class->show = gste_slideshow_real_show;
	widget_class->draw = gste_slideshow_real_draw;
	widget_class->configure_event = gste_slideshow_real_configure;

	g_type_class_add_private (klass, sizeof (GSTESlideshowPrivate));

	g_object_class_install_property (object_class,
	                                 PROP_IMAGES_LOCATION,
	                                 g_param_spec_string ("images-location",
	                                         NULL,
	                                         NULL,
	                                         NULL,
	                                         G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
	                                 PROP_SORT_IMAGES,
	                                 g_param_spec_boolean ("sort-images",
	                                         NULL,
	                                         NULL,
	                                         FALSE,
	                                         G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
	                                 PROP_SOLID_BACKGROUND,
	                                 g_param_spec_string ("background-color",
	                                         NULL,
	                                         NULL,
	                                         NULL,
	                                         G_PARAM_READWRITE));
	g_object_class_install_property (object_class,
	                                 PROP_NO_STRETCH_HINT,
	                                 g_param_spec_boolean ("no-stretch",
	                                         NULL,
	                                         NULL,
	                                         FALSE,
	                                         G_PARAM_READWRITE));
}

static void
set_visual (GtkWidget *widget)
{
	GdkScreen *screen;
	GdkVisual *visual;

	screen = gtk_widget_get_screen (widget);
	visual = gdk_screen_get_rgba_visual (screen);
	if (visual == NULL)
	{
		visual = gdk_screen_get_system_visual (screen);
	}

	gtk_widget_set_visual (widget, visual);
}

static void
gste_slideshow_init (GSTESlideshow *show)
{
	show->priv = GSTE_SLIDESHOW_GET_PRIVATE (show);

	show->priv->images_location = g_strdup (DEFAULT_IMAGES_LOCATION);

	show->priv->op_q = g_async_queue_new ();
	show->priv->results_q = g_async_queue_new ();

	g_thread_new ("loadthread", (GThreadFunc)load_threadfunc, show->priv->op_q);

	set_visual (GTK_WIDGET (show));
}

static void
gste_slideshow_finalize (GObject *object)
{
	GSTESlideshow *show;
	gpointer       result;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GSTE_IS_SLIDESHOW (object));

	show = GSTE_SLIDESHOW (object);

	g_return_if_fail (show->priv != NULL);

	if (show->priv->surf)
	{
		cairo_surface_destroy (show->priv->surf);
	}

	if (show->priv->timeout_id > 0)
	{
		g_source_remove (show->priv->timeout_id);
		show->priv->timeout_id = 0;
	}

	if (show->priv->results_pull_id > 0)
	{
		g_source_remove (show->priv->results_pull_id);
		show->priv->results_pull_id = 0;
	}

	if (show->priv->results_q != NULL)
	{
		result = g_async_queue_try_pop (show->priv->results_q);

		while (result)
		{
			result = g_async_queue_try_pop (show->priv->results_q);
		}
		g_async_queue_unref (show->priv->results_q);
	}

	g_free (show->priv->images_location);
	show->priv->images_location = NULL;

	if (show->priv->background_color)
	{
		g_slice_free (PangoColor, show->priv->background_color);
		show->priv->background_color = NULL;
	}

	if (show->priv->timer != NULL)
	{
		g_timer_destroy (show->priv->timer);
	}

	G_OBJECT_CLASS (parent_class)->finalize (object);
}