[gnome-control-center/extensible-shell: 5/7] Add extension point support



commit bf072998d01973e41bcb0f7cfc7742b51d0ecea7
Author: William Jon McCann <jmccann redhat com>
Date:   Mon Jan 11 21:26:47 2010 -0500

    Add extension point support
    
    Keyboard and some of appearance capplet have been ported to it.

 capplets/appearance/Makefile.am                    |   73 +-
 capplets/appearance/appearance-desktop.c           |    5 +-
 capplets/appearance/appearance-main.c              |    2 +-
 capplets/appearance/appearance-module.c            |   42 +
 capplets/appearance/cc-appearance-panel.c          |  219 +++
 capplets/appearance/cc-appearance-panel.h          |   54 +
 capplets/appearance/cc-background-item.c           |  703 ++++++++
 capplets/appearance/cc-background-item.h           |   71 +
 capplets/appearance/cc-background-page.c           | 1811 ++++++++++++++++++++
 capplets/appearance/cc-background-page.h           |   55 +
 capplets/appearance/cc-backgrounds-monitor.c       |  714 ++++++++
 capplets/appearance/cc-backgrounds-monitor.h       |   69 +
 capplets/appearance/gnome-wp-item.c                |    3 -
 capplets/appearance/gnome-wp-item.h                |    1 -
 capplets/appearance/gnome-wp-xml.c                 |    2 +-
 capplets/keyboard/Makefile.am                      |   51 +-
 capplets/keyboard/cc-keyboard-panel.c              |  293 ++++
 capplets/keyboard/cc-keyboard-panel.h              |   54 +
 .../keyboard/gnome-keyboard-properties-dialog.ui   |    4 +-
 capplets/keyboard/keyboard-module.c                |   42 +
 po/POTFILES.in                                     |    5 +
 shell/Makefile.am                                  |    9 +-
 shell/cc-page.c                                    |  178 ++
 shell/cc-page.h                                    |   53 +
 shell/cc-panel.c                                   |  182 ++
 shell/cc-panel.h                                   |   55 +
 shell/control-center.c                             |  155 ++-
 27 files changed, 4811 insertions(+), 94 deletions(-)
---
diff --git a/capplets/appearance/Makefile.am b/capplets/appearance/Makefile.am
index b940d73..bfde011 100644
--- a/capplets/appearance/Makefile.am
+++ b/capplets/appearance/Makefile.am
@@ -3,15 +3,64 @@ SUBDIRS = data
 # This is used in GNOMECC_CAPPLETS_CFLAGS
 cappletname = appearance
 
+module_flags = -export_dynamic -avoid-version -module -no-undefined -export-symbols-regex '^g_io_module_(load|unload)'
+
+INCLUDES = \
+	$(METACITY_CFLAGS) \
+	$(GNOMECC_CAPPLETS_CFLAGS) \
+	$(FONT_CAPPLET_CFLAGS) \
+	-DGNOMELOCALEDIR="\"$(datadir)/locale\"" \
+	-DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \
+	-DGNOMECC_UI_DIR="\"$(uidir)\"" \
+	-DGNOMECC_PIXMAP_DIR="\"$(pixmapdir)\"" \
+	-DWALLPAPER_DATADIR="\"$(wallpaperdir)\""
+
+AM_CFLAGS = -DGNOME_DESKTOP_USE_UNSTABLE_API
+
+noinst_LTLIBRARIES = libappearance-common.la
+
 bin_PROGRAMS = gnome-appearance-properties
 
+libappearance_common_la_SOURCES = \
+	theme-installer.c \
+	theme-installer.h \
+	theme-save.c \
+	theme-save.h \
+	theme-util.c \
+	theme-util.h
+
+ccmodulesdir = $(libdir)/control-center-1/extensions
+ccmodules_LTLIBRARIES = libappearance.la
+
+libappearance_la_SOURCES = \
+	appearance-module.c \
+	cc-backgrounds-monitor.h \
+	cc-backgrounds-monitor.c \
+	cc-background-item.h \
+	cc-background-item.c \
+	cc-background-page.h \
+	cc-background-page.c \
+	cc-appearance-panel.h \
+	cc-appearance-panel.c
+
+libappearance_la_LDFLAGS = \
+	$(module_flags)
+
+libappearance_la_LIBADD = \
+	libappearance-common.la \
+	$(GNOMECC_CAPPLETS_LIBS) \
+	$(LIBGNOMEKBDUI_LIBS)
+
+libappearance_la_CFLAGS = \
+	-I$(top_builddir)/shell \
+	-I$(top_srcdir)/shell
+
 gnome_appearance_properties_SOURCES = \
 	appearance.h \
 	appearance-desktop.c \
 	appearance-desktop.h \
 	appearance-font.c \
 	appearance-font.h \
-	appearance-main.c \
 	appearance-themes.c \
 	appearance-themes.h \
 	appearance-style.c \
@@ -24,16 +73,10 @@ gnome_appearance_properties_SOURCES = \
 	gnome-wp-item.h \
 	gnome-wp-xml.c \
 	gnome-wp-xml.h \
-	theme-installer.c \
-	theme-installer.h \
-	theme-save.c \
-	theme-save.h \
-	theme-util.c \
-	theme-util.h
-
-AM_CFLAGS = -DGNOME_DESKTOP_USE_UNSTABLE_API
+	appearance-main.c
 
 gnome_appearance_properties_LDADD = \
+	libappearance-common.la \
 	$(top_builddir)/libwindow-settings/libgnome-window-settings.la \
 	$(top_builddir)/capplets/common/libcommon.la \
 	$(GNOMECC_CAPPLETS_LIBS) \
@@ -41,20 +84,10 @@ gnome_appearance_properties_LDADD = \
 	$(METACITY_LIBS)
 gnome_appearance_properties_LDFLAGS = -export-dynamic
 
-gtkbuilderdir = $(pkgdatadir)/ui
+uidir = $(pkgdatadir)/ui
 pixmapdir = $(pkgdatadir)/pixmaps
 wallpaperdir = $(datadir)/gnome-background-properties
 
-INCLUDES = \
-	$(METACITY_CFLAGS) \
-	$(GNOMECC_CAPPLETS_CFLAGS) \
-	$(FONT_CAPPLET_CFLAGS) \
-	-DGNOMELOCALEDIR="\"$(datadir)/locale\"" \
-	-DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \
-	-DGNOMECC_GTKBUILDER_DIR="\"$(gtkbuilderdir)\"" \
-	-DGNOMECC_PIXMAP_DIR="\"$(pixmapdir)\"" \
-	-DWALLPAPER_DATADIR="\"$(wallpaperdir)\""
-
 CLEANFILES = $(GNOMECC_CAPPLETS_CLEANFILES)
 
 -include $(top_srcdir)/git.mk
diff --git a/capplets/appearance/appearance-desktop.c b/capplets/appearance/appearance-desktop.c
index 2ae6bd6..401b4d2 100644
--- a/capplets/appearance/appearance-desktop.c
+++ b/capplets/appearance/appearance-desktop.c
@@ -192,10 +192,11 @@ wp_add_image (AppearanceData *data,
   }
   else
   {
-    item = gnome_wp_item_new (filename, data->wp_hash, data->thumb_factory);
+    item = gnome_wp_item_new (filename, data->thumb_factory);
 
     if (item != NULL)
     {
+      g_hash_table_insert (data->wp_hash, item->filename, item);
       wp_props_load_wallpaper (item->filename, item, data);
     }
   }
@@ -999,7 +1000,7 @@ wp_load_stuffs (void *user_data)
   item = g_hash_table_lookup (data->wp_hash, "(none)");
   if (item == NULL)
   {
-    item = gnome_wp_item_new ("(none)", data->wp_hash, data->thumb_factory);
+    item = gnome_wp_item_new ("(none)", data->thumb_factory);
     if (item != NULL)
     {
       wp_props_load_wallpaper (item->filename, item, data);
diff --git a/capplets/appearance/appearance-main.c b/capplets/appearance/appearance-main.c
index 4a4a718..0d7dbc6 100644
--- a/capplets/appearance/appearance-main.c
+++ b/capplets/appearance/appearance-main.c
@@ -43,7 +43,7 @@ init_appearance_data (int *argc, char ***argv, GOptionContext *context)
   activate_settings_daemon ();
 
   /* set up the data */
-  uifile = g_build_filename (GNOMECC_GTKBUILDER_DIR, "appearance.ui",
+  uifile = g_build_filename (GNOMECC_UI_DIR, "appearance.ui",
                              NULL);
   ui = gtk_builder_new ();
   gtk_builder_add_from_file (ui, uifile, &err);
diff --git a/capplets/appearance/appearance-module.c b/capplets/appearance/appearance-module.c
new file mode 100644
index 0000000..8aae2fd
--- /dev/null
+++ b/capplets/appearance/appearance-module.c
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gio/gio.h>
+
+#include "cc-appearance-panel.h"
+
+void
+g_io_module_load (GIOModule *module)
+{
+  bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+  cc_appearance_panel_register (module);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/capplets/appearance/cc-appearance-panel.c b/capplets/appearance/cc-appearance-panel.c
new file mode 100644
index 0000000..b0517a7
--- /dev/null
+++ b/capplets/appearance/cc-appearance-panel.c
@@ -0,0 +1,219 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnomeui/gnome-desktop-thumbnail.h>
+
+#include "cc-background-page.h"
+#include "cc-appearance-panel.h"
+
+#include "gconf-property-editor.h"
+#if 0
+#include "appearance-desktop.h"
+#include "appearance-font.h"
+#include "appearance-themes.h"
+#include "appearance-style.h"
+#endif
+#include "theme-installer.h"
+#include "theme-thumbnail.h"
+
+#define CC_APPEARANCE_PANEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_APPEARANCE_PANEL, CcAppearancePanelPrivate))
+
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (builder, s))
+
+struct CcAppearancePanelPrivate
+{
+        GtkWidget *notebook;
+        CcPage    *theme_page;
+        CcPage    *background_page;
+        CcPage    *font_page;
+};
+
+enum {
+        PROP_0,
+};
+
+static void     cc_appearance_panel_class_init     (CcAppearancePanelClass *klass);
+static void     cc_appearance_panel_init           (CcAppearancePanel      *appearance_panel);
+static void     cc_appearance_panel_finalize       (GObject             *object);
+
+G_DEFINE_DYNAMIC_TYPE (CcAppearancePanel, cc_appearance_panel, CC_TYPE_PANEL)
+
+static void
+cc_appearance_panel_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+cc_appearance_panel_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+#if 0
+/* FIXME */
+static void
+install_theme (CcAppearancePanel *panel,
+               const char        *filename)
+{
+        GFile     *inst;
+        GtkWidget *toplevel;
+
+        g_assert (filename != NULL);
+
+        inst = g_file_new_for_commandline_arg (filename);
+
+        toplevel = gtk_widget_get_toplevel (GTK_WIDGET (panel));
+        if (GTK_WIDGET_TOPLEVEL (toplevel)) {
+                gnome_theme_install (inst, GTK_WINDOW (toplevel));
+        } else {
+                gnome_theme_install (inst, NULL);
+        }
+        g_object_unref (inst);
+}
+#endif
+
+static void
+setup_panel (CcAppearancePanel *panel)
+{
+        GtkWidget *label;
+        char      *display_name;
+
+        /* FIXME: should a panel subclass notebook? */
+        panel->priv->notebook = gtk_notebook_new ();
+        gtk_container_add (GTK_CONTAINER (panel), panel->priv->notebook);
+        gtk_widget_show (panel->priv->notebook);
+
+        /* FIXME: load pages */
+        panel->priv->background_page = cc_background_page_new ();
+        g_object_get (panel->priv->background_page,
+                      "display-name", &display_name,
+                      NULL);
+        label = gtk_label_new (display_name);
+        g_free (display_name);
+        gtk_notebook_append_page (GTK_NOTEBOOK (panel->priv->notebook), GTK_WIDGET (panel->priv->background_page), label);
+        gtk_widget_show (GTK_WIDGET (panel->priv->background_page));
+}
+
+static GObject *
+cc_appearance_panel_constructor (GType                  type,
+                               guint                  n_construct_properties,
+                               GObjectConstructParam *construct_properties)
+{
+        CcAppearancePanel      *appearance_panel;
+
+        appearance_panel = CC_APPEARANCE_PANEL (G_OBJECT_CLASS (cc_appearance_panel_parent_class)->constructor (type,
+                                                                                                                n_construct_properties,
+                                                                                                                construct_properties));
+
+        g_object_set (appearance_panel,
+                      "display-name", _("Appearance"),
+                      "id", "gnome-appearance-properties.desktop",
+                      NULL);
+
+        //theme_thumbnail_factory_init (0, NULL);
+
+        setup_panel (appearance_panel);
+
+        return G_OBJECT (appearance_panel);
+}
+
+static void
+cc_appearance_panel_class_init (CcAppearancePanelClass *klass)
+{
+        GObjectClass  *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = cc_appearance_panel_get_property;
+        object_class->set_property = cc_appearance_panel_set_property;
+        object_class->constructor = cc_appearance_panel_constructor;
+        object_class->finalize = cc_appearance_panel_finalize;
+
+        g_type_class_add_private (klass, sizeof (CcAppearancePanelPrivate));
+}
+
+static void
+cc_appearance_panel_class_finalize (CcAppearancePanelClass *klass)
+{
+}
+
+static void
+cc_appearance_panel_init (CcAppearancePanel *panel)
+{
+        GConfClient *client;
+
+        panel->priv = CC_APPEARANCE_PANEL_GET_PRIVATE (panel);
+
+        client = gconf_client_get_default ();
+        gconf_client_add_dir (client,
+                              "/desktop/gnome/peripherals/appearance",
+                              GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
+        gconf_client_add_dir (client, "/desktop/gnome/interface",
+                              GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
+        g_object_unref (client);
+}
+
+static void
+cc_appearance_panel_finalize (GObject *object)
+{
+        CcAppearancePanel *appearance_panel;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (CC_IS_APPEARANCE_PANEL (object));
+
+        appearance_panel = CC_APPEARANCE_PANEL (object);
+
+        g_return_if_fail (appearance_panel->priv != NULL);
+
+        G_OBJECT_CLASS (cc_appearance_panel_parent_class)->finalize (object);
+}
+
+void
+cc_appearance_panel_register (GIOModule *module)
+{
+        cc_appearance_panel_register_type (G_TYPE_MODULE (module));
+        g_io_extension_point_implement (CC_PANEL_EXTENSION_POINT_NAME,
+                                        CC_TYPE_APPEARANCE_PANEL,
+                                        "appearance",
+                                        10);
+}
diff --git a/capplets/appearance/cc-appearance-panel.h b/capplets/appearance/cc-appearance-panel.h
new file mode 100644
index 0000000..c27629c
--- /dev/null
+++ b/capplets/appearance/cc-appearance-panel.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __CC_APPEARANCE_PANEL_H
+#define __CC_APPEARANCE_PANEL_H
+
+#include <gtk/gtk.h>
+#include "cc-panel.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_APPEARANCE_PANEL         (cc_appearance_panel_get_type ())
+#define CC_APPEARANCE_PANEL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_APPEARANCE_PANEL, CcAppearancePanel))
+#define CC_APPEARANCE_PANEL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_APPEARANCE_PANEL, CcAppearancePanelClass))
+#define CC_IS_APPEARANCE_PANEL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_APPEARANCE_PANEL))
+#define CC_IS_APPEARANCE_PANEL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_APPEARANCE_PANEL))
+#define CC_APPEARANCE_PANEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_APPEARANCE_PANEL, CcAppearancePanelClass))
+
+typedef struct CcAppearancePanelPrivate CcAppearancePanelPrivate;
+
+typedef struct
+{
+        CcPanel                 parent;
+        CcAppearancePanelPrivate *priv;
+} CcAppearancePanel;
+
+typedef struct
+{
+        CcPanelClass   parent_class;
+} CcAppearancePanelClass;
+
+GType              cc_appearance_panel_get_type   (void);
+void               cc_appearance_panel_register   (GIOModule         *module);
+
+G_END_DECLS
+
+#endif /* __CC_APPEARANCE_PANEL_H */
diff --git a/capplets/appearance/cc-background-item.c b/capplets/appearance/cc-background-item.c
new file mode 100644
index 0000000..ed48fef
--- /dev/null
+++ b/capplets/appearance/cc-background-item.c
@@ -0,0 +1,703 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#include <gconf/gconf-client.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnomeui/gnome-bg.h>
+
+#include "cc-background-item.h"
+
+#define CC_BACKGROUND_ITEM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_BACKGROUND_ITEM, CcBackgroundItemPrivate))
+
+struct CcBackgroundItemPrivate
+{
+        /* properties */
+        char            *name;
+        char            *filename;
+        char            *description;
+        char            *placement;
+        char            *shading;
+        char            *primary_color;
+        char            *secondary_color;
+        gboolean         is_deleted;
+
+        /* internal */
+        GnomeBG         *bg;
+        char            *mime_type;
+        int              width;
+        int              height;
+};
+
+enum {
+        PROP_0,
+        PROP_NAME,
+        PROP_FILENAME,
+        PROP_DESCRIPTION,
+        PROP_PLACEMENT,
+        PROP_SHADING,
+        PROP_PRIMARY_COLOR,
+        PROP_SECONDARY_COLOR,
+        PROP_IS_DELETED,
+};
+
+enum {
+        CHANGED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     cc_background_item_class_init     (CcBackgroundItemClass *klass);
+static void     cc_background_item_init           (CcBackgroundItem      *background_item);
+static void     cc_background_item_finalize       (GObject               *object);
+
+G_DEFINE_TYPE (CcBackgroundItem, cc_background_item, G_TYPE_OBJECT)
+
+static GConfEnumStringPair placement_lookup[] = {
+        { GNOME_BG_PLACEMENT_CENTERED, "centered" },
+        { GNOME_BG_PLACEMENT_FILL_SCREEN, "stretched" },
+        { GNOME_BG_PLACEMENT_SCALED, "scaled" },
+        { GNOME_BG_PLACEMENT_ZOOMED, "zoom" },
+        { GNOME_BG_PLACEMENT_TILED, "wallpaper" },
+        { 0, NULL }
+};
+
+static GConfEnumStringPair shading_lookup[] = {
+        { GNOME_BG_COLOR_SOLID, "solid" },
+        { GNOME_BG_COLOR_H_GRADIENT, "horizontal-gradient" },
+        { GNOME_BG_COLOR_V_GRADIENT, "vertical-gradient" },
+        { 0, NULL }
+};
+
+static const char *
+placement_to_string (GnomeBGPlacement type)
+{
+        return gconf_enum_to_string (placement_lookup, type);
+}
+
+static const char *
+shading_to_string (GnomeBGColorType type)
+{
+        return gconf_enum_to_string (shading_lookup, type);
+}
+
+static GnomeBGPlacement
+string_to_placement (const char *option)
+{
+        int i = GNOME_BG_PLACEMENT_SCALED;
+        gconf_string_to_enum (placement_lookup, option, &i);
+        return i;
+}
+
+static GnomeBGColorType
+string_to_shading (const char *shading)
+{
+        int i = GNOME_BG_COLOR_SOLID;
+        gconf_string_to_enum (shading_lookup, shading, &i);
+        return i;
+}
+
+static GdkPixbuf *
+add_slideshow_frame (GdkPixbuf *pixbuf)
+{
+        GdkPixbuf *sheet;
+        GdkPixbuf *sheet2;
+        GdkPixbuf *tmp;
+        int        w;
+        int        h;
+
+        w = gdk_pixbuf_get_width (pixbuf);
+        h = gdk_pixbuf_get_height (pixbuf);
+
+        sheet = gdk_pixbuf_new (GDK_COLORSPACE_RGB, FALSE, 8, w, h);
+        gdk_pixbuf_fill (sheet, 0x00000000);
+        sheet2 = gdk_pixbuf_new_subpixbuf (sheet, 1, 1, w - 2, h - 2);
+        gdk_pixbuf_fill (sheet2, 0xffffffff);
+        g_object_unref (sheet2);
+
+        tmp = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, w + 6, h + 6);
+
+        gdk_pixbuf_fill (tmp, 0x00000000);
+        gdk_pixbuf_composite (sheet, tmp, 6, 6, w, h, 6.0, 6.0, 1.0, 1.0, GDK_INTERP_NEAREST, 255);
+        gdk_pixbuf_composite (sheet, tmp, 3, 3, w, h, 3.0, 3.0, 1.0, 1.0, GDK_INTERP_NEAREST, 255);
+        gdk_pixbuf_composite (pixbuf, tmp, 0, 0, w, h, 0.0, 0.0, 1.0, 1.0, GDK_INTERP_NEAREST, 255);
+
+        g_object_unref (sheet);
+
+        return tmp;
+}
+
+static void
+set_bg_properties (CcBackgroundItem *item)
+{
+        int      shading;
+        int      placement;
+        GdkColor pcolor = { 0, 0, 0, 0 };
+        GdkColor scolor = { 0, 0, 0, 0 };
+
+        if (item->priv->filename)
+                gnome_bg_set_filename (item->priv->bg, item->priv->filename);
+
+        gdk_color_parse (item->priv->primary_color, &pcolor);
+        gdk_color_parse (item->priv->secondary_color, &scolor);
+        placement = string_to_placement (item->priv->placement);
+        shading = string_to_shading (item->priv->shading);
+
+        gnome_bg_set_color (item->priv->bg, shading, &pcolor, &scolor);
+        gnome_bg_set_placement (item->priv->bg, placement);
+}
+
+
+gboolean
+cc_background_item_changes_with_time (CcBackgroundItem *item)
+{
+        gboolean changes;
+
+        changes = FALSE;
+        if (item->priv->bg != NULL) {
+                changes = gnome_bg_changes_with_time (item->priv->bg);
+        }
+        return changes;
+}
+
+static void
+update_description (CcBackgroundItem *item)
+{
+        g_free (item->priv->description);
+        item->priv->description = NULL;
+
+        if (strcmp (item->priv->filename, "(none)") == 0) {
+                item->priv->description = g_strdup (item->priv->name);
+        } else {
+                const char *description;
+                char       *size;
+                char       *dirname;
+
+                dirname = g_path_get_dirname (item->priv->filename);
+
+                description = NULL;
+                size = NULL;
+
+                if (item->priv->mime_type != NULL) {
+                        if (strcmp (item->priv->mime_type, "application/xml") == 0) {
+                                if (gnome_bg_changes_with_time (item->priv->bg))
+                                        description = _("Slide Show");
+                                else if (item->priv->width > 0 && item->priv->height > 0)
+                                        description = _("Image");
+                        } else {
+                                description = g_content_type_get_description (item->priv->mime_type);
+                        }
+                }
+
+                if (gnome_bg_has_multiple_sizes (item->priv->bg)) {
+                        size = g_strdup (_("multiple sizes"));
+                } else if (item->priv->width > 0 && item->priv->height > 0) {
+                        /* translators: x pixel(s) by y pixel(s) */
+                        size = g_strdup_printf ("%d %s by %d %s",
+                                                item->priv->width,
+                                                ngettext ("pixel", "pixels", item->priv->width),
+                                                item->priv->height,
+                                                ngettext ("pixel", "pixels", item->priv->height));
+                }
+
+                if (description != NULL && size != NULL) {
+                        /* translators: <b>wallpaper name</b>
+                         * mime type, size
+                         * Folder: /path/to/file
+                         */
+                        item->priv->description = g_markup_printf_escaped (_("<b>%s</b>\n"
+                                                                             "%s, %s\n"
+                                                                             "Folder: %s"),
+                                                                           item->priv->name,
+                                                                           description,
+                                                                           size,
+                                                                           dirname);
+                } else {
+                        /* translators: <b>wallpaper name</b>
+                         * Image missing
+                         * Folder: /path/to/file
+                         */
+                        item->priv->description = g_markup_printf_escaped (_("<b>%s</b>\n"
+                                                                             "%s\n"
+                                                                             "Folder: %s"),
+                                                                           item->priv->name,
+                                                                           _("Image missing"),
+                                                                           dirname);
+                }
+
+                g_free (size);
+                g_free (dirname);
+        }
+}
+
+GdkPixbuf *
+cc_background_item_get_frame_thumbnail (CcBackgroundItem             *item,
+                                        GnomeDesktopThumbnailFactory *thumbs,
+                                        int                           width,
+                                        int                           height,
+                                        int                           frame)
+{
+        GdkPixbuf *pixbuf = NULL;
+
+        set_bg_properties (item);
+
+        if (frame != -1)
+                pixbuf = gnome_bg_create_frame_thumbnail (item->priv->bg,
+                                                          thumbs,
+                                                          gdk_screen_get_default (),
+                                                          width,
+                                                          height,
+                                                          frame);
+        else
+                pixbuf = gnome_bg_create_thumbnail (item->priv->bg,
+                                                    thumbs,
+                                                    gdk_screen_get_default(),
+                                                    width,
+                                                    height);
+
+        if (pixbuf != NULL
+            && gnome_bg_changes_with_time (item->priv->bg)) {
+                GdkPixbuf *tmp;
+
+                tmp = add_slideshow_frame (pixbuf);
+                g_object_unref (pixbuf);
+                pixbuf = tmp;
+        }
+
+        gnome_bg_get_image_size (item->priv->bg,
+                                 thumbs,
+                                 width,
+                                 height,
+                                 &item->priv->width,
+                                 &item->priv->height);
+
+        update_description (item);
+
+        return pixbuf;
+}
+
+
+GdkPixbuf *
+cc_background_item_get_thumbnail (CcBackgroundItem             *item,
+                                  GnomeDesktopThumbnailFactory *thumbs,
+                                  int                           width,
+                                  int                           height)
+{
+        return cc_background_item_get_frame_thumbnail (item, thumbs, width, height, -1);
+}
+
+static void
+update_info (CcBackgroundItem *item)
+{
+        GFile     *file;
+        GFileInfo *info;
+
+        file = g_file_new_for_commandline_arg (item->priv->filename);
+
+        info = g_file_query_info (file,
+                                  G_FILE_ATTRIBUTE_STANDARD_NAME ","
+                                  G_FILE_ATTRIBUTE_STANDARD_SIZE ","
+                                  G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+                                  G_FILE_ATTRIBUTE_TIME_MODIFIED,
+                                  G_FILE_QUERY_INFO_NONE,
+                                  NULL,
+                                  NULL);
+        g_object_unref (file);
+
+        g_free (item->priv->mime_type);
+        item->priv->mime_type = NULL;
+
+        if (info == NULL
+            || g_file_info_get_content_type (info) == NULL) {
+                if (strcmp (item->priv->filename, "(none)") == 0) {
+                        item->priv->mime_type = g_strdup ("image/x-no-data");
+                        g_free (item->priv->name);
+                        item->priv->name = g_strdup (_("No Desktop Background"));
+                        //item->priv->size = 0;
+                }
+        } else {
+                if (item->priv->name == NULL) {
+                        const char *name;
+
+                        g_free (item->priv->name);
+
+                        name = g_file_info_get_name (info);
+                        if (g_utf8_validate (name, -1, NULL))
+                                item->priv->name = g_strdup (name);
+                        else
+                                item->priv->name = g_filename_to_utf8 (name,
+                                                                       -1,
+                                                                       NULL,
+                                                                       NULL,
+                                                                       NULL);
+                }
+
+                item->priv->mime_type = g_strdup (g_file_info_get_content_type (info));
+
+#if 0
+                item->priv->size = g_file_info_get_size (info);
+                item->priv->mtime = g_file_info_get_attribute_uint64 (info,
+                                                                      G_FILE_ATTRIBUTE_TIME_MODIFIED);
+#endif
+        }
+
+        if (info != NULL)
+                g_object_unref (info);
+
+}
+
+static void
+on_bg_changed (GnomeBG          *bg,
+               CcBackgroundItem *item)
+{
+        g_signal_emit (item, signals[CHANGED], 0);
+}
+
+gboolean
+cc_background_item_load (CcBackgroundItem *item)
+{
+        gboolean ret;
+
+        g_return_val_if_fail (item != NULL, FALSE);
+
+        update_info (item);
+
+        ret = FALSE;
+        if (item->priv->mime_type != NULL
+            && (g_str_has_prefix (item->priv->mime_type, "image/")
+                || strcmp (item->priv->mime_type, "application/xml") == 0)) {
+                ret = TRUE;
+
+                set_bg_properties (item);
+        } else {
+                /* FIXME: return error message? */
+                /* unknown mime type */
+        }
+
+        update_description (item);
+
+        return TRUE;
+}
+
+static void
+_set_name (CcBackgroundItem *item,
+           const char       *value)
+{
+        g_free (item->priv->name);
+        item->priv->name = g_strdup (value);
+}
+
+static void
+_set_filename (CcBackgroundItem *item,
+               const char       *value)
+{
+        g_free (item->priv->filename);
+        item->priv->filename = g_strdup (value);
+}
+
+static void
+_set_description (CcBackgroundItem *item,
+                  const char       *value)
+{
+        g_free (item->priv->description);
+        item->priv->description = g_strdup (value);
+}
+
+static void
+_set_placement (CcBackgroundItem *item,
+                const char       *value)
+{
+        g_free (item->priv->placement);
+        item->priv->placement = g_strdup (value);
+}
+
+static void
+_set_shading (CcBackgroundItem *item,
+              const char       *value)
+{
+        g_free (item->priv->shading);
+        item->priv->shading = g_strdup (value);
+}
+
+static void
+_set_primary_color (CcBackgroundItem *item,
+                    const char       *value)
+{
+        g_free (item->priv->primary_color);
+        item->priv->primary_color = g_strdup (value);
+}
+
+static void
+_set_secondary_color (CcBackgroundItem *item,
+                      const char       *value)
+{
+        g_free (item->priv->secondary_color);
+        item->priv->secondary_color = g_strdup (value);
+}
+
+static void
+_set_is_deleted (CcBackgroundItem *item,
+                 gboolean          value)
+{
+        item->priv->is_deleted = value;
+}
+
+static void
+cc_background_item_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+        CcBackgroundItem *self;
+
+        self = CC_BACKGROUND_ITEM (object);
+
+        switch (prop_id) {
+        case PROP_NAME:
+                _set_name (self, g_value_get_string (value));
+                break;
+        case PROP_FILENAME:
+                _set_filename (self, g_value_get_string (value));
+                break;
+        case PROP_DESCRIPTION:
+                _set_description (self, g_value_get_string (value));
+                break;
+        case PROP_PLACEMENT:
+                _set_placement (self, g_value_get_string (value));
+                break;
+        case PROP_SHADING:
+                _set_shading (self, g_value_get_string (value));
+                break;
+        case PROP_PRIMARY_COLOR:
+                _set_primary_color (self, g_value_get_string (value));
+                break;
+        case PROP_SECONDARY_COLOR:
+                _set_secondary_color (self, g_value_get_string (value));
+                break;
+        case PROP_IS_DELETED:
+                _set_is_deleted (self, g_value_get_boolean (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+cc_background_item_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+        CcBackgroundItem *self;
+
+        self = CC_BACKGROUND_ITEM (object);
+
+        switch (prop_id) {
+        case PROP_NAME:
+                g_value_set_string (value, self->priv->name);
+                break;
+        case PROP_FILENAME:
+                g_value_set_string (value, self->priv->filename);
+                break;
+        case PROP_DESCRIPTION:
+                g_value_set_string (value, self->priv->description);
+                break;
+        case PROP_PLACEMENT:
+                g_value_set_string (value, self->priv->placement);
+                break;
+        case PROP_SHADING:
+                g_value_set_string (value, self->priv->shading);
+                break;
+        case PROP_PRIMARY_COLOR:
+                g_value_set_string (value, self->priv->primary_color);
+                break;
+        case PROP_SECONDARY_COLOR:
+                g_value_set_string (value, self->priv->secondary_color);
+                break;
+        case PROP_IS_DELETED:
+                g_value_set_boolean (value, self->priv->is_deleted);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+cc_background_item_constructor (GType                  type,
+                                guint                  n_construct_properties,
+                                GObjectConstructParam *construct_properties)
+{
+        CcBackgroundItem      *background_item;
+
+        background_item = CC_BACKGROUND_ITEM (G_OBJECT_CLASS (cc_background_item_parent_class)->constructor (type,
+                                                                                                                         n_construct_properties,
+                                                                                                                         construct_properties));
+
+        return G_OBJECT (background_item);
+}
+
+static void
+cc_background_item_class_init (CcBackgroundItemClass *klass)
+{
+        GObjectClass  *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = cc_background_item_get_property;
+        object_class->set_property = cc_background_item_set_property;
+        object_class->constructor = cc_background_item_constructor;
+        object_class->finalize = cc_background_item_finalize;
+
+        signals [CHANGED]
+                = g_signal_new ("changed",
+                                G_TYPE_FROM_CLASS (object_class),
+                                G_SIGNAL_RUN_LAST,
+                                0,
+                                NULL,
+                                NULL,
+                                g_cclosure_marshal_VOID__VOID,
+                                G_TYPE_NONE,
+                                0);
+
+        g_object_class_install_property (object_class,
+                                         PROP_NAME,
+                                         g_param_spec_string ("name",
+                                                              "name",
+                                                              "name",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+        g_object_class_install_property (object_class,
+                                         PROP_FILENAME,
+                                         g_param_spec_string ("filename",
+                                                              "filename",
+                                                              "filename",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+        g_object_class_install_property (object_class,
+                                         PROP_DESCRIPTION,
+                                         g_param_spec_string ("description",
+                                                              "description",
+                                                              "description",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+        g_object_class_install_property (object_class,
+                                         PROP_PLACEMENT,
+                                         g_param_spec_string ("placement",
+                                                              "placement",
+                                                              "placement",
+                                                              "scaled",
+                                                              G_PARAM_READWRITE));
+        g_object_class_install_property (object_class,
+                                         PROP_SHADING,
+                                         g_param_spec_string ("shading",
+                                                              "shading",
+                                                              "shading",
+                                                              "solid",
+                                                              G_PARAM_READWRITE));
+        g_object_class_install_property (object_class,
+                                         PROP_PRIMARY_COLOR,
+                                         g_param_spec_string ("primary-color",
+                                                              "primary-color",
+                                                              "primary-color",
+                                                              "#000000000000",
+                                                              G_PARAM_READWRITE));
+        g_object_class_install_property (object_class,
+                                         PROP_SECONDARY_COLOR,
+                                         g_param_spec_string ("secondary-color",
+                                                              "secondary-color",
+                                                              "secondary-color",
+                                                              "#000000000000",
+                                                              G_PARAM_READWRITE));
+
+        g_object_class_install_property (object_class,
+                                         PROP_IS_DELETED,
+                                         g_param_spec_boolean ("is-deleted",
+                                                               NULL,
+                                                               NULL,
+                                                               FALSE,
+                                                               G_PARAM_READWRITE));
+
+        g_type_class_add_private (klass, sizeof (CcBackgroundItemPrivate));
+}
+
+static void
+cc_background_item_init (CcBackgroundItem *item)
+{
+        item->priv = CC_BACKGROUND_ITEM_GET_PRIVATE (item);
+
+        item->priv->bg = gnome_bg_new ();
+
+        g_signal_connect (item->priv->bg,
+                          "changed",
+                          G_CALLBACK (on_bg_changed),
+                          item);
+
+        item->priv->shading = g_strdup ("solid");
+        item->priv->placement = g_strdup ("scaled");
+        item->priv->primary_color = g_strdup ("#000000000000");
+        item->priv->secondary_color = g_strdup ("#000000000000");
+}
+
+static void
+cc_background_item_finalize (GObject *object)
+{
+        CcBackgroundItem *item;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (CC_IS_BACKGROUND_ITEM (object));
+
+        item = CC_BACKGROUND_ITEM (object);
+
+        g_return_if_fail (item->priv != NULL);
+
+        g_free (item->priv->name);
+        g_free (item->priv->filename);
+        g_free (item->priv->description);
+        g_free (item->priv->primary_color);
+        g_free (item->priv->secondary_color);
+        g_free (item->priv->mime_type);
+
+        if (item->priv->bg != NULL)
+                g_object_unref (item->priv->bg);
+
+        G_OBJECT_CLASS (cc_background_item_parent_class)->finalize (object);
+}
+
+CcBackgroundItem *
+cc_background_item_new (const char *filename)
+{
+        GObject *object;
+
+        object = g_object_new (CC_TYPE_BACKGROUND_ITEM,
+                               "filename", filename,
+                               NULL);
+
+        return CC_BACKGROUND_ITEM (object);
+}
diff --git a/capplets/appearance/cc-background-item.h b/capplets/appearance/cc-background-item.h
new file mode 100644
index 0000000..e8cc9d7
--- /dev/null
+++ b/capplets/appearance/cc-background-item.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __CC_BACKGROUND_ITEM_H
+#define __CC_BACKGROUND_ITEM_H
+
+#include <glib-object.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnomeui/gnome-desktop-thumbnail.h>
+#include "cc-background-item.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_ITEM         (cc_background_item_get_type ())
+#define CC_BACKGROUND_ITEM(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_BACKGROUND_ITEM, CcBackgroundItem))
+#define CC_BACKGROUND_ITEM_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_BACKGROUND_ITEM, CcBackgroundItemClass))
+#define CC_IS_BACKGROUND_ITEM(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_BACKGROUND_ITEM))
+#define CC_IS_BACKGROUND_ITEM_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_BACKGROUND_ITEM))
+#define CC_BACKGROUND_ITEM_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_BACKGROUND_ITEM, CcBackgroundItemClass))
+
+typedef struct CcBackgroundItemPrivate CcBackgroundItemPrivate;
+
+typedef struct
+{
+        GObject                  parent;
+        CcBackgroundItemPrivate *priv;
+} CcBackgroundItem;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+        void (* changed)           (CcBackgroundItem *item);
+} CcBackgroundItemClass;
+
+GType              cc_background_item_get_type (void);
+
+CcBackgroundItem * cc_background_item_new                 (const char                   *filename);
+gboolean           cc_background_item_load                (CcBackgroundItem             *item);
+gboolean           cc_background_item_changes_with_time   (CcBackgroundItem             *item);
+
+GdkPixbuf *        cc_background_item_get_thumbnail       (CcBackgroundItem             *item,
+                                                           GnomeDesktopThumbnailFactory *thumbs,
+                                                           int                           width,
+                                                           int                           height);
+GdkPixbuf *        cc_background_item_get_frame_thumbnail (CcBackgroundItem             *item,
+                                                           GnomeDesktopThumbnailFactory *thumbs,
+                                                           int                           width,
+                                                           int                           height,
+                                                           int                           frame);
+
+G_END_DECLS
+
+#endif /* __CC_BACKGROUND_ITEM_H */
diff --git a/capplets/appearance/cc-background-page.c b/capplets/appearance/cc-background-page.c
new file mode 100644
index 0000000..58bbd80
--- /dev/null
+++ b/capplets/appearance/cc-background-page.c
@@ -0,0 +1,1811 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+#include <gconf/gconf-client.h>
+
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include <libgnomeui/gnome-desktop-thumbnail.h>
+
+#include "cc-background-page.h"
+#include "cc-background-item.h"
+#include "cc-backgrounds-monitor.h"
+
+#define CC_BACKGROUND_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_BACKGROUND_PAGE, CcBackgroundPagePrivate))
+
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (builder, s))
+#define BACKGROUNDS_DIR "/usr/share/backgrounds"
+
+#define WP_PATH_KEY "/desktop/gnome/background"
+#define WP_FILE_KEY WP_PATH_KEY "/picture_filename"
+#define WP_OPTIONS_KEY WP_PATH_KEY "/picture_options"
+#define WP_SHADING_KEY WP_PATH_KEY "/color_shading_type"
+#define WP_PCOLOR_KEY WP_PATH_KEY "/primary_color"
+#define WP_SCOLOR_KEY WP_PATH_KEY "/secondary_color"
+
+struct CcBackgroundPagePrivate
+{
+        CcBackgroundsMonitor *monitor;
+        GtkTreeModel *model;
+        GtkWidget    *icon_view;
+        GtkWidget    *remove_button;
+        GtkWidget    *color_menu;
+        GtkWidget    *style_menu;
+        GtkWidget    *primary_color_picker;
+        GtkWidget    *secondary_color_picker;
+        GtkWidget    *file_chooser;
+        GtkWidget    *file_chooser_preview;
+
+        GnomeDesktopThumbnailFactory *thumb_factory;
+
+        int           frame;
+        int           thumb_width;
+        int           thumb_height;
+
+        gulong        screen_size_handler;
+        gulong        screen_monitors_handler;
+};
+
+enum {
+        PROP_0,
+};
+
+static void     cc_background_page_class_init     (CcBackgroundPageClass *klass);
+static void     cc_background_page_init           (CcBackgroundPage      *background_page);
+static void     cc_background_page_finalize       (GObject             *object);
+
+G_DEFINE_TYPE (CcBackgroundPage, cc_background_page, CC_TYPE_PAGE)
+
+enum {
+        TARGET_URI_LIST,
+        TARGET_BGIMAGE
+};
+
+static const GtkTargetEntry drop_types[] = {
+        { "text/uri-list", 0, TARGET_URI_LIST },
+        { "property/bgimage", 0, TARGET_BGIMAGE }
+};
+
+static const GtkTargetEntry drag_types[] = {
+        {"text/uri-list", GTK_TARGET_OTHER_WIDGET, TARGET_URI_LIST}
+};
+
+enum {
+        COL_PIXBUF,
+        COL_ITEM,
+};
+
+enum {
+        SHADE_SOLID = 0,
+        SHADE_H_GRADIENT,
+        SHADE_V_GRADIENT,
+};
+
+enum {
+        SCALE_TILE = 0,
+        SCALE_ZOOM,
+        SCALE_CENTER,
+        SCALE_SCALE,
+        SCALE_STRETCH,
+};
+
+static GConfEnumStringPair options_lookup[] = {
+        { 0, "wallpaper" },
+        { 1, "zoom" },
+        { 2, "centered" },
+        { 3, "scaled" },
+        { 4, "stretched" },
+        { 0, NULL }
+};
+
+static GConfEnumStringPair shade_lookup[] = {
+        { 0, "solid" },
+        { 1, "horizontal-gradient" },
+        { 2, "vertical-gradient" },
+        { 0, NULL }
+};
+
+static const char *
+option_to_string (int type)
+{
+        return gconf_enum_to_string (options_lookup, type);
+}
+
+static const char *
+shading_to_string (int type)
+{
+        return gconf_enum_to_string (shade_lookup, type);
+}
+
+static int
+string_to_option (const char *option)
+{
+        int i = 3;
+        gconf_string_to_enum (options_lookup, option, &i);
+        return i;
+}
+
+static int
+string_to_shade (const char *shade_type)
+{
+        int i = 0;
+        gconf_string_to_enum (shade_lookup, shade_type, &i);
+        return i;
+}
+
+static void
+cc_background_page_set_property (GObject      *object,
+                                 guint         prop_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+cc_background_page_get_property (GObject    *object,
+                                 guint       prop_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+on_style_menu_changed (GtkComboBox      *combobox,
+                       CcBackgroundPage *page)
+{
+        GConfClient *client;
+        int          options;
+
+        client = gconf_client_get_default ();
+        options = gtk_combo_box_get_active (GTK_COMBO_BOX (page->priv->style_menu));
+        if (gconf_client_key_is_writable (client, WP_OPTIONS_KEY, NULL))
+                gconf_client_set_string (client,
+                                         WP_OPTIONS_KEY,
+                                         option_to_string (options),
+                                         NULL);
+        g_object_unref (client);
+}
+
+static void
+on_color_menu_changed (GtkWidget        *combobox,
+                       CcBackgroundPage *page)
+{
+        GConfClient *client;
+        int          shade_type;
+
+        client = gconf_client_get_default ();
+        shade_type = gtk_combo_box_get_active (GTK_COMBO_BOX (page->priv->color_menu));
+        if (gconf_client_key_is_writable (client, WP_SHADING_KEY, NULL))
+                gconf_client_set_string (client,
+                                         WP_SHADING_KEY,
+                                         shading_to_string (shade_type),
+                                         NULL);
+        g_object_unref (client);
+}
+
+static void
+on_color_changed (GtkWidget        *widget,
+                  CcBackgroundPage *page)
+{
+        GConfClient *client;
+        GdkColor     pcolor;
+        GdkColor     scolor;
+        char        *spcolor;
+        char        *sscolor;
+
+        gtk_color_button_get_color (GTK_COLOR_BUTTON (page->priv->primary_color_picker),
+                                    &pcolor);
+        gtk_color_button_get_color (GTK_COLOR_BUTTON (page->priv->secondary_color_picker),
+                                    &scolor);
+
+        spcolor = gdk_color_to_string (&pcolor);
+        sscolor = gdk_color_to_string (&scolor);
+
+        client = gconf_client_get_default ();
+        gconf_client_set_string (client, WP_PCOLOR_KEY, spcolor, NULL);
+        gconf_client_set_string (client, WP_SCOLOR_KEY, sscolor, NULL);
+        g_object_unref (client);
+
+        g_free (spcolor);
+        g_free (sscolor);
+}
+
+static GdkPixbuf *buttons[3];
+
+static void
+create_button_images (CcBackgroundPage *page)
+{
+        GtkIconSet *icon_set;
+        GdkPixbuf  *pixbuf;
+        GdkPixbuf  *pb;
+        GdkPixbuf  *pb2;
+        int         i, w, h;
+
+        icon_set = gtk_style_lookup_icon_set (page->priv->icon_view->style, "gtk-media-play");
+        pb = gtk_icon_set_render_icon (icon_set,
+                                       page->priv->icon_view->style,
+                                       GTK_TEXT_DIR_RTL,
+                                       GTK_STATE_NORMAL,
+                                       GTK_ICON_SIZE_MENU,
+                                       page->priv->icon_view,
+                                       NULL);
+        pb2 = gtk_icon_set_render_icon (icon_set,
+                                        page->priv->icon_view->style,
+                                        GTK_TEXT_DIR_LTR,
+                                        GTK_STATE_NORMAL,
+                                        GTK_ICON_SIZE_MENU,
+                                        page->priv->icon_view,
+                                        NULL);
+        w = gdk_pixbuf_get_width (pb);
+        h = gdk_pixbuf_get_height (pb);
+
+        for (i = 0; i < 3; i++) {
+                pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, 2 * w, h);
+                gdk_pixbuf_fill (pixbuf, 0);
+                if (i > 0)
+                        gdk_pixbuf_composite (pb, pixbuf, 0, 0, w, h, 0, 0, 1, 1, GDK_INTERP_NEAREST, 255);
+                if (i < 2)
+                        gdk_pixbuf_composite (pb2, pixbuf, w, 0, w, h, w, 0, 1, 1, GDK_INTERP_NEAREST, 255);
+
+                buttons[i] = pixbuf;
+        }
+
+        g_object_unref (pb);
+        g_object_unref (pb2);
+}
+
+static CcBackgroundItem *
+get_selected_item (CcBackgroundPage *page,
+                   GtkTreeIter      *piter)
+{
+        CcBackgroundItem *item;
+        GList            *selected;
+        GtkTreeIter       iter;
+        gboolean          res;
+
+        item = NULL;
+
+        selected = gtk_icon_view_get_selected_items (GTK_ICON_VIEW (page->priv->icon_view));
+
+        if (selected == NULL) {
+                return NULL;
+        }
+
+        res = gtk_tree_model_get_iter (page->priv->model,
+                                       &iter,
+                                       selected->data);
+        g_list_foreach (selected, (GFunc) gtk_tree_path_free, NULL);
+        g_list_free (selected);
+
+        if (res) {
+                if (piter != NULL) {
+                        *piter = iter;
+                }
+
+                gtk_tree_model_get (page->priv->model, &iter, COL_ITEM, &item, -1);
+        }
+
+        return item;
+}
+
+static void
+ui_update_sensitivities (CcBackgroundPage *page)
+{
+        GConfClient      *client;
+        CcBackgroundItem *item;
+        char             *filename;
+
+        item = get_selected_item (page, NULL);
+        filename = NULL;
+        if (item != NULL) {
+                g_object_get (item, "filename", &filename, NULL);
+                g_object_unref (item);
+        }
+
+        client = gconf_client_get_default ();
+        if (!gconf_client_key_is_writable (client, WP_OPTIONS_KEY, NULL)
+            || (filename != NULL && !strcmp (filename, "(none)")))
+                gtk_widget_set_sensitive (page->priv->style_menu, FALSE);
+        else
+                gtk_widget_set_sensitive (page->priv->style_menu, TRUE);
+
+        if (!gconf_client_key_is_writable (client, WP_SHADING_KEY, NULL))
+                gtk_widget_set_sensitive (page->priv->color_menu, FALSE);
+        else
+                gtk_widget_set_sensitive (page->priv->color_menu, TRUE);
+
+        if (!gconf_client_key_is_writable (client, WP_PCOLOR_KEY, NULL))
+                gtk_widget_set_sensitive (page->priv->primary_color_picker, FALSE);
+        else
+                gtk_widget_set_sensitive (page->priv->primary_color_picker, TRUE);
+
+        if (!gconf_client_key_is_writable (client, WP_SCOLOR_KEY, NULL))
+                gtk_widget_set_sensitive (page->priv->secondary_color_picker, FALSE);
+        else
+                gtk_widget_set_sensitive (page->priv->secondary_color_picker, TRUE);
+
+        if (filename == NULL || strcmp (filename, "(none)") == 0)
+                gtk_widget_set_sensitive (page->priv->remove_button, FALSE);
+        else
+                gtk_widget_set_sensitive (page->priv->remove_button, TRUE);
+
+        g_object_unref (client);
+        g_free (filename);
+}
+
+static gboolean
+find_uri_in_model (GtkTreeModel *model,
+                   const char   *uri,
+                   GtkTreeIter  *piter)
+{
+        GtkTreeIter iter;
+        gboolean    valid;
+
+        if (uri == NULL) {
+                return FALSE;
+        }
+
+        for (valid = gtk_tree_model_get_iter_first (model, &iter);
+             valid;
+             valid = gtk_tree_model_iter_next (model, &iter)) {
+                CcBackgroundItem *item;
+                char             *filename;
+
+                item = NULL;
+                gtk_tree_model_get (model, &iter, COL_ITEM, &item, -1);
+                if (item == NULL)
+                        continue;
+
+                filename = NULL;
+                g_object_get (item, "filename", &filename, NULL);
+                g_object_unref (item);
+
+                if (filename != NULL) {
+                        int cmp = strcmp (filename, uri);
+                        g_free (filename);
+
+                        if (cmp == 0) {
+                                if (piter != NULL)
+                                        *piter = iter;
+                                return TRUE;
+                        }
+                }
+        }
+
+        return FALSE;
+}
+
+static void
+on_item_changed (CcBackgroundItem *item,
+                 CcBackgroundPage *page)
+{
+        GtkTreeIter iter;
+        char       *uri;
+
+        uri = NULL;
+        g_object_get (item, "filename", &uri, NULL);
+        if (find_uri_in_model (page->priv->model, uri, &iter)) {
+                GdkPixbuf *pixbuf;
+
+                g_signal_handlers_block_by_func (item, G_CALLBACK (on_item_changed), page);
+
+                pixbuf = cc_background_item_get_thumbnail (item,
+                                                           page->priv->thumb_factory,
+                                                           page->priv->thumb_width,
+                                                           page->priv->thumb_height);
+                if (pixbuf != NULL) {
+                        gtk_list_store_set (GTK_LIST_STORE (page->priv->model),
+                                            &iter,
+                                            COL_PIXBUF, pixbuf,
+                                            -1);
+                        g_object_unref (pixbuf);
+                }
+
+                g_signal_handlers_unblock_by_func (item, G_CALLBACK (on_item_changed), page);
+        }
+
+        g_free (uri);
+}
+
+static void
+load_item (CcBackgroundPage *page,
+           CcBackgroundItem *item)
+{
+        GtkTreeIter  iter;
+        GdkPixbuf   *pixbuf;
+        gboolean     deleted;
+
+        g_signal_connect (item,
+                          "changed",
+                          G_CALLBACK (on_item_changed),
+                          page);
+
+        g_object_get (item, "is-deleted", &deleted, NULL);
+        if (deleted == TRUE)
+                return;
+
+        gtk_list_store_append (GTK_LIST_STORE (page->priv->model), &iter);
+
+        pixbuf = cc_background_item_get_thumbnail (item,
+                                                   page->priv->thumb_factory,
+                                                   page->priv->thumb_width,
+                                                   page->priv->thumb_height);
+
+        gtk_list_store_set (GTK_LIST_STORE (page->priv->model),
+                            &iter,
+                            COL_PIXBUF, pixbuf,
+                            COL_ITEM, item,
+                            -1);
+
+        if (pixbuf != NULL)
+                g_object_unref (pixbuf);
+
+}
+
+static void
+load_item_iter (CcBackgroundItem *item,
+                CcBackgroundPage *page)
+{
+        load_item (page, item);
+}
+
+static gboolean
+reload_item (GtkTreeModel     *model,
+             GtkTreePath      *path,
+             GtkTreeIter      *iter,
+             CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        GdkPixbuf        *pixbuf;
+
+        gtk_tree_model_get (model, iter, COL_ITEM, &item, -1);
+
+        pixbuf = cc_background_item_get_thumbnail (item,
+                                                   page->priv->thumb_factory,
+                                                   page->priv->thumb_width,
+                                                   page->priv->thumb_height);
+        g_object_unref (item);
+
+        if (pixbuf) {
+                gtk_list_store_set (GTK_LIST_STORE (page->priv->model),
+                                    iter,
+                                    COL_PIXBUF, pixbuf,
+                                    -1);
+                g_object_unref (pixbuf);
+        }
+
+        return FALSE;
+}
+
+static gdouble
+get_monitor_aspect_ratio_for_widget (GtkWidget *widget)
+{
+        gdouble      aspect;
+        int          monitor;
+        GdkRectangle rect;
+
+        monitor = gdk_screen_get_monitor_at_window (gtk_widget_get_screen (widget),
+                                                    gtk_widget_get_window (widget));
+        gdk_screen_get_monitor_geometry (gtk_widget_get_screen (widget), monitor, &rect);
+        aspect = rect.height / (gdouble)rect.width;
+
+        return aspect;
+}
+
+#define LIST_IMAGE_SIZE 108
+
+static void
+compute_thumbnail_sizes (CcBackgroundPage *page)
+{
+        gdouble aspect;
+
+        aspect = get_monitor_aspect_ratio_for_widget (page->priv->icon_view);
+        if (aspect > 1) {
+                /* portrait */
+                page->priv->thumb_width = LIST_IMAGE_SIZE / aspect;
+                page->priv->thumb_height = LIST_IMAGE_SIZE;
+        } else {
+                page->priv->thumb_width = LIST_IMAGE_SIZE;
+                page->priv->thumb_height = LIST_IMAGE_SIZE * aspect;
+        }
+}
+
+static void
+reload_wallpapers (CcBackgroundPage *page)
+{
+        compute_thumbnail_sizes (page);
+        gtk_tree_model_foreach (page->priv->model,
+                                (GtkTreeModelForeachFunc)reload_item,
+                                page);
+}
+
+static void
+select_item (CcBackgroundPage *page,
+             CcBackgroundItem *item,
+             gboolean          scroll)
+{
+        GtkTreeIter  iter;
+        char        *uri;
+
+        if (item == NULL)
+                return;
+
+        uri = NULL;
+        g_object_get (item, "filename", &uri, NULL);
+        g_debug ("Selecting item %s", uri);
+        if (find_uri_in_model (page->priv->model, uri, &iter)) {
+                GtkTreePath *path;
+
+                path = gtk_tree_model_get_path (page->priv->model, &iter);
+                gtk_icon_view_select_path (GTK_ICON_VIEW (page->priv->icon_view), path);
+                if (scroll)
+                        gtk_icon_view_scroll_to_path (GTK_ICON_VIEW (page->priv->icon_view), path, FALSE, 0.5, 0.0);
+
+                gtk_tree_path_free (path);
+        }
+        g_free (uri);
+}
+
+static CcBackgroundItem *
+add_item_for_filename (CcBackgroundPage *page,
+                       const char       *filename)
+{
+        CcBackgroundItem *item;
+        gboolean          added;
+
+        if (filename == NULL)
+                return NULL;
+
+        added = FALSE;
+
+        item = cc_background_item_new (filename);
+        if (cc_background_item_load (item)) {
+                added = cc_backgrounds_monitor_add_item (page->priv->monitor, item);
+        }
+        if (!added) {
+                g_object_unref (item);
+                item = NULL;
+        }
+
+        return item;
+}
+
+/* FIXME: move to base class? */
+static GtkWidget *
+get_toplevel_window (CcBackgroundPage *page)
+{
+        GtkWidget *toplevel;
+
+        toplevel = gtk_widget_get_toplevel (GTK_WIDGET (page));
+        if (!GTK_WIDGET_TOPLEVEL (toplevel)) {
+                return NULL;
+        }
+
+        return toplevel;
+}
+
+static void
+enable_busy_cursor (CcBackgroundPage *page,
+                    gboolean          enable)
+{
+        GdkCursor    *cursor;
+        GtkWidget    *toplevel;
+        GdkWindow    *window;
+
+        toplevel = get_toplevel_window (page);
+        if (toplevel == NULL)
+                return;
+
+        window = gtk_widget_get_window (toplevel);
+
+        if (enable) {
+                cursor = gdk_cursor_new_for_display (gdk_display_get_default (),
+                                                     GDK_WATCH);
+        } else {
+                cursor = NULL;
+        }
+
+        gdk_window_set_cursor (window, cursor);
+
+        if (cursor != NULL)
+                gdk_cursor_unref (cursor);
+}
+
+static void
+add_items_for_filenames (CcBackgroundPage *page,
+                         GSList           *filenames)
+{
+        CcBackgroundItem *item;
+
+        enable_busy_cursor (page, TRUE);
+
+        item = NULL;
+        while (filenames != NULL) {
+                gchar *uri = filenames->data;
+
+                item = add_item_for_filename (page, uri);
+                g_object_unref (item);
+                filenames = g_slist_remove (filenames, uri);
+                g_free (uri);
+        }
+
+        enable_busy_cursor (page, FALSE);
+
+        if (item != NULL) {
+                select_item (page, item, TRUE);
+        }
+}
+
+static void
+ui_update_option_menu (CcBackgroundPage *page,
+                       int               value)
+{
+        gtk_combo_box_set_active (GTK_COMBO_BOX (page->priv->style_menu),
+                                  value);
+}
+
+static void
+ui_update_shade_menu (CcBackgroundPage *page,
+                      int               value)
+{
+        gtk_combo_box_set_active (GTK_COMBO_BOX (page->priv->color_menu),
+                                  value);
+
+        if (value == SHADE_SOLID)
+                gtk_widget_hide (page->priv->secondary_color_picker);
+        else
+                gtk_widget_show (page->priv->secondary_color_picker);
+}
+
+static void
+on_item_added (CcBackgroundsMonitor *monitor,
+               CcBackgroundItem     *item,
+               CcBackgroundPage     *page)
+{
+        g_debug ("Item added");
+        load_item (page, item);
+}
+
+static void
+on_item_removed (CcBackgroundsMonitor *monitor,
+                 CcBackgroundItem     *item,
+                 CcBackgroundPage     *page)
+{
+        GtkTreeIter iter;
+        char       *uri;
+
+        uri = NULL;
+        g_object_get (item, "filename", &uri, NULL);
+        g_debug ("Item removed: %s", uri);
+        if (find_uri_in_model (page->priv->model, uri, &iter)) {
+                gtk_list_store_remove (GTK_LIST_STORE (page->priv->model), &iter);
+        }
+        g_free (uri);
+}
+
+static void
+update_ui_from_gconf (CcBackgroundPage *page)
+{
+        GConfClient      *client;
+        char             *uri;
+        char             *path;
+        CcBackgroundItem *item;
+        GtkTreeIter       iter;
+        gboolean          already_added;
+
+        client = gconf_client_get_default ();
+
+        uri = gconf_client_get_string (client,
+                                       WP_FILE_KEY,
+                                       NULL);
+
+        if (uri != NULL && *uri == '\0') {
+                g_free (uri);
+                uri = NULL;
+        }
+        if (uri == NULL)
+                uri = g_strdup ("(none)");
+
+        path = NULL;
+        if (g_utf8_validate (uri, -1, NULL)
+            && g_file_test (uri, G_FILE_TEST_EXISTS))
+                path = g_strdup (uri);
+        else
+                path = g_filename_from_utf8 (uri, -1, NULL, NULL, NULL);
+        g_free (uri);
+
+        /* now update or add item */
+        already_added = FALSE;
+        item = NULL;
+        if (find_uri_in_model (page->priv->model, path, &iter)) {
+                gtk_tree_model_get (page->priv->model, &iter, COL_ITEM, &item, -1);
+                already_added = TRUE;
+        } else {
+                item = cc_background_item_new (path);
+        }
+        g_free (path);
+
+        if (item != NULL) {
+                char             *placement;
+                char             *shading;
+                char             *primary_color;
+                char             *secondary_color;
+
+                placement = gconf_client_get_string (client,
+                                                     WP_OPTIONS_KEY,
+                                                     NULL);
+                shading = gconf_client_get_string (client,
+                                                   WP_SHADING_KEY,
+                                                   NULL);
+                primary_color = gconf_client_get_string (client,
+                                                         WP_PCOLOR_KEY,
+                                                         NULL);
+                secondary_color = gconf_client_get_string (client,
+                                                           WP_SCOLOR_KEY,
+                                                           NULL);
+                if (placement == NULL)
+                        placement = g_strdup ("none");
+
+                g_object_unref (client);
+
+
+                g_object_set (item,
+                              "primary-color", primary_color,
+                              "secondary-color", secondary_color,
+                              "placement", placement,
+                              "shading", shading,
+                              NULL);
+
+                if (cc_background_item_load (item)) {
+                        if (!already_added)
+                                cc_backgrounds_monitor_add_item (page->priv->monitor, item);
+                }
+                select_item (page, item, TRUE);
+                g_object_unref (item);
+        }
+}
+
+static gboolean
+load_stuffs (CcBackgroundPage *page)
+{
+        GList *items;
+
+        compute_thumbnail_sizes (page);
+
+        g_debug ("Beginning to load");
+        cc_backgrounds_monitor_load (page->priv->monitor);
+        g_debug ("Finished loading");
+        items = cc_backgrounds_monitor_get_items (page->priv->monitor);
+        g_list_foreach (items, (GFunc)load_item_iter, page);
+        g_list_free (items);
+
+        g_signal_connect (page->priv->monitor,
+                          "item-added",
+                          G_CALLBACK (on_item_added),
+                          page);
+        g_signal_connect (page->priv->monitor,
+                          "item-removed",
+                          G_CALLBACK (on_item_removed),
+                          page);
+
+        update_ui_from_gconf (page);
+
+        return FALSE;
+}
+
+static void
+on_icon_view_realize (GtkWidget        *widget,
+                      CcBackgroundPage *page);
+
+static void
+on_icon_view_realize (GtkWidget        *widget,
+                      CcBackgroundPage *page)
+{
+        g_idle_add ((GSourceFunc)load_stuffs, page);
+        /* only run once */
+        g_signal_handlers_disconnect_by_func (widget, on_icon_view_realize, page);
+}
+
+static void
+next_frame (CcBackgroundPage *page,
+            GtkCellRenderer  *cr,
+            int               direction)
+{
+        CcBackgroundItem *item;
+        GtkTreeIter       iter;
+        GdkPixbuf        *pixbuf;
+        GdkPixbuf        *pb;
+        int               frame;
+
+        pixbuf = NULL;
+
+        frame = page->priv->frame + direction;
+        item = get_selected_item (page, &iter);
+        if (item == NULL)
+                return;
+
+        if (frame >= 0)
+                pixbuf = cc_background_item_get_frame_thumbnail (item,
+                                                                 page->priv->thumb_factory,
+                                                                 page->priv->thumb_width,
+                                                                 page->priv->thumb_height,
+                                                                 frame);
+        if (pixbuf) {
+                gtk_list_store_set (GTK_LIST_STORE (page->priv->model),
+                                    &iter,
+                                    COL_PIXBUF, pixbuf,
+                                    -1);
+                g_object_unref (pixbuf);
+                page->priv->frame = frame;
+        }
+
+        pb = buttons[1];
+        if (direction < 0) {
+                if (frame == 0)
+                        pb = buttons[0];
+        } else {
+                pixbuf = cc_background_item_get_frame_thumbnail (item,
+                                                                 page->priv->thumb_factory,
+                                                                 page->priv->thumb_width,
+                                                                 page->priv->thumb_height,
+                                                                 frame + 1);
+                if (pixbuf)
+                        g_object_unref (pixbuf);
+                else
+                        pb = buttons[2];
+        }
+        g_object_set (cr, "pixbuf", pb, NULL);
+        g_object_unref (item);
+}
+
+static gboolean
+on_icon_view_button_press (GtkWidget        *widget,
+                           GdkEventButton   *event,
+                           CcBackgroundPage *page)
+{
+        GtkCellRenderer *cell;
+
+        if (event->type != GDK_BUTTON_PRESS)
+                return FALSE;
+
+        if (gtk_icon_view_get_item_at_pos (GTK_ICON_VIEW (widget),
+                                           event->x,
+                                           event->y,
+                                           NULL,
+                                           &cell)) {
+                if (g_object_get_data (G_OBJECT (cell), "buttons")) {
+                        int              w;
+                        int              h;
+                        GtkCellRenderer *cell2 = NULL;
+
+                        gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &w, &h);
+                        if (gtk_icon_view_get_item_at_pos (GTK_ICON_VIEW (widget),
+                                                           event->x + w,
+                                                           event->y,
+                                                           NULL, &cell2)
+                            && cell == cell2) {
+                                next_frame (page, cell, -1);
+                        } else {
+                                next_frame (page, cell, 1);
+                        }
+                        return TRUE;
+                }
+        }
+
+        return FALSE;
+}
+
+static void
+update_gconf_from_item (CcBackgroundPage *page,
+                        CcBackgroundItem *item)
+{
+        GConfClient    *client;
+        GConfChangeSet *cs;
+        char           *pcolor;
+        char           *scolor;
+        char           *filename;
+        char           *shade;
+        char           *scale;
+
+        if (item == NULL) {
+                return;
+        }
+
+        cs = gconf_change_set_new ();
+
+        g_object_get (item,
+                      "filename", &filename,
+                      "shading", &shade,
+                      "placement", &scale,
+                      "primary-color", &pcolor,
+                      "secondary-color", &scolor,
+                      NULL);
+
+        if (strcmp (filename, "(none)") == 0) {
+                gconf_change_set_set_string (cs, WP_OPTIONS_KEY, "none");
+                gconf_change_set_set_string (cs, WP_FILE_KEY, "");
+        } else {
+                gchar *uri;
+
+                if (g_utf8_validate (filename, -1, NULL))
+                        uri = g_strdup (filename);
+                else
+                        uri = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
+
+                if (uri == NULL) {
+                        g_warning ("Failed to convert filename to UTF-8: %s", filename);
+                } else {
+                        gconf_change_set_set_string (cs, WP_FILE_KEY, uri);
+                        g_free (uri);
+                }
+
+                gconf_change_set_set_string (cs,
+                                             WP_OPTIONS_KEY,
+                                             scale);
+        }
+
+        gconf_change_set_set_string (cs,
+                                     WP_SHADING_KEY,
+                                     shade);
+        gconf_change_set_set_string (cs, WP_PCOLOR_KEY, pcolor);
+        gconf_change_set_set_string (cs, WP_SCOLOR_KEY, scolor);
+
+        g_free (filename);
+        g_free (shade);
+        g_free (scale);
+        g_free (pcolor);
+        g_free (scolor);
+
+        client = gconf_client_get_default ();
+        gconf_client_commit_change_set (client, cs, TRUE, NULL);
+        g_object_unref (client);
+
+        gconf_change_set_unref (cs);
+}
+
+static void
+on_icon_view_selection_changed (GtkIconView      *view,
+                                CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        GtkCellRenderer  *cr;
+        GList            *cells;
+        GList            *l;
+
+        /* update the frame buttons */
+        page->priv->frame = -1;
+        cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (view));
+        for (l = cells; l; l = l->next) {
+                cr = l->data;
+                if (g_object_get_data (G_OBJECT (cr), "buttons"))
+                        g_object_set (cr,
+                                      "pixbuf", buttons[0],
+                                      NULL);
+        }
+        g_list_free (cells);
+
+        item = get_selected_item (page, NULL);
+        g_debug ("Selection changed %p", item);
+        if (item != NULL) {
+                update_gconf_from_item (page, item);
+                g_object_unref (item);
+        }
+}
+
+static void
+buttons_cell_data_func (GtkCellLayout    *layout,
+                        GtkCellRenderer  *cell,
+                        GtkTreeModel     *model,
+                        GtkTreeIter      *iter,
+                        CcBackgroundPage *page)
+{
+        GtkTreePath      *path;
+        CcBackgroundItem *item;
+        gboolean          visible;
+
+        path = gtk_tree_model_get_path (model, iter);
+
+        visible = FALSE;
+        if (gtk_icon_view_path_is_selected (GTK_ICON_VIEW (layout), path)) {
+                item = get_selected_item (page, NULL);
+                if (item != NULL) {
+                        visible = cc_background_item_changes_with_time (item);
+                        g_object_unref (item);
+                }
+        }
+
+        g_object_set (G_OBJECT (cell),
+                      "visible", visible,
+                      NULL);
+
+        gtk_tree_path_free (path);
+}
+
+static void
+update_file_chooser_preview (GtkFileChooser   *chooser,
+                             CcBackgroundPage *page)
+{
+        char       *uri;
+        GdkPixbuf  *pixbuf;
+        const char *mime_type;
+        GFile      *file;
+        GFileInfo  *file_info;
+
+        gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
+
+        uri = gtk_file_chooser_get_preview_uri (chooser);
+        if (uri == NULL) {
+                return;
+
+        }
+
+        file = g_file_new_for_uri (uri);
+        file_info = g_file_query_info (file,
+                                       G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+                                       G_FILE_QUERY_INFO_NONE,
+                                       NULL, NULL);
+        g_object_unref (file);
+
+        mime_type = NULL;
+        if (file_info != NULL) {
+                mime_type = g_file_info_get_content_type (file_info);
+                g_object_unref (file_info);
+        }
+
+        pixbuf = NULL;
+        if (mime_type != NULL) {
+                pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (page->priv->thumb_factory,
+                                                                             uri,
+                                                                             mime_type);
+        }
+
+        if (pixbuf != NULL) {
+                gtk_image_set_from_pixbuf (GTK_IMAGE (page->priv->file_chooser_preview), pixbuf);
+                g_object_unref (pixbuf);
+        } else {
+                gtk_image_set_from_stock (GTK_IMAGE (page->priv->file_chooser_preview),
+                                          "gtk-dialog-question",
+                                          GTK_ICON_SIZE_DIALOG);
+        }
+}
+
+static void
+create_filechooser (CcBackgroundPage *page)
+{
+        const char    *start_dir;
+        const char    *pictures;
+        GtkFileFilter *filter;
+        GtkWidget     *toplevel;
+        GtkWindow     *window;
+
+        window = NULL;
+        toplevel = gtk_widget_get_toplevel (GTK_WIDGET (page));
+        if (GTK_WIDGET_TOPLEVEL (toplevel)) {
+                window = GTK_WINDOW (toplevel);
+        }
+
+        page->priv->file_chooser = gtk_file_chooser_dialog_new (_("Add Wallpaper"),
+                                                                window,
+                                                                GTK_FILE_CHOOSER_ACTION_OPEN,
+                                                                GTK_STOCK_CANCEL,
+                                                                GTK_RESPONSE_CANCEL,
+                                                                GTK_STOCK_OPEN,
+                                                                GTK_RESPONSE_OK,
+                                                                NULL);
+
+        gtk_dialog_set_default_response (GTK_DIALOG (page->priv->file_chooser), GTK_RESPONSE_OK);
+        gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (page->priv->file_chooser), TRUE);
+        gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (page->priv->file_chooser), FALSE);
+
+        start_dir = g_get_home_dir ();
+        if (g_file_test (BACKGROUNDS_DIR, G_FILE_TEST_IS_DIR)) {
+                gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (page->priv->file_chooser),
+                                                      BACKGROUNDS_DIR,
+                                                      NULL);
+                start_dir = BACKGROUNDS_DIR;
+        }
+
+        pictures = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+        if (pictures != NULL
+            && g_file_test (pictures, G_FILE_TEST_IS_DIR)) {
+                gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (page->priv->file_chooser),
+                                                      pictures,
+                                                      NULL);
+                start_dir = pictures;
+        }
+
+        gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (page->priv->file_chooser),
+                                             start_dir);
+
+        filter = gtk_file_filter_new ();
+        gtk_file_filter_add_pixbuf_formats (filter);
+        gtk_file_filter_set_name (filter, _("Images"));
+        gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (page->priv->file_chooser),
+                                     filter);
+
+        filter = gtk_file_filter_new ();
+        gtk_file_filter_set_name (filter, _("All files"));
+        gtk_file_filter_add_pattern (filter, "*");
+        gtk_file_chooser_add_filter (GTK_FILE_CHOOSER (page->priv->file_chooser), filter);
+
+        page->priv->file_chooser_preview = gtk_image_new ();
+        gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (page->priv->file_chooser),
+                                             page->priv->file_chooser_preview);
+        gtk_widget_set_size_request (page->priv->file_chooser_preview, 128, -1);
+
+        gtk_widget_show (page->priv->file_chooser_preview);
+
+        g_signal_connect (page->priv->file_chooser,
+                          "update-preview",
+                          (GCallback) update_file_chooser_preview,
+                          page);
+}
+
+static void
+on_add_button_clicked (GtkWidget        *widget,
+                       CcBackgroundPage *page)
+{
+        GSList *files;
+
+        if (page->priv->file_chooser == NULL)
+                create_filechooser (page);
+
+        switch (gtk_dialog_run (GTK_DIALOG (page->priv->file_chooser))) {
+        case GTK_RESPONSE_OK:
+                files = gtk_file_chooser_get_filenames (GTK_FILE_CHOOSER (page->priv->file_chooser));
+                add_items_for_filenames (page, files);
+        case GTK_RESPONSE_CANCEL:
+        default:
+                gtk_widget_hide (page->priv->file_chooser);
+                break;
+        }
+}
+
+static void
+on_remove_button_clicked (GtkWidget        *widget,
+                          CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        GtkTreeIter       iter;
+        GtkTreePath      *path;
+
+        item = get_selected_item (page, &iter);
+        if (item == NULL) {
+                return;
+        }
+
+        if (cc_backgrounds_monitor_remove_item (page->priv->monitor, item)) {
+                path = gtk_tree_model_get_path (page->priv->model, &iter);
+        } else {
+                path = gtk_tree_path_new_first ();
+        }
+
+        gtk_icon_view_select_path (GTK_ICON_VIEW (page->priv->icon_view), path);
+        gtk_icon_view_set_cursor (GTK_ICON_VIEW (page->priv->icon_view), path, NULL, FALSE);
+        gtk_tree_path_free (path);
+
+        g_object_unref (item);
+}
+
+static void
+wp_drag_received (GtkWidget        *widget,
+                  GdkDragContext   *context,
+                  int               x,
+                  int               y,
+                  GtkSelectionData *selection_data,
+                  guint             info,
+                  guint             time,
+                  CcBackgroundPage *page)
+{
+        if (info == TARGET_URI_LIST
+            || info == TARGET_BGIMAGE) {
+                GSList *realuris = NULL;
+                gchar **uris;
+
+                uris = g_uri_list_extract_uris ((char *) selection_data->data);
+                if (uris != NULL) {
+                        char **uri;
+
+                        enable_busy_cursor (page, TRUE);
+
+                        for (uri = uris; *uri; ++uri) {
+                                GFile *f;
+
+                                f = g_file_new_for_uri (*uri);
+                                realuris = g_slist_append (realuris, g_file_get_path (f));
+                                g_object_unref (f);
+                        }
+
+                        add_items_for_filenames (page, realuris);
+
+                        enable_busy_cursor (page, FALSE);
+
+                        g_strfreev (uris);
+                }
+        }
+}
+
+static void
+wp_drag_get_data (GtkWidget        *widget,
+                  GdkDragContext   *context,
+                  GtkSelectionData *selection_data,
+                  guint             type,
+                  guint             time,
+                  CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        char             *uris[2];
+        char             *filename;
+
+        if (type != TARGET_URI_LIST) {
+                return;
+        }
+
+        item = get_selected_item (page, NULL);
+        if (item == NULL) {
+                return;
+        }
+        g_object_get (item,
+                      "filename", &filename,
+                      NULL);
+        uris[0] = g_filename_to_uri (filename, NULL, NULL);
+        uris[1] = NULL;
+
+        gtk_selection_data_set_uris (selection_data, uris);
+
+        g_free (uris[0]);
+        g_free (filename);
+        g_object_unref (item);
+}
+
+static gboolean
+on_query_tooltip (GtkWidget        *widget,
+                  int               x,
+                  int               y,
+                  gboolean          keyboard_mode,
+                  GtkTooltip       *tooltip,
+                  CcBackgroundPage *page)
+{
+        GtkTreeIter       iter;
+        CcBackgroundItem *item;
+
+        if (gtk_icon_view_get_tooltip_context (GTK_ICON_VIEW (page->priv->icon_view),
+                                               &x, &y,
+                                               keyboard_mode,
+                                               NULL,
+                                               NULL,
+                                               &iter)) {
+                gtk_tree_model_get (page->priv->model, &iter, COL_ITEM, &item, -1);
+                if (item != NULL) {
+                        char *description;
+                        g_object_get (item, "description", &description, NULL);
+                        gtk_tooltip_set_markup (tooltip, description);
+                        g_free (description);
+                        g_object_unref (item);
+                }
+
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+static gint
+sort_model_iter (GtkTreeModel     *model,
+                 GtkTreeIter      *a,
+                 GtkTreeIter      *b,
+                 CcBackgroundPage *page)
+
+{
+        CcBackgroundItem *itema;
+        CcBackgroundItem *itemb;
+        char             *filenamea;
+        char             *filenameb;
+        char             *descriptiona;
+        char             *descriptionb;
+        int               retval;
+
+        gtk_tree_model_get (model, a, COL_ITEM, &itema, -1);
+        gtk_tree_model_get (model, b, COL_ITEM, &itemb, -1);
+
+        g_object_get (itema,
+                      "filename", &filenamea,
+                      "description", &descriptiona,
+                      NULL);
+        g_object_get (itemb,
+                      "filename", &filenameb,
+                      "description", &descriptionb,
+                      NULL);
+        if (strcmp (filenamea, "(none)") == 0) {
+                retval =  -1;
+        } else if (strcmp (filenameb, "(none)") == 0) {
+                retval =  1;
+        } else if (descriptiona != NULL && descriptionb != NULL) {
+                retval = g_utf8_collate (descriptiona, descriptionb);
+        } else {
+                retval = 0;
+        }
+        g_free (filenamea);
+        g_free (filenameb);
+        g_free (descriptiona);
+        g_free (descriptionb);
+        if (itema != NULL)
+                g_object_unref (itema);
+        if (itemb != NULL)
+                g_object_unref (itemb);
+
+        return retval;
+}
+
+static void
+screen_monitors_changed (GdkScreen        *screen,
+                         CcBackgroundPage *page)
+{
+        reload_wallpapers (page);
+}
+
+static void
+setup_page (CcBackgroundPage *page)
+{
+        GtkBuilder      *builder;
+        GtkWidget       *widget;
+        GError          *error;
+        GtkCellRenderer *cr;
+
+        builder = gtk_builder_new ();
+
+        error = NULL;
+        gtk_builder_add_from_file (builder,
+                                   GNOMECC_UI_DIR
+                                   "/appearance.ui",
+                                   &error);
+        if (error != NULL) {
+                g_error (_("Could not load user interface file: %s"),
+                         error->message);
+                g_error_free (error);
+                return;
+        }
+
+        page->priv->model = GTK_TREE_MODEL (gtk_list_store_new (2,
+                                                                GDK_TYPE_PIXBUF,
+                                                                G_TYPE_OBJECT));
+
+        page->priv->icon_view = WID ("wp_view");
+        gtk_icon_view_set_model (GTK_ICON_VIEW (page->priv->icon_view),
+                                 GTK_TREE_MODEL (page->priv->model));
+
+        g_signal_connect_after (page->priv->icon_view,
+                                "realize",
+                                (GCallback) on_icon_view_realize,
+                                page);
+
+        gtk_cell_layout_clear (GTK_CELL_LAYOUT (page->priv->icon_view));
+
+        cr = gtk_cell_renderer_pixbuf_new ();
+        g_object_set (cr, "xpad", 5, "ypad", 5, NULL);
+
+        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (page->priv->icon_view), cr, TRUE);
+        gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (page->priv->icon_view),
+                                        cr,
+                                        "pixbuf", 0,
+                                        NULL);
+
+        cr = gtk_cell_renderer_pixbuf_new ();
+        create_button_images (page);
+        g_object_set (cr,
+                      "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE,
+                      "pixbuf", buttons[0],
+                      NULL);
+        g_object_set_data (G_OBJECT (cr), "buttons", GINT_TO_POINTER (TRUE));
+
+        gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (page->priv->icon_view), cr, FALSE);
+        gtk_cell_layout_set_cell_data_func (GTK_CELL_LAYOUT (page->priv->icon_view),
+                                            cr,
+                                            (GtkCellLayoutDataFunc) buttons_cell_data_func,
+                                            page,
+                                            NULL);
+
+        gtk_widget_set_has_tooltip (page->priv->icon_view, TRUE);
+        g_signal_connect (page->priv->icon_view,
+                          "selection-changed",
+                          (GCallback) on_icon_view_selection_changed,
+                          page);
+        g_signal_connect (page->priv->icon_view,
+                          "button-press-event",
+                          G_CALLBACK (on_icon_view_button_press),
+                          page);
+        g_signal_connect (page->priv->icon_view,
+                          "query-tooltip",
+                          (GCallback) on_query_tooltip,
+                          page);
+
+        page->priv->frame = -1;
+
+        gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (page->priv->model),
+                                         1,
+                                         (GtkTreeIterCompareFunc) sort_model_iter,
+                                         page,
+                                         NULL);
+
+        gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (page->priv->model),
+                                              1,
+                                              GTK_SORT_ASCENDING);
+
+        gtk_drag_dest_set (page->priv->icon_view,
+                           GTK_DEST_DEFAULT_ALL,
+                           drop_types,
+                           G_N_ELEMENTS (drop_types),
+                           GDK_ACTION_COPY | GDK_ACTION_MOVE);
+        g_signal_connect (page->priv->icon_view,
+                          "drag_data_received",
+                          (GCallback) wp_drag_received,
+                          page);
+
+        gtk_drag_source_set (page->priv->icon_view,
+                             GDK_BUTTON1_MASK,
+                             drag_types,
+                             G_N_ELEMENTS (drag_types),
+                             GDK_ACTION_COPY);
+        g_signal_connect (page->priv->icon_view,
+                          "drag-data-get",
+                          (GCallback) wp_drag_get_data,
+                          page);
+
+        page->priv->style_menu = WID ("wp_style_menu");
+        g_signal_connect (page->priv->style_menu,
+                          "changed",
+                          (GCallback) on_style_menu_changed,
+                          page);
+
+        page->priv->color_menu = WID ("wp_color_menu");
+        g_signal_connect (page->priv->color_menu,
+                          "changed",
+                          (GCallback) on_color_menu_changed,
+                          page);
+
+        page->priv->secondary_color_picker = WID ("wp_scpicker");
+        g_signal_connect (page->priv->secondary_color_picker,
+                          "color-set",
+                          (GCallback) on_color_changed,
+                          page);
+
+        page->priv->primary_color_picker = WID ("wp_pcpicker");
+        g_signal_connect (page->priv->primary_color_picker,
+                          "color-set",
+                          (GCallback) on_color_changed,
+                          page);
+
+        widget = WID ("wp_add_button");
+        g_signal_connect (widget,
+                          "clicked",
+                          (GCallback) on_add_button_clicked,
+                          page);
+
+        page->priv->remove_button = WID ("wp_rem_button");
+        g_signal_connect (page->priv->remove_button,
+                          "clicked",
+                          (GCallback) on_remove_button_clicked,
+                          page);
+
+        /* FIXME: only register after initial load? */
+        page->priv->screen_monitors_handler = g_signal_connect (gtk_widget_get_screen (page->priv->icon_view),
+                                                                "monitors-changed",
+                                                                G_CALLBACK (screen_monitors_changed),
+                                                                page);
+        page->priv->screen_size_handler = g_signal_connect (gtk_widget_get_screen (page->priv->icon_view),
+                                                            "size-changed",
+                                                            G_CALLBACK (screen_monitors_changed),
+                                                            page);
+
+
+        ui_update_sensitivities (page);
+
+        /* create the file selector later to save time on startup */
+
+        widget = WID ("background_vbox");
+        gtk_widget_reparent (widget, GTK_WIDGET (page));
+        gtk_widget_show (widget);
+}
+
+static GObject *
+cc_background_page_constructor (GType                  type,
+                               guint                  n_construct_properties,
+                               GObjectConstructParam *construct_properties)
+{
+        CcBackgroundPage      *background_page;
+
+        background_page = CC_BACKGROUND_PAGE (G_OBJECT_CLASS (cc_background_page_parent_class)->constructor (type,
+                                                                                                                n_construct_properties,
+                                                                                                                construct_properties));
+
+        g_object_set (background_page,
+                      "display-name", _("Background"),
+                      "id", "background",
+                      NULL);
+
+        setup_page (background_page);
+
+        return G_OBJECT (background_page);
+}
+
+static void
+cc_background_page_class_init (CcBackgroundPageClass *klass)
+{
+        GObjectClass  *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = cc_background_page_get_property;
+        object_class->set_property = cc_background_page_set_property;
+        object_class->constructor = cc_background_page_constructor;
+        object_class->finalize = cc_background_page_finalize;
+
+        g_type_class_add_private (klass, sizeof (CcBackgroundPagePrivate));
+}
+
+static void
+change_current_uri (CcBackgroundPage *page,
+                    const char       *uri)
+{
+        CcBackgroundItem *item;
+        GtkTreeIter       iter;
+
+        item = NULL;
+        g_debug ("Looking for %s", uri);
+        if (find_uri_in_model (page->priv->model, uri, &iter)) {
+                gtk_tree_model_get (page->priv->model, &iter, COL_ITEM, &item, -1);
+        } else {
+                item = add_item_for_filename (page, uri);
+                if (item != NULL)
+                        g_object_ref (item);
+        }
+        if (item != NULL) {
+                select_item (page, item, TRUE);
+                g_object_unref (item);
+        }
+}
+
+static void
+on_gconf_file_changed (GConfClient      *client,
+                       guint             id,
+                       GConfEntry       *entry,
+                       CcBackgroundPage *page)
+{
+        const char *uri;
+        char       *path;
+
+        uri = gconf_value_get_string (entry->value);
+
+        path = NULL;
+        if (uri == NULL || *uri == '\0') {
+                path = g_strdup ("(none)");
+        } else {
+                if (g_utf8_validate (uri, -1, NULL)
+                    && g_file_test (uri, G_FILE_TEST_EXISTS))
+                        path = g_strdup (uri);
+                else
+                        path = g_filename_from_utf8 (uri, -1, NULL, NULL, NULL);
+        }
+
+        change_current_uri (page, path);
+
+        g_free (path);
+}
+
+static void
+on_gconf_options_changed (GConfClient      *client,
+                          guint             id,
+                          GConfEntry       *entry,
+                          CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        const char       *placement;
+
+        placement = gconf_value_get_string (entry->value);
+
+        /* "none" means we don't use a background image */
+        if (placement == NULL
+            || strcmp (placement, "none") == 0) {
+                change_current_uri (page, "(none)");
+                return;
+        }
+
+        ui_update_option_menu (page, string_to_option (placement));
+
+        item = get_selected_item (page, NULL);
+        if (item != NULL) {
+                g_object_set (item, "placement", placement, NULL);
+                cc_background_item_load (item);
+                g_object_unref (item);
+        }
+}
+
+static void
+on_gconf_shading_changed (GConfClient      *client,
+                          guint             id,
+                          GConfEntry       *entry,
+                          CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        const char       *shading;
+
+        shading = gconf_value_get_string (entry->value);
+        ui_update_shade_menu (page, string_to_shade (shading));
+
+        item = get_selected_item (page, NULL);
+        if (item != NULL) {
+                g_object_set (item, "shading", shading, NULL);
+                cc_background_item_load (item);
+                g_object_unref (item);
+        }
+}
+
+static void
+on_gconf_color1_changed (GConfClient      *client,
+                         guint             id,
+                         GConfEntry       *entry,
+                         CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        GdkColor          color;
+        const char       *colorhex;
+
+        colorhex = gconf_value_get_string (entry->value);
+
+        gdk_color_parse (colorhex, &color);
+        gtk_color_button_set_color (GTK_COLOR_BUTTON (page->priv->primary_color_picker),
+                                    &color);
+
+        item = get_selected_item (page, NULL);
+        if (item != NULL) {
+                g_object_set (item, "primary-color", colorhex, NULL);
+                cc_background_item_load (item);
+                g_object_unref (item);
+        }
+}
+
+static void
+on_gconf_color2_changed (GConfClient      *client,
+                         guint             id,
+                         GConfEntry       *entry,
+                         CcBackgroundPage *page)
+{
+        CcBackgroundItem *item;
+        GdkColor          color;
+        const char       *colorhex;
+
+        ui_update_sensitivities (page);
+
+        colorhex = gconf_value_get_string (entry->value);
+
+        gdk_color_parse (colorhex, &color);
+        gtk_color_button_set_color (GTK_COLOR_BUTTON (page->priv->secondary_color_picker),
+                                    &color);
+
+        item = get_selected_item (page, NULL);
+        if (item != NULL) {
+                g_object_set (item, "secondary-color", colorhex, NULL);
+                cc_background_item_load (item);
+                g_object_unref (item);
+        }
+}
+
+static void
+cc_background_page_init (CcBackgroundPage *page)
+{
+        GConfClient *client;
+
+        page->priv = CC_BACKGROUND_PAGE_GET_PRIVATE (page);
+
+        page->priv->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+
+        page->priv->monitor = cc_backgrounds_monitor_new ();
+
+        client = gconf_client_get_default ();
+        gconf_client_add_dir (client,
+                              WP_PATH_KEY,
+                              GCONF_CLIENT_PRELOAD_ONELEVEL,
+                              NULL);
+
+        gconf_client_notify_add (client,
+                                 WP_FILE_KEY,
+                                 (GConfClientNotifyFunc) on_gconf_file_changed,
+                                 page, NULL, NULL);
+        gconf_client_notify_add (client,
+                                 WP_OPTIONS_KEY,
+                                 (GConfClientNotifyFunc) on_gconf_options_changed,
+                                 page, NULL, NULL);
+        gconf_client_notify_add (client,
+                                 WP_SHADING_KEY,
+                                 (GConfClientNotifyFunc) on_gconf_shading_changed,
+                                 page, NULL, NULL);
+        gconf_client_notify_add (client,
+                                 WP_PCOLOR_KEY,
+                                 (GConfClientNotifyFunc) on_gconf_color1_changed,
+                                 page, NULL, NULL);
+        gconf_client_notify_add (client,
+                                 WP_SCOLOR_KEY,
+                                 (GConfClientNotifyFunc) on_gconf_color2_changed,
+                                 page, NULL, NULL);
+
+        g_object_unref (client);
+
+}
+
+static void
+cc_background_page_finalize (GObject *object)
+{
+        CcBackgroundPage *page;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (CC_IS_BACKGROUND_PAGE (object));
+
+        page = CC_BACKGROUND_PAGE (object);
+
+        g_return_if_fail (page->priv != NULL);
+
+        if (page->priv->screen_monitors_handler > 0) {
+                g_signal_handler_disconnect (gtk_widget_get_screen (page->priv->icon_view),
+                                             page->priv->screen_monitors_handler);
+                page->priv->screen_monitors_handler = 0;
+        }
+        if (page->priv->screen_size_handler > 0) {
+                g_signal_handler_disconnect (gtk_widget_get_screen (page->priv->icon_view),
+                                             page->priv->screen_size_handler);
+                page->priv->screen_size_handler = 0;
+        }
+
+        if (page->priv->file_chooser != NULL) {
+                g_object_ref_sink (page->priv->file_chooser);
+                g_object_unref (page->priv->file_chooser);
+        }
+
+        if (page->priv->monitor != NULL) {
+                cc_backgrounds_monitor_save (page->priv->monitor);
+                g_object_unref (page->priv->monitor);
+        }
+
+        G_OBJECT_CLASS (cc_background_page_parent_class)->finalize (object);
+}
+
+CcPage *
+cc_background_page_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (CC_TYPE_BACKGROUND_PAGE, NULL);
+
+        return CC_PAGE (object);
+}
diff --git a/capplets/appearance/cc-background-page.h b/capplets/appearance/cc-background-page.h
new file mode 100644
index 0000000..58b729b
--- /dev/null
+++ b/capplets/appearance/cc-background-page.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __CC_BACKGROUND_PAGE_H
+#define __CC_BACKGROUND_PAGE_H
+
+#include <gtk/gtk.h>
+#include "cc-page.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUND_PAGE         (cc_background_page_get_type ())
+#define CC_BACKGROUND_PAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_BACKGROUND_PAGE, CcBackgroundPage))
+#define CC_BACKGROUND_PAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_BACKGROUND_PAGE, CcBackgroundPageClass))
+#define CC_IS_BACKGROUND_PAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_BACKGROUND_PAGE))
+#define CC_IS_BACKGROUND_PAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_BACKGROUND_PAGE))
+#define CC_BACKGROUND_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_BACKGROUND_PAGE, CcBackgroundPageClass))
+
+typedef struct CcBackgroundPagePrivate CcBackgroundPagePrivate;
+
+typedef struct
+{
+        CcPage                parent;
+        CcBackgroundPagePrivate *priv;
+} CcBackgroundPage;
+
+typedef struct
+{
+        CcPageClass   parent_class;
+} CcBackgroundPageClass;
+
+GType              cc_background_page_get_type   (void);
+
+CcPage *           cc_background_page_new        (void);
+
+G_END_DECLS
+
+#endif /* __CC_BACKGROUND_PAGE_H */
diff --git a/capplets/appearance/cc-backgrounds-monitor.c b/capplets/appearance/cc-backgrounds-monitor.c
new file mode 100644
index 0000000..a18bfa5
--- /dev/null
+++ b/capplets/appearance/cc-backgrounds-monitor.c
@@ -0,0 +1,714 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#include "cc-backgrounds-monitor.h"
+#include "cc-background-item.h"
+
+#define CC_BACKGROUNDS_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_BACKGROUNDS_MONITOR, CcBackgroundsMonitorPrivate))
+
+struct CcBackgroundsMonitorPrivate
+{
+        GHashTable *item_hash;
+};
+
+enum {
+        PROP_0,
+};
+
+enum {
+        ITEM_ADDED,
+        ITEM_REMOVED,
+        LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void     cc_backgrounds_monitor_class_init     (CcBackgroundsMonitorClass *klass);
+static void     cc_backgrounds_monitor_init           (CcBackgroundsMonitor      *backgrounds_monitor);
+static void     cc_backgrounds_monitor_finalize       (GObject             *object);
+
+G_DEFINE_TYPE (CcBackgroundsMonitor, cc_backgrounds_monitor, G_TYPE_OBJECT)
+
+#include <gio/gio.h>
+#include <string.h>
+#include <libxml/parser.h>
+
+static gboolean
+xml_get_bool (const xmlNode *parent,
+              const char    *prop_name)
+{
+        xmlChar * prop;
+        gboolean ret_val = FALSE;
+
+        g_assert (parent != NULL);
+        g_assert (prop_name != NULL);
+
+        prop = xmlGetProp ((xmlNode *) parent, (xmlChar*)prop_name);
+        if (prop == NULL) {
+                goto done;
+        }
+
+        if (!g_ascii_strcasecmp ((char *)prop, "true")
+            || !g_ascii_strcasecmp ((char *)prop, "1")) {
+                ret_val = TRUE;
+        } else {
+                ret_val = FALSE;
+        }
+        g_free (prop);
+ done:
+        return ret_val;
+}
+
+static void
+xml_set_bool (const xmlNode *parent,
+              const xmlChar *prop_name,
+              gboolean       value)
+{
+        g_assert (parent != NULL);
+        g_assert (prop_name != NULL);
+
+        if (value) {
+                xmlSetProp ((xmlNode *) parent, prop_name, (xmlChar *)"true");
+        } else {
+                xmlSetProp ((xmlNode *) parent, prop_name, (xmlChar *)"false");
+        }
+}
+
+static void
+load_legacy (CcBackgroundsMonitor *monitor)
+{
+        FILE *fp;
+        char *foo;
+        char *filename;
+
+        filename = g_build_filename (g_get_home_dir (),
+                                     ".gnome2",
+                                     "wallpapers.list",
+                                     NULL);
+
+        if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+                if ((fp = fopen (filename, "r")) != NULL) {
+                        foo = (char *) g_malloc (sizeof (char) * 4096);
+                        while (fgets (foo, 4096, fp)) {
+                                CcBackgroundItem *item;
+
+                                if (foo[strlen (foo) - 1] == '\n') {
+                                        foo[strlen (foo) - 1] = '\0';
+                                }
+
+                                item = g_hash_table_lookup (monitor->priv->item_hash, foo);
+                                if (item != NULL) {
+                                        continue;
+                                }
+
+                                if (!g_file_test (foo, G_FILE_TEST_EXISTS)) {
+                                        continue;
+                                }
+
+                                item = cc_background_item_new (foo);
+                                if (cc_background_item_load (item)) {
+                                        cc_backgrounds_monitor_add_item (monitor, item);
+                                } else {
+                                        g_object_unref (item);
+                                }
+                        }
+                        fclose (fp);
+                        g_free (foo);
+                }
+        }
+
+        g_free (filename);
+}
+
+static void
+load_xml (CcBackgroundsMonitor *monitor,
+          const char           *xml_filename)
+{
+        xmlDoc             *wplist;
+        xmlNode            *root;
+        xmlNode            *list;
+        xmlNode            *wpa;
+        xmlChar            *nodelang;
+        const char * const *syslangs;
+        int                 i;
+
+        //g_debug ("loading from: %s", xml_filename);
+
+        wplist = xmlParseFile (xml_filename);
+
+        if (wplist == NULL)
+                return;
+
+        syslangs = g_get_language_names ();
+
+        root = xmlDocGetRootElement (wplist);
+
+        for (list = root->children; list != NULL; list = list->next) {
+                char             *filename;
+                char             *name;
+                char             *primary_color;
+                char             *secondary_color;
+                char             *options;
+                char             *shade_type;
+                gboolean          deleted;
+
+                if (strcmp ((char *)list->name, "wallpaper") != 0) {
+                        continue;
+                }
+
+                filename = NULL;
+                name = NULL;
+                options = NULL;
+                shade_type = NULL;
+                primary_color = NULL;
+                secondary_color = NULL;
+
+                deleted = xml_get_bool (list, "deleted");
+
+                for (wpa = list->children; wpa != NULL; wpa = wpa->next) {
+                        if (wpa->type == XML_COMMENT_NODE) {
+                                continue;
+                        }
+
+                        if (strcmp ((char *)wpa->name, "filename") == 0) {
+                                if (wpa->last != NULL
+                                    && wpa->last->content != NULL) {
+                                        char *content;
+
+                                        content = g_strstrip ((char *)wpa->last->content);
+
+                                        if (strcmp (content, "(none)") == 0)
+                                                filename = g_strdup (content);
+                                        else if (g_utf8_validate (content, -1, NULL) &&
+                                                 g_file_test (content, G_FILE_TEST_EXISTS))
+                                                filename = g_strdup (content);
+                                        else
+                                                filename = g_filename_from_utf8 (content, -1, NULL, NULL, NULL);
+                                } else {
+                                        break;
+                                }
+                        } else if (strcmp ((char *)wpa->name, "name") == 0) {
+                                if (wpa->last != NULL && wpa->last->content != NULL) {
+                                        nodelang = xmlNodeGetLang (wpa->last);
+
+                                        if (name == NULL && nodelang == NULL) {
+                                                name = g_strdup (g_strstrip ((char *)wpa->last->content));
+                                        } else {
+                                                for (i = 0; syslangs[i] != NULL; i++) {
+                                                        if (strcmp (syslangs[i], (char *)nodelang) == 0) {
+                                                                g_free (name);
+                                                                name = g_strdup (g_strstrip ((char *)wpa->last->content));
+                                                                break;
+                                                        }
+                                                }
+                                        }
+
+                                        xmlFree (nodelang);
+                                } else {
+                                        break;
+                                }
+                        } else if (strcmp ((char *)wpa->name, "options") == 0) {
+                                if (wpa->last != NULL) {
+                                        options = g_strdup (g_strstrip ((char *)wpa->last->content));
+                                }
+                        } else if (strcmp ((char *)wpa->name, "shade_type") == 0) {
+                                if (wpa->last != NULL) {
+                                        shade_type = g_strdup (g_strstrip ((char *)wpa->last->content));
+                                }
+                        } else if (strcmp ((char *)wpa->name, "pcolor") == 0) {
+                                if (wpa->last != NULL) {
+                                        primary_color = g_strdup (g_strstrip ((char *)wpa->last->content));
+                                }
+                        } else if (strcmp ((char *)wpa->name, "scolor") == 0) {
+                                if (wpa->last != NULL) {
+                                        secondary_color = g_strdup (g_strstrip ((char *)wpa->last->content));
+                                }
+                        } else if (strcmp ((char *)wpa->name, "text") == 0) {
+                                /* Do nothing here, libxml2 is being weird */
+                        } else {
+                                g_warning ("Unknown Tag: %s", wpa->name);
+                        }
+                }
+
+                /* Make sure we don't already have this one and that filename exists */
+                if (filename != NULL
+                    && g_hash_table_lookup (monitor->priv->item_hash, filename) == NULL) {
+                        CcBackgroundItem *item;
+
+                        item = cc_background_item_new (filename);
+                        g_object_set (item,
+                                      "name", name,
+                                      "primary-color", primary_color,
+                                      "secondary-color", secondary_color,
+                                      "placement", options,
+                                      "shading", shade_type,
+                                      NULL);
+
+                        if (cc_background_item_load (item)) {
+                                if (cc_backgrounds_monitor_add_item (monitor, item)) {
+                                        g_debug ("Added item %s", name);
+                                }
+                        }
+                        g_object_unref (item);
+                }
+
+                g_free (filename);
+                g_free (name);
+                g_free (primary_color);
+                g_free (secondary_color);
+                g_free (options);
+                g_free (shade_type);
+        }
+        xmlFreeDoc (wplist);
+}
+
+static void
+file_changed (GFileMonitor         *file_monitor,
+              GFile                *file,
+              GFile                *other_file,
+              GFileMonitorEvent     event_type,
+              CcBackgroundsMonitor *monitor)
+{
+        char *filename;
+
+        switch (event_type) {
+        case G_FILE_MONITOR_EVENT_CHANGED:
+        case G_FILE_MONITOR_EVENT_CREATED:
+                filename = g_file_get_path (file);
+                load_xml (monitor, filename);
+                g_free (filename);
+                break;
+        default:
+                break;
+        }
+}
+
+static void
+add_monitor (CcBackgroundsMonitor *monitor,
+             GFile                *directory)
+{
+        GFileMonitor *file_monitor;
+        GError       *error = NULL;
+
+        file_monitor = g_file_monitor_directory (directory,
+                                                 G_FILE_MONITOR_NONE,
+                                                 NULL,
+                                                 &error);
+        if (error != NULL) {
+                char *path;
+
+                path = g_file_get_parse_name (directory);
+                g_warning ("Unable to monitor directory %s: %s",
+                           path,
+                           error->message);
+                g_error_free (error);
+                g_free (path);
+                return;
+        }
+
+        g_signal_connect (file_monitor,
+                          "changed",
+                          G_CALLBACK (file_changed),
+                          monitor);
+}
+
+static void
+load_from_dir (CcBackgroundsMonitor *monitor,
+               const char           *path)
+{
+        GFile           *directory;
+        GFileEnumerator *enumerator;
+        GError          *error;
+        GFileInfo       *info;
+
+        if (!g_file_test (path, G_FILE_TEST_IS_DIR)) {
+                return;
+        }
+
+        g_debug ("Loading from directory %s", path);
+        directory = g_file_new_for_path (path);
+
+        error = NULL;
+        enumerator = g_file_enumerate_children (directory,
+                                                G_FILE_ATTRIBUTE_STANDARD_NAME,
+                                                G_FILE_QUERY_INFO_NONE,
+                                                NULL,
+                                                &error);
+        if (error != NULL) {
+                g_warning ("Unable to check directory %s: %s", path, error->message);
+                g_error_free (error);
+                g_object_unref (directory);
+                return;
+        }
+
+        while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL))) {
+                const char *filename;
+                char       *fullpath;
+
+                filename = g_file_info_get_name (info);
+                fullpath = g_build_filename (path, filename, NULL);
+                g_object_unref (info);
+
+                load_xml (monitor, fullpath);
+                g_free (fullpath);
+        }
+        g_file_enumerator_close (enumerator, NULL, NULL);
+
+        add_monitor (monitor, directory);
+
+        g_object_unref (directory);
+}
+
+static void
+ensure_none (CcBackgroundsMonitor *monitor)
+{
+        CcBackgroundItem *item;
+
+        item = g_hash_table_lookup (monitor->priv->item_hash, "(none)");
+        if (item == NULL) {
+                item = cc_background_item_new ("(none)");
+                if (cc_background_item_load (item)) {
+                        cc_backgrounds_monitor_add_item (monitor, item);
+                } else {
+                        g_object_unref (item);
+                }
+        } else {
+                g_object_set (item, "is-deleted", FALSE, NULL);
+        }
+}
+
+void
+cc_backgrounds_monitor_load (CcBackgroundsMonitor *monitor)
+{
+        const char * const *system_data_dirs;
+        char               *filename;
+        int                 i;
+
+        g_return_if_fail (monitor != NULL);
+
+        filename = g_build_filename (g_get_home_dir (),
+                                     ".gnome2",
+                                     "backgrounds.xml",
+                                     NULL);
+
+        if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+                load_xml (monitor, filename);
+        } else {
+                g_free (filename);
+                filename = g_build_filename (g_get_home_dir (),
+                                             ".gnome2",
+                                             "wp-list.xml",
+                                             NULL);
+                if (g_file_test (filename, G_FILE_TEST_EXISTS)) {
+                        load_xml (monitor, filename);
+                }
+        }
+        g_free (filename);
+
+        filename = g_build_filename (g_get_user_data_dir (),
+                                     "gnome-background-properties",
+                                     NULL);
+        load_from_dir (monitor, filename);
+        g_free (filename);
+
+        system_data_dirs = g_get_system_data_dirs ();
+        for (i = 0; system_data_dirs[i]; i++) {
+                filename = g_build_filename (system_data_dirs[i],
+                                             "gnome-background-properties",
+                                             NULL);
+                load_from_dir (monitor, filename);
+                g_free (filename);
+        }
+
+        load_from_dir (monitor, WALLPAPER_DATADIR);
+
+        load_legacy (monitor);
+
+        /* We always want to have a (none) entry */
+        ensure_none (monitor);
+}
+
+static void
+list_flatten (const char       *key,
+              CcBackgroundItem *item,
+              GList           **list)
+{
+        g_return_if_fail (key != NULL);
+        g_return_if_fail (item != NULL);
+
+        *list = g_list_prepend (*list, item);
+}
+
+gboolean
+cc_backgrounds_monitor_add_item (CcBackgroundsMonitor *monitor,
+                                 CcBackgroundItem     *item)
+{
+        char    *uri;
+        gboolean deleted;
+        gboolean ret;
+
+        ret = FALSE;
+
+        g_return_val_if_fail (monitor != NULL, FALSE);
+        g_return_val_if_fail (item != NULL, FALSE);
+
+        uri = NULL;
+        g_object_get (item,
+                      "filename", &uri,
+                      "is-deleted", &deleted,
+                      NULL);
+        if (g_hash_table_lookup (monitor->priv->item_hash, uri) == NULL) {
+                g_debug ("Inserting %s", uri);
+                g_hash_table_insert (monitor->priv->item_hash,
+                                     g_strdup (uri),
+                                     g_object_ref (item));
+                ret = TRUE;
+        } else if (!deleted) {
+                g_debug ("Undeleting %s", uri);
+                g_object_set (item, "is-deleted", FALSE, NULL);
+                ret = TRUE;
+        }
+
+        if (ret)
+                g_signal_emit (monitor, signals [ITEM_ADDED], 0, item);
+
+        g_free (uri);
+
+        return ret;
+}
+
+gboolean
+cc_backgrounds_monitor_remove_item (CcBackgroundsMonitor *monitor,
+                                    CcBackgroundItem     *item)
+{
+        char    *uri;
+        gboolean deleted;
+        gboolean ret;
+
+        ret = FALSE;
+
+        g_return_val_if_fail (monitor != NULL, FALSE);
+        g_return_val_if_fail (item != NULL, FALSE);
+
+        g_object_get (item,
+                      "filename", &uri,
+                      "is-deleted", &deleted,
+                      NULL);
+        if (g_hash_table_lookup (monitor->priv->item_hash, uri) != NULL
+            && !deleted) {
+                g_object_set (item, "is-deleted", TRUE, NULL);
+                g_signal_emit (monitor, signals [ITEM_REMOVED], 0, item);
+                ret = TRUE;
+        }
+        g_free (uri);
+        return ret;
+}
+
+GList *
+cc_backgrounds_monitor_get_items (CcBackgroundsMonitor *monitor)
+{
+        GList *list;
+
+        g_return_val_if_fail (monitor != NULL, NULL);
+
+        list = NULL;
+        g_hash_table_foreach (monitor->priv->item_hash,
+                              (GHFunc) list_flatten,
+                              &list);
+        list = g_list_reverse (list);
+        return list;
+}
+
+void
+cc_backgrounds_monitor_save (CcBackgroundsMonitor *monitor)
+{
+        xmlDoc  *wplist;
+        xmlNode *root;
+        xmlNode *wallpaper;
+        xmlNode *node;
+        GList   *list = NULL;
+        char    *wpfile;
+
+        g_return_if_fail (monitor != NULL);
+
+        g_hash_table_foreach (monitor->priv->item_hash,
+                              (GHFunc) list_flatten,
+                              &list);
+        list = g_list_reverse (list);
+
+        wpfile = g_build_filename (g_get_home_dir (),
+                                   "/.gnome2",
+                                   "backgrounds.xml",
+                                   NULL);
+
+        xmlKeepBlanksDefault (0);
+
+        wplist = xmlNewDoc ((xmlChar *)"1.0");
+        xmlCreateIntSubset (wplist, (xmlChar *)"wallpapers", NULL, (xmlChar *)"gnome-wp-list.dtd");
+        root = xmlNewNode (NULL, (xmlChar *)"wallpapers");
+        xmlDocSetRootElement (wplist, root);
+
+        while (list != NULL) {
+                CcBackgroundItem *item;
+                char             *scale;
+                char             *shade;
+                char             *filename;
+                char             *utf8_filename;
+                char             *name;
+                char             *pcolor;
+                char             *scolor;
+                gboolean          deleted;
+
+                item = list->data;
+                g_object_get (item,
+                              "filename", &filename,
+                              "name", &name,
+                              "primary-color", &pcolor,
+                              "secondary-color", &scolor,
+                              "shading", &shade,
+                              "placement", &scale,
+                              "is-deleted", &deleted,
+                              NULL);
+
+                if (strcmp (filename, "(none)") == 0
+                    || (g_utf8_validate (filename, -1, NULL)
+                        && g_file_test (filename, G_FILE_TEST_EXISTS))) {
+                        utf8_filename = g_strdup (filename);
+                } else {
+                        utf8_filename = g_filename_to_utf8 (filename, -1, NULL, NULL, NULL);
+                }
+
+                wallpaper = xmlNewChild (root, NULL, (xmlChar *)"wallpaper", NULL);
+                xml_set_bool (wallpaper, (xmlChar *)"deleted", deleted);
+                node = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"name", (xmlChar *)name);
+                node = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"filename", (xmlChar *)filename);
+                node = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"options", (xmlChar *)scale);
+                node = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"shade_type", (xmlChar *)shade);
+                node = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"pcolor", (xmlChar *)pcolor);
+                node = xmlNewTextChild (wallpaper, NULL, (xmlChar *)"scolor", (xmlChar *)scolor);
+
+                g_free (pcolor);
+                g_free (scolor);
+                g_free (filename);
+                g_free (utf8_filename);
+                g_free (name);
+                g_free (shade);
+                g_free (scale);
+
+                list = g_list_delete_link (list, list);
+        }
+
+        xmlSaveFormatFile (wpfile, wplist, 1);
+        xmlFreeDoc (wplist);
+        g_free (wpfile);
+}
+
+static GObject *
+cc_backgrounds_monitor_constructor (GType                  type,
+                                    guint                  n_construct_properties,
+                                    GObjectConstructParam *construct_properties)
+{
+        CcBackgroundsMonitor      *backgrounds_monitor;
+
+        backgrounds_monitor = CC_BACKGROUNDS_MONITOR (G_OBJECT_CLASS (cc_backgrounds_monitor_parent_class)->constructor (type,
+                                                                                                                         n_construct_properties,
+                                                                                                                         construct_properties));
+
+        return G_OBJECT (backgrounds_monitor);
+}
+
+static void
+cc_backgrounds_monitor_class_init (CcBackgroundsMonitorClass *klass)
+{
+        GObjectClass  *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->constructor = cc_backgrounds_monitor_constructor;
+        object_class->finalize = cc_backgrounds_monitor_finalize;
+
+        signals [ITEM_ADDED]
+                = g_signal_new ("item-added",
+                                G_TYPE_FROM_CLASS (object_class),
+                                G_SIGNAL_RUN_LAST,
+                                0,
+                                NULL,
+                                NULL,
+                                g_cclosure_marshal_VOID__OBJECT,
+                                G_TYPE_NONE,
+                                1, CC_TYPE_BACKGROUND_ITEM);
+        signals [ITEM_REMOVED]
+                = g_signal_new ("item-removed",
+                                G_TYPE_FROM_CLASS (object_class),
+                                G_SIGNAL_RUN_LAST,
+                                0,
+                                NULL,
+                                NULL,
+                                g_cclosure_marshal_VOID__OBJECT,
+                                G_TYPE_NONE,
+                                1, CC_TYPE_BACKGROUND_ITEM);
+
+        g_type_class_add_private (klass, sizeof (CcBackgroundsMonitorPrivate));
+}
+
+static void
+cc_backgrounds_monitor_init (CcBackgroundsMonitor *monitor)
+{
+        monitor->priv = CC_BACKGROUNDS_MONITOR_GET_PRIVATE (monitor);
+
+        monitor->priv->item_hash = g_hash_table_new_full (g_str_hash,
+                                                          g_str_equal,
+                                                          g_free,
+                                                          g_object_unref);
+}
+
+static void
+cc_backgrounds_monitor_finalize (GObject *object)
+{
+        CcBackgroundsMonitor *monitor;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (CC_IS_BACKGROUNDS_MONITOR (object));
+
+        monitor = CC_BACKGROUNDS_MONITOR (object);
+
+        g_return_if_fail (monitor->priv != NULL);
+
+        g_hash_table_destroy (monitor->priv->item_hash);
+
+        G_OBJECT_CLASS (cc_backgrounds_monitor_parent_class)->finalize (object);
+}
+
+CcBackgroundsMonitor *
+cc_backgrounds_monitor_new (void)
+{
+        GObject *object;
+
+        object = g_object_new (CC_TYPE_BACKGROUNDS_MONITOR, NULL);
+
+        return CC_BACKGROUNDS_MONITOR (object);
+}
diff --git a/capplets/appearance/cc-backgrounds-monitor.h b/capplets/appearance/cc-backgrounds-monitor.h
new file mode 100644
index 0000000..8e46e2c
--- /dev/null
+++ b/capplets/appearance/cc-backgrounds-monitor.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __CC_BACKGROUNDS_MONITOR_H
+#define __CC_BACKGROUNDS_MONITOR_H
+
+#include <glib-object.h>
+#include "cc-background-item.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_BACKGROUNDS_MONITOR         (cc_backgrounds_monitor_get_type ())
+#define CC_BACKGROUNDS_MONITOR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_BACKGROUNDS_MONITOR, CcBackgroundsMonitor))
+#define CC_BACKGROUNDS_MONITOR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_BACKGROUNDS_MONITOR, CcBackgroundsMonitorClass))
+#define CC_IS_BACKGROUNDS_MONITOR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_BACKGROUNDS_MONITOR))
+#define CC_IS_BACKGROUNDS_MONITOR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_BACKGROUNDS_MONITOR))
+#define CC_BACKGROUNDS_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_BACKGROUNDS_MONITOR, CcBackgroundsMonitorClass))
+
+typedef struct CcBackgroundsMonitorPrivate CcBackgroundsMonitorPrivate;
+
+typedef struct
+{
+        GObject                      parent;
+        CcBackgroundsMonitorPrivate *priv;
+} CcBackgroundsMonitor;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+
+        void (* item_added)           (CcBackgroundsMonitor *monitor,
+                                       CcBackgroundItem     *item);
+        void (* item_removed)         (CcBackgroundsMonitor *monitor,
+                                       CcBackgroundItem     *item);
+} CcBackgroundsMonitorClass;
+
+GType                  cc_backgrounds_monitor_get_type    (void);
+
+CcBackgroundsMonitor * cc_backgrounds_monitor_new         (void);
+
+void                   cc_backgrounds_monitor_load        (CcBackgroundsMonitor *monitor);
+void                   cc_backgrounds_monitor_save        (CcBackgroundsMonitor *monitor);
+
+GList *                cc_backgrounds_monitor_get_items   (CcBackgroundsMonitor *monitor);
+gboolean               cc_backgrounds_monitor_add_item    (CcBackgroundsMonitor *monitor,
+                                                           CcBackgroundItem     *item);
+gboolean               cc_backgrounds_monitor_remove_item (CcBackgroundsMonitor *monitor,
+                                                           CcBackgroundItem     *item);
+
+G_END_DECLS
+
+#endif /* __CC_BACKGROUNDS_MONITOR_H */
diff --git a/capplets/appearance/gnome-wp-item.c b/capplets/appearance/gnome-wp-item.c
index b4af8fc..74f5b79 100644
--- a/capplets/appearance/gnome-wp-item.c
+++ b/capplets/appearance/gnome-wp-item.c
@@ -123,7 +123,6 @@ void gnome_wp_item_update (GnomeWPItem *item) {
 }
 
 GnomeWPItem * gnome_wp_item_new (const gchar * filename,
-				 GHashTable * wallpapers,
 				 GnomeDesktopThumbnailFactory * thumbnails) {
   GnomeWPItem *item = g_new0 (GnomeWPItem, 1);
 
@@ -143,8 +142,6 @@ GnomeWPItem * gnome_wp_item_new (const gchar * filename,
     gnome_wp_item_update (item);
     gnome_wp_item_ensure_gnome_bg (item);
     gnome_wp_item_update_description (item);
-
-    g_hash_table_insert (wallpapers, item->filename, item);
   } else {
     gnome_wp_item_free (item);
     item = NULL;
diff --git a/capplets/appearance/gnome-wp-item.h b/capplets/appearance/gnome-wp-item.h
index 03dc417..6654e3d 100644
--- a/capplets/appearance/gnome-wp-item.h
+++ b/capplets/appearance/gnome-wp-item.h
@@ -66,7 +66,6 @@ struct _GnomeWPItem {
 };
 
 GnomeWPItem * gnome_wp_item_new (const gchar *filename,
-				 GHashTable *wallpapers,
 				 GnomeDesktopThumbnailFactory *thumbnails);
 
 void gnome_wp_item_free (GnomeWPItem *item);
diff --git a/capplets/appearance/gnome-wp-xml.c b/capplets/appearance/gnome-wp-xml.c
index 5ecea04..8598bff 100644
--- a/capplets/appearance/gnome-wp-xml.c
+++ b/capplets/appearance/gnome-wp-xml.c
@@ -83,7 +83,7 @@ static void gnome_wp_load_legacy (AppearanceData *data) {
 	  continue;
 	}
 
-	item = gnome_wp_item_new (foo, data->wp_hash, data->thumb_factory);
+	item = gnome_wp_item_new (foo, data->thumb_factory);
 	if (item != NULL && item->fileinfo == NULL) {
 	  gnome_wp_item_free (item);
 	}
diff --git a/capplets/keyboard/Makefile.am b/capplets/keyboard/Makefile.am
index 0535402..e608163 100644
--- a/capplets/keyboard/Makefile.am
+++ b/capplets/keyboard/Makefile.am
@@ -1,10 +1,20 @@
 # This is used in GNOMECC_CAPPLETS_CFLAGS
 cappletname = keyboard
 
+module_flags = -export_dynamic -avoid-version -module -no-undefined -export-symbols-regex '^g_io_module_(load|unload)'
+
+INCLUDES = \
+	$(GNOMECC_CAPPLETS_CFLAGS) \
+	$(LIBGNOMEKBDUI_CFLAGS) \
+	-DGNOMELOCALEDIR="\"$(datadir)/locale\"" \
+	-DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \
+	-DGNOMECC_UI_DIR="\"$(uidir)\""
+
+noinst_LTLIBRARIES = libkeyboard-common.la
+
 bin_PROGRAMS = gnome-keyboard-properties
 
-gnome_keyboard_properties_SOURCES = \
-	gnome-keyboard-properties.c \
+libkeyboard_common_la_SOURCES = \
 	gnome-keyboard-properties-a11y.c \
 	gnome-keyboard-properties-a11y.h \
 	gnome-keyboard-properties-xkb.c \
@@ -15,7 +25,36 @@ gnome_keyboard_properties_SOURCES = \
 	gnome-keyboard-properties-xkbpv.c \
 	gnome-keyboard-properties-xkb.h
 
-gnome_keyboard_properties_LDADD = $(GNOMECC_CAPPLETS_LIBS) $(LIBGNOMEKBDUI_LIBS)
+libkeyboard_common_la_LDFLAGS = \
+       -no-undefined -export_dynamic -avoid-version
+
+ccmodulesdir = $(libdir)/control-center-1/extensions
+ccmodules_LTLIBRARIES = libkeyboard.la
+
+libkeyboard_la_SOURCES = \
+	keyboard-module.c \
+	cc-keyboard-panel.h \
+	cc-keyboard-panel.c
+
+libkeyboard_la_LDFLAGS = \
+	$(module_flags)
+
+libkeyboard_la_LIBADD = \
+	libkeyboard-common.la \
+	$(GNOMECC_CAPPLETS_LIBS) \
+	$(LIBGNOMEKBDUI_LIBS)
+
+libkeyboard_la_CFLAGS = \
+	-I$(top_builddir)/shell \
+	-I$(top_srcdir)/shell
+
+gnome_keyboard_properties_SOURCES = \
+	gnome-keyboard-properties.c
+
+gnome_keyboard_properties_LDADD = \
+	libkeyboard-common.la \
+	$(GNOMECC_CAPPLETS_LIBS) \
+	$(LIBGNOMEKBDUI_LIBS)
 
 @INTLTOOL_DESKTOP_RULE@
 
@@ -30,12 +69,6 @@ desktopdir = $(datadir)/applications
 Desktop_in_files = keyboard.desktop.in
 desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop)
 
-INCLUDES = \
-	$(GNOMECC_CAPPLETS_CFLAGS) \
-	$(LIBGNOMEKBDUI_CFLAGS) \
-	-DGNOMELOCALEDIR="\"$(datadir)/locale\"" \
-	-DGNOMECC_DATA_DIR="\"$(pkgdatadir)\"" \
-	-DGNOMECC_UI_DIR="\"$(uidir)\""
 CLEANFILES = $(GNOMECC_CAPPLETS_CLEANFILES) $(Desktop_in_files) $(desktop_DATA)
 EXTRA_DIST = $(ui_DATA)
 
diff --git a/capplets/keyboard/cc-keyboard-panel.c b/capplets/keyboard/cc-keyboard-panel.c
new file mode 100644
index 0000000..bd45ee6
--- /dev/null
+++ b/capplets/keyboard/cc-keyboard-panel.c
@@ -0,0 +1,293 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+#include <glib/gi18n-lib.h>
+
+#include "cc-keyboard-panel.h"
+
+#include "gconf-property-editor.h"
+#include "capplet-stock-icons.h"
+
+#include "gnome-keyboard-properties-a11y.h"
+#include "gnome-keyboard-properties-xkb.h"
+
+#define CC_KEYBOARD_PANEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_KEYBOARD_PANEL, CcKeyboardPanelPrivate))
+
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (builder, s))
+
+struct CcKeyboardPanelPrivate
+{
+        gpointer dummy;
+};
+
+enum {
+        PROP_0,
+};
+
+static void     cc_keyboard_panel_class_init     (CcKeyboardPanelClass *klass);
+static void     cc_keyboard_panel_init           (CcKeyboardPanel      *keyboard_panel);
+static void     cc_keyboard_panel_finalize       (GObject             *object);
+
+G_DEFINE_DYNAMIC_TYPE (CcKeyboardPanel, cc_keyboard_panel, CC_TYPE_PANEL)
+
+static void
+cc_keyboard_panel_set_property (GObject      *object,
+                                guint         prop_id,
+                                const GValue *value,
+                                GParamSpec   *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+cc_keyboard_panel_get_property (GObject    *object,
+                                guint       prop_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+        switch (prop_id) {
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+
+static GConfValue *
+blink_from_widget (GConfPropertyEditor *peditor,
+                   const GConfValue    *value)
+{
+        GConfValue *new_value;
+
+        new_value = gconf_value_new (GCONF_VALUE_INT);
+        gconf_value_set_int (new_value,
+                             2600 - gconf_value_get_int (value));
+
+        return new_value;
+}
+
+static GConfValue *
+blink_to_widget (GConfPropertyEditor * peditor, const GConfValue * value)
+{
+        GConfValue *new_value;
+        gint current_rate;
+
+        current_rate = gconf_value_get_int (value);
+        new_value = gconf_value_new (GCONF_VALUE_INT);
+        gconf_value_set_int (new_value,
+                             CLAMP (2600 - current_rate, 100, 2500));
+
+        return new_value;
+}
+
+static void
+setup_panel (CcKeyboardPanel *panel)
+{
+        GtkBuilder     *builder;
+        GtkSizeGroup   *size_group;
+        GtkWidget      *image;
+        GtkWidget      *widget;
+        GObject        *peditor;
+        char           *monitor;
+        GConfChangeSet *changeset;
+
+        changeset = NULL;
+
+        builder = gtk_builder_new ();
+        gtk_builder_add_from_file (builder,
+                                   GNOMECC_UI_DIR
+                                   "/gnome-keyboard-properties-dialog.ui",
+                                   NULL);
+
+        size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+        gtk_size_group_add_widget (size_group, WID ("repeat_slow_label"));
+        gtk_size_group_add_widget (size_group, WID ("delay_short_label"));
+        gtk_size_group_add_widget (size_group, WID ("blink_slow_label"));
+        g_object_unref (G_OBJECT (size_group));
+
+        size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+        gtk_size_group_add_widget (size_group, WID ("repeat_fast_label"));
+        gtk_size_group_add_widget (size_group, WID ("delay_long_label"));
+        gtk_size_group_add_widget (size_group, WID ("blink_fast_label"));
+        g_object_unref (G_OBJECT (size_group));
+
+        size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+        gtk_size_group_add_widget (size_group, WID ("repeat_delay_scale"));
+        gtk_size_group_add_widget (size_group, WID ("repeat_speed_scale"));
+        gtk_size_group_add_widget (size_group, WID ("cursor_blink_time_scale"));
+        g_object_unref (G_OBJECT (size_group));
+
+        image = gtk_image_new_from_stock (GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image (GTK_BUTTON (WID ("xkb_layouts_add")), image);
+
+        image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
+        gtk_button_set_image (GTK_BUTTON (WID ("xkb_reset_to_defaults")), image);
+
+        peditor = gconf_peditor_new_boolean (changeset, "/desktop/gnome/peripherals/keyboard/repeat",
+                                             WID ("repeat_toggle"), NULL);
+        gconf_peditor_widget_set_guard (GCONF_PROPERTY_EDITOR (peditor),
+                                        WID ("repeat_table"));
+
+        gconf_peditor_new_numeric_range (changeset, "/desktop/gnome/peripherals/keyboard/delay",
+                                         WID ("repeat_delay_scale"), NULL);
+
+        gconf_peditor_new_numeric_range (changeset, "/desktop/gnome/peripherals/keyboard/rate",
+                                         WID ("repeat_speed_scale"), NULL);
+
+        peditor = gconf_peditor_new_boolean (changeset, "/desktop/gnome/interface/cursor_blink",
+                                             WID ("cursor_toggle"), NULL);
+        gconf_peditor_widget_set_guard (GCONF_PROPERTY_EDITOR (peditor),
+                                        WID ("cursor_hbox"));
+        gconf_peditor_new_numeric_range (changeset,
+                                         "/desktop/gnome/interface/cursor_blink_time",
+                                         WID ("cursor_blink_time_scale"),
+                                         "conv-to-widget-cb",
+                                         blink_to_widget,
+                                         "conv-from-widget-cb",
+                                         blink_from_widget, NULL);
+
+        /* Ergonomics */
+        monitor = g_find_program_in_path ("gnome-typing-monitor");
+        if (monitor != NULL) {
+                g_free (monitor);
+
+                peditor = gconf_peditor_new_boolean (changeset, "/desktop/gnome/typing_break/enabled",
+                                                     WID ("break_enabled_toggle"), NULL);
+                gconf_peditor_widget_set_guard (GCONF_PROPERTY_EDITOR (peditor),
+                                                WID ("break_details_table"));
+                gconf_peditor_new_numeric_range (changeset,
+                                                 "/desktop/gnome/typing_break/type_time",
+                                                 WID ("break_enabled_spin"), NULL);
+                gconf_peditor_new_numeric_range (changeset,
+                                                 "/desktop/gnome/typing_break/break_time",
+                                                 WID ("break_interval_spin"),
+                                                 NULL);
+                gconf_peditor_new_boolean (changeset,
+                                           "/desktop/gnome/typing_break/allow_postpone",
+                                           WID ("break_postponement_toggle"),
+                                           NULL);
+
+        } else {
+                /* don't show the typing break tab if the daemon is not available */
+                GtkNotebook *nb;
+                gint         tb_page;
+
+                nb = GTK_NOTEBOOK (WID ("keyboard_notebook"));
+                tb_page = gtk_notebook_page_num (nb, WID ("break_enabled_toggle"));
+                gtk_notebook_remove_page (nb, tb_page);
+        }
+
+        setup_xkb_tabs (builder, changeset);
+        setup_a11y_tabs (builder, changeset);
+
+        widget = WID ("main-vbox");
+        gtk_widget_reparent (widget, GTK_WIDGET (panel));
+        gtk_widget_show (widget);
+}
+
+static GObject *
+cc_keyboard_panel_constructor (GType                  type,
+                               guint                  n_construct_properties,
+                               GObjectConstructParam *construct_properties)
+{
+        CcKeyboardPanel      *keyboard_panel;
+
+        keyboard_panel = CC_KEYBOARD_PANEL (G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->constructor (type,
+                                                                                                          n_construct_properties,
+                                                                                                          construct_properties));
+        g_object_set (keyboard_panel,
+                      "display-name", _("Keyboard"),
+                      "id", "keyboard.desktop",
+                      NULL);
+
+        setup_panel (keyboard_panel);
+
+        return G_OBJECT (keyboard_panel);
+}
+
+static void
+cc_keyboard_panel_class_init (CcKeyboardPanelClass *klass)
+{
+        GObjectClass  *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = cc_keyboard_panel_get_property;
+        object_class->set_property = cc_keyboard_panel_set_property;
+        object_class->constructor = cc_keyboard_panel_constructor;
+        object_class->finalize = cc_keyboard_panel_finalize;
+
+        g_type_class_add_private (klass, sizeof (CcKeyboardPanelPrivate));
+}
+
+static void
+cc_keyboard_panel_class_finalize (CcKeyboardPanelClass *klass)
+{
+}
+
+static void
+cc_keyboard_panel_init (CcKeyboardPanel *panel)
+{
+        GConfClient *client;
+
+        panel->priv = CC_KEYBOARD_PANEL_GET_PRIVATE (panel);
+
+        client = gconf_client_get_default ();
+        gconf_client_add_dir (client,
+                              "/desktop/gnome/peripherals/keyboard",
+                              GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
+        gconf_client_add_dir (client, "/desktop/gnome/interface",
+                              GCONF_CLIENT_PRELOAD_ONELEVEL, NULL);
+        g_object_unref (client);
+}
+
+static void
+cc_keyboard_panel_finalize (GObject *object)
+{
+        CcKeyboardPanel *keyboard_panel;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (CC_IS_KEYBOARD_PANEL (object));
+
+        keyboard_panel = CC_KEYBOARD_PANEL (object);
+
+        g_return_if_fail (keyboard_panel->priv != NULL);
+
+        G_OBJECT_CLASS (cc_keyboard_panel_parent_class)->finalize (object);
+}
+
+void
+cc_keyboard_panel_register (GIOModule *module)
+{
+        cc_keyboard_panel_register_type (G_TYPE_MODULE (module));
+        g_io_extension_point_implement (CC_PANEL_EXTENSION_POINT_NAME,
+                                        CC_TYPE_KEYBOARD_PANEL,
+                                        "keyboard",
+                                        10);
+}
diff --git a/capplets/keyboard/cc-keyboard-panel.h b/capplets/keyboard/cc-keyboard-panel.h
new file mode 100644
index 0000000..78ef63e
--- /dev/null
+++ b/capplets/keyboard/cc-keyboard-panel.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#ifndef __CC_SIMPLE_PANEL_H
+#define __CC_SIMPLE_PANEL_H
+
+#include <gtk/gtk.h>
+#include "cc-panel.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_KEYBOARD_PANEL         (cc_keyboard_panel_get_type ())
+#define CC_KEYBOARD_PANEL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_KEYBOARD_PANEL, CcKeyboardPanel))
+#define CC_KEYBOARD_PANEL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_KEYBOARD_PANEL, CcKeyboardPanelClass))
+#define CC_IS_KEYBOARD_PANEL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_KEYBOARD_PANEL))
+#define CC_IS_KEYBOARD_PANEL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_KEYBOARD_PANEL))
+#define CC_KEYBOARD_PANEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_KEYBOARD_PANEL, CcKeyboardPanelClass))
+
+typedef struct CcKeyboardPanelPrivate CcKeyboardPanelPrivate;
+
+typedef struct
+{
+        CcPanel                 parent;
+        CcKeyboardPanelPrivate *priv;
+} CcKeyboardPanel;
+
+typedef struct
+{
+        CcPanelClass   parent_class;
+} CcKeyboardPanelClass;
+
+GType              cc_keyboard_panel_get_type   (void);
+void               cc_keyboard_panel_register   (GIOModule         *module);
+
+G_END_DECLS
+
+#endif /* __CC_KEYBOARD_PANEL_H */
diff --git a/capplets/keyboard/gnome-keyboard-properties-dialog.ui b/capplets/keyboard/gnome-keyboard-properties-dialog.ui
index 36b9df5..5459787 100644
--- a/capplets/keyboard/gnome-keyboard-properties-dialog.ui
+++ b/capplets/keyboard/gnome-keyboard-properties-dialog.ui
@@ -77,12 +77,12 @@
     <property name="type_hint">dialog</property>
     <property name="has_separator">False</property>
     <child internal-child="vbox">
-      <object class="GtkVBox" id="dialog-vbox1">
+      <object class="GtkVBox" id="dialog-vbox">
         <property name="visible">True</property>
         <property name="orientation">vertical</property>
         <property name="spacing">2</property>
         <child>
-          <object class="GtkVBox" id="vbox1">
+          <object class="GtkVBox" id="main-vbox">
             <property name="visible">True</property>
             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
             <property name="border_width">5</property>
diff --git a/capplets/keyboard/keyboard-module.c b/capplets/keyboard/keyboard-module.c
new file mode 100644
index 0000000..86798a0
--- /dev/null
+++ b/capplets/keyboard/keyboard-module.c
@@ -0,0 +1,42 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <config.h>
+
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gio/gio.h>
+
+#include "cc-keyboard-panel.h"
+
+void
+g_io_module_load (GIOModule *module)
+{
+  bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+  bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+  cc_keyboard_panel_register (module);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9caccb4..d22b9c8 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,9 @@ capplets/appearance/appearance-font.c
 capplets/appearance/appearance-main.c
 capplets/appearance/appearance-style.c
 capplets/appearance/appearance-themes.c
+capplets/appearance/cc-appearance-panel.c
+capplets/appearance/cc-background-item.c
+capplets/appearance/cc-background-page.c
 [type: gettext/glade]capplets/appearance/data/appearance.ui
 capplets/appearance/data/gnome-appearance-properties.desktop.in.in
 capplets/appearance/data/gnome-theme-installer.desktop.in.in
@@ -55,6 +58,7 @@ capplets/keyboard/gnome-keyboard-properties.c
 [type: gettext/glade]capplets/keyboard/gnome-keyboard-properties-layout-chooser.ui
 [type: gettext/glade]capplets/keyboard/gnome-keyboard-properties-model-chooser.ui
 [type: gettext/glade]capplets/keyboard/gnome-keyboard-properties-options-dialog.ui
+capplets/keyboard/cc-keyboard-panel.c
 capplets/keyboard/gnome-keyboard-properties-xkb.c
 capplets/keyboard/gnome-keyboard-properties-xkbltadd.c
 capplets/keyboard/gnome-keyboard-properties-xkblt.c
@@ -78,6 +82,7 @@ shell/control-center.c
 shell/control-center.schemas.in
 shell/gnomecc.desktop.in.in
 shell/gnomecc.directory.in
+shell/shell.ui
 typing-break/drw-break-window.c
 typing-break/drwright.c
 typing-break/main.c
diff --git a/shell/Makefile.am b/shell/Makefile.am
index b7c4919..da62c63 100644
--- a/shell/Makefile.am
+++ b/shell/Makefile.am
@@ -2,7 +2,7 @@ INCLUDES =					\
 	-I$(top_srcdir)				\
 	$(GNOMECC_SHELL_CFLAGS)
 
-bin_PROGRAMS = gnome-control-center 
+bin_PROGRAMS = gnome-control-center
 
 menudir = $(sysconfdir)/xdg/menus
 menu_DATA = gnomecc.menu
@@ -11,13 +11,20 @@ uidir = $(pkgdatadir)/ui
 ui_DATA = shell.ui
 
 gnome_control_center_SOURCES =		\
+	cc-page.h			\
+	cc-page.c			\
+	cc-panel.h			\
+	cc-panel.c			\
 	control-center.c
 
 gnome_control_center_LDADD =						\
 	$(GNOMECC_SHELL_LIBS)
 
+gnome_control_center_LDFLAGS = -export-dynamic
+
 AM_CPPFLAGS =							\
 	-DGNOMELOCALEDIR="\"$(datadir)/locale\""		\
+	-DEXTENSION_DIR="\"$(libdir)/control-center-1/extensions\""					\
 	-DUIDIR="\"$(uidir)\""					\
 	-DMENUDIR="\"$(menudir)\""
 
diff --git a/shell/cc-page.c b/shell/cc-page.c
new file mode 100644
index 0000000..9b95aed
--- /dev/null
+++ b/shell/cc-page.c
@@ -0,0 +1,178 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include "cc-page.h"
+
+#define CC_PAGE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_PAGE, CcPagePrivate))
+
+struct CcPagePrivate
+{
+        char            *id;
+        char            *display_name;
+};
+
+enum {
+        PROP_0,
+        PROP_ID,
+        PROP_DISPLAY_NAME,
+};
+
+static void     cc_page_class_init    (CcPageClass *klass);
+static void     cc_page_init          (CcPage      *page);
+static void     cc_page_finalize      (GObject     *object);
+
+G_DEFINE_ABSTRACT_TYPE (CcPage, cc_page, GTK_TYPE_ALIGNMENT)
+
+static void
+_cc_page_set_id (CcPage     *page,
+                 const char *id)
+{
+        g_free (page->priv->id);
+        page->priv->id = g_strdup (id);
+}
+
+static void
+_cc_page_set_display_name (CcPage     *page,
+                           const char *name)
+{
+        g_free (page->priv->display_name);
+        page->priv->display_name = g_strdup (name);
+}
+
+static void
+cc_page_set_property (GObject      *object,
+                      guint         prop_id,
+                      const GValue *value,
+                      GParamSpec   *pspec)
+{
+        CcPage *self;
+
+        self = CC_PAGE (object);
+
+        switch (prop_id) {
+        case PROP_ID:
+                _cc_page_set_id (self, g_value_get_string (value));
+                break;
+        case PROP_DISPLAY_NAME:
+                _cc_page_set_display_name (self, g_value_get_string (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+cc_page_get_property (GObject    *object,
+                      guint       prop_id,
+                      GValue     *value,
+                      GParamSpec *pspec)
+{
+        CcPage *self;
+
+        self = CC_PAGE (object);
+
+        switch (prop_id) {
+        case PROP_ID:
+                g_value_set_string (value, self->priv->id);
+                break;
+        case PROP_DISPLAY_NAME:
+                g_value_set_string (value, self->priv->display_name);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+cc_page_constructor (GType                  type,
+                     guint                  n_construct_properties,
+                     GObjectConstructParam *construct_properties)
+{
+        CcPage      *page;
+
+        page = CC_PAGE (G_OBJECT_CLASS (cc_page_parent_class)->constructor (type,
+                                                                            n_construct_properties,
+                                                                            construct_properties));
+
+        return G_OBJECT (page);
+}
+
+static void
+cc_page_class_init (CcPageClass *klass)
+{
+        GObjectClass    *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = cc_page_get_property;
+        object_class->set_property = cc_page_set_property;
+        object_class->constructor = cc_page_constructor;
+        object_class->finalize = cc_page_finalize;
+
+        g_type_class_add_private (klass, sizeof (CcPagePrivate));
+
+        g_object_class_install_property (object_class,
+                                         PROP_ID,
+                                         g_param_spec_string ("id",
+                                                              "id",
+                                                              "id",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (object_class,
+                                         PROP_DISPLAY_NAME,
+                                         g_param_spec_string ("display-name",
+                                                              "display name",
+                                                              "display name",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+cc_page_init (CcPage *page)
+{
+
+        page->priv = CC_PAGE_GET_PRIVATE (page);
+}
+
+static void
+cc_page_finalize (GObject *object)
+{
+        CcPage *page;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (CC_IS_PAGE (object));
+
+        page = CC_PAGE (object);
+
+        g_return_if_fail (page->priv != NULL);
+
+        g_free (page->priv->id);
+        g_free (page->priv->display_name);
+
+        G_OBJECT_CLASS (cc_page_parent_class)->finalize (object);
+}
diff --git a/shell/cc-page.h b/shell/cc-page.h
new file mode 100644
index 0000000..8ab20e0
--- /dev/null
+++ b/shell/cc-page.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifndef __CC_PAGE_H
+#define __CC_PAGE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_PAGE         (cc_page_get_type ())
+#define CC_PAGE(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_PAGE, CcPage))
+#define CC_PAGE_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_PAGE, CcPageClass))
+#define CC_IS_PAGE(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_PAGE))
+#define CC_IS_PAGE_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_PAGE))
+#define CC_PAGE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_PAGE, CcPageClass))
+
+typedef struct CcPagePrivate CcPagePrivate;
+
+typedef struct
+{
+        GtkAlignment   parent;
+        CcPagePrivate *priv;
+} CcPage;
+
+typedef struct
+{
+        GtkAlignmentClass   parent_class;
+} CcPageClass;
+
+GType               cc_page_get_type               (void);
+
+G_END_DECLS
+
+#endif /* __CC_PAGE_H */
diff --git a/shell/cc-panel.c b/shell/cc-panel.c
new file mode 100644
index 0000000..6d53f6d
--- /dev/null
+++ b/shell/cc-panel.c
@@ -0,0 +1,182 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include "cc-panel.h"
+
+#define CC_PANEL_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CC_TYPE_PANEL, CcPanelPrivate))
+
+struct CcPanelPrivate
+{
+        char            *id;
+        char            *display_name;
+        char            *category;
+        char            *current_location;
+};
+
+enum {
+        PROP_0,
+        PROP_ID,
+        PROP_DISPLAY_NAME,
+        PROP_CATEGORY,
+        PROP_CURRENT_LOCATION,
+};
+
+static void     cc_panel_class_init    (CcPanelClass *klass);
+static void     cc_panel_init          (CcPanel      *panel);
+static void     cc_panel_finalize      (GObject       *object);
+
+G_DEFINE_ABSTRACT_TYPE (CcPanel, cc_panel, GTK_TYPE_ALIGNMENT)
+
+static void
+_cc_panel_set_id (CcPanel    *panel,
+                  const char *id)
+{
+        g_free (panel->priv->id);
+        panel->priv->id = g_strdup (id);
+}
+
+static void
+_cc_panel_set_display_name (CcPanel    *panel,
+                            const char *name)
+{
+        g_free (panel->priv->display_name);
+        panel->priv->display_name = g_strdup (name);
+}
+
+static void
+cc_panel_set_property (GObject      *object,
+                       guint         prop_id,
+                       const GValue *value,
+                       GParamSpec   *pspec)
+{
+        CcPanel *self;
+
+        self = CC_PANEL (object);
+
+        switch (prop_id) {
+        case PROP_ID:
+                _cc_panel_set_id (self, g_value_get_string (value));
+                break;
+        case PROP_DISPLAY_NAME:
+                _cc_panel_set_display_name (self, g_value_get_string (value));
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static void
+cc_panel_get_property (GObject    *object,
+                       guint       prop_id,
+                       GValue     *value,
+                       GParamSpec *pspec)
+{
+        CcPanel *self;
+
+        self = CC_PANEL (object);
+
+        switch (prop_id) {
+        case PROP_ID:
+                g_value_set_string (value, self->priv->id);
+                break;
+        case PROP_DISPLAY_NAME:
+                g_value_set_string (value, self->priv->display_name);
+                break;
+        default:
+                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+                break;
+        }
+}
+
+static GObject *
+cc_panel_constructor (GType                  type,
+                      guint                  n_construct_properties,
+                      GObjectConstructParam *construct_properties)
+{
+        CcPanel      *panel;
+
+        panel = CC_PANEL (G_OBJECT_CLASS (cc_panel_parent_class)->constructor (type,
+                                                                               n_construct_properties,
+                                                                               construct_properties));
+
+        return G_OBJECT (panel);
+}
+
+static void
+cc_panel_class_init (CcPanelClass *klass)
+{
+        GObjectClass    *object_class = G_OBJECT_CLASS (klass);
+
+        object_class->get_property = cc_panel_get_property;
+        object_class->set_property = cc_panel_set_property;
+        object_class->constructor = cc_panel_constructor;
+        object_class->finalize = cc_panel_finalize;
+
+        g_type_class_add_private (klass, sizeof (CcPanelPrivate));
+
+        g_object_class_install_property (object_class,
+                                         PROP_ID,
+                                         g_param_spec_string ("id",
+                                                              "id",
+                                                              "id",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+        g_object_class_install_property (object_class,
+                                         PROP_DISPLAY_NAME,
+                                         g_param_spec_string ("display-name",
+                                                              "display name",
+                                                              "display name",
+                                                              NULL,
+                                                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+cc_panel_init (CcPanel *panel)
+{
+
+        panel->priv = CC_PANEL_GET_PRIVATE (panel);
+}
+
+static void
+cc_panel_finalize (GObject *object)
+{
+        CcPanel *panel;
+
+        g_return_if_fail (object != NULL);
+        g_return_if_fail (CC_IS_PANEL (object));
+
+        panel = CC_PANEL (object);
+
+        g_return_if_fail (panel->priv != NULL);
+
+        g_free (panel->priv->id);
+        g_free (panel->priv->display_name);
+
+        G_OBJECT_CLASS (cc_panel_parent_class)->finalize (object);
+}
diff --git a/shell/cc-panel.h b/shell/cc-panel.h
new file mode 100644
index 0000000..162e366
--- /dev/null
+++ b/shell/cc-panel.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+
+#ifndef __CC_PANEL_H
+#define __CC_PANEL_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_PANEL         (cc_panel_get_type ())
+#define CC_PANEL(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), CC_TYPE_PANEL, CcPanel))
+#define CC_PANEL_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), CC_TYPE_PANEL, CcPanelClass))
+#define CC_IS_PANEL(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), CC_TYPE_PANEL))
+#define CC_IS_PANEL_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), CC_TYPE_PANEL))
+#define CC_PANEL_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), CC_TYPE_PANEL, CcPanelClass))
+
+#define CC_PANEL_EXTENSION_POINT_NAME "control-center-panel-1"
+
+typedef struct CcPanelPrivate CcPanelPrivate;
+
+typedef struct
+{
+        GtkAlignment    parent;
+        CcPanelPrivate *priv;
+} CcPanel;
+
+typedef struct
+{
+        GtkAlignmentClass   parent_class;
+} CcPanelClass;
+
+GType               cc_panel_get_type               (void);
+
+G_END_DECLS
+
+#endif /* __CC_PANEL_H */
diff --git a/shell/control-center.c b/shell/control-center.c
index 7fc9a81..478a6bd 100644
--- a/shell/control-center.c
+++ b/shell/control-center.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2009, 2010 Intel, Inc.
+ * Copyright (c) 2010 Red Hat, Inc.
  *
  * The Control Center is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by the
@@ -18,14 +19,16 @@
  * Author: Thomas Wood <thos gnome org>
  */
 
+#include <gio/gio.h>
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
 #include <string.h>
 #define GMENU_I_KNOW_THIS_IS_UNSTABLE
 #include <gnome-menus/gmenu-tree.h>
 
-#define W(b,x) GTK_WIDGET (gtk_builder_get_object (b, x))
+#include "cc-panel.h"
 
+#define W(b,x) GTK_WIDGET (gtk_builder_get_object (b, x))
 
 typedef struct
 {
@@ -45,6 +48,61 @@ typedef struct
 
 void item_activated_cb (GtkIconView *icon_view, GtkTreePath *path, ShellData *data);
 
+static GHashTable *panels = NULL;
+
+static void
+load_panel_plugins (void)
+{
+  static volatile GType panel_type = G_TYPE_INVALID;
+  static GIOExtensionPoint *ep = NULL;
+  GList *modules;
+  GList *panel_implementations;
+  GList *l;
+
+  /* make sure base type is registered */
+  if (panel_type == G_TYPE_INVALID)
+    {
+      panel_type = g_type_from_name ("CcPanel");
+    }
+
+  panels = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+
+  if (ep == NULL)
+    {
+      g_debug ("Registering extension point");
+      ep = g_io_extension_point_register (CC_PANEL_EXTENSION_POINT_NAME);
+    }
+
+  /* load all modules */
+  g_debug ("Loading all modules in %s", EXTENSION_DIR);
+  modules = g_io_modules_load_all_in_directory (EXTENSION_DIR);
+
+  g_debug ("Loaded %d modules", g_list_length (modules));
+
+  /* find all extensions */
+  panel_implementations = g_io_extension_point_get_extensions (ep);
+  for (l = panel_implementations; l != NULL; l = l->next)
+    {
+      GIOExtension *extension;
+      CcPanel *panel;
+      char *id;
+
+      extension = l->data;
+
+      g_debug ("Found extension: %s %d", g_io_extension_get_name (extension), g_io_extension_get_priority (extension));
+      panel = g_object_new (g_io_extension_get_type (extension), NULL);
+      g_object_get (panel, "id", &id, NULL);
+      g_hash_table_insert (panels, g_strdup (id), g_object_ref (panel));
+      g_debug ("id: '%s'", id);
+      g_free (id);
+    }
+
+  /* unload all modules; the module our instantiated authority is in won't be unloaded because
+   * we've instantiated a reference to a type in this module
+   */
+  g_list_foreach (modules, (GFunc) g_type_module_unuse, NULL);
+  g_list_free (modules);
+}
 
 gboolean
 button_release_cb (GtkWidget      *view,
@@ -176,11 +234,14 @@ fill_model (ShellData *data)
           foo = gmenu_tree_directory_get_contents (l->data);
           dir_name = gmenu_tree_directory_get_name (l->data);
 
-          store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING,
+          store = gtk_list_store_new (4,
+                                      G_TYPE_STRING,
+                                      G_TYPE_STRING,
+                                      G_TYPE_STRING,
                                       GDK_TYPE_PIXBUF);
 
           iconview = gtk_icon_view_new_with_model (GTK_TREE_MODEL (store));
-          gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (iconview), 2);
+          gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (iconview), 3);
           gtk_icon_view_set_text_column (GTK_ICON_VIEW (iconview), 0);
           gtk_icon_view_set_item_width (GTK_ICON_VIEW (iconview), 120);
           g_signal_connect (iconview, "item-activated",
@@ -214,6 +275,7 @@ fill_model (ShellData *data)
                   GError *err = NULL;
                   const gchar *icon = gmenu_tree_entry_get_icon (f->data);
                   const gchar *name = gmenu_tree_entry_get_name (f->data);
+                  const gchar *id = gmenu_tree_entry_get_desktop_file_id (f->data);
                   const gchar *exec = gmenu_tree_entry_get_exec (f->data);
                   GdkPixbuf *pixbuf = NULL;
 
@@ -231,7 +293,8 @@ fill_model (ShellData *data)
                   gtk_list_store_insert_with_values (store, NULL, 0,
                                                      0, name,
                                                      1, exec,
-                                                     2, pixbuf,
+                                                     2, id,
+                                                     3, pixbuf,
                                                      -1);
 
                   gtk_list_store_insert_with_values (data->store, NULL, 0,
@@ -246,37 +309,6 @@ fill_model (ShellData *data)
 
 }
 
-static gboolean
-switch_after_delay (ShellData *data)
-{
-  gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), 2);
-
-  gtk_widget_show (W (data->builder, "home-button"));
-
-  gtk_window_set_title (GTK_WINDOW (data->window), data->current_title);
-
-  return FALSE;
-}
-
-void
-plug_added_cb (GtkSocket  *socket,
-               ShellData  *data)
-{
-  GtkWidget *notebook;
-  GSList *l;
-
-  notebook = W (data->builder, "notebook");
-
-  /* FIXME: this shouldn't be necassary if the capplet doesn't add to the socket
-   * until it is fully ready */
-  g_timeout_add (100, (GSourceFunc) switch_after_delay, data);
-
-  /* make sure no items are selected when the user switches back to the icon
-   * views */
-  for (l = data->icon_views; l; l = l->next)
-      gtk_icon_view_unselect_all (GTK_ICON_VIEW (l->data));
-}
-
 void
 item_activated_cb (GtkIconView *icon_view,
                    GtkTreePath *path,
@@ -284,48 +316,61 @@ item_activated_cb (GtkIconView *icon_view,
 {
   GtkTreeModel *model;
   GtkTreeIter iter = {0,};
-  gchar *name, *exec, *command;
-  GtkWidget *socket, *notebook;
-  guint socket_id = 0;
+  gchar *name, *exec, *id, *markup;
+  GtkWidget *notebook;
   static gint index = -1;
-
-  /* create new socket */
-  socket = gtk_socket_new ();
-
-  g_signal_connect (socket, "plug-added", G_CALLBACK (plug_added_cb), data);
+  CcPanel *panel;
 
   notebook = data->notebook;
   if (index >= 0)
     gtk_notebook_remove_page (GTK_NOTEBOOK (notebook), index);
-  index = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), socket, NULL);
-
-  gtk_widget_show (socket);
-
-  socket_id = gtk_socket_get_id (GTK_SOCKET (socket));
 
   /* get exec */
   model = gtk_icon_view_get_model (icon_view);
 
   gtk_tree_model_get_iter (model, &iter, path);
 
-  gtk_tree_model_get (model, &iter, 0, &name, 1, &exec, -1);
+  gtk_tree_model_get (model, &iter, 0, &name, 1, &exec, 2, &id, -1);
+
+  g_debug ("activated id: '%s'", id);
 
   g_free (data->current_title);
   data->current_title = name;
 
-  /* start app */
-  command = g_strdup_printf ("%s --socket=%u", exec, socket_id);
-  g_spawn_command_line_async (command, NULL);
-  g_free (command);
+  /* first look for a panel module */
+  panel = g_hash_table_lookup (panels, id);
+  if (panel != NULL)
+    {
+      index = gtk_notebook_append_page (GTK_NOTEBOOK (notebook), GTK_WIDGET (panel), NULL);
+      gtk_widget_show_all (GTK_WIDGET (panel));
+      gtk_notebook_set_current_page (GTK_NOTEBOOK (notebook), index);
+      gtk_widget_show (W (data->builder, "home-button"));
+      gtk_window_set_title (GTK_WINDOW (data->window), data->current_title);
+    }
+  else
+    {
+      /* start app directly */
+      g_debug ("Panel module not found for %s", id);
+      g_spawn_command_line_async (exec, NULL);
+    }
 
+  g_free (id);
   g_free (exec);
 }
 
-void
+static void
 home_button_clicked_cb (GtkButton *button,
                         ShellData *data)
 {
+  int        page;
+  GtkWidget *widget;
+
+  page = gtk_notebook_get_current_page (GTK_NOTEBOOK (data->notebook));
   gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), 0);
+  widget = gtk_notebook_get_nth_page (GTK_NOTEBOOK (data->notebook), page);
+  gtk_widget_hide (widget);
+  gtk_notebook_remove_page (GTK_NOTEBOOK (data->notebook), page);
+
   gtk_window_set_title (GTK_WINDOW (data->window), "System Settings");
 
   gtk_widget_hide (GTK_WIDGET (button));
@@ -415,6 +460,8 @@ main (int argc, char **argv)
   g_signal_connect (widget, "key-press-event",
                     G_CALLBACK (search_entry_key_press_event_cb), data);
 
+  load_panel_plugins ();
+
   gtk_widget_show_all (data->window);
 
   gtk_main ();



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]