1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007-2021 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: (nullable): the name of the cell
375 * @table_cell: (transfer none) (nullable): 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 g_hash_table_foreach_remove(data
->cell_names
, _adg_value_match
, table_cell
);
414 } else if (table_cell
== NULL
) {
415 g_hash_table_remove(data
->cell_names
, name
);
417 g_hash_table_insert(data
->cell_names
, g_strdup(name
), table_cell
);
422 * adg_table_get_table_style:
423 * @table: an #AdgTable
425 * Gets the #AdgTableStyle explicitely set on @table. This is a kind
426 * of accessor function: for rendering purpose use adg_entity_style()
427 * instead. The returned object is owned by @table and should not be
430 * Returns: (transfer none): the requested style or <constant>NULL</constant> on errors.
435 adg_table_get_table_style(AdgTable
*table
)
437 AdgTablePrivate
*data
;
439 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
441 data
= adg_table_get_instance_private(table
);
442 return (AdgStyle
*) data
->table_style
;
446 * adg_table_get_cell:
447 * @table: an #AdgTable
448 * @name: the name of a cell
450 * Gets the cell named @name inside @table. Only named cells
451 * can be retrieved by this method.
453 * The returned cell is owned by @table and must not be
456 * Returns: (transfer none): the requested cell or <constant>NULL</constant> if not found.
461 adg_table_get_cell(AdgTable
*table
, const gchar
*name
)
463 AdgTablePrivate
*data
;
465 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
467 data
= adg_table_get_instance_private(table
);
468 if (data
->cell_names
== NULL
)
471 return g_hash_table_lookup(data
->cell_names
, name
);
475 * adg_table_set_table_dress:
476 * @table: an #AdgTable
477 * @dress: the new #AdgDress to use
479 * Sets a new table dress for rendering @table. The new dress
480 * must be related to the original dress for this property:
481 * you cannot set a dress used for line styles to a dress
484 * The check is done by calling adg_dress_are_related() with
485 * @dress and the previous dress as arguments. Check out its
486 * documentation for details on what is a related dress.
491 adg_table_set_table_dress(AdgTable
*table
, AdgDress dress
)
493 g_return_if_fail(ADG_IS_TABLE(table
));
494 g_object_set(table
, "table-dress", dress
, NULL
);
498 * adg_table_get_table_dress:
499 * @table: an #AdgTable
501 * Gets the table dress to be used in rendering @table.
503 * Returns: (transfer none): the current table dress.
508 adg_table_get_table_dress(AdgTable
*table
)
510 AdgTablePrivate
*data
;
512 g_return_val_if_fail(ADG_IS_TABLE(table
), ADG_DRESS_UNDEFINED
);
514 data
= adg_table_get_instance_private(table
);
515 return data
->table_dress
;
519 * adg_table_switch_frame:
520 * @table: an #AdgTable
521 * @new_state: the new state of the frame
523 * Sets the #AdgTable:has-frame property: <constant>TRUE</constant>
524 * will draw a frame around the whole table using the
525 * #AdgTableStyle:frame-dress dress of the table style.
530 adg_table_switch_frame(AdgTable
*table
, gboolean new_state
)
532 g_return_if_fail(ADG_IS_TABLE(table
));
533 g_object_set(table
, "has-frame", new_state
, NULL
);
537 * adg_table_has_frame:
538 * @table: an #AdgTable
540 * Returns the state of the #AdgTable:has-frame property.
542 * Returns: the current state.
547 adg_table_has_frame(AdgTable
*table
)
549 AdgTablePrivate
*data
;
551 g_return_val_if_fail(ADG_IS_TABLE(table
), FALSE
);
553 data
= adg_table_get_instance_private(table
);
554 return data
->has_frame
;
558 * adg_table_invalidate_grid:
559 * @table: an #AdgTable
562 * This method is only useful in table children implementation.
565 * Clears the internal grid cache, effectively forcing its
566 * regeneration next time the #AdgEntity::arrange signal is emitted.
569 adg_table_invalidate_grid(AdgTable
*table
)
571 AdgTablePrivate
*data
;
573 g_return_if_fail(ADG_IS_TABLE(table
));
575 data
= adg_table_get_instance_private(table
);
577 g_object_unref(data
->grid
);
584 _adg_destroy(AdgEntity
*entity
)
586 _adg_propagate((AdgTable
*) entity
, "destroy");
588 if (_ADG_OLD_ENTITY_CLASS
->destroy
)
589 _ADG_OLD_ENTITY_CLASS
->destroy(entity
);
593 _adg_global_changed(AdgEntity
*entity
)
595 if (_ADG_OLD_ENTITY_CLASS
->global_changed
)
596 _ADG_OLD_ENTITY_CLASS
->global_changed(entity
);
598 _adg_propagate((AdgTable
*) entity
, "global-changed");
602 _adg_local_changed(AdgEntity
*entity
)
604 if (_ADG_OLD_ENTITY_CLASS
->local_changed
)
605 _ADG_OLD_ENTITY_CLASS
->local_changed(entity
);
607 _adg_propagate((AdgTable
*) entity
, "local-changed");
611 _adg_invalidate(AdgEntity
*entity
)
613 _adg_propagate((AdgTable
*) entity
, "invalidate");
617 _adg_arrange(AdgEntity
*entity
)
619 AdgTable
*table
= (AdgTable
*) entity
;
620 AdgTablePrivate
*data
= adg_table_get_instance_private(table
);
621 CpmlExtents extents
= { 0 };
622 CpmlExtents row_layout
;
623 const CpmlExtents
*row_extents
;
624 const CpmlPair
*spacing
;
625 const CpmlPair
*size
;
629 /* Resolve the table style */
630 if (data
->table_style
== NULL
)
631 data
->table_style
= (AdgTableStyle
*)
632 adg_entity_style(entity
, data
->table_dress
);
634 spacing
= adg_table_style_get_cell_spacing(data
->table_style
);
636 /* Compute the size of the table */
637 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
638 row
= row_node
->data
;
639 size
= adg_table_row_size_request(row
);
641 if (size
->x
> extents
.size
.x
)
642 extents
.size
.x
= size
->x
;
643 extents
.size
.y
+= size
->y
;
646 /* Arrange the layout of the table components */
647 row_layout
.is_defined
= 1;
648 row_layout
.org
.x
= extents
.org
.x
;
649 row_layout
.org
.y
= extents
.org
.y
+ spacing
->y
;
650 row_layout
.size
.x
= extents
.size
.x
;
651 row_layout
.size
.y
= -1;
652 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
653 row
= row_node
->data
;
654 row_extents
= adg_table_row_arrange(row
, &row_layout
);
655 row_layout
.org
.y
+= row_extents
->size
.y
+ spacing
->y
;
658 _adg_arrange_grid(entity
);
659 _adg_arrange_frame(entity
, &extents
);
661 extents
.is_defined
= TRUE
;
662 cpml_extents_transform(&extents
, adg_entity_get_global_matrix(entity
));
663 cpml_extents_transform(&extents
, adg_entity_get_local_matrix(entity
));
664 adg_entity_set_extents(entity
, &extents
);
668 _adg_arrange_grid(AdgEntity
*entity
)
670 AdgTable
*table
= (AdgTable
*) entity
;
671 AdgTablePrivate
*data
= adg_table_get_instance_private(table
);
679 path
= adg_path_new();
680 trail
= (AdgTrail
*) path
;
682 adg_table_foreach_cell(table
, (GCallback
) _adg_append_frame
, path
);
684 if (!adg_trail_get_extents(trail
)->is_defined
)
687 dress
= adg_table_style_get_grid_dress(data
->table_style
);
688 data
->grid
= g_object_new(ADG_TYPE_STROKE
,
693 adg_entity_arrange((AdgEntity
*) data
->grid
);
697 _adg_arrange_frame(AdgEntity
*entity
, const CpmlExtents
*extents
)
699 AdgTablePrivate
*data
= adg_table_get_instance_private((AdgTable
*) entity
);
705 if (data
->frame
|| !data
->has_frame
)
708 path
= adg_path_new();
709 trail
= (AdgTrail
*) path
;
711 cpml_pair_copy(&pair
, &extents
->org
);
712 adg_path_move_to(path
, &pair
);
713 pair
.x
+= extents
->size
.x
;
714 adg_path_line_to(path
, &pair
);
715 pair
.y
+= extents
->size
.y
;
716 adg_path_line_to(path
, &pair
);
717 pair
.x
-= extents
->size
.x
;
718 adg_path_line_to(path
, &pair
);
719 adg_path_close(path
);
721 dress
= adg_table_style_get_frame_dress(data
->table_style
);
722 data
->frame
= g_object_new(ADG_TYPE_STROKE
,
727 adg_entity_arrange((AdgEntity
*) data
->frame
);
731 _adg_render(AdgEntity
*entity
, cairo_t
*cr
)
733 AdgTablePrivate
*data
= adg_table_get_instance_private((AdgTable
*) entity
);
735 adg_style_apply((AdgStyle
*) data
->table_style
, entity
, cr
);
737 _adg_propagate((AdgTable
*) entity
, "render", cr
);
741 _adg_propagate(AdgTable
*table
, const gchar
*detailed_signal
, ...)
744 AdgTablePrivate
*data
;
745 AdgProxyData proxy_data
;
747 if (!g_signal_parse_name(detailed_signal
, G_TYPE_FROM_INSTANCE(table
),
748 &proxy_data
.signal_id
, &proxy_data
.detail
, FALSE
)) {
749 g_return_if_reached();
752 va_start(proxy_data
.var_args
, detailed_signal
);
753 data
= adg_table_get_instance_private(table
);
756 G_VA_COPY(var_copy
, proxy_data
.var_args
);
757 g_signal_emit_valist(data
->frame
, proxy_data
.signal_id
,
758 proxy_data
.detail
, var_copy
);
762 G_VA_COPY(var_copy
, proxy_data
.var_args
);
763 g_signal_emit_valist(data
->grid
, proxy_data
.signal_id
,
764 proxy_data
.detail
, var_copy
);
767 adg_table_foreach_cell(table
, (GCallback
) _adg_proxy_signal
, &proxy_data
);
768 va_end(proxy_data
.var_args
);
772 _adg_foreach_row(AdgTableRow
*table_row
, const AdgClosure
*closure
)
774 adg_table_row_foreach(table_row
, closure
->callback
, closure
->user_data
);
778 _adg_append_frame(AdgTableCell
*table_cell
, AdgPath
*path
)
781 const CpmlExtents
*extents
;
783 if (! adg_table_cell_has_frame(table_cell
))
786 extents
= adg_table_cell_get_extents(table_cell
);
788 cpml_pair_copy(&pair
, &extents
->org
);
789 adg_path_move_to(path
, &pair
);
790 pair
.x
+= extents
->size
.x
;
791 adg_path_line_to(path
, &pair
);
792 pair
.y
+= extents
->size
.y
;
793 adg_path_line_to(path
, &pair
);
794 pair
.x
-= extents
->size
.x
;
795 adg_path_line_to(path
, &pair
);
796 adg_path_close(path
);
800 _adg_proxy_signal(AdgTableCell
*table_cell
, AdgProxyData
*proxy_data
)
803 AdgAlignment
*alignment
;
806 entity
= adg_table_cell_title(table_cell
);
808 alignment
= (AdgAlignment
*) adg_entity_get_parent(entity
);
809 G_VA_COPY(var_copy
, proxy_data
->var_args
);
810 g_signal_emit_valist(alignment
, proxy_data
->signal_id
,
811 proxy_data
->detail
, var_copy
);
814 entity
= adg_table_cell_value(table_cell
);
816 alignment
= (AdgAlignment
*) adg_entity_get_parent(entity
);
817 G_VA_COPY(var_copy
, proxy_data
->var_args
);
818 g_signal_emit_valist(alignment
, proxy_data
->signal_id
,
819 proxy_data
->detail
, var_copy
);
824 _adg_value_match(gpointer key
, gpointer value
, gpointer user_data
)
826 return value
== user_data
;