1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
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.
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
{
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;
58 static GdkColor colors
[] = {
60 { 0x0, 0x0000, 0x0000, 0x0000 },
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 */
70 { 0x0, 0xee00, 0xee00, 0xec00 }, /* aluminium */
72 { 0x0, 0x8800, 0x8a00, 0x8500 }, /* no name grey */
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 */
82 { 0x0, 0xd300, 0xd700, 0xcf00 }, /* aluminium */
84 { 0x0, 0x5500, 0x5700, 0x5300 }, /* no name grey */
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 */
94 { 0x0, 0xba00, 0xbd00, 0xb600 }, /* aluminium */
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
,
106 static void giggle_graph_renderer_set_property (GObject
*object
,
110 static void giggle_graph_renderer_get_size (GtkCellRenderer
*cell
,
112 GdkRectangle
*cell_area
,
117 static void giggle_graph_renderer_render (GtkCellRenderer
*cell
,
120 GdkRectangle
*background_area
,
121 GdkRectangle
*cell_area
,
122 GdkRectangle
*expose_area
,
125 G_DEFINE_TYPE (GiggleGraphRenderer
, giggle_graph_renderer
, GTK_TYPE_CELL_RENDERER
)
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 (
143 g_param_spec_object ("revision",
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");
156 giggle_graph_renderer_init (GiggleGraphRenderer
*instance
)
158 instance
->_priv
= GET_PRIV (instance
);
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
);
176 giggle_graph_renderer_get_property (GObject
*object
,
181 GiggleGraphRendererPrivate
*priv
= GIGGLE_GRAPH_RENDERER (object
)->_priv
;
185 g_value_set_object (value
, priv
->revision
);
188 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
193 giggle_graph_renderer_set_property (GObject
*object
,
198 GiggleGraphRendererPrivate
*priv
= GIGGLE_GRAPH_RENDERER (object
)->_priv
;
202 if (priv
->revision
) {
203 g_object_unref (priv
->revision
);
205 priv
->revision
= GIT_REVISION (g_value_dup_object (value
));
208 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
213 giggle_graph_renderer_get_size (GtkCellRenderer
*cell
,
215 GdkRectangle
*cell_area
,
221 GiggleGraphRendererPrivate
*priv
;
224 priv
= GIGGLE_GRAPH_RENDERER (cell
)->_priv
;
225 size
= PANGO_PIXELS (pango_font_description_get_size (widget
->style
->font_desc
));
228 *height
= PATH_SPACE (size
);
232 /* the +1 is because we leave half at each side */
233 *width
= PATH_SPACE (size
) * (priv
->n_paths
+ 1);
246 giggle_graph_renderer_render (GtkCellRenderer
*cell
,
249 GdkRectangle
*background_area
,
250 GdkRectangle
*cell_area
,
251 GdkRectangle
*expose_area
,
254 GiggleGraphRendererPrivate
*priv
;
255 GiggleGraphRendererPathState
*path_state
;
256 GitRevision
*revision
;
265 priv
= GIGGLE_GRAPH_RENDERER (cell
)->_priv
;
267 if (!priv
->revision
) {
271 cr
= gdk_cairo_create (window
);
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
);
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
);
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));
307 /* paint connections between paths */
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
]);
315 x
+ (cur_pos
* PATH_SPACE (size
)),
318 x
+ (pos
* PATH_SPACE (size
)),
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
);
329 children
= children
->next
;
333 cairo_set_source_rgb (cr
, 0, 0, 0);
335 x
+ (cur_pos
* PATH_SPACE (size
)),
337 DOT_RADIUS (size
), 0, 2 * G_PI
);
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
]);
344 x
+ (cur_pos
* PATH_SPACE (size
)),
346 DOT_RADIUS (size
) - 1, 0, 2 * G_PI
);
351 g_hash_table_destroy (table
);
355 giggle_graph_renderer_new (void)
357 return g_object_new (GIGGLE_TYPE_GRAPH_RENDERER
, NULL
);
361 find_free_path (GHashTable
*visible_paths
,
367 /* find first path not in list */
368 while (g_hash_table_lookup (visible_paths
, GINT_TO_POINTER (cur_path
))) {
374 /* increment number of paths */
375 if (*path
> *n_paths
) {
381 get_initial_status_foreach (gpointer key
,
385 GiggleGraphRendererPathState path_state
;
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
);
401 get_initial_status (GHashTable
*visible_paths
)
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
);
415 free_paths_state (GArray
*array
)
417 g_array_free (array
, FALSE
);
421 giggle_graph_renderer_calculate_revision_state (GiggleGraphRenderer
*renderer
,
422 GitRevision
*revision
,
423 GHashTable
*visible_paths
,
426 GiggleGraphRendererPathState path_state
;
427 GiggleGraphRendererPrivate
*priv
;
431 gboolean current_path_reused
= FALSE
;
432 gboolean update_color
;
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
);
441 rev
= GIT_REVISION (children
->data
);
442 n_path
= GPOINTER_TO_INT (g_hash_table_lookup (priv
->paths_info
, rev
));
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
;
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
)));
458 path_state
.upper_n_color
= *n_color
= NEXT_COLOR (*n_color
);
460 path_state
.upper_n_color
= path_state
.lower_n_color
;
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
;
492 g_object_set_qdata_full (G_OBJECT (revision
), revision_paths_state_quark
,
493 paths_state
, (GDestroyNotify
) free_paths_state
);
497 giggle_graph_renderer_validate_model (GiggleGraphRenderer
*renderer
,
501 GiggleGraphRendererPrivate
*priv
;
505 GitRevision
*revision
;
506 GHashTable
*visible_paths
;
507 GType contained_type
;
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
);
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
);
528 /* need to calculate state backwards for proper color asignment */
530 gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, n_children
);
531 gtk_tree_model_get (model
, &iter
, column
, &revision
, -1);
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
);