[AdgPath] Binary operator after path close now allowed
[adg.git] / adg / adg-table.c
blobf30d07793a3222ea6f34ea5ff0a90e609b49d9b4
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009 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.
21 /**
22 * SECTION:adg-table
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 its internal model any number
29 * of row using adg_table_row_new() or adg_table_row_new_before().
31 * Every row should be segmented with different cells by using
32 * adg_table_cell_new() or adg_table_cell_new_before(). Any cell can
33 * be filled with a title and a value: the font to be used will be
34 * picked up from the #AdgTableStyle got by resolving the
35 * #AdgTable:table-dress property.
36 **/
38 /**
39 * AdgTable:
41 * All fields are private and should not be used directly.
42 * Use its public methods instead.
43 **/
45 /**
46 * AdgTableRow:
48 * An opaque structure referring to a row of an #AdgTable. Any
49 * table can have an unlimited number of rows.
50 **/
52 /**
53 * AdgTableCell:
55 * An opaque structure referring to the cell of an #AdgTableRow.
56 * Any row can have an unlimited number of cells.
57 **/
60 #include "adg-table.h"
61 #include "adg-table-private.h"
62 #include "adg-dress-builtins.h"
63 #include "adg-path.h"
64 #include "adg-line-style.h"
65 #include "adg-toy-text.h"
66 #include "adg-intl.h"
68 #define PARENT_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class)
69 #define PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class)
72 enum {
73 PROP_0,
74 PROP_TABLE_DRESS,
75 PROP_HAS_FRAME
78 static void dispose (GObject *object);
79 static void finalize (GObject *object);
80 static void get_property (GObject *object,
81 guint param_id,
82 GValue *value,
83 GParamSpec *pspec);
84 static void set_property (GObject *object,
85 guint param_id,
86 const GValue *value,
87 GParamSpec *pspec);
88 static void global_changed (AdgEntity *entity);
89 static void local_changed (AdgEntity *entity);
90 static void invalidate (AdgEntity *entity);
91 static void arrange (AdgEntity *entity);
92 static void arrange_grid (AdgEntity *entity);
93 static void arrange_frame (AdgEntity *entity);
94 static void render (AdgEntity *entity,
95 cairo_t *cr);
96 static gboolean switch_frame (AdgTable *table,
97 gboolean state);
98 static void propagate (AdgTable *table,
99 const gchar *detailed_signal,
100 ...);
101 static AdgTableRow * row_new (AdgTable *table,
102 AdgTableRow *before_row);
103 static void row_arrange_size (AdgTableRow *row);
104 static void row_arrange (AdgTableRow *row);
105 static void row_dispose (AdgTableRow *row);
106 static void row_free (AdgTableRow *row);
107 static AdgTableCell * cell_new (AdgTableRow *row,
108 AdgTableCell *before_cell,
109 gdouble width,
110 gboolean has_frame,
111 const gchar *name,
112 AdgEntity *title,
113 AdgEntity *value);
114 static void cell_arrange_size (AdgTableCell *cell);
115 static void cell_arrange (AdgTableCell *cell);
116 static void cell_dispose (AdgTableCell *cell);
117 static void cell_free (AdgTableCell *cell);
118 static gboolean value_match (gpointer key,
119 gpointer value,
120 gpointer user_data);
123 G_DEFINE_TYPE(AdgTable, adg_table, ADG_TYPE_ENTITY);
126 static void
127 adg_table_class_init(AdgTableClass *klass)
129 GObjectClass *gobject_class;
130 AdgEntityClass *entity_class;
131 GParamSpec *param;
133 gobject_class = (GObjectClass *) klass;
134 entity_class = (AdgEntityClass *) klass;
136 g_type_class_add_private(klass, sizeof(AdgTablePrivate));
138 gobject_class->dispose = dispose;
139 gobject_class->finalize = finalize;
140 gobject_class->get_property = get_property;
141 gobject_class->set_property = set_property;
143 entity_class->global_changed = global_changed;
144 entity_class->local_changed = local_changed;
145 entity_class->invalidate = invalidate;
146 entity_class->arrange = arrange;
147 entity_class->render = render;
149 param = adg_param_spec_dress("table-dress",
150 P_("Table Dress"),
151 P_("The dress to use for stroking this entity"),
152 ADG_DRESS_TABLE,
153 G_PARAM_READWRITE);
154 g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param);
156 param = g_param_spec_boolean("has-frame",
157 P_("Has Frame Flag"),
158 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
159 TRUE,
160 G_PARAM_READWRITE);
161 g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param);
164 static void
165 adg_table_init(AdgTable *table)
167 AdgTablePrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(table,
168 ADG_TYPE_TABLE,
169 AdgTablePrivate);
171 data->table_dress = ADG_DRESS_TABLE;
172 data->has_frame = TRUE;
174 data->table_style = NULL;
175 data->grid = NULL;
176 data->frame = NULL;
177 data->rows = NULL;
178 data->cell_names = NULL;
180 table->data = data;
183 static void
184 dispose(GObject *object)
186 AdgTablePrivate *data = ((AdgTable *) object)->data;
188 invalidate((AdgEntity *) object);
190 if (data->grid != NULL) {
191 g_object_unref(data->grid);
192 data->grid = NULL;
195 if (data->frame != NULL) {
196 g_object_unref(data->frame);
197 data->frame = NULL;
200 /* The rows finalization will happen in the finalize() method */
201 if (data->rows != NULL)
202 g_slist_foreach(data->rows, (GFunc) row_dispose, NULL);
204 if (PARENT_OBJECT_CLASS->dispose != NULL)
205 PARENT_OBJECT_CLASS->dispose(object);
208 static void
209 finalize(GObject *object)
211 AdgTable *table;
212 AdgTablePrivate *data;
214 table = (AdgTable *) object;
215 data = table->data;
217 if (data->rows != NULL) {
218 g_slist_foreach(data->rows, (GFunc) row_free, NULL);
219 g_slist_free(data->rows);
222 if (data->cell_names != NULL)
223 g_hash_table_destroy(data->cell_names);
225 if (PARENT_OBJECT_CLASS->finalize != NULL)
226 PARENT_OBJECT_CLASS->finalize(object);
229 static void
230 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
232 AdgTablePrivate *data = ((AdgTable *) object)->data;
234 switch (prop_id) {
235 case PROP_TABLE_DRESS:
236 g_value_set_int(value, data->table_dress);
237 break;
238 case PROP_HAS_FRAME:
239 g_value_set_boolean(value, data->has_frame);
240 break;
241 default:
242 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
243 break;
247 static void
248 set_property(GObject *object, guint prop_id,
249 const GValue *value, GParamSpec *pspec)
251 AdgTable *table;
252 AdgTablePrivate *data;
254 table = (AdgTable *) object;
255 data = table->data;
257 switch (prop_id) {
258 case PROP_TABLE_DRESS:
259 adg_dress_set(&data->table_dress, g_value_get_int(value));
260 break;
261 case PROP_HAS_FRAME:
262 switch_frame(table, g_value_get_boolean(value));
263 break;
264 default:
265 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
266 break;
272 * adg_table_new:
274 * Creates a new empty table entity. The #AdgEntity:local-method
275 * property is set by default to #ADG_MIX_DISABLED, that is the
276 * table is not subject to any local transformations.
278 * Returns: the newly created table entity
280 AdgTable *
281 adg_table_new(void)
283 return g_object_new(ADG_TYPE_TABLE,
284 "local-method", ADG_MIX_DISABLED, NULL);
288 * adg_table_get_table_dress:
289 * @table: an #AdgTable
291 * Gets the table dress to be used in rendering @table.
293 * Returns: the current table dress
295 AdgDress
296 adg_table_get_table_dress(AdgTable *table)
298 AdgTablePrivate *data;
300 g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED);
302 data = table->data;
304 return data->table_dress;
308 * adg_table_set_table_dress:
309 * @table: an #AdgTable
310 * @dress: the new #AdgDress to use
312 * Sets a new table dress for rendering @table. The new dress
313 * must be related to the original dress for this property:
314 * you cannot set a dress used for line styles to a dress
315 * managing fonts.
317 * The check is done by calling adg_dress_are_related() with
318 * @dress and the previous dress as arguments. Check out its
319 * documentation for details on what is a related dress.
321 void
322 adg_table_set_table_dress(AdgTable *table, AdgDress dress)
324 AdgTablePrivate *data;
326 g_return_if_fail(ADG_IS_TABLE(table));
328 data = table->data;
330 if (adg_dress_set(&data->table_dress, dress))
331 g_object_notify((GObject *) table, "table-dress");
335 * adg_table_has_frame:
336 * @table: an #AdgTable
338 * Returns the state of the #AdgTable:has-frame property.
340 * Returns: the current state
342 gboolean
343 adg_table_has_frame(AdgTable *table)
345 AdgTablePrivate *data;
347 g_return_val_if_fail(ADG_IS_TABLE(table), FALSE);
349 data = table->data;
351 return data->has_frame;
355 * adg_table_switch_frame:
356 * @table: an #AdgTable
357 * @state: the new state of the frame
359 * Sets the #AdgTable:has-frame property: %TRUE will draw a
360 * frame around the whole table using the #AdgTableStyle:frame-dress
361 * dress of the table style.
363 void
364 adg_table_switch_frame(AdgTable *table, gboolean state)
366 g_return_if_fail(ADG_IS_TABLE(table));
368 if (switch_frame(table, state))
369 g_object_notify((GObject *) table, "has-frame");
373 * adg_table_get_n_rows:
374 * @table: an #AdgTable
376 * Gets the number of rows stored in @table.
378 * Returns: the number of rows or %0 on empty @table or errors
380 guint
381 adg_table_get_n_rows(AdgTable *table)
383 AdgTablePrivate *data;
385 g_return_val_if_fail(ADG_IS_TABLE(table), 0);
387 data = table->data;
389 if (data->rows == NULL)
390 return 0;
392 return g_slist_length(data->rows);
396 * adg_table_row_new:
397 * @table: an #AdgTable
399 * Creates a new empty row and appends it at the end of the rows
400 * yet present in @table. By default, the height of this new
401 * row will be the fallback value provided by the table style:
402 * you can override it by using adg_table_row_set_height().
404 * Returns: the newly created row or %NULL on errors
406 AdgTableRow *
407 adg_table_row_new(AdgTable *table)
409 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
411 return row_new(table, NULL);
415 * adg_table_row_new_before:
416 * @row: a valid #AdgTableRow
418 * Creates a new empty row with default height and inserts it
419 * just before @row.
421 * Returns: the newly created row or %NULL on errors
423 AdgTableRow *
424 adg_table_row_new_before(AdgTableRow *row)
426 g_return_val_if_fail(row != NULL, NULL);
427 g_return_val_if_fail(ADG_IS_TABLE(row->table), NULL);
429 return row_new(row->table, row);
433 * adg_table_row_delete:
434 * @row: a valid #AdgTableRow
436 * Removes @row from its owner table and frees every resources allocated
437 * by it. This means also the eventual cells owned by @row will be freed.
439 void
440 adg_table_row_delete(AdgTableRow *row)
442 AdgTable *table;
443 AdgTablePrivate *data;
445 g_return_if_fail(row != NULL);
447 table = row->table;
449 g_return_if_fail(ADG_IS_TABLE(table));
451 data = table->data;
453 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
454 g_slist_free(row->cells);
455 data->rows = g_slist_remove(data->rows, row);
457 g_free(row);
461 * adg_table_row_get_n_cells:
462 * @row: a valid #AdgTableRow
464 * Gets the number of cells stored in @row.
466 * Returns: the number of cells or %0 on empty row or errors
468 guint
469 adg_table_row_get_n_cells(const AdgTableRow *row)
471 g_return_val_if_fail(row != NULL, 0);
473 if (row->cells == NULL)
474 return 0;
476 return g_slist_length(row->cells);
480 * adg_table_row_set_height:
481 * @row: a valid #AdgTableRow
482 * @height: the new height
484 * Sets a new height on @row. The extents will be invalidated to
485 * recompute the whole layout of the table. Specifying %0 in
486 * @height will use the default height set in the table style.
488 void
489 adg_table_row_set_height(AdgTableRow *row, gdouble height)
491 g_return_if_fail(row != NULL);
493 row->height = height;
495 adg_entity_invalidate((AdgEntity *) row->table);
499 * adg_table_row_extents:
500 * @row: a valid #AdgTableRow
502 * Gets the extents of @row. This function is useful only after the
503 * arrange() phase as in the other situation the extents will likely
504 * be not up to date.
506 * Returns: the extents of @row or %NULL on errors
508 const CpmlExtents *
509 adg_table_row_extents(AdgTableRow *row)
511 g_return_val_if_fail(row != NULL, NULL);
513 return &row->extents;
517 * adg_table_get_cell_by_name:
518 * @table: an #AdgTable
519 * @name: the name of a cell
521 * Gets the cell named @name inside @table.
523 * Returns: the requested cell or %NULL if not found
525 AdgTableCell *
526 adg_table_get_cell_by_name(AdgTable *table, const gchar *name)
528 AdgTablePrivate *data;
530 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
532 data = table->data;
534 if (data->cell_names == NULL)
535 return NULL;
537 return g_hash_table_lookup(data->cell_names, name);
541 * adg_table_cell_new:
542 * @row: a valid #AdgTableRow
543 * @width: width of the cell
545 * Creates a new empty cell without a frame and appends it at the
546 * end of the cells yet present in @row. You can add content to the
547 * cell by using adg_table_cell_set_title() and
548 * adg_table_cell_set_value() or enable the frame with
549 * adg_table_cell_switch_frame().
551 * A positive @width value specifies the width of this cell in global
552 * space: if the width of its content (that is, either the title or the
553 * value entity) will be greater than @width, it will be rendered
554 * outside the cell boundary box, luckely overwriting the adiacent
555 * cells.
557 * Using %0 as @width means the width of the cell will be automatically
558 * adjusted to the maximum width of its content.
560 * Negative width values are not allowed: this condition will raise
561 * a warning without any further processing.
563 * Returns: the newly created cell or %NULL on errors
565 AdgTableCell *
566 adg_table_cell_new(AdgTableRow *row, gdouble width)
568 g_return_val_if_fail(row != NULL, NULL);
569 g_return_val_if_fail(width >= 0, NULL);
571 return cell_new(row, NULL, width, FALSE, NULL, NULL, NULL);
575 * adg_table_cell_new_before:
576 * @cell: a valid #AdgTableCell
577 * @width: width of the cell
579 * Creates a new cell and inserts it rigthly before the @cell cell.
580 * This works similarily and accepts the same parameters as the
581 * adg_table_cell_new() function.
583 * Returns: the newly created cell or %NULL on errors
585 AdgTableCell *
586 adg_table_cell_new_before(AdgTableCell *cell, gdouble width)
588 g_return_val_if_fail(cell != NULL, NULL);
589 g_return_val_if_fail(cell->row != NULL, NULL);
590 g_return_val_if_fail(width >= 0, NULL);
592 return cell_new(cell->row, cell, width, FALSE, NULL, NULL, NULL);
596 * adg_table_cell_new_full:
597 * @row: a valid #AdgTableRow
598 * @width: width of the cell
599 * @name: name to associate
600 * @title: title to render
601 * @value: value to render
603 * A convenient function to append a framed cell to @row with a
604 * specific title and value text. The font to use for rendering
605 * @title and @value will be picked up from the table style, so
606 * be sure to have the correct table dress set before calling
607 * this function.
609 * @row and @width have the same meanings as in adg_table_cell_new():
610 * check its documentation for details.
612 * @name is an optional identifier to univoquely access this cell
613 * by using adg_table_get_cell_by_name(). The identifier must be
614 * univoque: if there is yet a cell with the same name a warning
615 * message will be raised and the function will fail.
617 * Returns: the newly created cell or %NULL on errors
619 AdgTableCell *
620 adg_table_cell_new_full(AdgTableRow *row, gdouble width, const gchar *name,
621 const gchar *title, const gchar *value)
623 AdgEntity *table, *title_entity, *value_entity;
624 AdgTableStyle *table_style;
626 g_return_val_if_fail(row != NULL, NULL);
628 table = (AdgEntity *) row->table;
630 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
632 table_style = (AdgTableStyle *)
633 adg_entity_style(table, adg_table_get_table_dress(row->table));
635 if (title != NULL) {
636 AdgDress dress = adg_table_style_get_title_dress(table_style);
637 title_entity = g_object_new(ADG_TYPE_TOY_TEXT,
638 "local-method", ADG_MIX_PARENT,
639 "label", title,
640 "font-dress", dress,
641 "parent", table, NULL);
642 } else {
643 title_entity = NULL;
646 if (value != NULL) {
647 AdgDress dress = adg_table_style_get_value_dress(table_style);
648 value_entity = g_object_new(ADG_TYPE_TOY_TEXT,
649 "local-method", ADG_MIX_PARENT,
650 "label", value,
651 "font-dress", dress,
652 "parent", table, NULL);
653 } else {
654 value_entity = NULL;
657 return cell_new(row, NULL, width, TRUE, name, title_entity, value_entity);
661 * adg_table_cell_delete:
662 * @cell: a valid #AdgTableCell
664 * Deletes @cell removing it from the container row and freeing
665 * any resource associated to it.
667 void
668 adg_table_cell_delete(AdgTableCell *cell)
670 AdgTableRow *row;
672 g_return_if_fail(cell != NULL);
674 row = cell->row;
676 g_return_if_fail(row != NULL);
678 cell_free(cell);
679 row->cells = g_slist_remove(row->cells, cell);
683 * adg_table_cell_get_title:
684 * @cell: a valid #AdgTableCell
686 * Gets the current title of @cell. The returned string is owned
687 * by @cell and must not be modified or freed.
689 * Returns: the title entity or %NULL for undefined title
691 AdgEntity *
692 adg_table_cell_get_title(AdgTableCell *cell)
694 g_return_val_if_fail(cell != NULL, NULL);
696 return cell->title;
700 * adg_table_cell_set_title:
701 * @cell: a valid #AdgTableCell
702 * @title: the new title entity
704 * Sets @title as the new title entity of @cell. The top left
705 * corner of the bounding box of @title will be cohincident to
706 * the top left corner of the cell extents, taking into accounts
707 * eventual padding spaces specified by the table style.
709 * The old internal entity is unrefenrenced while the @title (if
710 * not %NULL) is refenenced with g_object_ref_sink().
712 * @title can be %NULL, in which case the old entity is removed.
714 void
715 adg_table_cell_set_title(AdgTableCell *cell, AdgEntity *title)
717 g_return_if_fail(cell != NULL);
718 g_return_if_fail(title == NULL || ADG_IS_ENTITY(title));
720 if (title == cell->title)
721 return;
723 if (cell->title != NULL)
724 g_object_unref(cell->title);
726 cell->title = title;
728 if (cell->title != NULL)
729 g_object_ref_sink(cell->title);
731 /* Invalidate the whole table if the with of this cell depends
732 * on the cell content */
733 if (cell->width == 0)
734 adg_entity_invalidate((AdgEntity *) cell->row->table);
738 * adg_table_cell_get_value:
739 * @cell: a valid #AdgTableCell
741 * Gets the current value of @cell. The returned string is owned
742 * by @cell and must not be modified or freed.
744 * Returns: the value entity or %NULL for undefined value
746 AdgEntity *
747 adg_table_cell_get_value(AdgTableCell *cell)
749 g_return_val_if_fail(cell != NULL, NULL);
751 return cell->value;
755 * adg_table_cell_set_value:
756 * @cell: a valid #AdgTableCell
757 * @value: the new value entity
759 * Sets @value as the new value entity of @cell. The bottom middle
760 * point of the bounding box of @value will be cohincident to the
761 * bottom middle point of the cell extents, taking into accounts
762 * eventual padding spaces specified by the table style.
764 * The old internal entity is unrefenrenced while the @value (if
765 * not %NULL) is refenenced with g_object_ref_sink().
767 * @value can be %NULL, in which case the old entity is removed.
769 void
770 adg_table_cell_set_value(AdgTableCell *cell, AdgEntity *value)
772 g_return_if_fail(cell != NULL);
773 g_return_if_fail(value == NULL || ADG_IS_ENTITY(value));
775 if (value == cell->value)
776 return;
778 if (cell->value != NULL)
779 g_object_unref(cell->value);
781 cell->value = value;
783 if (cell->value != NULL)
784 g_object_ref_sink(cell->value);
786 /* Invalidate the whole table if the with of this cell depends
787 * on the cell content */
788 if (cell->width == 0)
789 adg_entity_invalidate((AdgEntity *) cell->row->table);
793 * adg_table_cell_set_width:
794 * @cell: a valid #AdgTableCell
795 * @width: the new width
797 * Sets a new width on @cell. The extents on the whole table
798 * will be invalidated, so will be recomputed in the next
799 * arrange() phase.
801 void
802 adg_table_cell_set_width(AdgTableCell *cell, gdouble width)
804 g_return_if_fail(cell != NULL);
806 cell->width = width;
808 adg_entity_invalidate((AdgEntity *) cell->row->table);
812 * adg_table_cell_has_frame:
813 * @cell: a valid #AdgTableCell
815 * Gets the frame flag of @cell.
817 * Returns: the frame flag
819 gboolean
820 adg_table_cell_has_frame(AdgTableCell *cell)
822 g_return_val_if_fail(cell != NULL, FALSE);
824 return cell->has_frame;
828 * adg_table_cell_switch_frame:
829 * @cell: a valid #AdgTableCell
830 * @state: the new frame state
832 * Sets the frame flag of @cell: if @state is %TRUE, a frame around
833 * @cell will be rendered using the #AdgTableStyle:cell-dress dress
834 * of the table style.
836 void
837 adg_table_cell_switch_frame(AdgTableCell *cell, gboolean state)
839 AdgTablePrivate *data;
841 g_return_if_fail(cell != NULL);
843 if (cell->has_frame == state)
844 return;
846 data = cell->row->table->data;
847 cell->has_frame = state;
849 if (data->grid != NULL) {
850 g_object_unref(data->grid);
851 data->grid = NULL;
856 * adg_table_cell_extents:
857 * @cell: a valid #AdgTableCell
859 * Gets the extents of @cell. This function is useful only after the
860 * arrange() phase as in the other situation the extents will likely
861 * be not up to date.
863 * Returns: the extents of @cell or %NULL on errors
865 const CpmlExtents *
866 adg_table_cell_extents(AdgTableCell *cell)
868 g_return_val_if_fail(cell != NULL, NULL);
870 return &cell->extents;
874 static void
875 global_changed(AdgEntity *entity)
877 PARENT_ENTITY_CLASS->global_changed(entity);
878 propagate((AdgTable *) entity, "global-changed");
881 static void
882 local_changed(AdgEntity *entity)
884 PARENT_ENTITY_CLASS->local_changed(entity);
885 propagate((AdgTable *) entity, "local-changed");
888 static void
889 invalidate(AdgEntity *entity)
891 propagate((AdgTable *) entity, "invalidate");
894 static void
895 arrange(AdgEntity *entity)
897 AdgTable *table;
898 AdgTablePrivate *data;
899 CpmlExtents extents;
900 const AdgPair *spacing;
901 GSList *row_node;
902 AdgTableRow *row;
903 gdouble y;
905 table = (AdgTable *) entity;
906 data = table->data;
907 cpml_extents_copy(&extents, adg_entity_extents(entity));
909 /* Resolve the table style */
910 if (data->table_style == NULL)
911 data->table_style = (AdgTableStyle *)
912 adg_entity_style(entity, data->table_dress);
914 if (extents.is_defined) {
915 if (data->grid != NULL)
916 adg_entity_arrange((AdgEntity *) data->grid);
917 if (data->frame != NULL)
918 adg_entity_arrange((AdgEntity *) data->frame);
919 return;
922 spacing = adg_table_style_get_cell_spacing(data->table_style);
923 extents.size.x = 0;
924 extents.size.y = 0;
926 for (row_node = data->rows; row_node; row_node = row_node->next) {
927 row = row_node->data;
929 row_arrange_size(row);
931 if (row->extents.size.x > extents.size.x)
932 extents.size.x = row->extents.size.x;
933 extents.size.y += row->extents.size.y;
936 /* TODO: update the org according to the table alignments */
938 y = extents.org.y + spacing->y;
939 for (row_node = data->rows; row_node; row_node = row_node->next) {
940 row = row_node->data;
942 row->extents.org.x = extents.org.x;
943 row->extents.org.y = y;
945 row_arrange(row);
947 y += row->extents.size.y + spacing->y;
950 extents.is_defined = TRUE;
951 adg_entity_set_extents(entity, &extents);
953 arrange_grid(entity);
954 arrange_frame(entity);
957 static void
958 arrange_grid(AdgEntity *entity)
960 AdgTablePrivate *data;
961 AdgPath *path;
962 AdgTrail *trail;
963 GSList *row_node, *cell_node;
964 AdgTableRow *row;
965 AdgTableCell *cell;
966 AdgPair pair;
967 AdgDress dress;
969 data = ((AdgTable *) entity)->data;
971 if (data->grid != NULL)
972 return;
974 path = adg_path_new();
975 trail = (AdgTrail *) path;
977 for (row_node = data->rows; row_node; row_node = row_node->next) {
978 row = row_node->data;
980 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
981 cell = cell_node->data;
983 if (!cell->has_frame)
984 continue;
986 cpml_pair_copy(&pair, &cell->extents.org);
987 adg_path_move_to(path, &pair);
988 pair.x += cell->extents.size.x;
989 adg_path_line_to(path, &pair);
990 pair.y += cell->extents.size.y;
991 adg_path_line_to(path, &pair);
992 pair.x -= cell->extents.size.x;
993 adg_path_line_to(path, &pair);
994 adg_path_close(path);
998 if (!adg_trail_extents(trail)->is_defined)
999 return;
1001 dress = adg_table_style_get_grid_dress(data->table_style);
1002 data->grid = g_object_new(ADG_TYPE_STROKE,
1003 "local-method", ADG_MIX_PARENT,
1004 "line-dress", dress,
1005 "trail", trail,
1006 "parent", entity, NULL);
1007 adg_entity_arrange((AdgEntity *) data->grid);
1010 static void
1011 arrange_frame(AdgEntity *entity)
1013 AdgTablePrivate *data;
1014 AdgPath *path;
1015 const CpmlExtents *extents;
1016 AdgPair pair;
1017 AdgDress dress;
1019 data = ((AdgTable *) entity)->data;
1021 if (data->frame != NULL || !data->has_frame)
1022 return;
1024 path = adg_path_new();
1025 extents = adg_entity_extents(entity);
1027 cpml_pair_copy(&pair, &extents->org);
1028 adg_path_move_to(path, &pair);
1029 pair.x += extents->size.x;
1030 adg_path_line_to(path, &pair);
1031 pair.y += extents->size.y;
1032 adg_path_line_to(path, &pair);
1033 pair.x -= extents->size.x;
1034 adg_path_line_to(path, &pair);
1035 adg_path_close(path);
1037 dress = adg_table_style_get_frame_dress(data->table_style);
1039 data->frame = g_object_new(ADG_TYPE_STROKE,
1040 "local-method", ADG_MIX_PARENT,
1041 "line-dress", dress,
1042 "trail", (AdgTrail *) path,
1043 "parent", entity, NULL);
1044 adg_entity_arrange((AdgEntity *) data->frame);
1047 static void
1048 render(AdgEntity *entity, cairo_t *cr)
1050 cairo_set_matrix(cr, adg_entity_ctm(entity));
1052 propagate((AdgTable *) entity, "render", cr);
1055 static gboolean
1056 switch_frame(AdgTable *table, gboolean state)
1058 AdgTablePrivate *data = table->data;
1060 if (data->has_frame == state)
1061 return FALSE;
1063 data->has_frame = state;
1065 if (data->frame != NULL) {
1066 g_object_unref(data->frame);
1067 data->frame = NULL;
1070 return TRUE;
1073 static void
1074 propagate(AdgTable *table, const gchar *detailed_signal, ...)
1076 guint signal_id;
1077 GQuark detail = 0;
1078 va_list var_args, var_copy;
1079 AdgTablePrivate *data;
1080 GSList *row_node;
1081 AdgTableRow *row;
1082 GSList *cell_node;
1083 AdgTableCell *cell;
1085 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(table),
1086 &signal_id, &detail, FALSE)) {
1087 g_assert_not_reached();
1090 va_start(var_args, detailed_signal);
1091 data = table->data;
1093 if (data->frame != NULL) {
1094 G_VA_COPY(var_copy, var_args);
1095 g_signal_emit_valist(data->frame, signal_id, detail, var_copy);
1098 if (data->grid != NULL) {
1099 G_VA_COPY(var_copy, var_args);
1100 g_signal_emit_valist(data->grid, signal_id, detail, var_copy);
1103 for (row_node = data->rows; row_node; row_node = row_node->next) {
1104 row = row_node->data;
1106 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1107 cell = cell_node->data;
1109 if (cell->title != NULL) {
1110 G_VA_COPY(var_copy, var_args);
1111 g_signal_emit_valist(cell->title, signal_id, detail, var_copy);
1114 if (cell->value != NULL) {
1115 G_VA_COPY(var_copy, var_args);
1116 g_signal_emit_valist(cell->value, signal_id, detail, var_copy);
1121 va_end(var_args);
1124 static AdgTableRow *
1125 row_new(AdgTable *table, AdgTableRow *before_row)
1127 AdgTablePrivate *data;
1128 AdgTableRow *new_row;
1130 data = table->data;
1131 new_row = g_new(AdgTableRow, 1);
1132 new_row->table = table;
1133 new_row->cells = NULL;
1134 new_row->height = 0;
1135 new_row->extents.is_defined = FALSE;
1137 if (before_row == NULL) {
1138 data->rows = g_slist_append(data->rows, new_row);
1139 } else {
1140 GSList *before_node = g_slist_find(data->rows, before_row);
1142 /* This MUST be present, otherwise something really bad happened */
1143 g_assert(before_node != NULL);
1145 data->rows = g_slist_insert_before(data->rows, before_node, new_row);
1148 invalidate((AdgEntity *) table);
1150 return new_row;
1153 static void
1154 row_arrange_size(AdgTableRow *row)
1156 AdgTableStyle *table_style;
1157 const AdgPair *spacing;
1158 CpmlVector *size;
1159 AdgTableCell *cell;
1160 GSList *cell_node;
1162 table_style = GET_TABLE_STYLE(row->table);
1163 spacing = adg_table_style_get_cell_spacing(table_style);
1164 size = &row->extents.size;
1166 size->x = 0;
1167 if (row->height == 0)
1168 size->y = adg_table_style_get_row_height(table_style);
1169 else
1170 size->y = row->height;
1172 /* Compute the row width by summing every cell width */
1173 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1174 cell = cell_node->data;
1176 cell_arrange_size(cell);
1178 size->x += cell->extents.size.x + spacing->x;
1181 if (size->x > 0)
1182 size->x += spacing->x;
1185 /* Before calling this function, row->extents should be updated */
1186 static void
1187 row_arrange(AdgTableRow *row)
1189 AdgTableStyle *table_style;
1190 const AdgPair *spacing;
1191 const AdgPair *org;
1192 AdgTableCell *cell;
1193 GSList *cell_node;
1194 gdouble x;
1196 table_style = GET_TABLE_STYLE(row->table);
1197 spacing = adg_table_style_get_cell_spacing(table_style);
1198 org = &row->extents.org;
1199 x = org->x + spacing->x;
1201 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1202 cell = cell_node->data;
1204 cell->extents.org.x = x;
1205 cell->extents.org.y = org->y;
1207 cell_arrange(cell);
1209 x += cell->extents.size.x + spacing->x;
1212 row->extents.is_defined = TRUE;
1215 static void
1216 row_dispose(AdgTableRow *row)
1218 g_slist_foreach(row->cells, (GFunc) cell_dispose, NULL);
1221 static void
1222 row_free(AdgTableRow *row)
1224 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
1225 g_slist_free(row->cells);
1227 g_free(row);
1230 static AdgTableCell *
1231 cell_new(AdgTableRow *row, AdgTableCell *before_cell,
1232 gdouble width, gboolean has_frame,
1233 const gchar *name, AdgEntity *title, AdgEntity *value)
1235 AdgTablePrivate *data;
1236 AdgTableCell *new_cell;
1238 data = row->table->data;
1240 if (name != NULL) {
1241 if (data->cell_names == NULL) {
1242 data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal,
1243 g_free, NULL);
1244 } else if (g_hash_table_lookup(data->cell_names, name) != NULL) {
1245 g_warning(_("%s: `%s' cell name is yet used"),
1246 G_STRLOC, name);
1247 return NULL;
1251 new_cell = g_new(AdgTableCell, 1);
1252 new_cell->row = row;
1253 new_cell->width = width;
1254 new_cell->has_frame = has_frame;
1255 new_cell->title = title;
1256 new_cell->value = value;
1257 new_cell->extents.is_defined = FALSE;
1259 if (title != NULL)
1260 g_object_ref_sink(title);
1262 if (value != NULL)
1263 g_object_ref_sink(value);
1265 if (before_cell == NULL) {
1266 row->cells = g_slist_append(row->cells, new_cell);
1267 } else {
1268 GSList *before_node = g_slist_find(row->cells, before_cell);
1270 /* This MUST be present, otherwise something really bad happened */
1271 g_assert(before_node != NULL);
1273 row->cells = g_slist_insert_before(row->cells, before_node, new_cell);
1276 if (name != NULL)
1277 g_hash_table_insert(data->cell_names, g_strdup(name), new_cell);
1279 return new_cell;
1282 static void
1283 cell_arrange_size(AdgTableCell *cell)
1285 AdgTableStyle *table_style;
1286 CpmlVector *size;
1288 table_style = GET_TABLE_STYLE(cell->row->table);
1289 size = &cell->extents.size;
1291 if (cell->title != NULL)
1292 adg_entity_arrange(cell->title);
1294 if (cell->value != NULL)
1295 adg_entity_arrange(cell->value);
1297 size->y = cell->row->extents.size.y;
1299 if (cell->width == 0) {
1300 const CpmlVector *content_size;
1302 /* The width depends on the cell content (default: 0) */
1303 size->x = 0;
1305 if (cell->title != NULL) {
1306 content_size = &adg_entity_extents(cell->title)->size;
1307 size->x = content_size->x;
1310 if (cell->value != NULL) {
1311 content_size = &adg_entity_extents(cell->value)->size;
1312 if (content_size->x > size->x)
1313 size->x = content_size->x;
1316 size->x += adg_table_style_get_cell_padding(table_style)->x * 2;
1317 } else {
1318 size->x = cell->width;
1322 /* Before calling this function, cell->extents should be updated */
1323 static void
1324 cell_arrange(AdgTableCell *cell)
1326 AdgTableStyle *table_style;
1327 const AdgPair *padding;
1328 const CpmlPair *org;
1329 const CpmlVector *size, *content_size;
1330 AdgMatrix map;
1332 table_style = GET_TABLE_STYLE(cell->row->table);
1333 padding = adg_table_style_get_cell_padding(table_style);
1334 org = &cell->extents.org;
1335 size = &cell->extents.size;
1337 if (cell->title != NULL) {
1338 content_size = &adg_entity_extents(cell->title)->size;
1340 cairo_matrix_init_translate(&map,
1341 org->x + padding->x,
1342 org->y + content_size->y + padding->y);
1344 adg_entity_set_global_map(cell->title, &map);
1347 if (cell->value != NULL) {
1348 content_size = &adg_entity_extents(cell->value)->size;
1350 cairo_matrix_init_translate(&map,
1351 org->x + (size->x - content_size->x) / 2,
1352 org->y + size->y - padding->y);
1354 adg_entity_set_global_map(cell->value, &map);
1357 cell->extents.is_defined = TRUE;
1360 static void
1361 cell_dispose(AdgTableCell *cell)
1363 if (cell->title != NULL) {
1364 g_object_unref(cell->title);
1365 cell->title = NULL;
1368 if (cell->value != NULL) {
1369 g_object_unref(cell->value);
1370 cell->value = NULL;
1374 static void
1375 cell_free(AdgTableCell *cell)
1377 AdgTablePrivate *data = cell->row->table->data;
1379 g_hash_table_foreach_remove(data->cell_names, value_match, cell);
1380 cell_dispose(cell);
1382 g_free(cell);
1385 static gboolean
1386 value_match(gpointer key, gpointer value, gpointer user_data)
1388 return value == user_data;