[AdgLocalMode] Renamed to AdgMixMethod
[adg.git] / adg / adg-table.c
blob033592cd78fbba2c269d9be1becd150a56ec6cb4
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
77 static void dispose (GObject *object);
78 static void finalize (GObject *object);
79 static void get_property (GObject *object,
80 guint param_id,
81 GValue *value,
82 GParamSpec *pspec);
83 static void set_property (GObject *object,
84 guint param_id,
85 const GValue *value,
86 GParamSpec *pspec);
87 static void global_changed (AdgEntity *entity);
88 static void local_changed (AdgEntity *entity);
89 static void invalidate (AdgEntity *entity);
90 static void arrange (AdgEntity *entity);
91 static void arrange_grid (AdgEntity *entity);
92 static void arrange_frame (AdgEntity *entity);
93 static void render (AdgEntity *entity,
94 cairo_t *cr);
95 static void propagate (AdgTable *table,
96 const gchar *detailed_signal,
97 ...);
98 static AdgTableRow * row_new (AdgTable *table,
99 AdgTableRow *before_row);
100 static void row_arrange_size (AdgTableRow *row);
101 static void row_arrange (AdgTableRow *row);
102 static void row_dispose (AdgTableRow *row);
103 static void row_free (AdgTableRow *row);
104 static AdgTableCell * cell_new (AdgTableRow *row,
105 AdgTableCell *before_cell,
106 gdouble width,
107 const gchar *name,
108 AdgEntity *title,
109 AdgEntity *value);
110 static void cell_arrange_size (AdgTableCell *cell);
111 static void cell_arrange (AdgTableCell *cell);
112 static void cell_dispose (AdgTableCell *cell);
113 static void cell_free (AdgTableCell *cell);
114 static gboolean value_match (gpointer key,
115 gpointer value,
116 gpointer user_data);
119 G_DEFINE_TYPE(AdgTable, adg_table, ADG_TYPE_ENTITY);
122 static void
123 adg_table_class_init(AdgTableClass *klass)
125 GObjectClass *gobject_class;
126 AdgEntityClass *entity_class;
127 GParamSpec *param;
129 gobject_class = (GObjectClass *) klass;
130 entity_class = (AdgEntityClass *) klass;
132 g_type_class_add_private(klass, sizeof(AdgTablePrivate));
134 gobject_class->dispose = dispose;
135 gobject_class->finalize = finalize;
136 gobject_class->get_property = get_property;
137 gobject_class->set_property = set_property;
139 entity_class->global_changed = global_changed;
140 entity_class->local_changed = local_changed;
141 entity_class->invalidate = invalidate;
142 entity_class->arrange = arrange;
143 entity_class->render = render;
145 param = adg_param_spec_dress("table-dress",
146 P_("Table Dress"),
147 P_("The dress to use for stroking this entity"),
148 ADG_DRESS_TABLE,
149 G_PARAM_READWRITE);
150 g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param);
153 static void
154 adg_table_init(AdgTable *table)
156 AdgTablePrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(table,
157 ADG_TYPE_TABLE,
158 AdgTablePrivate);
160 data->table_dress = ADG_DRESS_TABLE;
161 data->grid = NULL;
162 data->frame = NULL;
163 data->rows = NULL;
164 data->cell_names = NULL;
166 table->data = data;
169 static void
170 dispose(GObject *object)
172 AdgTablePrivate *data = ((AdgTable *) object)->data;
174 invalidate((AdgEntity *) object);
176 if (data->rows != NULL)
177 g_slist_foreach(data->rows, (GFunc) row_dispose, NULL);
179 if (PARENT_OBJECT_CLASS->dispose != NULL)
180 PARENT_OBJECT_CLASS->dispose(object);
183 static void
184 finalize(GObject *object)
186 AdgTable *table;
187 AdgTablePrivate *data;
189 table = (AdgTable *) object;
190 data = table->data;
192 if (data->rows != NULL) {
193 g_slist_foreach(data->rows, (GFunc) row_free, NULL);
194 g_slist_free(data->rows);
197 if (data->cell_names != NULL)
198 g_hash_table_destroy(data->cell_names);
200 if (PARENT_OBJECT_CLASS->finalize != NULL)
201 PARENT_OBJECT_CLASS->finalize(object);
204 static void
205 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
207 AdgTablePrivate *data = ((AdgTable *) object)->data;
209 switch (prop_id) {
210 case PROP_TABLE_DRESS:
211 g_value_set_int(value, data->table_dress);
212 break;
213 default:
214 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
215 break;
219 static void
220 set_property(GObject *object, guint prop_id,
221 const GValue *value, GParamSpec *pspec)
223 AdgTable *table;
224 AdgTablePrivate *data;
226 table = (AdgTable *) object;
227 data = table->data;
229 switch (prop_id) {
230 case PROP_TABLE_DRESS:
231 adg_dress_set(&data->table_dress, g_value_get_int(value));
232 break;
233 default:
234 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
235 break;
241 * adg_table_new:
243 * Creates a new empty table entity. The #AdgEntity:local-method
244 * property is set by default to #ADG_MIX_DISABLED, that is the
245 * table is not subject to any local transformations.
247 * Returns: the newly created table entity
249 AdgTable *
250 adg_table_new(void)
252 return g_object_new(ADG_TYPE_TABLE,
253 "local-method", ADG_MIX_DISABLED, NULL);
257 * adg_table_get_table_dress:
258 * @table: an #AdgTable
260 * Gets the table dress to be used in rendering @table.
262 * Returns: the current table dress
264 AdgDress
265 adg_table_get_table_dress(AdgTable *table)
267 AdgTablePrivate *data;
269 g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED);
271 data = table->data;
273 return data->table_dress;
277 * adg_table_set_table_dress:
278 * @table: an #AdgTable
279 * @dress: the new #AdgDress to use
281 * Sets a new table dress for rendering @table. The new dress
282 * must be related to the original dress for this property:
283 * you cannot set a dress used for line styles to a dress
284 * managing fonts.
286 * The check is done by calling adg_dress_are_related() with
287 * @dress and the previous dress as arguments. Check out its
288 * documentation for details on what is a related dress.
290 void
291 adg_table_set_table_dress(AdgTable *table, AdgDress dress)
293 AdgTablePrivate *data;
295 g_return_if_fail(ADG_IS_TABLE(table));
297 data = table->data;
299 if (adg_dress_set(&data->table_dress, dress))
300 g_object_notify((GObject *) table, "table-dress");
304 * adg_table_get_n_rows:
305 * @table: an #AdgTable
307 * Gets the number of rows stored in @table.
309 * Returns: the number of rows or %0 on empty @table or errors
311 guint
312 adg_table_get_n_rows(AdgTable *table)
314 AdgTablePrivate *data;
316 g_return_val_if_fail(ADG_IS_TABLE(table), 0);
318 data = table->data;
320 if (data->rows == NULL)
321 return 0;
323 return g_slist_length(data->rows);
327 * adg_table_row_new:
328 * @table: an #AdgTable
330 * Creates a new empty row and appends it at the end of the rows
331 * yet present in @table. By default, the height of this new
332 * row will be the fallback value provided by the table style:
333 * you can override it by using adg_table_row_set_height().
335 * Returns: the newly created row or %NULL on errors
337 AdgTableRow *
338 adg_table_row_new(AdgTable *table)
340 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
342 return row_new(table, NULL);
346 * adg_table_row_new_before:
347 * @row: a valid #AdgTableRow
349 * Creates a new empty row with default height and inserts it
350 * just before @row.
352 * Returns: the newly created row or %NULL on errors
354 AdgTableRow *
355 adg_table_row_new_before(AdgTableRow *row)
357 g_return_val_if_fail(row != NULL, NULL);
358 g_return_val_if_fail(ADG_IS_TABLE(row->table), NULL);
360 return row_new(row->table, row);
364 * adg_table_row_delete:
365 * @row: a valid #AdgTableRow
367 * Removes @row from its owner table and frees every resources allocated
368 * by it. This means also the eventual cells owned by @row will be freed.
370 void
371 adg_table_row_delete(AdgTableRow *row)
373 AdgTable *table;
374 AdgTablePrivate *data;
376 g_return_if_fail(row != NULL);
378 table = row->table;
380 g_return_if_fail(ADG_IS_TABLE(table));
382 data = table->data;
384 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
385 g_slist_free(row->cells);
386 data->rows = g_slist_remove(data->rows, row);
388 g_free(row);
392 * adg_table_row_get_n_cells:
393 * @row: a valid #AdgTableRow
395 * Gets the number of cells stored in @row.
397 * Returns: the number of cells or %0 on empty row or errors
399 guint
400 adg_table_row_get_n_cells(const AdgTableRow *row)
402 g_return_val_if_fail(row != NULL, 0);
404 if (row->cells == NULL)
405 return 0;
407 return g_slist_length(row->cells);
411 * adg_table_row_set_height:
412 * @row: a valid #AdgTableRow
413 * @height: the new height
415 * Sets a new height on @row. The extents will be invalidated to
416 * recompute the whole layout of the table. Specifying %0 in
417 * @height will use the default height set in the table style.
419 void
420 adg_table_row_set_height(AdgTableRow *row, gdouble height)
422 g_return_if_fail(row != NULL);
424 row->height = height;
426 adg_entity_invalidate((AdgEntity *) row->table);
430 * adg_table_row_extents:
431 * @row: a valid #AdgTableRow
433 * Gets the extents of @row. This function is useful only after the
434 * arrange() phase as in the other situation the extents will likely
435 * be not up to date.
437 * Returns: the extents of @row or %NULL on errors
439 const CpmlExtents *
440 adg_table_row_extents(AdgTableRow *row)
442 g_return_val_if_fail(row != NULL, NULL);
444 return &row->extents;
448 * adg_table_get_cell_by_name:
449 * @table: an #AdgTable
450 * @name: the name of a cell
452 * Gets the cell named @name inside @table.
454 * Returns: the requested cell or %NULL if not found
456 AdgTableCell *
457 adg_table_get_cell_by_name(AdgTable *table, const gchar *name)
459 AdgTablePrivate *data;
461 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
463 data = table->data;
465 if (data->cell_names == NULL)
466 return NULL;
468 return g_hash_table_lookup(data->cell_names, name);
472 * adg_table_cell_new:
473 * @row: a valid #AdgTableRow
474 * @width: width of the cell
476 * Creates a new cell and appends it at the end of the cells
477 * yet present in @row. You can add content to the cell by using
478 * adg_table_cell_set_title() and adg_table_cell_set_value().
480 * A positive @width value specifies the width of this cell in global
481 * space: if the width of its content (that is, either the title or the
482 * value entity) will be greater than @width, it will be rendered
483 * outside the cell boundary box, luckely overwriting the adiacent
484 * cells.
486 * Using %0 as @width means the width of the cell will be automatically
487 * adjusted to the maximum width of its content.
489 * Negative width values are not allowed: this condition will raise
490 * a warning without any further processing.
492 * Returns: the newly created cell or %NULL on errors
494 AdgTableCell *
495 adg_table_cell_new(AdgTableRow *row, gdouble width)
497 g_return_val_if_fail(row != NULL, NULL);
498 g_return_val_if_fail(width >= 0, NULL);
500 return cell_new(row, NULL, width, NULL, NULL, NULL);
504 * adg_table_cell_new_before:
505 * @cell: a valid #AdgTableCell
506 * @width: width of the cell
508 * Creates a new cell and inserts it rigthly before the @cell cell.
509 * This works similarily and accepts the same parameters as the
510 * adg_table_cell_new() function.
512 * Returns: the newly created cell or %NULL on errors
514 AdgTableCell *
515 adg_table_cell_new_before(AdgTableCell *cell, gdouble width)
517 g_return_val_if_fail(cell != NULL, NULL);
518 g_return_val_if_fail(cell->row != NULL, NULL);
519 g_return_val_if_fail(width >= 0, NULL);
521 return cell_new(cell->row, cell, width, NULL, NULL, NULL);
525 * adg_table_cell_new_full:
526 * @row: a valid #AdgTableRow
527 * @width: width of the cell
528 * @name: name to associate
529 * @title: title to render
530 * @value: value to render
532 * A convenient function to append a cell to @row with a specific
533 * title and value text. The font to use for rendering @title and
534 * @value will be picked up from the table style, so be sure to
535 * have the correct table dress set before calling this function.
537 * @row and @width have the same meanings as in adg_table_cell_new():
538 * check its documentation for details.
540 * @name is an optional identifier to univoquely access this cell
541 * by using adg_table_get_cell_by_name(). The identifier must be
542 * univoque: if there is yet a cell with the same name a warning
543 * message will be raised and the function will fail.
545 * Returns: the newly created cell or %NULL on errors
547 AdgTableCell *
548 adg_table_cell_new_full(AdgTableRow *row, gdouble width, const gchar *name,
549 const gchar *title, const gchar *value)
551 AdgEntity *table, *title_entity, *value_entity;
552 AdgTableStyle *table_style;
554 g_return_val_if_fail(row != NULL, NULL);
556 table = (AdgEntity *) row->table;
558 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
560 table_style = (AdgTableStyle *)
561 adg_entity_style(table, adg_table_get_table_dress(row->table));
563 if (title != NULL) {
564 AdgDress dress = adg_table_style_get_title_dress(table_style);
565 title_entity = g_object_new(ADG_TYPE_TOY_TEXT,
566 "local-method", ADG_MIX_PARENT,
567 "label", title,
568 "font-dress", dress,
569 "parent", table, NULL);
570 } else {
571 title_entity = NULL;
574 if (value != NULL) {
575 AdgDress dress = adg_table_style_get_value_dress(table_style);
576 value_entity = g_object_new(ADG_TYPE_TOY_TEXT,
577 "local-method", ADG_MIX_PARENT,
578 "label", value,
579 "font-dress", dress,
580 "parent", table, NULL);
581 } else {
582 value_entity = NULL;
585 return cell_new(row, NULL, width, name, title_entity, value_entity);
589 * adg_table_cell_delete:
590 * @cell: a valid #AdgTableCell
592 * Deletes @cell removing it from the container row and freeing
593 * any resource associated to it.
595 void
596 adg_table_cell_delete(AdgTableCell *cell)
598 AdgTableRow *row;
600 g_return_if_fail(cell != NULL);
602 row = cell->row;
604 g_return_if_fail(row != NULL);
606 cell_free(cell);
607 row->cells = g_slist_remove(row->cells, cell);
611 * adg_table_cell_get_title:
612 * @cell: a valid #AdgTableCell
614 * Gets the current title of @cell. The returned string is owned
615 * by @cell and must not be modified or freed.
617 * Returns: the title entity or %NULL for undefined title
619 AdgEntity *
620 adg_table_cell_get_title(AdgTableCell *cell)
622 g_return_val_if_fail(cell != NULL, NULL);
624 return cell->title;
628 * adg_table_cell_set_title:
629 * @cell: a valid #AdgTableCell
630 * @title: the new title entity
632 * Sets @title as the new title entity of @cell. The top left
633 * corner of the bounding box of @title will be cohincident to
634 * the top left corner of the cell extents, taking into accounts
635 * eventual padding spaces specified by the table style.
637 * The old internal entity is unrefenrenced while the @title (if
638 * not %NULL) is refenenced with g_object_ref_sink().
640 * @title can be %NULL, in which case the old entity is removed.
642 void
643 adg_table_cell_set_title(AdgTableCell *cell, AdgEntity *title)
645 g_return_if_fail(cell != NULL);
646 g_return_if_fail(title == NULL || ADG_IS_ENTITY(title));
648 if (title == cell->title)
649 return;
651 if (cell->title != NULL)
652 g_object_unref(cell->title);
654 cell->title = title;
656 if (cell->title != NULL)
657 g_object_ref_sink(cell->title);
659 /* Invalidate the whole table if the with of this cell depends
660 * on the cell content */
661 if (cell->width == 0)
662 adg_entity_invalidate((AdgEntity *) cell->row->table);
666 * adg_table_cell_get_value:
667 * @cell: a valid #AdgTableCell
669 * Gets the current value of @cell. The returned string is owned
670 * by @cell and must not be modified or freed.
672 * Returns: the value entity or %NULL for undefined value
674 AdgEntity *
675 adg_table_cell_get_value(AdgTableCell *cell)
677 g_return_val_if_fail(cell != NULL, NULL);
679 return cell->value;
683 * adg_table_cell_set_value:
684 * @cell: a valid #AdgTableCell
685 * @value: the new value entity
687 * Sets @value as the new value entity of @cell. The bottom middle
688 * point of the bounding box of @value will be cohincident to the
689 * bottom middle point of the cell extents, taking into accounts
690 * eventual padding spaces specified by the table style.
692 * The old internal entity is unrefenrenced while the @value (if
693 * not %NULL) is refenenced with g_object_ref_sink().
695 * @value can be %NULL, in which case the old entity is removed.
697 void
698 adg_table_cell_set_value(AdgTableCell *cell, AdgEntity *value)
700 g_return_if_fail(cell != NULL);
701 g_return_if_fail(value == NULL || ADG_IS_ENTITY(value));
703 if (value == cell->value)
704 return;
706 if (cell->value != NULL)
707 g_object_unref(cell->value);
709 cell->value = value;
711 if (cell->value != NULL)
712 g_object_ref_sink(cell->value);
714 /* Invalidate the whole table if the with of this cell depends
715 * on the cell content */
716 if (cell->width == 0)
717 adg_entity_invalidate((AdgEntity *) cell->row->table);
721 * adg_table_cell_set_width:
722 * @cell: a valid #AdgTableCell
723 * @width: the new width
725 * Sets a new width on @cell. The extents on the whole table
726 * will be invalidated, so will be recomputed in the next
727 * arrange() phase.
729 void
730 adg_table_cell_set_width(AdgTableCell *cell, gdouble width)
732 g_return_if_fail(cell != NULL);
734 cell->width = width;
736 adg_entity_invalidate((AdgEntity *) cell->row->table);
740 * adg_table_cell_extents:
741 * @cell: a valid #AdgTableCell
743 * Gets the extents of @cell. This function is useful only after the
744 * arrange() phase as in the other situation the extents will likely
745 * be not up to date.
747 * Returns: the extents of @cell or %NULL on errors
749 const CpmlExtents *
750 adg_table_cell_extents(AdgTableCell *cell)
752 g_return_val_if_fail(cell != NULL, NULL);
754 return &cell->extents;
758 static void
759 global_changed(AdgEntity *entity)
761 PARENT_ENTITY_CLASS->global_changed(entity);
762 propagate((AdgTable *) entity, "global-changed");
765 static void
766 local_changed(AdgEntity *entity)
768 PARENT_ENTITY_CLASS->local_changed(entity);
769 propagate((AdgTable *) entity, "local-changed");
772 static void
773 invalidate(AdgEntity *entity)
775 propagate((AdgTable *) entity, "invalidate");
778 static void
779 arrange(AdgEntity *entity)
781 AdgTable *table;
782 AdgTablePrivate *data;
783 CpmlExtents extents;
784 const AdgPair *spacing;
785 GSList *row_node;
786 AdgTableRow *row;
787 gdouble y;
789 table = (AdgTable *) entity;
790 data = table->data;
791 cpml_extents_copy(&extents, adg_entity_extents(entity));
793 /* Resolve the table style */
794 if (data->table_style == NULL)
795 data->table_style = (AdgTableStyle *)
796 adg_entity_style(entity, data->table_dress);
798 if (extents.is_defined) {
799 if (data->grid != NULL)
800 adg_entity_arrange((AdgEntity *) data->grid);
801 if (data->frame != NULL)
802 adg_entity_arrange((AdgEntity *) data->frame);
803 return;
806 spacing = adg_table_style_get_cell_spacing(data->table_style);
807 extents.size.x = 0;
808 extents.size.y = 0;
810 for (row_node = data->rows; row_node; row_node = row_node->next) {
811 row = row_node->data;
813 row_arrange_size(row);
815 if (row->extents.size.x > extents.size.x)
816 extents.size.x = row->extents.size.x;
817 extents.size.y += row->extents.size.y;
820 /* TODO: update the org according to the table alignments */
822 y = extents.org.y + spacing->y;
823 for (row_node = data->rows; row_node; row_node = row_node->next) {
824 row = row_node->data;
826 row->extents.org.x = extents.org.x;
827 row->extents.org.y = y;
829 row_arrange(row);
831 y += row->extents.size.y + spacing->y;
834 extents.is_defined = TRUE;
835 adg_entity_set_extents(entity, &extents);
837 arrange_grid(entity);
838 arrange_frame(entity);
841 static void
842 arrange_grid(AdgEntity *entity)
844 AdgTablePrivate *data;
845 AdgPath *path;
846 AdgTrail *trail;
847 GSList *row_node, *cell_node;
848 AdgTableRow *row;
849 AdgTableCell *cell;
850 AdgPair pair;
851 AdgDress dress;
853 data = ((AdgTable *) entity)->data;
855 if (data->grid != NULL)
856 return;
858 path = adg_path_new();
859 trail = (AdgTrail *) path;
861 for (row_node = data->rows; row_node; row_node = row_node->next) {
862 row = row_node->data;
864 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
865 cell = cell_node->data;
867 if (cell->title == NULL && cell->value == NULL)
868 continue;
870 cpml_pair_copy(&pair, &cell->extents.org);
871 adg_path_move_to(path, &pair);
872 pair.x += cell->extents.size.x;
873 adg_path_line_to(path, &pair);
874 pair.y += cell->extents.size.y;
875 adg_path_line_to(path, &pair);
876 pair.x -= cell->extents.size.x;
877 adg_path_line_to(path, &pair);
878 adg_path_close(path);
882 if (!adg_trail_extents(trail)->is_defined)
883 return;
885 dress = adg_table_style_get_grid_dress(data->table_style);
886 data->grid = g_object_new(ADG_TYPE_STROKE,
887 "local-method", ADG_MIX_PARENT,
888 "line-dress", dress,
889 "trail", trail,
890 "parent", entity, NULL);
891 adg_entity_arrange((AdgEntity *) data->grid);
894 static void
895 arrange_frame(AdgEntity *entity)
897 AdgTablePrivate *data;
898 AdgPath *path;
899 const CpmlExtents *extents;
900 AdgPair pair;
901 AdgDress dress;
903 data = ((AdgTable *) entity)->data;
905 if (data->frame != NULL)
906 return;
908 path = adg_path_new();
909 extents = adg_entity_extents(entity);
911 cpml_pair_copy(&pair, &extents->org);
912 adg_path_move_to(path, &pair);
913 pair.x += extents->size.x;
914 adg_path_line_to(path, &pair);
915 pair.y += extents->size.y;
916 adg_path_line_to(path, &pair);
917 pair.x -= extents->size.x;
918 adg_path_line_to(path, &pair);
919 adg_path_close(path);
921 dress = adg_table_style_get_frame_dress(data->table_style);
923 data->frame = g_object_new(ADG_TYPE_STROKE,
924 "local-method", ADG_MIX_PARENT,
925 "line-dress", dress,
926 "trail", (AdgTrail *) path,
927 "parent", entity, NULL);
928 adg_entity_arrange((AdgEntity *) data->frame);
931 static void
932 render(AdgEntity *entity, cairo_t *cr)
934 cairo_set_matrix(cr, adg_entity_ctm(entity));
936 propagate((AdgTable *) entity, "render", cr);
939 static void
940 propagate(AdgTable *table, const gchar *detailed_signal, ...)
942 guint signal_id;
943 GQuark detail = 0;
944 va_list var_args, var_copy;
945 AdgTablePrivate *data;
946 GSList *row_node;
947 AdgTableRow *row;
948 GSList *cell_node;
949 AdgTableCell *cell;
951 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(table),
952 &signal_id, &detail, FALSE)) {
953 g_assert_not_reached();
956 va_start(var_args, detailed_signal);
957 data = table->data;
959 if (data->frame != NULL) {
960 G_VA_COPY(var_copy, var_args);
961 g_signal_emit_valist(data->frame, signal_id, detail, var_copy);
964 if (data->grid != NULL) {
965 G_VA_COPY(var_copy, var_args);
966 g_signal_emit_valist(data->grid, signal_id, detail, var_copy);
969 for (row_node = data->rows; row_node; row_node = row_node->next) {
970 row = row_node->data;
972 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
973 cell = cell_node->data;
975 if (cell->title != NULL) {
976 G_VA_COPY(var_copy, var_args);
977 g_signal_emit_valist(cell->title, signal_id, detail, var_copy);
980 if (cell->value != NULL) {
981 G_VA_COPY(var_copy, var_args);
982 g_signal_emit_valist(cell->value, signal_id, detail, var_copy);
987 va_end(var_args);
990 static AdgTableRow *
991 row_new(AdgTable *table, AdgTableRow *before_row)
993 AdgTablePrivate *data;
994 AdgTableRow *new_row;
996 data = table->data;
997 new_row = g_new(AdgTableRow, 1);
998 new_row->table = table;
999 new_row->cells = NULL;
1000 new_row->height = 0;
1001 new_row->extents.is_defined = FALSE;
1003 if (before_row == NULL) {
1004 data->rows = g_slist_append(data->rows, new_row);
1005 } else {
1006 GSList *before_node = g_slist_find(data->rows, before_row);
1008 /* This MUST be present, otherwise something really bad happened */
1009 g_assert(before_node != NULL);
1011 data->rows = g_slist_insert_before(data->rows, before_node, new_row);
1014 invalidate((AdgEntity *) table);
1016 return new_row;
1019 static void
1020 row_arrange_size(AdgTableRow *row)
1022 AdgTableStyle *table_style;
1023 const AdgPair *spacing;
1024 CpmlVector *size;
1025 AdgTableCell *cell;
1026 GSList *cell_node;
1028 table_style = GET_TABLE_STYLE(row->table);
1029 spacing = adg_table_style_get_cell_spacing(table_style);
1030 size = &row->extents.size;
1032 size->x = 0;
1033 if (row->height == 0)
1034 size->y = adg_table_style_get_row_height(table_style);
1035 else
1036 size->y = row->height;
1038 /* Compute the row width by summing every cell width */
1039 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1040 cell = cell_node->data;
1042 cell_arrange_size(cell);
1044 size->x += cell->extents.size.x + spacing->x;
1047 if (size->x > 0)
1048 size->x += spacing->x;
1051 /* Before calling this function, row->extents should be updated */
1052 static void
1053 row_arrange(AdgTableRow *row)
1055 AdgTableStyle *table_style;
1056 const AdgPair *spacing;
1057 const AdgPair *org;
1058 AdgTableCell *cell;
1059 GSList *cell_node;
1060 gdouble x;
1062 table_style = GET_TABLE_STYLE(row->table);
1063 spacing = adg_table_style_get_cell_spacing(table_style);
1064 org = &row->extents.org;
1065 x = org->x + spacing->x;
1067 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1068 cell = cell_node->data;
1070 cell->extents.org.x = x;
1071 cell->extents.org.y = org->y;
1073 cell_arrange(cell);
1075 x += cell->extents.size.x + spacing->x;
1078 row->extents.is_defined = TRUE;
1081 static void
1082 row_dispose(AdgTableRow *row)
1084 g_slist_foreach(row->cells, (GFunc) cell_dispose, NULL);
1087 static void
1088 row_free(AdgTableRow *row)
1090 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
1091 g_slist_free(row->cells);
1093 g_free(row);
1096 static AdgTableCell *
1097 cell_new(AdgTableRow *row, AdgTableCell *before_cell, gdouble width,
1098 const gchar *name, AdgEntity *title, AdgEntity *value)
1100 AdgTablePrivate *data;
1101 AdgTableCell *new_cell;
1103 data = row->table->data;
1105 if (name != NULL) {
1106 if (data->cell_names == NULL) {
1107 data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal,
1108 g_free, NULL);
1109 } else if (g_hash_table_lookup(data->cell_names, name) != NULL) {
1110 g_warning(_("%s: `%s' cell name is yet used"),
1111 G_STRLOC, name);
1112 return NULL;
1116 new_cell = g_new(AdgTableCell, 1);
1117 new_cell->row = row;
1118 new_cell->width = width;
1119 new_cell->title = title;
1120 new_cell->value = value;
1121 new_cell->extents.is_defined = FALSE;
1123 if (title != NULL)
1124 g_object_ref_sink(title);
1126 if (value != NULL)
1127 g_object_ref_sink(value);
1129 if (before_cell == NULL) {
1130 row->cells = g_slist_append(row->cells, new_cell);
1131 } else {
1132 GSList *before_node = g_slist_find(row->cells, before_cell);
1134 /* This MUST be present, otherwise something really bad happened */
1135 g_assert(before_node != NULL);
1137 row->cells = g_slist_insert_before(row->cells, before_node, new_cell);
1140 if (name != NULL)
1141 g_hash_table_insert(data->cell_names, g_strdup(name), new_cell);
1143 return new_cell;
1146 static void
1147 cell_arrange_size(AdgTableCell *cell)
1149 AdgTableStyle *table_style;
1150 CpmlVector *size;
1152 table_style = GET_TABLE_STYLE(cell->row->table);
1153 size = &cell->extents.size;
1155 if (cell->title != NULL)
1156 adg_entity_arrange(cell->title);
1158 if (cell->value != NULL)
1159 adg_entity_arrange(cell->value);
1161 size->y = cell->row->extents.size.y;
1163 if (cell->width == 0) {
1164 const CpmlVector *content_size;
1166 /* The width depends on the cell content (default: 0) */
1167 size->x = 0;
1169 if (cell->title != NULL) {
1170 content_size = &adg_entity_extents(cell->title)->size;
1171 size->x = content_size->x;
1174 if (cell->value != NULL) {
1175 content_size = &adg_entity_extents(cell->value)->size;
1176 if (content_size->x > size->x)
1177 size->x = content_size->x;
1180 size->x += adg_table_style_get_cell_padding(table_style)->x * 2;
1181 } else {
1182 size->x = cell->width;
1186 /* Before calling this function, cell->extents should be updated */
1187 static void
1188 cell_arrange(AdgTableCell *cell)
1190 AdgTableStyle *table_style;
1191 const AdgPair *padding;
1192 const CpmlPair *org;
1193 const CpmlVector *size, *content_size;
1194 AdgMatrix map;
1196 table_style = GET_TABLE_STYLE(cell->row->table);
1197 padding = adg_table_style_get_cell_padding(table_style);
1198 org = &cell->extents.org;
1199 size = &cell->extents.size;
1201 if (cell->title != NULL) {
1202 content_size = &adg_entity_extents(cell->title)->size;
1204 cairo_matrix_init_translate(&map,
1205 org->x + padding->x,
1206 org->y + content_size->y + padding->y);
1208 adg_entity_set_global_map(cell->title, &map);
1211 if (cell->value != NULL) {
1212 content_size = &adg_entity_extents(cell->value)->size;
1214 cairo_matrix_init_translate(&map,
1215 org->x + (size->x - content_size->x) / 2,
1216 org->y + size->y - padding->y);
1218 adg_entity_set_global_map(cell->value, &map);
1221 cell->extents.is_defined = TRUE;
1224 static void
1225 cell_dispose(AdgTableCell *cell)
1227 if (cell->title != NULL) {
1228 g_object_unref(cell->title);
1229 cell->title = NULL;
1232 if (cell->value != NULL) {
1233 g_object_unref(cell->value);
1234 cell->value = NULL;
1238 static void
1239 cell_free(AdgTableCell *cell)
1241 AdgTablePrivate *data = cell->row->table->data;
1243 g_hash_table_foreach_remove(data->cell_names, value_match, cell);
1244 cell_dispose(cell);
1246 g_free(cell);
1249 static gboolean
1250 value_match(gpointer key, gpointer value, gpointer user_data)
1252 return value == user_data;