[gitg] Implemented spinner to show fetch progress
- From: Jesse van den Kieboom <jessevdk src gnome org>
- To: svn-commits-list gnome org
- Subject: [gitg] Implemented spinner to show fetch progress
- Date: Sun, 5 Jul 2009 01:13:50 +0000 (UTC)
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]