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-dress-builtins.h"
55 #include "adg-style.h"
56 #include "adg-table-style.h"
58 #include "adg-stroke.h"
59 #include "adg-container.h"
60 #include "adg-alignment.h"
61 #include "adg-entity-private.h"
63 #include "adg-table.h"
64 #include "adg-table-private.h"
65 #include "adg-table-row.h"
66 #include "adg-table-cell.h"
69 #define _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class)
70 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class)
73 G_DEFINE_TYPE(AdgTable
, adg_table
, ADG_TYPE_ENTITY
)
88 static void _adg_dispose (GObject
*object
);
89 static void _adg_finalize (GObject
*object
);
90 static void _adg_get_property (GObject
*object
,
94 static void _adg_set_property (GObject
*object
,
98 static void _adg_destroy (AdgEntity
*entity
);
99 static void _adg_global_changed (AdgEntity
*entity
);
100 static void _adg_local_changed (AdgEntity
*entity
);
101 static void _adg_invalidate (AdgEntity
*entity
);
102 static void _adg_arrange (AdgEntity
*entity
);
103 static void _adg_arrange_grid (AdgEntity
*entity
);
104 static void _adg_arrange_frame (AdgEntity
*entity
,
105 const CpmlExtents
*extents
);
106 static void _adg_render (AdgEntity
*entity
,
108 static void _adg_propagate (AdgTable
*table
,
109 const gchar
*detailed_signal
,
111 static void _adg_foreach_row (AdgTableRow
*table_row
,
112 const AdgClosure
*closure
);
113 static void _adg_append_frame (AdgTableCell
*table_cell
,
115 static void _adg_proxy_signal (AdgTableCell
*table_cell
,
116 AdgProxyData
*proxy_data
);
117 static gboolean
_adg_value_match (gpointer key
,
123 adg_table_class_init(AdgTableClass
*klass
)
125 GObjectClass
*gobject_class
;
126 AdgEntityClass
*entity_class
;
129 gobject_class
= (GObjectClass
*) klass
;
130 entity_class
= (AdgEntityClass
*) klass
;
132 g_type_class_add_private(klass
, sizeof(AdgTablePrivate
));
134 gobject_class
->dispose
= _adg_dispose
;
135 gobject_class
->finalize
= _adg_finalize
;
136 gobject_class
->get_property
= _adg_get_property
;
137 gobject_class
->set_property
= _adg_set_property
;
139 entity_class
->destroy
= _adg_destroy
;
140 entity_class
->global_changed
= _adg_global_changed
;
141 entity_class
->local_changed
= _adg_local_changed
;
142 entity_class
->invalidate
= _adg_invalidate
;
143 entity_class
->arrange
= _adg_arrange
;
144 entity_class
->render
= _adg_render
;
146 param
= adg_param_spec_dress("table-dress",
148 P_("The dress to use for stroking this entity"),
151 g_object_class_install_property(gobject_class
, PROP_TABLE_DRESS
, param
);
153 param
= g_param_spec_boolean("has-frame",
154 P_("Has Frame Flag"),
155 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
158 g_object_class_install_property(gobject_class
, PROP_HAS_FRAME
, param
);
162 adg_table_init(AdgTable
*table
)
164 AdgTablePrivate
*data
= G_TYPE_INSTANCE_GET_PRIVATE(table
,
167 AdgEntityPrivate
*entity_data
= ((AdgEntity
*) table
)->data
;
169 data
->table_dress
= ADG_DRESS_TABLE
;
170 data
->has_frame
= TRUE
;
172 data
->table_style
= NULL
;
176 data
->cell_names
= NULL
;
180 /* Initialize to custom default some AdgEntity field by directly
181 * accessing the private struct to avoid notify signal emissions
183 entity_data
->local_mix
= ADG_MIX_DISABLED
;
187 _adg_dispose(GObject
*object
)
189 AdgTable
*table
= (AdgTable
*) object
;
190 AdgTablePrivate
*data
= table
->data
;
192 adg_table_invalidate_grid(table
);
195 g_object_unref(data
->frame
);
200 adg_table_foreach_cell(table
,
201 (GCallback
) adg_table_cell_dispose
, NULL
);
205 if (_ADG_OLD_OBJECT_CLASS
->dispose
)
206 _ADG_OLD_OBJECT_CLASS
->dispose(object
);
210 _adg_finalize(GObject
*object
)
213 AdgTablePrivate
*data
;
215 table
= (AdgTable
*) object
;
219 g_slist_foreach(data
->rows
, (GFunc
) adg_table_row_free
, NULL
);
220 g_slist_free(data
->rows
);
223 if (data
->cell_names
)
224 g_hash_table_destroy(data
->cell_names
);
226 if (_ADG_OLD_OBJECT_CLASS
->finalize
)
227 _ADG_OLD_OBJECT_CLASS
->finalize(object
);
231 _adg_get_property(GObject
*object
, guint prop_id
,
232 GValue
*value
, GParamSpec
*pspec
)
234 AdgTablePrivate
*data
= ((AdgTable
*) object
)->data
;
237 case PROP_TABLE_DRESS
:
238 g_value_set_int(value
, data
->table_dress
);
241 g_value_set_boolean(value
, data
->has_frame
);
244 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
250 _adg_set_property(GObject
*object
, guint prop_id
,
251 const GValue
*value
, GParamSpec
*pspec
)
253 AdgTablePrivate
*data
= ((AdgTable
*) object
)->data
;
256 case PROP_TABLE_DRESS
:
257 data
->table_dress
= g_value_get_int(value
);
260 data
->has_frame
= g_value_get_boolean(value
);
263 G_OBJECT_WARN_INVALID_PROPERTY_ID(object
, prop_id
, pspec
);
272 * Creates a new empty table entity.
274 * Returns: the newly created table entity
281 return g_object_new(ADG_TYPE_TABLE
, NULL
);
286 * @table: an #AdgTable
287 * @table_row: a valid #AdgTableRow
288 * @before_row: (allow-none): an #AdgTableRow or %NULL
290 * Inserts @table_row inside the rows list of @table. If @before_row
291 * is specified, @table_row is inserted before it.
296 adg_table_insert(AdgTable
*table
, AdgTableRow
*table_row
,
297 AdgTableRow
*before_row
)
299 AdgTablePrivate
*data
;
301 g_return_if_fail(ADG_IS_TABLE(table
));
302 g_return_if_fail(table_row
!= NULL
);
306 if (before_row
== NULL
) {
307 data
->rows
= g_slist_append(data
->rows
, table_row
);
309 GSList
*before
= g_slist_find(data
->rows
, before_row
);
311 /* This MUST be present, otherwise something really bad happened */
312 g_return_if_fail(before
!= NULL
);
314 data
->rows
= g_slist_insert_before(data
->rows
, before
, table_row
);
320 * @table: an #AdgTable
321 * @table_row: a valid #AdgTableRow
323 * Removes @table_row from list of rows of @table.
328 adg_table_remove(AdgTable
*table
, AdgTableRow
*table_row
)
330 AdgTablePrivate
*data
;
332 g_return_if_fail(ADG_IS_TABLE(table
));
333 g_return_if_fail(table_row
!= NULL
);
336 data
->rows
= g_slist_remove(data
->rows
, table_row
);
341 * @table: an #AdgTable
342 * @callback: (scope call): a callback
343 * @user_data: callback user data
345 * Invokes @callback on each row of @table.
346 * The callback should be declared as:
349 * void callback(AdgTableRow *table_row, gpointer user_data);
355 adg_table_foreach(AdgTable
*table
, GCallback callback
, gpointer user_data
)
357 AdgTablePrivate
*data
= table
->data
;
359 g_return_if_fail(table
!= NULL
);
360 g_return_if_fail(callback
!= NULL
);
362 g_slist_foreach(data
->rows
, (GFunc
) callback
, user_data
);
366 * adg_table_foreach_cell:
367 * @table: an #AdgTable
368 * @callback: (scope call): a callback
369 * @user_data: callback user data
371 * Invokes @callback on each cell of @table.
372 * The callback should be declared as:
375 * void callback(AdgTableCell *table_cell, gpointer user_data);
381 adg_table_foreach_cell(AdgTable
*table
,
382 GCallback callback
, gpointer user_data
)
384 AdgClosure closure
= { callback
, user_data
};
385 adg_table_foreach(table
, (GCallback
) _adg_foreach_row
, &closure
);
389 * adg_table_set_cell:
390 * @table: an #AdgTable
391 * @name: the name of the cell
392 * @table_cell: the named cell
394 * Binds @table_cell to @name, so it can be accessed by name later
395 * with adg_table_get_cell(). Internally the binding is handled with
396 * an hash table, so accessing the cell this way is O(1).
398 * If @name is %NULL, any binding to @ŧable_cell will be removed.
399 * This is quite inefficient because the whole hash table must be scanned.
401 * If @table_cell is %NULL, the key with @name in the hash table will
404 * Both @name and @table_cell cannot be %NULL at the same time.
409 adg_table_set_cell(AdgTable
*table
, const gchar
*name
,
410 AdgTableCell
*table_cell
)
412 AdgTablePrivate
*data
;
414 g_return_if_fail(ADG_IS_TABLE(table
));
415 g_return_if_fail(name
!= NULL
|| table_cell
!= NULL
);
419 if (data
->cell_names
== NULL
) {
420 /* Check if trying to remove from an empty hash table */
421 if (name
== NULL
|| table_cell
== NULL
)
424 data
->cell_names
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
429 /* _adg_value_match() will return the key in user_data[1] */
430 gpointer user_data
[] = { table_cell
, NULL
};
431 g_hash_table_find(data
->cell_names
, _adg_value_match
, user_data
);
432 g_hash_table_remove(data
->cell_names
, user_data
[1]);
433 } else if (table_cell
== NULL
) {
434 g_hash_table_remove(data
->cell_names
, name
);
436 g_hash_table_insert(data
->cell_names
, g_strdup(name
), table_cell
);
441 * adg_table_get_table_style:
442 * @table: an #AdgTable
444 * Gets the #AdgTableStyle explicitely set on @table. This is a kind
445 * of accessor function: for rendering purpose use adg_entity_style()
446 * instead. The returned object is owned by @table and should not be
449 * Returns: (transfer none): the requested style or %NULL on errors.
454 adg_table_get_table_style(AdgTable
*table
)
456 AdgTablePrivate
*data
;
458 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
461 return (AdgStyle
*) data
->table_style
;
465 * adg_table_get_cell:
466 * @table: an #AdgTable
467 * @name: the name of a cell
469 * Gets the cell named @name inside @table. Only named cells
470 * can be retrieved by this method.
472 * The returned cell is owned by @ŧable and must not be
475 * Returns: (transfer none): the requested cell or %NULL if not found.
480 adg_table_get_cell(AdgTable
*table
, const gchar
*name
)
482 AdgTablePrivate
*data
;
484 g_return_val_if_fail(ADG_IS_TABLE(table
), NULL
);
488 if (data
->cell_names
== NULL
)
491 return g_hash_table_lookup(data
->cell_names
, name
);
495 * adg_table_set_table_dress:
496 * @table: an #AdgTable
497 * @dress: the new #AdgDress to use
499 * Sets a new table dress for rendering @table. The new dress
500 * must be related to the original dress for this property:
501 * you cannot set a dress used for line styles to a dress
504 * The check is done by calling adg_dress_are_related() with
505 * @dress and the previous dress as arguments. Check out its
506 * documentation for details on what is a related dress.
511 adg_table_set_table_dress(AdgTable
*table
, AdgDress dress
)
513 g_return_if_fail(ADG_IS_TABLE(table
));
514 g_object_set(table
, "table-dress", dress
, NULL
);
518 * adg_table_get_table_dress:
519 * @table: an #AdgTable
521 * Gets the table dress to be used in rendering @table.
523 * Returns: (transfer none): the current table dress.
528 adg_table_get_table_dress(AdgTable
*table
)
530 AdgTablePrivate
*data
;
532 g_return_val_if_fail(ADG_IS_TABLE(table
), ADG_DRESS_UNDEFINED
);
536 return data
->table_dress
;
540 * adg_table_switch_frame:
541 * @table: an #AdgTable
542 * @new_state: the new state of the frame
544 * Sets the #AdgTable:has-frame property: %TRUE will draw a
545 * frame around the whole table using the #AdgTableStyle:frame-dress
546 * dress of the table style.
551 adg_table_switch_frame(AdgTable
*table
, gboolean new_state
)
553 g_return_if_fail(ADG_IS_TABLE(table
));
554 g_object_set(table
, "has-frame", new_state
, NULL
);
558 * adg_table_has_frame:
559 * @table: an #AdgTable
561 * Returns the state of the #AdgTable:has-frame property.
563 * Returns: the current state.
568 adg_table_has_frame(AdgTable
*table
)
570 AdgTablePrivate
*data
;
572 g_return_val_if_fail(ADG_IS_TABLE(table
), FALSE
);
576 return data
->has_frame
;
580 * adg_table_invalidate_grid:
581 * @table: an #AdgTable
584 * This method is only useful in table children implementation.
587 * Clears the internal grid cache, effectively forcing its
588 * regeneration next time the #AdgEntity::arrange signal is emitted.
591 adg_table_invalidate_grid(AdgTable
*table
)
593 AdgTablePrivate
*data
;
595 g_return_if_fail(ADG_IS_TABLE(table
));
600 g_object_unref(data
->grid
);
607 _adg_destroy(AdgEntity
*entity
)
609 _adg_propagate((AdgTable
*) entity
, "destroy");
611 if (_ADG_OLD_ENTITY_CLASS
->destroy
)
612 _ADG_OLD_ENTITY_CLASS
->destroy(entity
);
616 _adg_global_changed(AdgEntity
*entity
)
618 if (_ADG_OLD_ENTITY_CLASS
->global_changed
)
619 _ADG_OLD_ENTITY_CLASS
->global_changed(entity
);
621 _adg_propagate((AdgTable
*) entity
, "global-changed");
625 _adg_local_changed(AdgEntity
*entity
)
627 if (_ADG_OLD_ENTITY_CLASS
->local_changed
)
628 _ADG_OLD_ENTITY_CLASS
->local_changed(entity
);
630 _adg_propagate((AdgTable
*) entity
, "local-changed");
634 _adg_invalidate(AdgEntity
*entity
)
636 _adg_propagate((AdgTable
*) entity
, "invalidate");
640 _adg_arrange(AdgEntity
*entity
)
643 AdgTablePrivate
*data
;
644 CpmlExtents extents
= { 0 };
645 CpmlExtents row_layout
= { 0 };
646 const CpmlExtents
*row_extents
;
647 const CpmlPair
*spacing
;
648 const CpmlPair
*size
;
652 table
= (AdgTable
*) entity
;
655 /* Resolve the table style */
656 if (data
->table_style
== NULL
)
657 data
->table_style
= (AdgTableStyle
*)
658 adg_entity_style(entity
, data
->table_dress
);
660 spacing
= adg_table_style_get_cell_spacing(data
->table_style
);
662 /* Compute the size of the table */
663 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
664 row
= row_node
->data
;
665 size
= adg_table_row_size_request(row
);
667 if (size
->x
> extents
.size
.x
)
668 extents
.size
.x
= size
->x
;
669 extents
.size
.y
+= size
->y
;
672 /* Arrange the layout of the table components */
673 row_layout
.org
.x
= extents
.org
.x
;
674 row_layout
.org
.y
= extents
.org
.y
+ spacing
->y
;
675 row_layout
.size
.x
= extents
.size
.x
;
676 row_layout
.size
.y
= -1;
677 for (row_node
= data
->rows
; row_node
; row_node
= row_node
->next
) {
678 row
= row_node
->data
;
679 row_extents
= adg_table_row_arrange(row
, &row_layout
);
680 row_layout
.org
.y
+= row_extents
->size
.y
+ spacing
->y
;
683 _adg_arrange_grid(entity
);
684 _adg_arrange_frame(entity
, &extents
);
686 extents
.is_defined
= TRUE
;
687 cpml_extents_transform(&extents
, adg_entity_get_global_matrix(entity
));
688 cpml_extents_transform(&extents
, adg_entity_get_local_matrix(entity
));
689 adg_entity_set_extents(entity
, &extents
);
693 _adg_arrange_grid(AdgEntity
*entity
)
696 AdgTablePrivate
*data
;
701 table
= (AdgTable
*) entity
;
707 path
= adg_path_new();
708 trail
= (AdgTrail
*) path
;
710 adg_table_foreach_cell(table
, (GCallback
) _adg_append_frame
, path
);
712 if (!adg_trail_get_extents(trail
)->is_defined
)
715 dress
= adg_table_style_get_grid_dress(data
->table_style
);
716 data
->grid
= g_object_new(ADG_TYPE_STROKE
,
721 adg_entity_arrange((AdgEntity
*) data
->grid
);
725 _adg_arrange_frame(AdgEntity
*entity
, const CpmlExtents
*extents
)
727 AdgTablePrivate
*data
;
733 data
= ((AdgTable
*) entity
)->data
;
735 if (data
->frame
|| !data
->has_frame
)
738 path
= adg_path_new();
739 trail
= (AdgTrail
*) path
;
741 cpml_pair_copy(&pair
, &extents
->org
);
742 adg_path_move_to(path
, &pair
);
743 pair
.x
+= extents
->size
.x
;
744 adg_path_line_to(path
, &pair
);
745 pair
.y
+= extents
->size
.y
;
746 adg_path_line_to(path
, &pair
);
747 pair
.x
-= extents
->size
.x
;
748 adg_path_line_to(path
, &pair
);
749 adg_path_close(path
);
751 dress
= adg_table_style_get_frame_dress(data
->table_style
);
752 data
->frame
= g_object_new(ADG_TYPE_STROKE
,
757 adg_entity_arrange((AdgEntity
*) data
->frame
);
761 _adg_render(AdgEntity
*entity
, cairo_t
*cr
)
763 AdgTablePrivate
*data
= ((AdgTable
*) entity
)->data
;
765 adg_style_apply((AdgStyle
*) data
->table_style
, entity
, cr
);
767 _adg_propagate((AdgTable
*) entity
, "render", cr
);
771 _adg_propagate(AdgTable
*table
, const gchar
*detailed_signal
, ...)
774 AdgTablePrivate
*data
;
775 AdgProxyData proxy_data
;
777 if (!g_signal_parse_name(detailed_signal
, G_TYPE_FROM_INSTANCE(table
),
778 &proxy_data
.signal_id
, &proxy_data
.detail
, FALSE
)) {
779 g_return_if_reached();
782 va_start(proxy_data
.var_args
, detailed_signal
);
786 G_VA_COPY(var_copy
, proxy_data
.var_args
);
787 g_signal_emit_valist(data
->frame
, proxy_data
.signal_id
,
788 proxy_data
.detail
, var_copy
);
792 G_VA_COPY(var_copy
, proxy_data
.var_args
);
793 g_signal_emit_valist(data
->grid
, proxy_data
.signal_id
,
794 proxy_data
.detail
, var_copy
);
797 adg_table_foreach_cell(table
, (GCallback
) _adg_proxy_signal
, &proxy_data
);
798 va_end(proxy_data
.var_args
);
802 _adg_foreach_row(AdgTableRow
*table_row
, const AdgClosure
*closure
)
804 adg_table_row_foreach(table_row
, closure
->callback
, closure
->user_data
);
808 _adg_append_frame(AdgTableCell
*table_cell
, AdgPath
*path
)
811 const CpmlExtents
*extents
;
813 if (! adg_table_cell_has_frame(table_cell
))
816 extents
= adg_table_cell_get_extents(table_cell
);
818 cpml_pair_copy(&pair
, &extents
->org
);
819 adg_path_move_to(path
, &pair
);
820 pair
.x
+= extents
->size
.x
;
821 adg_path_line_to(path
, &pair
);
822 pair
.y
+= extents
->size
.y
;
823 adg_path_line_to(path
, &pair
);
824 pair
.x
-= extents
->size
.x
;
825 adg_path_line_to(path
, &pair
);
826 adg_path_close(path
);
830 _adg_proxy_signal(AdgTableCell
*table_cell
, AdgProxyData
*proxy_data
)
833 AdgAlignment
*alignment
;
836 entity
= adg_table_cell_title(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
);
844 entity
= adg_table_cell_value(table_cell
);
846 alignment
= (AdgAlignment
*) adg_entity_get_parent(entity
);
847 G_VA_COPY(var_copy
, proxy_data
->var_args
);
848 g_signal_emit_valist(alignment
, proxy_data
->signal_id
,
849 proxy_data
->detail
, var_copy
);
854 _adg_value_match(gpointer key
, gpointer value
, gpointer user_data
)
856 gpointer
*array
= user_data
;
858 if (value
== array
[0]) {