[AdgTable] Prefixed internal symbols
[adg.git] / src / adg / adg-table.c
blob4c69891339502e231246b0b123338a7aba7d2bbd
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 _ADG_OLD_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class)
84 #define _ADG_OLD_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class)
87 G_DEFINE_TYPE(AdgTable, adg_table, ADG_TYPE_ENTITY);
89 enum {
90 PROP_0,
91 PROP_TABLE_DRESS,
92 PROP_HAS_FRAME
96 static void _adg_dispose (GObject *object);
97 static void _adg_finalize (GObject *object);
98 static void _adg_get_property (GObject *object,
99 guint param_id,
100 GValue *value,
101 GParamSpec *pspec);
102 static void _adg_set_property (GObject *object,
103 guint param_id,
104 const GValue *value,
105 GParamSpec *pspec);
106 static void _adg_global_changed (AdgEntity *entity);
107 static void _adg_local_changed (AdgEntity *entity);
108 static void _adg_invalidate (AdgEntity *entity);
109 static void _adg_arrange (AdgEntity *entity);
110 static void _adg_arrange_grid (AdgEntity *entity);
111 static void _adg_arrange_frame (AdgEntity *entity,
112 const CpmlExtents *extents);
113 static void _adg_render (AdgEntity *entity,
114 cairo_t *cr);
115 static void _adg_propagate (AdgTable *table,
116 const gchar *detailed_signal,
117 ...);
118 static AdgTableRow * _adg_row_new (AdgTable *table,
119 AdgTableRow *before_row);
120 static void _adg_row_arrange (AdgTableRow *row);
121 static void _adg_row_arrange_size (AdgTableRow *row);
122 static void _adg_row_dispose (AdgTableRow *row);
123 static void _adg_row_free (AdgTableRow *row);
124 static AdgTableCell * _adg_cell_new (AdgTableRow *row,
125 AdgTableCell *before_cell,
126 gdouble width,
127 gboolean has_frame,
128 const gchar *name,
129 AdgEntity *title,
130 AdgEntity *value);
131 static void _adg_cell_set_name (AdgTableCell *cell,
132 const gchar *name);
133 static gboolean _adg_cell_set_title (AdgTableCell *cell,
134 AdgEntity *title);
135 static gboolean _adg_cell_set_value (AdgTableCell *cell,
136 AdgEntity *value);
137 static void _adg_cell_set_value_pos (AdgTableCell *cell,
138 const AdgPair *from_factor,
139 const AdgPair *to_factor);
140 static void _adg_cell_arrange (AdgTableCell *cell);
141 static void _adg_cell_arrange_size (AdgTableCell *cell);
142 static void _adg_cell_dispose (AdgTableCell *cell);
143 static void _adg_cell_free (AdgTableCell *cell);
144 static gboolean _adg_value_match (gpointer key,
145 gpointer value,
146 gpointer user_data);
149 static void
150 adg_table_class_init(AdgTableClass *klass)
152 GObjectClass *gobject_class;
153 AdgEntityClass *entity_class;
154 GParamSpec *param;
156 gobject_class = (GObjectClass *) klass;
157 entity_class = (AdgEntityClass *) klass;
159 g_type_class_add_private(klass, sizeof(AdgTablePrivate));
161 gobject_class->dispose = _adg_dispose;
162 gobject_class->finalize = _adg_finalize;
163 gobject_class->get_property = _adg_get_property;
164 gobject_class->set_property = _adg_set_property;
166 entity_class->global_changed = _adg_global_changed;
167 entity_class->local_changed = _adg_local_changed;
168 entity_class->invalidate = _adg_invalidate;
169 entity_class->arrange = _adg_arrange;
170 entity_class->render = _adg_render;
172 param = adg_param_spec_dress("table-dress",
173 P_("Table Dress"),
174 P_("The dress to use for stroking this entity"),
175 ADG_DRESS_TABLE,
176 G_PARAM_READWRITE);
177 g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param);
179 param = g_param_spec_boolean("has-frame",
180 P_("Has Frame Flag"),
181 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
182 TRUE,
183 G_PARAM_READWRITE);
184 g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param);
187 static void
188 adg_table_init(AdgTable *table)
190 AdgTablePrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(table,
191 ADG_TYPE_TABLE,
192 AdgTablePrivate);
194 data->table_dress = ADG_DRESS_TABLE;
195 data->has_frame = TRUE;
197 data->table_style = NULL;
198 data->grid = NULL;
199 data->frame = NULL;
200 data->rows = NULL;
201 data->cell_names = NULL;
203 table->data = data;
206 static void
207 _adg_dispose(GObject *object)
209 AdgTablePrivate *data = ((AdgTable *) object)->data;
211 if (data->grid) {
212 g_object_unref(data->grid);
213 data->grid = NULL;
216 if (data->frame) {
217 g_object_unref(data->frame);
218 data->frame = NULL;
221 /* The rows finalization will happen in the finalize() method */
222 if (data->rows)
223 g_slist_foreach(data->rows, (GFunc) _adg_row_dispose, NULL);
225 if (_ADG_OLD_OBJECT_CLASS->dispose)
226 _ADG_OLD_OBJECT_CLASS->dispose(object);
229 static void
230 _adg_finalize(GObject *object)
232 AdgTable *table;
233 AdgTablePrivate *data;
235 table = (AdgTable *) object;
236 data = table->data;
238 if (data->rows) {
239 g_slist_foreach(data->rows, (GFunc) _adg_row_free, NULL);
240 g_slist_free(data->rows);
243 if (data->cell_names)
244 g_hash_table_destroy(data->cell_names);
246 if (_ADG_OLD_OBJECT_CLASS->finalize)
247 _ADG_OLD_OBJECT_CLASS->finalize(object);
250 static void
251 _adg_get_property(GObject *object, guint prop_id,
252 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 _adg_set_property(GObject *object, guint prop_id,
271 const GValue *value, GParamSpec *pspec)
273 AdgTablePrivate *data = ((AdgTable *) object)->data;
275 switch (prop_id) {
276 case PROP_TABLE_DRESS:
277 data->table_dress = g_value_get_int(value);
278 break;
279 case PROP_HAS_FRAME:
280 data->has_frame = g_value_get_boolean(value);
281 break;
282 default:
283 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
284 break;
290 * adg_table_new:
292 * Creates a new empty table entity. The #AdgEntity:local-method
293 * property is set by default to #ADG_MIX_DISABLED, that is the
294 * table is not subject to any local transformations.
296 * Returns: the newly created table entity
298 AdgTable *
299 adg_table_new(void)
301 return g_object_new(ADG_TYPE_TABLE,
302 "local-method", ADG_MIX_DISABLED,
303 NULL);
307 * adg_table_set_table_dress:
308 * @table: an #AdgTable
309 * @dress: the new #AdgDress to use
311 * Sets a new table dress for rendering @table. The new dress
312 * must be related to the original dress for this property:
313 * you cannot set a dress used for line styles to a dress
314 * managing fonts.
316 * The check is done by calling adg_dress_are_related() with
317 * @dress and the previous dress as arguments. Check out its
318 * documentation for details on what is a related dress.
320 void
321 adg_table_set_table_dress(AdgTable *table, AdgDress dress)
323 g_return_if_fail(ADG_IS_TABLE(table));
324 g_object_set((GObject *) table, "table-dress", dress, NULL);
328 * adg_table_get_table_dress:
329 * @table: an #AdgTable
331 * Gets the table dress to be used in rendering @table.
333 * Returns: the current table dress
335 AdgDress
336 adg_table_get_table_dress(AdgTable *table)
338 AdgTablePrivate *data;
340 g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED);
342 data = table->data;
344 return data->table_dress;
348 * adg_table_switch_frame:
349 * @table: an #AdgTable
350 * @new_state: the new state of the frame
352 * Sets the #AdgTable:has-frame property: %TRUE will draw a
353 * frame around the whole table using the #AdgTableStyle:frame-dress
354 * dress of the table style.
356 void
357 adg_table_switch_frame(AdgTable *table, gboolean new_state)
359 g_return_if_fail(ADG_IS_TABLE(table));
360 g_object_set(table, "has-frame", new_state, NULL);
364 * adg_table_has_frame:
365 * @table: an #AdgTable
367 * Returns the state of the #AdgTable:has-frame property.
369 * Returns: the current state
371 gboolean
372 adg_table_has_frame(AdgTable *table)
374 AdgTablePrivate *data;
376 g_return_val_if_fail(ADG_IS_TABLE(table), FALSE);
378 data = table->data;
380 return data->has_frame;
384 * adg_table_get_n_rows:
385 * @table: an #AdgTable
387 * Gets the number of rows stored in @table.
389 * Returns: the number of rows or %0 on empty @table or errors
391 guint
392 adg_table_get_n_rows(AdgTable *table)
394 AdgTablePrivate *data;
396 g_return_val_if_fail(ADG_IS_TABLE(table), 0);
398 data = table->data;
400 if (data->rows == NULL)
401 return 0;
403 return g_slist_length(data->rows);
407 * adg_table_row_new:
408 * @table: an #AdgTable
410 * Creates a new empty row and appends it at the end of the rows
411 * yet present in @table. By default, the height of this new
412 * row will be the fallback value provided by the table style:
413 * you can override it by using adg_table_row_set_height().
415 * Returns: the newly created row or %NULL on errors
417 AdgTableRow *
418 adg_table_row_new(AdgTable *table)
420 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
422 return _adg_row_new(table, NULL);
426 * adg_table_row_new_before:
427 * @row: a valid #AdgTableRow
429 * Creates a new empty row with default height and inserts it
430 * just before @row.
432 * Returns: the newly created row or %NULL on errors
434 AdgTableRow *
435 adg_table_row_new_before(AdgTableRow *row)
437 g_return_val_if_fail(row != NULL, NULL);
438 g_return_val_if_fail(ADG_IS_TABLE(row->table), NULL);
440 return _adg_row_new(row->table, row);
444 * adg_table_row_delete:
445 * @row: a valid #AdgTableRow
447 * Removes @row from its owner table and frees every resources allocated
448 * by it. This means also the eventual cells owned by @row will be freed.
450 void
451 adg_table_row_delete(AdgTableRow *row)
453 AdgTable *table;
454 AdgTablePrivate *data;
456 g_return_if_fail(row != NULL);
458 table = row->table;
460 g_return_if_fail(ADG_IS_TABLE(table));
462 data = table->data;
464 g_slist_foreach(row->cells, (GFunc) _adg_cell_free, NULL);
465 g_slist_free(row->cells);
466 data->rows = g_slist_remove(data->rows, row);
468 g_free(row);
472 * adg_table_row_get_n_cells:
473 * @row: a valid #AdgTableRow
475 * Gets the number of cells stored in @row.
477 * Returns: the number of cells or %0 on empty row or errors
479 guint
480 adg_table_row_get_n_cells(const AdgTableRow *row)
482 g_return_val_if_fail(row != NULL, 0);
484 if (row->cells == NULL)
485 return 0;
487 return g_slist_length(row->cells);
491 * adg_table_row_set_height:
492 * @row: a valid #AdgTableRow
493 * @height: the new height
495 * Sets a new height on @row. The extents will be invalidated to
496 * recompute the whole layout of the table. Specifying %0 in
497 * @height will use the default height set in the table style.
499 void
500 adg_table_row_set_height(AdgTableRow *row, gdouble height)
502 g_return_if_fail(row != NULL);
504 row->height = height;
506 adg_entity_invalidate((AdgEntity *) row->table);
510 * adg_table_row_get_height:
511 * @row: a valid #AdgTableRow
513 * Gets the height of @row.
515 * Returns: the requested height or %0 on errors
517 gdouble
518 adg_table_row_get_height(AdgTableRow *row)
520 g_return_val_if_fail(row != NULL, 0.);
522 return row->height;
526 * adg_table_row_get_extents:
527 * @row: a valid #AdgTableRow
529 * Gets the extents of @row. This function is useful only after the
530 * arrange() phase as in the other situation the extents will likely
531 * be not up to date.
533 * Returns: the extents of @row or %NULL on errors
535 const CpmlExtents *
536 adg_table_row_get_extents(AdgTableRow *row)
538 g_return_val_if_fail(row != NULL, NULL);
540 return &row->extents;
544 * adg_table_cell_new:
545 * @row: a valid #AdgTableRow
546 * @width: width of the cell
548 * Creates a new empty cell without a frame and appends it at the
549 * end of the cells yet present in @row. You can add content to the
550 * cell by using adg_table_cell_set_title() and
551 * adg_table_cell_set_value() or enable the frame with
552 * adg_table_cell_switch_frame().
554 * A positive @width value specifies the width of this cell in global
555 * space: if the width of its content (that is, either the title or the
556 * value entity) will be greater than @width, it will be rendered
557 * outside the cell boundary box, luckely overwriting the adiacent
558 * cells.
560 * Using %0 as @width means the width of the cell will be automatically
561 * adjusted to the maximum width of its content.
563 * Negative width values are not allowed: this condition will raise
564 * a warning without any further processing.
566 * Returns: the newly created cell or %NULL on errors
568 AdgTableCell *
569 adg_table_cell_new(AdgTableRow *row, gdouble width)
571 g_return_val_if_fail(row != NULL, NULL);
572 g_return_val_if_fail(width >= 0, NULL);
574 return _adg_cell_new(row, NULL, width, FALSE, NULL, NULL, NULL);
578 * adg_table_cell_new_before:
579 * @cell: a valid #AdgTableCell
580 * @width: width of the cell
582 * Creates a new cell and inserts it rigthly before the @cell cell.
583 * This works similarily and accepts the same parameters as the
584 * adg_table_cell_new() function.
586 * Returns: the newly created cell or %NULL on errors
588 AdgTableCell *
589 adg_table_cell_new_before(AdgTableCell *cell, gdouble width)
591 g_return_val_if_fail(cell != NULL, NULL);
592 g_return_val_if_fail(cell->row != NULL, NULL);
593 g_return_val_if_fail(width >= 0, NULL);
595 return _adg_cell_new(cell->row, cell, width, FALSE, NULL, NULL, NULL);
599 * adg_table_cell_new_full:
600 * @row: a valid #AdgTableRow
601 * @width: width of the cell
602 * @name: name to associate
603 * @title: title to render
604 * @value: value to render
606 * A convenient function to append a framed cell to @row with a
607 * specific title and value text. The font to use for rendering
608 * @title and @value will be picked up from the table style, so
609 * be sure to have the correct table dress set before calling
610 * this function.
612 * @row and @width have the same meanings as in adg_table_cell_new():
613 * check its documentation for details.
615 * @name is an optional identifier to univoquely access this cell
616 * by using adg_table_cell(). The identifier must be univoque:
617 * if there is yet a cell with the same name a warning message will
618 * be raised and the function will fail.
620 * Returns: the newly created cell or %NULL on errors
622 AdgTableCell *
623 adg_table_cell_new_full(AdgTableRow *row, gdouble width, const gchar *name,
624 const gchar *title, const gchar *value)
626 AdgTableCell *cell;
628 g_return_val_if_fail(row != NULL, NULL);
630 cell = _adg_cell_new(row, NULL, width, TRUE, name, NULL, NULL);
632 if (title)
633 adg_table_cell_set_text_title(cell, title);
635 if (value)
636 adg_table_cell_set_text_value(cell, value);
638 return cell;
642 * adg_table_cell:
643 * @table: an #AdgTable
644 * @name: the name of a cell
646 * Gets the cell named @name inside @table. Only named cells can be
647 * retrieved by this method.
649 * Returns: the requested cell or %NULL if not found
651 AdgTableCell *
652 adg_table_cell(AdgTable *table, const gchar *name)
654 AdgTablePrivate *data;
656 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
658 data = table->data;
660 if (data->cell_names == NULL)
661 return NULL;
663 return g_hash_table_lookup(data->cell_names, name);
667 * adg_table_cell_delete:
668 * @cell: a valid #AdgTableCell
670 * Deletes @cell removing it from the container row and freeing
671 * any resource associated to it.
673 void
674 adg_table_cell_delete(AdgTableCell *cell)
676 AdgTableRow *row;
678 g_return_if_fail(cell != NULL);
680 row = cell->row;
682 g_return_if_fail(row != NULL);
684 _adg_cell_free(cell);
685 row->cells = g_slist_remove(row->cells, cell);
689 * adg_table_cell_set_name:
690 * @cell: a valid #AdgTableCell
691 * @name: the new name of @cell
693 * Sets a new name on @cell: this will allow to access @cell by
694 * name at a later time using the adg_table_cell() API.
696 void
697 adg_table_cell_set_name(AdgTableCell *cell, const gchar *name)
699 AdgTablePrivate *data;
701 g_return_if_fail(cell != NULL);
703 data = cell->row->table->data;
705 _adg_cell_set_name(cell, NULL);
706 _adg_cell_set_name(cell, name);
710 * adg_table_cell_get_name:
711 * @cell: a valid #AdgTableCell
713 * Gets the name assigned to @cell. This function is highly inefficient
714 * as the cell names are stored in a hash table optimized to get a cell
715 * from a name. Getting the name from a cell involves a full hash table
716 * inspection.
718 * Returns: the name bound of @cell or %NULL on no name or errors
720 const gchar *
721 adg_table_cell_get_name(AdgTableCell *cell)
723 AdgTablePrivate *data;
725 g_return_val_if_fail(cell != NULL, NULL);
727 data = cell->row->table->data;
729 return g_hash_table_find(data->cell_names, _adg_value_match, cell);
733 * adg_table_cell_set_title:
734 * @cell: a valid #AdgTableCell
735 * @title: the new title entity
737 * Sets @title as the new title entity of @cell. The top left
738 * corner of the bounding box of @title will be cohincident to
739 * the top left corner of the cell extents, taking into accounts
740 * eventual padding spaces specified by the table style.
742 * The old internal entity is unrefenrenced while the @title (if
743 * not %NULL) is refenenced with g_object_ref_sink().
745 * @title can be %NULL, in which case the old entity is removed.
747 void
748 adg_table_cell_set_title(AdgTableCell *cell, AdgEntity *title)
750 g_return_if_fail(cell != NULL);
751 g_return_if_fail(title == NULL || ADG_IS_ENTITY(title));
753 if (_adg_cell_set_title(cell, title))
754 adg_entity_invalidate((AdgEntity *) cell->row->table);
758 * adg_table_cell_set_text_title:
759 * @cell: a valid #AdgTableCell
760 * @title: a text string
762 * Convenient function to set a the title of a cell using an #AdgToyText
763 * entity with the font dress picked from #AdgTable:table-dress with
764 * a call to adg_table_style_get_title_dress().
766 void
767 adg_table_cell_set_text_title(AdgTableCell *cell, const gchar *title)
769 AdgTable *table;
770 AdgEntity *entity;
771 AdgTableStyle *table_style;
772 const AdgPair *padding;
773 AdgDress table_dress, font_dress;
774 AdgMatrix map;
776 g_return_if_fail(cell != NULL);
778 if (title == NULL)
779 adg_table_cell_set_title(cell, NULL);
781 if (cell->title) {
782 const gchar *old_title;
784 if (ADG_IS_TOY_TEXT(cell->title))
785 old_title = adg_toy_text_get_label((AdgToyText *) cell->title);
786 else
787 old_title = NULL;
789 if (g_strcmp0(title, old_title) == 0)
790 return;
793 table = cell->row->table;
794 table_dress = adg_table_get_table_dress(table);
795 table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table,
796 table_dress);
797 padding = adg_table_style_get_cell_padding(table_style);
798 font_dress = adg_table_style_get_title_dress(table_style);
799 entity = g_object_new(ADG_TYPE_TOY_TEXT, "label", title,
800 "font-dress", font_dress, NULL);
802 cairo_matrix_init_translate(&map, padding->x, padding->y);
803 adg_entity_set_global_map(entity, &map);
805 adg_table_cell_set_title(cell, entity);
809 * adg_table_cell_title:
810 * @cell: a valid #AdgTableCell
812 * Gets the current title of @cell. The returned string is owned
813 * by @cell and must not be modified or freed.
815 * Returns: the title entity or %NULL for undefined title
817 AdgEntity *
818 adg_table_cell_title(AdgTableCell *cell)
820 g_return_val_if_fail(cell != NULL, NULL);
822 return cell->title;
826 * adg_table_cell_set_value:
827 * @cell: a valid #AdgTableCell
828 * @value: the new value entity
830 * Sets @value as the new value entity of @cell. The bottom middle
831 * point of the bounding box of @value will be cohincident to the
832 * bottom middle point of the cell extents, taking into accounts
833 * eventual padding spaces specified by the table style.
835 * The old internal entity is unrefenrenced while the @value (if
836 * not %NULL) is refenenced with g_object_ref_sink().
838 * @value can be %NULL, in which case the old entity is removed.
840 void
841 adg_table_cell_set_value(AdgTableCell *cell, AdgEntity *value)
843 g_return_if_fail(cell != NULL);
844 g_return_if_fail(value == NULL || ADG_IS_ENTITY(value));
846 if (_adg_cell_set_value(cell, value))
847 adg_entity_invalidate((AdgEntity *) cell->row->table);
851 * adg_table_cell_set_text_value:
852 * @cell: a valid #AdgTableCell
853 * @value: a text string
855 * Convenient function to set a the value of a cell using an #AdgToyText
856 * entity with a value font dress picked from #AdgTable:table-dress with
857 * a call to adg_table_style_get_value_dress().
859 void
860 adg_table_cell_set_text_value(AdgTableCell *cell, const gchar *value)
862 AdgTable *table;
863 AdgEntity *entity;
864 AdgTableStyle *table_style;
865 const AdgPair *padding;
866 AdgDress table_dress, font_dress;
867 AdgMatrix map;
869 g_return_if_fail(cell != NULL);
871 if (value == NULL)
872 adg_table_cell_set_value(cell, NULL);
874 if (cell->value) {
875 const gchar *old_value;
877 if (ADG_IS_TOY_TEXT(cell->value))
878 old_value = adg_toy_text_get_label((AdgToyText *) cell->value);
879 else
880 old_value = NULL;
882 if (g_strcmp0(value, old_value) == 0)
883 return;
886 table = cell->row->table;
887 table_dress = adg_table_get_table_dress(table);
888 table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table,
889 table_dress);
890 padding = adg_table_style_get_cell_padding(table_style);
891 font_dress = adg_table_style_get_value_dress(table_style);
892 entity = g_object_new(ADG_TYPE_TOY_TEXT, "label", value,
893 "font-dress", font_dress, NULL);
895 cairo_matrix_init_translate(&map, 0, -padding->y);
896 adg_entity_set_global_map(entity, &map);
898 adg_table_cell_set_value(cell, entity);
902 * adg_table_cell_value:
903 * @cell: a valid #AdgTableCell
905 * Gets the current value of @cell. The returned string is owned
906 * by @cell and must not be modified or freed.
908 * Returns: the value entity or %NULL for undefined value
910 AdgEntity *
911 adg_table_cell_value(AdgTableCell *cell)
913 g_return_val_if_fail(cell != NULL, NULL);
915 return cell->value;
919 * adg_table_cell_set_value_pos:
920 * @cell: a valid #AdgTableCell
921 * @from_factor: the alignment factor on the value entity
922 * @to_factor: the alignment factor on the cell
924 * Sets a new custom position for the value entity of @cell. The
925 * @from_factor specifies the source point (as a fraction of the
926 * value extents) while the @to_factor is the destination point
927 * (specified as a fraction of the cell extents) the source point
928 * must be moved to.
930 void
931 adg_table_cell_set_value_pos(AdgTableCell *cell, const AdgPair *from_factor,
932 const AdgPair *to_factor)
934 g_return_if_fail(cell != NULL);
935 g_return_if_fail(cell->value != NULL);
937 _adg_cell_set_value_pos(cell, from_factor, to_factor);
941 * adg_table_cell_set_width:
942 * @cell: a valid #AdgTableCell
943 * @width: the new width
945 * Sets a new width on @cell. The extents on the whole table
946 * will be invalidated, so will be recomputed in the next
947 * arrange() phase.
949 void
950 adg_table_cell_set_width(AdgTableCell *cell, gdouble width)
952 g_return_if_fail(cell != NULL);
954 cell->width = width;
956 adg_entity_invalidate((AdgEntity *) cell->row->table);
960 * adg_table_cell_get_width:
961 * @cell: a valid #AdgTableCell
963 * Gets the width of @cell.
965 * Returns: the requested width or %0 on errors
967 gdouble
968 adg_table_cell_get_width(AdgTableCell *cell)
970 g_return_val_if_fail(cell != NULL, 0.);
972 return cell->width;
976 * adg_table_cell_switch_frame:
977 * @cell: a valid #AdgTableCell
978 * @new_state: the new frame state
980 * Sets the frame flag of @cell: if @new_state is %TRUE, a frame around
981 * @cell will be rendered using the #AdgTableStyle:cell-dress dress
982 * of the table style.
984 void
985 adg_table_cell_switch_frame(AdgTableCell *cell, gboolean new_state)
987 AdgTablePrivate *data;
989 g_return_if_fail(cell != NULL);
991 if (cell->has_frame == new_state)
992 return;
994 data = cell->row->table->data;
995 cell->has_frame = new_state;
997 if (data->grid) {
998 g_object_unref(data->grid);
999 data->grid = NULL;
1004 * adg_table_cell_has_frame:
1005 * @cell: a valid #AdgTableCell
1007 * Gets the frame flag of @cell.
1009 * Returns: the frame flag
1011 gboolean
1012 adg_table_cell_has_frame(AdgTableCell *cell)
1014 g_return_val_if_fail(cell != NULL, FALSE);
1016 return cell->has_frame;
1020 * adg_table_cell_get_extents:
1021 * @cell: a valid #AdgTableCell
1023 * Gets the extents of @cell. This function is useful only after the
1024 * arrange() phase as in the other situation the extents will likely
1025 * be not up to date.
1027 * Returns: the extents of @cell or %NULL on errors
1029 const CpmlExtents *
1030 adg_table_cell_get_extents(AdgTableCell *cell)
1032 g_return_val_if_fail(cell != NULL, NULL);
1034 return &cell->extents;
1038 static void
1039 _adg_global_changed(AdgEntity *entity)
1041 if (_ADG_OLD_ENTITY_CLASS->global_changed)
1042 _ADG_OLD_ENTITY_CLASS->global_changed(entity);
1044 _adg_propagate((AdgTable *) entity, "global-changed");
1047 static void
1048 _adg_local_changed(AdgEntity *entity)
1050 if (_ADG_OLD_ENTITY_CLASS->local_changed)
1051 _ADG_OLD_ENTITY_CLASS->local_changed(entity);
1053 _adg_propagate((AdgTable *) entity, "local-changed");
1056 static void
1057 _adg_invalidate(AdgEntity *entity)
1059 _adg_propagate((AdgTable *) entity, "invalidate");
1062 static void
1063 _adg_arrange(AdgEntity *entity)
1065 AdgTable *table;
1066 AdgTablePrivate *data;
1067 CpmlExtents extents = { 0 };
1068 const AdgPair *spacing;
1069 GSList *row_node;
1070 AdgTableRow *row;
1071 gdouble y;
1073 table = (AdgTable *) entity;
1074 data = table->data;
1076 /* Resolve the table style */
1077 if (data->table_style == NULL)
1078 data->table_style = (AdgTableStyle *)
1079 adg_entity_style(entity, data->table_dress);
1081 spacing = adg_table_style_get_cell_spacing(data->table_style);
1083 /* Compute the size of the table */
1084 for (row_node = data->rows; row_node; row_node = row_node->next) {
1085 row = row_node->data;
1087 _adg_row_arrange_size(row);
1089 if (row->extents.size.x > extents.size.x)
1090 extents.size.x = row->extents.size.x;
1091 extents.size.y += row->extents.size.y;
1094 /* Arrange the layout of the table components */
1095 y = extents.org.y + spacing->y;
1096 for (row_node = data->rows; row_node; row_node = row_node->next) {
1097 row = row_node->data;
1099 row->extents.org.x = extents.org.x;
1100 row->extents.org.y = y;
1101 row->extents.size.x = extents.size.x;
1103 _adg_row_arrange(row);
1105 y += row->extents.size.y + spacing->y;
1108 _adg_arrange_grid(entity);
1109 _adg_arrange_frame(entity, &extents);
1111 extents.is_defined = TRUE;
1112 cpml_extents_transform(&extents, adg_entity_get_global_matrix(entity));
1113 cpml_extents_transform(&extents, adg_entity_get_local_matrix(entity));
1114 adg_entity_set_extents(entity, &extents);
1117 static void
1118 _adg_arrange_grid(AdgEntity *entity)
1120 AdgTablePrivate *data;
1121 AdgPath *path;
1122 AdgTrail *trail;
1123 GSList *row_node, *cell_node;
1124 AdgTableRow *row;
1125 AdgTableCell *cell;
1126 AdgPair pair;
1127 AdgDress dress;
1129 data = ((AdgTable *) entity)->data;
1131 if (data->grid)
1132 return;
1134 path = adg_path_new();
1135 trail = (AdgTrail *) path;
1137 for (row_node = data->rows; row_node; row_node = row_node->next) {
1138 row = row_node->data;
1140 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1141 cell = cell_node->data;
1143 if (!cell->has_frame)
1144 continue;
1146 cpml_pair_copy(&pair, &cell->extents.org);
1147 adg_path_move_to(path, &pair);
1148 pair.x += cell->extents.size.x;
1149 adg_path_line_to(path, &pair);
1150 pair.y += cell->extents.size.y;
1151 adg_path_line_to(path, &pair);
1152 pair.x -= cell->extents.size.x;
1153 adg_path_line_to(path, &pair);
1154 adg_path_close(path);
1158 if (!adg_trail_get_extents(trail)->is_defined)
1159 return;
1161 dress = adg_table_style_get_grid_dress(data->table_style);
1162 data->grid = g_object_new(ADG_TYPE_STROKE,
1163 "line-dress", dress,
1164 "trail", trail,
1165 "parent", entity,
1166 NULL);
1167 adg_entity_arrange((AdgEntity *) data->grid);
1170 static void
1171 _adg_arrange_frame(AdgEntity *entity, const CpmlExtents *extents)
1173 AdgTablePrivate *data;
1174 AdgPath *path;
1175 AdgTrail *trail;
1176 AdgPair pair;
1177 AdgDress dress;
1179 data = ((AdgTable *) entity)->data;
1181 if (data->frame || !data->has_frame)
1182 return;
1184 path = adg_path_new();
1185 trail = (AdgTrail *) path;
1187 cpml_pair_copy(&pair, &extents->org);
1188 adg_path_move_to(path, &pair);
1189 pair.x += extents->size.x;
1190 adg_path_line_to(path, &pair);
1191 pair.y += extents->size.y;
1192 adg_path_line_to(path, &pair);
1193 pair.x -= extents->size.x;
1194 adg_path_line_to(path, &pair);
1195 adg_path_close(path);
1197 dress = adg_table_style_get_frame_dress(data->table_style);
1198 data->frame = g_object_new(ADG_TYPE_STROKE,
1199 "line-dress", dress,
1200 "trail", trail,
1201 "parent", entity,
1202 NULL);
1203 adg_entity_arrange((AdgEntity *) data->frame);
1206 static void
1207 _adg_render(AdgEntity *entity, cairo_t *cr)
1209 AdgTablePrivate *data = ((AdgTable *) entity)->data;
1211 cairo_transform(cr, adg_entity_get_local_matrix(entity));
1212 adg_style_apply((AdgStyle *) data->table_style, entity, cr);
1214 _adg_propagate((AdgTable *) entity, "render", cr);
1217 static void
1218 _adg_propagate(AdgTable *table, const gchar *detailed_signal, ...)
1220 guint signal_id;
1221 GQuark detail = 0;
1222 va_list var_args, var_copy;
1223 AdgTablePrivate *data;
1224 GSList *row_node;
1225 AdgTableRow *row;
1226 GSList *cell_node;
1227 AdgTableCell *cell;
1228 AdgAlignment *alignment;
1230 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(table),
1231 &signal_id, &detail, FALSE)) {
1232 g_return_if_reached();
1235 va_start(var_args, detailed_signal);
1236 data = table->data;
1238 if (data->frame) {
1239 G_VA_COPY(var_copy, var_args);
1240 g_signal_emit_valist(data->frame, signal_id, detail, var_copy);
1243 if (data->grid) {
1244 G_VA_COPY(var_copy, var_args);
1245 g_signal_emit_valist(data->grid, signal_id, detail, var_copy);
1248 for (row_node = data->rows; row_node; row_node = row_node->next) {
1249 row = row_node->data;
1251 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1252 cell = cell_node->data;
1254 if (cell->title) {
1255 alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1256 G_VA_COPY(var_copy, var_args);
1257 g_signal_emit_valist(alignment, signal_id, detail, var_copy);
1260 if (cell->value) {
1261 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1262 G_VA_COPY(var_copy, var_args);
1263 g_signal_emit_valist(alignment, signal_id, detail, var_copy);
1268 va_end(var_args);
1271 static AdgTableRow *
1272 _adg_row_new(AdgTable *table, AdgTableRow *before_row)
1274 AdgTablePrivate *data;
1275 AdgTableRow *new_row;
1277 data = table->data;
1278 new_row = g_new(AdgTableRow, 1);
1279 new_row->table = table;
1280 new_row->cells = NULL;
1281 new_row->height = 0;
1282 new_row->extents.is_defined = FALSE;
1284 if (before_row == NULL) {
1285 data->rows = g_slist_append(data->rows, new_row);
1286 } else {
1287 GSList *before_node = g_slist_find(data->rows, before_row);
1289 /* This MUST be present, otherwise something really bad happened */
1290 g_return_val_if_fail(before_node != NULL, NULL);
1292 data->rows = g_slist_insert_before(data->rows, before_node, new_row);
1295 _adg_invalidate((AdgEntity *) table);
1297 return new_row;
1300 /* Before calling this function, row->extents should be updated */
1301 static void
1302 _adg_row_arrange(AdgTableRow *row)
1304 AdgTableStyle *table_style;
1305 const AdgPair *spacing;
1306 const AdgPair *org;
1307 AdgTableCell *cell;
1308 GSList *cell_node;
1309 gdouble x;
1311 table_style = GET_TABLE_STYLE(row->table);
1312 spacing = adg_table_style_get_cell_spacing(table_style);
1313 org = &row->extents.org;
1314 x = org->x + spacing->x;
1316 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1317 cell = cell_node->data;
1319 cell->extents.org.x = x;
1320 cell->extents.org.y = org->y;
1322 _adg_cell_arrange(cell);
1324 x += cell->extents.size.x + spacing->x;
1327 row->extents.is_defined = TRUE;
1330 static void
1331 _adg_row_arrange_size(AdgTableRow *row)
1333 AdgTableStyle *table_style;
1334 const AdgPair *spacing;
1335 CpmlVector *size;
1336 AdgTableCell *cell;
1337 GSList *cell_node;
1339 table_style = GET_TABLE_STYLE(row->table);
1340 spacing = adg_table_style_get_cell_spacing(table_style);
1341 size = &row->extents.size;
1343 size->x = 0;
1344 if (row->height == 0)
1345 size->y = adg_table_style_get_row_height(table_style);
1346 else
1347 size->y = row->height;
1349 /* Compute the row width by summing every cell width */
1350 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1351 cell = cell_node->data;
1353 _adg_cell_arrange_size(cell);
1355 size->x += cell->extents.size.x + spacing->x;
1358 if (size->x > 0)
1359 size->x += spacing->x;
1362 static void
1363 _adg_row_dispose(AdgTableRow *row)
1365 g_slist_foreach(row->cells, (GFunc) _adg_cell_dispose, NULL);
1368 static void
1369 _adg_row_free(AdgTableRow *row)
1371 g_slist_foreach(row->cells, (GFunc) _adg_cell_free, NULL);
1372 g_slist_free(row->cells);
1374 g_free(row);
1377 static AdgTableCell *
1378 _adg_cell_new(AdgTableRow *row, AdgTableCell *before_cell,
1379 gdouble width, gboolean has_frame,
1380 const gchar *name, AdgEntity *title, AdgEntity *value)
1382 AdgTablePrivate *data;
1383 AdgTableCell *new_cell;
1385 data = row->table->data;
1387 if (name && data->cell_names &&
1388 g_hash_table_lookup(data->cell_names, name)) {
1389 g_warning(_("%s: `%s' cell name is yet used"), G_STRLOC, name);
1390 return NULL;
1393 new_cell = g_new(AdgTableCell, 1);
1394 new_cell->row = row;
1395 new_cell->width = width;
1396 new_cell->has_frame = has_frame;
1397 new_cell->title = NULL;
1398 new_cell->value = NULL;
1399 new_cell->extents.is_defined = FALSE;
1400 new_cell->value_factor.x = 0.5;
1401 new_cell->value_factor.y = 1;
1403 _adg_cell_set_title(new_cell, title);
1404 _adg_cell_set_value(new_cell, value);
1406 if (before_cell == NULL) {
1407 row->cells = g_slist_append(row->cells, new_cell);
1408 } else {
1409 GSList *before_node = g_slist_find(row->cells, before_cell);
1411 /* This MUST be not null, otherwise something really bad happened */
1412 g_return_val_if_fail(before_node != NULL, NULL);
1414 row->cells = g_slist_insert_before(row->cells, before_node, new_cell);
1417 if (name)
1418 _adg_cell_set_name(new_cell, name);
1420 return new_cell;
1423 static void
1424 _adg_cell_set_name(AdgTableCell *cell, const gchar *name)
1426 AdgTablePrivate *data = cell->row->table->data;
1428 if (data->cell_names == NULL && name == NULL)
1429 return;
1431 if (data->cell_names == NULL)
1432 data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal,
1433 g_free, NULL);
1435 if (name == NULL)
1436 g_hash_table_foreach_remove(data->cell_names, _adg_value_match, cell);
1437 else
1438 g_hash_table_insert(data->cell_names, g_strdup(name), cell);
1441 static gboolean
1442 _adg_cell_set_title(AdgTableCell *cell, AdgEntity *title)
1444 AdgAlignment *alignment;
1446 if (cell->title == title)
1447 return FALSE;
1449 if (cell->title) {
1450 alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1451 g_object_unref(alignment);
1454 cell->title = title;
1456 if (title) {
1457 alignment = adg_alignment_new_explicit(0, -1);
1458 g_object_ref_sink(alignment);
1459 adg_entity_set_parent((AdgEntity *) alignment,
1460 (AdgEntity *) cell->row->table);
1462 adg_container_add((AdgContainer *) alignment, title);
1465 return TRUE;
1468 static gboolean
1469 _adg_cell_set_value(AdgTableCell *cell, AdgEntity *value)
1471 AdgAlignment *alignment;
1473 if (cell->value == value)
1474 return FALSE;
1476 if (cell->value) {
1477 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1478 g_object_unref(alignment);
1481 cell->value = value;
1483 if (value) {
1484 alignment = adg_alignment_new_explicit(0.5, 0);
1485 g_object_ref_sink(alignment);
1486 adg_entity_set_parent((AdgEntity *) alignment,
1487 (AdgEntity *) cell->row->table);
1489 adg_container_add((AdgContainer *) alignment, value);
1492 return TRUE;
1495 static void
1496 _adg_cell_set_value_pos(AdgTableCell *cell,
1497 const AdgPair *from_factor, const AdgPair *to_factor)
1499 AdgAlignment *alignment;
1501 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1503 if (from_factor)
1504 adg_alignment_set_factor(alignment, from_factor);
1506 if (to_factor)
1507 cell->value_factor = *to_factor;
1510 /* Before calling this function, cell->extents should be updated */
1511 static void
1512 _adg_cell_arrange(AdgTableCell *cell)
1514 CpmlExtents *extents;
1515 AdgAlignment *alignment;
1516 AdgMatrix map;
1518 extents = &cell->extents;
1520 if (cell->title) {
1521 alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1523 cairo_matrix_init_translate(&map, extents->org.x, extents->org.y);
1524 adg_entity_set_global_map((AdgEntity *) alignment, &map);
1527 if (cell->value) {
1528 AdgPair to;
1530 alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1531 to.x = extents->size.x * cell->value_factor.x + extents->org.x;
1532 to.y = extents->size.y * cell->value_factor.y + extents->org.y;
1534 cairo_matrix_init_translate(&map, to.x, to.y);
1535 adg_entity_set_global_map((AdgEntity *) alignment, &map);
1538 extents->is_defined = TRUE;
1541 static void
1542 _adg_cell_arrange_size(AdgTableCell *cell)
1544 CpmlVector *size;
1545 AdgAlignment *title_alignment;
1546 AdgAlignment *value_alignment;
1548 size = &cell->extents.size;
1550 if (cell->title) {
1551 title_alignment = (AdgAlignment *) adg_entity_get_parent(cell->title);
1552 adg_entity_arrange((AdgEntity *) title_alignment);
1553 } else {
1554 title_alignment = NULL;
1557 if (cell->value) {
1558 value_alignment = (AdgAlignment *) adg_entity_get_parent(cell->value);
1559 adg_entity_arrange((AdgEntity *) value_alignment);
1560 } else {
1561 value_alignment = NULL;
1564 size->y = cell->row->extents.size.y;
1566 if (cell->width == 0) {
1567 AdgTableStyle *table_style = GET_TABLE_STYLE(cell->row->table);
1568 const CpmlExtents *extents;
1570 /* The width depends on the cell content (default = 0) */
1571 size->x = 0;
1573 if (title_alignment) {
1574 extents = adg_entity_get_extents((AdgEntity *) title_alignment);
1575 size->x = extents->size.x;
1578 if (value_alignment) {
1579 extents = adg_entity_get_extents((AdgEntity *) value_alignment);
1580 if (extents->size.x > size->x)
1581 size->x = extents->size.x;
1584 size->x += adg_table_style_get_cell_spacing(table_style)->x * 2;
1585 } else {
1586 size->x = cell->width;
1590 static void
1591 _adg_cell_dispose(AdgTableCell *cell)
1593 _adg_cell_set_title(cell, NULL);
1594 _adg_cell_set_value(cell, NULL);
1597 static void
1598 _adg_cell_free(AdgTableCell *cell)
1600 _adg_cell_set_name(cell, NULL);
1601 _adg_cell_dispose(cell);
1602 g_free(cell);
1605 static gboolean
1606 _adg_value_match(gpointer key, gpointer value, gpointer user_data)
1608 return value == user_data;