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

   eel-background.c: Object for the background of a widget.

   Copyright (C) 2000 Eazel, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library 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.

   Author: Darin Adler <darin@eazel.com>
*/

#include <config.h>
#include "eel-background.h"
#include "eel-gdk-extensions.h"
#include "eel-gdk-pixbuf-extensions.h"
#include "eel-glib-extensions.h"
#include "eel-mate-extensions.h"
#include "eel-gtk-macros.h"
#include "eel-lib-self-check-functions.h"
#include "eel-string.h"
#include "eel-marshal.h"
#include "eel-types.h"
#include "eel-type-builtins.h"
#include <gtk/gtk.h>
#include <eel/eel-canvas.h>
#include <eel/eel-canvas-util.h>
#include <gdk/gdkx.h>
#include <gio/gio.h>
#include <math.h>
#include <stdio.h>
#define MATE_DESKTOP_USE_UNSTABLE_API
#include <libmateui/mate-bg.h>

static void       eel_background_class_init                (gpointer       klass);
static void       eel_background_init                      (gpointer       object,
        gpointer       klass);
static void       eel_background_finalize                  (GObject       *object);
static GdkPixmap *eel_background_get_pixmap_and_color      (EelBackground *background,
        GdkWindow     *window,
        GdkColor      *color);

static void set_image_properties (EelBackground *background);

static void init_fade (EelBackground *background, GtkWidget *widget);
static void free_fade (EelBackground *background);

EEL_CLASS_BOILERPLATE (EelBackground, eel_background, GTK_TYPE_OBJECT)

enum
{
    APPEARANCE_CHANGED,
    SETTINGS_CHANGED,
    RESET,
    LAST_SIGNAL
};

/* This is the size of the GdkRGB dither matrix, in order to avoid
 * bad dithering when tiling the gradient
 */
#define GRADIENT_PIXMAP_TILE_SIZE 128

static guint signals[LAST_SIGNAL];

struct EelBackgroundDetails
{
    char *color;

    MateBG *bg;
    GtkWidget *widget;

    /* Realized data: */
    GdkPixmap *background_pixmap;
    gboolean background_pixmap_is_unset_root_pixmap;
    MateBGCrossfade *fade;
    int background_entire_width;
    int background_entire_height;
    GdkColor default_color;

    gboolean use_base;

    /* Is this background attached to desktop window */
    gboolean is_desktop;
    /* Desktop screen size watcher */
    gulong screen_size_handler;
    /* Desktop monitors configuration watcher */
    gulong screen_monitors_handler;
    /* Can we use common pixmap for root window and desktop window */
    gboolean use_common_pixmap;
    guint change_idle_id;

    /* activity status */
    gboolean is_active;
};

static void
eel_background_class_init (gpointer klass)
{
    GObjectClass *object_class;

    object_class = G_OBJECT_CLASS (klass);

    eel_type_init ();

    signals[APPEARANCE_CHANGED] =
        g_signal_new ("appearance_changed",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
                      G_STRUCT_OFFSET (EelBackgroundClass,
                                       appearance_changed),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE,
                      0);
    signals[SETTINGS_CHANGED] =
        g_signal_new ("settings_changed",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
                      G_STRUCT_OFFSET (EelBackgroundClass,
                                       settings_changed),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__INT,
                      G_TYPE_NONE,
                      1, G_TYPE_INT);
    signals[RESET] =
        g_signal_new ("reset",
                      G_TYPE_FROM_CLASS (object_class),
                      G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE,
                      G_STRUCT_OFFSET (EelBackgroundClass,
                                       reset),
                      NULL, NULL,
                      g_cclosure_marshal_VOID__VOID,
                      G_TYPE_NONE,
                      0);

    object_class->finalize = eel_background_finalize;
}

static void
on_bg_changed (MateBG *bg, EelBackground *background)
{
    init_fade (background, background->details->widget);
    g_signal_emit (G_OBJECT (background),
                   signals[APPEARANCE_CHANGED], 0);
}

static void
on_bg_transitioned (MateBG *bg, EelBackground *background)
{
    free_fade (background);
    g_signal_emit (G_OBJECT (background),
                   signals[APPEARANCE_CHANGED], 0);
}

static void
eel_background_init (gpointer object, gpointer klass)
{
    EelBackground *background;

    background = EEL_BACKGROUND (object);

    background->details = g_new0 (EelBackgroundDetails, 1);
    background->details->default_color.red = 0xffff;
    background->details->default_color.green = 0xffff;
    background->details->default_color.blue = 0xffff;
    background->details->bg = mate_bg_new ();
    background->details->is_active = TRUE;

    g_signal_connect (background->details->bg, "changed",
                      G_CALLBACK (on_bg_changed), background);
    g_signal_connect (background->details->bg, "transitioned",
                      G_CALLBACK (on_bg_transitioned), background);

}

/* The safe way to clear an image from a background is:
 * 		eel_background_set_image_uri (NULL);
 * This fn is a private utility - it does NOT clear
 * the details->bg_uri setting.
 */
static void
eel_background_remove_current_image (EelBackground *background)
{
    if (background->details->bg != NULL)
    {
        g_object_unref (G_OBJECT (background->details->bg));
        background->details->bg = NULL;
    }
}

static void
free_fade (EelBackground *background)
{
    if (background->details->fade != NULL)
    {
        g_object_unref (background->details->fade);
        background->details->fade = NULL;
    }
}

static void
free_background_pixmap (EelBackground *background)
{
    GdkDisplay *display;
    GdkPixmap *pixmap;

    pixmap = background->details->background_pixmap;
    if (pixmap != NULL)
    {
        /* If we created a root pixmap and didn't set it as background
           it will live forever, so we need to kill it manually.
           If set as root background it will be killed next time the
           background is changed. */
        if (background->details->background_pixmap_is_unset_root_pixmap)
        {
            display = gdk_drawable_get_display (GDK_DRAWABLE (pixmap));
            XKillClient (GDK_DISPLAY_XDISPLAY (display),
                         GDK_PIXMAP_XID (pixmap));
        }
        g_object_unref (pixmap);
        background->details->background_pixmap = NULL;
    }
}


static void
eel_background_finalize (GObject *object)
{
    EelBackground *background;

    background = EEL_BACKGROUND (object);

    g_free (background->details->color);
    eel_background_remove_current_image (background);

    free_background_pixmap (background);

    free_fade (background);

    g_free (background->details);

    EEL_CALL_PARENT (G_OBJECT_CLASS, finalize, (object));
}

static EelBackgroundImagePlacement
placement_mate_to_eel (MateBGPlacement p)
{
    switch (p)
    {
    case MATE_BG_PLACEMENT_CENTERED:
        return EEL_BACKGROUND_CENTERED;
    case MATE_BG_PLACEMENT_FILL_SCREEN:
        return EEL_BACKGROUND_SCALED;
    case MATE_BG_PLACEMENT_SCALED:
        return EEL_BACKGROUND_SCALED_ASPECT;
    case MATE_BG_PLACEMENT_ZOOMED:
        return EEL_BACKGROUND_ZOOM;
    case MATE_BG_PLACEMENT_TILED:
        return EEL_BACKGROUND_TILED;
    case MATE_BG_PLACEMENT_SPANNED:
        return EEL_BACKGROUND_SPANNED;
    }

    return EEL_BACKGROUND_TILED;
}

static MateBGPlacement
placement_eel_to_mate (EelBackgroundImagePlacement p)
{
    switch (p)
    {
    case EEL_BACKGROUND_CENTERED:
        return MATE_BG_PLACEMENT_CENTERED;
    case EEL_BACKGROUND_SCALED:
        return MATE_BG_PLACEMENT_FILL_SCREEN;
    case EEL_BACKGROUND_SCALED_ASPECT:
        return MATE_BG_PLACEMENT_SCALED;
    case EEL_BACKGROUND_ZOOM:
        return MATE_BG_PLACEMENT_ZOOMED;
    case EEL_BACKGROUND_TILED:
        return MATE_BG_PLACEMENT_TILED;
    case EEL_BACKGROUND_SPANNED:
        return MATE_BG_PLACEMENT_SPANNED;
    }

    return MATE_BG_PLACEMENT_TILED;
}

EelBackgroundImagePlacement
eel_background_get_image_placement (EelBackground *background)
{
    g_return_val_if_fail (EEL_IS_BACKGROUND (background), EEL_BACKGROUND_TILED);

    return placement_mate_to_eel (mate_bg_get_placement (background->details->bg));
}

void
eel_background_set_image_placement (EelBackground              *background,
                                    EelBackgroundImagePlacement new_placement)
{
    g_return_if_fail (EEL_IS_BACKGROUND (background));

    mate_bg_set_placement (background->details->bg,
                           placement_eel_to_mate (new_placement));
}

EelBackground *
eel_background_new (void)
{
    return EEL_BACKGROUND (g_object_new (EEL_TYPE_BACKGROUND, NULL));
}

static void
eel_background_unrealize (EelBackground *background)
{
    free_background_pixmap (background);

    background->details->background_entire_width = 0;
    background->details->background_entire_height = 0;
    background->details->default_color.red = 0xffff;
    background->details->default_color.green = 0xffff;
    background->details->default_color.blue = 0xffff;
}

static void
drawable_get_adjusted_size (EelBackground *background,
                            GdkDrawable   *drawable,
                            int		  *width,
                            int	          *height)
{
    GdkScreen *screen;

    /*
     * Screen resolution change makes root drawable have incorrect size.
     */
#if GTK_CHECK_VERSION(3, 0, 0)
    width = gdk_window_get_width(GDK_WINDOW(drawable));
    height = gdk_window_get_height(GDK_WINDOW(drawable));
#else
    gdk_drawable_get_size(drawable, width, height); // FIXME: this is right?
#endif


    if (background->details->is_desktop)
    {
        screen = gdk_drawable_get_screen (drawable);
        *width = gdk_screen_get_width (screen);
        *height = gdk_screen_get_height (screen);
    }
}

static gboolean
eel_background_ensure_realized (EelBackground *background, GdkWindow *window)
{
    gpointer data;
    GtkWidget *widget;
    GtkStyle *style;
    gboolean changed;
    int entire_width;
    int entire_height;

    drawable_get_adjusted_size (background, window, &entire_width, &entire_height);

    /* Set the default color */

    /* Get the widget to which the window belongs and its style as well */
    gdk_window_get_user_data (window, &data);
    widget = GTK_WIDGET (data);
    if (widget != NULL)
    {
        style = gtk_widget_get_style (widget);
        if (background->details->use_base)
        {
            background->details->default_color = style->base[GTK_STATE_NORMAL];
        }
        else
        {
            background->details->default_color = style->bg[GTK_STATE_NORMAL];
        }

        gdk_rgb_find_color (style->colormap, &(background->details->default_color));
    }

    /* If the window size is the same as last time, don't update */
    if (entire_width == background->details->background_entire_width &&
            entire_height == background->details->background_entire_height)
    {
        return FALSE;
    }

    free_background_pixmap (background);

    changed = FALSE;

    set_image_properties (background);

    background->details->background_pixmap = mate_bg_create_pixmap (background->details->bg,
            window,
            entire_width, entire_height,
            background->details->is_desktop);
    background->details->background_pixmap_is_unset_root_pixmap = background->details->is_desktop;

    /* We got the pixmap and everything, so we don't care about a change
       that is pending (unless things actually change after this time) */
    g_object_set_data (G_OBJECT (background->details->bg),
                       "ignore-pending-change", GINT_TO_POINTER (TRUE));
    changed = TRUE;


    background->details->background_entire_width = entire_width;
    background->details->background_entire_height = entire_height;

    return changed;
}

#define CLAMP_COLOR(v) (t = (v), CLAMP (t, 0, G_MAXUSHORT))
#define SATURATE(v) ((1.0 - saturation) * intensity + saturation * (v))

static void
make_color_inactive (EelBackground *background, GdkColor *color)
{
    double intensity, saturation;
    gushort t;

    if (!background->details->is_active)
    {
        saturation = 0.7;
        intensity = color->red * 0.30 + color->green * 0.59 + color->blue * 0.11;
        color->red = SATURATE (color->red);
        color->green = SATURATE (color->green);
        color->blue = SATURATE (color->blue);

        if (intensity > G_MAXUSHORT / 2)
        {
            color->red *= 0.9;
            color->green *= 0.9;
            color->blue *= 0.9;
        }
        else
        {
            color->red *= 1.25;
            color->green *= 1.25;
            color->blue *= 1.25;
        }

        color->red = CLAMP_COLOR (color->red);
        color->green = CLAMP_COLOR (color->green);
        color->blue = CLAMP_COLOR (color->blue);
    }
}

static GdkPixmap *
eel_background_get_pixmap_and_color (EelBackground *background,
                                     GdkWindow     *window,
                                     GdkColor      *color)
{
    int entire_width;
    int entire_height;

    drawable_get_adjusted_size (background, window, &entire_width, &entire_height);

    eel_background_ensure_realized (background, window);

    *color = background->details->default_color;
    make_color_inactive (background, color);

    if (background->details->background_pixmap != NULL)
    {
        return g_object_ref (background->details->background_pixmap);
    }
    return NULL;
}

void
eel_background_expose (GtkWidget                   *widget,
                       GdkEventExpose              *event)
{
    GdkColor color;
    int window_width;
    int window_height;
    GdkPixmap *pixmap;
    GdkGC *gc;
    GdkGCValues gc_values;
    GdkGCValuesMask value_mask;
    GdkWindow *widget_window;

    EelBackground *background;

    widget_window = gtk_widget_get_window (widget);
    if (event->window != widget_window)
    {
        return;
    }

    background = eel_get_widget_background (widget);

    drawable_get_adjusted_size (background, widget_window, &window_width, &window_height);

    pixmap = eel_background_get_pixmap_and_color (background,
             widget_window,
             &color);

    if (pixmap)
    {
        gc_values.tile = pixmap;
        gc_values.ts_x_origin = 0;
        gc_values.ts_y_origin = 0;
        gc_values.fill = GDK_TILED;
        value_mask = GDK_GC_FILL | GDK_GC_TILE | GDK_GC_TS_X_ORIGIN | GDK_GC_TS_Y_ORIGIN;
    }
    else
    {
        gdk_rgb_find_color (gtk_widget_get_colormap (widget), &color);
        gc_values.foreground = color;
        gc_values.fill = GDK_SOLID;
        value_mask = GDK_GC_FILL | GDK_GC_FOREGROUND;
    }

    gc = gdk_gc_new_with_values (widget_window, &gc_values, value_mask);

    gdk_gc_set_clip_rectangle (gc, &event->area);

    gdk_draw_rectangle (widget_window, gc, TRUE, 0, 0, window_width, window_height);

    g_object_unref (gc);

    if (pixmap)
    {
        g_object_unref (pixmap);
    }
}

static void
set_image_properties (EelBackground *background)
{
    GdkColor c;
    if (!background->details->color)
    {
        c = background->details->default_color;
        make_color_inactive (background, &c);
        mate_bg_set_color (background->details->bg, MATE_BG_COLOR_SOLID,
                           &c, NULL);
    }
    else if (!eel_gradient_is_gradient (background->details->color))
    {
        eel_gdk_color_parse_with_white_default (background->details->color, &c);
        make_color_inactive (background, &c);
        mate_bg_set_color (background->details->bg, MATE_BG_COLOR_SOLID, &c, NULL);
    }
    else
    {
        GdkColor c1;
        GdkColor c2;
        char *spec;

        spec = eel_gradient_get_start_color_spec (background->details->color);
        eel_gdk_color_parse_with_white_default (spec, &c1);
        make_color_inactive (background, &c1);
        g_free (spec);

        spec = eel_gradient_get_end_color_spec (background->details->color);
        eel_gdk_color_parse_with_white_default (spec, &c2);
        make_color_inactive (background, &c2);
        g_free (spec);

        if (eel_gradient_is_horizontal (background->details->color))
            mate_bg_set_color (background->details->bg, MATE_BG_COLOR_H_GRADIENT, &c1, &c2);
        else
            mate_bg_set_color (background->details->bg, MATE_BG_COLOR_V_GRADIENT, &c1, &c2);

    }
}

char *
eel_background_get_color (EelBackground *background)
{
    g_return_val_if_fail (EEL_IS_BACKGROUND (background), NULL);

    return g_strdup (background->details->color);
}

char *
eel_background_get_image_uri (EelBackground *background)
{
    const char *filename;

    g_return_val_if_fail (EEL_IS_BACKGROUND (background), NULL);

    filename = mate_bg_get_filename (background->details->bg);
    if (filename)
    {
        return g_filename_to_uri (filename, NULL, NULL);
    }
    return NULL;
}

/* Use style->base as the default color instead of bg */
void
eel_background_set_use_base (EelBackground *background,
                             gboolean use_base)
{
    background->details->use_base = use_base;
}

void
eel_background_set_color (EelBackground *background,
                          const char *color)
{
    if (eel_strcmp (background->details->color, color) != 0)
    {
        g_free (background->details->color);
        background->details->color = g_strdup (color);

        set_image_properties (background);
    }
}

static gboolean
eel_background_set_image_uri_helper (EelBackground *background,
                                     const char *image_uri,
                                     gboolean emit_signal)
{
    char *filename;

    if (image_uri != NULL)
    {
        filename = g_filename_from_uri (image_uri, NULL, NULL);
    }
    else
    {
        filename = NULL;
    }

    mate_bg_set_filename (background->details->bg, filename);

    if (emit_signal)
    {
        g_signal_emit (GTK_OBJECT (background), signals[SETTINGS_CHANGED], 0, GDK_ACTION_COPY);
    }

    set_image_properties (background);

    g_free (filename);

    return TRUE;
}

void
eel_background_set_image_uri (EelBackground *background, const char *image_uri)
{


    eel_background_set_image_uri_helper (background, image_uri, TRUE);
}

/* Use this fn to set both the image and color and avoid flash. The color isn't
 * changed till after the image is done loading, that way if an update occurs
 * before then, it will use the old color and image.
 */
static void
eel_background_set_image_uri_and_color (EelBackground *background, GdkDragAction action,
                                        const char *image_uri, const char *color)
{
    eel_background_set_image_uri_helper (background, image_uri, FALSE);
    eel_background_set_color (background, color);

    /* We always emit, even if the color didn't change, because the image change
     * relies on us doing it here.
     */

    g_signal_emit (background, signals[SETTINGS_CHANGED], 0, action);
}

void
eel_background_receive_dropped_background_image (EelBackground *background,
        GdkDragAction action,
        const char *image_uri)
{
    /* Currently, we only support tiled images. So we set the placement.
     * We rely on eel_background_set_image_uri_and_color to emit
     * the SETTINGS_CHANGED & APPEARANCE_CHANGE signals.
     */
    eel_background_set_image_placement (background, EEL_BACKGROUND_TILED);

    eel_background_set_image_uri_and_color (background, action, image_uri, NULL);
}

/**
 * eel_background_is_set:
 *
 * Check whether the background's color or image has been set.
 */
gboolean
eel_background_is_set (EelBackground *background)
{
    g_assert (EEL_IS_BACKGROUND (background));

    return background->details->color != NULL
           || mate_bg_get_filename (background->details->bg) != NULL;
}

/**
 * eel_background_reset:
 *
 * Emit the reset signal to forget any color or image that has been
 * set previously.
 */
void
eel_background_reset (EelBackground *background)
{
    g_return_if_fail (EEL_IS_BACKGROUND (background));

    g_signal_emit (GTK_OBJECT (background), signals[RESET], 0);
}

static void
set_root_pixmap (EelBackground *background,
                 GdkWindow     *window)
{
    GdkPixmap *pixmap, *root_pixmap;
    GdkScreen *screen;
    GdkColor color;

    pixmap = eel_background_get_pixmap_and_color (background,
             window,
             &color);
    screen = gdk_drawable_get_screen (window);

    if (background->details->use_common_pixmap)
    {
        background->details->background_pixmap_is_unset_root_pixmap = FALSE;
        root_pixmap = g_object_ref (pixmap);
    }
    else
    {
        root_pixmap = mate_bg_create_pixmap (background->details->bg, window,
                                             gdk_screen_get_width (screen), gdk_screen_get_height (screen), TRUE);
    }

    mate_bg_set_pixmap_as_root (screen, pixmap);

    g_object_unref (pixmap);
    g_object_unref (root_pixmap);
}

static gboolean
fade_to_pixmap (EelBackground *background,
                GdkWindow     *window,
                GdkPixmap     *pixmap)
{
    if (background->details->fade == NULL)
    {
        return FALSE;
    }

    if (!mate_bg_crossfade_set_end_pixmap (background->details->fade,
                                           pixmap))
    {
        return FALSE;
    }

    if (!mate_bg_crossfade_is_started (background->details->fade))
    {
        mate_bg_crossfade_start (background->details->fade, window);
        if (background->details->is_desktop)
        {
            g_signal_connect_swapped (background->details->fade,
                                      "finished",
                                      G_CALLBACK (set_root_pixmap), background);
        }
    }

    return mate_bg_crossfade_is_started (background->details->fade);
}


static void
eel_background_set_up_widget (EelBackground *background, GtkWidget *widget)
{
    GtkStyle *style;
    GdkPixmap *pixmap;
    GdkColor color;

    int window_width;
    int window_height;

    GdkWindow *window;
    GdkWindow *widget_window;
    gboolean in_fade;

    if (!gtk_widget_get_realized (widget))
    {
        return;
    }

    widget_window = gtk_widget_get_window (widget);
    drawable_get_adjusted_size (background, widget_window, &window_width, &window_height);

    pixmap = eel_background_get_pixmap_and_color (background,
             widget_window,
             &color);

    style = gtk_widget_get_style (widget);

    gdk_rgb_find_color (style->colormap, &color);

    if (EEL_IS_CANVAS (widget))
    {
        window = gtk_layout_get_bin_window (GTK_LAYOUT (widget));
    }
    else
    {
        window = widget_window;
    }

    if (background->details->fade != NULL)
    {
        in_fade = fade_to_pixmap (background, window, pixmap);
    }
    else
    {
        in_fade = FALSE;
    }

    if (!in_fade)
    {
        if (background->details->is_desktop)
        {
            gdk_window_set_back_pixmap (window, pixmap, FALSE);
        }
        else
        {
            gdk_window_set_background (window, &color);
            gdk_window_set_back_pixmap (window, pixmap, FALSE);
        }
    }

    if (background->details->is_desktop && !in_fade)
    {
        set_root_pixmap (background, window);
    }

    if (pixmap)
    {
        g_object_unref (pixmap);
    }
}

static gboolean
on_background_changed (EelBackground *background)
{
    if (background->details->change_idle_id == 0)
    {
        return FALSE;
    }

    background->details->change_idle_id = 0;

    eel_background_unrealize (background);
    eel_background_set_up_widget (background, background->details->widget);

    gtk_widget_queue_draw (background->details->widget);

    return FALSE;
}

static void
init_fade (EelBackground *background, GtkWidget *widget)
{
    if (widget == NULL || !gtk_widget_get_realized (widget))
        return;

    if (!background->details->is_desktop)
    {
        return;
    }

    if (background->details->fade == NULL)
    {
        GdkWindow *window;
        int old_width, old_height, width, height;

        /* If this was the result of a screen size change,
         * we don't want to crossfade
         */
        window = gtk_widget_get_window (widget);

#if GTK_CHECK_VERSION(3, 0, 0)
        old_width = gdk_window_get_width(GDK_WINDOW(window));
        old_height = gdk_window_get_height(GDK_WINDOW(window));
#else
        gdk_drawable_get_size(window, &old_width, &old_height);
#endif

        drawable_get_adjusted_size (background, window,
                                    &width, &height);
        if (old_width == width && old_height == height)
        {
            background->details->fade = mate_bg_crossfade_new (width, height);
            g_signal_connect_swapped (background->details->fade,
                                      "finished",
                                      G_CALLBACK (free_fade),
                                      background);
        }
    }

    if (background->details->fade != NULL && !mate_bg_crossfade_is_started (background->details->fade))
    {
        GdkPixmap *start_pixmap;

        if (background->details->background_pixmap == NULL)
        {
            start_pixmap = mate_bg_get_pixmap_from_root (gtk_widget_get_screen (widget));
        }
        else
        {
            start_pixmap = g_object_ref (background->details->background_pixmap);
        }
        mate_bg_crossfade_set_start_pixmap (background->details->fade,
                                            start_pixmap);
        g_object_unref (start_pixmap);
    }
}

static void
eel_widget_queue_background_change (GtkWidget *widget)
{
    EelBackground *background;

    background = eel_get_widget_background (widget);

    if (background->details->change_idle_id > 0)
    {
        return;
    }

    background->details->change_idle_id = g_idle_add ((GSourceFunc) on_background_changed, background);
}

/* Callback used when the style of a widget changes.  We have to regenerate its
 * EelBackgroundStyle so that it will match the chosen GTK+ theme.
 */
static void
widget_style_set_cb (GtkWidget *widget, GtkStyle *previous_style, gpointer data)
{
    EelBackground *background;

    background = EEL_BACKGROUND (data);

    if (previous_style != NULL)
    {
        eel_widget_queue_background_change (widget);
    }
}

static void
screen_size_changed (GdkScreen *screen, EelBackground *background)
{
    g_signal_emit (background, signals[APPEARANCE_CHANGED], 0);
}

static void
widget_realized_setup (GtkWidget *widget, gpointer data)
{
    EelBackground *background;

    background = EEL_BACKGROUND (data);

    if (background->details->is_desktop)
    {
        GdkWindow *root_window;
        GdkScreen *screen;

        screen = gtk_widget_get_screen (widget);

        if (background->details->screen_size_handler > 0)
        {
            g_signal_handler_disconnect (screen,
                                         background->details->screen_size_handler);
        }

        background->details->screen_size_handler =
            g_signal_connect (screen, "size_changed",
                              G_CALLBACK (screen_size_changed), background);
        if (background->details->screen_monitors_handler > 0)
        {
            g_signal_handler_disconnect (screen,
                                         background->details->screen_monitors_handler);
        }
        background->details->screen_monitors_handler =
            g_signal_connect (screen, "monitors-changed",
                              G_CALLBACK (screen_size_changed), background);

        root_window = gdk_screen_get_root_window(screen);

#if GTK_CHECK_VERSION(3, 0, 0)
        if (gdk_window_get_visual(root_window) == gtk_widget_get_visual(widget))
#else
        if (gdk_drawable_get_visual(root_window) == gtk_widget_get_visual(widget))
#endif
        {
            background->details->use_common_pixmap = TRUE;
        }
        else
        {
            background->details->use_common_pixmap = FALSE;
        }

        init_fade (background, widget);
    }
}

static void
widget_realize_cb (GtkWidget *widget, gpointer data)
{
    EelBackground *background;

    background = EEL_BACKGROUND (data);

    widget_realized_setup (widget, data);

    eel_background_set_up_widget (background, widget);
}

static void
widget_unrealize_cb (GtkWidget *widget, gpointer data)
{
    EelBackground *background;

    background = EEL_BACKGROUND (data);

    if (background->details->screen_size_handler > 0)
    {
        g_signal_handler_disconnect (gtk_widget_get_screen (GTK_WIDGET (widget)),
                                     background->details->screen_size_handler);
        background->details->screen_size_handler = 0;
    }
    if (background->details->screen_monitors_handler > 0)
    {
        g_signal_handler_disconnect (gtk_widget_get_screen (GTK_WIDGET (widget)),
                                     background->details->screen_monitors_handler);
        background->details->screen_monitors_handler = 0;
    }
    background->details->use_common_pixmap = FALSE;
}

void
eel_background_set_desktop (EelBackground *background, GtkWidget *widget, gboolean is_desktop)
{
    background->details->is_desktop = is_desktop;

    if (gtk_widget_get_realized (widget) && background->details->is_desktop)
    {
        widget_realized_setup (widget, background);
    }

}

gboolean
eel_background_is_desktop (EelBackground *background)
{
    return background->details->is_desktop;
}

static void
on_widget_destroyed (GtkWidget *widget, EelBackground *background)
{
    if (background->details->change_idle_id != 0)
    {
        g_source_remove (background->details->change_idle_id);
        background->details->change_idle_id = 0;
    }

    background->details->widget = NULL;
}

/* Gets the background attached to a widget.

   If the widget doesn't already have a EelBackground object,
   this will create one. To change the widget's background, you can
   just call eel_background methods on the widget.

   If the widget is a canvas, nothing more needs to be done.  For
   normal widgets, you need to call eel_background_expose() from your
   expose handler to draw the background.

   Later, we might want a call to find out if we already have a background,
   or a way to share the same background among multiple widgets; both would
   be straightforward.
*/
EelBackground *
eel_get_widget_background (GtkWidget *widget)
{
    gpointer data;
    EelBackground *background;

    g_return_val_if_fail (GTK_IS_WIDGET (widget), NULL);

    /* Check for an existing background. */
    data = g_object_get_data (G_OBJECT (widget), "eel_background");
    if (data != NULL)
    {
        g_assert (EEL_IS_BACKGROUND (data));
        return data;
    }

    /* Store the background in the widget's data. */
    background = eel_background_new ();
    g_object_ref_sink (background);
    g_object_set_data_full (G_OBJECT (widget), "eel_background",
                            background, g_object_unref);
    background->details->widget = widget;
    g_signal_connect_object (widget, "destroy", G_CALLBACK (on_widget_destroyed), background, 0);

    /* Arrange to get the signal whenever the background changes. */
    g_signal_connect_object (background, "appearance_changed",
                             G_CALLBACK (eel_widget_queue_background_change), widget, G_CONNECT_SWAPPED);
    eel_widget_queue_background_change (widget);

    g_signal_connect_object (widget, "style_set",
                             G_CALLBACK (widget_style_set_cb),
                             background,
                             0);
    g_signal_connect_object (widget, "realize",
                             G_CALLBACK (widget_realize_cb),
                             background,
                             0);
    g_signal_connect_object (widget, "unrealize",
                             G_CALLBACK (widget_unrealize_cb),
                             background,
                             0);

    return background;
}

/* determine if a background is darker or lighter than average, to help clients know what
   colors to draw on top with */
gboolean
eel_background_is_dark (EelBackground *background)
{
    GdkScreen *screen;
    GdkRectangle rect;

    /* only check for the background on the 0th monitor */
    screen = gdk_screen_get_default ();
    gdk_screen_get_monitor_geometry (screen, 0, &rect);

    return mate_bg_is_dark (background->details->bg, rect.width, rect.height);
}

/* handle dropped colors */
void
eel_background_receive_dropped_color (EelBackground *background,
                                      GtkWidget *widget,
                                      GdkDragAction action,
                                      int drop_location_x,
                                      int drop_location_y,
                                      const GtkSelectionData *selection_data)
{
    guint16 *channels;
    char *color_spec;
    char *new_gradient_spec;
    int left_border, right_border, top_border, bottom_border;
    GtkAllocation allocation;

    g_return_if_fail (EEL_IS_BACKGROUND (background));
    g_return_if_fail (GTK_IS_WIDGET (widget));
    g_return_if_fail (selection_data != NULL);

    /* Convert the selection data into a color spec. */
    if (gtk_selection_data_get_length ((GtkSelectionData *) selection_data) != 8 ||
            gtk_selection_data_get_format ((GtkSelectionData *) selection_data) != 16)
    {
        g_warning ("received invalid color data");
        return;
    }
    channels = (guint16 *) gtk_selection_data_get_data ((GtkSelectionData *) selection_data);
    color_spec = g_strdup_printf ("#%02X%02X%02X",
                                  channels[0] >> 8,
                                  channels[1] >> 8,
                                  channels[2] >> 8);

    /* Figure out if the color was dropped close enough to an edge to create a gradient.
       For the moment, this is hard-wired, but later the widget will have to have some
       say in where the borders are.
    */
    gtk_widget_get_allocation (widget, &allocation);
    left_border = 32;
    right_border = allocation.width - 32;
    top_border = 32;
    bottom_border = allocation.height - 32;
    if (drop_location_x < left_border && drop_location_x <= right_border)
    {
        new_gradient_spec = eel_gradient_set_left_color_spec (background->details->color, color_spec);
    }
    else if (drop_location_x >= left_border && drop_location_x > right_border)
    {
        new_gradient_spec = eel_gradient_set_right_color_spec (background->details->color, color_spec);
    }
    else if (drop_location_y < top_border && drop_location_y <= bottom_border)
    {
        new_gradient_spec = eel_gradient_set_top_color_spec (background->details->color, color_spec);
    }
    else if (drop_location_y >= top_border && drop_location_y > bottom_border)
    {
        new_gradient_spec = eel_gradient_set_bottom_color_spec (background->details->color, color_spec);
    }
    else
    {
        new_gradient_spec = g_strdup (color_spec);
    }

    g_free (color_spec);

    eel_background_set_image_uri_and_color (background, action, NULL, new_gradient_spec);

    g_free (new_gradient_spec);
}

void
eel_background_save_to_mateconf (EelBackground *background)
{
    MateConfClient *client = mateconf_client_get_default ();

    if (background->details->bg)
        mate_bg_save_to_preferences (background->details->bg, client);
}

void
eel_background_set_active (EelBackground *background,
                           gboolean is_active)
{
    if (background->details->is_active != is_active)
    {
        background->details->is_active = is_active;
        set_image_properties (background);
    }
}

/* self check code */

#if !defined (EEL_OMIT_SELF_CHECK)

void
eel_self_check_background (void)
{
    EelBackground *background;

    background = eel_background_new ();

    eel_background_set_color (background, NULL);
    eel_background_set_color (background, "");
    eel_background_set_color (background, "red");
    eel_background_set_color (background, "red-blue");
    eel_background_set_color (background, "red-blue:h");

    g_object_ref_sink (background);
    g_object_unref (background);
}

#endif
