doc: updated copyright
[adg.git] / src / adg / adg-table.c
blob450a6f2634f4798758790fcc97b8ae6a5f160afd
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010,2011,2012,2013 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-table
23 * @short_description: A tabular entity
25 * The #AdgTable is the entity to be used for rendering data arranged
26 * in tabular evironments.
28 * To define a table, you should add to it a serie of one or more
29 * #AdgTableRow by using the #AdgTableRow specific APIs.
31 * Since: 1.0
32 **/
34 /**
35 * AdgTable:
37 * All fields are private and should not be used directly.
38 * Use its public methods instead.
40 * Since: 1.0
41 **/
44 #include "adg-internal.h"
46 #include "adg-model.h"
47 #include "adg-trail.h"
48 #include "adg-dress.h"
49 #include "adg-dress-builtins.h"
50 #include "adg-style.h"
51 #include "adg-table-style.h"
52 #include "adg-path.h"
53 #include "adg-stroke.h"
54 #include "adg-container.h"
55 #include "adg-alignment.h"
57 #include "adg-table.h"
58 #include "adg-table-private.h"
59 #include "adg-table-row.h"
60 #include "adg-table-cell.h"
63 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class)
64 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class)
67 G_DEFINE_TYPE(AdgTable, adg_table, ADG_TYPE_ENTITY)
69 enum {
70 PROP_0,
71 PROP_TABLE_DRESS,
72 PROP_HAS_FRAME
75 typedef struct {
76 GCallback callback;
77 gpointer user_data;
78 } AdgClosure;
82 static void _adg_dispose (GObject *object);
83 static void _adg_finalize (GObject *object);
84 static void _adg_get_property (GObject *object,
85 guint param_id,
86 GValue *value,
87 GParamSpec *pspec);
88 static void _adg_set_property (GObject *object,
89 guint param_id,
90 const GValue *value,
91 GParamSpec *pspec);
92 static void _adg_destroy (AdgEntity *entity);
93 static void _adg_global_changed (AdgEntity *entity);
94 static void _adg_local_changed (AdgEntity *entity);
95 static void _adg_invalidate (AdgEntity *entity);
96 static void _adg_arrange (AdgEntity *entity);
97 static void _adg_arrange_grid (AdgEntity *entity);
98 static void _adg_arrange_frame (AdgEntity *entity,
99 const CpmlExtents *extents);
100 static void _adg_render (AdgEntity *entity,
101 cairo_t *cr);
102 static void _adg_propagate (AdgTable *table,
103 const gchar *detailed_signal,
104 ...);
105 static void _adg_foreach_row (AdgTableRow *table_row,
106 const AdgClosure *closure);
107 static void _adg_append_frame (AdgTableCell *table_cell,
108 AdgPath *path);
109 static void _adg_proxy_signal (AdgTableCell *table_cell,
110 AdgProxyData *proxy_data);
111 static gboolean _adg_value_match (gpointer key,
112 gpointer value,
113 gpointer user_data);
116 static void
117 adg_table_class_init(AdgTableClass *klass)
119 GObjectClass *gobject_class;
120 AdgEntityClass *entity_class;
121 GParamSpec *param;
123 gobject_class = (GObjectClass *) klass;
124 entity_class = (AdgEntityClass *) klass;
126 g_type_class_add_private(klass, sizeof(AdgTablePrivate));
128 gobject_class->dispose = _adg_dispose;
129 gobject_class->finalize = _adg_finalize;
130 gobject_class->get_property = _adg_get_property;
131 gobject_class->set_property = _adg_set_property;
133 entity_class->destroy = _adg_destroy;
134 entity_class->global_changed = _adg_global_changed;
135 entity_class->local_changed = _adg_local_changed;
136 entity_class->invalidate = _adg_invalidate;
137 entity_class->arrange = _adg_arrange;
138 entity_class->render = _adg_render;
140 param = adg_param_spec_dress("table-dress",
141 P_("Table Dress"),
142 P_("The dress to use for stroking this entity"),
143 ADG_DRESS_TABLE,
144 G_PARAM_READWRITE);
145 g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param);
147 param = g_param_spec_boolean("has-frame",
148 P_("Has Frame Flag"),
149 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
150 TRUE,
151 G_PARAM_READWRITE);
152 g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param);
155 static void
156 adg_table_init(AdgTable *table)
158 AdgTablePrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(table,
159 ADG_TYPE_TABLE,
160 AdgTablePrivate);
162 data->table_dress = ADG_DRESS_TABLE;
163 data->has_frame = TRUE;
165 data->table_style = NULL;
166 data->grid = NULL;
167 data->frame = NULL;
168 data->rows = NULL;
169 data->cell_names = NULL;
171 table->data = data;
174 static void
175 _adg_dispose(GObject *object)
177 AdgTable *table = (AdgTable *) object;
178 AdgTablePrivate *data = table->data;
180 adg_table_invalidate_grid(table);
182 if (data->frame) {
183 g_object_unref(data->frame);
184 data->frame = NULL;
187 if (data->rows) {
188 adg_table_foreach_cell(table,
189 (GCallback) adg_table_cell_dispose, NULL);
190 data->rows = NULL;
193 if (_ADG_OLD_OBJECT_CLASS->dispose)
194 _ADG_OLD_OBJECT_CLASS->dispose(object);
197 static void
198 _adg_finalize(GObject *object)
200 AdgTable *table;
201 AdgTablePrivate *data;
203 table = (AdgTable *) object;
204 data = table->data;
206 if (data->rows) {
207 g_slist_foreach(data->rows, (GFunc) adg_table_row_free, NULL);
208 g_slist_free(data->rows);
211 if (data->cell_names)
212 g_hash_table_destroy(data->cell_names);
214 if (_ADG_OLD_OBJECT_CLASS->finalize)
215 _ADG_OLD_OBJECT_CLASS->finalize(object);
218 static void
219 _adg_get_property(GObject *object, guint prop_id,
220 GValue *value, GParamSpec *pspec)
222 AdgTablePrivate *data = ((AdgTable *) object)->data;
224 switch (prop_id) {
225 case PROP_TABLE_DRESS:
226 g_value_set_int(value, data->table_dress);
227 break;
228 case PROP_HAS_FRAME:
229 g_value_set_boolean(value, data->has_frame);
230 break;
231 default:
232 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
233 break;
237 static void
238 _adg_set_property(GObject *object, guint prop_id,
239 const GValue *value, GParamSpec *pspec)
241 AdgTablePrivate *data = ((AdgTable *) object)->data;
243 switch (prop_id) {
244 case PROP_TABLE_DRESS:
245 data->table_dress = g_value_get_int(value);
246 break;
247 case PROP_HAS_FRAME:
248 data->has_frame = g_value_get_boolean(value);
249 break;
250 default:
251 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
252 break;
258 * adg_table_new:
260 * Creates a new empty table entity. The #AdgEntity:local-method
261 * property is set by default to #ADG_MIX_DISABLED, that is the
262 * table is not subject to any local transformations.
264 * Returns: the newly created table entity
266 * Since: 1.0
268 AdgTable *
269 adg_table_new(void)
271 return g_object_new(ADG_TYPE_TABLE,
272 "local-method", ADG_MIX_DISABLED,
273 NULL);
277 * adg_table_insert:
278 * @table: an #AdgTable
279 * @table_row: a valid #AdgTableRow
280 * @before_row: (allow-none): an #AdgTableRow or %NULL
282 * Inserts @table_row inside the rows list of @table. If @before_row
283 * is specified, @table_row is inserted before it.
285 * Since: 1.0
287 void
288 adg_table_insert(AdgTable *table, AdgTableRow *table_row,
289 AdgTableRow *before_row)
291 AdgTablePrivate *data;
293 g_return_if_fail(ADG_IS_TABLE(table));
294 g_return_if_fail(table_row != NULL);
296 data = table->data;
298 if (before_row == NULL) {
299 data->rows = g_slist_append(data->rows, table_row);
300 } else {
301 GSList *before = g_slist_find(data->rows, before_row);
303 /* This MUST be present, otherwise something really bad happened */
304 g_return_if_fail(before != NULL);
306 data->rows = g_slist_insert_before(data->rows, before, table_row);
311 * adg_table_remove:
312 * @table: an #AdgTable
313 * @table_row: a valid #AdgTableRow
315 * Removes @table_row from list of rows of @table.
317 * Since: 1.0
319 void
320 adg_table_remove(AdgTable *table, AdgTableRow *table_row)
322 AdgTablePrivate *data;
324 g_return_if_fail(ADG_IS_TABLE(table));
325 g_return_if_fail(table_row != NULL);
327 data = table->data;
328 data->rows = g_slist_remove(data->rows, table_row);
332 * adg_table_foreach:
333 * @table: an #AdgTable
334 * @callback: (scope call): a callback
335 * @user_data: callback user data
337 * Invokes @callback on each row of @table.
338 * The callback should be declared as:
340 * |[
341 * void callback(AdgTableRow *table_row, gpointer user_data);
342 * ]|
344 * Since: 1.0
346 void
347 adg_table_foreach(AdgTable *table, GCallback callback, gpointer user_data)
349 AdgTablePrivate *data = table->data;
351 g_return_if_fail(table != NULL);
352 g_return_if_fail(callback != NULL);
354 g_slist_foreach(data->rows, (GFunc) callback, user_data);
358 * adg_table_foreach_cell:
359 * @table: an #AdgTable
360 * @callback: (scope call): a callback
361 * @user_data: callback user data
363 * Invokes @callback on each cell of @table.
364 * The callback should be declared as:
366 * |[
367 * void callback(AdgTableCell *table_cell, gpointer user_data);
368 * ]|
370 * Since: 1.0
372 void
373 adg_table_foreach_cell(AdgTable *table,
374 GCallback callback, gpointer user_data)
376 AdgClosure closure = { callback, user_data };
377 adg_table_foreach(table, (GCallback) _adg_foreach_row, &closure);
381 * adg_table_set_cell:
382 * @table: an #AdgTable
383 * @name: the name of the cell
384 * @table_cell: the named cell
386 * Binds @table_cell to @name, so it can be accessed by name later
387 * with adg_table_get_cell(). Internally the binding is handled with
388 * an hash table, so accessing the cell this way is O(1).
390 * If @name is %NULL, any binding to @ŧable_cell will be removed.
391 * This is quite inefficient because the whole hash table must be scanned.
393 * If @table_cell is %NULL, the key with @name in the hash table will
394 * be removed.
396 * Both @name and @table_cell cannot be %NULL at the same time.
398 * Since: 1.0
400 void
401 adg_table_set_cell(AdgTable *table, const gchar *name,
402 AdgTableCell *table_cell)
404 AdgTablePrivate *data;
406 g_return_if_fail(ADG_IS_TABLE(table));
407 g_return_if_fail(name != NULL || table_cell != NULL);
409 data = table->data;
411 if (data->cell_names == NULL) {
412 /* Check if trying to remove from an empty hash table */
413 if (name == NULL || table_cell == NULL)
414 return;
416 data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal,
417 g_free, NULL);
420 if (name == NULL) {
421 /* _adg_value_match() will return the key in user_data[1] */
422 gpointer user_data[] = { table_cell, NULL };
423 g_hash_table_find(data->cell_names, _adg_value_match, user_data);
424 g_hash_table_remove(data->cell_names, user_data[1]);
425 } else if (table_cell == NULL) {
426 g_hash_table_remove(data->cell_names, name);
427 } else {
428 g_hash_table_insert(data->cell_names, g_strdup(name), table_cell);
433 * adg_table_get_table_style:
434 * @table: an #AdgTable
436 * Gets the #AdgTableStyle explicitely set on @table. This is a kind
437 * of accessor function: for rendering purpose use adg_entity_style()
438 * instead. The returned object is owned by @table and should not be
439 * freed or modified.
441 * Returns: (transfer none): the requested style or %NULL on errors.
443 * Since: 1.0
445 AdgStyle *
446 adg_table_get_table_style(AdgTable *table)
448 AdgTablePrivate *data;
450 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
452 data = table->data;
453 return (AdgStyle *) data->table_style;
457 * adg_table_get_cell:
458 * @table: an #AdgTable
459 * @name: the name of a cell
461 * Gets the cell named @name inside @table. Only named cells
462 * can be retrieved by this method.
464 * The returned cell is owned by @ŧable and must not be
465 * modified or freed.
467 * Returns: (transfer none): the requested cell or %NULL if not found.
469 * Since: 1.0
471 AdgTableCell *
472 adg_table_get_cell(AdgTable *table, const gchar *name)
474 AdgTablePrivate *data;
476 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
478 data = table->data;
480 if (data->cell_names == NULL)
481 return NULL;
483 return g_hash_table_lookup(data->cell_names, name);
487 * adg_table_set_table_dress:
488 * @table: an #AdgTable
489 * @dress: the new #AdgDress to use
491 * Sets a new table dress for rendering @table. The new dress
492 * must be related to the original dress for this property:
493 * you cannot set a dress used for line styles to a dress
494 * managing fonts.
496 * The check is done by calling adg_dress_are_related() with
497 * @dress and the previous dress as arguments. Check out its
498 * documentation for details on what is a related dress.
500 * Since: 1.0
502 void
503 adg_table_set_table_dress(AdgTable *table, AdgDress dress)
505 g_return_if_fail(ADG_IS_TABLE(table));
506 g_object_set(table, "table-dress", dress, NULL);
510 * adg_table_get_table_dress:
511 * @table: an #AdgTable
513 * Gets the table dress to be used in rendering @table.
515 * Returns: (transfer none): the current table dress.
517 * Since: 1.0
519 AdgDress
520 adg_table_get_table_dress(AdgTable *table)
522 AdgTablePrivate *data;
524 g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED);
526 data = table->data;
528 return data->table_dress;
532 * adg_table_switch_frame:
533 * @table: an #AdgTable
534 * @new_state: the new state of the frame
536 * Sets the #AdgTable:has-frame property: %TRUE will draw a
537 * frame around the whole table using the #AdgTableStyle:frame-dress
538 * dress of the table style.
540 * Since: 1.0
542 void
543 adg_table_switch_frame(AdgTable *table, gboolean new_state)
545 g_return_if_fail(ADG_IS_TABLE(table));
546 g_object_set(table, "has-frame", new_state, NULL);
550 * adg_table_has_frame:
551 * @table: an #AdgTable
553 * Returns the state of the #AdgTable:has-frame property.
555 * Returns: the current state.
557 * Since: 1.0
559 gboolean
560 adg_table_has_frame(AdgTable *table)
562 AdgTablePrivate *data;
564 g_return_val_if_fail(ADG_IS_TABLE(table), FALSE);
566 data = table->data;
568 return data->has_frame;
572 * adg_table_invalidate_grid:
573 * @table: an #AdgTable
575 * <note><para>
576 * This method is only useful in table children implementation.
577 * </para></note>
579 * Clears the internal grid cache, effectively forcing its
580 * regeneration next time the #AdgEntity::arrange signal is emitted.
582 void
583 adg_table_invalidate_grid(AdgTable *table)
585 AdgTablePrivate *data;
587 g_return_if_fail(ADG_IS_TABLE(table));
589 data = table->data;
591 if (data->grid) {
592 g_object_unref(data->grid);
593 data->grid = NULL;
598 static void
599 _adg_destroy(AdgEntity *entity)
601 _adg_propagate((AdgTable *) entity, "destroy");
603 if (_ADG_OLD_ENTITY_CLASS->destroy)
604 _ADG_OLD_ENTITY_CLASS->destroy(entity);
607 static void
608 _adg_global_changed(AdgEntity *entity)
610 if (_ADG_OLD_ENTITY_CLASS->global_changed)
611 _ADG_OLD_ENTITY_CLASS->global_changed(entity);
613 _adg_propagate((AdgTable *) entity, "global-changed");
616 static void
617 _adg_local_changed(AdgEntity *entity)
619 if (_ADG_OLD_ENTITY_CLASS->local_changed)
620 _ADG_OLD_ENTITY_CLASS->local_changed(entity);
622 _adg_propagate((AdgTable *) entity, "local-changed");
625 static void
626 _adg_invalidate(AdgEntity *entity)
628 _adg_propagate((AdgTable *) entity, "invalidate");
631 static void
632 _adg_arrange(AdgEntity *entity)
634 AdgTable *table;
635 AdgTablePrivate *data;
636 CpmlExtents extents = { 0 };
637 CpmlExtents row_layout = { 0 };
638 const CpmlExtents *row_extents;
639 const CpmlPair *spacing;
640 const CpmlPair *size;
641 GSList *row_node;
642 AdgTableRow *row;
644 table = (AdgTable *) entity;
645 data = table->data;
647 /* Resolve the table style */
648 if (data->table_style == NULL)
649 data->table_style = (AdgTableStyle *)
650 adg_entity_style(entity, data->table_dress);
652 spacing = adg_table_style_get_cell_spacing(data->table_style);
654 /* Compute the size of the table */
655 for (row_node = data->rows; row_node; row_node = row_node->next) {
656 row = row_node->data;
657 size = adg_table_row_size_request(row);
659 if (size->x > extents.size.x)
660 extents.size.x = size->x;
661 extents.size.y += size->y;
664 /* Arrange the layout of the table components */
665 row_layout.org.x = extents.org.x;
666 row_layout.org.y = extents.org.y + spacing->y;
667 row_layout.size.x = extents.size.x;
668 row_layout.size.y = -1;
669 for (row_node = data->rows; row_node; row_node = row_node->next) {
670 row = row_node->data;
671 row_extents = adg_table_row_arrange(row, &row_layout);
672 row_layout.org.y += row_extents->size.y + spacing->y;
675 _adg_arrange_grid(entity);
676 _adg_arrange_frame(entity, &extents);
678 extents.is_defined = TRUE;
679 cpml_extents_transform(&extents, adg_entity_get_global_matrix(entity));
680 cpml_extents_transform(&extents, adg_entity_get_local_matrix(entity));
681 adg_entity_set_extents(entity, &extents);
684 static void
685 _adg_arrange_grid(AdgEntity *entity)
687 AdgTable *table;
688 AdgTablePrivate *data;
689 AdgPath *path;
690 AdgTrail *trail;
691 AdgDress dress;
693 table = (AdgTable *) entity;
694 data = table->data;
696 if (data->grid)
697 return;
699 path = adg_path_new();
700 trail = (AdgTrail *) path;
702 adg_table_foreach_cell(table, (GCallback) _adg_append_frame, path);
704 if (!adg_trail_get_extents(trail)->is_defined)
705 return;
707 dress = adg_table_style_get_grid_dress(data->table_style);
708 data->grid = g_object_new(ADG_TYPE_STROKE,
709 "line-dress", dress,
710 "trail", trail,
711 "parent", entity,
712 NULL);
713 adg_entity_arrange((AdgEntity *) data->grid);
716 static void
717 _adg_arrange_frame(AdgEntity *entity, const CpmlExtents *extents)
719 AdgTablePrivate *data;
720 AdgPath *path;
721 AdgTrail *trail;
722 CpmlPair pair;
723 AdgDress dress;
725 data = ((AdgTable *) entity)->data;
727 if (data->frame || !data->has_frame)
728 return;
730 path = adg_path_new();
731 trail = (AdgTrail *) path;
733 cpml_pair_copy(&pair, &extents->org);
734 adg_path_move_to(path, &pair);
735 pair.x += extents->size.x;
736 adg_path_line_to(path, &pair);
737 pair.y += extents->size.y;
738 adg_path_line_to(path, &pair);
739 pair.x -= extents->size.x;
740 adg_path_line_to(path, &pair);
741 adg_path_close(path);
743 dress = adg_table_style_get_frame_dress(data->table_style);
744 data->frame = g_object_new(ADG_TYPE_STROKE,
745 "line-dress", dress,
746 "trail", trail,
747 "parent", entity,
748 NULL);
749 adg_entity_arrange((AdgEntity *) data->frame);
752 static void
753 _adg_render(AdgEntity *entity, cairo_t *cr)
755 AdgTablePrivate *data = ((AdgTable *) entity)->data;
757 adg_style_apply((AdgStyle *) data->table_style, entity, cr);
759 _adg_propagate((AdgTable *) entity, "render", cr);
762 static void
763 _adg_propagate(AdgTable *table, const gchar *detailed_signal, ...)
765 va_list var_copy;
766 AdgTablePrivate *data;
767 AdgProxyData proxy_data;
769 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(table),
770 &proxy_data.signal_id, &proxy_data.detail, FALSE)) {
771 g_return_if_reached();
774 va_start(proxy_data.var_args, detailed_signal);
775 data = table->data;
777 if (data->frame) {
778 G_VA_COPY(var_copy, proxy_data.var_args);
779 g_signal_emit_valist(data->frame, proxy_data.signal_id,
780 proxy_data.detail, var_copy);
783 if (data->grid) {
784 G_VA_COPY(var_copy, proxy_data.var_args);
785 g_signal_emit_valist(data->grid, proxy_data.signal_id,
786 proxy_data.detail, var_copy);
789 adg_table_foreach_cell(table, (GCallback) _adg_proxy_signal, &proxy_data);
790 va_end(proxy_data.var_args);
793 static void
794 _adg_foreach_row(AdgTableRow *table_row, const AdgClosure *closure)
796 adg_table_row_foreach(table_row, closure->callback, closure->user_data);
799 static void
800 _adg_append_frame(AdgTableCell *table_cell, AdgPath *path)
802 CpmlPair pair;
803 const CpmlExtents *extents;
805 if (! adg_table_cell_has_frame(table_cell))
806 return;
808 extents = adg_table_cell_get_extents(table_cell);
810 cpml_pair_copy(&pair, &extents->org);
811 adg_path_move_to(path, &pair);
812 pair.x += extents->size.x;
813 adg_path_line_to(path, &pair);
814 pair.y += extents->size.y;
815 adg_path_line_to(path, &pair);
816 pair.x -= extents->size.x;
817 adg_path_line_to(path, &pair);
818 adg_path_close(path);
821 static void
822 _adg_proxy_signal(AdgTableCell *table_cell, AdgProxyData *proxy_data)
824 AdgEntity *entity;
825 AdgAlignment *alignment;
826 va_list var_copy;
828 entity = adg_table_cell_title(table_cell);
829 if (entity) {
830 alignment = (AdgAlignment *) adg_entity_get_parent(entity);
831 G_VA_COPY(var_copy, proxy_data->var_args);
832 g_signal_emit_valist(alignment, proxy_data->signal_id,
833 proxy_data->detail, var_copy);
836 entity = adg_table_cell_value(table_cell);
837 if (entity) {
838 alignment = (AdgAlignment *) adg_entity_get_parent(entity);
839 G_VA_COPY(var_copy, proxy_data->var_args);
840 g_signal_emit_valist(alignment, proxy_data->signal_id,
841 proxy_data->detail, var_copy);
845 static gboolean
846 _adg_value_match(gpointer key, gpointer value, gpointer user_data)
848 gpointer *array = user_data;
850 if (value == array[0]) {
851 array[1] = key;
852 return TRUE;
854 return FALSE;