aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/libempathy-gtk/gossip-cell-renderer-expander.c
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/libempathy-gtk/gossip-cell-renderer-expander.c')
-rw-r--r--trunk/libempathy-gtk/gossip-cell-renderer-expander.c482
1 files changed, 482 insertions, 0 deletions
diff --git a/trunk/libempathy-gtk/gossip-cell-renderer-expander.c b/trunk/libempathy-gtk/gossip-cell-renderer-expander.c
new file mode 100644
index 000000000..e116ace7b
--- /dev/null
+++ b/trunk/libempathy-gtk/gossip-cell-renderer-expander.c
@@ -0,0 +1,482 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2006-2007 Imendio AB
+ *
+ * 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.
+ *
+ * Authors: Kristian Rietveld <kris@imendio.com>
+ */
+
+/* To do:
+ * - should probably cancel animation if model changes
+ * - need to handle case where node-in-animation is removed
+ * - it only handles a single animation at a time; but I guess users
+ * aren't fast enough to trigger two or more animations at once anyway :P
+ * (could guard for this by just cancelling the "old" animation, and
+ * start the new one).
+ */
+
+#include <gtk/gtktreeview.h>
+
+#include "gossip-cell-renderer-expander.h"
+
+#define GET_PRIV(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GOSSIP_TYPE_CELL_RENDERER_EXPANDER, GossipCellRendererExpanderPriv))
+
+static void gossip_cell_renderer_expander_init (GossipCellRendererExpander *expander);
+static void gossip_cell_renderer_expander_class_init (GossipCellRendererExpanderClass *klass);
+static void gossip_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec);
+static void gossip_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gossip_cell_renderer_expander_finalize (GObject *object);
+static void gossip_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height);
+static void gossip_cell_renderer_expander_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags);
+static gboolean gossip_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags);
+
+enum {
+ PROP_0,
+ PROP_EXPANDER_STYLE,
+ PROP_EXPANDER_SIZE,
+ PROP_ACTIVATABLE
+};
+
+typedef struct _GossipCellRendererExpanderPriv GossipCellRendererExpanderPriv;
+
+struct _GossipCellRendererExpanderPriv {
+ GtkExpanderStyle expander_style;
+ gint expander_size;
+
+ GtkTreeView *animation_view;
+ GtkTreeRowReference *animation_node;
+ GtkExpanderStyle animation_style;
+ guint animation_timeout;
+ GdkRectangle animation_area;
+
+ guint activatable : 1;
+ guint animation_expanding : 1;
+};
+
+G_DEFINE_TYPE (GossipCellRendererExpander, gossip_cell_renderer_expander, GTK_TYPE_CELL_RENDERER)
+
+static void
+gossip_cell_renderer_expander_init (GossipCellRendererExpander *expander)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (expander);
+
+ priv->expander_style = GTK_EXPANDER_COLLAPSED;
+ priv->expander_size = 12;
+ priv->activatable = TRUE;
+ priv->animation_node = NULL;
+
+ GTK_CELL_RENDERER (expander)->xpad = 2;
+ GTK_CELL_RENDERER (expander)->ypad = 2;
+ GTK_CELL_RENDERER (expander)->mode = GTK_CELL_RENDERER_MODE_ACTIVATABLE;
+}
+
+static void
+gossip_cell_renderer_expander_class_init (GossipCellRendererExpanderClass *klass)
+{
+ GObjectClass *object_class;
+ GtkCellRendererClass *cell_class;
+
+ object_class = G_OBJECT_CLASS (klass);
+ cell_class = GTK_CELL_RENDERER_CLASS (klass);
+
+ object_class->finalize = gossip_cell_renderer_expander_finalize;
+
+ object_class->get_property = gossip_cell_renderer_expander_get_property;
+ object_class->set_property = gossip_cell_renderer_expander_set_property;
+
+ cell_class->get_size = gossip_cell_renderer_expander_get_size;
+ cell_class->render = gossip_cell_renderer_expander_render;
+ cell_class->activate = gossip_cell_renderer_expander_activate;
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_STYLE,
+ g_param_spec_enum ("expander-style",
+ "Expander Style",
+ "Style to use when painting the expander",
+ GTK_TYPE_EXPANDER_STYLE,
+ GTK_EXPANDER_COLLAPSED,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_EXPANDER_SIZE,
+ g_param_spec_int ("expander-size",
+ "Expander Size",
+ "The size of the expander",
+ 0,
+ G_MAXINT,
+ 12,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class,
+ PROP_ACTIVATABLE,
+ g_param_spec_boolean ("activatable",
+ "Activatable",
+ "The expander can be activated",
+ TRUE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (object_class, sizeof (GossipCellRendererExpanderPriv));
+}
+
+static void
+gossip_cell_renderer_expander_get_property (GObject *object,
+ guint param_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+ priv = GET_PRIV (expander);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ g_value_set_enum (value, priv->expander_style);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ g_value_set_int (value, priv->expander_size);
+ break;
+
+ case PROP_ACTIVATABLE:
+ g_value_set_boolean (value, priv->activatable);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gossip_cell_renderer_expander_set_property (GObject *object,
+ guint param_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (object);
+ priv = GET_PRIV (expander);
+
+ switch (param_id) {
+ case PROP_EXPANDER_STYLE:
+ priv->expander_style = g_value_get_enum (value);
+ break;
+
+ case PROP_EXPANDER_SIZE:
+ priv->expander_size = g_value_get_int (value);
+ break;
+
+ case PROP_ACTIVATABLE:
+ priv->activatable = g_value_get_boolean (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
+ break;
+ }
+}
+
+static void
+gossip_cell_renderer_expander_finalize (GObject *object)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (object);
+
+ if (priv->animation_timeout) {
+ g_source_remove (priv->animation_timeout);
+ priv->animation_timeout = 0;
+ }
+
+ if (priv->animation_node) {
+ gtk_tree_row_reference_free (priv->animation_node);
+ }
+
+ (* G_OBJECT_CLASS (gossip_cell_renderer_expander_parent_class)->finalize) (object);
+}
+
+GtkCellRenderer *
+gossip_cell_renderer_expander_new (void)
+{
+ return g_object_new (GOSSIP_TYPE_CELL_RENDERER_EXPANDER, NULL);
+}
+
+static void
+gossip_cell_renderer_expander_get_size (GtkCellRenderer *cell,
+ GtkWidget *widget,
+ GdkRectangle *cell_area,
+ gint *x_offset,
+ gint *y_offset,
+ gint *width,
+ gint *height)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+
+ expander = (GossipCellRendererExpander*) cell;
+ priv = GET_PRIV (expander);
+
+ if (cell_area) {
+ if (x_offset) {
+ *x_offset = cell->xalign * (cell_area->width - (priv->expander_size + (2 * cell->xpad)));
+ *x_offset = MAX (*x_offset, 0);
+ }
+
+ if (y_offset) {
+ *y_offset = cell->yalign * (cell_area->height - (priv->expander_size + (2 * cell->ypad)));
+ *y_offset = MAX (*y_offset, 0);
+ }
+ } else {
+ if (x_offset)
+ *x_offset = 0;
+
+ if (y_offset)
+ *y_offset = 0;
+ }
+
+ if (width)
+ *width = cell->xpad * 2 + priv->expander_size;
+
+ if (height)
+ *height = cell->ypad * 2 + priv->expander_size;
+}
+
+static void
+gossip_cell_renderer_expander_render (GtkCellRenderer *cell,
+ GdkWindow *window,
+ GtkWidget *widget,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GdkRectangle *expose_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+ GtkExpanderStyle expander_style;
+ gint x_offset, y_offset;
+
+ expander = (GossipCellRendererExpander*) cell;
+ priv = GET_PRIV (expander);
+
+ if (priv->animation_node) {
+ GtkTreePath *path;
+ GdkRectangle rect;
+
+ /* Not sure if I like this ... */
+ path = gtk_tree_row_reference_get_path (priv->animation_node);
+ gtk_tree_view_get_background_area (priv->animation_view, path,
+ NULL, &rect);
+ gtk_tree_path_free (path);
+
+ if (background_area->y == rect.y)
+ expander_style = priv->animation_style;
+ else
+ expander_style = priv->expander_style;
+ } else
+ expander_style = priv->expander_style;
+
+ gossip_cell_renderer_expander_get_size (cell, widget, cell_area,
+ &x_offset, &y_offset,
+ NULL, NULL);
+
+ gtk_paint_expander (widget->style,
+ window,
+ GTK_STATE_NORMAL,
+ expose_area,
+ widget,
+ "treeview",
+ cell_area->x + x_offset + cell->xpad + priv->expander_size / 2,
+ cell_area->y + y_offset + cell->ypad + priv->expander_size / 2,
+ expander_style);
+}
+
+static void
+invalidate_node (GtkTreeView *tree_view,
+ GtkTreePath *path)
+{
+ GdkWindow *bin_window;
+ GdkRectangle rect;
+
+ bin_window = gtk_tree_view_get_bin_window (tree_view);
+
+ gtk_tree_view_get_background_area (tree_view, path, NULL, &rect);
+
+ rect.x = 0;
+ rect.width = GTK_WIDGET (tree_view)->allocation.width;
+
+ gdk_window_invalidate_rect (bin_window, &rect, TRUE);
+}
+
+static gboolean
+do_animation (GossipCellRendererExpander *expander)
+{
+ GossipCellRendererExpanderPriv *priv;
+ GtkTreePath *path;
+ gboolean done = FALSE;
+
+ priv = GET_PRIV (expander);
+
+ if (priv->animation_expanding) {
+ if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED)
+ priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+ else if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED) {
+ priv->animation_style = GTK_EXPANDER_EXPANDED;
+ done = TRUE;
+ }
+ } else {
+ if (priv->animation_style == GTK_EXPANDER_SEMI_EXPANDED)
+ priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+ else if (priv->animation_style == GTK_EXPANDER_SEMI_COLLAPSED) {
+ priv->animation_style = GTK_EXPANDER_COLLAPSED;
+ done = TRUE;
+ }
+ }
+
+ path = gtk_tree_row_reference_get_path (priv->animation_node);
+ invalidate_node (priv->animation_view, path);
+ gtk_tree_path_free (path);
+
+ if (done) {
+ gtk_tree_row_reference_free (priv->animation_node);
+ priv->animation_node = NULL;
+ priv->animation_timeout = 0;
+ }
+
+ return !done;
+}
+
+static gboolean
+animation_timeout (gpointer data)
+{
+ gboolean retval;
+
+ GDK_THREADS_ENTER ();
+
+ retval = do_animation (data);
+
+ GDK_THREADS_LEAVE ();
+
+ return retval;
+}
+
+static void
+gossip_cell_renderer_expander_start_animation (GossipCellRendererExpander *expander,
+ GtkTreeView *tree_view,
+ GtkTreePath *path,
+ gboolean expanding,
+ GdkRectangle *background_area)
+{
+ GossipCellRendererExpanderPriv *priv;
+
+ priv = GET_PRIV (expander);
+
+ if (expanding) {
+ priv->animation_style = GTK_EXPANDER_SEMI_COLLAPSED;
+ } else {
+ priv->animation_style = GTK_EXPANDER_SEMI_EXPANDED;
+ }
+
+ invalidate_node (tree_view, path);
+
+ priv->animation_expanding = expanding;
+ priv->animation_view = tree_view;
+ priv->animation_node = gtk_tree_row_reference_new (gtk_tree_view_get_model (tree_view), path);
+ priv->animation_timeout = g_timeout_add (50, animation_timeout, expander);
+}
+
+static gboolean
+gossip_cell_renderer_expander_activate (GtkCellRenderer *cell,
+ GdkEvent *event,
+ GtkWidget *widget,
+ const gchar *path_string,
+ GdkRectangle *background_area,
+ GdkRectangle *cell_area,
+ GtkCellRendererState flags)
+{
+ GossipCellRendererExpander *expander;
+ GossipCellRendererExpanderPriv *priv;
+ GtkTreePath *path;
+ gboolean animate;
+ gboolean expanding;
+
+ expander = GOSSIP_CELL_RENDERER_EXPANDER (cell);
+ priv = GET_PRIV (cell);
+
+ if (!GTK_IS_TREE_VIEW (widget) || !priv->activatable)
+ return FALSE;
+
+ path = gtk_tree_path_new_from_string (path_string);
+
+ if (gtk_tree_path_get_depth (path) > 1) {
+ gtk_tree_path_free (path);
+ return TRUE;
+ }
+
+ g_object_get (gtk_widget_get_settings (GTK_WIDGET (widget)),
+ "gtk-enable-animations", &animate,
+ NULL);
+
+ if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (widget), path)) {
+ gtk_tree_view_collapse_row (GTK_TREE_VIEW (widget), path);
+ expanding = FALSE;
+ } else {
+ gtk_tree_view_expand_row (GTK_TREE_VIEW (widget), path, FALSE);
+ expanding = TRUE;
+ }
+
+ if (animate) {
+ gossip_cell_renderer_expander_start_animation (expander,
+ GTK_TREE_VIEW (widget),
+ path,
+ expanding,
+ background_area);
+ }
+
+ gtk_tree_path_free (path);
+
+ return TRUE;
+}