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 "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
{
40 GHashTable
*paths_info
;
41 GiggleRevision
*revision
;
44 typedef struct GiggleGraphRendererPathState GiggleGraphRendererPathState
;
46 struct GiggleGraphRendererPathState
{
47 GdkColor
*upper_color
;
48 GdkColor
*lower_color
;
56 static GdkColor colors
[] = {
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 */
66 { 0x0, 0xee00, 0xee00, 0xec00 }, /* aluminium */
68 { 0x0, 0x8800, 0x8a00, 0x8500 }, /* no name grey */
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 */
78 { 0x0, 0xd300, 0xd700, 0xcf00 }, /* aluminium */
80 { 0x0, 0x5500, 0x5700, 0x5300 }, /* no name grey */
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 */
90 { 0x0, 0xba00, 0xbd00, 0xb600 }, /* aluminium */
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
,
102 static void giggle_graph_renderer_set_property (GObject
*object
,
106 static void giggle_graph_renderer_get_size (GtkCellRenderer
*cell
,
108 GdkRectangle
*cell_area
,
113 static void giggle_graph_renderer_render (GtkCellRenderer
*cell
,
116 GdkRectangle
*background_area
,
117 GdkRectangle
*cell_area
,
118 GdkRectangle
*expose_area
,
121 G_DEFINE_TYPE (GiggleGraphRenderer
, giggle_graph_renderer
, GTK_TYPE_CELL_RENDERER
)
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 (
139 g_param_spec_object ("revision",
142 GIGGLE_TYPE_REVISION
,
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");
152 giggle_graph_renderer_init (GiggleGraphRenderer
*instance
)
154 instance
->_priv
= GET_PRIV (instance
);
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
);
172 giggle_graph_renderer_get_property (GObject
*object
,
177 GiggleGraphRendererPrivate
*priv
= GIGGLE_GRAPH_RENDERER (object
)->_priv
;
181 g_value_set_object (value
, priv
->revision
);
184 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
189 giggle_graph_renderer_set_property (GObject
*object
,
194 GiggleGraphRendererPrivate
*priv
= GIGGLE_GRAPH_RENDERER (object
)->_priv
;
198 if (priv
->revision
) {
199 g_object_unref (priv
->revision
);
201 priv
->revision
= GIGGLE_REVISION (g_value_dup_object (value
));
204 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
209 giggle_graph_renderer_get_size (GtkCellRenderer
*cell
,
211 GdkRectangle
*cell_area
,
217 GiggleGraphRendererPrivate
*priv
;
220 priv
= GIGGLE_GRAPH_RENDERER (cell
)->_priv
;
221 size
= PANGO_PIXELS (pango_font_description_get_size (widget
->style
->font_desc
));
224 *height
= PATH_SPACE (size
);
228 /* the +1 is because we leave half at each side */
229 *width
= PATH_SPACE (size
) * (priv
->n_paths
+ 1);
242 get_list_foreach (gpointer key
,
246 *list
= g_list_prepend (*list
, key
);
250 get_list (GHashTable
*table
)
254 g_hash_table_foreach (table
, (GHFunc
) get_list_foreach
, &list
);
259 giggle_graph_renderer_render (GtkCellRenderer
*cell
,
262 GdkRectangle
*background_area
,
263 GdkRectangle
*cell_area
,
264 GdkRectangle
*expose_area
,
267 GiggleGraphRendererPrivate
*priv
;
268 GiggleGraphRendererPathState
*path_state
;
269 GiggleRevision
*revision
;
270 GHashTable
*paths_state
;
274 GList
*children
, *paths
, *path
;
277 priv
= GIGGLE_GRAPH_RENDERER (cell
)->_priv
;
279 g_return_if_fail (priv
->revision
!= NULL
);
281 cr
= gdk_cairo_create (window
);
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
));
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
);
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));
317 /* paint connections between paths */
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
);
325 x
+ (cur_pos
* PATH_SPACE (size
)),
328 x
+ (pos
* PATH_SPACE (size
)),
333 children
= children
->next
;
337 cairo_set_source_rgb (cr
, 0, 0, 0);
339 x
+ (cur_pos
* PATH_SPACE (size
)),
341 DOT_RADIUS (size
), 0, 2 * G_PI
);
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
);
348 x
+ (cur_pos
* PATH_SPACE (size
)),
350 DOT_RADIUS (size
) - 1, 0, 2 * G_PI
);
359 giggle_graph_renderer_new (void)
361 return g_object_new (GIGGLE_TYPE_GRAPH_RENDERER
, NULL
);
365 find_free_path (GHashTable
*visible_paths
,
371 /* find first path not in list */
372 while (g_hash_table_lookup (visible_paths
, GINT_TO_POINTER (cur_path
))) {
378 /* increment number of paths */
379 if (*path
> *n_paths
) {
385 get_initial_status_foreach (gpointer key
,
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
);
399 get_initial_status (GHashTable
*paths
,
400 GHashTable
*visible_paths
)
402 g_hash_table_foreach (visible_paths
, (GHFunc
) get_initial_status_foreach
, paths
);
406 giggle_graph_renderer_calculate_revision_state (GiggleGraphRenderer
*renderer
,
407 GiggleRevision
*revision
,
408 GHashTable
*visible_paths
,
411 GiggleGraphRendererPathState
*path_state
;
412 GiggleGraphRendererPrivate
*priv
;
413 GHashTable
*paths_state
;
415 gboolean current_path_reused
= FALSE
;
416 gboolean update_color
;
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
);
426 path_state
= g_new0 (GiggleGraphRendererPathState
, 1);
427 n_path
= GPOINTER_TO_INT (g_hash_table_lookup (priv
->paths_info
, children
->data
));
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
;
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
));
442 *n_color
= NEXT_COLOR (*n_color
);
443 path_state
->upper_color
= &colors
[*n_color
];
445 path_state
->upper_color
= path_state
->lower_color
;
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
);
471 giggle_graph_renderer_validate_model (GiggleGraphRenderer
*renderer
,
475 GiggleGraphRendererPrivate
*priv
;
479 GiggleRevision
*revision
;
480 GHashTable
*visible_paths
;
481 GType contained_type
;
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
);
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
);
502 /* need to calculate state backwards for proper color asignment */
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
);