1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2019 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.
32 * By default, the #AdgEntity:local-mix property is set to
33 * #ADG_MIX_DISABLED on #AdgTable entities.
42 * All fields are private and should not be used directly.
43 * Use its public methods instead.
49 #include "adg-internal.h"
51 #include "adg-model.h"
52 #include "adg-trail.h"
53 #include "adg-style.h"
54 #include "adg-table-style.h"
56 #include "adg-stroke.h"
57 #include "adg-container.h"
58 #include "adg-alignment.h"
59 #include "adg-dress.h"
60 #include "adg-param-dress.h"
62 #include "adg-table.h"
63 #include "adg-table-private.h"
64 #include "adg-table-row.h"
65 #include "adg-table-cell.h"
68 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class)
69 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class)
72 G_DEFINE_TYPE_WITH_PRIVATE(AdgTable
, adg_table
, ADG_TYPE_ENTITY
)
87 static void _adg_dispose (GObject
*object
);
88 static void _adg_finalize (GObject
*object
);
89 static void _adg_get_property (GObject
*object
,
93 static void _adg_set_property (GObject
*object
,
97 static void _adg_destroy (AdgEntity
*entity
);
98 static void _adg_global_changed (AdgEntity
*entity
);
99 static void _adg_local_changed (AdgEntity
*entity
);
100 static void _adg_invalidate (AdgEntity
*entity
);
101 static void _adg_arrange (AdgEntity
*entity
);
102 static void _adg_arrange_grid (AdgEntity
*entity
);
103 static void _adg_arrange_frame (AdgEntity
*entity
,
104 const CpmlExtents
*extents
);
105 static void _adg_render (AdgEntity
*entity
,
107 static void _adg_propagate (AdgTable
*table
,
108 const gchar
*detailed_signal
,
110 static void _adg_foreach_row (AdgTableRow
*table_row
,
111 const AdgClosure
*closure
);
112 static void _adg_append_frame (AdgTableCell
*table_cell
,
114 static void _adg_proxy_signal (AdgTableCell
*table_cell
,
115 AdgProxyData
*proxy_data
);
116 static gboolean
_adg_value_match (gpointer key
,
122 adg_table_class_init(AdgTableClass
*klass
)
124 GObjectClass
*gobject_class
;
125 AdgEntityClass
*entity_class
;
128 gobject_class
= (GObjectClass
*) klass
;
129 entity_class
= (AdgEntityClass
*) klass
;
131 gobject_class
->dispose
= _adg_dispose
;
132 gobject_class
->finalize
= _adg_finalize
;
133 gobject_class
->get_property
= _adg_get_property
;
134 gobject_class
->set_property
= _adg_set_property
;
136 entity_class
->destroy
= _adg_destroy
;
137 entity_class
->global_changed
= _adg_global_changed
;
138 entity_class
->local_changed
= _adg_local_changed
;
139 entity_class
->invalidate
= _adg_invalidate
;
140 entity_class
->arrange
= _adg_arrange
;
141 entity_class
->render
= _adg_render
;
143 param
= adg_param_spec_dress("table-dress",
145 P_("The dress to use for stroking this entity"),
148 g_object_class_install_property(gobject_class
, PROP_TABLE_DRESS
, param
);
150 param
= g_param_spec_boolean("has-frame",
151 P_("Has Frame Flag"),
152 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
155 g_object_class_install_property(gobject_class
, PROP_HAS_FRAME
, param
);
159 adg_table_init(AdgTable
*table
)
161 AdgTablePrivate
*data
= adg_table_get_instance_private(table
);
162 data
->table_dress
= ADG_DRESS_TABLE
;
163 data
->has_frame
= TRUE
;
164 data
->table_style
= NULL
;
168 data
->cell_names
= NULL
;
169 adg_entity_set_local_mix((AdgEntity
*) table
, ADG_MIX_DISABLED
);
173 _adg_dispose(GObject
*object
)
175 AdgTable
*table
= (AdgTable
*) object
;
176 AdgTablePrivate
*data
= adg_table_get_instance_private(table
);
178 adg_table_invalidate_grid(table
);
181 g_object_unref(data
->frame
);
186 adg_table_foreach_cell(table
,
187 (GCallback
) adg_table_cell_dispose
, NULL
);
191 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
192 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
196 _adg_finalize(GObject
*object
)
198 AdgTablePrivate
*data
= adg_table_get_instance_private((AdgTable
*) object
);
201 g_slist_foreach(data
->rows
, (GFunc
) adg_table_row_free
, NULL
);
202 g_slist_free(data
->rows
);
205 if (data
->cell_names
)
206 g_hash_table_destroy(data
->cell_names
);
208 if (_ADG_OLD_OBJECT_CLASS
->finalize
)
209 _ADG_OLD_OBJECT_CLASS
->finalize(object
);
213 _adg_get_property(GObject
*object
, guint prop_id
,
214 GValue
*value
, GParamSpec
*pspec
)
216 AdgTablePrivate
*data
= adg_table_get_instance_private((AdgTable
*) object
);
219 case PROP_TABLE_DRESS
:
220 g_value_set_enum(value
, data
->table_dress
);
223 g_value_set_boolean(value
, data
->has_frame
);
226 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
232 _adg_set_property(GObject
*object
, guint prop_id
,
233 const GValue
*value
, GParamSpec
*pspec
)
235 AdgTablePrivate
*data
= adg_table_get_instance_private((AdgTable
*) object
);
238 case PROP_TABLE_DRESS
:
239 data
->table_dress
= g_value_get_enum(value
);
242 data
->has_frame
= g_value_get_boolean(value
);
245 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
254 * Creates a new empty table entity.
256 * Returns: the newly created table entity
263 return g_object_new(ADG_TYPE_TABLE
, NULL
);
268 * @table: an #AdgTable
269 * @table_row: a valid #AdgTableRow
270 * @before_row: (allow-none): an #AdgTableRow or <constant>NULL</constant>
272 * Inserts @table_row inside the rows list of @table. If @before_row
273 * is specified, @table_row is inserted before it.
278 adg_table_insert(AdgTable
*table
, AdgTableRow
*table_row
,
279 AdgTableRow
*before_row
)
281 AdgTablePrivate
*data
;
283 g_return_if_fail(ADG_IS_TABLE(table
));
284 g_return_if_fail(table_row
!= NULL
);
286 data
= adg_table_get_instance_private(table
);
288 if (before_row
== NULL
) {
289 data
->rows
= g_slist_append(data
->rows
, table_row
);
291 GSList
*before
= g_slist_find(data
->rows
, before_row
);
293 /* This MUST be present, otherwise something really bad happened */
294 g_return_if_fail(before
!= NULL
);
296 data
->rows
= g_slist_insert_before(data
->rows
, before
, table_row
);
302 * @table: an #AdgTable
303 * @table_row: a valid #AdgTableRow
305 * Removes @table_row from list of rows of @table.
310 adg_table_remove(AdgTable
*table
, AdgTableRow
*table_row
)
312 AdgTablePrivate
*data
;
314 g_return_if_fail(ADG_IS_TABLE(table
));
315 g_return_if_fail(table_row
!= NULL
);
317 data
= adg_table_get_instance_private(table
);
318 data
->rows
= g_slist_remove(data
->rows
, table_row
);
323 * @table: an #AdgTable
324 * @callback: (scope call): a callback
325 * @user_data: callback user data
327 * Invokes @callback on each row of @table.
328 * The callback should be declared as:
330 * <informalexample><programlisting language="C">
331 * void callback(AdgTableRow *table_row, gpointer user_data);
332 * </programlisting></informalexample>
337 adg_table_foreach(AdgTable
*table
, GCallback callback
, gpointer user_data
)
339 AdgTablePrivate
*data
;
341 g_return_if_fail(table
!= NULL
);
342 g_return_if_fail(callback
!= NULL
);
344 data
= adg_table_get_instance_private(table
);
345 g_slist_foreach(data
->rows
, (GFunc
) callback
, user_data
);
349 * adg_table_foreach_cell:
350 * @table: an #AdgTable
351 * @callback: (scope call): a callback
352 * @user_data: callback user data
354 * Invokes @callback on each cell of @table.
355 * The callback should be declared as:
357 * <informalexample><programlisting language="C">
358 * void callback(AdgTableCell *table_cell, gpointer user_data);
359 * </programlisting></informalexample>
364 adg_table_foreach_cell(AdgTable
*table
,
365 GCallback callback
, gpointer user_data
)
367 AdgClosure closure
= { callback
, user_data
};
368 adg_table_foreach(table
, (GCallback
) _adg_foreach_row
, &closure
);
372 * adg_table_set_cell:
373 * @table: an #AdgTable
374 * @name: the name of the cell
375 * @table_cell: the named cell
377 * Binds @table_cell to @name, so it can be accessed by name later
378 * with adg_table_get_cell(). Internally the binding is handled with
379 * an hash table, so accessing the cell this way is O(1).
381 * If @name is <constant>NULL</constant>, any binding to @table_cell
382 * will be removed. This is quite inefficient because the whole hash
383 * table must be scanned.
385 * If @table_cell is <constant>NULL</constant>, the key with @name
386 * in the hash table will be removed.
388 * Both @name and @table_cell cannot be <constant>NULL</constant> at the same time.
393 adg_table_set_cell(AdgTable
*table
, const gchar
*name
,
394 AdgTableCell
*table_cell
)
396 AdgTablePrivate
*data
;
398 g_return_if_fail(ADG_IS_TABLE(table
));
399 g_return_if_fail(name
!= NULL
|| table_cell
!= NULL
);
401 data
= adg_table_get_instance_private(table
);
403 if (data
->cell_names
== NULL
) {
404 /* Check if trying to remove from an empty hash table */
405 if (name
== NULL
|| table_cell
== NULL
)
408 data
->cell_names
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
413 /* _adg_value_match() will return the key in user_data[1] */
414 gpointer user_data
[] = { table_cell
, NULL
};
415 g_hash_table_find(data
->cell_names
, _adg_value_match
, user_data
);
416 g_hash_table_remove(data
->cell_names
, user_data
[1]);
417 } else if (table_cell
== NULL
) {
418 g_hash_table_remove(data
->cell_names
, name
);
420 g_hash_table_insert(data
->cell_names
, g_strdup(name
), table_cell
);
425 * adg_table_get_table_style:
426 * @table: an #AdgTable
428 * Gets the #AdgTableStyle explicitely set on @table. This is a kind
429 * of accessor function: for rendering purpose use adg_entity_style()
430 * instead. The returned object is owned by @table and should not be
433 * Returns: (transfer none): the requested style or <constant>NULL</constant> on errors.
438 adg_table_get_table_style(AdgTable
*table
)
440 AdgTablePrivate
*data
;
442 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
444 data
= adg_table_get_instance_private(table
);
445 return (AdgStyle
*) data
->table_style
;
449 * adg_table_get_cell:
450 * @table: an #AdgTable
451 * @name: the name of a cell
453 * Gets the cell named @name inside @table. Only named cells
454 * can be retrieved by this method.
456 * The returned cell is owned by @table and must not be
459 * Returns: (transfer none): the requested cell or <constant>NULL</constant> if not found.
464 adg_table_get_cell(AdgTable
*table
, const gchar
*name
)
466 AdgTablePrivate
*data
;
468 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
470 data
= adg_table_get_instance_private(table
);
471 if (data
->cell_names
== NULL
)
474 return g_hash_table_lookup(data
->cell_names
, name
);
478 * adg_table_set_table_dress:
479 * @table: an #AdgTable
480 * @dress: the new #AdgDress to use
482 * Sets a new table dress for rendering @table. The new dress
483 * must be related to the original dress for this property:
484 * you cannot set a dress used for line styles to a dress
487 * The check is done by calling adg_dress_are_related() with
488 * @dress and the previous dress as arguments. Check out its
489 * documentation for details on what is a related dress.
494 adg_table_set_table_dress(AdgTable
*table
, AdgDress dress
)
496 g_return_if_fail(ADG_IS_TABLE(table
));
497 g_object_set(table
, "table-dress", dress
, NULL
);
501 * adg_table_get_table_dress:
502 * @table: an #AdgTable
504 * Gets the table dress to be used in rendering @table.
506 * Returns: (transfer none): the current table dress.
511 adg_table_get_table_dress(AdgTable
*table
)
513 AdgTablePrivate
*data
;
515 g_return_val_if_fail(ADG_IS_TABLE(table
), ADG_DRESS_UNDEFINED
);
517 data
= adg_table_get_instance_private(table
);
518 return data
->table_dress
;
522 * adg_table_switch_frame:
523 * @table: an #AdgTable
524 * @new_state: the new state of the frame
526 * Sets the #AdgTable:has-frame property: <constant>TRUE</constant>
527 * will draw a frame around the whole table using the
528 * #AdgTableStyle:frame-dress dress of the table style.
533 adg_table_switch_frame(AdgTable
*table
, gboolean new_state
)
535 g_return_if_fail(ADG_IS_TABLE(table
));
536 g_object_set(table
, "has-frame", new_state
, NULL
);
540 * adg_table_has_frame:
541 * @table: an #AdgTable
543 * Returns the state of the #AdgTable:has-frame property.
545 * Returns: the current state.
550 adg_table_has_frame(AdgTable
*table
)
552 AdgTablePrivate
*data
;
554 g_return_val_if_fail(ADG_IS_TABLE(table
), FALSE
);
556 data
= adg_table_get_instance_private(table
);
557 return data
->has_frame
;
561 * adg_table_invalidate_grid:
562 * @table: an #AdgTable
565 * This method is only useful in table children implementation.
568 * Clears the internal grid cache, effectively forcing its
569 * regeneration next time the #AdgEntity::arrange signal is emitted.
572 adg_table_invalidate_grid(AdgTable
*table
)
574 AdgTablePrivate
*data
;
576 g_return_if_fail(ADG_IS_TABLE(table
));
578 data
= adg_table_get_instance_private(table
);
580 g_object_unref(data
->grid
);
587 _adg_destroy(AdgEntity
*entity
)
589 _adg_propagate((AdgTable
*) entity
, "destroy");
591 if (_ADG_OLD_ENTITY_CLASS
->destroy
)
592 _ADG_OLD_ENTITY_CLASS
->destroy(entity
);
596 _adg_global_changed(AdgEntity
*entity
)
598 if (_ADG_OLD_ENTITY_CLASS
->global_changed
)
599 _ADG_OLD_ENTITY_CLASS
->global_changed(entity
);
601 _adg_propagate((AdgTable
*) entity
, "global-changed");
605 _adg_local_changed(AdgEntity
*entity
)
607 if (_ADG_OLD_ENTITY_CLASS
->local_changed
)
608 _ADG_OLD_ENTITY_CLASS
->local_changed(entity
);
610 _adg_propagate((AdgTable
*) entity
, "local-changed");
614 _adg_invalidate(AdgEntity
*entity
)
616 _adg_propagate((AdgTable
*) entity
, "invalidate");
620 _adg_arrange(AdgEntity
*entity
)
622 AdgTable
*table
= (AdgTable
*) entity
;
623 AdgTablePrivate
*data
= adg_table_get_instance_private(table
);
624 CpmlExtents extents
= { 0 };
625 CpmlExtents row_layout
;
626 const CpmlExtents
*row_extents
;
627 const CpmlPair
*spacing
;
628 const CpmlPair
*size
;
632 /* Resolve the table style */
633 if (data
->table_style
== NULL
)
634 data
->table_style
= (AdgTableStyle
*)
635 adg_entity_style(entity
, data
->table_dress
);
637 spacing
= adg_table_style_get_cell_spacing(data
->table_style
);
639 /* Compute the size of the table */
640 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
641 row
= row_node
->data
;
642 size
= adg_table_row_size_request(row
);
644 if (size
->x
> extents
.size
.x
)
645 extents
.size
.x
= size
->x
;
646 extents
.size
.y
+= size
->y
;
649 /* Arrange the layout of the table components */
650 row_layout
.is_defined
= 1;
651 row_layout
.org
.x
= extents
.org
.x
;
652 row_layout
.org
.y
= extents
.org
.y
+ spacing
->y
;
653 row_layout
.size
.x
= extents
.size
.x
;
654 row_layout
.size
.y
= -1;
655 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
656 row
= row_node
->data
;
657 row_extents
= adg_table_row_arrange(row
, &row_layout
);
658 row_layout
.org
.y
+= row_extents
->size
.y
+ spacing
->y
;
661 _adg_arrange_grid(entity
);
662 _adg_arrange_frame(entity
, &extents
);
664 extents
.is_defined
= TRUE
;
665 cpml_extents_transform(&extents
, adg_entity_get_global_matrix(entity
));
666 cpml_extents_transform(&extents
, adg_entity_get_local_matrix(entity
));
667 adg_entity_set_extents(entity
, &extents
);
671 _adg_arrange_grid(AdgEntity
*entity
)
673 AdgTable
*table
= (AdgTable
*) entity
;
674 AdgTablePrivate
*data
= adg_table_get_instance_private(table
);
682 path
= adg_path_new();
683 trail
= (AdgTrail
*) path
;
685 adg_table_foreach_cell(table
, (GCallback
) _adg_append_frame
, path
);
687 if (!adg_trail_get_extents(trail
)->is_defined
)
690 dress
= adg_table_style_get_grid_dress(data
->table_style
);
691 data
->grid
= g_object_new(ADG_TYPE_STROKE
,
696 adg_entity_arrange((AdgEntity
*) data
->grid
);
700 _adg_arrange_frame(AdgEntity
*entity
, const CpmlExtents
*extents
)
702 AdgTablePrivate
*data
= adg_table_get_instance_private((AdgTable
*) entity
);
708 if (data
->frame
|| !data
->has_frame
)
711 path
= adg_path_new();
712 trail
= (AdgTrail
*) path
;
714 cpml_pair_copy(&pair
, &extents
->org
);
715 adg_path_move_to(path
, &pair
);
716 pair
.x
+= extents
->size
.x
;
717 adg_path_line_to(path
, &pair
);
718 pair
.y
+= extents
->size
.y
;
719 adg_path_line_to(path
, &pair
);
720 pair
.x
-= extents
->size
.x
;
721 adg_path_line_to(path
, &pair
);
722 adg_path_close(path
);
724 dress
= adg_table_style_get_frame_dress(data
->table_style
);
725 data
->frame
= g_object_new(ADG_TYPE_STROKE
,
730 adg_entity_arrange((AdgEntity
*) data
->frame
);
734 _adg_render(AdgEntity
*entity
, cairo_t
*cr
)
736 AdgTablePrivate
*data
= adg_table_get_instance_private((AdgTable
*) entity
);
738 adg_style_apply((AdgStyle
*) data
->table_style
, entity
, cr
);
740 _adg_propagate((AdgTable
*) entity
, "render", cr
);
744 _adg_propagate(AdgTable
*table
, const gchar
*detailed_signal
, ...)
747 AdgTablePrivate
*data
;
748 AdgProxyData proxy_data
;
750 if (!g_signal_parse_name(detailed_signal
, G_TYPE_FROM_INSTANCE(table
),
751 &proxy_data
.signal_id
, &proxy_data
.detail
, FALSE
)) {
752 g_return_if_reached();
755 va_start(proxy_data
.var_args
, detailed_signal
);
756 data
= adg_table_get_instance_private(table
);
759 G_VA_COPY(var_copy
, proxy_data
.var_args
);
760 g_signal_emit_valist(data
->frame
, proxy_data
.signal_id
,
761 proxy_data
.detail
, var_copy
);
765 G_VA_COPY(var_copy
, proxy_data
.var_args
);
766 g_signal_emit_valist(data
->grid
, proxy_data
.signal_id
,
767 proxy_data
.detail
, var_copy
);
770 adg_table_foreach_cell(table
, (GCallback
) _adg_proxy_signal
, &proxy_data
);
771 va_end(proxy_data
.var_args
);
775 _adg_foreach_row(AdgTableRow
*table_row
, const AdgClosure
*closure
)
777 adg_table_row_foreach(table_row
, closure
->callback
, closure
->user_data
);
781 _adg_append_frame(AdgTableCell
*table_cell
, AdgPath
*path
)
784 const CpmlExtents
*extents
;
786 if (! adg_table_cell_has_frame(table_cell
))
789 extents
= adg_table_cell_get_extents(table_cell
);
791 cpml_pair_copy(&pair
, &extents
->org
);
792 adg_path_move_to(path
, &pair
);
793 pair
.x
+= extents
->size
.x
;
794 adg_path_line_to(path
, &pair
);
795 pair
.y
+= extents
->size
.y
;
796 adg_path_line_to(path
, &pair
);
797 pair
.x
-= extents
->size
.x
;
798 adg_path_line_to(path
, &pair
);
799 adg_path_close(path
);
803 _adg_proxy_signal(AdgTableCell
*table_cell
, AdgProxyData
*proxy_data
)
806 AdgAlignment
*alignment
;
809 entity
= adg_table_cell_title(table_cell
);
811 alignment
= (AdgAlignment
*) adg_entity_get_parent(entity
);
812 G_VA_COPY(var_copy
, proxy_data
->var_args
);
813 g_signal_emit_valist(alignment
, proxy_data
->signal_id
,
814 proxy_data
->detail
, var_copy
);
817 entity
= adg_table_cell_value(table_cell
);
819 alignment
= (AdgAlignment
*) adg_entity_get_parent(entity
);
820 G_VA_COPY(var_copy
, proxy_data
->var_args
);
821 g_signal_emit_valist(alignment
, proxy_data
->signal_id
,
822 proxy_data
->detail
, var_copy
);
827 _adg_value_match(gpointer key
, gpointer value
, gpointer user_data
)
829 gpointer
*array
= user_data
;
831 if (value
== array
[0]) {