glade: Fix make file some files were not installed
[anjuta.git] / plugins / git / giggle-graph-renderer.c
blobe3867ec63c17dc2f4bdc4a5f40c07c45592f9018
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., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, 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 const 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 cairo_t *cr,
119 GtkWidget *widget,
120 const GdkRectangle *background_area,
121 const GdkRectangle *cell_area,
122 GtkCellRendererState flags);
124 G_DEFINE_TYPE (GiggleGraphRenderer, giggle_graph_renderer, GTK_TYPE_CELL_RENDERER)
126 static void
127 giggle_graph_renderer_class_init (GiggleGraphRendererClass *class)
129 GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class);
130 GObjectClass *object_class = G_OBJECT_CLASS (class);
132 cell_class->get_size = giggle_graph_renderer_get_size;
133 cell_class->render = giggle_graph_renderer_render;
135 object_class->finalize = giggle_graph_renderer_finalize;
136 object_class->set_property = giggle_graph_renderer_set_property;
137 object_class->get_property = giggle_graph_renderer_get_property;
139 g_object_class_install_property (
140 object_class,
141 PROP_REVISION,
142 g_param_spec_object ("revision",
143 "revision",
144 "revision",
145 GIT_TYPE_REVISION,
146 G_PARAM_READWRITE));
148 g_type_class_add_private (object_class,
149 sizeof (GiggleGraphRendererPrivate));
151 revision_paths_state_quark = g_quark_from_static_string ("giggle-revision-paths-state");
154 static void
155 giggle_graph_renderer_init (GiggleGraphRenderer *instance)
157 instance->_priv = GET_PRIV (instance);
160 static void
161 giggle_graph_renderer_finalize (GObject *object)
163 GiggleGraphRendererPrivate *priv;
165 priv = GET_PRIV (object);
167 if (priv->paths_info) {
168 g_hash_table_destroy (priv->paths_info);
171 G_OBJECT_CLASS (giggle_graph_renderer_parent_class)->finalize (object);
174 static void
175 giggle_graph_renderer_get_property (GObject *object,
176 guint param_id,
177 GValue *value,
178 GParamSpec *pspec)
180 GiggleGraphRendererPrivate *priv = GIGGLE_GRAPH_RENDERER (object)->_priv;
182 switch (param_id) {
183 case PROP_REVISION:
184 g_value_set_object (value, priv->revision);
185 break;
186 default:
187 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
191 static void
192 giggle_graph_renderer_set_property (GObject *object,
193 guint param_id,
194 const GValue *value,
195 GParamSpec *pspec)
197 GiggleGraphRendererPrivate *priv = GIGGLE_GRAPH_RENDERER (object)->_priv;
199 switch (param_id) {
200 case PROP_REVISION:
201 if (priv->revision) {
202 g_object_unref (priv->revision);
204 priv->revision = GIT_REVISION (g_value_dup_object (value));
205 break;
206 default:
207 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
211 static void
212 giggle_graph_renderer_get_size (GtkCellRenderer *cell,
213 GtkWidget *widget,
214 const GdkRectangle *cell_area,
215 gint *x_offset,
216 gint *y_offset,
217 gint *width,
218 gint *height)
220 GiggleGraphRendererPrivate *priv;
221 gint size;
223 priv = GIGGLE_GRAPH_RENDERER (cell)->_priv;
224 size = PANGO_PIXELS (pango_font_description_get_size (gtk_widget_get_style (widget)->font_desc));
226 if (height) {
227 *height = PATH_SPACE (size);
230 if (width) {
231 /* the +1 is because we leave half at each side */
232 *width = PATH_SPACE (size) * (priv->n_paths + 1);
235 if (x_offset) {
236 x_offset = 0;
239 if (y_offset) {
240 y_offset = 0;
244 static void
245 giggle_graph_renderer_render (GtkCellRenderer *cell,
246 cairo_t *cr,
247 GtkWidget *widget,
248 const GdkRectangle *background_area,
249 const GdkRectangle *cell_area,
250 GtkCellRendererState flags)
252 GiggleGraphRendererPrivate *priv;
253 GiggleGraphRendererPathState *path_state;
254 GitRevision *revision;
255 GArray *paths_state;
256 GHashTable *table;
257 gint x, y, h;
258 gint cur_pos, pos;
259 GList *children;
260 gint size, i;
262 priv = GIGGLE_GRAPH_RENDERER (cell)->_priv;
264 if (!priv->revision) {
265 return;
268 x = cell_area->x;
269 y = background_area->y;
270 h = background_area->height;
271 revision = priv->revision;
272 size = PANGO_PIXELS (pango_font_description_get_size (gtk_widget_get_style (widget)->font_desc));
274 table = g_hash_table_new (g_direct_hash, g_direct_equal);
275 paths_state = g_object_get_qdata (G_OBJECT (revision), revision_paths_state_quark);
276 children = git_revision_get_children (revision);
277 cur_pos = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
278 cairo_set_line_width (cr, LINE_WIDTH (size));
279 cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
281 /* paint paths */
282 for (i = 0; i < paths_state->len; i++) {
283 path_state = & g_array_index (paths_state, GiggleGraphRendererPathState, i);
284 g_hash_table_insert (table, GINT_TO_POINTER ((gint) path_state->n_path), path_state);
285 pos = path_state->n_path;
287 if (path_state->lower_n_color != INVALID_COLOR &&
288 (pos != cur_pos || git_revision_has_parents (revision))) {
289 gdk_cairo_set_source_color (cr, &colors[path_state->lower_n_color]);
290 cairo_move_to (cr, x + (pos * PATH_SPACE (size)), y + (h / 2));
291 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y + h);
292 cairo_stroke (cr);
295 if (path_state->upper_n_color != INVALID_COLOR) {
296 gdk_cairo_set_source_color (cr, &colors[path_state->upper_n_color]);
297 cairo_move_to (cr, x + (pos * PATH_SPACE (size)), y);
298 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y + (h / 2));
299 cairo_stroke (cr);
303 /* paint connections between paths */
304 while (children) {
305 pos = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, children->data));
306 path_state = g_hash_table_lookup (table, GINT_TO_POINTER (pos));
308 if (path_state->upper_n_color != INVALID_COLOR) {
309 gdk_cairo_set_source_color (cr, &colors[path_state->upper_n_color]);
310 cairo_move_to (cr,
311 x + (cur_pos * PATH_SPACE (size)),
312 y + (h / 2));
313 cairo_line_to (cr,
314 x + (pos * PATH_SPACE (size)),
315 y + (h / 2));
317 /* redraw the upper part of the path before
318 * stroking to get a rounded connection
320 cairo_line_to (cr, x + (pos * PATH_SPACE (size)), y);
322 cairo_stroke (cr);
325 children = children->next;
328 /* paint circle */
329 cairo_set_source_rgb (cr, 0, 0, 0);
330 cairo_arc (cr,
331 x + (cur_pos * PATH_SPACE (size)),
332 y + (h / 2),
333 DOT_RADIUS (size), 0, 2 * G_PI);
334 cairo_stroke (cr);
336 /* paint internal circle */
337 path_state = g_hash_table_lookup (table, GINT_TO_POINTER (cur_pos));
338 gdk_cairo_set_source_color (cr, &colors[path_state->lower_n_color]);
339 cairo_arc (cr,
340 x + (cur_pos * PATH_SPACE (size)),
341 y + (h / 2),
342 DOT_RADIUS (size) - 1, 0, 2 * G_PI);
343 cairo_fill (cr);
344 cairo_stroke (cr);
346 g_hash_table_destroy (table);
349 GtkCellRenderer *
350 giggle_graph_renderer_new (void)
352 return g_object_new (GIGGLE_TYPE_GRAPH_RENDERER, NULL);
355 static void
356 find_free_path (GHashTable *visible_paths,
357 gint *n_paths,
358 gint *path)
360 gint cur_path = 1;
362 /* find first path not in list */
363 while (g_hash_table_lookup (visible_paths, GINT_TO_POINTER (cur_path))) {
364 cur_path++;
367 *path = cur_path;
369 /* increment number of paths */
370 if (*path > *n_paths) {
371 *n_paths = *path;
375 static void
376 get_initial_status_foreach (gpointer key,
377 gpointer value,
378 gpointer user_data)
380 GiggleGraphRendererPathState path_state;
381 GArray *array;
382 gint n_color, n_path;
384 array = (GArray *) user_data;
385 n_color = GPOINTER_TO_INT (value);
386 n_path = GPOINTER_TO_INT (key);
388 path_state.n_path = n_path;
389 path_state.lower_n_color = n_color;
390 path_state.upper_n_color = n_color;
392 g_array_append_val (array, path_state);
395 static GArray *
396 get_initial_status (GHashTable *visible_paths)
398 GArray *array;
399 guint size;
401 size = g_hash_table_size (visible_paths);
402 array = g_array_sized_new (FALSE, TRUE, sizeof (GiggleGraphRendererPathState), size);
404 g_hash_table_foreach (visible_paths, (GHFunc) get_initial_status_foreach, array);
406 return array;
409 static void
410 free_paths_state (GArray *array)
412 g_array_free (array, TRUE);
415 static void
416 giggle_graph_renderer_calculate_revision_state (GiggleGraphRenderer *renderer,
417 GitRevision *revision,
418 GHashTable *visible_paths,
419 gint *n_color)
421 GiggleGraphRendererPathState path_state;
422 GiggleGraphRendererPrivate *priv;
423 GitRevision *rev;
424 GArray *paths_state;
425 GList *children;
426 gboolean current_path_reused = FALSE;
427 gboolean update_color;
428 gint n_path, i;
430 priv = renderer->_priv;
431 children = git_revision_get_children (revision);
432 update_color = (g_list_length (children) > 1);
433 paths_state = get_initial_status (visible_paths);
435 while (children) {
436 rev = GIT_REVISION (children->data);
437 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, rev));
439 if (!n_path) {
440 /* there wasn't a path for this revision, choose one */
441 if (!current_path_reused) {
442 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
443 current_path_reused = TRUE;
444 } else {
445 find_free_path (visible_paths, &priv->n_paths, &n_path);
448 g_hash_table_insert (priv->paths_info, rev, GINT_TO_POINTER (n_path));
449 path_state.lower_n_color =
450 GPOINTER_TO_INT (g_hash_table_lookup (visible_paths, GINT_TO_POINTER (n_path)));
452 if (update_color) {
453 path_state.upper_n_color = *n_color = NEXT_COLOR (*n_color);
454 } else {
455 path_state.upper_n_color = path_state.lower_n_color;
457 } else {
458 path_state.lower_n_color =
459 GPOINTER_TO_INT (g_hash_table_lookup (visible_paths, GINT_TO_POINTER (n_path)));
461 path_state.upper_n_color = path_state.lower_n_color;
464 path_state.n_path = n_path;
465 g_hash_table_insert (visible_paths, GINT_TO_POINTER (n_path), GINT_TO_POINTER ((gint) path_state.upper_n_color));
466 g_array_append_val (paths_state, path_state);
468 children = children->next;
471 if (!current_path_reused) {
472 /* current path is a dead end, remove it from the visible paths table */
473 n_path = GPOINTER_TO_INT (g_hash_table_lookup (priv->paths_info, revision));
474 g_hash_table_remove (visible_paths, GINT_TO_POINTER (n_path));
476 for (i = 0; i < paths_state->len; i++) {
477 path_state = g_array_index (paths_state, GiggleGraphRendererPathState, i);
479 if (path_state.n_path == n_path) {
480 path_state.upper_n_color = INVALID_COLOR;
481 ((GiggleGraphRendererPathState *) paths_state->data)[i] = path_state;
482 break;
487 g_object_set_qdata_full (G_OBJECT (revision), revision_paths_state_quark,
488 paths_state, (GDestroyNotify) free_paths_state);
491 void
492 giggle_graph_renderer_validate_model (GiggleGraphRenderer *renderer,
493 GtkTreeModel *model,
494 gint column)
496 GiggleGraphRendererPrivate *priv;
497 GtkTreeIter iter;
498 gint n_children;
499 gint n_color = 0;
500 GitRevision *revision;
501 GHashTable *visible_paths;
502 GType contained_type;
503 gint n_path;
505 g_return_if_fail (GIGGLE_IS_GRAPH_RENDERER (renderer));
506 g_return_if_fail (GTK_IS_TREE_MODEL (model));
508 priv = renderer->_priv;
509 contained_type = gtk_tree_model_get_column_type (model, column);
511 /*g_return_if_fail (contained_type == GIT_TYPE_REVISION);*/
513 if (priv->paths_info) {
514 g_hash_table_destroy (priv->paths_info);
517 priv->n_paths = 0;
518 priv->paths_info = g_hash_table_new (g_direct_hash, g_direct_equal);
519 visible_paths = g_hash_table_new (g_direct_hash, g_direct_equal);
520 n_children = gtk_tree_model_iter_n_children (model, NULL);
522 while (n_children) {
523 /* need to calculate state backwards for proper color asignment */
524 n_children--;
525 gtk_tree_model_iter_nth_child (model, &iter, NULL, n_children);
526 gtk_tree_model_get (model, &iter, column, &revision, -1);
528 if (revision) {
529 if (!git_revision_has_parents (revision)) {
530 n_color = NEXT_COLOR (n_color);
531 find_free_path (visible_paths, &priv->n_paths, &n_path);
532 g_hash_table_insert (priv->paths_info, revision, GINT_TO_POINTER (n_path));
533 g_hash_table_insert (visible_paths, GINT_TO_POINTER (n_path), GINT_TO_POINTER (n_color));
536 giggle_graph_renderer_calculate_revision_state (renderer, revision, visible_paths, &n_color);
537 g_object_unref (revision);
541 g_hash_table_destroy (visible_paths);