[gitg] Implemented spinner to show fetch progress



commit 2f7c20d35aad204a0ca48f8f4f789e9f69968981
Author: Jesse van den Kieboom <jessevdk gnome org>
Date:   Sat Jul 4 17:32:51 2009 +0200

    Implemented spinner to show fetch progress
    
    This should look a bit better than having the pulsating progress bar

 gitg/Makefile.am              |    2 +
 gitg/gitg-repository-dialog.c |  107 +++---
 gitg/gitg-repository.ui       |   12 +-
 gitg/gitg-spinner.c           |  794 +++++++++++++++++++++++++++++++++++++++++
 gitg/gitg-spinner.h           |   99 +++++
 5 files changed, 961 insertions(+), 53 deletions(-)
---
diff --git a/gitg/Makefile.am b/gitg/Makefile.am
index f890554..7b387a3 100644
--- a/gitg/Makefile.am
+++ b/gitg/Makefile.am
@@ -41,6 +41,7 @@ NOINST_H_FILES = 			\
 	gitg-revision-view.h		\
 	gitg-runner.h			\
 	gitg-settings.h			\
+	gitg-spinner.h			\
 	gitg-types.h			\
 	gitg-utils.h			\
 	gitg-window.h			\
@@ -75,6 +76,7 @@ gitg_SOURCES = 				\
 	gitg-revision-view.c		\
 	gitg-runner.c			\
 	gitg-settings.c			\
+	gitg-spinner.c			\
 	gitg-utils.c			\
 	gitg-window.c			\
 	sexy-icon-entry.c		\
diff --git a/gitg/gitg-repository-dialog.c b/gitg/gitg-repository-dialog.c
index e6db39b..5eaa78d 100644
--- a/gitg/gitg-repository-dialog.c
+++ b/gitg/gitg-repository-dialog.c
@@ -5,6 +5,7 @@
 #include "gitg-repository-dialog.h"
 #include "gitg-utils.h"
 #include "gitg-config.h"
+#include "gitg-spinner.h"
 
 #define GITG_REPOSITORY_DIALOG_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE((object), GITG_TYPE_REPOSITORY_DIALOG, GitgRepositoryDialogPrivate))
 
@@ -43,16 +44,15 @@ typedef struct
 	GitgRepositoryDialog *dialog;
 	GitgRunner *runner;
 	GtkTreeRowReference *reference;
-	
-	guint timeout_id;
+	GitgSpinner *spinner;
 } FetchInfo;
 
 static void
 fetch_cleanup (FetchInfo *info)
 {
-	if (info->timeout_id)
+	if (info->spinner)
 	{
-		g_source_remove (info->timeout_id);
+		g_object_unref (info->spinner);
 	}
 	
 	info->dialog->priv->fetchers = g_list_remove (info->dialog->priv->fetchers, info);
@@ -68,7 +68,7 @@ fetch_cleanup (FetchInfo *info)
 
 		gtk_list_store_set (info->dialog->priv->list_store_remotes,
 		                    &iter,
-		                    COLUMN_FETCH, G_MAXULONG,
+		                    COLUMN_FETCH, NULL,
 		                    -1);
 
 		gtk_tree_path_free (path);
@@ -147,15 +147,19 @@ update_fetch (GitgRepositoryDialog *dialog)
 	{
 		GtkTreePath *path = (GtkTreePath *)item->data;
 		GtkTreeIter iter;
-		gulong num;
+		GdkPixbuf *fetch;
 		
 		gtk_tree_model_get_iter (model, &iter, path);
-		gtk_tree_model_get (model, &iter, COLUMN_FETCH, &num, -1);
+		gtk_tree_model_get (model, &iter, COLUMN_FETCH, &fetch, -1);
 		
-		if (num == G_MAXULONG)
+		if (!fetch)
 		{
 			show_fetch = TRUE;
 		}
+		else
+		{
+			g_object_unref (fetch);
+		}
 	}
 	
 	if (!rows)
@@ -208,49 +212,38 @@ add_remote (GitgRepositoryDialog *dialog, gchar const *name, gchar const *url, G
 	                    iter ? iter : &it,
 	                    COLUMN_NAME, name,
 	                    COLUMN_URL, url,
-	                    COLUMN_FETCH, G_MAXULONG,
+	                    COLUMN_FETCH, NULL,
 	                    -1);
 }
 
-static gboolean
-fetch_timeout_cb (FetchInfo *info)
+static void
+on_spinner_frame (GitgSpinner *spinner, GdkPixbuf *pixbuf, FetchInfo *info)
 {
 	GtkTreeIter iter;
 	GtkTreePath *path = gtk_tree_row_reference_get_path (info->reference);
-	GtkTreeModel *model = GTK_TREE_MODEL (info->dialog->priv->list_store_remotes);
-	gulong num;
 	
-	gtk_tree_model_get_iter (model,
+	gtk_tree_model_get_iter (GTK_TREE_MODEL (info->dialog->priv->list_store_remotes),
 	                         &iter,
 	                         path);
 
-	gtk_tree_model_get (model, &iter, COLUMN_FETCH, &num, -1);
 	gtk_list_store_set (info->dialog->priv->list_store_remotes,
 	                    &iter,
-	                    COLUMN_FETCH, num + 1,
+	                    COLUMN_FETCH, pixbuf,
 	                    -1);
-	
-	return TRUE;
+
+	gtk_tree_path_free (path);
 }
 
 static void
 on_fetch_begin_loading (GitgRunner *runner, FetchInfo *info)
 {
-	GtkTreeIter iter;
-	GtkTreePath *path = gtk_tree_row_reference_get_path (info->reference);
+	info->spinner = gitg_spinner_new (GTK_ICON_SIZE_MENU);
+	gitg_spinner_set_screen (info->spinner, gtk_widget_get_screen (GTK_WIDGET (info->dialog)));
 	
-	gtk_tree_model_get_iter (GTK_TREE_MODEL (info->dialog->priv->list_store_remotes),
-	                         &iter,
-	                         path);
-
-	gtk_list_store_set (info->dialog->priv->list_store_remotes,
-	                    &iter,
-	                    COLUMN_FETCH, 0,
-	                    -1);
-
-	gtk_tree_path_free (path);
+	g_signal_connect (info->spinner, "frame", G_CALLBACK (on_spinner_frame), info);
+	gitg_spinner_start (info->spinner);
 	
-	info->timeout_id = g_timeout_add (100, (GSourceFunc)fetch_timeout_cb, info);
+	update_fetch (info->dialog);
 }
 
 static void
@@ -274,14 +267,13 @@ static void
 fetch_remote (GitgRepositoryDialog *dialog, GtkTreeIter *iter)
 {
 	GitgRunner *runner = gitg_runner_new (1000);
-	FetchInfo *info = g_slice_new (FetchInfo);
+	FetchInfo *info = g_slice_new0 (FetchInfo);
 	GtkTreeModel *model = GTK_TREE_MODEL (dialog->priv->list_store_remotes);
 	
 	GtkTreePath *path = gtk_tree_model_get_path (model, iter);
 	
 	info->dialog = dialog;
 	info->reference = gtk_tree_row_reference_new (model, path);
-	info->timeout_id = 0;
 	info->runner = runner;
 
 	gtk_tree_path_free (path); 
@@ -381,17 +373,27 @@ init_properties(GitgRepositoryDialog *dialog)
 }
 
 static void
-fetch_data_cb (GtkTreeViewColumn *column, GtkCellRendererProgress *cell, GtkTreeModel *model, GtkTreeIter *iter, GitgRepositoryDialog *dialog)
+fetch_data_cb (GtkTreeViewColumn    *column,
+               GtkCellRenderer      *cell,
+               GtkTreeModel         *model,
+               GtkTreeIter          *iter,
+               GitgRepositoryDialog *dialog)
 {
-	gulong num;
+	GdkPixbuf *fetch;
 	
-	gtk_tree_model_get (model, iter, COLUMN_FETCH, &num, -1);
+	gtk_tree_model_get (model, iter, COLUMN_FETCH, &fetch, -1);
 	
-	g_object_set (cell,
-	              "pulse", num,
-	              "visible", num != G_MAXULONG,
-	              "xalign", 1.0,
-	              NULL);
+	if (fetch)
+	{
+		g_object_set (cell, "pixbuf", fetch, NULL);
+		g_object_unref (fetch);
+	}
+	else
+	{
+		g_object_set (cell, 
+		              "stock-id", GTK_STOCK_NETWORK, 
+		              NULL);
+	}
 }
 
 static void
@@ -421,14 +423,12 @@ create_repository_dialog (GitgWindow *window)
 	repository_dialog->priv->button_remove_remote = GTK_BUTTON(gtk_builder_get_object(b, "button_remove_remote"));
 	repository_dialog->priv->button_fetch_remote = GTK_BUTTON(gtk_builder_get_object(b, "button_fetch_remote"));
 	repository_dialog->priv->image_fetch_remote = GTK_IMAGE(gtk_builder_get_object(b, "image_fetch_remote"));
-	
-	GtkCellRenderer *renderer = gtk_cell_renderer_progress_new ();
-	
-	GObject *column = gtk_builder_get_object (b, "tree_view_remotes_column_url");
-	gtk_tree_view_column_pack_end (GTK_TREE_VIEW_COLUMN (column), renderer, TRUE);
 
+	GObject *renderer = gtk_builder_get_object(b, "tree_view_remotes_renderer_icon");
+	GObject *column = gtk_builder_get_object(b, "tree_view_remotes_column_icon");
+	
 	gtk_tree_view_column_set_cell_data_func (GTK_TREE_VIEW_COLUMN (column),
-	                                         renderer,
+	                                         GTK_CELL_RENDERER (renderer),
 	                                         (GtkTreeCellDataFunc)fetch_data_cb,
 	                                         repository_dialog,
 	                                         NULL);
@@ -511,20 +511,25 @@ on_button_fetch_remote_clicked (GtkButton *button, GitgRepositoryDialog *dialog)
 	{
 		GtkTreePath *path = (GtkTreePath *)item->data;
 		GtkTreeIter iter;
-		gulong num;
+		GdkPixbuf *fetch;
 		
 		gtk_tree_model_get_iter (model, &iter, path);
-		gtk_tree_model_get (model, &iter, COLUMN_FETCH, &num, -1);
+		gtk_tree_model_get (model, &iter, COLUMN_FETCH, &fetch, -1);
 		
-		if (num == G_MAXULONG && dialog->priv->show_fetch)
+		if (!fetch && dialog->priv->show_fetch)
 		{
 			fetch_remote (dialog, &iter);
 		}
-		else if (num != G_MAXULONG && !dialog->priv->show_fetch)
+		else if (fetch && !dialog->priv->show_fetch)
 		{
 			fetch_remote_cancel (dialog, &iter);
 		}
 		
+		if (fetch)
+		{
+			g_object_unref (fetch);
+		}
+		
 		gtk_tree_path_free (path);
 	}
 	
diff --git a/gitg/gitg-repository.ui b/gitg/gitg-repository.ui
index bddb6e7..1e0bba3 100644
--- a/gitg/gitg-repository.ui
+++ b/gitg/gitg-repository.ui
@@ -9,8 +9,8 @@
       <column type="gchararray"/>
       <!-- column-name url -->
       <column type="gchararray"/>
-      <!-- column-name fetch -->
-      <column type="gulong"/>
+      <!-- column-name fetching -->
+      <column type="GdkPixbuf"/>
     </columns>
   </object>
   <object class="GitgRepositoryDialog" id="dialog_repository">
@@ -48,6 +48,14 @@
                         <property name="model">list_store_remotes</property>
                         <property name="rules_hint">True</property>
                         <child>
+                          <object class="GtkTreeViewColumn" id="tree_view_remotes_column_icon">
+                            <child>
+                              <object class="GtkCellRendererPixbuf" id="tree_view_remotes_renderer_icon">
+                              </object>
+                            </child>
+                          </object>
+                        </child>
+                        <child>
                           <object class="GtkTreeViewColumn" id="tree_view_remotes_column_name">
                             <property name="title">Name</property>
                             <child>
diff --git a/gitg/gitg-spinner.c b/gitg/gitg-spinner.c
new file mode 100644
index 0000000..a5f327b
--- /dev/null
+++ b/gitg/gitg-spinner.c
@@ -0,0 +1,794 @@
+/*
+ * gitg-spinner.c
+ * This file is part of gitg
+ *
+ * Copyright (C) 2009 - Jesse van den kieboom
+ * Copyright (C) 2005 - Paolo Maggi 
+ * Copyright (C) 2002-2004 Marco Pesenti Gritti
+ * Copyright (C) 2004 Christian Persch
+ * Copyright (C) 2000 - Eazel, 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.
+ */
+ 
+/*
+ * This widget was originally written by Andy Hertzfeld <andy eazel com> for
+ * Nautilus. It was then modified by Marco Pesenti Gritti and Christian Persch
+ * for Epiphany.
+ *
+ * Modified by the gitg Team, 2005. See the AUTHORS file for a 
+ * list of people on the gitg Team.  
+ * See the ChangeLog files for a list of changes. 
+ *
+ * Modified by the gitg team, 2009.
+ *
+ * $Id$
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "gitg-spinner.h"
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+/* Spinner cache implementation */
+
+#define GITG_TYPE_SPINNER_CACHE		(gitg_spinner_cache_get_type())
+#define GITG_SPINNER_CACHE(object)		(G_TYPE_CHECK_INSTANCE_CAST((object), GITG_TYPE_SPINNER_CACHE, GitgSpinnerCache))
+#define GITG_SPINNER_CACHE_CLASS(klass) 	(G_TYPE_CHECK_CLASS_CAST((klass), GITG_TYPE_SPINNER_CACHE, GitgSpinnerCacheClass))
+#define GITG_IS_SPINNER_CACHE(object)		(G_TYPE_CHECK_INSTANCE_TYPE((object), GITG_TYPE_SPINNER_CACHE))
+#define GITG_IS_SPINNER_CACHE_CLASS(klass)	(G_TYPE_CHECK_CLASS_TYPE((klass), GITG_TYPE_SPINNER_CACHE))
+#define GITG_SPINNER_CACHE_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), GITG_TYPE_SPINNER_CACHE, GitgSpinnerCacheClass))
+
+typedef struct _GitgSpinnerCache		GitgSpinnerCache;
+typedef struct _GitgSpinnerCacheClass		GitgSpinnerCacheClass;
+typedef struct _GitgSpinnerCachePrivate	GitgSpinnerCachePrivate;
+
+struct _GitgSpinnerCacheClass
+{
+	GObjectClass parent_class;
+};
+
+struct _GitgSpinnerCache
+{
+	GObject parent_object;
+
+	/*< private >*/
+	GitgSpinnerCachePrivate *priv;
+};
+
+#define GITG_SPINNER_CACHE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GITG_TYPE_SPINNER_CACHE, GitgSpinnerCachePrivate))
+
+struct _GitgSpinnerCachePrivate
+{
+	/* Hash table of GdkScreen -> GitgSpinnerCacheData */
+	GHashTable *hash;
+};
+
+typedef struct
+{
+	guint        ref_count;
+	GtkIconSize  size;
+	gint         width;
+	gint         height;
+	GdkPixbuf  **animation_pixbufs;
+	guint        n_animation_pixbufs;
+} GitgSpinnerImages;
+
+#define LAST_ICON_SIZE			GTK_ICON_SIZE_DIALOG + 1
+#define SPINNER_ICON_NAME		"process-working"
+#define SPINNER_FALLBACK_ICON_NAME	"gnome-spinner"
+#define GITG_SPINNER_IMAGES_INVALID	((GitgSpinnerImages *) 0x1)
+
+typedef struct
+{
+	GdkScreen          *screen;
+	GtkIconTheme       *icon_theme;
+	GitgSpinnerImages  *images[LAST_ICON_SIZE];
+} GitgSpinnerCacheData;
+
+static void gitg_spinner_cache_class_init	(GitgSpinnerCacheClass *klass);
+static void gitg_spinner_cache_init		(GitgSpinnerCache      *cache);
+
+static GObjectClass *gitg_spinner_cache_parent_class;
+
+static GType
+gitg_spinner_cache_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0))
+	{
+		const GTypeInfo our_info =
+		{
+			sizeof (GitgSpinnerCacheClass),
+			NULL,
+			NULL,
+			(GClassInitFunc) gitg_spinner_cache_class_init,
+			NULL,
+			NULL,
+			sizeof (GitgSpinnerCache),
+			0,
+			(GInstanceInitFunc) gitg_spinner_cache_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "GitgSpinnerCache",
+					       &our_info, 0);
+	}
+
+	return type;
+}
+
+static GitgSpinnerImages *
+gitg_spinner_images_ref (GitgSpinnerImages *images)
+{
+	g_return_val_if_fail (images != NULL, NULL);
+
+	images->ref_count++;
+
+	return images;
+}
+
+static void
+gitg_spinner_images_unref (GitgSpinnerImages *images)
+{
+	g_return_if_fail (images != NULL);
+
+	images->ref_count--;
+
+	if (images->ref_count == 0)
+	{
+		guint i;
+
+		/* LOG ("Freeing spinner images %p for size %d", images, images->size); */
+
+		for (i = 0; i < images->n_animation_pixbufs; ++i)
+		{
+			g_object_unref (images->animation_pixbufs[i]);
+		}
+		
+		g_free (images->animation_pixbufs);
+		g_free (images);
+	}
+}
+
+static void
+gitg_spinner_cache_data_unload (GitgSpinnerCacheData *data)
+{
+	GtkIconSize size;
+	GitgSpinnerImages *images;
+
+	g_return_if_fail (data != NULL);
+
+	/* LOG ("GitgSpinnerDataCache unload for screen %p", data->screen); */
+
+	for (size = GTK_ICON_SIZE_INVALID; size < LAST_ICON_SIZE; ++size)
+	{
+		images = data->images[size];
+		data->images[size] = NULL;
+
+		if (images != NULL && images != GITG_SPINNER_IMAGES_INVALID)
+		{
+			gitg_spinner_images_unref (images);
+		}
+	}
+}
+
+static GdkPixbuf *
+extract_frame (GdkPixbuf *grid_pixbuf,
+	       int x,
+	       int y,
+	       int size)
+{
+	GdkPixbuf *pixbuf;
+
+	if (x + size > gdk_pixbuf_get_width (grid_pixbuf) ||
+	    y + size > gdk_pixbuf_get_height (grid_pixbuf))
+	{
+		return NULL;
+	}
+
+	pixbuf = gdk_pixbuf_new_subpixbuf (grid_pixbuf,
+					   x, y,
+					   size, size);
+	g_return_val_if_fail (pixbuf != NULL, NULL);
+
+	return pixbuf;
+}
+
+static GdkPixbuf *
+scale_to_size (GdkPixbuf *pixbuf,
+	       int dw,
+	       int dh)
+{
+	GdkPixbuf *result;
+	int pw, ph;
+
+	g_return_val_if_fail (pixbuf != NULL, NULL);
+
+	pw = gdk_pixbuf_get_width (pixbuf);
+	ph = gdk_pixbuf_get_height (pixbuf);
+
+	if (pw != dw || ph != dh)
+	{
+		result = gdk_pixbuf_scale_simple (pixbuf, dw, dh,
+						  GDK_INTERP_BILINEAR);
+		g_object_unref (pixbuf);
+		return result;
+	}
+
+	return pixbuf;
+}
+
+static GitgSpinnerImages *
+gitg_spinner_images_load (GdkScreen    *screen,
+                          GtkIconTheme *icon_theme,
+                          GtkIconSize   icon_size)
+{
+	GitgSpinnerImages *images;
+	GdkPixbuf *icon_pixbuf, *pixbuf;
+	GtkIconInfo *icon_info = NULL;
+	int grid_width, grid_height, x, y, requested_size, size, isw, ish, n;
+	const char *icon;
+	GSList *list = NULL, *l;
+
+	/* LOG ("GitgSpinnerCacheData loading for screen %p at size %d", screen, icon_size); */
+
+	/* START_PROFILER ("loading spinner animation") */
+	
+	if (screen == NULL)
+		screen = gdk_screen_get_default ();
+
+	if (!gtk_icon_size_lookup_for_settings (gtk_settings_get_for_screen (screen),
+						icon_size, &isw, &ish))
+		goto loser;
+ 
+	requested_size = MAX (ish, isw);
+
+	/* Load the animation. The 'rest icon' is the 0th frame */
+	icon_info = gtk_icon_theme_lookup_icon (icon_theme,
+						SPINNER_ICON_NAME,
+						requested_size, 0);
+
+	if (icon_info == NULL)
+	{
+		g_warning ("Throbber animation not found");
+
+		/* If the icon naming spec compliant name wasn't found, try the old name */
+		icon_info = gtk_icon_theme_lookup_icon (icon_theme,
+							SPINNER_FALLBACK_ICON_NAME,
+						        requested_size, 0);
+		if (icon_info == NULL)
+		{
+			g_warning ("Throbber fallback animation not found either");
+			goto loser;
+	 	}
+	}
+
+	g_assert (icon_info != NULL);
+
+	size = gtk_icon_info_get_base_size (icon_info);
+	icon = gtk_icon_info_get_filename (icon_info);
+
+	if (icon == NULL)
+		goto loser;
+
+	icon_pixbuf = gdk_pixbuf_new_from_file (icon, NULL);
+	gtk_icon_info_free (icon_info);
+	icon_info = NULL;
+
+	if (icon_pixbuf == NULL)
+	{
+		g_warning ("Could not load the spinner file");
+		goto loser;
+	}
+
+	grid_width = gdk_pixbuf_get_width (icon_pixbuf);
+	grid_height = gdk_pixbuf_get_height (icon_pixbuf);
+
+	n = 0;
+	for (y = 0; y < grid_height; y += size)
+	{
+		for (x = 0; x < grid_width ; x += size)
+		{
+			pixbuf = extract_frame (icon_pixbuf, x, y, size);
+
+			if (pixbuf)
+			{
+				list = g_slist_prepend (list, pixbuf);
+				++n;
+			}
+			else
+			{
+				g_warning ("Cannot extract frame (%d, %d) from the grid\n", x, y);
+			}
+		}
+	}
+
+	g_object_unref (icon_pixbuf);
+
+	if (list == NULL)
+		goto loser;
+
+	/* g_assert (n > 0); */
+
+	if (size > requested_size)
+	{
+		for (l = list; l != NULL; l = l->next)
+		{
+			l->data = scale_to_size (l->data, isw, ish);
+		}
+ 	}
+
+	/* Now we've successfully got all the data */
+	images = g_new (GitgSpinnerImages, 1);
+	images->ref_count = 1;
+ 
+	images->size = icon_size;
+	images->width = images->height = requested_size;
+
+	images->n_animation_pixbufs = n;
+	images->animation_pixbufs = g_new (GdkPixbuf *, n);
+
+	for (l = list; l != NULL; l = l->next)
+	{
+		g_assert (l->data != NULL);
+		images->animation_pixbufs[--n] = l->data;
+	}
+	g_assert (n == 0);
+
+	g_slist_free (list);
+
+	/* STOP_PROFILER ("loading spinner animation") */
+	return images;
+ 
+loser:
+	if (icon_info)
+	{
+		gtk_icon_info_free (icon_info);
+ 	}
+
+	g_slist_foreach (list, (GFunc) g_object_unref, NULL);
+
+	/* STOP_PROFILER ("loading spinner animation") */
+
+	return NULL;
+}
+
+static GitgSpinnerCacheData *
+gitg_spinner_cache_data_new (GdkScreen *screen)
+{
+	GitgSpinnerCacheData *data;
+
+	data = g_new0 (GitgSpinnerCacheData, 1);
+
+	data->screen = screen;
+	data->icon_theme = gtk_icon_theme_get_for_screen (screen);
+
+	g_signal_connect_swapped (data->icon_theme,
+				  "changed",
+				  G_CALLBACK (gitg_spinner_cache_data_unload),
+				  data);
+
+	return data;
+}
+
+static void
+gitg_spinner_cache_data_free (GitgSpinnerCacheData *data)
+{
+	g_return_if_fail (data != NULL);
+	g_return_if_fail (data->icon_theme != NULL);
+
+	g_signal_handlers_disconnect_by_func (data->icon_theme,
+					      G_CALLBACK (gitg_spinner_cache_data_unload),
+					      data);
+
+	gitg_spinner_cache_data_unload (data);
+
+	g_free (data);
+}
+
+static GitgSpinnerImages *
+gitg_spinner_cache_get_images (GitgSpinnerCache *cache,
+                               GdkScreen        *screen,
+                               GtkIconSize       icon_size)
+{
+	GitgSpinnerCachePrivate *priv = cache->priv;
+	GitgSpinnerCacheData *data;
+	GitgSpinnerImages *images;
+
+	g_return_val_if_fail (icon_size >= 0 && icon_size < LAST_ICON_SIZE, NULL);
+
+	data = g_hash_table_lookup (priv->hash, screen);
+
+	if (data == NULL)
+	{
+		data = gitg_spinner_cache_data_new (screen);
+
+		/* FIXME: think about what happens when the screen's display is closed later on */
+		g_hash_table_insert (priv->hash, screen, data);
+	}
+
+	images = data->images[icon_size];
+
+	if (images == GITG_SPINNER_IMAGES_INVALID)
+	{
+		/* Load failed, but don't try endlessly again! */
+		return NULL;
+	}
+
+	if (images != NULL)
+	{
+		/* Return cached data */
+		return gitg_spinner_images_ref (images);
+	}
+
+	images = gitg_spinner_images_load (screen, data->icon_theme, icon_size);
+
+	if (images == NULL)
+ 	{
+		/* Mark as failed-to-load */
+		data->images[icon_size] = GITG_SPINNER_IMAGES_INVALID;
+ 
+		return NULL;
+ 	}
+
+	data->images[icon_size] = images;
+
+	return gitg_spinner_images_ref (images);
+}
+
+static void
+gitg_spinner_cache_init (GitgSpinnerCache *cache)
+{
+	GitgSpinnerCachePrivate *priv;
+
+	priv = cache->priv = GITG_SPINNER_CACHE_GET_PRIVATE (cache);
+
+	/* LOG ("GitgSpinnerCache initialising"); */
+
+	priv->hash = g_hash_table_new_full (g_direct_hash,
+					    g_direct_equal,
+					    NULL,
+					    (GDestroyNotify) gitg_spinner_cache_data_free);
+}
+
+static void
+gitg_spinner_cache_finalize (GObject *object)
+{
+	GitgSpinnerCache *cache = GITG_SPINNER_CACHE (object); 
+	GitgSpinnerCachePrivate *priv = cache->priv;
+
+	g_hash_table_destroy (priv->hash);
+
+	/* LOG ("GitgSpinnerCache finalised"); */
+
+	G_OBJECT_CLASS (gitg_spinner_cache_parent_class)->finalize (object);
+}
+
+static void
+gitg_spinner_cache_class_init (GitgSpinnerCacheClass *klass)
+{
+	GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+	gitg_spinner_cache_parent_class = g_type_class_peek_parent (klass);
+
+	object_class->finalize = gitg_spinner_cache_finalize;
+
+	g_type_class_add_private (object_class, sizeof (GitgSpinnerCachePrivate));
+}
+
+static GitgSpinnerCache *spinner_cache = NULL;
+
+static GitgSpinnerCache *
+gitg_spinner_cache_ref (void)
+{
+	if (spinner_cache == NULL)
+	{
+		GitgSpinnerCache **cache_ptr;
+
+		spinner_cache = g_object_new (GITG_TYPE_SPINNER_CACHE, NULL);
+		cache_ptr = &spinner_cache;
+		g_object_add_weak_pointer (G_OBJECT (spinner_cache),
+					   (gpointer *) cache_ptr);
+
+		return spinner_cache;
+	}
+
+	return g_object_ref (spinner_cache);
+}
+
+/* Spinner implementation */
+
+#define SPINNER_TIMEOUT 50 /* ms */
+
+#define GITG_SPINNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), GITG_TYPE_SPINNER, GitgSpinnerPrivate))
+
+struct _GitgSpinnerPrivate
+{
+	GdkScreen          *screen;
+	GitgSpinnerCache   *cache;
+	GtkIconSize         size;
+	GitgSpinnerImages  *images;
+	guint               current_image;
+	guint               timeout;
+	guint               timer_task;
+	guint               spinning : 1;
+	guint               need_load : 1;
+};
+
+enum
+{
+	FRAME,
+	NUM_SIGNALS
+};
+
+static guint spinner_signals[NUM_SIGNALS] = {0,};
+
+static void gitg_spinner_class_init	(GitgSpinnerClass *class);
+static void gitg_spinner_init		(GitgSpinner      *spinner);
+
+static GObjectClass *parent_class;
+
+GType
+gitg_spinner_get_type (void)
+{
+	static GType type = 0;
+
+	if (G_UNLIKELY (type == 0))
+	{
+		const GTypeInfo our_info =
+		{
+			sizeof (GitgSpinnerClass),
+			NULL, /* base_init */
+			NULL, /* base_finalize */
+			(GClassInitFunc) gitg_spinner_class_init,
+			NULL,
+			NULL, /* class_data */
+			sizeof (GitgSpinner),
+			0, /* n_preallocs */
+			(GInstanceInitFunc) gitg_spinner_init
+		};
+
+		type = g_type_register_static (G_TYPE_OBJECT,
+					       "GitgSpinner",
+					       &our_info, 0);
+	}
+
+	return type;
+}
+
+static gboolean
+gitg_spinner_load_images (GitgSpinner *spinner)
+{
+	GitgSpinnerPrivate *priv = spinner->priv;
+
+	if (priv->need_load)
+	{
+		priv->images = gitg_spinner_cache_get_images (priv->cache, priv->screen, priv->size);
+
+		priv->current_image = 0; /* 'rest' icon */
+		priv->need_load = FALSE;
+	}
+
+	return priv->images != NULL;
+}
+
+static void
+gitg_spinner_unload_images (GitgSpinner *spinner)
+{
+	GitgSpinnerPrivate *priv = spinner->priv;
+
+	if (priv->images != NULL)
+	{
+		gitg_spinner_images_unref (priv->images);
+		priv->images = NULL;
+	}
+
+	priv->current_image = 0;
+	priv->need_load = TRUE;
+}
+
+static void
+gitg_spinner_init (GitgSpinner *spinner)
+{
+	spinner->priv = GITG_SPINNER_GET_PRIVATE (spinner);
+
+	spinner->priv->cache = gitg_spinner_cache_ref ();
+	spinner->priv->size = GTK_ICON_SIZE_MENU;
+	spinner->priv->timeout = SPINNER_TIMEOUT;
+	spinner->priv->need_load = TRUE;
+}
+
+static gboolean
+bump_spinner_frame_cb (GitgSpinner *spinner)
+{
+	GitgSpinnerPrivate *priv = spinner->priv;
+
+	/* This can happen when we've unloaded the images on a theme
+	 * change, but haven't been in the queued size request yet.
+	 * Just skip this update.
+	 */
+	if (priv->images == NULL)
+	{
+		if (!gitg_spinner_load_images (spinner))
+		{
+			return FALSE;
+		}
+	}
+
+	priv->current_image++;
+
+	if (priv->current_image >= priv->images->n_animation_pixbufs)
+	{
+		/* the 0th frame is the 'rest' icon */
+		priv->current_image = MIN (1, priv->images->n_animation_pixbufs);
+	}
+
+	g_signal_emit (spinner, spinner_signals[FRAME], 0, priv->images->animation_pixbufs[priv->current_image]);
+
+	/* run again */
+	return TRUE;
+}
+
+/**
+ * gitg_spinner_start:
+ * @spinner: a #GitgSpinner
+ *
+ * Start the spinner animation.
+ **/
+void
+gitg_spinner_start (GitgSpinner *spinner)
+{
+	GitgSpinnerPrivate *priv = spinner->priv;
+
+	priv->spinning = TRUE;
+
+	if (priv->timer_task == 0 && gitg_spinner_load_images (spinner))
+	{
+		/* the 0th frame is the 'rest' icon */
+		priv->current_image = MIN (0, priv->images->n_animation_pixbufs);
+
+		priv->timer_task = g_timeout_add_full (G_PRIORITY_LOW,
+						       priv->timeout,
+						       (GSourceFunc) bump_spinner_frame_cb,
+						       spinner,
+						       NULL);
+
+		bump_spinner_frame_cb (spinner);
+	}
+}
+
+static void
+gitg_spinner_remove_update_callback (GitgSpinner *spinner)
+{
+	GitgSpinnerPrivate *priv = spinner->priv;
+
+	if (priv->timer_task != 0)
+	{
+		g_source_remove (priv->timer_task);
+		priv->timer_task = 0;
+	}
+}
+
+/**
+ * gitg_spinner_stop:
+ * @spinner: a #GitgSpinner
+ *
+ * Stop the spinner animation.
+ **/
+void
+gitg_spinner_stop (GitgSpinner *spinner)
+{
+	GitgSpinnerPrivate *priv = spinner->priv;
+
+	priv->spinning = FALSE;
+	priv->current_image = 0;
+
+	if (priv->timer_task != 0)
+	{
+		gitg_spinner_remove_update_callback (spinner);
+	}
+}
+
+void
+gitg_spinner_set_screen (GitgSpinner *spinner, GdkScreen *screen)
+{
+	g_return_if_fail (GITG_IS_SPINNER (spinner));
+	g_return_if_fail (GDK_IS_SCREEN (screen));
+
+	if (spinner->priv->screen != screen)
+	{
+		gitg_spinner_unload_images (spinner);
+		
+		if (spinner->priv->screen)
+		{
+			g_object_unref (spinner->priv->screen);
+		}
+		
+		spinner->priv->screen = g_object_ref (screen);
+	}
+}
+
+static void
+gitg_spinner_dispose (GObject *object)
+{
+	GitgSpinner *spinner = GITG_SPINNER (object);
+
+	G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gitg_spinner_finalize (GObject *object)
+{
+	GitgSpinner *spinner = GITG_SPINNER (object);
+
+	gitg_spinner_remove_update_callback (spinner);
+	gitg_spinner_unload_images (spinner);
+
+	g_object_unref (spinner->priv->cache);
+
+	G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gitg_spinner_class_init (GitgSpinnerClass *class)
+{
+	GObjectClass *object_class =  G_OBJECT_CLASS (class);
+
+	parent_class = g_type_class_peek_parent (class);
+
+	object_class->dispose = gitg_spinner_dispose;
+	object_class->finalize = gitg_spinner_finalize;
+	
+	spinner_signals[FRAME] =
+   		g_signal_new ("frame",
+			      G_OBJECT_CLASS_TYPE (object_class),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (GitgSpinnerClass, frame),
+			      NULL, NULL,
+			      g_cclosure_marshal_VOID__OBJECT,
+			      G_TYPE_NONE,
+			      1,
+			      GDK_TYPE_PIXBUF);
+
+	g_type_class_add_private (object_class, sizeof (GitgSpinnerPrivate));
+}
+
+GitgSpinner *
+gitg_spinner_new (GtkIconSize size)
+{
+	GitgSpinner *spinner = g_object_new (GITG_TYPE_SPINNER, NULL);
+	
+	spinner->priv->size = size;
+	return spinner;
+}
+
+GdkPixbuf *
+gitg_spinner_get_pixbuf (GitgSpinner *spinner)
+{
+	g_return_val_if_fail (GITG_IS_SPINNER (spinner), NULL);
+	
+	if (spinner->priv->timer_task == 0)
+	{
+		return NULL;
+	}
+	
+	return g_object_ref (spinner->priv->images->animation_pixbufs[spinner->priv->current_image]);
+}
diff --git a/gitg/gitg-spinner.h b/gitg/gitg-spinner.h
new file mode 100644
index 0000000..b7a5b98
--- /dev/null
+++ b/gitg/gitg-spinner.h
@@ -0,0 +1,99 @@
+/*
+ * gitg-spinner.h
+ * This file is part of gitg
+ *
+ * Copyright (C) 2009 - Jesse van den Kieboom
+ * Copyright (C) 2005 - Paolo Maggi 
+ * Copyright (C) 2000 - Eazel, 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.
+ */
+ 
+/*
+ * This widget was originally written by Andy Hertzfeld <andy eazel com> for
+ * Nautilus.
+ *
+ * Modified by the gitg Team, 2005. See the AUTHORS file for a 
+ * list of people on the gitg Team.  
+ * See the ChangeLog files for a list of changes. 
+ *
+ * Modified by the gitg Team, 2009
+ *
+ * $Id$
+ */
+
+#ifndef __GITG_SPINNER_H__
+#define __GITG_SPINNER_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/*
+ * Type checking and casting macros
+ */
+#define GITG_TYPE_SPINNER		(gitg_spinner_get_type ())
+#define GITG_SPINNER(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GITG_TYPE_SPINNER, GitgSpinner))
+#define GITG_SPINNER_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GITG_TYPE_SPINNER, GitgSpinnerClass))
+#define GITG_IS_SPINNER(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GITG_TYPE_SPINNER))
+#define GITG_IS_SPINNER_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GITG_TYPE_SPINNER))
+#define GITG_SPINNER_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GITG_TYPE_SPINNER, GitgSpinnerClass))
+
+
+/* Private structure type */
+typedef struct _GitgSpinnerPrivate	GitgSpinnerPrivate;
+
+/*
+ * Main object structure
+ */
+typedef struct _GitgSpinner		GitgSpinner;
+
+struct _GitgSpinner
+{
+	GObject parent;
+
+	/*< private >*/
+	GitgSpinnerPrivate *priv;
+};
+
+/*
+ * Class definition
+ */
+typedef struct _GitgSpinnerClass	GitgSpinnerClass;
+
+struct _GitgSpinnerClass
+{
+	GObjectClass parent_class;
+	
+	void (*frame)(GitgSpinner *spinner, GdkPixbuf *pixbuf);
+};
+
+/*
+ * Public methods
+ */
+GType			gitg_spinner_get_type	(void) G_GNUC_CONST;
+
+GitgSpinner	   *gitg_spinner_new		(GtkIconSize  size);
+void 			gitg_spinner_set_screen (GitgSpinner *spinner, 
+										 GdkScreen   *screen);
+void			gitg_spinner_start		(GitgSpinner  *spinner);
+void			gitg_spinner_stop		(GitgSpinner  *spinner);
+
+GdkPixbuf      *gitg_spinner_get_pixbuf (GitgSpinner *spinner);
+
+G_END_DECLS
+
+#endif /* __GITG_SPINNER_H__ */



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