/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/* -*- mode: c; style: linux -*- */

/* applier.c
 * Copyright (C) 2001 Ximian, Inc.
 *
 * Written by Bradford Hovinen <hovinen@ximian.com>
 *
 * 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, 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <string.h>
#include <gnome.h>
#include <gdk/gdk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkprivate.h>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <unistd.h>

#include "applier.h"

#define MONITOR_CONTENTS_X 0 
#define MONITOR_CONTENTS_Y 0
#define MONITOR_CONTENTS_DEFAULT_WIDTH 64
#define MONITOR_CONTENTS_DEFAULT_HEIGHT 48

enum {
	PROP_0,
	PROP_TYPE,
	PROP_PREVIEW_WIDTH,
	PROP_PREVIEW_HEIGHT,
	PROP_SCREEN
};

struct _BGApplierPrivate 
{
	GtkWidget          *preview_widget;        /* The widget for previewing
						    * -- this is not used for
						    * actual rendering; it is
						    * returned if requested */
	BGPreferences      *last_prefs;            /* A cache of the last
						    * bg_preferences structure to
						    * be applied */

	GdkPixbuf          *wallpaper_pixbuf;      /* The "raw" wallpaper pixbuf */

	BGApplierType         type;                  /* Whether we render to the
						    * root or the preview */

	/* Where on the pixmap we should render the background image. Should
         * have origin 0,0 and width and height equal to the desktop size if we
         * are rendering to the desktop. The area to which we render the pixbuf
         * will be smaller if we are rendering a centered image smaller than the
         * screen or scaling and keeping aspect ratio and the background color
         * is solid. */
	GdkRectangle        render_geom;

	/* Where to render the pixbuf, relative to the pixmap. This will be the
	 * same as render_geom above if we have no solid color area to worry
	 * about. By convention, a negative value means that the pixbuf is
	 * larger than the pixmap, so the region should be fetched from a subset
	 * of the pixbuf. */
	GdkRectangle        pixbuf_render_geom;

	/* Where to fetch the data from the pixbuf. We use this in case we are
	 * rendering a centered image that is larger than the size of the
	 * desktop. Otherwise it is (0,0) */
	GdkPoint            pixbuf_xlate;

	/* Geometry of the pixbuf used to render the gradient. If the wallpaper
	 * is not enabled, we use the following optimization: On one dimension,
	 * this should be equal to the dimension of render_geom, while on the
	 * other dimension it should be the constant 32. This avoids wasting
	 * memory with rendundant data. */
	GdkPoint            grad_geom;

	GdkPixbuf          *pixbuf;            /* "working" pixbuf - All data
						* are rendered onto this for
						* display */
	GdkPixmap          *pixmap;            /* Pixmap onto which we dump the
						* pixbuf above when we are ready
						* to render to the screen */
	gboolean            pixmap_is_set;     /* TRUE iff the pixmap above
						* has been set as the root
						* pixmap */
	guint               timeout;           /* "Cleanup" timeout handler;
						* reset to 30 seconds every
						* time apply is called. */
	GdkWindow          *root_window;       /* Root window on which to
						* render the background */
	GdkScreen          *screen;            /* Screen on which to render
						* the background */
	guint               size_changed_cb_id; /* Signal connection id. */
};

static GObjectClass *parent_class;

static void bg_applier_init          (BGApplier           *prefs,
				      BGApplierClass      *class);
static void bg_applier_class_init    (BGApplierClass      *class);
static void bg_applier_base_init     (BGApplierClass      *class);

static void bg_applier_set_prop      (GObject           *object, 
				      guint              prop_id,
				      const GValue      *value,
				      GParamSpec        *pspec);
static void bg_applier_get_prop      (GObject           *object, 
				      guint              prop_id,
				      GValue            *value,
				      GParamSpec        *pspec);

static void bg_applier_dispose       (GObject           *object);
static void bg_applier_finalize      (GObject           *object);

static void run_render_pipeline      (BGApplier           *bg_applier, 
				      const BGPreferences *prefs);
static void draw_disabled_message    (GtkWidget         *widget,
				      const guint width,
				      const guint height);

static void size_changed_cb          (GdkScreen           *screen,
				      BGApplier           *bg_applier);
static void render_background        (BGApplier           *bg_applier,
				      const BGPreferences *prefs);
static void render_wallpaper         (BGApplier           *bg_applier,
				      const BGPreferences *prefs);
static void render_to_screen         (BGApplier           *bg_applier,
				      const BGPreferences *prefs);
static void create_pixmap            (BGApplier           *bg_applier,
				      const BGPreferences *prefs);
static void get_geometry             (wallpaper_type_t   wallpaper_type,
				      GdkPixbuf         *pixbuf,
				      GdkRectangle      *field_geom,
				      GdkRectangle      *virtual_geom,
				      GdkRectangle      *dest_geom,
				      GdkRectangle      *src_geom);

static GdkPixbuf *place_pixbuf       (GdkPixbuf         *dest_pixbuf,
				      GdkPixbuf         *src_pixbuf,
				      GdkRectangle      *dest_geom,
				      GdkRectangle      *src_geom,
				      guint              alpha,
				      GdkColor          *bg_color);
static GdkPixbuf *tile_pixbuf        (GdkPixbuf         *dest_pixbuf,
				      GdkPixbuf         *src_pixbuf,
				      GdkRectangle      *field_geom,
				      guint              alpha,
				      GdkColor          *bg_color);
static void fill_gradient            (GdkPixbuf         *pixbuf,
				      GdkColor          *c1,
				      GdkColor          *c2,
				      orientation_t      orientation);

static gboolean need_wallpaper_load_p  (const BGApplier     *bg_applier,
					const BGPreferences *prefs);
static gboolean need_root_pixmap_p     (const BGApplier     *bg_applier,
					const BGPreferences *prefs);
static gboolean wallpaper_full_cover_p (const BGApplier     *bg_applier,
					const BGPreferences *prefs);
static gboolean render_small_pixmap_p  (const BGPreferences *prefs);

static GdkPixmap *make_root_pixmap   (GdkScreen         *screen,
				      gint               width,
				      gint               height);
static void set_root_pixmap          (GdkPixmap         *pixmap,
				      GdkScreen         *screen);

static gboolean is_nautilus_running  (void);

static gboolean cleanup_cb           (BGApplier *bg_applier);

static void preview_realized_cb      (GtkWidget *preview,
				      BGApplier *bg_applier);

GType
bg_applier_get_type (void)
{
	static GType bg_applier_type = 0;

	if (!bg_applier_type) {
		static GTypeInfo bg_applier_info = {
			sizeof (BGApplierClass),
			(GBaseInitFunc) bg_applier_base_init,
			NULL, /* GBaseFinalizeFunc */
			(GClassInitFunc) bg_applier_class_init,
			NULL, /* GClassFinalizeFunc */
			NULL, /* user-supplied data */
			sizeof (BGApplier),
			0, /* n_preallocs */
			(GInstanceInitFunc) bg_applier_init,
			NULL
		};

		bg_applier_type = 
			g_type_register_static (G_TYPE_OBJECT, 
						"BGApplier",
						&bg_applier_info, 0);
	}

	return bg_applier_type;
}

static void
bg_applier_init (BGApplier *bg_applier, BGApplierClass *class)
{
	bg_applier->p                     = g_new0 (BGApplierPrivate, 1);
	bg_applier->p->last_prefs         = NULL;
	bg_applier->p->pixbuf             = NULL;
	bg_applier->p->wallpaper_pixbuf   = NULL;
	bg_applier->p->timeout            = 0;
	bg_applier->p->render_geom.width  = -1;
	bg_applier->p->render_geom.height = -1;
	bg_applier->p->type               = BG_APPLIER_PREVIEW;

	bg_applier->p->screen            = gdk_screen_get_default ();
	bg_applier->p->root_window       = gdk_screen_get_root_window (bg_applier->p->screen);

	bg_applier->p->size_changed_cb_id = 0;
}

static void
bg_applier_class_init (BGApplierClass *class) 
{
	GObjectClass *object_class;

	object_class = G_OBJECT_CLASS (class);

	object_class->dispose = bg_applier_dispose;
	object_class->finalize = bg_applier_finalize;
	object_class->set_property = bg_applier_set_prop;
	object_class->get_property = bg_applier_get_prop;

	g_object_class_install_property
		(object_class, PROP_TYPE,
		 g_param_spec_int ("type",
				   _("Type"),
				   _("Type of bg_applier: BG_APPLIER_ROOT for root window or BG_APPLIER_PREVIEW for preview"),
				   0, 1, 0,
				   G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property
		(object_class, PROP_PREVIEW_WIDTH,
		 g_param_spec_uint ("preview_width",
			 	    _("Preview Width"),
				    _("Width if applier is a preview: Defaults to 64."),
				    1, 65535, MONITOR_CONTENTS_DEFAULT_WIDTH,
				    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property
		(object_class, PROP_PREVIEW_HEIGHT,
		 g_param_spec_uint ("preview_height",
				    _("Preview Height"),
				    _("Height if applier is a preview: Defaults to 48."),
				    1, 65535, MONITOR_CONTENTS_DEFAULT_HEIGHT,
				    G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	g_object_class_install_property
		(object_class, PROP_SCREEN,
		 g_param_spec_object ("screen",
				      _("Screen"),
				      _("Screen on which BGApplier is to draw"),
				      GDK_TYPE_SCREEN,
				      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

	parent_class = 
		G_OBJECT_CLASS (g_type_class_ref (G_TYPE_OBJECT));
}

static void
bg_applier_base_init (BGApplierClass *class) 
{
}

static void
bg_applier_set_prop (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) 
{
	BGApplier *bg_applier;

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

	bg_applier = BG_APPLIER (object);

	switch (prop_id) {
	case PROP_TYPE:
		bg_applier->p->type = g_value_get_int (value);

		switch (bg_applier->p->type) {
		case BG_APPLIER_ROOT:
			bg_applier->p->render_geom.x = 0;
			bg_applier->p->render_geom.y = 0;
			bg_applier->p->render_geom.width = gdk_screen_get_width (bg_applier->p->screen);
			bg_applier->p->render_geom.height = gdk_screen_get_height (bg_applier->p->screen);
			bg_applier->p->pixmap = NULL;
			bg_applier->p->pixmap_is_set = FALSE;

			if (bg_applier->p->size_changed_cb_id == 0)
				bg_applier->p->size_changed_cb_id = g_signal_connect (bg_applier->p->screen, "size_changed",
										      G_CALLBACK (size_changed_cb), bg_applier);
			break;

		case BG_APPLIER_PREVIEW:
			if (bg_applier->p->size_changed_cb_id)
				g_signal_handler_disconnect (bg_applier->p->screen,
							     bg_applier->p->size_changed_cb_id);
			bg_applier->p->size_changed_cb_id = 0;
			bg_applier->p->render_geom.x = MONITOR_CONTENTS_X;
			bg_applier->p->render_geom.y = MONITOR_CONTENTS_Y;
			
			if (bg_applier->p->render_geom.width == -1)
			{
				bg_applier->p->render_geom.width = MONITOR_CONTENTS_DEFAULT_WIDTH;
				bg_applier->p->render_geom.height = MONITOR_CONTENTS_DEFAULT_HEIGHT;
			}

			break;

		default:
			g_critical ("Bad bg_applier type: %d", bg_applier->p->type);
			break;
		}

		break;

	case PROP_PREVIEW_WIDTH:
		if (bg_applier->p->type == BG_APPLIER_PREVIEW)
			bg_applier->p->render_geom.width = g_value_get_uint (value);
		break;

	case PROP_PREVIEW_HEIGHT:
		if (bg_applier->p->type == BG_APPLIER_PREVIEW)
			bg_applier->p->render_geom.height = g_value_get_uint (value);
		break;

	case PROP_SCREEN:
		if (bg_applier->p->type == BG_APPLIER_ROOT) {
			if (bg_applier->p->size_changed_cb_id)
				g_signal_handler_disconnect (bg_applier->p->screen,
							     bg_applier->p->size_changed_cb_id);
			bg_applier->p->screen            = g_value_get_object (value);
			bg_applier->p->root_window       = gdk_screen_get_root_window (bg_applier->p->screen);
			bg_applier->p->render_geom.width = gdk_screen_get_width (bg_applier->p->screen);
			bg_applier->p->render_geom.height = gdk_screen_get_height (bg_applier->p->screen);
			bg_applier->p->size_changed_cb_id = g_signal_connect (bg_applier->p->screen, "size_changed",
									      G_CALLBACK (size_changed_cb), bg_applier);
		}
		break;
		
	default:
		g_warning ("Bad property set");
		break;
	}
}

static void
bg_applier_get_prop (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) 
{
	BGApplier *bg_applier;

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

	bg_applier = BG_APPLIER (object);

	switch (prop_id) {
	case PROP_TYPE:
		g_value_set_int (value, bg_applier->p->type);
		break;

	case PROP_SCREEN:
		g_value_set_object (value, bg_applier->p->screen);
		break;

	default:
		g_warning ("Bad property get");
		break;
	}
}

static void
bg_applier_dispose (GObject *object) 
{
	BGApplier *bg_applier;

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

	bg_applier = BG_APPLIER (object);

	g_assert (bg_applier->p->pixbuf == NULL);

	if (bg_applier->p->last_prefs != NULL)
		g_object_unref (G_OBJECT (bg_applier->p->last_prefs));
	bg_applier->p->last_prefs = NULL;

	if (bg_applier->p->wallpaper_pixbuf != NULL)
		g_object_unref (G_OBJECT (bg_applier->p->wallpaper_pixbuf));
	bg_applier->p->wallpaper_pixbuf = NULL;

	if (bg_applier->p->size_changed_cb_id)
		g_signal_handler_disconnect (bg_applier->p->screen,
					     bg_applier->p->size_changed_cb_id);
	bg_applier->p->size_changed_cb_id = 0;

	parent_class->dispose (object);
}

static void
bg_applier_finalize (GObject *object) 
{
	BGApplier *bg_applier;

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

	bg_applier = BG_APPLIER (object);

	g_free (bg_applier->p);

	parent_class->finalize (object);
}

GObject *
bg_applier_new (BGApplierType type) 
{
	GObject *object;

	object = g_object_new (bg_applier_get_type (),
			       "type", type,
			       NULL);

	return object;
}

GObject *
bg_applier_new_at_size (BGApplierType type,
			const guint width,
			const guint height)
{
	GObject *object;

	object = g_object_new (bg_applier_get_type (),
			       "type", type,
			       "preview_width", width,
			       "preview_height", height,
			       NULL);

	return object;
}

GObject *
bg_applier_new_for_screen (BGApplierType  type,
			   GdkScreen     *screen)
{
	GObject *object;

	g_return_val_if_fail (type == BG_APPLIER_ROOT, NULL);

	object = g_object_new (bg_applier_get_type (),
			       "type", type,
			       "screen", screen,
			       NULL);
	return object;
}

static void
refresh_render (BGApplier *bg_applier,
		BGPreferences *prefs,
		gboolean need_wallpaper_load)
{
	if (bg_applier->p->type == BG_APPLIER_ROOT && is_nautilus_running ()) {
		return;
	}

	if (!prefs->enabled) {
		if (bg_applier->p->type == BG_APPLIER_PREVIEW)
			draw_disabled_message (bg_applier_get_preview_widget (bg_applier), bg_applier->p->render_geom.width, bg_applier->p->render_geom.height);
		return;
	}

	if (need_wallpaper_load) {
		if (bg_applier->p->wallpaper_pixbuf != NULL)
			g_object_unref (G_OBJECT (bg_applier->p->wallpaper_pixbuf));

		bg_applier->p->wallpaper_pixbuf = NULL;

		if (prefs->wallpaper_enabled) {
			g_return_if_fail (prefs->wallpaper_filename != NULL);

			if (prefs->wallpaper_type == WPTYPE_STRETCHED ||
			    prefs->wallpaper_type == WPTYPE_SCALED) {
				bg_applier->p->wallpaper_pixbuf = 
					gdk_pixbuf_new_from_file_at_scale (prefs->wallpaper_filename,
									   bg_applier->p->render_geom.width,
									   bg_applier->p->render_geom.height,
									   prefs->wallpaper_type == WPTYPE_SCALED,
									   NULL);
			} else {
				bg_applier->p->wallpaper_pixbuf = 
					gdk_pixbuf_new_from_file (prefs->wallpaper_filename, NULL);
			}

			if (bg_applier->p->wallpaper_pixbuf == NULL) {
				prefs->wallpaper_enabled = FALSE;
			}
			else if (bg_applier->p->type == BG_APPLIER_ROOT) {
				if (bg_applier->p->timeout)
					g_source_remove (bg_applier->p->timeout);
				bg_applier->p->timeout = g_timeout_add (30000, (GSourceFunc) cleanup_cb, bg_applier);
			}
		}
	}

	run_render_pipeline (bg_applier, prefs);

	if (bg_applier->p->type == BG_APPLIER_PREVIEW && bg_applier->p->preview_widget != NULL)
		gtk_widget_queue_draw (bg_applier->p->preview_widget);
}

static void
size_changed_cb (GdkScreen *screen,
		 BGApplier *bg_applier)
{
	bg_applier->p->render_geom.width = gdk_screen_get_width (bg_applier->p->screen);
	bg_applier->p->render_geom.height = gdk_screen_get_height (bg_applier->p->screen);
	if (bg_applier->p->last_prefs) {
		refresh_render (bg_applier,
				bg_applier->p->last_prefs,
				TRUE);
	}
}

void
bg_applier_apply_prefs (BGApplier           *bg_applier, 
			const BGPreferences *prefs)
{
	BGPreferences *new_prefs;

	g_return_if_fail (bg_applier != NULL);
	g_return_if_fail (IS_BG_APPLIER (bg_applier));

	new_prefs = BG_PREFERENCES (bg_preferences_clone (prefs));

	if (new_prefs->wallpaper_type == WPTYPE_NONE)
	{
		new_prefs->wallpaper_enabled = FALSE;
		new_prefs->wallpaper_type = WPTYPE_CENTERED;
	}

	refresh_render (bg_applier, new_prefs, need_wallpaper_load_p (bg_applier, new_prefs));

	if (bg_applier->p->last_prefs != NULL)
		g_object_unref (G_OBJECT (bg_applier->p->last_prefs));

	bg_applier->p->last_prefs = new_prefs;
}

gboolean
bg_applier_render_color_p (const BGApplier *bg_applier, const BGPreferences *prefs) 
{
	g_return_val_if_fail (bg_applier != NULL, FALSE);
	g_return_val_if_fail (IS_BG_APPLIER (bg_applier), FALSE);
	g_return_val_if_fail (prefs != NULL, FALSE);
	g_return_val_if_fail (IS_BG_PREFERENCES (prefs), FALSE);

	return prefs->enabled && !wallpaper_full_cover_p (bg_applier, prefs);
}

GtkWidget *
bg_applier_get_preview_widget (BGApplier *bg_applier) 
{
	if (bg_applier->p->preview_widget == NULL)
	{
		bg_applier->p->preview_widget = gtk_image_new ();

		/* We need to initialize the pixmap, but this
		 * needs GCs, so we have to wait until realize. */
		g_signal_connect (G_OBJECT (bg_applier->p->preview_widget),
				  "realize",
				  (GCallback) preview_realized_cb,
				  bg_applier);
	}

	return bg_applier->p->preview_widget;
}

GdkPixbuf *
bg_applier_get_wallpaper_pixbuf (BGApplier *bg_applier)
{
	g_return_val_if_fail (bg_applier != NULL, NULL);
	g_return_val_if_fail (IS_BG_APPLIER (bg_applier), NULL);

	return bg_applier->p->wallpaper_pixbuf;
}

static void
draw_disabled_message (GtkWidget *widget, const guint w, const guint h)
{
	GdkPixmap      *pixmap;
	GdkColor        color;
	PangoLayout    *layout;
	PangoRectangle  extents;
	GdkGC          *gc;
	gint            x, y;
	const char     *disabled_string = _("Disabled");

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GTK_IS_IMAGE (widget));

	x = MONITOR_CONTENTS_X;
	y = MONITOR_CONTENTS_Y;

	if (!GTK_WIDGET_REALIZED (widget)) {
		gtk_widget_realize (widget);
	}

	gtk_image_get_pixmap (GTK_IMAGE (widget), &pixmap, NULL);
	gc = gdk_gc_new (widget->window);

	color.red = 0x0;
	color.green = 0x0;
	color.blue = 0x0;
	gdk_gc_set_rgb_fg_color (gc, &color);
	gdk_draw_rectangle (pixmap, gc, TRUE, x, y, w, h);

	layout = gtk_widget_create_pango_layout (widget, disabled_string);
	pango_layout_get_pixel_extents (layout, &extents, NULL);

	color.red = 0xffff;
	color.green = 0xffff;
	color.blue = 0xffff;
	gdk_gc_set_rgb_fg_color (gc, &color);

	/* fixme: I do not understand the logic (Lauris) */

	gdk_draw_layout (widget->window,
			 gc,
			 x + (w - extents.width) / 2,
			 y + (h - extents.height) / 2 + extents.height / 2,
			 layout);

	g_object_unref (G_OBJECT (gc));
	g_object_unref (G_OBJECT (layout));
}

static void
run_render_pipeline (BGApplier *bg_applier, const BGPreferences *prefs)
{
	g_return_if_fail (bg_applier != NULL);
	g_return_if_fail (IS_BG_APPLIER (bg_applier));
	g_return_if_fail (prefs != NULL);
	g_return_if_fail (IS_BG_PREFERENCES (prefs));

	g_assert (bg_applier->p->pixbuf == NULL);

	/* Initialize bg_applier->p->render_geom */
	bg_applier->p->pixbuf_render_geom.x = bg_applier->p->render_geom.x;
	bg_applier->p->pixbuf_render_geom.y = bg_applier->p->render_geom.y;
	bg_applier->p->pixbuf_render_geom.width = bg_applier->p->render_geom.width;
	bg_applier->p->pixbuf_render_geom.height = bg_applier->p->render_geom.height;
	bg_applier->p->pixbuf_xlate.x = 0;
	bg_applier->p->pixbuf_xlate.y = 0;

	render_background (bg_applier, prefs);

	if (need_root_pixmap_p (bg_applier, prefs))
		create_pixmap (bg_applier, prefs);

	render_wallpaper (bg_applier, prefs);
	render_to_screen (bg_applier, prefs);

	if (bg_applier->p->pixbuf != NULL) {
		g_object_unref (G_OBJECT (bg_applier->p->pixbuf));
		bg_applier->p->pixbuf = NULL;
	}
}

/* Create the gradient image if necessary and put it into a fresh pixbuf
 *
 * Preconditions:
 *   1. prefs is valid
 *   2. The old bg_applier->p->pixbuf, if it existed, has been destroyed
 *
 * Postconditions (assuming gradient is enabled):
 *   1. bg_applier->p->pixbuf contains a newly rendered gradient
 */

static void
render_background (BGApplier *bg_applier, const BGPreferences *prefs) 
{
	g_return_if_fail (bg_applier != NULL);
	g_return_if_fail (IS_BG_APPLIER (bg_applier));
	g_return_if_fail (prefs != NULL);
	g_return_if_fail (IS_BG_PREFERENCES (prefs));

	if (prefs->gradient_enabled && !wallpaper_full_cover_p (bg_applier, prefs)) {
		bg_applier->p->grad_geom.x = bg_applier->p->render_geom.width;
		bg_applier->p->grad_geom.y = bg_applier->p->render_geom.height;

		if (bg_applier->p->type == BG_APPLIER_ROOT && !prefs->wallpaper_enabled) {
			if (prefs->orientation == ORIENTATION_HORIZ)
				bg_applier->p->grad_geom.y = 32;
			else
				bg_applier->p->grad_geom.x = 32;
		}

		bg_applier->p->pixbuf = 
			gdk_pixbuf_new (GDK_COLORSPACE_RGB, 
					FALSE, 8, 
					bg_applier->p->grad_geom.x, 
					bg_applier->p->grad_geom.y);

		fill_gradient (bg_applier->p->pixbuf,
			       prefs->color1, prefs->color2, 
			       prefs->orientation);

		bg_applier->p->pixbuf_render_geom.width = bg_applier->p->grad_geom.x;
		bg_applier->p->pixbuf_render_geom.height = bg_applier->p->grad_geom.y;
	}
}

/* Render the wallpaper onto the pixbuf-in-progress.
 *
 * Preconditions:
 *   1. The wallpaper pixbuf has been loaded and is in
 *      bg_applier->p->wallpaper_pixbuf.
 *   2. The structure bg_applier->p->render_geom is filled out properly as
 *      described in the documentation above (this should be invariant).
 *   3. The various fields in prefs are valid
 *
 * Postconditions (assuming wallpaper is enabled):
 *   1. bg_applier->p->pixbuf contains the pixbuf-in-progress with the wallpaper
 *      correctly rendered.
 *   2. bg_applier->p->pixbuf_render_geom has been modified, if necessary,
 *      according to the requirements of the wallpaper; it should be set by
 *      default to be the same as bg_applier->p->render_geom.
 */

static void
render_wallpaper (BGApplier *bg_applier, const BGPreferences *prefs) 
{
	GdkRectangle  src_geom = { 0, };
	GdkRectangle  dest_geom = { 0, };
	GdkRectangle  virtual_geom;
	GdkPixbuf    *prescaled_pixbuf = NULL;
	guint         alpha;
	gint          tmp1, tmp2;
	gint          pwidth, pheight;

	g_return_if_fail (bg_applier != NULL);
	g_return_if_fail (IS_BG_APPLIER (bg_applier));
	g_return_if_fail (prefs != NULL);
	g_return_if_fail (IS_BG_PREFERENCES (prefs));

	if (prefs->wallpaper_enabled) {
		if (bg_applier->p->wallpaper_pixbuf == NULL)
			return;

		gdk_drawable_get_size (bg_applier->p->root_window, &tmp1, &tmp2);
		virtual_geom.x = virtual_geom.y = 0;
		virtual_geom.width = tmp1;
		virtual_geom.height = tmp2;

		pwidth = gdk_pixbuf_get_width (bg_applier->p->wallpaper_pixbuf);
		pheight = gdk_pixbuf_get_height (bg_applier->p->wallpaper_pixbuf);

		get_geometry (prefs->wallpaper_type,
			      bg_applier->p->wallpaper_pixbuf,
			      &(bg_applier->p->render_geom),
			      &virtual_geom, &dest_geom, &src_geom);

		/* Modify bg_applier->p->pixbuf_render_geom if necessary */
		if (bg_applier->p->pixbuf == NULL) {   /* This means we didn't render a gradient */
			bg_applier->p->pixbuf_render_geom.x = dest_geom.x + bg_applier->p->render_geom.x;
			bg_applier->p->pixbuf_render_geom.y = dest_geom.y + bg_applier->p->render_geom.y;
			bg_applier->p->pixbuf_render_geom.width = dest_geom.width;
			bg_applier->p->pixbuf_render_geom.height = dest_geom.height;
		}

		if (prefs->wallpaper_type == WPTYPE_TILED) {
			if (dest_geom.width != pwidth || dest_geom.height != pheight) {
				int hscale = pwidth * bg_applier->p->render_geom.width / virtual_geom.width;
				int vscale = pheight * bg_applier->p->render_geom.height / virtual_geom.height;

				if (hscale < 1) hscale = 1;
				if (vscale < 1) vscale = 1;

				prescaled_pixbuf = gdk_pixbuf_scale_simple
					(bg_applier->p->wallpaper_pixbuf, hscale, vscale,
					 GDK_INTERP_BILINEAR);
			} else {
				prescaled_pixbuf = bg_applier->p->wallpaper_pixbuf;
				g_object_ref (G_OBJECT (prescaled_pixbuf));
			}
		}

		if (prefs->adjust_opacity) {
			alpha = 2.56 * prefs->opacity;
			alpha = alpha * alpha / 256;
			alpha = CLAMP (alpha, 0, 255);
		} else {
			alpha = 255;
		}

		if (prefs->wallpaper_type == WPTYPE_TILED)
			bg_applier->p->pixbuf = tile_pixbuf (bg_applier->p->pixbuf,
							  prescaled_pixbuf,
							  &(bg_applier->p->render_geom),
							  alpha, prefs->color1);
		else
			bg_applier->p->pixbuf = place_pixbuf (bg_applier->p->pixbuf,
							   bg_applier->p->wallpaper_pixbuf,
							   &dest_geom, &src_geom,
							   alpha, prefs->color1);

		if (bg_applier->p->pixbuf == bg_applier->p->wallpaper_pixbuf) {
			bg_applier->p->pixbuf_xlate.x = src_geom.x;
			bg_applier->p->pixbuf_xlate.y = src_geom.y;
		}

		if (prescaled_pixbuf != NULL)
			g_object_unref (G_OBJECT (prescaled_pixbuf));
	}
}

/* Take whatever we have rendered and transfer it to the display.
 *
 * Preconditions:
 *   1. We have already rendered the gradient and wallpaper, and
 *      bg_applier->p->pixbuf is a valid GdkPixbuf containing that rendered data.
 *   2. The structure bg_applier->p->pixbuf_render_geom contains the coordonites on
 *      the destination visual to which we should render the contents of
 *      bg_applier->p->pixbuf
 *   3. The structure bg_applier->p->render_geom contains the total area that the
 *      background should cover (i.e. the whole desktop if we are rendering to
 *      the root window, or the region inside the monitor if we are rendering to
 *      the preview).
 *   4. The strucutre prefs->color1 contains the background color to be used on
 *      areas not covered by the above pixbuf.
 */

static void
render_to_screen (BGApplier *bg_applier, const BGPreferences *prefs) 
{
	GdkGC *gc;

	g_return_if_fail (bg_applier != NULL);
	g_return_if_fail (IS_BG_APPLIER (bg_applier));
	g_return_if_fail (prefs != NULL);
	g_return_if_fail (IS_BG_PREFERENCES (prefs));

	gc = gdk_gc_new (bg_applier->p->pixmap);

	if (bg_applier->p->pixbuf != NULL) {
		if (bg_applier->p->pixbuf_render_geom.x != 0 ||
		    bg_applier->p->pixbuf_render_geom.y != 0 ||
		    bg_applier->p->pixbuf_render_geom.width != bg_applier->p->render_geom.width ||
		    bg_applier->p->pixbuf_render_geom.height != bg_applier->p->render_geom.height)
		{
			gboolean success;

#if 0
			gdk_color_alloc (gdk_window_get_colormap (bg_applier->p->root_window), prefs->color1);
#else
			gdk_colormap_alloc_colors (gdk_drawable_get_colormap (bg_applier->p->root_window),
						   prefs->color1, 1, FALSE, TRUE, &success);
#endif

			gdk_gc_set_foreground (gc, prefs->color1);
			gdk_draw_rectangle (bg_applier->p->pixmap, gc, TRUE,
					    bg_applier->p->render_geom.x,
					    bg_applier->p->render_geom.y, 
					    bg_applier->p->render_geom.width,
					    bg_applier->p->render_geom.height);
		}

		gdk_pixbuf_render_to_drawable
			(bg_applier->p->pixbuf,
			 bg_applier->p->pixmap, gc,
			 bg_applier->p->pixbuf_xlate.x,
			 bg_applier->p->pixbuf_xlate.y,
			 bg_applier->p->pixbuf_render_geom.x,
			 bg_applier->p->pixbuf_render_geom.y,
			 bg_applier->p->pixbuf_render_geom.width,
			 bg_applier->p->pixbuf_render_geom.height,
			 GDK_RGB_DITHER_MAX, 0, 0);
	} else {
		if (bg_applier->p->type == BG_APPLIER_ROOT) {
			gboolean success;

#if 0
			gdk_color_alloc (gdk_window_get_colormap (bg_applier->p->root_window), prefs->color1);
#else
			gdk_colormap_alloc_colors (gdk_drawable_get_colormap (bg_applier->p->root_window),
						   prefs->color1, 1, FALSE, TRUE, &success);
#endif
			gdk_window_set_background (bg_applier->p->root_window, prefs->color1);
			gdk_window_clear (bg_applier->p->root_window);
		}
		else if (bg_applier->p->type == BG_APPLIER_PREVIEW) {
			gboolean success;

#if 0
			gdk_color_alloc (gdk_window_get_colormap (bg_applier->p->preview_widget->window), prefs->color1);
#else
			gdk_colormap_alloc_colors (gdk_drawable_get_colormap (bg_applier->p->root_window),
						   prefs->color1, 1, FALSE, TRUE, &success);
#endif

			if (bg_applier->p->type == BG_APPLIER_PREVIEW) {
				gdk_gc_set_foreground (gc, prefs->color1);
				gdk_draw_rectangle (bg_applier->p->pixmap, gc, TRUE,
						    bg_applier->p->render_geom.x,
						    bg_applier->p->render_geom.y, 
						    bg_applier->p->render_geom.width,
						    bg_applier->p->render_geom.height);
			}
			else if (bg_applier->p->type == BG_APPLIER_ROOT) {
				gdk_window_set_back_pixmap (bg_applier->p->root_window, NULL, FALSE);
				gdk_window_set_background (bg_applier->p->root_window, prefs->color1);
			}
		}
	}

	if (bg_applier->p->type == BG_APPLIER_ROOT && !bg_applier->p->pixmap_is_set &&
	    (prefs->wallpaper_enabled || prefs->gradient_enabled))
		set_root_pixmap (bg_applier->p->pixmap, bg_applier->p->screen);
	else if (bg_applier->p->type == BG_APPLIER_ROOT && !bg_applier->p->pixmap_is_set)
		set_root_pixmap (NULL, bg_applier->p->screen);

	g_object_unref (G_OBJECT (gc));
}

/* Create a pixmap that will replace the current root pixmap. This function has
 * no effect if the bg_applier is for the preview window
 */

static void
create_pixmap (BGApplier *bg_applier, const BGPreferences *prefs) 
{
	gint width, height;

	g_return_if_fail (bg_applier != NULL);
	g_return_if_fail (IS_BG_APPLIER (bg_applier));
	g_return_if_fail (prefs != NULL);
	g_return_if_fail (IS_BG_PREFERENCES (prefs));

	switch (bg_applier->p->type) {
	case BG_APPLIER_ROOT:
		if (prefs->gradient_enabled && !prefs->wallpaper_enabled) {
			width = bg_applier->p->grad_geom.x;
			height = bg_applier->p->grad_geom.y;
		} else {
			width = bg_applier->p->render_geom.width;
			height = bg_applier->p->render_geom.height;
		}

		bg_applier->p->pixmap = make_root_pixmap (bg_applier->p->screen, width, height);
		bg_applier->p->pixmap_is_set = FALSE;
		break;

	case BG_APPLIER_PREVIEW:
		bg_applier_get_preview_widget (bg_applier);

		if (!GTK_WIDGET_REALIZED (bg_applier->p->preview_widget))
			gtk_widget_realize (bg_applier->p->preview_widget);

#if 0
		bg_applier->p->pixmap = GTK_PIXMAP (bg_applier->p->preview_widget)->pixmap;
#else
		if (!bg_applier->p->pixmap)
			gtk_image_get_pixmap (GTK_IMAGE (bg_applier->p->preview_widget), &bg_applier->p->pixmap, NULL);
#endif
		bg_applier->p->pixmap_is_set = TRUE;
		break;
	}
}

/* Compute geometry information based on the wallpaper type. In particular,
 * determine where on the destination visual the wallpaper should be rendered
 * and where the data from the source pixbuf the image should be fetched.
 */

static void
get_geometry (wallpaper_type_t  wallpaper_type,
	      GdkPixbuf        *pixbuf,
	      GdkRectangle     *field_geom,
	      GdkRectangle     *virtual_geom,
	      GdkRectangle     *dest_geom,
	      GdkRectangle     *src_geom)
{
	gdouble asp, xfactor, yfactor;
	gint pwidth, pheight;
	gint st = 0;

	if (field_geom->width != virtual_geom->width)
		xfactor = (gdouble) field_geom->width / (gdouble) virtual_geom->width;
	else
		xfactor = 1.0;

	if (field_geom->height != virtual_geom->height)
		yfactor = (gdouble) field_geom->height / (gdouble) virtual_geom->height;
	else
		yfactor = 1.0;

	pwidth = gdk_pixbuf_get_width (pixbuf);
	pheight = gdk_pixbuf_get_height (pixbuf);

	switch (wallpaper_type) {
	case WPTYPE_TILED:
		src_geom->x = src_geom->y = 0;
		dest_geom->x = dest_geom->y = 0;

		src_geom->width = pwidth;
		src_geom->height = pheight;

		dest_geom->width = field_geom->width;
		dest_geom->height = field_geom->height;

		break;

	case WPTYPE_CENTERED:
		if (virtual_geom->width < pwidth) {
			src_geom->width = virtual_geom->width;
			src_geom->x = (pwidth - virtual_geom->width) / 2;
			dest_geom->width = field_geom->width;
			dest_geom->x = 0;
		} else {
			src_geom->width = pwidth;
			src_geom->x = 0;
			dest_geom->width = MIN ((gdouble) src_geom->width * xfactor, field_geom->width);
			dest_geom->x = (field_geom->width - dest_geom->width) / 2;
		}

		if (virtual_geom->height < pheight) {
			src_geom->height = virtual_geom->height;
			src_geom->y = (pheight - virtual_geom->height) / 2;
			dest_geom->height = field_geom->height;
			dest_geom->y = 0;
		} else {
			src_geom->height = pheight;
			src_geom->y = 0;
			dest_geom->height = MIN ((gdouble) src_geom->height * yfactor, field_geom->height);
			dest_geom->y = (field_geom->height - dest_geom->height) / 2;
		}

		break;

	case WPTYPE_SCALED:
		asp = (gdouble) pwidth / (gdouble) virtual_geom->width;

		if (asp < (gdouble) pheight / virtual_geom->height) {
			asp = (gdouble) pheight / (gdouble) virtual_geom->height;
			st = 1;
		}

		if (st) {
			dest_geom->width = pwidth / asp * xfactor;
			dest_geom->height = field_geom->height;
			dest_geom->x = (field_geom->width - dest_geom->width) / 2;
			dest_geom->y = 0;
		} else {
			dest_geom->height = pheight / asp * yfactor;
			dest_geom->width = field_geom->width;
			dest_geom->x = 0;
			dest_geom->y = (field_geom->height - dest_geom->height) / 2;
		}

		src_geom->x = src_geom->y = 0;
		src_geom->width = pwidth;
		src_geom->height = pheight;

		break;

	case WPTYPE_ZOOM:
		asp = (gdouble) pwidth / (gdouble) virtual_geom->width;

		if (asp > (gdouble) pheight / virtual_geom->height) {
			src_geom->width = pwidth * virtual_geom->height / pheight;
			src_geom->height = pheight;
			src_geom->x = (pwidth - src_geom->width) / 2;
			src_geom->y = 0;
		} else {
			src_geom->width = pwidth;
			src_geom->height = pheight * virtual_geom->width / pwidth;
			src_geom->x = 0;
			src_geom->y = (pheight - src_geom->height) / 2;
		}

		dest_geom->x = dest_geom->y = 0;
		dest_geom->width = field_geom->width;
		dest_geom->height = field_geom->height;

		break;

	case WPTYPE_STRETCHED:
		dest_geom->width = field_geom->width;
		dest_geom->height = field_geom->height;
		dest_geom->x = 0;
		dest_geom->y = 0;
		src_geom->x = src_geom->y = 0;
		src_geom->width = pwidth;
		src_geom->height = pheight;
		break;
	default:
		g_error ("Bad wallpaper type");
		break;
	}
}

/* Place one pixbuf onto another, compositing and scaling as necessary */

static GdkPixbuf *
place_pixbuf (GdkPixbuf *dest_pixbuf,
	      GdkPixbuf *src_pixbuf,
	      GdkRectangle *dest_geom,
	      GdkRectangle *src_geom,
	      guint alpha,
	      GdkColor *bg_color) 
{
	gboolean need_composite;
	gboolean need_scaling;
	gdouble scale_x, scale_y;
	gint real_dest_x, real_dest_y;
	guint colorv;

	need_composite = (alpha < 255 || gdk_pixbuf_get_has_alpha (src_pixbuf));
	need_scaling = ((dest_geom->width != src_geom->width) || (dest_geom->height != src_geom->height));

	if (need_scaling) {
		scale_x = (gdouble) dest_geom->width / (gdouble) src_geom->width;
		scale_y = (gdouble) dest_geom->height / (gdouble) src_geom->height;
	} else {
		scale_x = scale_y = 1.0;
	}

	if (need_composite && dest_pixbuf != NULL) {
		gdk_pixbuf_composite
			(src_pixbuf, dest_pixbuf,
			 dest_geom->x, dest_geom->y,
			 dest_geom->width, 
			 dest_geom->height,
			 dest_geom->x - src_geom->x * scale_x,
			 dest_geom->y - src_geom->y * scale_y,
			 scale_x, scale_y,
			 GDK_INTERP_BILINEAR,
			 alpha);
	}
	else if (need_composite) {
		dest_pixbuf = gdk_pixbuf_new
			(GDK_COLORSPACE_RGB, FALSE, 8,
			 dest_geom->width, dest_geom->height);

		colorv = ((bg_color->red & 0xff00) << 8) |
			(bg_color->green & 0xff00) |
			((bg_color->blue & 0xff00) >> 8);

		gdk_pixbuf_composite_color 
			(src_pixbuf, dest_pixbuf,
			 0, 0,
			 dest_geom->width, 
			 dest_geom->height,
			 -src_geom->x * scale_x,
			 -src_geom->y * scale_y,
			 scale_x, scale_y,
			 GDK_INTERP_BILINEAR,
			 alpha, 0, 0, 65536,
			 colorv, colorv);
	}
	else if (need_scaling) {
		if (dest_pixbuf == NULL) {
			dest_pixbuf = gdk_pixbuf_new
				(GDK_COLORSPACE_RGB, FALSE, 8,
				 dest_geom->width, dest_geom->height);
			real_dest_x = real_dest_y = 0;
		} else {
			real_dest_x = dest_geom->x;
			real_dest_y = dest_geom->y;
		}

		gdk_pixbuf_scale 
			(src_pixbuf, dest_pixbuf,
			 real_dest_x, real_dest_y,
			 dest_geom->width,
			 dest_geom->height,
			 real_dest_x - src_geom->x * scale_x,
			 real_dest_y - src_geom->y * scale_y,
			 scale_x, scale_y,
			 GDK_INTERP_BILINEAR);
	}
	else if (dest_pixbuf != NULL) {
		gdk_pixbuf_copy_area
			(src_pixbuf,
			 src_geom->x, src_geom->y, 
			 src_geom->width,
			 src_geom->height,
			 dest_pixbuf,
			 dest_geom->x, dest_geom->y);
	} else {
		dest_pixbuf = src_pixbuf;
		g_object_ref (G_OBJECT (dest_pixbuf));
	}

	return dest_pixbuf;
}

/* Tile one pixbuf repeatedly onto another, compositing as necessary. Assumes
 * that the source pixbuf has already been scaled properly
 */

static GdkPixbuf *
tile_pixbuf (GdkPixbuf *dest_pixbuf,
	     GdkPixbuf *src_pixbuf,
	     GdkRectangle *field_geom,
	     guint alpha,
	     GdkColor *bg_color) 
{
	gboolean need_composite;
	gboolean use_simple;
	gdouble cx, cy;
	gdouble colorv;
	gint pwidth, pheight;

	need_composite = (alpha < 255 || gdk_pixbuf_get_has_alpha (src_pixbuf));
	use_simple = (dest_pixbuf == NULL);

	if (dest_pixbuf == NULL)
		dest_pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, field_geom->width, field_geom->height);

	if (need_composite && use_simple)
		colorv = ((bg_color->red & 0xff00) << 8) |
			(bg_color->green & 0xff00) |
			((bg_color->blue & 0xff00) >> 8);
	else
		colorv = 0;

	pwidth = gdk_pixbuf_get_width (src_pixbuf);
	pheight = gdk_pixbuf_get_height (src_pixbuf);

	for (cy = 0; cy < field_geom->height; cy += pheight) {
		for (cx = 0; cx < field_geom->width; cx += pwidth) {
			if (need_composite && !use_simple)
				gdk_pixbuf_composite
					(src_pixbuf, dest_pixbuf,
					 cx, cy,
					 MIN (pwidth, field_geom->width - cx), 
					 MIN (pheight, field_geom->height - cy),
					 cx, cy,
					 1.0, 1.0,
					 GDK_INTERP_BILINEAR,
					 alpha);
			else if (need_composite && use_simple)
				gdk_pixbuf_composite_color
					(src_pixbuf, dest_pixbuf,
					 cx, cy,
					 MIN (pwidth, field_geom->width - cx), 
					 MIN (pheight, field_geom->height - cy),
					 cx, cy,
					 1.0, 1.0,
					 GDK_INTERP_BILINEAR,
					 alpha,
					 65536, 65536, 65536,
					 colorv, colorv);
			else
				gdk_pixbuf_copy_area
					(src_pixbuf,
					 0, 0,
					 MIN (pwidth, field_geom->width - cx),
					 MIN (pheight, field_geom->height - cy),
					 dest_pixbuf,
					 cx, cy);
		}
	}

	return dest_pixbuf;
}

/* Fill a raw character array with gradient data; the data may then be imported
 * into a GdkPixbuf
 */

static void
fill_gradient (GdkPixbuf     *pixbuf,
	       GdkColor      *c1,
	       GdkColor      *c2,
	       orientation_t  orientation)
{
	int i, j;
	int dr, dg, db;
	int gs1;
	int vc = ((orientation == ORIENTATION_HORIZ) || (c1 == c2));
	int w = gdk_pixbuf_get_width (pixbuf);
	int h = gdk_pixbuf_get_height (pixbuf);
	guchar *b, *row;
	guchar *d = gdk_pixbuf_get_pixels (pixbuf);
	int rowstride = gdk_pixbuf_get_rowstride (pixbuf);

#define R1 c1->red
#define G1 c1->green
#define B1 c1->blue
#define R2 c2->red
#define G2 c2->green
#define B2 c2->blue

	dr = R2 - R1;
	dg = G2 - G1;
	db = B2 - B1;

	gs1 = (orientation == ORIENTATION_VERT) ? h-1 : w-1;

	row = g_new (unsigned char, rowstride);

	if (vc) {
		b = row;
		for (j = 0; j < w; j++) {
			*b++ = (R1 + (j * dr) / gs1) >> 8;
			*b++ = (G1 + (j * dg) / gs1) >> 8;
			*b++ = (B1 + (j * db) / gs1) >> 8;
		}
	}

	for (i = 0; i < h; i++) {
		if (!vc) {
			unsigned char cr, cg, cb;
			cr = (R1 + (i * dr) / gs1) >> 8;
			cg = (G1 + (i * dg) / gs1) >> 8;
			cb = (B1 + (i * db) / gs1) >> 8;
			b = row;
			for (j = 0; j < w; j++) {
				*b++ = cr;
				*b++ = cg;
				*b++ = cb;
			}
		}
		memcpy (d, row, w * 3);
		d += rowstride;
	}

#undef R1
#undef G1
#undef B1
#undef R2
#undef G2
#undef B2

	g_free (row);
}

/* Boolean predicates to assist optimization and rendering */

/* Return TRUE iff the wallpaper filename or enabled settings have changed
 * between old_prefs and new_prefs
 */

static gboolean
need_wallpaper_load_p (const BGApplier *bg_applier, const BGPreferences *prefs)
{
	if (bg_applier->p->last_prefs == NULL)
		return TRUE;
	else if (prefs->wallpaper_enabled && bg_applier->p->wallpaper_pixbuf == NULL)
		return TRUE;
	else if (bg_applier->p->last_prefs->wallpaper_enabled != prefs->wallpaper_enabled)
		return TRUE;
	else if (!bg_applier->p->last_prefs->wallpaper_enabled && !prefs->wallpaper_enabled)
		return FALSE;
	else if (strcmp (bg_applier->p->last_prefs->wallpaper_filename, prefs->wallpaper_filename))
		return TRUE;
	else if (bg_applier->p->last_prefs->wallpaper_type == prefs->wallpaper_type)
		return FALSE;
	else if (bg_applier->p->last_prefs->wallpaper_type != WPTYPE_TILED &&
		 bg_applier->p->last_prefs->wallpaper_type != WPTYPE_CENTERED)
		return TRUE;
	else if (prefs->wallpaper_type != WPTYPE_TILED &&
		 prefs->wallpaper_type != WPTYPE_CENTERED)
		return TRUE;
	else
		return FALSE;
}

/* Return TRUE iff we need to create a new root pixmap */

static gboolean
need_root_pixmap_p (const BGApplier *bg_applier, const BGPreferences *prefs) 
{
	if (bg_applier->p->pixmap == NULL)
		return TRUE;
	else if (prefs->wallpaper_enabled == FALSE && prefs->gradient_enabled == FALSE)
		return FALSE;
	else if (bg_applier->p->last_prefs == NULL)
		return TRUE;
	else if (bg_applier->p->last_prefs->wallpaper_enabled == FALSE && bg_applier->p->last_prefs->gradient_enabled == FALSE)
		return TRUE;
	else if (render_small_pixmap_p (bg_applier->p->last_prefs) != render_small_pixmap_p (prefs))
		return TRUE;
	else if (!render_small_pixmap_p (bg_applier->p->last_prefs) &&
		 !render_small_pixmap_p (prefs))
		return FALSE;
	else if (bg_applier->p->last_prefs->orientation != prefs->orientation)
		return TRUE;
	else
		return FALSE;
}

/* Return TRUE iff the colors are equal */

/* Return TRUE iff the wallpaper completely covers the colors in the given
 * bg_preferences structure, assuming we have already loaded the wallpaper pixbuf */

static gboolean
wallpaper_full_cover_p (const BGApplier *bg_applier, const BGPreferences *prefs) 
{
	gint swidth, sheight;
	gint pwidth, pheight;
	gdouble asp1, asp2;

	if (bg_applier->p->wallpaper_pixbuf == NULL)
		return FALSE;
	else if (gdk_pixbuf_get_has_alpha (bg_applier->p->wallpaper_pixbuf))
		return FALSE;
	else if (prefs->wallpaper_type == WPTYPE_TILED)
		return TRUE;
	else if (prefs->wallpaper_type == WPTYPE_STRETCHED)
		return TRUE;

	gdk_drawable_get_size (bg_applier->p->root_window, &swidth, &sheight);
	pwidth = gdk_pixbuf_get_width (bg_applier->p->wallpaper_pixbuf);
	pheight = gdk_pixbuf_get_height (bg_applier->p->wallpaper_pixbuf);

	if (prefs->wallpaper_type == WPTYPE_CENTERED) {
		if (pwidth >= swidth && pheight >= sheight)
			return TRUE;
		else
			return FALSE;
	}
	else if (prefs->wallpaper_type == WPTYPE_SCALED) {
		asp1 = (gdouble) swidth / (gdouble) sheight;
		asp2 = (gdouble) pwidth / (gdouble) pheight;

		if (swidth * (asp1 - asp2) < 1 && swidth * (asp2 - asp1) < 1)
			return TRUE;
		else
			return FALSE;
	}

	return FALSE;
}

/* Return TRUE if we can optimize the rendering by using a small thin pixmap */

static gboolean
render_small_pixmap_p (const BGPreferences *prefs) 
{
	return prefs->gradient_enabled && !prefs->wallpaper_enabled;
}

/* Create a persistent pixmap. We create a separate display
 * and set the closedown mode on it to RetainPermanent
 */
static GdkPixmap *
make_root_pixmap (GdkScreen *screen, gint width, gint height)
{
	Display *display;
	char *display_name;
	Pixmap result;
	GdkPixmap *gdk_pixmap;
	int screen_num;

	screen_num = gdk_screen_get_number (screen);

	gdk_flush ();

	display_name = DisplayString (GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()));

	display = XOpenDisplay (display_name);

        if (display == NULL) {
                g_warning ("Unable to open display '%s' when setting background pixmap\n",
                           (display_name) ? display_name : "NULL");
                return NULL;
        }

	XSetCloseDownMode (display, RetainPermanent);

	result = XCreatePixmap (display,
				RootWindow (display, screen_num),
				width, height,
				DefaultDepth (display, screen_num));

	XCloseDisplay (display);

	gdk_pixmap = gdk_pixmap_foreign_new (result);
	gdk_drawable_set_colormap (GDK_DRAWABLE (gdk_pixmap),
				   gdk_drawable_get_colormap (gdk_screen_get_root_window (screen)));

	return gdk_pixmap;
}

/* Set the root pixmap, and properties pointing to it. We
 * do this atomically with XGrabServer to make sure that
 * we won't leak the pixmap if somebody else it setting
 * it at the same time. (This assumes that they follow the
 * same conventions we do)
 */

static void 
set_root_pixmap (GdkPixmap *pixmap, GdkScreen *screen) 
{
	Atom type;
	gulong nitems, bytes_after;
	gint format;
	guchar *data_esetroot;
	Pixmap pixmap_id;
	Display *display;
	int screen_num;

	/* Final check to see if nautilus is running. If it is, we don't
	   touch the root pixmap at all. */
	if (is_nautilus_running ())
		return;

	screen_num = gdk_screen_get_number (screen);

	if (pixmap != NULL)
		pixmap_id = GDK_WINDOW_XWINDOW (pixmap);
	else
		pixmap_id = 0;

	display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());

	XGrabServer (display);

	XGetWindowProperty (display, RootWindow (display, screen_num),
			    XInternAtom (display, "ESETROOT_PMAP_ID", False),
			    0L, 1L, False, XA_PIXMAP,
			    &type, &format, &nitems, &bytes_after,
			    &data_esetroot);

	if (type == XA_PIXMAP) {
		if (format == 32 && nitems == 1) {
			Pixmap old_pixmap;

			old_pixmap = *((Pixmap *) data_esetroot);

			if (pixmap != NULL && old_pixmap != pixmap_id)
				XKillClient (display, old_pixmap);
			else if (pixmap == NULL)
				pixmap_id = old_pixmap;
		}

		XFree (data_esetroot);
	}

	if (pixmap != NULL) {
		XChangeProperty (display, RootWindow (display, screen_num),
				 XInternAtom (display, "ESETROOT_PMAP_ID", FALSE),
				 XA_PIXMAP, 32, PropModeReplace,
				 (guchar *) &pixmap_id, 1);
		XChangeProperty (display, RootWindow (display, screen_num),
				 XInternAtom (display, "_XROOTPMAP_ID", FALSE),
				 XA_PIXMAP, 32, PropModeReplace,
				 (guchar *) &pixmap_id, 1);

		XSetWindowBackgroundPixmap (display, RootWindow (display, screen_num),
					    pixmap_id);
	} else if (pixmap == NULL) {
		XDeleteProperty (display, RootWindow (display, screen_num),
				 XInternAtom (display, "ESETROOT_PMAP_ID", FALSE));
		XDeleteProperty (display, RootWindow (display, screen_num),
				 XInternAtom (display, "_XROOTPMAP_ID", FALSE));
	}

	XClearWindow (display, RootWindow (display, screen_num));
	XUngrabServer (display);
	XFlush (display);
}

static gboolean
is_nautilus_running (void)
{
	Atom window_id_atom;
	Window nautilus_xid;
	Atom actual_type;
	int actual_format;
	unsigned long nitems, bytes_after;
	unsigned char *data;
	int retval;
	Atom wmclass_atom;
	gboolean running;
	gint error;

	window_id_atom = XInternAtom (GDK_DISPLAY (), 
				      "NAUTILUS_DESKTOP_WINDOW_ID", True);

	if (window_id_atom == None) return FALSE;

	retval = XGetWindowProperty (GDK_DISPLAY (), GDK_ROOT_WINDOW (),
				     window_id_atom, 0, 1, False, XA_WINDOW,
				     &actual_type, &actual_format, &nitems,
				     &bytes_after, &data);

	if (data != NULL) {
		nautilus_xid = *(Window *) data;
		XFree (data);
	} else {
		return FALSE;
	}

	if (actual_type != XA_WINDOW) return FALSE;
	if (actual_format != 32) return FALSE;

	wmclass_atom = XInternAtom (GDK_DISPLAY (), "WM_CLASS", False);

	gdk_error_trap_push ();

	retval = XGetWindowProperty (GDK_DISPLAY (), nautilus_xid,
				     wmclass_atom, 0, 24, False, XA_STRING,
				     &actual_type, &actual_format, &nitems,
				     &bytes_after, &data);

	error = gdk_error_trap_pop ();

	if (error == BadWindow) return FALSE;

	if (actual_type == XA_STRING &&
	    nitems == 24 &&
	    bytes_after == 0 &&
	    actual_format == 8 &&
	    data != NULL &&
	    !strcmp ((char *)data, "desktop_window") &&
	    !strcmp ((char *)data + strlen ((char *)data) + 1, "Nautilus"))
		running = TRUE;
	else
		running = FALSE;

	if (data != NULL)
		XFree (data);

	return running;
}

static gboolean
cleanup_cb (BGApplier *bg_applier)
{
	g_message ("cleanup_cb: Enter");

	if (bg_applier->p->wallpaper_pixbuf != NULL) {
		g_object_unref (G_OBJECT (bg_applier->p->wallpaper_pixbuf));
		bg_applier->p->wallpaper_pixbuf = NULL;	
	}

	if (bg_applier->p->pixbuf != NULL) {
		g_object_unref (G_OBJECT (bg_applier->p->pixbuf));
		bg_applier->p->pixbuf = NULL;
	}

	bg_applier->p->timeout = 0;
	
	return FALSE;
}

static void
preview_realized_cb (GtkWidget *preview, BGApplier *bg_applier)
{
	GdkPixmap *pixmap;

	/* Only draw clean image if no pref set yet */
	if (bg_applier->p->last_prefs)
		return;       

	gtk_image_get_pixmap (GTK_IMAGE (preview), &pixmap, NULL);
	if (!pixmap) {
		pixmap = gdk_pixmap_new (preview->window,
					 bg_applier->p->render_geom.width,
					 bg_applier->p->render_geom.height,
					 -1);
		gtk_image_set_from_pixmap (GTK_IMAGE (preview), pixmap, NULL);
	}
	
	gdk_draw_rectangle (pixmap,
			    preview->style->bg_gc[preview->state],
			    TRUE,
			    bg_applier->p->render_geom.x,
			    bg_applier->p->render_geom.y,
			    bg_applier->p->render_geom.width,
			    bg_applier->p->render_geom.height);
}

