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.
32 * By default, the #AdgText: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-dress.h"
54 #include "adg-style.h"
55 #include "adg-table-style.h"
57 #include "adg-stroke.h"
58 #include "adg-container.h"
59 #include "adg-alignment.h"
60 #include "adg-entity-private.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(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 g_type_class_add_private(klass
, sizeof(AdgTablePrivate
));
133 gobject_class
->dispose
= _adg_dispose
;
134 gobject_class
->finalize
= _adg_finalize
;
135 gobject_class
->get_property
= _adg_get_property
;
136 gobject_class
->set_property
= _adg_set_property
;
138 entity_class
->destroy
= _adg_destroy
;
139 entity_class
->global_changed
= _adg_global_changed
;
140 entity_class
->local_changed
= _adg_local_changed
;
141 entity_class
->invalidate
= _adg_invalidate
;
142 entity_class
->arrange
= _adg_arrange
;
143 entity_class
->render
= _adg_render
;
145 param
= adg_param_spec_dress("table-dress",
147 P_("The dress to use for stroking this entity"),
150 g_object_class_install_property(gobject_class
, PROP_TABLE_DRESS
, param
);
152 param
= g_param_spec_boolean("has-frame",
153 P_("Has Frame Flag"),
154 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
157 g_object_class_install_property(gobject_class
, PROP_HAS_FRAME
, param
);
161 adg_table_init(AdgTable
*table
)
163 AdgTablePrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(table
,
166 AdgEntityPrivate
*entity_data
= ((AdgEntity
*) table
)->data
;
168 data
->table_dress
= ADG_DRESS_TABLE
;
169 data
->has_frame
= TRUE
;
171 data
->table_style
= NULL
;
175 data
->cell_names
= NULL
;
179 /* Initialize to custom default some AdgEntity field by directly
180 * accessing the private struct to avoid notify signal emissions
182 entity_data
->local_mix
= ADG_MIX_DISABLED
;
186 _adg_dispose(GObject
*object
)
188 AdgTable
*table
= (AdgTable
*) object
;
189 AdgTablePrivate
*data
= table
->data
;
191 adg_table_invalidate_grid(table
);
194 g_object_unref(data
->frame
);
199 adg_table_foreach_cell(table
,
200 (GCallback
) adg_table_cell_dispose
, NULL
);
204 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
205 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
209 _adg_finalize(GObject
*object
)
212 AdgTablePrivate
*data
;
214 table
= (AdgTable
*) object
;
218 g_slist_foreach(data
->rows
, (GFunc
) adg_table_row_free
, NULL
);
219 g_slist_free(data
->rows
);
222 if (data
->cell_names
)
223 g_hash_table_destroy(data
->cell_names
);
225 if (_ADG_OLD_OBJECT_CLASS
->finalize
)
226 _ADG_OLD_OBJECT_CLASS
->finalize(object
);
230 _adg_get_property(GObject
*object
, guint prop_id
,
231 GValue
*value
, GParamSpec
*pspec
)
233 AdgTablePrivate
*data
= ((AdgTable
*) object
)->data
;
236 case PROP_TABLE_DRESS
:
237 g_value_set_enum(value
, data
->table_dress
);
240 g_value_set_boolean(value
, data
->has_frame
);
243 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
249 _adg_set_property(GObject
*object
, guint prop_id
,
250 const GValue
*value
, GParamSpec
*pspec
)
252 AdgTablePrivate
*data
= ((AdgTable
*) object
)->data
;
255 case PROP_TABLE_DRESS
:
256 data
->table_dress
= g_value_get_enum(value
);
259 data
->has_frame
= g_value_get_boolean(value
);
262 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
271 * Creates a new empty table entity.
273 * Returns: the newly created table entity
280 return g_object_new(ADG_TYPE_TABLE
, NULL
);
285 * @table: an #AdgTable
286 * @table_row: a valid #AdgTableRow
287 * @before_row: (allow-none): an #AdgTableRow or %NULL
289 * Inserts @table_row inside the rows list of @table. If @before_row
290 * is specified, @table_row is inserted before it.
295 adg_table_insert(AdgTable
*table
, AdgTableRow
*table_row
,
296 AdgTableRow
*before_row
)
298 AdgTablePrivate
*data
;
300 g_return_if_fail(ADG_IS_TABLE(table
));
301 g_return_if_fail(table_row
!= NULL
);
305 if (before_row
== NULL
) {
306 data
->rows
= g_slist_append(data
->rows
, table_row
);
308 GSList
*before
= g_slist_find(data
->rows
, before_row
);
310 /* This MUST be present, otherwise something really bad happened */
311 g_return_if_fail(before
!= NULL
);
313 data
->rows
= g_slist_insert_before(data
->rows
, before
, table_row
);
319 * @table: an #AdgTable
320 * @table_row: a valid #AdgTableRow
322 * Removes @table_row from list of rows of @table.
327 adg_table_remove(AdgTable
*table
, AdgTableRow
*table_row
)
329 AdgTablePrivate
*data
;
331 g_return_if_fail(ADG_IS_TABLE(table
));
332 g_return_if_fail(table_row
!= NULL
);
335 data
->rows
= g_slist_remove(data
->rows
, table_row
);
340 * @table: an #AdgTable
341 * @callback: (scope call): a callback
342 * @user_data: callback user data
344 * Invokes @callback on each row of @table.
345 * The callback should be declared as:
348 * void callback(AdgTableRow *table_row, gpointer user_data);
354 adg_table_foreach(AdgTable
*table
, GCallback callback
, gpointer user_data
)
356 AdgTablePrivate
*data
= table
->data
;
358 g_return_if_fail(table
!= NULL
);
359 g_return_if_fail(callback
!= NULL
);
361 g_slist_foreach(data
->rows
, (GFunc
) callback
, user_data
);
365 * adg_table_foreach_cell:
366 * @table: an #AdgTable
367 * @callback: (scope call): a callback
368 * @user_data: callback user data
370 * Invokes @callback on each cell of @table.
371 * The callback should be declared as:
374 * void callback(AdgTableCell *table_cell, gpointer user_data);
380 adg_table_foreach_cell(AdgTable
*table
,
381 GCallback callback
, gpointer user_data
)
383 AdgClosure closure
= { callback
, user_data
};
384 adg_table_foreach(table
, (GCallback
) _adg_foreach_row
, &closure
);
388 * adg_table_set_cell:
389 * @table: an #AdgTable
390 * @name: the name of the cell
391 * @table_cell: the named cell
393 * Binds @table_cell to @name, so it can be accessed by name later
394 * with adg_table_get_cell(). Internally the binding is handled with
395 * an hash table, so accessing the cell this way is O(1).
397 * If @name is %NULL, any binding to @ŧable_cell will be removed.
398 * This is quite inefficient because the whole hash table must be scanned.
400 * If @table_cell is %NULL, the key with @name in the hash table will
403 * Both @name and @table_cell cannot be %NULL at the same time.
408 adg_table_set_cell(AdgTable
*table
, const gchar
*name
,
409 AdgTableCell
*table_cell
)
411 AdgTablePrivate
*data
;
413 g_return_if_fail(ADG_IS_TABLE(table
));
414 g_return_if_fail(name
!= NULL
|| table_cell
!= NULL
);
418 if (data
->cell_names
== NULL
) {
419 /* Check if trying to remove from an empty hash table */
420 if (name
== NULL
|| table_cell
== NULL
)
423 data
->cell_names
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
428 /* _adg_value_match() will return the key in user_data[1] */
429 gpointer user_data
[] = { table_cell
, NULL
};
430 g_hash_table_find(data
->cell_names
, _adg_value_match
, user_data
);
431 g_hash_table_remove(data
->cell_names
, user_data
[1]);
432 } else if (table_cell
== NULL
) {
433 g_hash_table_remove(data
->cell_names
, name
);
435 g_hash_table_insert(data
->cell_names
, g_strdup(name
), table_cell
);
440 * adg_table_get_table_style:
441 * @table: an #AdgTable
443 * Gets the #AdgTableStyle explicitely set on @table. This is a kind
444 * of accessor function: for rendering purpose use adg_entity_style()
445 * instead. The returned object is owned by @table and should not be
448 * Returns: (transfer none): the requested style or %NULL on errors.
453 adg_table_get_table_style(AdgTable
*table
)
455 AdgTablePrivate
*data
;
457 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
460 return (AdgStyle
*) data
->table_style
;
464 * adg_table_get_cell:
465 * @table: an #AdgTable
466 * @name: the name of a cell
468 * Gets the cell named @name inside @table. Only named cells
469 * can be retrieved by this method.
471 * The returned cell is owned by @ŧable and must not be
474 * Returns: (transfer none): the requested cell or %NULL if not found.
479 adg_table_get_cell(AdgTable
*table
, const gchar
*name
)
481 AdgTablePrivate
*data
;
483 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
487 if (data
->cell_names
== NULL
)
490 return g_hash_table_lookup(data
->cell_names
, name
);
494 * adg_table_set_table_dress:
495 * @table: an #AdgTable
496 * @dress: the new #AdgDress to use
498 * Sets a new table dress for rendering @table. The new dress
499 * must be related to the original dress for this property:
500 * you cannot set a dress used for line styles to a dress
503 * The check is done by calling adg_dress_are_related() with
504 * @dress and the previous dress as arguments. Check out its
505 * documentation for details on what is a related dress.
510 adg_table_set_table_dress(AdgTable
*table
, AdgDress dress
)
512 g_return_if_fail(ADG_IS_TABLE(table
));
513 g_object_set(table
, "table-dress", dress
, NULL
);
517 * adg_table_get_table_dress:
518 * @table: an #AdgTable
520 * Gets the table dress to be used in rendering @table.
522 * Returns: (transfer none): the current table dress.
527 adg_table_get_table_dress(AdgTable
*table
)
529 AdgTablePrivate
*data
;
531 g_return_val_if_fail(ADG_IS_TABLE(table
), ADG_DRESS_UNDEFINED
);
535 return data
->table_dress
;
539 * adg_table_switch_frame:
540 * @table: an #AdgTable
541 * @new_state: the new state of the frame
543 * Sets the #AdgTable:has-frame property: %TRUE will draw a
544 * frame around the whole table using the #AdgTableStyle:frame-dress
545 * dress of the table style.
550 adg_table_switch_frame(AdgTable
*table
, gboolean new_state
)
552 g_return_if_fail(ADG_IS_TABLE(table
));
553 g_object_set(table
, "has-frame", new_state
, NULL
);
557 * adg_table_has_frame:
558 * @table: an #AdgTable
560 * Returns the state of the #AdgTable:has-frame property.
562 * Returns: the current state.
567 adg_table_has_frame(AdgTable
*table
)
569 AdgTablePrivate
*data
;
571 g_return_val_if_fail(ADG_IS_TABLE(table
), FALSE
);
575 return data
->has_frame
;
579 * adg_table_invalidate_grid:
580 * @table: an #AdgTable
583 * This method is only useful in table children implementation.
586 * Clears the internal grid cache, effectively forcing its
587 * regeneration next time the #AdgEntity::arrange signal is emitted.
590 adg_table_invalidate_grid(AdgTable
*table
)
592 AdgTablePrivate
*data
;
594 g_return_if_fail(ADG_IS_TABLE(table
));
599 g_object_unref(data
->grid
);
606 _adg_destroy(AdgEntity
*entity
)
608 _adg_propagate((AdgTable
*) entity
, "destroy");
610 if (_ADG_OLD_ENTITY_CLASS
->destroy
)
611 _ADG_OLD_ENTITY_CLASS
->destroy(entity
);
615 _adg_global_changed(AdgEntity
*entity
)
617 if (_ADG_OLD_ENTITY_CLASS
->global_changed
)
618 _ADG_OLD_ENTITY_CLASS
->global_changed(entity
);
620 _adg_propagate((AdgTable
*) entity
, "global-changed");
624 _adg_local_changed(AdgEntity
*entity
)
626 if (_ADG_OLD_ENTITY_CLASS
->local_changed
)
627 _ADG_OLD_ENTITY_CLASS
->local_changed(entity
);
629 _adg_propagate((AdgTable
*) entity
, "local-changed");
633 _adg_invalidate(AdgEntity
*entity
)
635 _adg_propagate((AdgTable
*) entity
, "invalidate");
639 _adg_arrange(AdgEntity
*entity
)
642 AdgTablePrivate
*data
;
643 CpmlExtents extents
= { 0 };
644 CpmlExtents row_layout
= { 0 };
645 const CpmlExtents
*row_extents
;
646 const CpmlPair
*spacing
;
647 const CpmlPair
*size
;
651 table
= (AdgTable
*) entity
;
654 /* Resolve the table style */
655 if (data
->table_style
== NULL
)
656 data
->table_style
= (AdgTableStyle
*)
657 adg_entity_style(entity
, data
->table_dress
);
659 spacing
= adg_table_style_get_cell_spacing(data
->table_style
);
661 /* Compute the size of the table */
662 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
663 row
= row_node
->data
;
664 size
= adg_table_row_size_request(row
);
666 if (size
->x
> extents
.size
.x
)
667 extents
.size
.x
= size
->x
;
668 extents
.size
.y
+= size
->y
;
671 /* Arrange the layout of the table components */
672 row_layout
.org
.x
= extents
.org
.x
;
673 row_layout
.org
.y
= extents
.org
.y
+ spacing
->y
;
674 row_layout
.size
.x
= extents
.size
.x
;
675 row_layout
.size
.y
= -1;
676 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
677 row
= row_node
->data
;
678 row_extents
= adg_table_row_arrange(row
, &row_layout
);
679 row_layout
.org
.y
+= row_extents
->size
.y
+ spacing
->y
;
682 _adg_arrange_grid(entity
);
683 _adg_arrange_frame(entity
, &extents
);
685 extents
.is_defined
= TRUE
;
686 cpml_extents_transform(&extents
, adg_entity_get_global_matrix(entity
));
687 cpml_extents_transform(&extents
, adg_entity_get_local_matrix(entity
));
688 adg_entity_set_extents(entity
, &extents
);
692 _adg_arrange_grid(AdgEntity
*entity
)
695 AdgTablePrivate
*data
;
700 table
= (AdgTable
*) entity
;
706 path
= adg_path_new();
707 trail
= (AdgTrail
*) path
;
709 adg_table_foreach_cell(table
, (GCallback
) _adg_append_frame
, path
);
711 if (!adg_trail_get_extents(trail
)->is_defined
)
714 dress
= adg_table_style_get_grid_dress(data
->table_style
);
715 data
->grid
= g_object_new(ADG_TYPE_STROKE
,
720 adg_entity_arrange((AdgEntity
*) data
->grid
);
724 _adg_arrange_frame(AdgEntity
*entity
, const CpmlExtents
*extents
)
726 AdgTablePrivate
*data
;
732 data
= ((AdgTable
*) entity
)->data
;
734 if (data
->frame
|| !data
->has_frame
)
737 path
= adg_path_new();
738 trail
= (AdgTrail
*) path
;
740 cpml_pair_copy(&pair
, &extents
->org
);
741 adg_path_move_to(path
, &pair
);
742 pair
.x
+= extents
->size
.x
;
743 adg_path_line_to(path
, &pair
);
744 pair
.y
+= extents
->size
.y
;
745 adg_path_line_to(path
, &pair
);
746 pair
.x
-= extents
->size
.x
;
747 adg_path_line_to(path
, &pair
);
748 adg_path_close(path
);
750 dress
= adg_table_style_get_frame_dress(data
->table_style
);
751 data
->frame
= g_object_new(ADG_TYPE_STROKE
,
756 adg_entity_arrange((AdgEntity
*) data
->frame
);
760 _adg_render(AdgEntity
*entity
, cairo_t
*cr
)
762 AdgTablePrivate
*data
= ((AdgTable
*) entity
)->data
;
764 adg_style_apply((AdgStyle
*) data
->table_style
, entity
, cr
);
766 _adg_propagate((AdgTable
*) entity
, "render", cr
);
770 _adg_propagate(AdgTable
*table
, const gchar
*detailed_signal
, ...)
773 AdgTablePrivate
*data
;
774 AdgProxyData proxy_data
;
776 if (!g_signal_parse_name(detailed_signal
, G_TYPE_FROM_INSTANCE(table
),
777 &proxy_data
.signal_id
, &proxy_data
.detail
, FALSE
)) {
778 g_return_if_reached();
781 va_start(proxy_data
.var_args
, detailed_signal
);
785 G_VA_COPY(var_copy
, proxy_data
.var_args
);
786 g_signal_emit_valist(data
->frame
, proxy_data
.signal_id
,
787 proxy_data
.detail
, var_copy
);
791 G_VA_COPY(var_copy
, proxy_data
.var_args
);
792 g_signal_emit_valist(data
->grid
, proxy_data
.signal_id
,
793 proxy_data
.detail
, var_copy
);
796 adg_table_foreach_cell(table
, (GCallback
) _adg_proxy_signal
, &proxy_data
);
797 va_end(proxy_data
.var_args
);
801 _adg_foreach_row(AdgTableRow
*table_row
, const AdgClosure
*closure
)
803 adg_table_row_foreach(table_row
, closure
->callback
, closure
->user_data
);
807 _adg_append_frame(AdgTableCell
*table_cell
, AdgPath
*path
)
810 const CpmlExtents
*extents
;
812 if (! adg_table_cell_has_frame(table_cell
))
815 extents
= adg_table_cell_get_extents(table_cell
);
817 cpml_pair_copy(&pair
, &extents
->org
);
818 adg_path_move_to(path
, &pair
);
819 pair
.x
+= extents
->size
.x
;
820 adg_path_line_to(path
, &pair
);
821 pair
.y
+= extents
->size
.y
;
822 adg_path_line_to(path
, &pair
);
823 pair
.x
-= extents
->size
.x
;
824 adg_path_line_to(path
, &pair
);
825 adg_path_close(path
);
829 _adg_proxy_signal(AdgTableCell
*table_cell
, AdgProxyData
*proxy_data
)
832 AdgAlignment
*alignment
;
835 entity
= adg_table_cell_title(table_cell
);
837 alignment
= (AdgAlignment
*) adg_entity_get_parent(entity
);
838 G_VA_COPY(var_copy
, proxy_data
->var_args
);
839 g_signal_emit_valist(alignment
, proxy_data
->signal_id
,
840 proxy_data
->detail
, var_copy
);
843 entity
= adg_table_cell_value(table_cell
);
845 alignment
= (AdgAlignment
*) adg_entity_get_parent(entity
);
846 G_VA_COPY(var_copy
, proxy_data
->var_args
);
847 g_signal_emit_valist(alignment
, proxy_data
->signal_id
,
848 proxy_data
->detail
, var_copy
);
853 _adg_value_match(gpointer key
, gpointer value
, gpointer user_data
)
855 gpointer
*array
= user_data
;
857 if (value
== array
[0]) {