[AdgTransformationMode] Renamed to AdgTransformMode
[adg.git] / adg / adg-table.c
blobbd56225810d0f581d5df57a13f15cd724c787d8c
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 invalidate (AdgEntity *entity);
88 static void arrange (AdgEntity *entity);
89 static void arrange_grid (AdgEntity *entity);
90 static void arrange_frame (AdgEntity *entity);
91 static void render (AdgEntity *entity,
92 cairo_t *cr);
93 static AdgTableRow * row_new (AdgTable *table,
94 AdgTableRow *before_row);
95 static void row_arrange_size (AdgTableRow *row);
96 static void row_arrange (AdgTableRow *row);
97 static void row_render (AdgTableRow *row,
98 cairo_t *cr);
99 static void row_dispose (AdgTableRow *row);
100 static void row_free (AdgTableRow *row);
101 static AdgTableCell * cell_new (AdgTableRow *row,
102 AdgTableCell *before_cell,
103 gdouble width,
104 const gchar *name,
105 AdgEntity *title,
106 AdgEntity *value);
107 static void cell_arrange_size (AdgTableCell *cell);
108 static void cell_arrange (AdgTableCell *cell);
109 static void cell_render (AdgTableCell *cell,
110 cairo_t *cr);
111 static void cell_dispose (AdgTableCell *cell);
112 static void cell_free (AdgTableCell *cell);
113 static gboolean value_match (gpointer key,
114 gpointer value,
115 gpointer user_data);
118 G_DEFINE_TYPE(AdgTable, adg_table, ADG_TYPE_ENTITY);
121 static void
122 adg_table_class_init(AdgTableClass *klass)
124 GObjectClass *gobject_class;
125 AdgEntityClass *entity_class;
126 GParamSpec *param;
128 gobject_class = (GObjectClass *) klass;
129 entity_class = (AdgEntityClass *) klass;
131 g_type_class_add_private(klass, sizeof(AdgTablePrivate));
133 gobject_class->dispose = dispose;
134 gobject_class->finalize = finalize;
135 gobject_class->get_property = get_property;
136 gobject_class->set_property = set_property;
138 entity_class->invalidate = invalidate;
139 entity_class->arrange = arrange;
140 entity_class->render = render;
142 param = adg_param_spec_dress("table-dress",
143 P_("Table Dress"),
144 P_("The dress to use for stroking this entity"),
145 ADG_DRESS_TABLE,
146 G_PARAM_READWRITE);
147 g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param);
150 static void
151 adg_table_init(AdgTable *table)
153 AdgTablePrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(table,
154 ADG_TYPE_TABLE,
155 AdgTablePrivate);
157 data->table_dress = ADG_DRESS_TABLE;
158 data->grid = NULL;
159 data->frame = NULL;
160 data->rows = NULL;
161 data->cell_names = NULL;
163 table->data = data;
166 static void
167 dispose(GObject *object)
169 AdgTablePrivate *data = ((AdgTable *) object)->data;
171 invalidate((AdgEntity *) object);
173 if (data->rows != NULL)
174 g_slist_foreach(data->rows, (GFunc) row_dispose, NULL);
176 if (PARENT_OBJECT_CLASS->dispose != NULL)
177 PARENT_OBJECT_CLASS->dispose(object);
180 static void
181 finalize(GObject *object)
183 AdgTable *table;
184 AdgTablePrivate *data;
186 table = (AdgTable *) object;
187 data = table->data;
189 if (data->rows != NULL) {
190 g_slist_foreach(data->rows, (GFunc) row_free, NULL);
191 g_slist_free(data->rows);
194 if (data->cell_names != NULL)
195 g_hash_table_destroy(data->cell_names);
197 if (PARENT_OBJECT_CLASS->finalize != NULL)
198 PARENT_OBJECT_CLASS->finalize(object);
201 static void
202 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
204 AdgTablePrivate *data = ((AdgTable *) object)->data;
206 switch (prop_id) {
207 case PROP_TABLE_DRESS:
208 g_value_set_int(value, data->table_dress);
209 break;
210 default:
211 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
212 break;
216 static void
217 set_property(GObject *object, guint prop_id,
218 const GValue *value, GParamSpec *pspec)
220 AdgTable *table;
221 AdgTablePrivate *data;
223 table = (AdgTable *) object;
224 data = table->data;
226 switch (prop_id) {
227 case PROP_TABLE_DRESS:
228 adg_dress_set(&data->table_dress, g_value_get_int(value));
229 break;
230 default:
231 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
232 break;
238 * adg_table_new:
240 * Creates a new table entity.
242 * Returns: the newly created table entity
244 AdgTable *
245 adg_table_new(void)
247 return g_object_new(ADG_TYPE_TABLE, NULL);
251 * adg_table_get_table_dress:
252 * @table: an #AdgTable
254 * Gets the table dress to be used in rendering @table.
256 * Returns: the current table dress
258 AdgDress
259 adg_table_get_table_dress(AdgTable *table)
261 AdgTablePrivate *data;
263 g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED);
265 data = table->data;
267 return data->table_dress;
271 * adg_table_set_table_dress:
272 * @table: an #AdgTable
273 * @dress: the new #AdgDress to use
275 * Sets a new table dress for rendering @table. The new dress
276 * must be related to the original dress for this property:
277 * you cannot set a dress used for line styles to a dress
278 * managing fonts.
280 * The check is done by calling adg_dress_are_related() with
281 * @dress and the previous dress as arguments. Check out its
282 * documentation for details on what is a related dress.
284 void
285 adg_table_set_table_dress(AdgTable *table, AdgDress dress)
287 AdgTablePrivate *data;
289 g_return_if_fail(ADG_IS_TABLE(table));
291 data = table->data;
293 if (adg_dress_set(&data->table_dress, dress))
294 g_object_notify((GObject *) table, "table-dress");
298 * adg_table_get_n_rows:
299 * @table: an #AdgTable
301 * Gets the number of rows stored in @table.
303 * Returns: the number of rows or %0 on empty @table or errors
305 guint
306 adg_table_get_n_rows(AdgTable *table)
308 AdgTablePrivate *data;
310 g_return_val_if_fail(ADG_IS_TABLE(table), 0);
312 data = table->data;
314 if (data->rows == NULL)
315 return 0;
317 return g_slist_length(data->rows);
321 * adg_table_row_new:
322 * @table: an #AdgTable
324 * Creates a new empty row and appends it at the end of the rows
325 * yet present in @table. By default, the height of this new
326 * row will be the fallback value provided by the table style:
327 * you can override it by using adg_table_row_set_height().
329 * Returns: the newly created row or %NULL on errors
331 AdgTableRow *
332 adg_table_row_new(AdgTable *table)
334 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
336 return row_new(table, NULL);
340 * adg_table_row_new_before:
341 * @row: a valid #AdgTableRow
343 * Creates a new empty row with default height and inserts it
344 * just before @row.
346 * Returns: the newly created row or %NULL on errors
348 AdgTableRow *
349 adg_table_row_new_before(AdgTableRow *row)
351 g_return_val_if_fail(row != NULL, NULL);
352 g_return_val_if_fail(ADG_IS_TABLE(row->table), NULL);
354 return row_new(row->table, row);
358 * adg_table_row_delete:
359 * @row: a valid #AdgTableRow
361 * Removes @row from its owner table and frees every resources allocated
362 * by it. This means also the eventual cells owned by @row will be freed.
364 void
365 adg_table_row_delete(AdgTableRow *row)
367 AdgTable *table;
368 AdgTablePrivate *data;
370 g_return_if_fail(row != NULL);
372 table = row->table;
374 g_return_if_fail(ADG_IS_TABLE(table));
376 data = table->data;
378 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
379 g_slist_free(row->cells);
380 data->rows = g_slist_remove(data->rows, row);
382 g_free(row);
386 * adg_table_row_get_n_cells:
387 * @row: a valid #AdgTableRow
389 * Gets the number of cells stored in @row.
391 * Returns: the number of cells or %0 on empty row or errors
393 guint
394 adg_table_row_get_n_cells(const AdgTableRow *row)
396 g_return_val_if_fail(row != NULL, 0);
398 if (row->cells == NULL)
399 return 0;
401 return g_slist_length(row->cells);
405 * adg_table_row_set_height:
406 * @row: a valid #AdgTableRow
407 * @height: the new height
409 * Sets a new height on @row. The extents will be invalidated to
410 * recompute the whole layout of the table. Specifying %0 in
411 * @height will use the default height set in the table style.
413 void
414 adg_table_row_set_height(AdgTableRow *row, gdouble height)
416 g_return_if_fail(row != NULL);
418 row->height = height;
420 adg_entity_invalidate((AdgEntity *) row->table);
424 * adg_table_row_extents:
425 * @row: a valid #AdgTableRow
427 * Gets the extents of @row. This function is useful only after the
428 * arrange() phase as in the other situation the extents will likely
429 * be not up to date.
431 * Returns: the extents of @row or %NULL on errors
433 const CpmlExtents *
434 adg_table_row_extents(AdgTableRow *row)
436 g_return_val_if_fail(row != NULL, NULL);
438 return &row->extents;
442 * adg_table_get_cell_by_name:
443 * @table: an #AdgTable
444 * @name: the name of a cell
446 * Gets the cell named @name inside @table.
448 * Returns: the requested cell or %NULL if not found
450 AdgTableCell *
451 adg_table_get_cell_by_name(AdgTable *table, const gchar *name)
453 AdgTablePrivate *data;
455 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
457 data = table->data;
459 if (data->cell_names == NULL)
460 return NULL;
462 return g_hash_table_lookup(data->cell_names, name);
466 * adg_table_cell_new:
467 * @row: a valid #AdgTableRow
468 * @width: width of the cell
470 * Creates a new cell and appends it at the end of the cells
471 * yet present in @row. You can add content to the cell by using
472 * adg_table_cell_set_title() and adg_table_cell_set_value().
474 * A positive @width value specifies the width of this cell in global
475 * space: if the width of its content (that is, either the title or the
476 * value entity) will be greater than @width, it will be rendered
477 * outside the cell boundary box, luckely overwriting the adiacent
478 * cells.
480 * Using %0 as @width means the width of the cell will be automatically
481 * adjusted to the maximum width of its content.
483 * Negative width values are not allowed: this condition will raise
484 * a warning without any further processing.
486 * Returns: the newly created cell or %NULL on errors
488 AdgTableCell *
489 adg_table_cell_new(AdgTableRow *row, gdouble width)
491 g_return_val_if_fail(row != NULL, NULL);
492 g_return_val_if_fail(width >= 0, NULL);
494 return cell_new(row, NULL, width, NULL, NULL, NULL);
498 * adg_table_cell_new_before:
499 * @cell: a valid #AdgTableCell
500 * @width: width of the cell
502 * Creates a new cell and inserts it rigthly before the @cell cell.
503 * This works similarily and accepts the same parameters as the
504 * adg_table_cell_new() function.
506 * Returns: the newly created cell or %NULL on errors
508 AdgTableCell *
509 adg_table_cell_new_before(AdgTableCell *cell, gdouble width)
511 g_return_val_if_fail(cell != NULL, NULL);
512 g_return_val_if_fail(cell->row != NULL, NULL);
513 g_return_val_if_fail(width >= 0, NULL);
515 return cell_new(cell->row, cell, width, NULL, NULL, NULL);
519 * adg_table_cell_new_full:
520 * @row: a valid #AdgTableRow
521 * @width: width of the cell
522 * @name: name to associate
523 * @title: title to render
524 * @value: value to render
526 * A convenient function to append a cell to @row with a specific
527 * title and value text. The font to use for rendering @title and
528 * @value will be picked up from the table style, so be sure to
529 * have the correct table dress set before calling this function.
531 * @row and @width have the same meanings as in adg_table_cell_new():
532 * check its documentation for details.
534 * @name is an optional identifier to univoquely access this cell
535 * by using adg_table_get_cell_by_name(). The identifier must be
536 * univoque: if there is yet a cell with the same name a warning
537 * message will be raised and the function will fail.
539 * Returns: the newly created cell or %NULL on errors
541 AdgTableCell *
542 adg_table_cell_new_full(AdgTableRow *row, gdouble width, const gchar *name,
543 const gchar *title, const gchar *value)
545 AdgEntity *title_entity, *value_entity;
546 AdgTableStyle *table_style;
548 g_return_val_if_fail(row != NULL, NULL);
549 g_return_val_if_fail(ADG_IS_TABLE(row->table), NULL);
551 table_style = (AdgTableStyle *)
552 adg_entity_style((AdgEntity *) row->table,
553 adg_table_get_table_dress(row->table));
555 if (title != NULL) {
556 AdgDress dress = adg_table_style_get_title_dress(table_style);
557 title_entity = g_object_new(ADG_TYPE_TOY_TEXT,
558 "label", title,
559 "font-dress", dress, NULL);
560 } else {
561 title_entity = NULL;
564 if (value != NULL) {
565 AdgDress dress = adg_table_style_get_value_dress(table_style);
566 value_entity = g_object_new(ADG_TYPE_TOY_TEXT,
567 "label", value,
568 "font-dress", dress, NULL);
569 } else {
570 value_entity = NULL;
573 return cell_new(row, NULL, width, name, title_entity, value_entity);
577 * adg_table_cell_delete:
578 * @cell: a valid #AdgTableCell
580 * Deletes @cell removing it from the container row and freeing
581 * any resource associated to it.
583 void
584 adg_table_cell_delete(AdgTableCell *cell)
586 AdgTableRow *row;
588 g_return_if_fail(cell != NULL);
590 row = cell->row;
592 g_return_if_fail(row != NULL);
594 cell_free(cell);
595 row->cells = g_slist_remove(row->cells, cell);
599 * adg_table_cell_get_title:
600 * @cell: a valid #AdgTableCell
602 * Gets the current title of @cell. The returned string is owned
603 * by @cell and must not be modified or freed.
605 * Returns: the title entity or %NULL for undefined title
607 AdgEntity *
608 adg_table_cell_get_title(AdgTableCell *cell)
610 g_return_val_if_fail(cell != NULL, NULL);
612 return cell->title;
616 * adg_table_cell_set_title:
617 * @cell: a valid #AdgTableCell
618 * @title: the new title entity
620 * Sets @title as the new title entity of @cell. The top left
621 * corner of the bounding box of @title will be cohincident to
622 * the top left corner of the cell extents, taking into accounts
623 * eventual padding spaces specified by the table style.
625 * The old internal entity is unrefenrenced while the @title (if
626 * not %NULL) is refenenced with g_object_ref_sink().
628 * @title can be %NULL, in which case the old entity is removed.
630 void
631 adg_table_cell_set_title(AdgTableCell *cell, AdgEntity *title)
633 g_return_if_fail(cell != NULL);
634 g_return_if_fail(title == NULL || ADG_IS_ENTITY(title));
636 if (title == cell->title)
637 return;
639 if (cell->title != NULL)
640 g_object_unref(cell->title);
642 cell->title = title;
644 if (cell->title != NULL)
645 g_object_ref_sink(cell->title);
647 /* Invalidate the whole table if the with of this cell depends
648 * on the cell content */
649 if (cell->width == 0)
650 adg_entity_invalidate((AdgEntity *) cell->row->table);
654 * adg_table_cell_get_value:
655 * @cell: a valid #AdgTableCell
657 * Gets the current value of @cell. The returned string is owned
658 * by @cell and must not be modified or freed.
660 * Returns: the value entity or %NULL for undefined value
662 AdgEntity *
663 adg_table_cell_get_value(AdgTableCell *cell)
665 g_return_val_if_fail(cell != NULL, NULL);
667 return cell->value;
671 * adg_table_cell_set_value:
672 * @cell: a valid #AdgTableCell
673 * @value: the new value entity
675 * Sets @value as the new value entity of @cell. The bottom middle
676 * point of the bounding box of @value will be cohincident to the
677 * bottom middle point of the cell extents, taking into accounts
678 * eventual padding spaces specified by the table style.
680 * The old internal entity is unrefenrenced while the @value (if
681 * not %NULL) is refenenced with g_object_ref_sink().
683 * @value can be %NULL, in which case the old entity is removed.
685 void
686 adg_table_cell_set_value(AdgTableCell *cell, AdgEntity *value)
688 g_return_if_fail(cell != NULL);
689 g_return_if_fail(value == NULL || ADG_IS_ENTITY(value));
691 if (value == cell->value)
692 return;
694 if (cell->value != NULL)
695 g_object_unref(cell->value);
697 cell->value = value;
699 if (cell->value != NULL)
700 g_object_ref_sink(cell->value);
702 /* Invalidate the whole table if the with of this cell depends
703 * on the cell content */
704 if (cell->width == 0)
705 adg_entity_invalidate((AdgEntity *) cell->row->table);
709 * adg_table_cell_set_width:
710 * @cell: a valid #AdgTableCell
711 * @width: the new width
713 * Sets a new width on @cell. The extents on the whole table
714 * will be invalidated, so will be recomputed in the next
715 * arrange() phase.
717 void
718 adg_table_cell_set_width(AdgTableCell *cell, gdouble width)
720 g_return_if_fail(cell != NULL);
722 cell->width = width;
724 adg_entity_invalidate((AdgEntity *) cell->row->table);
728 * adg_table_cell_extents:
729 * @cell: a valid #AdgTableCell
731 * Gets the extents of @cell. This function is useful only after the
732 * arrange() phase as in the other situation the extents will likely
733 * be not up to date.
735 * Returns: the extents of @cell or %NULL on errors
737 const CpmlExtents *
738 adg_table_cell_extents(AdgTableCell *cell)
740 g_return_val_if_fail(cell != NULL, NULL);
742 return &cell->extents;
746 static void
747 invalidate(AdgEntity *entity)
749 AdgTablePrivate *data = ((AdgTable *) entity)->data;
751 if (data->frame != NULL) {
752 g_object_unref(data->frame);
753 data->frame = NULL;
756 if (data->grid != NULL) {
757 g_object_unref(data->grid);
758 data->grid = NULL;
762 static void
763 arrange(AdgEntity *entity)
765 AdgTable *table;
766 AdgTablePrivate *data;
767 CpmlExtents extents;
768 const AdgPair *spacing;
769 GSList *row_node;
770 AdgTableRow *row;
771 gdouble y;
773 table = (AdgTable *) entity;
774 data = table->data;
775 cpml_extents_copy(&extents, adg_entity_extents(entity));
777 /* Resolve the table style */
778 if (data->table_style == NULL)
779 data->table_style = (AdgTableStyle *)
780 adg_entity_style(entity, data->table_dress);
782 if (extents.is_defined)
783 return;
785 spacing = adg_table_style_get_cell_spacing(data->table_style);
786 extents.size.x = 0;
787 extents.size.y = 0;
789 for (row_node = data->rows; row_node; row_node = row_node->next) {
790 row = row_node->data;
792 row_arrange_size(row);
794 if (row->extents.size.x > extents.size.x)
795 extents.size.x = row->extents.size.x;
796 extents.size.y += row->extents.size.y;
799 /* TODO: update the org according to the table alignments */
801 y = extents.org.y + spacing->y;
802 for (row_node = data->rows; row_node; row_node = row_node->next) {
803 row = row_node->data;
805 row->extents.org.x = extents.org.x;
806 row->extents.org.y = y;
808 row_arrange(row);
810 y += row->extents.size.y + spacing->y;
813 extents.is_defined = TRUE;
814 adg_entity_set_extents(entity, &extents);
816 arrange_grid(entity);
817 arrange_frame(entity);
820 static void
821 arrange_grid(AdgEntity *entity)
823 AdgTablePrivate *data;
824 AdgPath *path;
825 AdgTrail *trail;
826 GSList *row_node, *cell_node;
827 AdgTableRow *row;
828 AdgTableCell *cell;
829 AdgPair pair;
830 AdgDress dress;
832 data = ((AdgTable *) entity)->data;
834 if (data->grid != NULL)
835 return;
837 path = adg_path_new();
838 trail = (AdgTrail *) path;
840 for (row_node = data->rows; row_node; row_node = row_node->next) {
841 row = row_node->data;
843 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
844 cell = cell_node->data;
846 if (cell->title == NULL && cell->value == NULL)
847 continue;
849 cpml_pair_copy(&pair, &cell->extents.org);
850 adg_path_move_to(path, &pair);
851 pair.x += cell->extents.size.x;
852 adg_path_line_to(path, &pair);
853 pair.y += cell->extents.size.y;
854 adg_path_line_to(path, &pair);
855 pair.x -= cell->extents.size.x;
856 adg_path_line_to(path, &pair);
857 adg_path_close(path);
861 if (!adg_trail_extents(trail)->is_defined)
862 return;
864 dress = adg_table_style_get_grid_dress(data->table_style);
865 data->grid = g_object_new(ADG_TYPE_STROKE,
866 "line-dress", dress,
867 "trail", trail, NULL);
870 static void
871 arrange_frame(AdgEntity *entity)
873 AdgTablePrivate *data;
874 AdgPath *path;
875 const CpmlExtents *extents;
876 AdgPair pair;
877 AdgDress dress;
879 data = ((AdgTable *) entity)->data;
881 if (data->frame != NULL)
882 return;
884 path = adg_path_new();
885 extents = adg_entity_extents(entity);
887 cpml_pair_copy(&pair, &extents->org);
888 adg_path_move_to(path, &pair);
889 pair.x += extents->size.x;
890 adg_path_line_to(path, &pair);
891 pair.y += extents->size.y;
892 adg_path_line_to(path, &pair);
893 pair.x -= extents->size.x;
894 adg_path_line_to(path, &pair);
895 adg_path_close(path);
897 dress = adg_table_style_get_frame_dress(data->table_style);
899 data->frame = g_object_new(ADG_TYPE_STROKE,
900 "line-dress", dress,
901 "trail", (AdgTrail *) path, NULL);
904 static void
905 render(AdgEntity *entity, cairo_t *cr)
907 AdgTable *table;
908 AdgTablePrivate *data;
909 GSList *row_node;
911 table = (AdgTable *) entity;
912 data = table->data;
914 if (data->frame != NULL)
915 adg_entity_render((AdgEntity *) data->frame, cr);
917 if (data->grid != NULL)
918 adg_entity_render((AdgEntity *) data->grid, cr);
920 for (row_node = data->rows; row_node; row_node = row_node->next)
921 row_render(row_node->data, cr);
924 static AdgTableRow *
925 row_new(AdgTable *table, AdgTableRow *before_row)
927 AdgTablePrivate *data;
928 AdgTableRow *new_row;
930 data = table->data;
931 new_row = g_new(AdgTableRow, 1);
932 new_row->table = table;
933 new_row->cells = NULL;
934 new_row->height = 0;
935 new_row->extents.is_defined = FALSE;
937 if (before_row == NULL) {
938 data->rows = g_slist_append(data->rows, new_row);
939 } else {
940 GSList *before_node = g_slist_find(data->rows, before_row);
942 /* This MUST be present, otherwise something really bad happened */
943 g_assert(before_node != NULL);
945 data->rows = g_slist_insert_before(data->rows, before_node, new_row);
948 invalidate((AdgEntity *) table);
950 return new_row;
953 static void
954 row_arrange_size(AdgTableRow *row)
956 AdgTableStyle *table_style;
957 const AdgPair *spacing;
958 CpmlVector *size;
959 AdgTableCell *cell;
960 GSList *cell_node;
962 table_style = GET_TABLE_STYLE(row->table);
963 spacing = adg_table_style_get_cell_spacing(table_style);
964 size = &row->extents.size;
966 size->x = 0;
967 if (row->height == 0)
968 size->y = adg_table_style_get_row_height(table_style);
969 else
970 size->y = row->height;
972 /* Compute the row width by summing every cell width */
973 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
974 cell = cell_node->data;
976 cell_arrange_size(cell);
978 size->x += cell->extents.size.x + spacing->x;
981 if (size->x > 0)
982 size->x += spacing->x;
985 /* Before calling this function, row->extents should be updated */
986 static void
987 row_arrange(AdgTableRow *row)
989 AdgTableStyle *table_style;
990 const AdgPair *spacing;
991 const AdgPair *org;
992 AdgTableCell *cell;
993 GSList *cell_node;
994 gdouble x;
996 table_style = GET_TABLE_STYLE(row->table);
997 spacing = adg_table_style_get_cell_spacing(table_style);
998 org = &row->extents.org;
999 x = org->x + spacing->x;
1001 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1002 cell = cell_node->data;
1004 cell->extents.org.x = x;
1005 cell->extents.org.y = org->y;
1007 cell_arrange(cell);
1009 x += cell->extents.size.x + spacing->x;
1012 row->extents.is_defined = TRUE;
1015 static void
1016 row_render(AdgTableRow *row, cairo_t *cr)
1018 GSList *cell_node;
1020 for (cell_node = row->cells; cell_node; cell_node = cell_node->next)
1021 cell_render(cell_node->data, cr);
1024 static void
1025 row_dispose(AdgTableRow *row)
1027 g_slist_foreach(row->cells, (GFunc) cell_dispose, NULL);
1030 static void
1031 row_free(AdgTableRow *row)
1033 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
1034 g_slist_free(row->cells);
1036 g_free(row);
1039 static AdgTableCell *
1040 cell_new(AdgTableRow *row, AdgTableCell *before_cell, gdouble width,
1041 const gchar *name, AdgEntity *title, AdgEntity *value)
1043 AdgTablePrivate *data;
1044 AdgTableCell *new_cell;
1046 data = row->table->data;
1048 if (name != NULL) {
1049 if (data->cell_names == NULL) {
1050 data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal,
1051 g_free, NULL);
1052 } else if (g_hash_table_lookup(data->cell_names, name) != NULL) {
1053 g_warning(_("%s: `%s' cell name is yet used"),
1054 G_STRLOC, name);
1055 return NULL;
1059 new_cell = g_new(AdgTableCell, 1);
1060 new_cell->row = row;
1061 new_cell->width = width;
1062 new_cell->title = title;
1063 new_cell->value = value;
1064 new_cell->extents.is_defined = FALSE;
1066 if (title != NULL)
1067 g_object_ref_sink(title);
1069 if (value != NULL)
1070 g_object_ref_sink(value);
1072 if (before_cell == NULL) {
1073 row->cells = g_slist_append(row->cells, new_cell);
1074 } else {
1075 GSList *before_node = g_slist_find(row->cells, before_cell);
1077 /* This MUST be present, otherwise something really bad happened */
1078 g_assert(before_node != NULL);
1080 row->cells = g_slist_insert_before(row->cells, before_node, new_cell);
1083 if (name != NULL)
1084 g_hash_table_insert(data->cell_names, g_strdup(name), new_cell);
1086 return new_cell;
1089 static void
1090 cell_arrange_size(AdgTableCell *cell)
1092 AdgTableStyle *table_style;
1093 CpmlVector *size;
1095 table_style = GET_TABLE_STYLE(cell->row->table);
1096 size = &cell->extents.size;
1098 if (cell->title != NULL)
1099 adg_entity_arrange(cell->title);
1101 if (cell->value != NULL)
1102 adg_entity_arrange(cell->value);
1104 size->y = cell->row->extents.size.y;
1106 if (cell->width == 0) {
1107 const CpmlVector *content_size;
1109 /* The width depends on the cell content (default: 0) */
1110 size->x = 0;
1112 if (cell->title != NULL) {
1113 content_size = &adg_entity_extents(cell->title)->size;
1114 size->x = content_size->x;
1117 if (cell->value != NULL) {
1118 content_size = &adg_entity_extents(cell->value)->size;
1119 if (content_size->x > size->x)
1120 size->x = content_size->x;
1123 size->x += adg_table_style_get_cell_padding(table_style)->x * 2;
1124 } else {
1125 size->x = cell->width;
1129 /* Before calling this function, cell->extents should be updated */
1130 static void
1131 cell_arrange(AdgTableCell *cell)
1133 AdgTableStyle *table_style;
1134 const AdgPair *padding;
1135 const CpmlPair *org;
1136 const CpmlVector *size, *content_size;
1137 AdgMatrix map;
1139 table_style = GET_TABLE_STYLE(cell->row->table);
1140 padding = adg_table_style_get_cell_padding(table_style);
1141 org = &cell->extents.org;
1142 size = &cell->extents.size;
1144 if (cell->title != NULL) {
1145 content_size = &adg_entity_extents(cell->title)->size;
1147 cairo_matrix_init_translate(&map,
1148 org->x + padding->x,
1149 org->y + content_size->y + padding->y);
1151 adg_entity_set_global_map(cell->title, &map);
1154 if (cell->value != NULL) {
1155 content_size = &adg_entity_extents(cell->value)->size;
1157 cairo_matrix_init_translate(&map,
1158 org->x + (size->x - content_size->x) / 2,
1159 org->y + size->y - padding->y);
1161 adg_entity_set_global_map(cell->value, &map);
1164 cell->extents.is_defined = TRUE;
1167 static void
1168 cell_render(AdgTableCell *cell, cairo_t *cr)
1170 if (cell->title != NULL)
1171 adg_entity_render(cell->title, cr);
1173 if (cell->value != NULL)
1174 adg_entity_render(cell->value, cr);
1177 static void
1178 cell_dispose(AdgTableCell *cell)
1180 if (cell->title != NULL) {
1181 g_object_unref(cell->title);
1182 cell->title = NULL;
1185 if (cell->value != NULL) {
1186 g_object_unref(cell->value);
1187 cell->value = NULL;
1191 static void
1192 cell_free(AdgTableCell *cell)
1194 AdgTablePrivate *data = cell->row->table->data;
1196 g_hash_table_foreach_remove(data->cell_names, value_match, cell);
1197 cell_dispose(cell);
1199 g_free(cell);
1202 static gboolean
1203 value_match(gpointer key, gpointer value, gpointer user_data)
1205 return value == user_data;