[AdgEdges] Added guard against invalid "source" properties
[adg.git] / adg / adg-table.c
blobd20b4dc03df0a9a368837e842d925d7037dcb7a5
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009,2010 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 could 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.
37 * The default title is placed at the upper left corner of the cell
38 * while the value is centered up to the bottom edge of the cell.
39 * Anyway, the value position can be customized by using the
40 * adg_table_cell_set_value_pos() method. Anyway, both entities react
41 * to the common map displacements.
43 * Some convenient functions to easily create title and value entities
44 * with plain text are provided: adg_table_cell_new_full(),
45 * adg_table_cell_set_text_title() and adg_table_cell_set_text_value().
46 * When using these methods keep in mind the underlying #AdgToyText
47 * entities will be displaced accordingly to the
48 * #AdgTableStyle:cell-padding value (not used when setting the
49 * entities throught other APIs).
50 **/
52 /**
53 * AdgTable:
55 * All fields are private and should not be used directly.
56 * Use its public methods instead.
57 **/
59 /**
60 * AdgTableRow:
62 * An opaque structure referring to a row of an #AdgTable. Any
63 * table can have an unlimited number of rows.
64 **/
66 /**
67 * AdgTableCell:
69 * An opaque structure referring to the cell of an #AdgTableRow.
70 * Any row can have an unlimited number of cells.
71 **/
74 #include "adg-internal.h"
75 #include "adg-table.h"
76 #include "adg-table-private.h"
77 #include "adg-alignment.h"
78 #include "adg-dress-builtins.h"
79 #include "adg-path.h"
80 #include "adg-line-style.h"
81 #include "adg-toy-text.h"
83 #define PARENT_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class)
84 #define PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class)
87 enum {
88 PROP_0,
89 PROP_TABLE_DRESS,
90 PROP_HAS_FRAME
93 static void dispose (GObject *object);
94 static void finalize (GObject *object);
95 static void get_property (GObject *object,
96 guint param_id,
97 GValue *value,
98 GParamSpec *pspec);
99 static void set_property (GObject *object,
100 guint param_id,
101 const GValue *value,
102 GParamSpec *pspec);
103 static void global_changed (AdgEntity *entity);
104 static void local_changed (AdgEntity *entity);
105 static void invalidate (AdgEntity *entity);
106 static void arrange (AdgEntity *entity);
107 static void arrange_grid (AdgEntity *entity);
108 static void arrange_frame (AdgEntity *entity);
109 static void render (AdgEntity *entity,
110 cairo_t *cr);
111 static gboolean switch_frame (AdgTable *table,
112 gboolean new_state);
113 static void propagate (AdgTable *table,
114 const gchar *detailed_signal,
115 ...);
116 static AdgTableRow * row_new (AdgTable *table,
117 AdgTableRow *before_row);
118 static void row_arrange_size (AdgTableRow *row);
119 static void row_arrange (AdgTableRow *row);
120 static void row_dispose (AdgTableRow *row);
121 static void row_free (AdgTableRow *row);
122 static AdgTableCell * cell_new (AdgTableRow *row,
123 AdgTableCell *before_cell,
124 gdouble width,
125 gboolean has_frame,
126 const gchar *name,
127 AdgEntity *title,
128 AdgEntity *value);
129 static void cell_set_name (AdgTableCell *cell,
130 const gchar *name);
131 static gboolean cell_set_title (AdgTableCell *cell,
132 AdgEntity *title);
133 static gboolean cell_set_value (AdgTableCell *cell,
134 AdgEntity *value);
135 static void cell_set_value_pos (AdgTableCell *cell,
136 const AdgPair *from_factor,
137 const AdgPair *to_factor);
138 static void cell_arrange_size (AdgTableCell *cell);
139 static void cell_arrange (AdgTableCell *cell);
140 static void cell_dispose (AdgTableCell *cell);
141 static void cell_free (AdgTableCell *cell);
142 static gboolean value_match (gpointer key,
143 gpointer value,
144 gpointer user_data);
147 G_DEFINE_TYPE(AdgTable, adg_table, ADG_TYPE_ENTITY);
150 static void
151 adg_table_class_init(AdgTableClass *klass)
153 GObjectClass *gobject_class;
154 AdgEntityClass *entity_class;
155 GParamSpec *param;
157 gobject_class = (GObjectClass *) klass;
158 entity_class = (AdgEntityClass *) klass;
160 g_type_class_add_private(klass, sizeof(AdgTablePrivate));
162 gobject_class->dispose = dispose;
163 gobject_class->finalize = finalize;
164 gobject_class->get_property = get_property;
165 gobject_class->set_property = set_property;
167 entity_class->global_changed = global_changed;
168 entity_class->local_changed = local_changed;
169 entity_class->invalidate = invalidate;
170 entity_class->arrange = arrange;
171 entity_class->render = render;
173 param = adg_param_spec_dress("table-dress",
174 P_("Table Dress"),
175 P_("The dress to use for stroking this entity"),
176 ADG_DRESS_TABLE,
177 G_PARAM_READWRITE);
178 g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param);
180 param = g_param_spec_boolean("has-frame",
181 P_("Has Frame Flag"),
182 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
183 TRUE,
184 G_PARAM_READWRITE);
185 g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param);
188 static void
189 adg_table_init(AdgTable *table)
191 AdgTablePrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(table,
192 ADG_TYPE_TABLE,
193 AdgTablePrivate);
195 data->table_dress = ADG_DRESS_TABLE;
196 data->has_frame = TRUE;
198 data->table_style = NULL;
199 data->grid = NULL;
200 data->frame = NULL;
201 data->rows = NULL;
202 data->cell_names = NULL;
204 table->data = data;
207 static void
208 dispose(GObject *object)
210 AdgTablePrivate *data = ((AdgTable *) object)->data;
212 if (data->grid != NULL) {
213 g_object_unref(data->grid);
214 data->grid = NULL;
217 if (data->frame != NULL) {
218 g_object_unref(data->frame);
219 data->frame = NULL;
222 /* The rows finalization will happen in the finalize() method */
223 if (data->rows != NULL)
224 g_slist_foreach(data->rows, (GFunc) row_dispose, NULL);
226 if (PARENT_OBJECT_CLASS->dispose)
227 PARENT_OBJECT_CLASS->dispose(object);
230 static void
231 finalize(GObject *object)
233 AdgTable *table;
234 AdgTablePrivate *data;
236 table = (AdgTable *) object;
237 data = table->data;
239 if (data->rows != NULL) {
240 g_slist_foreach(data->rows, (GFunc) row_free, NULL);
241 g_slist_free(data->rows);
244 if (data->cell_names != NULL)
245 g_hash_table_destroy(data->cell_names);
247 if (PARENT_OBJECT_CLASS->finalize)
248 PARENT_OBJECT_CLASS->finalize(object);
251 static void
252 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
254 AdgTablePrivate *data = ((AdgTable *) object)->data;
256 switch (prop_id) {
257 case PROP_TABLE_DRESS:
258 g_value_set_int(value, data->table_dress);
259 break;
260 case PROP_HAS_FRAME:
261 g_value_set_boolean(value, data->has_frame);
262 break;
263 default:
264 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
265 break;
269 static void
270 set_property(GObject *object, guint prop_id,
271 const GValue *value, GParamSpec *pspec)
273 AdgTable *table;
274 AdgTablePrivate *data;
276 table = (AdgTable *) object;
277 data = table->data;
279 switch (prop_id) {
280 case PROP_TABLE_DRESS:
281 adg_dress_set(&data->table_dress, g_value_get_int(value));
282 break;
283 case PROP_HAS_FRAME:
284 switch_frame(table, g_value_get_boolean(value));
285 break;
286 default:
287 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
288 break;
294 * adg_table_new:
296 * Creates a new empty table entity. The #AdgEntity:local-method
297 * property is set by default to #ADG_MIX_DISABLED, that is the
298 * table is not subject to any local transformations.
300 * Returns: the newly created table entity
302 AdgTable *
303 adg_table_new(void)
305 return g_object_new(ADG_TYPE_TABLE,
306 "local-method", ADG_MIX_DISABLED, NULL);
310 * adg_table_set_table_dress:
311 * @table: an #AdgTable
312 * @dress: the new #AdgDress to use
314 * Sets a new table dress for rendering @table. The new dress
315 * must be related to the original dress for this property:
316 * you cannot set a dress used for line styles to a dress
317 * managing fonts.
319 * The check is done by calling adg_dress_are_related() with
320 * @dress and the previous dress as arguments. Check out its
321 * documentation for details on what is a related dress.
323 void
324 adg_table_set_table_dress(AdgTable *table, AdgDress dress)
326 AdgTablePrivate *data;
328 g_return_if_fail(ADG_IS_TABLE(table));
330 data = table->data;
332 if (adg_dress_set(&data->table_dress, dress))
333 g_object_notify((GObject *) table, "table-dress");
337 * adg_table_get_table_dress:
338 * @table: an #AdgTable
340 * Gets the table dress to be used in rendering @table.
342 * Returns: the current table dress
344 AdgDress
345 adg_table_get_table_dress(AdgTable *table)
347 AdgTablePrivate *data;
349 g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED);
351 data = table->data;
353 return data->table_dress;
357 * adg_table_switch_frame:
358 * @table: an #AdgTable
359 * @new_state: the new state of the frame
361 * Sets the #AdgTable:has-frame property: %TRUE will draw a
362 * frame around the whole table using the #AdgTableStyle:frame-dress
363 * dress of the table style.
365 void
366 adg_table_switch_frame(AdgTable *table, gboolean new_state)
368 g_return_if_fail(ADG_IS_TABLE(table));
370 if (switch_frame(table, new_state))
371 g_object_notify((GObject *) table, "has-frame");
375 * adg_table_has_frame:
376 * @table: an #AdgTable
378 * Returns the state of the #AdgTable:has-frame property.
380 * Returns: the current state
382 gboolean
383 adg_table_has_frame(AdgTable *table)
385 AdgTablePrivate *data;
387 g_return_val_if_fail(ADG_IS_TABLE(table), FALSE);
389 data = table->data;
391 return data->has_frame;
395 * adg_table_get_n_rows:
396 * @table: an #AdgTable
398 * Gets the number of rows stored in @table.
400 * Returns: the number of rows or %0 on empty @table or errors
402 guint
403 adg_table_get_n_rows(AdgTable *table)
405 AdgTablePrivate *data;
407 g_return_val_if_fail(ADG_IS_TABLE(table), 0);
409 data = table->data;
411 if (data->rows == NULL)
412 return 0;
414 return g_slist_length(data->rows);
418 * adg_table_row_new:
419 * @table: an #AdgTable
421 * Creates a new empty row and appends it at the end of the rows
422 * yet present in @table. By default, the height of this new
423 * row will be the fallback value provided by the table style:
424 * you can override it by using adg_table_row_set_height().
426 * Returns: the newly created row or %NULL on errors
428 AdgTableRow *
429 adg_table_row_new(AdgTable *table)
431 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
433 return row_new(table, NULL);
437 * adg_table_row_new_before:
438 * @row: a valid #AdgTableRow
440 * Creates a new empty row with default height and inserts it
441 * just before @row.
443 * Returns: the newly created row or %NULL on errors
445 AdgTableRow *
446 adg_table_row_new_before(AdgTableRow *row)
448 g_return_val_if_fail(row != NULL, NULL);
449 g_return_val_if_fail(ADG_IS_TABLE(row->table), NULL);
451 return row_new(row->table, row);
455 * adg_table_row_delete:
456 * @row: a valid #AdgTableRow
458 * Removes @row from its owner table and frees every resources allocated
459 * by it. This means also the eventual cells owned by @row will be freed.
461 void
462 adg_table_row_delete(AdgTableRow *row)
464 AdgTable *table;
465 AdgTablePrivate *data;
467 g_return_if_fail(row != NULL);
469 table = row->table;
471 g_return_if_fail(ADG_IS_TABLE(table));
473 data = table->data;
475 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
476 g_slist_free(row->cells);
477 data->rows = g_slist_remove(data->rows, row);
479 g_free(row);
483 * adg_table_row_get_n_cells:
484 * @row: a valid #AdgTableRow
486 * Gets the number of cells stored in @row.
488 * Returns: the number of cells or %0 on empty row or errors
490 guint
491 adg_table_row_get_n_cells(const AdgTableRow *row)
493 g_return_val_if_fail(row != NULL, 0);
495 if (row->cells == NULL)
496 return 0;
498 return g_slist_length(row->cells);
502 * adg_table_row_set_height:
503 * @row: a valid #AdgTableRow
504 * @height: the new height
506 * Sets a new height on @row. The extents will be invalidated to
507 * recompute the whole layout of the table. Specifying %0 in
508 * @height will use the default height set in the table style.
510 void
511 adg_table_row_set_height(AdgTableRow *row, gdouble height)
513 g_return_if_fail(row != NULL);
515 row->height = height;
517 adg_entity_invalidate((AdgEntity *) row->table);
521 * adg_table_row_get_height:
522 * @row: a valid #AdgTableRow
524 * Gets the height of @row.
526 * Returns: the requested height or %0 on errors
528 gdouble
529 adg_table_row_get_height(AdgTableRow *row)
531 g_return_val_if_fail(row != NULL, 0.);
533 return row->height;
537 * adg_table_row_get_extents:
538 * @row: a valid #AdgTableRow
540 * Gets the extents of @row. This function is useful only after the
541 * arrange() phase as in the other situation the extents will likely
542 * be not up to date.
544 * Returns: the extents of @row or %NULL on errors
546 const CpmlExtents *
547 adg_table_row_get_extents(AdgTableRow *row)
549 g_return_val_if_fail(row != NULL, NULL);
551 return &row->extents;
555 * adg_table_cell_new:
556 * @row: a valid #AdgTableRow
557 * @width: width of the cell
559 * Creates a new empty cell without a frame and appends it at the
560 * end of the cells yet present in @row. You can add content to the
561 * cell by using adg_table_cell_set_title() and
562 * adg_table_cell_set_value() or enable the frame with
563 * adg_table_cell_switch_frame().
565 * A positive @width value specifies the width of this cell in global
566 * space: if the width of its content (that is, either the title or the
567 * value entity) will be greater than @width, it will be rendered
568 * outside the cell boundary box, luckely overwriting the adiacent
569 * cells.
571 * Using %0 as @width means the width of the cell will be automatically
572 * adjusted to the maximum width of its content.
574 * Negative width values are not allowed: this condition will raise
575 * a warning without any further processing.
577 * Returns: the newly created cell or %NULL on errors
579 AdgTableCell *
580 adg_table_cell_new(AdgTableRow *row, gdouble width)
582 g_return_val_if_fail(row != NULL, NULL);
583 g_return_val_if_fail(width >= 0, NULL);
585 return cell_new(row, NULL, width, FALSE, NULL, NULL, NULL);
589 * adg_table_cell_new_before:
590 * @cell: a valid #AdgTableCell
591 * @width: width of the cell
593 * Creates a new cell and inserts it rigthly before the @cell cell.
594 * This works similarily and accepts the same parameters as the
595 * adg_table_cell_new() function.
597 * Returns: the newly created cell or %NULL on errors
599 AdgTableCell *
600 adg_table_cell_new_before(AdgTableCell *cell, gdouble width)
602 g_return_val_if_fail(cell != NULL, NULL);
603 g_return_val_if_fail(cell->row != NULL, NULL);
604 g_return_val_if_fail(width >= 0, NULL);
606 return cell_new(cell->row, cell, width, FALSE, NULL, NULL, NULL);
610 * adg_table_cell_new_full:
611 * @row: a valid #AdgTableRow
612 * @width: width of the cell
613 * @name: name to associate
614 * @title: title to render
615 * @value: value to render
617 * A convenient function to append a framed cell to @row with a
618 * specific title and value text. The font to use for rendering
619 * @title and @value will be picked up from the table style, so
620 * be sure to have the correct table dress set before calling
621 * this function.
623 * @row and @width have the same meanings as in adg_table_cell_new():
624 * check its documentation for details.
626 * @name is an optional identifier to univoquely access this cell
627 * by using adg_table_cell(). The identifier must be univoque:
628 * if there is yet a cell with the same name a warning message will
629 * be raised and the function will fail.
631 * Returns: the newly created cell or %NULL on errors
633 AdgTableCell *
634 adg_table_cell_new_full(AdgTableRow *row, gdouble width, const gchar *name,
635 const gchar *title, const gchar *value)
637 AdgTableCell *cell;
639 g_return_val_if_fail(row != NULL, NULL);
641 cell = cell_new(row, NULL, width, TRUE, name, NULL, NULL);
643 if (title != NULL)
644 adg_table_cell_set_text_title(cell, title);
646 if (value != NULL)
647 adg_table_cell_set_text_value(cell, value);
649 return cell;
653 * adg_table_cell:
654 * @table: an #AdgTable
655 * @name: the name of a cell
657 * Gets the cell named @name inside @table. Only named cells can be
658 * retrieved by this method.
660 * Returns: the requested cell or %NULL if not found
662 AdgTableCell *
663 adg_table_cell(AdgTable *table, const gchar *name)
665 AdgTablePrivate *data;
667 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
669 data = table->data;
671 if (data->cell_names == NULL)
672 return NULL;
674 return g_hash_table_lookup(data->cell_names, name);
678 * adg_table_cell_delete:
679 * @cell: a valid #AdgTableCell
681 * Deletes @cell removing it from the container row and freeing
682 * any resource associated to it.
684 void
685 adg_table_cell_delete(AdgTableCell *cell)
687 AdgTableRow *row;
689 g_return_if_fail(cell != NULL);
691 row = cell->row;
693 g_return_if_fail(row != NULL);
695 cell_free(cell);
696 row->cells = g_slist_remove(row->cells, cell);
700 * adg_table_cell_set_name:
701 * @cell: a valid #AdgTableCell
702 * @name: the new name of @cell
704 * Sets a new name on @cell: this will allow to access @cell by
705 * name at a later time using the adg_table_cell() API.
707 void
708 adg_table_cell_set_name(AdgTableCell *cell, const gchar *name)
710 AdgTablePrivate *data;
712 g_return_if_fail(cell != NULL);
714 data = cell->row->table->data;
716 cell_set_name(cell, NULL);
717 cell_set_name(cell, name);
721 * adg_table_cell_get_name:
722 * @cell: a valid #AdgTableCell
724 * Gets the name assigned to @cell. This function is highly inefficient
725 * as the cell names are stored in a hash table optimized to get a cell
726 * from a name. Getting the name from a cell involves a full hash table
727 * inspection.
729 * Returns: the name bound of @cell or %NULL on no name or errors
731 const gchar *
732 adg_table_cell_get_name(AdgTableCell *cell)
734 AdgTablePrivate *data;
736 g_return_val_if_fail(cell != NULL, NULL);
738 data = cell->row->table->data;
740 return g_hash_table_find(data->cell_names, value_match, cell);
744 * adg_table_cell_set_title:
745 * @cell: a valid #AdgTableCell
746 * @title: the new title entity
748 * Sets @title as the new title entity of @cell. The top left
749 * corner of the bounding box of @title will be cohincident to
750 * the top left corner of the cell extents, taking into accounts
751 * eventual padding spaces specified by the table style.
753 * The old internal entity is unrefenrenced while the @title (if
754 * not %NULL) is refenenced with g_object_ref_sink().
756 * @title can be %NULL, in which case the old entity is removed.
758 void
759 adg_table_cell_set_title(AdgTableCell *cell, AdgEntity *title)
761 g_return_if_fail(cell != NULL);
762 g_return_if_fail(title == NULL || ADG_IS_ENTITY(title));
764 if (cell_set_title(cell, title))
765 adg_entity_invalidate((AdgEntity *) cell->row->table);
769 * adg_table_cell_set_text_title:
770 * @cell: a valid #AdgTableCell
771 * @title: a text string
773 * Convenient function to set a the title of a cell using an #AdgToyText
774 * entity with the font dress picked from #AdgTable:table-dress with
775 * a call to adg_table_style_get_title_dress().
777 void
778 adg_table_cell_set_text_title(AdgTableCell *cell, const gchar *title)
780 AdgTable *table;
781 AdgEntity *entity;
782 AdgTableStyle *table_style;
783 const AdgPair *padding;
784 AdgDress table_dress, font_dress;
785 AdgMatrix map;
787 g_return_if_fail(cell != NULL);
789 if (title == NULL)
790 adg_table_cell_set_title(cell, NULL);
792 if (cell->title != NULL) {
793 const gchar *old_title;
795 if (ADG_IS_TOY_TEXT(cell->title))
796 old_title = adg_toy_text_get_label((AdgToyText *) cell->title);
797 else
798 old_title = NULL;
800 if (adg_strcmp(title, old_title) == 0)
801 return;
804 table = cell->row->table;
805 table_dress = adg_table_get_table_dress(table);
806 table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table,
807 table_dress);
808 padding = adg_table_style_get_cell_padding(table_style);
809 font_dress = adg_table_style_get_title_dress(table_style);
810 entity = g_object_new(ADG_TYPE_TOY_TEXT, "label", title,
811 "font-dress", font_dress, NULL);
813 cairo_matrix_init_translate(&map, padding->x, padding->y);
814 adg_entity_set_global_map(entity, &map);
816 adg_table_cell_set_title(cell, entity);
820 * adg_table_cell_title:
821 * @cell: a valid #AdgTableCell
823 * Gets the current title of @cell. The returned string is owned
824 * by @cell and must not be modified or freed.
826 * Returns: the title entity or %NULL for undefined title
828 AdgEntity *
829 adg_table_cell_title(AdgTableCell *cell)
831 g_return_val_if_fail(cell != NULL, NULL);
833 return cell->title;
837 * adg_table_cell_set_value:
838 * @cell: a valid #AdgTableCell
839 * @value: the new value entity
841 * Sets @value as the new value entity of @cell. The bottom middle
842 * point of the bounding box of @value will be cohincident to the
843 * bottom middle point of the cell extents, taking into accounts
844 * eventual padding spaces specified by the table style.
846 * The old internal entity is unrefenrenced while the @value (if
847 * not %NULL) is refenenced with g_object_ref_sink().
849 * @value can be %NULL, in which case the old entity is removed.
851 void
852 adg_table_cell_set_value(AdgTableCell *cell, AdgEntity *value)
854 g_return_if_fail(cell != NULL);
855 g_return_if_fail(value == NULL || ADG_IS_ENTITY(value));
857 if (cell_set_value(cell, value))
858 adg_entity_invalidate((AdgEntity *) cell->row->table);
862 * adg_table_cell_set_text_value:
863 * @cell: a valid #AdgTableCell
864 * @value: a text string
866 * Convenient function to set a the value of a cell using an #AdgToyText
867 * entity with a value font dress picked from #AdgTable:table-dress with
868 * a call to adg_table_style_get_value_dress().
870 void
871 adg_table_cell_set_text_value(AdgTableCell *cell, const gchar *value)
873 AdgTable *table;
874 AdgEntity *entity;
875 AdgTableStyle *table_style;
876 const AdgPair *padding;
877 AdgDress table_dress, font_dress;
878 AdgMatrix map;
880 g_return_if_fail(cell != NULL);
882 if (value == NULL)
883 adg_table_cell_set_value(cell, NULL);
885 if (cell->value != NULL) {
886 const gchar *old_value;
888 if (ADG_IS_TOY_TEXT(cell->value))
889 old_value = adg_toy_text_get_label((AdgToyText *) cell->value);
890 else
891 old_value = NULL;
893 if (adg_strcmp(value, old_value) == 0)
894 return;
897 table = cell->row->table;
898 table_dress = adg_table_get_table_dress(table);
899 table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table,
900 table_dress);
901 padding = adg_table_style_get_cell_padding(table_style);
902 font_dress = adg_table_style_get_value_dress(table_style);
903 entity = g_object_new(ADG_TYPE_TOY_TEXT, "label", value,
904 "font-dress", font_dress, NULL);
906 cairo_matrix_init_translate(&map, 0, -padding->y);
907 adg_entity_set_global_map(entity, &map);
909 adg_table_cell_set_value(cell, entity);
913 * adg_table_cell_value:
914 * @cell: a valid #AdgTableCell
916 * Gets the current value of @cell. The returned string is owned
917 * by @cell and must not be modified or freed.
919 * Returns: the value entity or %NULL for undefined value
921 AdgEntity *
922 adg_table_cell_value(AdgTableCell *cell)
924 g_return_val_if_fail(cell != NULL, NULL);
926 return cell->value;
930 * adg_table_cell_set_value_pos:
931 * @cell: a valid #AdgTableCell
932 * @from_factor: the alignment factor on the value entity
933 * @to_factor: the alignment factor on the cell
935 * Sets a new custom position for the value entity of @cell. The
936 * @from_factor specifies the source point (as a fraction of the
937 * value extents) while the @to_factor is the destination point
938 * (specified as a fraction of the cell extents) the source point
939 * must be moved to.
941 void
942 adg_table_cell_set_value_pos(AdgTableCell *cell, const AdgPair *from_factor,
943 const AdgPair *to_factor)
945 g_return_if_fail(cell != NULL);
946 g_return_if_fail(cell->value != NULL);
948 cell_set_value_pos(cell, from_factor, to_factor);
952 * adg_table_cell_set_width:
953 * @cell: a valid #AdgTableCell
954 * @width: the new width
956 * Sets a new width on @cell. The extents on the whole table
957 * will be invalidated, so will be recomputed in the next
958 * arrange() phase.
960 void
961 adg_table_cell_set_width(AdgTableCell *cell, gdouble width)
963 g_return_if_fail(cell != NULL);
965 cell->width = width;
967 adg_entity_invalidate((AdgEntity *) cell->row->table);
971 * adg_table_cell_get_width:
972 * @cell: a valid #AdgTableCell
974 * Gets the width of @cell.
976 * Returns: the requested width or %0 on errors
978 gdouble
979 adg_table_cell_get_width(AdgTableCell *cell)
981 g_return_val_if_fail(cell != NULL, 0.);
983 return cell->width;
987 * adg_table_cell_switch_frame:
988 * @cell: a valid #AdgTableCell
989 * @new_state: the new frame state
991 * Sets the frame flag of @cell: if @new_state is %TRUE, a frame around
992 * @cell will be rendered using the #AdgTableStyle:cell-dress dress
993 * of the table style.
995 void
996 adg_table_cell_switch_frame(AdgTableCell *cell, gboolean new_state)
998 AdgTablePrivate *data;
1000 g_return_if_fail(cell != NULL);
1002 if (cell->has_frame == new_state)
1003 return;
1005 data = cell->row->table->data;
1006 cell->has_frame = new_state;
1008 if (data->grid != NULL) {
1009 g_object_unref(data->grid);
1010 data->grid = NULL;
1015 * adg_table_cell_has_frame:
1016 * @cell: a valid #AdgTableCell
1018 * Gets the frame flag of @cell.
1020 * Returns: the frame flag
1022 gboolean
1023 adg_table_cell_has_frame(AdgTableCell *cell)
1025 g_return_val_if_fail(cell != NULL, FALSE);
1027 return cell->has_frame;
1031 * adg_table_cell_get_extents:
1032 * @cell: a valid #AdgTableCell
1034 * Gets the extents of @cell. This function is useful only after the
1035 * arrange() phase as in the other situation the extents will likely
1036 * be not up to date.
1038 * Returns: the extents of @cell or %NULL on errors
1040 const CpmlExtents *
1041 adg_table_cell_get_extents(AdgTableCell *cell)
1043 g_return_val_if_fail(cell != NULL, NULL);
1045 return &cell->extents;
1049 static void
1050 global_changed(AdgEntity *entity)
1052 if (PARENT_ENTITY_CLASS->global_changed)
1053 PARENT_ENTITY_CLASS->global_changed(entity);
1055 propagate((AdgTable *) entity, "global-changed");
1058 static void
1059 local_changed(AdgEntity *entity)
1061 if (PARENT_ENTITY_CLASS->local_changed)
1062 PARENT_ENTITY_CLASS->local_changed(entity);
1064 propagate((AdgTable *) entity, "local-changed");
1067 static void
1068 invalidate(AdgEntity *entity)
1070 propagate((AdgTable *) entity, "invalidate");
1073 static void
1074 arrange(AdgEntity *entity)
1076 AdgTable *table;
1077 AdgTablePrivate *data;
1078 CpmlExtents extents;
1079 const AdgPair *spacing;
1080 GSList *row_node;
1081 AdgTableRow *row;
1082 gdouble y;
1084 table = (AdgTable *) entity;
1085 data = table->data;
1086 cpml_extents_copy(&extents, adg_entity_get_extents(entity));
1088 /* Resolve the table style */
1089 if (data->table_style == NULL)
1090 data->table_style = (AdgTableStyle *)
1091 adg_entity_style(entity, data->table_dress);
1093 if (extents.is_defined) {
1094 if (data->grid != NULL)
1095 adg_entity_arrange((AdgEntity *) data->grid);
1096 if (data->frame != NULL)
1097 adg_entity_arrange((AdgEntity *) data->frame);
1098 return;
1101 spacing = adg_table_style_get_cell_spacing(data->table_style);
1102 extents.size.x = 0;
1103 extents.size.y = 0;
1105 for (row_node = data->rows; row_node; row_node = row_node->next) {
1106 row = row_node->data;
1108 row_arrange_size(row);
1110 if (row->extents.size.x > extents.size.x)
1111 extents.size.x = row->extents.size.x;
1112 extents.size.y += row->extents.size.y;
1115 /* TODO: update the org according to the table alignments */
1117 y = extents.org.y + spacing->y;
1118 for (row_node = data->rows; row_node; row_node = row_node->next) {
1119 row = row_node->data;
1121 row->extents.org.x = extents.org.x;
1122 row->extents.org.y = y;
1124 row_arrange(row);
1126 y += row->extents.size.y + spacing->y;
1129 extents.is_defined = TRUE;
1130 cpml_extents_transform(&extents, adg_entity_get_global_matrix(entity));
1131 adg_entity_set_extents(entity, &extents);
1133 arrange_grid(entity);
1134 arrange_frame(entity);
1137 static void
1138 arrange_grid(AdgEntity *entity)
1140 AdgTablePrivate *data;
1141 AdgPath *path;
1142 AdgTrail *trail;
1143 GSList *row_node, *cell_node;
1144 AdgTableRow *row;
1145 AdgTableCell *cell;
1146 AdgPair pair;
1147 AdgDress dress;
1149 data = ((AdgTable *) entity)->data;
1151 if (data->grid != NULL)
1152 return;
1154 path = adg_path_new();
1155 trail = (AdgTrail *) path;
1157 for (row_node = data->rows; row_node; row_node = row_node->next) {
1158 row = row_node->data;
1160 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1161 cell = cell_node->data;
1163 if (!cell->has_frame)
1164 continue;
1166 cpml_pair_copy(&pair, &cell->extents.org);
1167 adg_path_move_to(path, &pair);
1168 pair.x += cell->extents.size.x;
1169 adg_path_line_to(path, &pair);
1170 pair.y += cell->extents.size.y;
1171 adg_path_line_to(path, &pair);
1172 pair.x -= cell->extents.size.x;
1173 adg_path_line_to(path, &pair);
1174 adg_path_close(path);
1178 if (!adg_trail_get_extents(trail)->is_defined)
1179 return;
1181 dress = adg_table_style_get_grid_dress(data->table_style);
1182 data->grid = g_object_new(ADG_TYPE_STROKE,
1183 "local-method", ADG_MIX_PARENT,
1184 "line-dress", dress,
1185 "trail", trail,
1186 "parent", entity, NULL);
1187 adg_entity_arrange((AdgEntity *) data->grid);
1190 static void
1191 arrange_frame(AdgEntity *entity)
1193 AdgTablePrivate *data;
1194 AdgPath *path;
1195 const CpmlExtents *extents;
1196 AdgPair pair;
1197 AdgDress dress;
1199 data = ((AdgTable *) entity)->data;
1201 if (data->frame != NULL || !data->has_frame)
1202 return;
1204 path = adg_path_new();
1205 extents = adg_entity_get_extents(entity);
1207 cpml_pair_copy(&pair, &extents->org);
1208 adg_path_move_to(path, &pair);
1209 pair.x += extents->size.x;
1210 adg_path_line_to(path, &pair);
1211 pair.y += extents->size.y;
1212 adg_path_line_to(path, &pair);
1213 pair.x -= extents->size.x;
1214 adg_path_line_to(path, &pair);
1215 adg_path_close(path);
1217 dress = adg_table_style_get_frame_dress(data->table_style);
1219 data->frame = g_object_new(ADG_TYPE_STROKE,
1220 "local-method", ADG_MIX_PARENT,
1221 "line-dress", dress,
1222 "trail", (AdgTrail *) path,
1223 "parent", entity, NULL);
1224 adg_entity_arrange((AdgEntity *) data->frame);
1227 static void
1228 render(AdgEntity *entity, cairo_t *cr)
1230 cairo_transform(cr, adg_entity_get_local_matrix(entity));
1232 propagate((AdgTable *) entity, "render", cr);
1235 static gboolean
1236 switch_frame(AdgTable *table, gboolean new_state)
1238 AdgTablePrivate *data = table->data;
1240 if (data->has_frame == new_state)
1241 return FALSE;
1243 data->has_frame = new_state;
1245 if (data->frame != NULL) {
1246 g_object_unref(data->frame);
1247 data->frame = NULL;
1250 return TRUE;
1253 static void
1254 propagate(AdgTable *table, const gchar *detailed_signal, ...)
1256 guint signal_id;
1257 GQuark detail = 0;
1258 va_list var_args, var_copy;
1259 AdgTablePrivate *data;
1260 GSList *row_node;
1261 AdgTableRow *row;
1262 GSList *cell_node;
1263 AdgTableCell *cell;
1264 AdgAlignment *alignment;
1266 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(table),
1267 &signal_id, &detail, FALSE)) {
1268 g_return_if_reached();
1271 va_start(var_args, detailed_signal);
1272 data = table->data;
1274 if (data->frame != NULL) {
1275 G_VA_COPY(var_copy, var_args);
1276 g_signal_emit_valist(data->frame, signal_id, detail, var_copy);
1279 if (data->grid != NULL) {
1280 G_VA_COPY(var_copy, var_args);
1281 g_signal_emit_valist(data->grid, signal_id, detail, var_copy);
1284 for (row_node = data->rows; row_node; row_node = row_node->next) {
1285 row = row_node->data;
1287 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1288 cell = cell_node->data;
1290 if (cell->title != NULL) {
1291 alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1293 G_VA_COPY(var_copy, var_args);
1294 g_signal_emit_valist(alignment, signal_id, detail, var_copy);
1297 if (cell->value != NULL) {
1298 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1300 G_VA_COPY(var_copy, var_args);
1301 g_signal_emit_valist(alignment, signal_id, detail, var_copy);
1306 va_end(var_args);
1309 static AdgTableRow *
1310 row_new(AdgTable *table, AdgTableRow *before_row)
1312 AdgTablePrivate *data;
1313 AdgTableRow *new_row;
1315 data = table->data;
1316 new_row = g_new(AdgTableRow, 1);
1317 new_row->table = table;
1318 new_row->cells = NULL;
1319 new_row->height = 0;
1320 new_row->extents.is_defined = FALSE;
1322 if (before_row == NULL) {
1323 data->rows = g_slist_append(data->rows, new_row);
1324 } else {
1325 GSList *before_node = g_slist_find(data->rows, before_row);
1327 /* This MUST be present, otherwise something really bad happened */
1328 g_return_val_if_fail(before_node != NULL, NULL);
1330 data->rows = g_slist_insert_before(data->rows, before_node, new_row);
1333 invalidate((AdgEntity *) table);
1335 return new_row;
1338 static void
1339 row_arrange_size(AdgTableRow *row)
1341 AdgTableStyle *table_style;
1342 const AdgPair *spacing;
1343 CpmlVector *size;
1344 AdgTableCell *cell;
1345 GSList *cell_node;
1347 table_style = GET_TABLE_STYLE(row->table);
1348 spacing = adg_table_style_get_cell_spacing(table_style);
1349 size = &row->extents.size;
1351 size->x = 0;
1352 if (row->height == 0)
1353 size->y = adg_table_style_get_row_height(table_style);
1354 else
1355 size->y = row->height;
1357 /* Compute the row width by summing every cell width */
1358 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1359 cell = cell_node->data;
1361 cell_arrange_size(cell);
1363 size->x += cell->extents.size.x + spacing->x;
1366 if (size->x > 0)
1367 size->x += spacing->x;
1370 /* Before calling this function, row->extents should be updated */
1371 static void
1372 row_arrange(AdgTableRow *row)
1374 AdgTableStyle *table_style;
1375 const AdgPair *spacing;
1376 const AdgPair *org;
1377 AdgTableCell *cell;
1378 GSList *cell_node;
1379 gdouble x;
1381 table_style = GET_TABLE_STYLE(row->table);
1382 spacing = adg_table_style_get_cell_spacing(table_style);
1383 org = &row->extents.org;
1384 x = org->x + spacing->x;
1386 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1387 cell = cell_node->data;
1389 cell->extents.org.x = x;
1390 cell->extents.org.y = org->y;
1392 cell_arrange(cell);
1394 x += cell->extents.size.x + spacing->x;
1397 row->extents.is_defined = TRUE;
1400 static void
1401 row_dispose(AdgTableRow *row)
1403 g_slist_foreach(row->cells, (GFunc) cell_dispose, NULL);
1406 static void
1407 row_free(AdgTableRow *row)
1409 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
1410 g_slist_free(row->cells);
1412 g_free(row);
1415 static AdgTableCell *
1416 cell_new(AdgTableRow *row, AdgTableCell *before_cell,
1417 gdouble width, gboolean has_frame,
1418 const gchar *name, AdgEntity *title, AdgEntity *value)
1420 AdgTablePrivate *data;
1421 AdgTableCell *new_cell;
1423 data = row->table->data;
1425 if (name != NULL && data->cell_names != NULL &&
1426 g_hash_table_lookup(data->cell_names, name) != NULL) {
1427 g_warning(_("%s: `%s' cell name is yet used"), G_STRLOC, name);
1428 return NULL;
1431 new_cell = g_new(AdgTableCell, 1);
1432 new_cell->row = row;
1433 new_cell->width = width;
1434 new_cell->has_frame = has_frame;
1435 new_cell->title = NULL;
1436 new_cell->value = NULL;
1437 new_cell->extents.is_defined = FALSE;
1438 new_cell->value_factor.x = 0.5;
1439 new_cell->value_factor.y = 1;
1441 cell_set_title(new_cell, title);
1442 cell_set_value(new_cell, value);
1444 if (before_cell == NULL) {
1445 row->cells = g_slist_append(row->cells, new_cell);
1446 } else {
1447 GSList *before_node = g_slist_find(row->cells, before_cell);
1449 /* This MUST be not null, otherwise something really bad happened */
1450 g_return_val_if_fail(before_node != NULL, NULL);
1452 row->cells = g_slist_insert_before(row->cells, before_node, new_cell);
1455 if (name != NULL)
1456 cell_set_name(new_cell, name);
1458 return new_cell;
1461 static void
1462 cell_set_name(AdgTableCell *cell, const gchar *name)
1464 AdgTablePrivate *data = cell->row->table->data;
1466 if (data->cell_names == NULL && name == NULL)
1467 return;
1469 if (data->cell_names == NULL)
1470 data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal,
1471 g_free, NULL);
1473 if (name == NULL)
1474 g_hash_table_foreach_remove(data->cell_names, value_match, cell);
1475 else
1476 g_hash_table_insert(data->cell_names, g_strdup(name), cell);
1479 static gboolean
1480 cell_set_title(AdgTableCell *cell, AdgEntity *title)
1482 AdgAlignment *alignment;
1484 if (cell->title == title)
1485 return FALSE;
1487 if (cell->title != NULL) {
1488 alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1489 g_object_unref(alignment);
1492 cell->title = title;
1494 if (title != NULL) {
1495 alignment = adg_alignment_new_explicit(0, -1);
1496 g_object_ref_sink(alignment);
1497 adg_entity_set_parent((AdgEntity *) alignment,
1498 (AdgEntity *) cell->row->table);
1500 adg_container_add((AdgContainer *) alignment, title);
1503 return TRUE;
1506 static gboolean
1507 cell_set_value(AdgTableCell *cell, AdgEntity *value)
1509 AdgAlignment *alignment;
1511 if (cell->value == value)
1512 return FALSE;
1514 if (cell->value != NULL) {
1515 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1516 g_object_unref(alignment);
1519 cell->value = value;
1521 if (value != NULL) {
1522 alignment = adg_alignment_new_explicit(0.5, 0);
1523 g_object_ref_sink(alignment);
1524 adg_entity_set_parent((AdgEntity *) alignment,
1525 (AdgEntity *) cell->row->table);
1527 adg_container_add((AdgContainer *) alignment, value);
1530 return TRUE;
1533 static void
1534 cell_set_value_pos(AdgTableCell *cell,
1535 const AdgPair *from_factor, const AdgPair *to_factor)
1537 AdgAlignment *alignment;
1539 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1541 if (from_factor != NULL)
1542 adg_alignment_set_factor(alignment, from_factor);
1544 if (to_factor != NULL)
1545 cell->value_factor = *to_factor;
1548 static void
1549 cell_arrange_size(AdgTableCell *cell)
1551 CpmlVector *size;
1552 AdgAlignment *title_alignment;
1553 AdgAlignment *value_alignment;
1555 size = &cell->extents.size;
1557 if (cell->title != NULL) {
1558 title_alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1559 adg_entity_arrange((AdgEntity *) title_alignment);
1560 } else {
1561 title_alignment = NULL;
1564 if (cell->value != NULL) {
1565 value_alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1566 adg_entity_arrange((AdgEntity *) value_alignment);
1567 } else {
1568 value_alignment = NULL;
1571 size->y = cell->row->extents.size.y;
1573 if (cell->width == 0) {
1574 AdgTableStyle *table_style = GET_TABLE_STYLE(cell->row->table);
1575 const CpmlExtents *extents;
1577 /* The width depends on the cell content (default = 0) */
1578 size->x = 0;
1580 if (title_alignment != NULL) {
1581 extents = adg_entity_get_extents((AdgEntity *) title_alignment);
1582 size->x = extents->size.x;
1585 if (value_alignment != NULL) {
1586 extents = adg_entity_get_extents((AdgEntity *) value_alignment);
1587 if (extents->size.x > size->x)
1588 size->x = extents->size.x;
1591 size->x += adg_table_style_get_cell_spacing(table_style)->x * 2;
1592 } else {
1593 size->x = cell->width;
1597 /* Before calling this function, cell->extents should be updated */
1598 static void
1599 cell_arrange(AdgTableCell *cell)
1601 CpmlExtents *extents;
1602 AdgAlignment *alignment;
1603 AdgMatrix map;
1605 extents = &cell->extents;
1607 if (cell->title != NULL) {
1608 alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1610 cairo_matrix_init_translate(&map, extents->org.x, extents->org.y);
1611 adg_entity_set_global_map((AdgEntity *) alignment, &map);
1614 if (cell->value != NULL) {
1615 AdgPair to;
1617 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1618 to.x = extents->size.x * cell->value_factor.x + extents->org.x;
1619 to.y = extents->size.y * cell->value_factor.y + extents->org.y;
1621 cairo_matrix_init_translate(&map, to.x, to.y);
1622 adg_entity_set_global_map((AdgEntity *) alignment, &map);
1625 extents->is_defined = TRUE;
1628 static void
1629 cell_dispose(AdgTableCell *cell)
1631 cell_set_title(cell, NULL);
1632 cell_set_value(cell, NULL);
1635 static void
1636 cell_free(AdgTableCell *cell)
1638 cell_set_name(cell, NULL);
1639 cell_dispose(cell);
1640 g_free(cell);
1643 static gboolean
1644 value_match(gpointer key, gpointer value, gpointer user_data)
1646 return value == user_data;