Implement bisecting
[anjuta-git-plugin.git] / plugins / git / giggle-graph-renderer.c
blobd23ebb64a75ea9c6d1d02209dcac89bcc26a8f93
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 "git-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 + 3)
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 % (G_N_ELEMENTS (colors) - 1)) + 1)
35 #define INVALID_COLOR 0
37 typedef struct GiggleGraphRendererPrivate GiggleGraphRendererPrivate;
39 struct GiggleGraphRendererPrivate {
40 gint n_paths;
41 GHashTable *paths_info;
42 GitRevision *revision;
45 typedef struct GiggleGraphRendererPathState GiggleGraphRendererPathState;
47 struct GiggleGraphRendererPathState {
48 gushort upper_n_color : 8;
49 gushort lower_n_color : 8;
50 gushort n_path;
53 enum {
54 PROP_0,
55 PROP_REVISION,
58 static GdkColor colors[] = {
59 /* invalid color */
60 { 0x0, 0x0000, 0x0000, 0x0000 },
61 /* light palette */
62 { 0x0, 0xfc00, 0xe900, 0x4f00 }, /* butter */
63 { 0x0, 0xfc00, 0xaf00, 0x3e00 }, /* orange */
64 { 0x0, 0xe900, 0xb900, 0x6e00 }, /* chocolate */
65 { 0x0, 0x8a00, 0xe200, 0x3400 }, /* chameleon */
66 { 0x0, 0x7200, 0x9f00, 0xcf00 }, /* sky blue */
67 { 0x0, 0xad00, 0x7f00, 0xa800 }, /* plum */
68 { 0x0, 0xef00, 0x2900, 0x2900 }, /* scarlet red */
69 #if 0
70 { 0x0, 0xee00, 0xee00, 0xec00 }, /* aluminium */
71 #endif
72 { 0x0, 0x8800, 0x8a00, 0x8500 }, /* no name grey */
73 /* medium palette */
74 { 0x0, 0xed00, 0xd400, 0x0000 }, /* butter */
75 { 0x0, 0xf500, 0x7900, 0x0000 }, /* orange */
76 { 0x0, 0xc100, 0x7d00, 0x1100 }, /* chocolate */
77 { 0x0, 0x7300, 0xd200, 0x1600 }, /* chameleon */
78 { 0x0, 0x3400, 0x6500, 0xa400 }, /* sky blue */
79 { 0x0, 0x7500, 0x5000, 0x7b00 }, /* plum */
80 { 0x0, 0xcc00, 0x0000, 0x0000 }, /* scarlet red */
81 #if 0
82 { 0x0, 0xd300, 0xd700, 0xcf00 }, /* aluminium */
83 #endif
84 { 0x0, 0x5500, 0x5700, 0x5300 }, /* no name grey */
85 /* dark palette */
86 { 0x0, 0xc400, 0xa000, 0x0000 }, /* butter */
87 { 0x0, 0xce00, 0x5c00, 0x0000 }, /* orange */
88 { 0x0, 0x8f00, 0x5900, 0x0200 }, /* chocolate */
89 { 0x0, 0x4e00, 0x9a00, 0x0600 }, /* chameleon */
90 { 0x0, 0x2000, 0x4a00, 0x8700 }, /* sky blue */
91 { 0x0, 0x5c00, 0x3500, 0x6600 }, /* plum */
92 { 0x0, 0xa400, 0x0000, 0x0000 }, /* scarlet red */
93 #if 0
94 { 0x0, 0xba00, 0xbd00, 0xb600 }, /* aluminium */
95 #endif
96 { 0x0, 0x2e00, 0x3400, 0x3600 }, /* no name grey */
99 static GQuark revision_paths_state_quark;
101 static void giggle_graph_renderer_finalize (GObject *object);
102 static void giggle_graph_renderer_get_property (GObject *object,
103 guint param_id,
104 GValue *value,
105 GParamSpec *pspec);
106 static void giggle_graph_renderer_set_property (GObject *object,
107 guint param_id,
108 const GValue *value,
109 GParamSpec *pspec);
110 static void giggle_graph_renderer_get_size (GtkCellRenderer *cell,
111 GtkWidget *widget,
112 GdkRectangle *cell_area,
113 gint *x_offset,
114 gint *y_offset,
115 gint *width,
116 gint *height);
117 static void giggle_graph_renderer_render (GtkCellRenderer *cell,
118 GdkWindow *window,
119 GtkWidget *widget,
120 GdkRectangle *background_area,
121 GdkRectangle *cell_area,
122 GdkRectangle *expose_area,
123 guint flags);
125 G_DEFINE_TYPE (GiggleGraphRenderer, giggle_graph_renderer, GTK_TYPE_CELL_RENDERER)
127 static void
128 giggle_graph_renderer_class_init (GiggleGraphRendererClass *class)
130 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
131 GObjectClass *object_class = G_OBJECT_CLASS (class);
133 cell_class->get_size = giggle_graph_renderer_get_size;
134 cell_class->render = giggle_graph_renderer_render;
136 object_class->finalize = giggle_graph_renderer_finalize;
137 object_class->set_property = giggle_graph_renderer_set_property;
138 object_class->get_property = giggle_graph_renderer_get_property;
140 g_object_class_install_property (
141 object_class,
142 PROP_REVISION,
143 g_param_spec_object ("revision",
144 "revision",
145 "revision",
146 GIT_TYPE_REVISION,
147 G_PARAM_READWRITE));
149 g_type_class_add_private (object_class,
150 sizeof (GiggleGraphRendererPrivate));
152 revision_paths_state_quark = g_quark_from_static_string ("giggle-revision-paths-state");
155 static void
156 giggle_graph_renderer_init (GiggleGraphRenderer *instance)
158 instance->_priv = GET_PRIV (instance);
161 static void
162 giggle_graph_renderer_finalize (GObject *object)
164 GiggleGraphRendererPrivate *priv;
166 priv = GET_PRIV (object);
168 if (priv->paths_info) {
169 g_hash_table_destroy (priv->paths_info);
172 G_OBJECT_CLASS (giggle_graph_renderer_parent_class)->finalize (object);
175 static void
176 giggle_graph_renderer_get_property (GObject *object,
177 guint param_id,
178 GValue *value,
179 GParamSpec *pspec)
181 GiggleGraphRendererPrivate *priv = GIGGLE_GRAPH_RENDERER (object)->_priv;
183 switch (param_id) {
184 case PROP_REVISION:
185 g_value_set_object (value, priv->revision);
186 break;
187 default:
188 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
192 static void
193 giggle_graph_renderer_set_property (GObject *object,
194 guint param_id,
195 const GValue *value,
196 GParamSpec *pspec)
198 GiggleGraphRendererPrivate *priv = GIGGLE_GRAPH_RENDERER (object)->_priv;
200 switch (param_id) {
201 case PROP_REVISION:
202 if (priv->revision) {
203 g_object_unref (priv->revision);
205 priv->revision = GIT_REVISION (g_value_dup_object (value));
206 break;
207 default:
208 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
212 static void
213 giggle_graph_renderer_get_size (GtkCellRenderer *cell,
214 GtkWidget *widget,
215 GdkRectangle *cell_area,
216 gint *x_offset,
217 gint *y_offset,
218 gint *width,
219 gint *height)
221 GiggleGraphRendererPrivate *priv;
222 gint size;
224 priv = GIGGLE_GRAPH_RENDERER (cell)->_priv;
225 size = PANGO_PIXELS (pango_font_description_get_size (widget->style->font_desc));
227 if (height) {
228 *height = PATH_SPACE (size);
231 if (width) {
232 /* the +1 is because we leave half at each side */
233 *width = PATH_SPACE (size) * (priv->n_paths + 1);
236 if (x_offset) {
237 x_offset = 0;
240 if (y_offset) {
241 y_offset = 0;
245 static void
246 giggle_graph_renderer_render (GtkCellRenderer *cell,
247 GdkWindow *window,
248 GtkWidget *widget,
249 GdkRectangle *background_area,
250 GdkRectangle *cell_area,
251 GdkRectangle *expose_area,
252 guint flags)
254 GiggleGraphRendererPrivate *priv;
255 GiggleGraphRendererPathState *path_state;
256 GitRevision *revision;
257 GArray *paths_state;
258 GHashTable *table;
259 cairo_t *cr;
260 gint x, y, h;
261 gint cur_pos, pos;
262 GList *children;
263 gint size, i;
265 priv = GIGGLE_GRAPH_RENDERER (cell)->_priv;
267 if (!priv->revision) {
268 return;
271 cr = gdk_cairo_create (window);
272 x = cell_area->x;
273 y = background_area->y;
274 h = background_area->height;
275 revision = priv->revision;
276 size = PANGO_PIXELS (pango_font_description_get_size (widget->style->font_desc));
278 table = g_hash_table_new (g_direct_hash, g_direct_equal);
279 paths_state = g_object_get_qdata (G_OBJECT (revision), revision_paths_state_quark);
280 children = git_revision_get_children (revision);
281 cur_pos = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
282 cairo_set_line_width (cr, LINE_WIDTH (size));
283 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
285 /* paint paths */
286 for (i = 0; i < paths_state->len; i++) {
287 path_state = & g_array_index (paths_state, GiggleGraphRendererPathState, i);
288 g_hash_table_insert (table, GINT_TO_POINTER ((gint) path_state->n_path), path_state);
289 pos = path_state->n_path;
291 if (path_state->lower_n_color != INVALID_COLOR &&
292 (pos != cur_pos || git_revision_has_parents (revision))) {
293 gdk_cairo_set_source_color (cr, &colors[path_state->lower_n_color]);
294 cairo_move_to (cr, x + (pos * PATH_SPACE (size)), y + (h / 2));
295 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y + h);
296 cairo_stroke (cr);
299 if (path_state->upper_n_color != INVALID_COLOR) {
300 gdk_cairo_set_source_color (cr, &colors[path_state->upper_n_color]);
301 cairo_move_to (cr, x + (pos * PATH_SPACE (size)), y);
302 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y + (h / 2));
303 cairo_stroke (cr);
307 /* paint connections between paths */
308 while (children) {
309 pos = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, children->data));
310 path_state = g_hash_table_lookup (table, GINT_TO_POINTER (pos));
312 if (path_state->upper_n_color != INVALID_COLOR) {
313 gdk_cairo_set_source_color (cr, &colors[path_state->upper_n_color]);
314 cairo_move_to (cr,
315 x + (cur_pos * PATH_SPACE (size)),
316 y + (h / 2));
317 cairo_line_to (cr,
318 x + (pos * PATH_SPACE (size)),
319 y + (h / 2));
321 /* redraw the upper part of the path before
322 * stroking to get a rounded connection
324 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y);
326 cairo_stroke (cr);
329 children = children->next;
332 /* paint circle */
333 cairo_set_source_rgb (cr, 0, 0, 0);
334 cairo_arc (cr,
335 x + (cur_pos * PATH_SPACE (size)),
336 y + (h / 2),
337 DOT_RADIUS (size), 0, 2 * G_PI);
338 cairo_stroke (cr);
340 /* paint internal circle */
341 path_state = g_hash_table_lookup (table, GINT_TO_POINTER (cur_pos));
342 gdk_cairo_set_source_color (cr, &colors[path_state->lower_n_color]);
343 cairo_arc (cr,
344 x + (cur_pos * PATH_SPACE (size)),
345 y + (h / 2),
346 DOT_RADIUS (size) - 1, 0, 2 * G_PI);
347 cairo_fill (cr);
348 cairo_stroke (cr);
350 cairo_destroy (cr);
351 g_hash_table_destroy (table);
354 GtkCellRenderer *
355 giggle_graph_renderer_new (void)
357 return g_object_new (GIGGLE_TYPE_GRAPH_RENDERER, NULL);
360 static void
361 find_free_path (GHashTable *visible_paths,
362 gint *n_paths,
363 gint *path)
365 gint cur_path = 1;
367 /* find first path not in list */
368 while (g_hash_table_lookup (visible_paths, GINT_TO_POINTER (cur_path))) {
369 cur_path++;
372 *path = cur_path;
374 /* increment number of paths */
375 if (*path > *n_paths) {
376 *n_paths = *path;
380 static void
381 get_initial_status_foreach (gpointer key,
382 gpointer value,
383 gpointer user_data)
385 GiggleGraphRendererPathState path_state;
386 GArray *array;
387 gint n_color, n_path;
389 array = (GArray *) user_data;
390 n_color = GPOINTER_TO_INT (value);
391 n_path = GPOINTER_TO_INT (key);
393 path_state.n_path = n_path;
394 path_state.lower_n_color = n_color;
395 path_state.upper_n_color = n_color;
397 g_array_append_val (array, path_state);
400 static GArray *
401 get_initial_status (GHashTable *visible_paths)
403 GArray *array;
404 guint size;
406 size = g_hash_table_size (visible_paths);
407 array = g_array_sized_new (FALSE, TRUE, sizeof (GiggleGraphRendererPathState), size);
409 g_hash_table_foreach (visible_paths, (GHFunc) get_initial_status_foreach, array);
411 return array;
414 static void
415 free_paths_state (GArray *array)
417 g_array_free (array, FALSE);
420 static void
421 giggle_graph_renderer_calculate_revision_state (GiggleGraphRenderer *renderer,
422 GitRevision *revision,
423 GHashTable *visible_paths,
424 gint *n_color)
426 GiggleGraphRendererPathState path_state;
427 GiggleGraphRendererPrivate *priv;
428 GitRevision *rev;
429 GArray *paths_state;
430 GList *children;
431 gboolean current_path_reused = FALSE;
432 gboolean update_color;
433 gint n_path, i;
435 priv = renderer->_priv;
436 children = git_revision_get_children (revision);
437 update_color = (g_list_length (children) > 1);
438 paths_state = get_initial_status (visible_paths);
440 while (children) {
441 rev = GIT_REVISION (children->data);
442 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, rev));
444 if (!n_path) {
445 /* there wasn't a path for this revision, choose one */
446 if (!current_path_reused) {
447 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
448 current_path_reused = TRUE;
449 } else {
450 find_free_path (visible_paths, &priv->n_paths, &n_path);
453 g_hash_table_insert (priv->paths_info, rev, GINT_TO_POINTER (n_path));
454 path_state.lower_n_color =
455 GPOINTER_TO_INT (g_hash_table_lookup (visible_paths, GINT_TO_POINTER (n_path)));
457 if (update_color) {
458 path_state.upper_n_color = *n_color = NEXT_COLOR (*n_color);
459 } else {
460 path_state.upper_n_color = path_state.lower_n_color;
462 } else {
463 path_state.lower_n_color =
464 GPOINTER_TO_INT (g_hash_table_lookup (visible_paths, GINT_TO_POINTER (n_path)));
466 path_state.upper_n_color = path_state.lower_n_color;
469 path_state.n_path = n_path;
470 g_hash_table_insert (visible_paths, GINT_TO_POINTER (n_path), GINT_TO_POINTER ((gint) path_state.upper_n_color));
471 g_array_append_val (paths_state, path_state);
473 children = children->next;
476 if (!current_path_reused) {
477 /* current path is a dead end, remove it from the visible paths table */
478 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
479 g_hash_table_remove (visible_paths, GINT_TO_POINTER (n_path));
481 for (i = 0; i < paths_state->len; i++) {
482 path_state = g_array_index (paths_state, GiggleGraphRendererPathState, i);
484 if (path_state.n_path == n_path) {
485 path_state.upper_n_color = INVALID_COLOR;
486 ((GiggleGraphRendererPathState *) paths_state->data)[i] = path_state;
487 break;
492 g_object_set_qdata_full (G_OBJECT (revision), revision_paths_state_quark,
493 paths_state, (GDestroyNotify) free_paths_state);
496 void
497 giggle_graph_renderer_validate_model (GiggleGraphRenderer *renderer,
498 GtkTreeModel *model,
499 gint column)
501 GiggleGraphRendererPrivate *priv;
502 GtkTreeIter iter;
503 gint n_children;
504 gint n_color = 0;
505 GitRevision *revision;
506 GHashTable *visible_paths;
507 GType contained_type;
508 gint n_path;
510 g_return_if_fail (GIGGLE_IS_GRAPH_RENDERER (renderer));
511 g_return_if_fail (GTK_IS_TREE_MODEL (model));
513 priv = renderer->_priv;
514 contained_type = gtk_tree_model_get_column_type (model, column);
516 /*g_return_if_fail (contained_type == GIT_TYPE_REVISION);*/
518 if (priv->paths_info) {
519 g_hash_table_destroy (priv->paths_info);
522 priv->n_paths = 0;
523 priv->paths_info = g_hash_table_new (g_direct_hash, g_direct_equal);
524 visible_paths = g_hash_table_new (g_direct_hash, g_direct_equal);
525 n_children = gtk_tree_model_iter_n_children (model, NULL);
527 while (n_children) {
528 /* need to calculate state backwards for proper color asignment */
529 n_children--;
530 gtk_tree_model_iter_nth_child (model, &iter, NULL, n_children);
531 gtk_tree_model_get (model, &iter, column, &revision, -1);
533 if (revision) {
534 if (!git_revision_has_parents (revision)) {
535 n_color = NEXT_COLOR (n_color);
536 find_free_path (visible_paths, &priv->n_paths, &n_path);
537 g_hash_table_insert (priv->paths_info, revision, GINT_TO_POINTER (n_path));
538 g_hash_table_insert (visible_paths, GINT_TO_POINTER (n_path), GINT_TO_POINTER (n_color));
541 giggle_graph_renderer_calculate_revision_state (renderer, revision, visible_paths, &n_color);
542 g_object_unref (revision);
546 g_hash_table_destroy (visible_paths);