[gthumb/ext: 2/3] [slideshow] various slideshow improvements



commit 7dd53c57eff21b3514d87bf1237941d845f143f6
Author: Paolo Bacchilega <paobac src gnome org>
Date:   Tue Sep 1 21:35:44 2009 +0200

    [slideshow] various slideshow improvements
    
    added a tab in the preferences dialog to configure the slideshow;
    make the transition system extensible.

 configure.ac                                       |    2 +
 extensions/comments/main.c                         |   39 ++--
 extensions/file_viewer/gth-file-viewer-page.c      |    2 +-
 extensions/file_viewer/main.c                      |    2 +-
 extensions/image_viewer/main.c                     |    2 +-
 extensions/image_viewer/preferences.h              |   14 +-
 extensions/photo_importer/dlg-photo-importer.c     |    6 +-
 extensions/slideshow/Makefile.am                   |    8 +-
 extensions/slideshow/actions.c                     |   25 ++-
 extensions/slideshow/data/Makefile.am              |   18 ++
 .../slideshow/data/gthumb-slideshow.schemas.in     |   57 ++++
 extensions/slideshow/data/ui/Makefile.am           |    5 +
 .../slideshow/data/ui/slideshow-preferences.ui     |  105 +++++++
 extensions/slideshow/gth-slideshow.c               |  287 +++++++++++---------
 extensions/slideshow/gth-slideshow.h               |   23 ++-
 extensions/slideshow/gth-transition.c              |  213 +++++++++++++++
 extensions/slideshow/gth-transition.h              |   68 +++++
 extensions/slideshow/main.c                        |  124 +++++++++
 extensions/slideshow/preferences.c                 |  189 +++++++++++++
 extensions/slideshow/preferences.h                 |   39 +++
 extensions/slideshow/slideshow.extension.in.in     |    1 -
 gthumb/dlg-personalize-filters.c                   |    4 +-
 gthumb/gth-browser.c                               |   15 +-
 gthumb/gth-filter-file.c                           |    2 +-
 gthumb/gth-hook.c                                  |    4 +-
 gthumb/gth-main-default-tests.c                    |  108 ++++----
 gthumb/gth-main.c                                  |  233 ++++++++--------
 gthumb/gth-main.h                                  |   18 +-
 gthumb/gth-test-chain.c                            |    2 +-
 gthumb/gth-test-selector.c                         |    8 +-
 30 files changed, 1272 insertions(+), 351 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d63faa9..820e734 100644
--- a/configure.ac
+++ b/configure.ac
@@ -286,6 +286,8 @@ extensions/search/Makefile
 extensions/search/data/Makefile
 extensions/search/data/ui/Makefile
 extensions/slideshow/Makefile
+extensions/slideshow/data/Makefile
+extensions/slideshow/data/ui/Makefile
 gthumb/Makefile
 gthumb/cursors/Makefile
 gthumb/icons/Makefile
diff --git a/extensions/comments/main.c b/extensions/comments/main.c
index 3ee1885..85a20d7 100644
--- a/extensions/comments/main.c
+++ b/extensions/comments/main.c
@@ -81,24 +81,27 @@ gthumb_extension_activate (void)
 	gth_main_register_metadata_info_v (comments_metadata_info);
 	gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_COMMENT);
 	gth_main_register_type ("edit-metadata-dialog-page", GTH_TYPE_EDIT_COMMENT_PAGE);
-	gth_main_register_test ("comment::note",
-				GTH_TYPE_TEST_SIMPLE,
-				"attributes", "comment::note",
-				"display-name", _("Comment"),
-				"data-type", GTH_TEST_DATA_TYPE_STRING,
-				"get-data-func", get_comment_for_test,
-				NULL);
-	gth_main_register_test ("comment::place",
-				GTH_TYPE_TEST_SIMPLE,
-				"attributes", "comment::place",
-				"display-name", _("Place"),
-				"data-type", GTH_TEST_DATA_TYPE_STRING,
-				"get-data-func", get_place_for_test,
-				NULL);
-	gth_main_register_test ("comment::category",
-				GTH_TYPE_TEST_CATEGORY,
-				"display-name", _("Tag"),
-				NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "comment::note",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "attributes", "comment::note",
+				  "display-name", _("Comment"),
+				  "data-type", GTH_TEST_DATA_TYPE_STRING,
+				  "get-data-func", get_comment_for_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "comment::place",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "attributes", "comment::place",
+				  "display-name", _("Place"),
+				  "data-type", GTH_TEST_DATA_TYPE_STRING,
+				  "get-data-func", get_place_for_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "comment::category",
+				  GTH_TYPE_TEST_CATEGORY,
+				  "display-name", _("Tag"),
+				  NULL);
 	gth_hook_add_callback ("add-sidecars", 10, G_CALLBACK (comments__add_sidecars_cb), NULL);
 }
 
diff --git a/extensions/file_viewer/gth-file-viewer-page.c b/extensions/file_viewer/gth-file-viewer-page.c
index 51a54f9..98df0f1 100644
--- a/extensions/file_viewer/gth-file-viewer-page.c
+++ b/extensions/file_viewer/gth-file-viewer-page.c
@@ -249,7 +249,7 @@ gth_file_viewer_page_finalize (GObject *obj)
 
 	self = GTH_FILE_VIEWER_PAGE (obj);
 
-	g_object_unref (self->priv->thumb_loader);
+	_g_object_unref (self->priv->thumb_loader);
 
 	G_OBJECT_CLASS (gth_file_viewer_page_parent_class)->finalize (obj);
 }
diff --git a/extensions/file_viewer/main.c b/extensions/file_viewer/main.c
index a83d43d..be2e406 100644
--- a/extensions/file_viewer/main.c
+++ b/extensions/file_viewer/main.c
@@ -30,7 +30,7 @@
 G_MODULE_EXPORT void
 gthumb_extension_activate (void)
 {
-	gth_main_register_object ("viewer-page", GTH_TYPE_FILE_VIEWER_PAGE);
+	gth_main_register_object (GTH_TYPE_VIEWER_PAGE, NULL, GTH_TYPE_FILE_VIEWER_PAGE, NULL);
 }
 
 
diff --git a/extensions/image_viewer/main.c b/extensions/image_viewer/main.c
index f3e3d75..5bd8a24 100644
--- a/extensions/image_viewer/main.c
+++ b/extensions/image_viewer/main.c
@@ -48,7 +48,7 @@ gthumb_extension_activate (void)
 	gth_main_register_metadata_category (image_metadata_category);
 	gth_main_register_metadata_info_v (image_metadata_info);
 	gth_main_register_metadata_provider (GTH_TYPE_METADATA_PROVIDER_IMAGE);
-	gth_main_register_object ("viewer-page", GTH_TYPE_IMAGE_VIEWER_PAGE);
+	gth_main_register_object (GTH_TYPE_VIEWER_PAGE, NULL, GTH_TYPE_IMAGE_VIEWER_PAGE, NULL);
 	gth_hook_add_callback ("dlg-preferences-construct", 10, G_CALLBACK (image_viewer__dlg_preferences_construct_cb), NULL);
 }
 
diff --git a/extensions/image_viewer/preferences.h b/extensions/image_viewer/preferences.h
index f8fd8d3..50c976f 100644
--- a/extensions/image_viewer/preferences.h
+++ b/extensions/image_viewer/preferences.h
@@ -25,13 +25,13 @@
 
 #include <gthumb.h>
 
-#define  PREF_ZOOM_QUALITY           "/apps/gthumb/viewer/zoom_quality"
-#define  PREF_ZOOM_CHANGE            "/apps/gthumb/viewer/zoom_change"
-#define  PREF_TRANSP_TYPE            "/apps/gthumb/viewer/transparency_type"
-#define  PREF_RESET_SCROLLBARS       "/apps/gthumb/viewer/reset_scrollbars"
-#define  PREF_CHECK_TYPE             "/apps/gthumb/viewer/check_type"
-#define  PREF_CHECK_SIZE             "/apps/gthumb/viewer/check_size"
-#define  PREF_BLACK_BACKGROUND       "/apps/gthumb/viewer/black_background"
+#define  PREF_VIEWER_ZOOM_QUALITY           "/apps/gthumb/viewer/zoom_quality"
+#define  PREF_VIEWER_ZOOM_CHANGE            "/apps/gthumb/viewer/zoom_change"
+#define  PREF_VIEWER_TRANSP_TYPE            "/apps/gthumb/viewer/transparency_type"
+#define  PREF_VIEWER_RESET_SCROLLBARS       "/apps/gthumb/viewer/reset_scrollbars"
+#define  PREF_VIEWER_CHECK_TYPE             "/apps/gthumb/viewer/check_type"
+#define  PREF_VIEWER_CHECK_SIZE             "/apps/gthumb/viewer/check_size"
+#define  PREF_VIEWER_BLACK_BACKGROUND       "/apps/gthumb/viewer/black_background"
 
 void image_viewer__dlg_preferences_construct_cb (GtkWidget  *dialog,
 						 GthBrowser *browser,
diff --git a/extensions/photo_importer/dlg-photo-importer.c b/extensions/photo_importer/dlg-photo-importer.c
index 4e55b1a..bc5382b 100644
--- a/extensions/photo_importer/dlg-photo-importer.c
+++ b/extensions/photo_importer/dlg-photo-importer.c
@@ -432,7 +432,7 @@ filter_combobox_changed_cb (GtkComboBox *widget,
 
 	idx = gtk_combo_box_get_active (widget);
 	test_id = g_list_nth (data->general_tests, idx)->data;
-	test = gth_main_get_test (test_id);
+	test = gth_main_get_registered_object (GTH_TYPE_TEST, test_id);
 	gth_file_list_set_filter (GTH_FILE_LIST (data->file_list), test);
 
 	g_object_unref (test);
@@ -654,7 +654,7 @@ dlg_photo_importer (GthBrowser *browser,
 
 	/**/
 
-	tests = gth_main_get_all_tests ();
+	tests = gth_main_get_registered_objects_id (GTH_TYPE_TEST);
 	general_filter = "file::type::is_media"; /* default value */
 	active_filter = 0;
 
@@ -667,7 +667,7 @@ dlg_photo_importer (GthBrowser *browser,
 			continue;
 
 		i_general += 1;
-		test = gth_main_get_test (registered_test_id);
+		test = gth_main_get_registered_object (GTH_TYPE_TEST, registered_test_id);
 		if (strcmp (registered_test_id, general_filter) == 0) {
 			active_filter = i_general;
 			gth_file_list_set_filter (GTH_FILE_LIST (data->file_list), test);
diff --git a/extensions/slideshow/Makefile.am b/extensions/slideshow/Makefile.am
index 8f70c94..09e09b0 100644
--- a/extensions/slideshow/Makefile.am
+++ b/extensions/slideshow/Makefile.am
@@ -1,5 +1,7 @@
 if ENABLE_CLUTTER
 
+SUBDIRS = data
+
 extensiondir = $(libdir)/gthumb-2.0/extensions
 extension_LTLIBRARIES = libslideshow.la
 
@@ -10,7 +12,11 @@ libslideshow_la_SOURCES = 		\
 	callbacks.h			\
 	gth-slideshow.c			\
 	gth-slideshow.h			\
-	main.c
+	gth-transition.c		\
+	gth-transition.h		\
+	main.c				\
+	preferences.c			\
+	preferences.h
 
 libslideshow_la_CFLAGS = $(GTHUMB_CFLAGS) $(CLUTTER_CFLAGS) $(DISABLE_DEPRECATED) $(WARNINGS) -I$(top_srcdir) -I$(top_builddir)/gthumb 
 libslideshow_la_LDFLAGS = $(EXTENSION_LIBTOOL_FLAGS)
diff --git a/extensions/slideshow/actions.c b/extensions/slideshow/actions.c
index 316e63d..5e9a4fa 100644
--- a/extensions/slideshow/actions.c
+++ b/extensions/slideshow/actions.c
@@ -25,6 +25,8 @@
 #include <glib/gi18n.h>
 #include <gthumb.h>
 #include "gth-slideshow.h"
+#include "gth-transition.h"
+#include "preferences.h"
 
 
 void
@@ -34,6 +36,8 @@ gth_browser_activate_action_view_slideshow (GtkAction  *action,
 	GList     *items;
 	GList     *file_list;
 	GtkWidget *slideshow;
+	char      *transition_id;
+	GList     *transitions = NULL;
 
 	items = gth_file_selection_get_selected (GTH_FILE_SELECTION (gth_browser_get_file_list_view (browser)));
 	if ((items == NULL) || (items->next == NULL))
@@ -42,11 +46,30 @@ gth_browser_activate_action_view_slideshow (GtkAction  *action,
 		file_list = gth_file_list_get_files (GTH_FILE_LIST (gth_browser_get_file_list (browser)), items);
 
 	slideshow = gth_slideshow_new (browser, file_list);
+	gth_slideshow_set_delay (GTH_SLIDESHOW (slideshow), eel_gconf_get_float (PREF_SLIDESHOW_CHANGE_DELAY, 5) * 1000);
+	gth_slideshow_set_automatic (GTH_SLIDESHOW (slideshow), eel_gconf_get_boolean (PREF_SLIDESHOW_AUTOMATIC, TRUE));
+	gth_slideshow_set_loop (GTH_SLIDESHOW (slideshow), eel_gconf_get_boolean (PREF_SLIDESHOW_WRAP_AROUND, FALSE));
+
+	transition_id = eel_gconf_get_string (PREF_SLIDESHOW_TRANSITION, DEFAULT_TRANSITION);
+	if (strcmp (transition_id, "random") == 0) {
+		transitions = gth_main_get_registered_objects (GTH_TYPE_TRANSITION);
+	}
+	else {
+		GthTransition *transition = gth_main_get_registered_object (GTH_TYPE_TRANSITION, transition_id);
+
+		if (transition != NULL)
+			transitions = g_list_append (NULL, transition);
+		else
+			transitions = NULL;
+	}
+	gth_slideshow_set_transitions (GTH_SLIDESHOW (slideshow), transitions);
+
 	gtk_window_fullscreen (GTK_WINDOW (slideshow));
 	/*gtk_window_set_default_size (GTK_WINDOW (slideshow), 700, 700);*/
 	gtk_window_present (GTK_WINDOW (slideshow));
-	gth_slideshow_play (GTH_SLIDESHOW (slideshow));
 
+	_g_object_list_unref (transitions);
+	g_free (transition_id);
 	_g_object_list_unref (file_list);
 	_gtk_tree_path_list_free (items);
 }
diff --git a/extensions/slideshow/data/Makefile.am b/extensions/slideshow/data/Makefile.am
new file mode 100644
index 0000000..c641128
--- /dev/null
+++ b/extensions/slideshow/data/Makefile.am
@@ -0,0 +1,18 @@
+SUBDIRS = ui
+
+schemadir = @GCONF_SCHEMA_FILE_DIR@
+schema_in_files = gthumb-slideshow.schemas.in
+schema_DATA = $(schema_in_files:.schemas.in=.schemas)
+
+ INTLTOOL_SCHEMAS_RULE@
+
+if GCONF_SCHEMAS_INSTALL
+install-data-local:
+	GCONF_CONFIG_SOURCE=$(GCONF_SCHEMA_CONFIG_SOURCE) $(GCONFTOOL) --makefile-install-rule $(top_builddir)/extensions/image_viewer/data/$(schema_DATA)
+endif
+
+EXTRA_DIST = $(schema_in_files)
+
+CLEANFILES = $(schema_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/slideshow/data/gthumb-slideshow.schemas.in b/extensions/slideshow/data/gthumb-slideshow.schemas.in
new file mode 100644
index 0000000..0c6bfa2
--- /dev/null
+++ b/extensions/slideshow/data/gthumb-slideshow.schemas.in
@@ -0,0 +1,57 @@
+<gconfschemafile>
+    <schemalist>
+
+      <schema>
+        <key>/schemas/apps/gthumb/slideshow/change_delay</key>
+        <applyto>/apps/gthumb/slideshow/change_delay</applyto>
+        <owner>gthumb</owner>
+        <type>float</type>
+        <default>4.0</default>
+        <locale name="C">
+          <short></short>
+          <long>
+          </long>
+        </locale>
+      </schema>
+
+      <schema>
+        <key>/schemas/apps/gthumb/slideshow/wrap_around</key>
+        <applyto>/apps/gthumb/slideshow/wrap_around</applyto>
+        <owner>gthumb</owner>
+        <type>bool</type>
+        <default>false</default>
+        <locale name="C">
+          <short></short>
+          <long>
+          </long>
+        </locale>
+      </schema>
+
+      <schema>
+        <key>/schemas/apps/gthumb/slideshow/automatic</key>
+        <applyto>/apps/gthumb/slideshow/automatic</applyto>
+        <owner>gthumb</owner>
+        <type>bool</type>
+        <default>true</default>
+        <locale name="C">
+          <short></short>
+          <long>
+          </long>
+        </locale>
+      </schema>
+
+      <schema>
+        <key>/schemas/apps/gthumb/slideshow/transition</key>
+        <applyto>/apps/gthumb/slideshow/transition</applyto>
+        <owner>gthumb</owner>
+        <type>string</type>
+        <default>fade-in</default>
+        <locale name="C">
+          <short></short>
+          <long>
+          </long>
+        </locale>
+      </schema>
+
+    </schemalist>
+</gconfschemafile>
diff --git a/extensions/slideshow/data/ui/Makefile.am b/extensions/slideshow/data/ui/Makefile.am
new file mode 100644
index 0000000..4263369
--- /dev/null
+++ b/extensions/slideshow/data/ui/Makefile.am
@@ -0,0 +1,5 @@
+uidir = $(datadir)/gthumb-2.0/ui
+ui_DATA = slideshow-preferences.ui
+EXTRA_DIST = $(ui_DATA)
+
+-include $(top_srcdir)/git.mk
diff --git a/extensions/slideshow/data/ui/slideshow-preferences.ui b/extensions/slideshow/data/ui/slideshow-preferences.ui
new file mode 100644
index 0000000..ebae268
--- /dev/null
+++ b/extensions/slideshow/data/ui/slideshow-preferences.ui
@@ -0,0 +1,105 @@
+<?xml version="1.0"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy project-wide -->
+  <object class="GtkAdjustment" id="delay_adjustment">
+    <property name="value">5</property>
+    <property name="lower">0.10000000000000001</property>
+    <property name="upper">100</property>
+    <property name="step_increment">0.10000000000000001</property>
+  </object>
+  <object class="GtkVBox" id="preferences_page">
+    <property name="visible">True</property>
+    <property name="border_width">12</property>
+    <property name="orientation">vertical</property>
+    <property name="spacing">6</property>
+    <child>
+      <object class="GtkHBox" id="transition_box">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkLabel" id="transition_label">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">_Transition effect:</property>
+            <property name="use_underline">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <placeholder/>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">0</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkHBox" id="hbox1">
+        <property name="visible">True</property>
+        <property name="spacing">6</property>
+        <child>
+          <object class="GtkCheckButton" id="automatic_checkbutton">
+            <property name="label" translatable="yes" comments="This is the first part of the phrase &quot;change automatically, every x seconds&quot;, where x is an input control that let the user choose a value.">_Change automatically, every</property>
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">False</property>
+            <property name="use_underline">True</property>
+            <property name="draw_indicator">True</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkSpinButton" id="change_delay_spinbutton">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="invisible_char">&#x25CF;</property>
+            <property name="adjustment">delay_adjustment</property>
+            <property name="digits">1</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkLabel" id="label2">
+            <property name="visible">True</property>
+            <property name="label" translatable="yes">seconds</property>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="position">2</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+    <child>
+      <object class="GtkCheckButton" id="wrap_around_checkbutton">
+        <property name="label" translatable="yes">_Restart when finished</property>
+        <property name="visible">True</property>
+        <property name="can_focus">True</property>
+        <property name="receives_default">False</property>
+        <property name="use_underline">True</property>
+        <property name="draw_indicator">True</property>
+      </object>
+      <packing>
+        <property name="expand">False</property>
+        <property name="fill">False</property>
+        <property name="position">2</property>
+      </packing>
+    </child>
+  </object>
+</interface>
diff --git a/extensions/slideshow/gth-slideshow.c b/extensions/slideshow/gth-slideshow.c
index f269f55..374a2ae 100644
--- a/extensions/slideshow/gth-slideshow.c
+++ b/extensions/slideshow/gth-slideshow.c
@@ -25,53 +25,109 @@
 #include <clutter/clutter.h>
 #include <clutter-gtk/clutter-gtk.h>
 #include "gth-slideshow.h"
+#include "gth-transition.h"
 
-#define ANIMATION_DURATION 200
-#define OPACITY_AT_MSECS(t)((int) (255 * ((double) (t) / ANIMATION_DURATION)))
 #define HIDE_CURSOR_DELAY 1000
 #define DEFAULT_DELAY 2000
-#define ANGLE_AT_MSECS(t)((180.0 * ((double) (t) / ANIMATION_DURATION)))
-#define POSITION_AT_MSECS(x, t)(((float)(x) * ((double) (t) / ANIMATION_DURATION)))
+
 
 struct _GthSlideshowPrivate {
 	GthBrowser      *browser;
 	GList           *file_list; /* GthFileData */
+	gboolean         automatic;
+	gboolean         loop;
 	GList           *current;
 	GthImageLoader  *image_loader;
-	ClutterActor    *stage;
+	GList           *transitions; /* GthTransition */
+	int              n_transitions;
+	GthTransition   *transition;
+	ClutterTimeline *timeline;
 	ClutterActor    *texture1;
 	ClutterActor    *texture2;
-	ClutterActor    *current_texture;
-	ClutterActor    *next_texture;
-	ClutterTimeline *timeline;
-	gboolean         first_frame;
 	guint            next_event;
 	guint            delay;
 	guint            hide_cursor_event;
+	GRand           *rand;
+	gboolean         first_show;
+	gboolean         one_loaded;
 };
 
 
 static gpointer parent_class = NULL;
 
 
+static GthTransition *
+_gth_slideshow_get_transition (GthSlideshow *self)
+{
+	if (self->priv->transitions == NULL)
+		return NULL;
+	else if (self->priv->transitions->next == NULL)
+		return self->priv->transitions->data;
+	else
+		return g_list_nth_data (self->priv->transitions,
+					g_rand_int_range (self->priv->rand, 0, self->priv->n_transitions));
+}
+
+
 static void
 _gth_slideshow_load_current_image (GthSlideshow *self)
 {
+	if (self->priv->next_event != 0) {
+		g_source_remove (self->priv->next_event);
+		self->priv->next_event = 0;
+	}
+
 	if (self->priv->current == NULL) {
-		gtk_widget_destroy (GTK_WIDGET (self));
-		return;
+		if (! self->priv->one_loaded || ! self->priv->loop) {
+			gtk_widget_destroy (GTK_WIDGET (self));
+			return;
+		}
+
+		if (clutter_timeline_get_direction (self->priv->timeline) == CLUTTER_TIMELINE_FORWARD)
+			self->priv->current = g_list_first (self->priv->file_list);
+		else
+			self->priv->current = g_list_last (self->priv->file_list);
 	}
 
+	self->priv->transition = _gth_slideshow_get_transition (self);
 	gth_image_loader_set_file_data (GTH_IMAGE_LOADER (self->priv->image_loader), (GthFileData *) self->priv->current->data);
 	gth_image_loader_load (GTH_IMAGE_LOADER (self->priv->image_loader));
 }
 
 
 static void
+_gth_slideshow_swap_current_and_next (GthSlideshow *self)
+{
+	ClutterGeometry tmp_geometry;
+
+	self->current_texture = self->next_texture;
+	if (self->current_texture == self->priv->texture1)
+		self->next_texture = self->priv->texture2;
+	else
+		self->next_texture = self->priv->texture1;
+
+	tmp_geometry = self->current_geometry;
+	self->current_geometry = self->next_geometry;
+	self->next_geometry = tmp_geometry;
+}
+
+
+static void
+_gth_slideshow_animation_completed (GthSlideshow *self)
+{
+	if (clutter_timeline_get_direction (self->priv->timeline) == CLUTTER_TIMELINE_FORWARD)
+		_gth_slideshow_swap_current_and_next (self);
+}
+
+
+static void
 _gth_slideshow_load_next_image (GthSlideshow *self)
 {
-	if (clutter_timeline_is_playing (self->priv->timeline))
-		return;
+	if (clutter_timeline_is_playing (self->priv->timeline)) {
+		clutter_timeline_pause (self->priv->timeline);
+		_gth_slideshow_animation_completed (self);
+	}
+
 	self->priv->current = self->priv->current->next;
 	clutter_timeline_set_direction (self->priv->timeline, CLUTTER_TIMELINE_FORWARD);
 	_gth_slideshow_load_current_image (self);
@@ -81,8 +137,11 @@ _gth_slideshow_load_next_image (GthSlideshow *self)
 static void
 _gth_slideshow_load_prev_image (GthSlideshow *self)
 {
-	if (clutter_timeline_is_playing (self->priv->timeline))
-			return;
+	if (clutter_timeline_is_playing (self->priv->timeline)) {
+		clutter_timeline_pause (self->priv->timeline);
+		_gth_slideshow_animation_completed (self);
+	}
+
 	self->priv->current = self->priv->current->prev;
 	clutter_timeline_set_direction (self->priv->timeline, CLUTTER_TIMELINE_BACKWARD);
 	_gth_slideshow_load_current_image (self);
@@ -94,8 +153,10 @@ next_image_cb (gpointer user_data)
 {
 	GthSlideshow *self = user_data;
 
-	g_source_remove (self->priv->next_event);
-	self->priv->next_event = 0;
+	if (self->priv->next_event != 0) {
+		g_source_remove (self->priv->next_event);
+		self->priv->next_event = 0;
+	}
 	_gth_slideshow_load_next_image (self);
 
 	return FALSE;
@@ -106,15 +167,13 @@ static void
 animation_completed_cb (ClutterTimeline *timeline,
 			GthSlideshow    *self)
 {
-	if (clutter_timeline_get_direction (self->priv->timeline) == CLUTTER_TIMELINE_FORWARD) {
-		self->priv->current_texture = self->priv->next_texture;
-		if (self->priv->current_texture == self->priv->texture1)
-			self->priv->next_texture = self->priv->texture2;
-		else
-			self->priv->next_texture = self->priv->texture1;
-	}
+	_gth_slideshow_animation_completed (self);
 
-	/*self->priv->next_event = g_timeout_add (self->priv->delay, next_image_cb, self);*/
+	if (self->priv->automatic) {
+		if (self->priv->next_event != 0)
+			g_source_remove (self->priv->next_event);
+		self->priv->next_event = g_timeout_add (self->priv->delay, next_image_cb, self);
+	}
 }
 
 
@@ -123,72 +182,10 @@ animation_frame_cb (ClutterTimeline *timeline,
 		    int              msecs,
 		    GthSlideshow    *self)
 {
-	float image_w, image_h;
-
-#ifndef SLIDE
-	clutter_actor_get_size (self->priv->stage, &image_w, &image_h);
-
-	clutter_actor_set_x (self->priv->next_texture, POSITION_AT_MSECS(image_w, ANIMATION_DURATION - msecs));
-	if (self->priv->current_texture != NULL)
-		clutter_actor_set_x (self->priv->current_texture, POSITION_AT_MSECS(- image_w, msecs));
-
-	if (self->priv->first_frame) {
-		if (self->priv->current_texture != NULL)
-			clutter_actor_show (self->priv->current_texture);
-		clutter_actor_show (self->priv->next_texture);
-		self->priv->first_frame = FALSE;
-	}
-#endif
-
-#ifdef FADE
-	if (self->priv->current_texture != NULL)
-		clutter_actor_set_opacity (self->priv->current_texture, OPACITY_AT_MSECS(ANIMATION_DURATION - msecs));
-	clutter_actor_set_opacity (self->priv->next_texture, OPACITY_AT_MSECS(msecs));
-
-	if (self->priv->first_frame) {
-		if (self->priv->current_texture != NULL) {
-			clutter_actor_show (self->priv->current_texture);
-			clutter_actor_raise (self->priv->next_texture, self->priv->current_texture);
-		}
-		clutter_actor_show (self->priv->next_texture);
-		self->priv->first_frame = FALSE;
-	}
-#endif
-
-#ifdef ROTATE
-	if ((float) msecs >= (float) ANIMATION_DURATION / 2.0) {
-		clutter_actor_show (self->priv->next_texture);
-		if (self->priv->current_texture != NULL)
-			clutter_actor_hide (self->priv->current_texture);
-	}
-	else {
-		clutter_actor_hide (self->priv->next_texture);
-		if (self->priv->current_texture != NULL)
-			clutter_actor_show (self->priv->current_texture);
-	}
-
-	clutter_actor_get_size (self->priv->stage, &image_w, &image_h);
-	clutter_actor_set_rotation (self->priv->next_texture,
-				    CLUTTER_Y_AXIS,
-		 		    ANGLE_AT_MSECS (ANIMATION_DURATION - msecs),
-		 		    image_w / 2.0,
-		 		    0.0,
-		 		    0.0);
-	if (self->priv->current_texture != NULL)
-		clutter_actor_set_rotation (self->priv->current_texture,
-					    CLUTTER_Y_AXIS,
-					    ANGLE_AT_MSECS (- msecs),
-					    image_w / 2.0,
-					    0.0,
-					    0.0);
-
-	if (self->priv->first_frame) {
-		if (self->priv->current_texture != NULL)
-			clutter_actor_raise (self->priv->next_texture, self->priv->current_texture);
-		clutter_actor_show (self->priv->next_texture);
-		self->priv->first_frame = FALSE;
-	}
-#endif
+	if (self->priv->transition != NULL)
+		gth_transition_frame (self->priv->transition, self, msecs);
+	if (self->first_frame)
+		self->first_frame = FALSE;
 }
 
 
@@ -196,7 +193,7 @@ static void
 animation_started_cb (ClutterTimeline *timeline,
 		      GthSlideshow    *self)
 {
-	self->priv->first_frame = TRUE;
+	self->first_frame = TRUE;
 }
 
 
@@ -213,35 +210,38 @@ image_loader_ready_cb (GthImageLoader *image_loader,
 	if (error != NULL) {
 		g_clear_error (&error);
 		_gth_slideshow_load_next_image (self);
+		return;
 	}
 
-	clutter_actor_hide (self->priv->next_texture);
+	self->priv->one_loaded = TRUE;
+
+	clutter_actor_hide (self->next_texture);
 
 	image = gth_image_loader_get_pixbuf (GTH_IMAGE_LOADER (image_loader));
-	gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (self->priv->next_texture), image, NULL);
+	gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (self->next_texture), image, NULL);
 
 	image_w = gdk_pixbuf_get_width (image);
 	image_h = gdk_pixbuf_get_height (image);
-	clutter_actor_get_size (self->priv->stage, &stage_w, &stage_h);
+	clutter_actor_get_size (self->stage, &stage_w, &stage_h);
 	scale_keeping_ratio (&image_w, &image_h, (int) stage_w, (int) stage_h, TRUE);
-	clutter_actor_set_size (self->priv->next_texture, (float) image_w, (float) image_h);
+	clutter_actor_set_size (self->next_texture, (float) image_w, (float) image_h);
 
 	image_x = (stage_w - image_w) / 2;
 	image_y = (stage_h - image_h) / 2;
-	clutter_actor_set_position (self->priv->next_texture, image_x, image_y);
+	clutter_actor_set_position (self->next_texture, image_x, image_y);
 
-	if (clutter_timeline_get_direction (self->priv->timeline) == CLUTTER_TIMELINE_BACKWARD) {
-		ClutterActor *tmp;
+	self->next_geometry.x = image_x;
+	self->next_geometry.y = image_y;
+	self->next_geometry.width = image_w;
+	self->next_geometry.height = image_h;
 
-		tmp = self->priv->next_texture;
-		self->priv->next_texture = self->priv->current_texture;
-		self->priv->current_texture = tmp;
-	}
+	if (clutter_timeline_get_direction (self->priv->timeline) == CLUTTER_TIMELINE_BACKWARD)
+		_gth_slideshow_swap_current_and_next (self);
 
 	clutter_timeline_rewind (self->priv->timeline);
 	clutter_timeline_start (self->priv->timeline);
-	if (self->priv->current_texture == NULL)
-		clutter_timeline_advance (self->priv->timeline, ANIMATION_DURATION);
+	if (self->current_texture == NULL)
+		clutter_timeline_advance (self->priv->timeline, GTH_TRANSITION_DURATION);
 }
 
 
@@ -252,6 +252,12 @@ gth_slideshow_init (GthSlideshow *self)
 	self->priv->file_list = NULL;
 	self->priv->next_event = 0;
 	self->priv->delay = DEFAULT_DELAY;
+	self->priv->automatic = FALSE;
+	self->priv->loop = FALSE;
+	self->priv->transitions = NULL;
+	self->priv->n_transitions = 0;
+	self->priv->rand = g_rand_new ();
+	self->priv->first_show = TRUE;
 
 	self->priv->image_loader = gth_image_loader_new (FALSE);
 	g_signal_connect (self->priv->image_loader, "ready", G_CALLBACK (image_loader_ready_cb), self);
@@ -272,6 +278,8 @@ gth_slideshow_finalize (GObject *object)
 	_g_object_unref (self->priv->browser);
 	_g_object_unref (self->priv->image_loader);
 	_g_object_unref (self->priv->timeline);
+	_g_object_list_unref (self->priv->transitions);
+	g_rand_free (self->priv->rand);
 
 	G_OBJECT_CLASS (parent_class)->finalize (object);
 }
@@ -326,7 +334,7 @@ hide_cursor_cb (gpointer data)
 	g_source_remove (self->priv->hide_cursor_event);
 	self->priv->hide_cursor_event = 0;
 
-	clutter_stage_hide_cursor (CLUTTER_STAGE (self->priv->stage));
+	clutter_stage_hide_cursor (CLUTTER_STAGE (self->stage));
 
 	return FALSE;
 }
@@ -338,7 +346,7 @@ stage_input_cb (ClutterStage *stage,
 	        GthSlideshow *self)
 {
 	if (event->type == CLUTTER_MOTION) {
-		clutter_stage_show_cursor (CLUTTER_STAGE (self->priv->stage));
+		clutter_stage_show_cursor (CLUTTER_STAGE (self->stage));
 		if (self->priv->hide_cursor_event != 0)
 			g_source_remove (self->priv->hide_cursor_event);
 		self->priv->hide_cursor_event = g_timeout_add (HIDE_CURSOR_DELAY, hide_cursor_cb, self);
@@ -362,6 +370,18 @@ stage_input_cb (ClutterStage *stage,
 
 
 static void
+gth_slideshow_show_cb (GtkWidget    *widget,
+		       GthSlideshow *self)
+{
+	if (! self->priv->first_show)
+		return;
+
+	_gth_slideshow_load_current_image (self);
+	self->priv->first_show = FALSE;
+}
+
+
+static void
 _gth_slideshow_construct (GthSlideshow *self,
 			  GthBrowser   *browser,
 			  GList        *file_list)
@@ -372,32 +392,35 @@ _gth_slideshow_construct (GthSlideshow *self,
 	self->priv->browser = _g_object_ref (browser);
 	self->priv->file_list = _g_object_list_ref (file_list);
 	self->priv->current = self->priv->file_list;
+	self->priv->one_loaded = FALSE;
 
 	embed = gtk_clutter_embed_new ();
-	self->priv->stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (embed));
-	clutter_stage_hide_cursor (CLUTTER_STAGE (self->priv->stage));
-	clutter_stage_set_color (CLUTTER_STAGE (self->priv->stage), &stage_color);
-	g_signal_connect (self->priv->stage, "motion-event", G_CALLBACK (stage_input_cb), self);
-	g_signal_connect (self->priv->stage, "key-release-event", G_CALLBACK (stage_input_cb), self);
+	self->stage = gtk_clutter_embed_get_stage (GTK_CLUTTER_EMBED (embed));
+	clutter_stage_hide_cursor (CLUTTER_STAGE (self->stage));
+	clutter_stage_set_color (CLUTTER_STAGE (self->stage), &stage_color);
+	g_signal_connect (self->stage, "motion-event", G_CALLBACK (stage_input_cb), self);
+	g_signal_connect (self->stage, "key-release-event", G_CALLBACK (stage_input_cb), self);
 
 	gtk_widget_show (embed);
 	gtk_container_add (GTK_CONTAINER (self), embed);
 
 	self->priv->texture1 = clutter_texture_new ();
 	clutter_actor_hide (self->priv->texture1);
-	clutter_container_add_actor (CLUTTER_CONTAINER (self->priv->stage), self->priv->texture1);
+	clutter_container_add_actor (CLUTTER_CONTAINER (self->stage), self->priv->texture1);
 
 	self->priv->texture2 = clutter_texture_new ();
 	clutter_actor_hide (self->priv->texture2);
-	clutter_container_add_actor (CLUTTER_CONTAINER (self->priv->stage), self->priv->texture2);
+	clutter_container_add_actor (CLUTTER_CONTAINER (self->stage), self->priv->texture2);
 
-	self->priv->current_texture = NULL;
-	self->priv->next_texture = self->priv->texture1;
+	self->current_texture = NULL;
+	self->next_texture = self->priv->texture1;
 
-	self->priv->timeline = clutter_timeline_new (ANIMATION_DURATION);
+	self->priv->timeline = clutter_timeline_new (GTH_TRANSITION_DURATION);
 	g_signal_connect (self->priv->timeline, "completed", G_CALLBACK (animation_completed_cb), self);
 	g_signal_connect (self->priv->timeline, "new-frame", G_CALLBACK (animation_frame_cb), self);
 	g_signal_connect (self->priv->timeline, "started", G_CALLBACK (animation_started_cb), self);
+
+	g_signal_connect (self, "show", G_CALLBACK (gth_slideshow_show_cb), self);
 }
 
 
@@ -414,19 +437,35 @@ gth_slideshow_new (GthBrowser *browser,
 }
 
 
-static gboolean
-start_playing (gpointer user_data)
+void
+gth_slideshow_set_delay (GthSlideshow *self,
+			 guint         msecs)
 {
-	GthSlideshow *self = user_data;
+	self->priv->delay = msecs;
+}
 
-	_gth_slideshow_load_current_image (self);
 
-	return FALSE;
+void
+gth_slideshow_set_automatic (GthSlideshow *self,
+			     gboolean      automatic)
+{
+	self->priv->automatic = automatic;
+}
+
+
+void
+gth_slideshow_set_loop (GthSlideshow *self,
+			gboolean      loop)
+{
+	self->priv->loop = loop;
 }
 
 
 void
-gth_slideshow_play (GthSlideshow *self)
+gth_slideshow_set_transitions (GthSlideshow *self,
+			       GList        *transitions)
 {
-	g_idle_add (start_playing, self);
+	_g_object_list_unref (self->priv->transitions);
+	self->priv->transitions = _g_object_list_ref (transitions);
+	self->priv->n_transitions = g_list_length (self->priv->transitions);
 }
diff --git a/extensions/slideshow/gth-slideshow.h b/extensions/slideshow/gth-slideshow.h
index 7d05209..b18c7d1 100644
--- a/extensions/slideshow/gth-slideshow.h
+++ b/extensions/slideshow/gth-slideshow.h
@@ -24,6 +24,8 @@
 #define GTH_SLIDESHOW_H
 
 #include <gthumb.h>
+#include <clutter/clutter.h>
+#include <clutter-gtk/clutter-gtk.h>
 
 G_BEGIN_DECLS
 
@@ -41,6 +43,12 @@ typedef struct _GthSlideshowPrivate  GthSlideshowPrivate;
 struct _GthSlideshow
 {
 	GtkWindow __parent;
+	ClutterActor        *stage;
+	ClutterActor        *current_texture;
+	ClutterActor        *next_texture;
+	ClutterGeometry      current_geometry;
+	ClutterGeometry      next_geometry;
+	gboolean             first_frame;
 	GthSlideshowPrivate *priv;
 };
 
@@ -49,10 +57,17 @@ struct _GthSlideshowClass
 	GtkWindowClass __parent_class;
 };
 
-GType            gth_slideshow_get_type  (void);
-GtkWidget *      gth_slideshow_new       (GthBrowser   *browser,
-					  GList        *file_list /* GthFileData */);
-void             gth_slideshow_play      (GthSlideshow *self);
+GType            gth_slideshow_get_type        (void);
+GtkWidget *      gth_slideshow_new             (GthBrowser       *browser,
+					        GList            *file_list /* GthFileData */);
+void             gth_slideshow_set_delay       (GthSlideshow     *self,
+					        guint             msecs);
+void             gth_slideshow_set_automatic   (GthSlideshow     *self,
+					        gboolean          automatic);
+void             gth_slideshow_set_loop        (GthSlideshow     *self,
+					        gboolean          loop);
+void             gth_slideshow_set_transitions (GthSlideshow     *self,
+					        GList            *transitions);
 
 G_END_DECLS
 
diff --git a/extensions/slideshow/gth-transition.c b/extensions/slideshow/gth-transition.c
new file mode 100644
index 0000000..ff1d55e
--- /dev/null
+++ b/extensions/slideshow/gth-transition.c
@@ -0,0 +1,213 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, 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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include "gth-transition.h"
+
+
+/* Properties */
+enum {
+        PROP_0,
+        PROP_ID,
+        PROP_DISPLAY_NAME,
+        PROP_FRAME_FUNC
+};
+
+
+struct _GthTransitionPrivate {
+	char         *id;
+	char         *display_name;
+	FrameFunc     frame_func;
+};
+
+
+static gpointer parent_class = NULL;
+
+
+static void
+gth_transition_init (GthTransition *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GTH_TYPE_TRANSITION, GthTransitionPrivate);
+	self->priv->id = g_strdup ("");
+	self->priv->display_name = g_strdup ("");
+	self->priv->frame_func = NULL;
+}
+
+
+static void
+gth_transition_finalize (GObject *object)
+{
+	GthTransition *self = GTH_TRANSITION (object);
+
+	g_free (self->priv->id);
+	g_free (self->priv->display_name);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+
+static void
+gth_transition_set_property (GObject      *object,
+			     guint         property_id,
+			     const GValue *value,
+			     GParamSpec   *pspec)
+{
+	GthTransition *self = GTH_TRANSITION (object);
+
+	switch (property_id) {
+	case PROP_ID:
+		g_free (self->priv->id);
+		self->priv->id = g_value_dup_string (value);
+		if (self->priv->id == NULL)
+			self->priv->id = g_strdup ("");
+		break;
+
+	case PROP_DISPLAY_NAME:
+		g_free (self->priv->display_name);
+		self->priv->display_name = g_value_dup_string (value);
+		if (self->priv->display_name == NULL)
+			self->priv->display_name = g_strdup ("");
+		break;
+
+	case PROP_FRAME_FUNC:
+		self->priv->frame_func = g_value_get_pointer (value);
+		break;
+
+	default:
+		break;
+	}
+}
+
+
+static void
+gth_transition_get_property (GObject    *object,
+			     guint       property_id,
+			     GValue     *value,
+			     GParamSpec *pspec)
+{
+	GthTransition *self = GTH_TRANSITION (object);
+
+	switch (property_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;
+
+	case PROP_FRAME_FUNC:
+		g_value_set_pointer (value, self->priv->frame_func);
+		break;
+
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+		break;
+	}
+}
+
+static void
+gth_transition_class_init (GthTransitionClass *klass)
+{
+	GObjectClass *object_class;
+
+	parent_class = g_type_class_peek_parent (klass);
+	g_type_class_add_private (klass, sizeof (GthTransitionPrivate));
+
+	object_class = G_OBJECT_CLASS (klass);
+	object_class->get_property = gth_transition_get_property;
+	object_class->set_property = gth_transition_set_property;
+	object_class->finalize = gth_transition_finalize;
+
+	g_object_class_install_property (object_class,
+					 PROP_ID,
+					 g_param_spec_string ("id",
+                                                              "ID",
+                                                              "The object id",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_DISPLAY_NAME,
+					 g_param_spec_string ("display-name",
+                                                              "Display name",
+                                                              "The user visible name",
+                                                              NULL,
+                                                              G_PARAM_READWRITE));
+	g_object_class_install_property (object_class,
+					 PROP_FRAME_FUNC,
+					 g_param_spec_pointer ("frame-func",
+							       "Frame Function",
+							       "The fuction used to set the current frame",
+							       G_PARAM_READWRITE));
+}
+
+
+GType
+gth_transition_get_type (void)
+{
+	static GType type = 0;
+
+	if (! type) {
+		GTypeInfo type_info = {
+			sizeof (GthTransitionClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gth_transition_class_init,
+			NULL,
+			NULL,
+			sizeof (GthTransition),
+			0,
+			(GInstanceInitFunc) gth_transition_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "GthTransition",
+					       &type_info,
+					       0);
+	}
+
+	return type;
+}
+
+
+const char *
+gth_transition_get_id (GthTransition *self)
+{
+	return self->priv->id;
+}
+
+
+const char *
+gth_transition_get_display_name (GthTransition *self)
+{
+	return self->priv->display_name;
+}
+
+
+void
+gth_transition_frame (GthTransition *self,
+		      GthSlideshow  *slideshow,
+		      int            msecs)
+{
+	if (self->priv->frame_func != NULL)
+		self->priv->frame_func (slideshow, msecs);
+}
diff --git a/extensions/slideshow/gth-transition.h b/extensions/slideshow/gth-transition.h
new file mode 100644
index 0000000..c760e48
--- /dev/null
+++ b/extensions/slideshow/gth-transition.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, 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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef GTH_TRANSITION_H
+#define GTH_TRANSITION_H
+
+#include <glib-object.h>
+#include <clutter/clutter.h>
+#include <clutter-gtk/clutter-gtk.h>
+#include "gth-slideshow.h"
+
+G_BEGIN_DECLS
+
+#define GTH_TRANSITION_DURATION 200
+
+#define GTH_TYPE_TRANSITION              (gth_transition_get_type ())
+#define GTH_TRANSITION(obj)              (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTH_TYPE_TRANSITION, GthTransition))
+#define GTH_TRANSITION_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), GTH_TRANSITION_TYPE, GthTransitionClass))
+#define GTH_IS_TRANSITION(obj)           (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTH_TYPE_TRANSITION))
+#define GTH_IS_TRANSITION_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), GTH_TYPE_TRANSITION))
+#define GTH_TRANSITION_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS((obj), GTH_TYPE_TRANSITION, GthTransitionClass))
+
+typedef struct _GthTransition         GthTransition;
+typedef struct _GthTransitionClass    GthTransitionClass;
+typedef struct _GthTransitionPrivate  GthTransitionPrivate;
+
+typedef void (*FrameFunc) (GthSlideshow *slideshow, int msecs);
+
+struct _GthTransition
+{
+	GtkWindow __parent;
+	GthTransitionPrivate *priv;
+};
+
+struct _GthTransitionClass
+{
+	GtkWindowClass __parent_class;
+};
+
+GType             gth_transition_get_type         (void);
+const char *      gth_transition_get_id           (GthTransition *self);
+const char *      gth_transition_get_display_name (GthTransition *self);
+void              gth_transition_frame            (GthTransition *self,
+						   GthSlideshow  *slideshow,
+						   int            msecs);
+
+G_END_DECLS
+
+#endif /* GTH_TRANSITION_H */
diff --git a/extensions/slideshow/main.c b/extensions/slideshow/main.c
index b6e4e74..df81799 100644
--- a/extensions/slideshow/main.c
+++ b/extensions/slideshow/main.c
@@ -25,13 +25,137 @@
 #include <gtk/gtk.h>
 #include <gthumb.h>
 #include "callbacks.h"
+#include "gth-transition.h"
+#include "preferences.h"
+
+
+#define OPACITY_AT_MSECS(t)((int) (255 * ((double) (t) / GTH_TRANSITION_DURATION)))
+#define ANGLE_AT_MSECS(t)((180.0 * ((double) (t) / GTH_TRANSITION_DURATION)))
+#define POSITION_AT_MSECS(x, t)(((float)(x) * ((double) (t) / GTH_TRANSITION_DURATION)))
+
+
+static void
+no_transition (GthSlideshow *self,
+	       int           msecs)
+{
+	if (self->first_frame) {
+		if (self->current_texture != NULL)
+			clutter_actor_hide (self->current_texture);
+		clutter_actor_show (self->next_texture);
+	}
+}
+
+
+static void
+slide_in_transition (GthSlideshow *self,
+		     int           msecs)
+{
+	float stage_w, stage_h;
+
+	clutter_actor_get_size (self->stage, &stage_w, &stage_h);
+
+	clutter_actor_set_x (self->next_texture, POSITION_AT_MSECS (stage_w, GTH_TRANSITION_DURATION - msecs) + self->next_geometry.x);
+	if (self->current_texture != NULL)
+		clutter_actor_set_x (self->current_texture, POSITION_AT_MSECS (- stage_w, msecs) + self->current_geometry.x);
+
+	if (self->first_frame) {
+		if (self->current_texture != NULL)
+			clutter_actor_show (self->current_texture);
+		clutter_actor_show (self->next_texture);
+	}
+}
+
+
+static void
+fade_in_transition (GthSlideshow *self,
+		    int           msecs)
+{
+	if (self->current_texture != NULL)
+		clutter_actor_set_opacity (self->current_texture, OPACITY_AT_MSECS (GTH_TRANSITION_DURATION - msecs));
+	clutter_actor_set_opacity (self->next_texture, OPACITY_AT_MSECS(msecs));
+
+	if (self->first_frame) {
+		if (self->current_texture != NULL) {
+			clutter_actor_show (self->current_texture);
+			clutter_actor_raise (self->next_texture, self->current_texture);
+		}
+		clutter_actor_show (self->next_texture);
+	}
+}
+
+
+static void
+flip_transition (GthSlideshow *self,
+		 int           msecs)
+{
+	float stage_w, stage_h;
+
+	if ((float) msecs >= (float) GTH_TRANSITION_DURATION / 2.0) {
+		clutter_actor_show (self->next_texture);
+		if (self->current_texture != NULL)
+			clutter_actor_hide (self->current_texture);
+	}
+	else {
+		clutter_actor_hide (self->next_texture);
+		if (self->current_texture != NULL)
+			clutter_actor_show (self->current_texture);
+	}
+
+	clutter_actor_get_size (self->stage, &stage_w, &stage_h);
+	clutter_actor_set_rotation (self->next_texture,
+				    CLUTTER_Y_AXIS,
+		 		    ANGLE_AT_MSECS (GTH_TRANSITION_DURATION - msecs),
+		 		    stage_w / 2.0,
+		 		    0.0,
+		 		    0.0);
+	if (self->current_texture != NULL)
+		clutter_actor_set_rotation (self->current_texture,
+					    CLUTTER_Y_AXIS,
+					    ANGLE_AT_MSECS (- msecs),
+					    stage_w / 2.0,
+					    0.0,
+					    0.0);
+
+	if (self->first_frame) {
+		if (self->current_texture != NULL)
+			clutter_actor_raise (self->next_texture, self->current_texture);
+		clutter_actor_show (self->next_texture);
+		self->first_frame = FALSE;
+	}
+}
 
 
 G_MODULE_EXPORT void
 gthumb_extension_activate (void)
 {
+	gth_main_register_object (GTH_TYPE_TRANSITION,
+				  "none",
+				  GTH_TYPE_TRANSITION,
+				  "display-name", _("None"),
+				  "frame-func", no_transition,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TRANSITION,
+				  "slide-in",
+				  GTH_TYPE_TRANSITION,
+				  "display-name", _("Slide In"),
+				  "frame-func", slide_in_transition,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TRANSITION,
+				  "fade-in",
+				  GTH_TYPE_TRANSITION,
+				  "display-name", _("Fade In"),
+				  "frame-func", fade_in_transition,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TRANSITION,
+				  "flip",
+				  GTH_TYPE_TRANSITION,
+				  "display-name", _("Flip Page"),
+				  "frame-func", flip_transition,
+				  NULL);
+
 	gth_hook_add_callback ("gth-browser-construct", 10, G_CALLBACK (ss__gth_browser_construct_cb), NULL);
 	gth_hook_add_callback ("gth-browser-update-sensitivity", 10, G_CALLBACK (ss__gth_browser_update_sensitivity_cb), NULL);
+	gth_hook_add_callback ("dlg-preferences-construct", 20, G_CALLBACK (ss__dlg_preferences_construct_cb), NULL);
 }
 
 
diff --git a/extensions/slideshow/preferences.c b/extensions/slideshow/preferences.c
new file mode 100644
index 0000000..f9fbc32
--- /dev/null
+++ b/extensions/slideshow/preferences.c
@@ -0,0 +1,189 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, 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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#include <config.h>
+#include <glib/gi18n.h>
+#include "gth-transition.h"
+#include "preferences.h"
+
+
+#define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
+#define BROWSER_DATA_KEY "slideshow-preference-data"
+
+
+enum {
+	TRANSITION_COLUMN_ID,
+	TRANSITION_COLUMN_DISPLAY_NAME
+};
+
+
+typedef struct {
+	GtkBuilder *builder;
+	GtkWidget  *transition_combobox;
+} BrowserData;
+
+
+static void
+browser_data_free (BrowserData *data)
+{
+	g_object_unref (data->builder);
+	g_free (data);
+}
+
+
+static void
+transition_combobox_changed_cb (GtkComboBox *combo_box,
+				BrowserData *data)
+{
+	GtkTreeIter   iter;
+	GtkTreeModel *tree_model;
+	char         *transition_id;
+
+	if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (data->transition_combobox), &iter))
+		return;
+
+	tree_model = gtk_combo_box_get_model (GTK_COMBO_BOX (data->transition_combobox));
+	gtk_tree_model_get (tree_model, &iter, TRANSITION_COLUMN_ID, &transition_id, -1);
+	eel_gconf_set_string (PREF_SLIDESHOW_TRANSITION, transition_id);
+
+	g_free (transition_id);
+}
+
+
+static void
+automatic_checkbutton_toggled_cb (GtkToggleButton *button,
+				  BrowserData     *data)
+{
+	eel_gconf_set_boolean (PREF_SLIDESHOW_AUTOMATIC, gtk_toggle_button_get_active (button));
+}
+
+
+static void
+wrap_around_checkbutton_toggled_cb (GtkToggleButton *button,
+				    BrowserData     *data)
+{
+	eel_gconf_set_boolean (PREF_SLIDESHOW_WRAP_AROUND, gtk_toggle_button_get_active (button));
+}
+
+
+static void
+change_delay_spinbutton_value_changed_cb (GtkSpinButton *spinbutton,
+				          BrowserData   *data)
+{
+	eel_gconf_set_float (PREF_SLIDESHOW_CHANGE_DELAY, gtk_spin_button_get_value (spinbutton));
+}
+
+
+void
+ss__dlg_preferences_construct_cb (GtkWidget  *dialog,
+				  GthBrowser *browser,
+				  GtkBuilder *dialog_builder)
+{
+	BrowserData     *data;
+	GtkWidget       *notebook;
+	GtkWidget       *page;
+	GtkListStore    *model;
+	GtkCellRenderer *renderer;
+	char            *current_transition;
+	GList           *transitions;
+	GList           *scan;
+	GtkTreeIter      iter;
+	int              i, i_active;
+	GtkWidget       *label;
+
+	data = g_new0 (BrowserData, 1);
+	data->builder = _gtk_builder_new_from_file ("slideshow-preferences.ui", "slideshow");
+
+	notebook = _gtk_builder_get_widget (dialog_builder, "notebook");
+
+	page = _gtk_builder_get_widget (data->builder, "preferences_page");
+	gtk_widget_show (page);
+
+	model = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+	data->transition_combobox = gtk_combo_box_new_with_model (GTK_TREE_MODEL (model));
+	g_object_unref (model);
+
+	renderer = gtk_cell_renderer_text_new ();
+	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (data->transition_combobox),
+				    renderer,
+				    TRUE);
+	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (data->transition_combobox),
+					renderer,
+					"text", TRANSITION_COLUMN_DISPLAY_NAME,
+					NULL);
+
+	current_transition = eel_gconf_get_string (PREF_SLIDESHOW_TRANSITION, DEFAULT_TRANSITION);
+	transitions = gth_main_get_registered_objects (GTH_TYPE_TRANSITION);
+	for (i = 0, i_active = 0, scan = transitions; scan; scan = scan->next, i++) {
+		GthTransition *transition = scan->data;
+
+		if (strcmp (gth_transition_get_id (transition), current_transition) == 0)
+			i_active = i;
+
+		gtk_list_store_append (model, &iter);
+		gtk_list_store_set (model, &iter,
+				    TRANSITION_COLUMN_ID, gth_transition_get_id (transition),
+				    TRANSITION_COLUMN_DISPLAY_NAME, gth_transition_get_display_name (transition),
+				    -1);
+	}
+
+	if (strcmp ("random", current_transition) == 0)
+		i_active = i;
+	gtk_list_store_append (model, &iter);
+	gtk_list_store_set (model, &iter,
+			    TRANSITION_COLUMN_ID, "random",
+			    TRANSITION_COLUMN_DISPLAY_NAME, _("Random"),
+			    -1);
+
+	gtk_combo_box_set_active (GTK_COMBO_BOX (data->transition_combobox), i_active);
+	gtk_widget_show (data->transition_combobox);
+	gtk_container_add (GTK_CONTAINER (GET_WIDGET ("transition_box")), data->transition_combobox);
+	g_free (current_transition);
+
+	gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("change_delay_spinbutton")), eel_gconf_get_float (PREF_SLIDESHOW_CHANGE_DELAY, 5));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("automatic_checkbutton")), eel_gconf_get_boolean (PREF_SLIDESHOW_AUTOMATIC, TRUE));
+	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("wrap_around_checkbutton")), eel_gconf_get_boolean (PREF_SLIDESHOW_WRAP_AROUND, FALSE));
+
+	g_signal_connect (G_OBJECT (data->transition_combobox),
+			  "changed",
+			  G_CALLBACK (transition_combobox_changed_cb),
+			  data);
+	g_signal_connect (G_OBJECT (GET_WIDGET ("automatic_checkbutton")),
+			  "toggled",
+			  G_CALLBACK (automatic_checkbutton_toggled_cb),
+			  data);
+	g_signal_connect (G_OBJECT (GET_WIDGET ("wrap_around_checkbutton")),
+			  "toggled",
+			  G_CALLBACK (wrap_around_checkbutton_toggled_cb),
+			  data);
+	g_signal_connect (G_OBJECT (GET_WIDGET ("change_delay_spinbutton")),
+			  "value-changed",
+			  G_CALLBACK (change_delay_spinbutton_value_changed_cb),
+			  data);
+
+	label = gtk_label_new (_("Slideshow"));
+	gtk_widget_show (label);
+
+	gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label);
+
+	g_object_set_data_full (G_OBJECT (dialog), BROWSER_DATA_KEY, data, (GDestroyNotify) browser_data_free);
+}
diff --git a/extensions/slideshow/preferences.h b/extensions/slideshow/preferences.h
new file mode 100644
index 0000000..354efa0
--- /dev/null
+++ b/extensions/slideshow/preferences.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ *  GThumb
+ *
+ *  Copyright (C) 2009 Free Software Foundation, 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 Street #330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef PREFERENCES_H
+#define PREFERENCES_H
+
+#include <gthumb.h>
+
+#define  PREF_SLIDESHOW_CHANGE_DELAY     "/apps/gthumb/slideshow/change_delay"
+#define  PREF_SLIDESHOW_WRAP_AROUND      "/apps/gthumb/slideshow/wrap_around"
+#define  PREF_SLIDESHOW_AUTOMATIC        "/apps/gthumb/slideshow/automatic"
+#define  PREF_SLIDESHOW_TRANSITION       "/apps/gthumb/slideshow/transition"
+
+#define  DEFAULT_TRANSITION "fade-in"
+
+void ss__dlg_preferences_construct_cb (GtkWidget  *dialog,
+				       GthBrowser *browser,
+				       GtkBuilder *builder);
+
+#endif /* CALLBACKS_H */
diff --git a/extensions/slideshow/slideshow.extension.in.in b/extensions/slideshow/slideshow.extension.in.in
index 6b4226c..0feac0b 100644
--- a/extensions/slideshow/slideshow.extension.in.in
+++ b/extensions/slideshow/slideshow.extension.in.in
@@ -8,4 +8,3 @@ Version=1.0
 [Loader]
 Type=module
 File=%LIBRARY%
-Requires=catalogs
diff --git a/gthumb/dlg-personalize-filters.c b/gthumb/dlg-personalize-filters.c
index e83443b..df0ee30 100644
--- a/gthumb/dlg-personalize-filters.c
+++ b/gthumb/dlg-personalize-filters.c
@@ -519,7 +519,7 @@ dlg_personalize_filters (GthBrowser *browser)
 
 	/* Set widgets data. */
 
-	tests = gth_main_get_all_tests ();
+	tests = gth_main_get_registered_objects_id (GTH_TYPE_TEST);
 	general_filter = eel_gconf_get_string (PREF_GENERAL_FILTER, DEFAULT_GENERAL_FILTER);
 	active_filter = 0;
 
@@ -536,7 +536,7 @@ dlg_personalize_filters (GthBrowser *browser)
 		if (strcmp (registered_test_id, general_filter) == 0)
 			active_filter = i_general;
 
-		test = gth_main_get_test (registered_test_id);
+		test = gth_main_get_registered_object (GTH_TYPE_TEST, registered_test_id);
 		data->general_tests = g_list_prepend (data->general_tests, g_strdup (gth_test_get_id (test)));
 		gtk_combo_box_append_text (GTK_COMBO_BOX (data->general_filter_combobox), gth_test_get_display_name (test));
 		g_object_unref (test);
diff --git a/gthumb/gth-browser.c b/gthumb/gth-browser.c
index 32918cd..d15ce77 100644
--- a/gthumb/gth-browser.c
+++ b/gthumb/gth-browser.c
@@ -108,6 +108,7 @@ struct _GthBrowserPrivateData {
 	GtkWidget         *list_extra_widget;
 	GtkWidget         *file_properties;
 
+	GList             *viewer_pages;
 	GtkWidget         *viewer_pane;
 	GtkWidget         *viewer_sidebar;
 	GtkWidget         *viewer_container;
@@ -1997,6 +1998,7 @@ gth_browser_finalize (GObject *object)
 		_g_object_unref (browser->priv->current_file);
 		_g_object_unref (browser->priv->viewer_page);
 		_g_object_unref (browser->priv->image_preloader);
+		_g_object_list_unref (browser->priv->viewer_pages);
 		_g_object_list_unref (browser->priv->history);
 		gth_icon_cache_free (browser->priv->menu_icon_cache);
 		g_hash_table_unref (browser->priv->named_dialogs);
@@ -4154,9 +4156,8 @@ _gth_browser_load_file (GthBrowser  *browser,
 			GthFileData *file_data,
 			gboolean     view)
 {
-	GPtrArray *viewer_pages;
-	int       i;
-	GList    *files;
+	GList *scan;
+	GList *files;
 
 	if (file_data == NULL) {
 		_gth_browser_deactivate_viewer_page (browser);
@@ -4181,11 +4182,11 @@ _gth_browser_load_file (GthBrowser  *browser,
 
 	_gth_browser_make_file_visible (browser, browser->priv->current_file);
 
-	viewer_pages = gth_main_get_object_set ("viewer-page");
-	for (i = viewer_pages->len - 1; i >= 0; i--) {
-		GthViewerPage *registered_viewer_page;
+	if (browser->priv->viewer_pages == NULL)
+		browser->priv->viewer_pages = gth_main_get_registered_objects (GTH_TYPE_VIEWER_PAGE);
+	for (scan = browser->priv->viewer_pages; scan; scan = scan->next) {
+		GthViewerPage *registered_viewer_page = scan->data;
 
-		registered_viewer_page = g_ptr_array_index (viewer_pages, i);
 		if (gth_viewer_page_can_view (registered_viewer_page, browser->priv->current_file)) {
 			if ((browser->priv->viewer_page != NULL) && (G_OBJECT_TYPE (registered_viewer_page) != G_OBJECT_TYPE (browser->priv->viewer_page))) {
 				gth_viewer_page_deactivate (browser->priv->viewer_page);
diff --git a/gthumb/gth-filter-file.c b/gthumb/gth-filter-file.c
index e761c2d..50b20f5 100644
--- a/gthumb/gth-filter-file.c
+++ b/gthumb/gth-filter-file.c
@@ -88,7 +88,7 @@ gth_filter_file_load_from_data (GthFilterFile  *filters,
 					dom_domizable_load_from_element (DOM_DOMIZABLE (test), child);
 				}
 				else if (strcmp (child->tag_name, "test") == 0) {
-					test = gth_main_get_test (dom_element_get_attribute (child, "id"));
+					test = gth_main_get_registered_object (GTH_TYPE_TEST, dom_element_get_attribute (child, "id"));
 					if (test != NULL)
 						dom_domizable_load_from_element (DOM_DOMIZABLE (test), child);
 				}
diff --git a/gthumb/gth-hook.c b/gthumb/gth-hook.c
index ad1b193..5c58758 100644
--- a/gthumb/gth-hook.c
+++ b/gthumb/gth-hook.c
@@ -117,9 +117,9 @@ hook_compare_func (GHook *new_hook,
 	GthHookCallback *new_function = GTH_HOOK_CALLBACK (new_hook);
 	GthHookCallback *sibling_function = GTH_HOOK_CALLBACK (sibling);
 
-	if (new_function->sort_order > sibling_function->sort_order)
+	if (new_function->sort_order < sibling_function->sort_order)
 		return -1;
-	else if (new_function->sort_order < sibling_function->sort_order)
+	else if (new_function->sort_order > sibling_function->sort_order)
 		return 1;
 	else
 		return 0;
diff --git a/gthumb/gth-main-default-tests.c b/gthumb/gth-main-default-tests.c
index 8b8ecd8..30bbcf1 100644
--- a/gthumb/gth-main-default-tests.c
+++ b/gthumb/gth-main-default-tests.c
@@ -135,54 +135,62 @@ get_filesize_for_test (GthTest       *test,
 void
 gth_main_register_default_tests (void)
 {
-	gth_main_register_test ("file::type::is_file",
-				GTH_TYPE_TEST_SIMPLE,
-				"display-name", _("All Files"),
-				"data-type", GTH_TEST_DATA_TYPE_NONE,
-				"get-data-func", is_file_test,
-				NULL);
-	gth_main_register_test ("file::type::is_image",
-				GTH_TYPE_TEST_SIMPLE,
-				"display-name", _("Images"),
-				"data-type", GTH_TEST_DATA_TYPE_NONE,
-				"get-data-func", is_image_test,
-				NULL);
-	gth_main_register_test ("file::type::is_video",
-				GTH_TYPE_TEST_SIMPLE,
-				"display-name", _("Video"),
-				"data-type", GTH_TEST_DATA_TYPE_NONE,
-				"get-data-func", is_video_test,
-				NULL);
-	gth_main_register_test ("file::type::is_audio",
-				GTH_TYPE_TEST_SIMPLE,
-				"display-name", _("Audio"),
-				"data-type", GTH_TEST_DATA_TYPE_NONE,
-				"get-data-func", is_audio_test,
-				NULL);
-	gth_main_register_test ("file::type::is_media",
-				GTH_TYPE_TEST_SIMPLE,
-				"display-name", _("Media"),
-				"data-type", GTH_TEST_DATA_TYPE_NONE,
-				"get-data-func", is_media_test,
-				NULL);
-	gth_main_register_test ("file::type::is_text",
-				GTH_TYPE_TEST_SIMPLE,
-				"display-name", _("Text Files"),
-				"data-type", GTH_TEST_DATA_TYPE_NONE,
-				"get-data-func", is_text_test,
-				NULL);
-	gth_main_register_test ("file::name",
-				GTH_TYPE_TEST_SIMPLE,
-				"attributes", "gth::file::display-name",
-				"display-name", _("Filename"),
-				"data-type", GTH_TEST_DATA_TYPE_STRING,
-				"get-data-func", get_filename_for_test,
-				NULL);
-	gth_main_register_test ("file::size",
-				GTH_TYPE_TEST_SIMPLE,
-				"attributes", "gth::file::size",
-				"display-name", _("Size"),
-				"data-type", GTH_TEST_DATA_TYPE_SIZE,
-				"get-data-func", get_filesize_for_test,
-				NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::type::is_file",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "display-name", _("All Files"),
+				  "data-type", GTH_TEST_DATA_TYPE_NONE,
+				  "get-data-func", is_file_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::type::is_image",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "display-name", _("Images"),
+				  "data-type", GTH_TEST_DATA_TYPE_NONE,
+				  "get-data-func", is_image_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::type::is_video",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "display-name", _("Video"),
+				  "data-type", GTH_TEST_DATA_TYPE_NONE,
+				  "get-data-func", is_video_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::type::is_audio",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "display-name", _("Audio"),
+				  "data-type", GTH_TEST_DATA_TYPE_NONE,
+				  "get-data-func", is_audio_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::type::is_media",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "display-name", _("Media"),
+				  "data-type", GTH_TEST_DATA_TYPE_NONE,
+				  "get-data-func", is_media_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::type::is_text",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "display-name", _("Text Files"),
+				  "data-type", GTH_TEST_DATA_TYPE_NONE,
+				  "get-data-func", is_text_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::name",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "attributes", "gth::file::display-name",
+				  "display-name", _("Filename"),
+				  "data-type", GTH_TEST_DATA_TYPE_STRING,
+				  "get-data-func", get_filename_for_test,
+				  NULL);
+	gth_main_register_object (GTH_TYPE_TEST,
+				  "file::size",
+				  GTH_TYPE_TEST_SIMPLE,
+				  "attributes", "gth::file::size",
+				  "display-name", _("Size"),
+				  "data-type", GTH_TEST_DATA_TYPE_SIZE,
+				  "get-data-func", get_filesize_for_test,
+				  NULL);
 }
diff --git a/gthumb/gth-main.c b/gthumb/gth-main.c
index b177e17..7218389 100644
--- a/gthumb/gth-main.c
+++ b/gthumb/gth-main.c
@@ -77,7 +77,8 @@ gth_type_spec_create_object (GthTypeSpec *spec,
 	GObject *object;
 
 	object = g_object_newv (spec->object_type, spec->n_params, spec->params);
-	g_object_set (object, "id", object_id, NULL);
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (object), "id"))
+		g_object_set (object, "id", object_id, NULL);
 
 	return object;
 }
@@ -94,11 +95,11 @@ struct _GthMainPrivate
 	GPtrArray           *metadata_info;
 	gboolean             metadata_info_sorted;
 	GHashTable          *sort_types;
-	GHashTable          *tests;
 	GHashTable          *loaders;
 	GList               *viewer_pages;
 	GHashTable          *types;
-	GHashTable          *objects;
+	GHashTable          *classes;
+	GHashTable          *objects_order;
 	GBookmarkFile       *bookmarks;
 	GthFilterFile       *filters;
 	GthTagsFile         *tags;
@@ -126,12 +127,14 @@ gth_main_finalize (GObject *object)
 
 		if (gth_main->priv->sort_types != NULL)
 			g_hash_table_unref (gth_main->priv->sort_types);
-		if (gth_main->priv->tests != NULL)
-			g_hash_table_unref (gth_main->priv->tests);
 		if (gth_main->priv->loaders != NULL)
 			g_hash_table_unref (gth_main->priv->loaders);
 		if (gth_main->priv->types != NULL)
 			g_hash_table_unref (gth_main->priv->types);
+		if (gth_main->priv->classes != NULL)
+			g_hash_table_unref (gth_main->priv->classes);
+		if (gth_main->priv->objects_order != NULL)
+			g_hash_table_unref (gth_main->priv->objects_order);
 
 		if (gth_main->priv->bookmarks != NULL)
 			g_bookmark_file_free (gth_main->priv->bookmarks);
@@ -168,10 +171,6 @@ gth_main_init (GthMain *main)
 							g_str_equal,
 							NULL,
 							NULL);
-	main->priv->tests = g_hash_table_new_full (g_str_hash,
-						   (GEqualFunc) g_content_type_equals,
-						   NULL,
-						   (GDestroyNotify) gth_type_spec_free);
 	main->priv->loaders = g_hash_table_new (g_str_hash, g_str_equal);
 	main->priv->metadata_category = g_ptr_array_new ();
 	main->priv->metadata_info = g_ptr_array_new ();
@@ -664,90 +663,6 @@ _gth_main_create_type_spec (GType       object_type,
 
 
 void
-gth_main_register_test (const char *object_id,
-			GType       object_type,
-			const char *first_property_name,
-			...)
-{
-	va_list      var_args;
-	GthTypeSpec *spec;
-
-	va_start (var_args, first_property_name);
-	spec = _gth_main_create_type_spec (object_type, first_property_name, var_args);
-	va_end (var_args);
-
-	g_hash_table_insert (Main->priv->tests, (gpointer) object_id, spec);
-}
-
-
-GthTest *
-gth_main_get_test (const char *object_id)
-{
-	GthTypeSpec *spec = NULL;
-
-	if (object_id == NULL)
-		return NULL;
-
-	spec = g_hash_table_lookup (Main->priv->tests, object_id);
-	if (spec == NULL)
-		return NULL;
-
-	return (GthTest *) gth_type_spec_create_object (spec, object_id);
-}
-
-
-static void
-collect_objects (gpointer key,
-		 gpointer value,
-		 gpointer user_data)
-{
-	GList       **objects = user_data;
-	GthTypeSpec  *spec = value;
-
-	*objects = g_list_prepend (*objects, gth_type_spec_create_object (spec, key));
-}
-
-
-G_GNUC_UNUSED
-static GList *
-_gth_main_get_all_objects (GHashTable *table)
-{
-	GList *objects = NULL;
-
-	g_hash_table_foreach (table, collect_objects, &objects);
-	return g_list_reverse (objects);
-}
-
-
-static void
-collect_names (gpointer key,
-	       gpointer value,
-	       gpointer user_data)
-{
-	GList **objects = user_data;
-
-	*objects = g_list_prepend (*objects, g_strdup (key));
-}
-
-
-static GList *
-_gth_main_get_all_object_names (GHashTable *table)
-{
-	GList *objects = NULL;
-
-	g_hash_table_foreach (table, collect_names, &objects);
-	return g_list_reverse (objects);
-}
-
-
-GList *
-gth_main_get_all_tests (void)
-{
-	return _gth_main_get_all_object_names (Main->priv->tests);
-}
-
-
-void
 gth_main_register_file_loader (FileLoader  loader,
 			       const char *first_mime_type,
 			       ...)
@@ -782,7 +697,7 @@ gth_main_get_general_filter (void)
 	GthTest *filter;
 
 	filter_name = eel_gconf_get_string (PREF_GENERAL_FILTER, DEFAULT_GENERAL_FILTER);
-	filter =  gth_main_get_test (filter_name);
+	filter =  gth_main_get_registered_object (GTH_TYPE_TEST, filter_name);
 	g_free (filter_name);
 
 	return filter;
@@ -908,39 +823,131 @@ gth_main_get_type_set (const char *set_name)
 
 
 static void
-_g_destroy_object_array (GPtrArray *array)
+g_ptr_array_destroy (gpointer array)
 {
-	g_ptr_array_foreach (array, (GFunc) g_object_unref, NULL);
-	g_ptr_array_free (array, TRUE);
+	g_ptr_array_free ((GPtrArray *) array, TRUE);
 }
 
 
 void
-gth_main_register_object (const char *set_name,
-			  GType       object_type)
-{
-	GPtrArray *set;
+gth_main_register_object (GType       superclass_type,
+			  const char *object_id,
+			  GType       object_type,
+			  const char *first_property_name,
+			  ...)
+{
+	const char  *superclass_name;
+	GHashTable  *object_hash;
+	GPtrArray   *object_order;
+	va_list      var_args;
+	GthTypeSpec *spec;
+	char        *id;
 
-	if (Main->priv->objects == NULL)
-		Main->priv->objects = g_hash_table_new_full (g_str_hash,
+	if (object_id == NULL)
+		object_id = g_type_name (object_type);
+
+	if (Main->priv->classes == NULL) {
+		Main->priv->classes = g_hash_table_new_full (g_str_hash,
 							     g_str_equal,
 							     (GDestroyNotify) g_free,
-							     (GDestroyNotify) _g_destroy_object_array);
+							     (GDestroyNotify) g_hash_table_destroy);
+		Main->priv->objects_order = g_hash_table_new_full (g_str_hash,
+							           g_str_equal,
+							           (GDestroyNotify) g_free,
+							           g_ptr_array_destroy);
+	}
 
-	set = g_hash_table_lookup (Main->priv->objects, set_name);
-	if (set == NULL) {
-		set = g_ptr_array_new ();
-		g_hash_table_insert (Main->priv->objects, g_strdup (set_name), set);
+	superclass_name = g_type_name (superclass_type);
+	object_hash = g_hash_table_lookup (Main->priv->classes, superclass_name);
+	object_order = g_hash_table_lookup (Main->priv->objects_order, superclass_name);
+
+	if (object_hash == NULL) {
+		object_hash = g_hash_table_new_full (g_str_hash,
+						     g_str_equal,
+						     (GDestroyNotify) g_free,
+						     (GDestroyNotify) gth_type_spec_free);
+		g_hash_table_insert (Main->priv->classes, g_strdup (superclass_name), object_hash);
+
+		object_order = g_ptr_array_new ();
+		g_hash_table_insert (Main->priv->objects_order, g_strdup (superclass_name), object_order);
 	}
 
-	g_ptr_array_add (set, g_object_new (object_type, NULL));
+	va_start (var_args, first_property_name);
+	spec = _gth_main_create_type_spec (object_type, first_property_name, var_args);
+	va_end (var_args);
+
+	id = g_strdup (object_id);
+	g_hash_table_insert (object_hash, id, spec);
+	g_ptr_array_add (object_order, id);
 }
 
 
-GPtrArray *
-gth_main_get_object_set (const char *set_name)
+GList *
+gth_main_get_registered_objects (GType superclass_type)
+{
+	GList      *objects = NULL;
+	GHashTable *object_hash;
+	GPtrArray  *object_order;
+	int         i;
+
+	object_hash = g_hash_table_lookup (Main->priv->classes, g_type_name (superclass_type));
+	if (object_hash == NULL)
+		return NULL;
+	object_order = g_hash_table_lookup (Main->priv->objects_order, g_type_name (superclass_type));
+
+	for (i = object_order->len - 1; i >= 0; i--) {
+		char        *object_id;
+		GthTypeSpec *spec;
+
+		object_id = g_ptr_array_index (object_order, i);
+		spec = g_hash_table_lookup (object_hash, object_id);
+		objects = g_list_prepend (objects, gth_type_spec_create_object (spec, object_id));
+	}
+
+	return objects;
+}
+
+
+GList *
+gth_main_get_registered_objects_id (GType superclass_type)
+{
+	GList      *objects = NULL;
+	GHashTable *object_hash;
+	GPtrArray  *object_order;
+	int         i;
+
+	object_hash = g_hash_table_lookup (Main->priv->classes, g_type_name (superclass_type));
+	if (object_hash == NULL)
+		return NULL;
+	object_order = g_hash_table_lookup (Main->priv->objects_order, g_type_name (superclass_type));
+
+	for (i = object_order->len - 1; i >= 0; i--) {
+		char *object_id;
+
+		object_id = g_ptr_array_index (object_order, i);
+		objects = g_list_prepend (objects, g_strdup (object_id));
+	}
+
+	return objects;
+}
+
+
+gpointer
+gth_main_get_registered_object (GType       superclass_type,
+				const char *object_id)
 {
-	return g_hash_table_lookup (Main->priv->objects, set_name);
+	GHashTable  *object_hash;
+	GthTypeSpec *spec;
+
+	object_hash = g_hash_table_lookup (Main->priv->classes, g_type_name (superclass_type));
+	if (object_hash == NULL)
+		return NULL;
+
+	spec = g_hash_table_lookup (object_hash, object_id);
+	if (spec == NULL)
+		return NULL;
+
+	return (gpointer) gth_type_spec_create_object (spec, object_id);
 }
 
 
@@ -1008,7 +1015,7 @@ gth_main_get_all_filters (void)
 	filter_file = gth_main_get_default_filter_file ();
 	filters = gth_filter_file_get_tests (filter_file);
 
-	registered_tests = gth_main_get_all_tests ();
+	registered_tests = gth_main_get_registered_objects_id (GTH_TYPE_TEST);
 	for (scan = registered_tests; scan; scan = scan->next) {
 		const char *registered_test_id = scan->data;
 		gboolean    test_present = FALSE;
@@ -1024,7 +1031,7 @@ gth_main_get_all_filters (void)
 		if (! test_present) {
 			GthTest *registered_test;
 
-			registered_test = gth_main_get_test (registered_test_id);
+			registered_test = gth_main_get_registered_object (GTH_TYPE_TEST, registered_test_id);
 			filters = g_list_append (filters, registered_test);
 			gth_filter_file_add (filter_file, registered_test);
 			changed = TRUE;
diff --git a/gthumb/gth-main.h b/gthumb/gth-main.h
index bf39fe4..79f21cb 100644
--- a/gthumb/gth-main.h
+++ b/gthumb/gth-main.h
@@ -86,21 +86,21 @@ GPtrArray *            gth_main_get_all_metadata_info         (void);
 void                   gth_main_register_sort_type            (GthFileDataSort      *sort_type);
 GthFileDataSort *      gth_main_get_sort_type                 (const char           *name);
 GList *                gth_main_get_all_sort_types            (void);
-void                   gth_main_register_test                 (const char           *id,
-						               GType                 type,
-						               const char           *first_property,
-						              ...);
-GthTest *              gth_main_get_test                      (const char           *id);
-GList *                gth_main_get_all_tests                 (void);
 void                   gth_main_register_file_loader          (FileLoader            loader,
 						               const char           *first_mime_type,
 						               ...);
 FileLoader             gth_main_get_file_loader               (const char           *mime_type);
 GthTest *              gth_main_get_general_filter            (void);
 GthTest *              gth_main_add_general_filter            (GthTest              *filter);
-void                   gth_main_register_object               (const char           *set_name,
-							       GType                 object_type);
-GPtrArray *            gth_main_get_object_set                (const char           *set_name);
+void		       gth_main_register_object               (GType                 superclass_type,
+							       const char           *object_id,
+							       GType                 object_type,
+							       const char           *first_property,
+							       ...);
+GList *                gth_main_get_registered_objects        (GType                 superclass_type);
+GList *                gth_main_get_registered_objects_id     (GType                 superclass_type);
+gpointer               gth_main_get_registered_object         (GType                 superclass_type,
+							       const char           *object_id);
 void                   gth_main_register_type                 (const char           *set_name,
 							       GType                 object_type);
 GArray *               gth_main_get_type_set                  (const char           *set_name);
diff --git a/gthumb/gth-test-chain.c b/gthumb/gth-test-chain.c
index cb4b5da..98e898a 100644
--- a/gthumb/gth-test-chain.c
+++ b/gthumb/gth-test-chain.c
@@ -159,7 +159,7 @@ gth_test_chain_real_load_from_element (DomDomizable *base,
 		if (g_strcmp0 (node->tag_name, "test") == 0) {
 			GthTest *test;
 
-			test = gth_main_get_test (dom_element_get_attribute (node, "id"));
+			test = gth_main_get_registered_object (GTH_TYPE_TEST, dom_element_get_attribute (node, "id"));
 			if (test == NULL)
 				continue;
 
diff --git a/gthumb/gth-test-selector.c b/gthumb/gth-test-selector.c
index d79d9d0..b9c201c 100644
--- a/gthumb/gth-test-selector.c
+++ b/gthumb/gth-test-selector.c
@@ -156,7 +156,7 @@ test_combo_box_changed_cb (GtkComboBox     *scope_combo_box,
 	if (test_name != NULL) {
 		GthTest *test;
 
-		test = gth_main_get_test (test_name);
+		test = gth_main_get_registered_object (GTH_TYPE_TEST, test_name);
 		gth_test_selector_set_test (self, test);
 		g_object_unref (test);
 	}
@@ -197,10 +197,10 @@ get_all_tests (void)
 	GList *scan;
 	GList *tests = NULL;
 
-	test_ids = gth_main_get_all_tests ();
+	test_ids = gth_main_get_registered_objects_id (GTH_TYPE_TEST);
 	for (scan = test_ids; scan; scan = scan->next) {
 		char *test_name = scan->data;
-		tests = g_list_prepend (tests, gth_main_get_test (test_name));
+		tests = g_list_prepend (tests, gth_main_get_registered_object (GTH_TYPE_TEST, test_name));
 	}
 
 	tests = g_list_sort (tests, compare_test_by_display_name);
@@ -375,7 +375,7 @@ gth_test_selector_set_test (GthTestSelector *self,
 	GthTest   *local_test = NULL;
 
 	if (test == NULL)
-		test = local_test = gth_main_get_test ("file::name");
+		test = local_test = gth_main_get_registered_object (GTH_TYPE_TEST, "file::name");
 
 	/* update the active test */
 



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