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.
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.
37 * All fields are private and should not be used directly.
38 * Use its public methods instead.
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"
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
)
82 static void _adg_dispose (GObject
*object
);
83 static void _adg_finalize (GObject
*object
);
84 static void _adg_get_property (GObject
*object
,
88 static void _adg_set_property (GObject
*object
,
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
,
102 static void _adg_propagate (AdgTable
*table
,
103 const gchar
*detailed_signal
,
105 static void _adg_foreach_row (AdgTableRow
*table_row
,
106 const AdgClosure
*closure
);
107 static void _adg_append_frame (AdgTableCell
*table_cell
,
109 static void _adg_proxy_signal (AdgTableCell
*table_cell
,
110 AdgProxyData
*proxy_data
);
111 static gboolean
_adg_value_match (gpointer key
,
117 adg_table_class_init(AdgTableClass
*klass
)
119 GObjectClass
*gobject_class
;
120 AdgEntityClass
*entity_class
;
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",
142 P_("The dress to use for stroking this entity"),
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"),
152 g_object_class_install_property(gobject_class
, PROP_HAS_FRAME
, param
);
156 adg_table_init(AdgTable
*table
)
158 AdgTablePrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(table
,
162 data
->table_dress
= ADG_DRESS_TABLE
;
163 data
->has_frame
= TRUE
;
165 data
->table_style
= NULL
;
169 data
->cell_names
= NULL
;
175 _adg_dispose(GObject
*object
)
177 AdgTable
*table
= (AdgTable
*) object
;
178 AdgTablePrivate
*data
= table
->data
;
180 adg_table_invalidate_grid(table
);
183 g_object_unref(data
->frame
);
188 adg_table_foreach_cell(table
,
189 (GCallback
) adg_table_cell_dispose
, NULL
);
193 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
194 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
198 _adg_finalize(GObject
*object
)
201 AdgTablePrivate
*data
;
203 table
= (AdgTable
*) object
;
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
);
219 _adg_get_property(GObject
*object
, guint prop_id
,
220 GValue
*value
, GParamSpec
*pspec
)
222 AdgTablePrivate
*data
= ((AdgTable
*) object
)->data
;
225 case PROP_TABLE_DRESS
:
226 g_value_set_int(value
, data
->table_dress
);
229 g_value_set_boolean(value
, data
->has_frame
);
232 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
238 _adg_set_property(GObject
*object
, guint prop_id
,
239 const GValue
*value
, GParamSpec
*pspec
)
241 AdgTablePrivate
*data
= ((AdgTable
*) object
)->data
;
244 case PROP_TABLE_DRESS
:
245 data
->table_dress
= g_value_get_int(value
);
248 data
->has_frame
= g_value_get_boolean(value
);
251 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
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
271 return g_object_new(ADG_TYPE_TABLE
,
272 "local-method", ADG_MIX_DISABLED
,
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.
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
);
298 if (before_row
== NULL
) {
299 data
->rows
= g_slist_append(data
->rows
, table_row
);
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
);
312 * @table: an #AdgTable
313 * @table_row: a valid #AdgTableRow
315 * Removes @table_row from list of rows of @table.
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
);
328 data
->rows
= g_slist_remove(data
->rows
, table_row
);
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:
341 * void callback(AdgTableRow *table_row, gpointer user_data);
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:
367 * void callback(AdgTableCell *table_cell, gpointer user_data);
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
396 * Both @name and @table_cell cannot be %NULL at the same time.
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
);
411 if (data
->cell_names
== NULL
) {
412 /* Check if trying to remove from an empty hash table */
413 if (name
== NULL
|| table_cell
== NULL
)
416 data
->cell_names
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
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
);
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
441 * Returns: (transfer none): the requested style or %NULL on errors.
446 adg_table_get_table_style(AdgTable
*table
)
448 AdgTablePrivate
*data
;
450 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
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
467 * Returns: (transfer none): the requested cell or %NULL if not found.
472 adg_table_get_cell(AdgTable
*table
, const gchar
*name
)
474 AdgTablePrivate
*data
;
476 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
480 if (data
->cell_names
== 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
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.
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.
520 adg_table_get_table_dress(AdgTable
*table
)
522 AdgTablePrivate
*data
;
524 g_return_val_if_fail(ADG_IS_TABLE(table
), ADG_DRESS_UNDEFINED
);
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.
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.
560 adg_table_has_frame(AdgTable
*table
)
562 AdgTablePrivate
*data
;
564 g_return_val_if_fail(ADG_IS_TABLE(table
), FALSE
);
568 return data
->has_frame
;
572 * adg_table_invalidate_grid:
573 * @table: an #AdgTable
576 * This method is only useful in table children implementation.
579 * Clears the internal grid cache, effectively forcing its
580 * regeneration next time the #AdgEntity::arrange signal is emitted.
583 adg_table_invalidate_grid(AdgTable
*table
)
585 AdgTablePrivate
*data
;
587 g_return_if_fail(ADG_IS_TABLE(table
));
592 g_object_unref(data
->grid
);
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
);
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");
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");
626 _adg_invalidate(AdgEntity
*entity
)
628 _adg_propagate((AdgTable
*) entity
, "invalidate");
632 _adg_arrange(AdgEntity
*entity
)
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
;
644 table
= (AdgTable
*) entity
;
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
);
685 _adg_arrange_grid(AdgEntity
*entity
)
688 AdgTablePrivate
*data
;
693 table
= (AdgTable
*) entity
;
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
)
707 dress
= adg_table_style_get_grid_dress(data
->table_style
);
708 data
->grid
= g_object_new(ADG_TYPE_STROKE
,
713 adg_entity_arrange((AdgEntity
*) data
->grid
);
717 _adg_arrange_frame(AdgEntity
*entity
, const CpmlExtents
*extents
)
719 AdgTablePrivate
*data
;
725 data
= ((AdgTable
*) entity
)->data
;
727 if (data
->frame
|| !data
->has_frame
)
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
,
749 adg_entity_arrange((AdgEntity
*) data
->frame
);
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
);
763 _adg_propagate(AdgTable
*table
, const gchar
*detailed_signal
, ...)
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
);
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
);
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
);
794 _adg_foreach_row(AdgTableRow
*table_row
, const AdgClosure
*closure
)
796 adg_table_row_foreach(table_row
, closure
->callback
, closure
->user_data
);
800 _adg_append_frame(AdgTableCell
*table_cell
, AdgPath
*path
)
803 const CpmlExtents
*extents
;
805 if (! adg_table_cell_has_frame(table_cell
))
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
);
822 _adg_proxy_signal(AdgTableCell
*table_cell
, AdgProxyData
*proxy_data
)
825 AdgAlignment
*alignment
;
828 entity
= adg_table_cell_title(table_cell
);
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
);
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
);
846 _adg_value_match(gpointer key
, gpointer value
, gpointer user_data
)
848 gpointer
*array
= user_data
;
850 if (value
== array
[0]) {