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., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, 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 const GdkRectangle
*cell_area
,
117 static void giggle_graph_renderer_render (GtkCellRenderer
*cell
,
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
)
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 (
142 g_param_spec_object ("revision",
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");
155 giggle_graph_renderer_init (GiggleGraphRenderer
*instance
)
157 instance
->_priv
= GET_PRIV (instance
);
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
);
175 giggle_graph_renderer_get_property (GObject
*object
,
180 GiggleGraphRendererPrivate
*priv
= GIGGLE_GRAPH_RENDERER (object
)->_priv
;
184 g_value_set_object (value
, priv
->revision
);
187 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
192 giggle_graph_renderer_set_property (GObject
*object
,
197 GiggleGraphRendererPrivate
*priv
= GIGGLE_GRAPH_RENDERER (object
)->_priv
;
201 if (priv
->revision
) {
202 g_object_unref (priv
->revision
);
204 priv
->revision
= GIT_REVISION (g_value_dup_object (value
));
207 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
212 giggle_graph_renderer_get_size (GtkCellRenderer
*cell
,
214 const GdkRectangle
*cell_area
,
220 GiggleGraphRendererPrivate
*priv
;
223 priv
= GIGGLE_GRAPH_RENDERER (cell
)->_priv
;
224 size
= PANGO_PIXELS (pango_font_description_get_size (gtk_widget_get_style (widget
)->font_desc
));
227 *height
= PATH_SPACE (size
);
231 /* the +1 is because we leave half at each side */
232 *width
= PATH_SPACE (size
) * (priv
->n_paths
+ 1);
245 giggle_graph_renderer_render (GtkCellRenderer
*cell
,
248 const GdkRectangle
*background_area
,
249 const GdkRectangle
*cell_area
,
250 GtkCellRendererState flags
)
252 GiggleGraphRendererPrivate
*priv
;
253 GiggleGraphRendererPathState
*path_state
;
254 GitRevision
*revision
;
262 priv
= GIGGLE_GRAPH_RENDERER (cell
)->_priv
;
264 if (!priv
->revision
) {
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
);
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
);
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));
303 /* paint connections between paths */
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
]);
311 x
+ (cur_pos
* PATH_SPACE (size
)),
314 x
+ (pos
* PATH_SPACE (size
)),
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
);
325 children
= children
->next
;
329 cairo_set_source_rgb (cr
, 0, 0, 0);
331 x
+ (cur_pos
* PATH_SPACE (size
)),
333 DOT_RADIUS (size
), 0, 2 * G_PI
);
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
]);
340 x
+ (cur_pos
* PATH_SPACE (size
)),
342 DOT_RADIUS (size
) - 1, 0, 2 * G_PI
);
346 g_hash_table_destroy (table
);
350 giggle_graph_renderer_new (void)
352 return g_object_new (GIGGLE_TYPE_GRAPH_RENDERER
, NULL
);
356 find_free_path (GHashTable
*visible_paths
,
362 /* find first path not in list */
363 while (g_hash_table_lookup (visible_paths
, GINT_TO_POINTER (cur_path
))) {
369 /* increment number of paths */
370 if (*path
> *n_paths
) {
376 get_initial_status_foreach (gpointer key
,
380 GiggleGraphRendererPathState path_state
;
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
);
396 get_initial_status (GHashTable
*visible_paths
)
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
);
410 free_paths_state (GArray
*array
)
412 g_array_free (array
, TRUE
);
416 giggle_graph_renderer_calculate_revision_state (GiggleGraphRenderer
*renderer
,
417 GitRevision
*revision
,
418 GHashTable
*visible_paths
,
421 GiggleGraphRendererPathState path_state
;
422 GiggleGraphRendererPrivate
*priv
;
426 gboolean current_path_reused
= FALSE
;
427 gboolean update_color
;
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
);
436 rev
= GIT_REVISION (children
->data
);
437 n_path
= GPOINTER_TO_INT (g_hash_table_lookup (priv
->paths_info
, rev
));
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
;
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
)));
453 path_state
.upper_n_color
= *n_color
= NEXT_COLOR (*n_color
);
455 path_state
.upper_n_color
= path_state
.lower_n_color
;
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
;
487 g_object_set_qdata_full (G_OBJECT (revision
), revision_paths_state_quark
,
488 paths_state
, (GDestroyNotify
) free_paths_state
);
492 giggle_graph_renderer_validate_model (GiggleGraphRenderer
*renderer
,
496 GiggleGraphRendererPrivate
*priv
;
500 GitRevision
*revision
;
501 GHashTable
*visible_paths
;
502 GType contained_type
;
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
);
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
);
523 /* need to calculate state backwards for proper color asignment */
525 gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, n_children
);
526 gtk_tree_model_get (model
, &iter
, column
, &revision
, -1);
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
);