Remove silly tag dereferences suffix
[giggle.git] / src / giggle-graph-renderer.c
blobfb6089de8c2491a782d1b6a9cf0397a09be8cf40
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * Copyright (C) 2007 Imendio AB
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License as
7 * published by the Free Software Foundation; either version 2 of the
8 * License, or (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * General Public License for more details.
15 * You should have received a copy of the GNU General Public
16 * License along with this program; if not, write to the
17 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 * Boston, MA 02111-1307, USA.
21 #include <config.h>
22 #include <math.h>
23 #include <gtk/gtk.h>
25 #include "giggle-graph-renderer.h"
26 #include "giggle-revision.h"
28 #define GET_PRIV(object) (G_TYPE_INSTANCE_GET_PRIVATE ((object), GIGGLE_TYPE_GRAPH_RENDERER, GiggleGraphRendererPrivate))
30 /* included padding */
31 #define PATH_SPACE(font_size) (font_size * 2)
32 #define DOT_RADIUS(font_size) (font_size / 2)
33 #define LINE_WIDTH(font_size) ((font_size / 6) << 1) /* we want the closest even number <= size/3 */
34 #define NEXT_COLOR(n_color) ((n_color + 1) % G_N_ELEMENTS (colors))
36 typedef struct GiggleGraphRendererPrivate GiggleGraphRendererPrivate;
38 struct GiggleGraphRendererPrivate {
39 gint n_paths;
40 GHashTable *paths_info;
41 GiggleRevision *revision;
44 typedef struct GiggleGraphRendererPathState GiggleGraphRendererPathState;
46 struct GiggleGraphRendererPathState {
47 GdkColor *upper_color;
48 GdkColor *lower_color;
51 enum {
52 PROP_0,
53 PROP_REVISION,
56 static GdkColor colors[] = {
57 /* light palette */
58 { 0x0, 0xfc00, 0xe900, 0x4f00 }, /* butter */
59 { 0x0, 0xfc00, 0xaf00, 0x3e00 }, /* orange */
60 { 0x0, 0xe900, 0xb900, 0x6e00 }, /* chocolate */
61 { 0x0, 0x8a00, 0xe200, 0x3400 }, /* chameleon */
62 { 0x0, 0x7200, 0x9f00, 0xcf00 }, /* sky blue */
63 { 0x0, 0xad00, 0x7f00, 0xa800 }, /* plum */
64 { 0x0, 0xef00, 0x2900, 0x2900 }, /* scarlet red */
65 #if 0
66 { 0x0, 0xee00, 0xee00, 0xec00 }, /* aluminium */
67 #endif
68 { 0x0, 0x8800, 0x8a00, 0x8500 }, /* no name grey */
69 /* medium palette */
70 { 0x0, 0xed00, 0xd400, 0x0000 }, /* butter */
71 { 0x0, 0xf500, 0x7900, 0x0000 }, /* orange */
72 { 0x0, 0xc100, 0x7d00, 0x1100 }, /* chocolate */
73 { 0x0, 0x7300, 0xd200, 0x1600 }, /* chameleon */
74 { 0x0, 0x3400, 0x6500, 0xa400 }, /* sky blue */
75 { 0x0, 0x7500, 0x5000, 0x7b00 }, /* plum */
76 { 0x0, 0xcc00, 0x0000, 0x0000 }, /* scarlet red */
77 #if 0
78 { 0x0, 0xd300, 0xd700, 0xcf00 }, /* aluminium */
79 #endif
80 { 0x0, 0x5500, 0x5700, 0x5300 }, /* no name grey */
81 /* dark palette */
82 { 0x0, 0xc400, 0xa000, 0x0000 }, /* butter */
83 { 0x0, 0xce00, 0x5c00, 0x0000 }, /* orange */
84 { 0x0, 0x8f00, 0x5900, 0x0200 }, /* chocolate */
85 { 0x0, 0x4e00, 0x9a00, 0x0600 }, /* chameleon */
86 { 0x0, 0x2000, 0x4a00, 0x8700 }, /* sky blue */
87 { 0x0, 0x5c00, 0x3500, 0x6600 }, /* plum */
88 { 0x0, 0xa400, 0x0000, 0x0000 }, /* scarlet red */
89 #if 0
90 { 0x0, 0xba00, 0xbd00, 0xb600 }, /* aluminium */
91 #endif
92 { 0x0, 0x2e00, 0x3400, 0x3600 }, /* no name grey */
95 static GQuark revision_paths_state_quark;
97 static void giggle_graph_renderer_finalize (GObject *object);
98 static void giggle_graph_renderer_get_property (GObject *object,
99 guint param_id,
100 GValue *value,
101 GParamSpec *pspec);
102 static void giggle_graph_renderer_set_property (GObject *object,
103 guint param_id,
104 const GValue *value,
105 GParamSpec *pspec);
106 static void giggle_graph_renderer_get_size (GtkCellRenderer *cell,
107 GtkWidget *widget,
108 GdkRectangle *cell_area,
109 gint *x_offset,
110 gint *y_offset,
111 gint *width,
112 gint *height);
113 static void giggle_graph_renderer_render (GtkCellRenderer *cell,
114 GdkWindow *window,
115 GtkWidget *widget,
116 GdkRectangle *background_area,
117 GdkRectangle *cell_area,
118 GdkRectangle *expose_area,
119 guint flags);
121 G_DEFINE_TYPE (GiggleGraphRenderer, giggle_graph_renderer, GTK_TYPE_CELL_RENDERER)
123 static void
124 giggle_graph_renderer_class_init (GiggleGraphRendererClass *class)
126 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
127 GObjectClass *object_class = G_OBJECT_CLASS (class);
129 cell_class->get_size = giggle_graph_renderer_get_size;
130 cell_class->render = giggle_graph_renderer_render;
132 object_class->finalize = giggle_graph_renderer_finalize;
133 object_class->set_property = giggle_graph_renderer_set_property;
134 object_class->get_property = giggle_graph_renderer_get_property;
136 g_object_class_install_property (
137 object_class,
138 PROP_REVISION,
139 g_param_spec_object ("revision",
140 "revision",
141 "revision",
142 GIGGLE_TYPE_REVISION,
143 G_PARAM_READWRITE));
145 g_type_class_add_private (object_class,
146 sizeof (GiggleGraphRendererPrivate));
148 revision_paths_state_quark = g_quark_from_static_string ("giggle-revision-paths-state");
151 static void
152 giggle_graph_renderer_init (GiggleGraphRenderer *instance)
154 instance->_priv = GET_PRIV (instance);
157 static void
158 giggle_graph_renderer_finalize (GObject *object)
160 GiggleGraphRendererPrivate *priv;
162 priv = GET_PRIV (object);
164 if (priv->paths_info) {
165 g_hash_table_destroy (priv->paths_info);
168 G_OBJECT_CLASS (giggle_graph_renderer_parent_class)->finalize (object);
171 static void
172 giggle_graph_renderer_get_property (GObject *object,
173 guint param_id,
174 GValue *value,
175 GParamSpec *pspec)
177 GiggleGraphRendererPrivate *priv = GIGGLE_GRAPH_RENDERER (object)->_priv;
179 switch (param_id) {
180 case PROP_REVISION:
181 g_value_set_object (value, priv->revision);
182 break;
183 default:
184 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
188 static void
189 giggle_graph_renderer_set_property (GObject *object,
190 guint param_id,
191 const GValue *value,
192 GParamSpec *pspec)
194 GiggleGraphRendererPrivate *priv = GIGGLE_GRAPH_RENDERER (object)->_priv;
196 switch (param_id) {
197 case PROP_REVISION:
198 if (priv->revision) {
199 g_object_unref (priv->revision);
201 priv->revision = GIGGLE_REVISION (g_value_dup_object (value));
202 break;
203 default:
204 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
208 static void
209 giggle_graph_renderer_get_size (GtkCellRenderer *cell,
210 GtkWidget *widget,
211 GdkRectangle *cell_area,
212 gint *x_offset,
213 gint *y_offset,
214 gint *width,
215 gint *height)
217 GiggleGraphRendererPrivate *priv;
218 gint size;
220 priv = GIGGLE_GRAPH_RENDERER (cell)->_priv;
221 size = PANGO_PIXELS (pango_font_description_get_size (widget->style->font_desc));
223 if (height) {
224 *height = PATH_SPACE (size);
227 if (width) {
228 /* the +1 is because we leave half at each side */
229 *width = PATH_SPACE (size) * (priv->n_paths + 1);
232 if (x_offset) {
233 x_offset = 0;
236 if (y_offset) {
237 y_offset = 0;
241 static void
242 get_list_foreach (gpointer key,
243 gpointer value,
244 GList **list)
246 *list = g_list_prepend (*list, key);
249 static GList*
250 get_list (GHashTable *table)
252 GList *list = NULL;
254 g_hash_table_foreach (table, (GHFunc) get_list_foreach, &list);
255 return list;
258 static void
259 giggle_graph_renderer_render (GtkCellRenderer *cell,
260 GdkWindow *window,
261 GtkWidget *widget,
262 GdkRectangle *background_area,
263 GdkRectangle *cell_area,
264 GdkRectangle *expose_area,
265 guint flags)
267 GiggleGraphRendererPrivate *priv;
268 GiggleGraphRendererPathState *path_state;
269 GiggleRevision *revision;
270 GHashTable *paths_state;
271 cairo_t *cr;
272 gint x, y, h;
273 gint cur_pos, pos;
274 GList *children, *paths, *path;
275 gint size;
277 priv = GIGGLE_GRAPH_RENDERER (cell)->_priv;
279 g_return_if_fail (priv->revision != NULL);
281 cr = gdk_cairo_create (window);
282 x = cell_area->x;
283 y = background_area->y;
284 h = background_area->height;
285 revision = priv->revision;
286 size = PANGO_PIXELS (pango_font_description_get_size (widget->style->font_desc));
288 paths_state = g_object_get_qdata (G_OBJECT (revision), revision_paths_state_quark);
289 children = giggle_revision_get_children (revision);
290 cur_pos = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
291 path_state = g_hash_table_lookup (paths_state, GINT_TO_POINTER (cur_pos));
292 paths = path = get_list (paths_state);
293 cairo_set_line_width (cr, LINE_WIDTH (size));
295 /* paint paths */
296 while (path) {
297 pos = GPOINTER_TO_INT (path->data);
298 path_state = g_hash_table_lookup (paths_state, GINT_TO_POINTER (pos));
300 if (path_state->lower_color) {
301 gdk_cairo_set_source_color (cr, path_state->lower_color);
302 cairo_move_to (cr, x + (pos * PATH_SPACE (size)), y + (h / 2));
303 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y + h);
304 cairo_stroke (cr);
307 if (path_state->upper_color) {
308 gdk_cairo_set_source_color (cr, path_state->upper_color);
309 cairo_move_to (cr, x + (pos * PATH_SPACE (size)), y);
310 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y + (h / 2));
311 cairo_stroke (cr);
314 path = path->next;
317 /* paint connections between paths */
318 while (children) {
319 pos = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, children->data));
320 path_state = g_hash_table_lookup (paths_state, GINT_TO_POINTER (pos));
322 if (path_state->upper_color) {
323 gdk_cairo_set_source_color (cr, path_state->upper_color);
324 cairo_move_to (cr,
325 x + (cur_pos * PATH_SPACE (size)),
326 y + (h / 2));
327 cairo_line_to (cr,
328 x + (pos * PATH_SPACE (size)),
329 y + (h / 2));
330 cairo_stroke (cr);
333 children = children->next;
336 /* paint circle */
337 cairo_set_source_rgb (cr, 0, 0, 0);
338 cairo_arc (cr,
339 x + (cur_pos * PATH_SPACE (size)),
340 y + (h / 2),
341 DOT_RADIUS (size), 0, 2 * G_PI);
342 cairo_stroke (cr);
344 /* paint internal circle */
345 path_state = g_hash_table_lookup (paths_state, GINT_TO_POINTER (cur_pos));
346 gdk_cairo_set_source_color (cr, path_state->lower_color);
347 cairo_arc (cr,
348 x + (cur_pos * PATH_SPACE (size)),
349 y + (h / 2),
350 DOT_RADIUS (size) - 1, 0, 2 * G_PI);
351 cairo_fill (cr);
352 cairo_stroke (cr);
354 cairo_destroy (cr);
355 g_list_free (paths);
358 GtkCellRenderer *
359 giggle_graph_renderer_new (void)
361 return g_object_new (GIGGLE_TYPE_GRAPH_RENDERER, NULL);
364 static void
365 find_free_path (GHashTable *visible_paths,
366 gint *n_paths,
367 gint *path)
369 gint cur_path = 1;
371 /* find first path not in list */
372 while (g_hash_table_lookup (visible_paths, GINT_TO_POINTER (cur_path))) {
373 cur_path++;
376 *path = cur_path;
378 /* increment number of paths */
379 if (*path > *n_paths) {
380 *n_paths = *path;
384 static void
385 get_initial_status_foreach (gpointer key,
386 gpointer value,
387 gpointer user_data)
389 GiggleGraphRendererPathState *path_state;
390 GHashTable *table = (GHashTable *) user_data;
392 path_state = g_new0 (GiggleGraphRendererPathState, 1);
393 path_state->lower_color = value;
394 path_state->upper_color = value;
395 g_hash_table_insert (table, key, path_state);
398 static void
399 get_initial_status (GHashTable *paths,
400 GHashTable *visible_paths)
402 g_hash_table_foreach (visible_paths, (GHFunc) get_initial_status_foreach, paths);
405 static void
406 giggle_graph_renderer_calculate_revision_state (GiggleGraphRenderer *renderer,
407 GiggleRevision *revision,
408 GHashTable *visible_paths,
409 gint *n_color)
411 GiggleGraphRendererPathState *path_state;
412 GiggleGraphRendererPrivate *priv;
413 GHashTable *paths_state;
414 GList *children;
415 gboolean current_path_reused = FALSE;
416 gboolean update_color;
417 gint n_path;
419 priv = renderer->_priv;
420 children = giggle_revision_get_children (revision);
421 update_color = (g_list_length (children) > 1);
422 paths_state = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
423 get_initial_status (paths_state, visible_paths);
425 while (children) {
426 path_state = g_new0 (GiggleGraphRendererPathState, 1);
427 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, children->data));
429 if (!n_path) {
430 /* there wasn't a path for this revision, choose one */
431 if (!current_path_reused) {
432 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
433 current_path_reused = TRUE;
434 } else {
435 find_free_path (visible_paths, &priv->n_paths, &n_path);
438 g_hash_table_insert (priv->paths_info, children->data, GINT_TO_POINTER (n_path));
439 path_state->lower_color = g_hash_table_lookup (visible_paths, GINT_TO_POINTER (n_path));
441 if (update_color) {
442 *n_color = NEXT_COLOR (*n_color);
443 path_state->upper_color = &colors[*n_color];
444 } else {
445 path_state->upper_color = path_state->lower_color;
447 } else {
448 path_state->lower_color = g_hash_table_lookup (visible_paths, GINT_TO_POINTER (n_path));
449 path_state->upper_color = path_state->lower_color;
452 g_hash_table_insert (visible_paths, GINT_TO_POINTER (n_path), path_state->upper_color);
453 g_hash_table_insert (paths_state, GINT_TO_POINTER (n_path), path_state);
455 children = children->next;
458 if (!current_path_reused) {
459 /* current path is a dead end, remove it from the visible paths table */
460 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
461 g_hash_table_remove (visible_paths, GINT_TO_POINTER (n_path));
462 path_state = g_hash_table_lookup (paths_state, GINT_TO_POINTER (n_path));
463 path_state->upper_color = NULL;
466 g_object_set_qdata_full (G_OBJECT (revision), revision_paths_state_quark,
467 paths_state, (GDestroyNotify) g_hash_table_destroy);
470 void
471 giggle_graph_renderer_validate_model (GiggleGraphRenderer *renderer,
472 GtkTreeModel *model,
473 gint column)
475 GiggleGraphRendererPrivate *priv;
476 GtkTreeIter iter;
477 gint n_children;
478 gint n_color = 0;
479 GiggleRevision *revision;
480 GHashTable *visible_paths;
481 GType contained_type;
482 gint n_path;
484 g_return_if_fail (GIGGLE_IS_GRAPH_RENDERER (renderer));
485 g_return_if_fail (GTK_IS_TREE_MODEL (model));
487 priv = renderer->_priv;
488 contained_type = gtk_tree_model_get_column_type (model, column);
490 g_return_if_fail (contained_type == GIGGLE_TYPE_REVISION);
492 if (priv->paths_info) {
493 g_hash_table_destroy (priv->paths_info);
496 priv->n_paths = 0;
497 priv->paths_info = g_hash_table_new (g_direct_hash, g_direct_equal);
498 visible_paths = g_hash_table_new (g_direct_hash, g_direct_equal);
499 n_children = gtk_tree_model_iter_n_children (model, NULL);
501 while (n_children) {
502 /* need to calculate state backwards for proper color asignment */
503 n_children--;
504 gtk_tree_model_iter_nth_child (model, &iter, NULL, n_children);
505 gtk_tree_model_get (model, &iter, column, &revision, -1);
507 if (!giggle_revision_get_parents (revision)) {
508 n_color = NEXT_COLOR (n_color);
509 find_free_path (visible_paths, &priv->n_paths, &n_path);
510 g_hash_table_insert (priv->paths_info, revision, GINT_TO_POINTER (n_path));
511 g_hash_table_insert (visible_paths, GINT_TO_POINTER (n_path), &colors[n_color]);
514 giggle_graph_renderer_calculate_revision_state (renderer, revision, visible_paths, &n_color);
515 g_object_unref (revision);
518 g_hash_table_destroy (visible_paths);