[AdgLogo] Redesigned logo to better fit a cell
[adg.git] / adg / adg-table.c
blobf905f8a0e4a2cab8c185e36bf98c41becceafca4
1 /* ADG - Automatic Drawing Generation
2 * Copyright (C) 2007,2008,2009 Nicola Fontana <ntd at entidi.it>
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
21 /**
22 * SECTION:adg-table
23 * @short_description: A tabular entity
25 * The #AdgTable is the entity to be used for rendering data arranged
26 * in tabular evironments.
28 * To define a table, you should add to its internal model any number
29 * of row using adg_table_row_new() or adg_table_row_new_before().
31 * Every row should be segmented with different cells by using
32 * adg_table_cell_new() or adg_table_cell_new_before(). Any cell can
33 * be filled with a title and a value: the font to be used will be
34 * picked up from the #AdgTableStyle got by resolving the
35 * #AdgTable:table-dress property.
36 **/
38 /**
39 * AdgTable:
41 * All fields are private and should not be used directly.
42 * Use its public methods instead.
43 **/
45 /**
46 * AdgTableRow:
48 * An opaque structure referring to a row of an #AdgTable. Any
49 * table can have an unlimited number of rows.
50 **/
52 /**
53 * AdgTableCell:
55 * An opaque structure referring to the cell of an #AdgTableRow.
56 * Any row can have an unlimited number of cells.
57 **/
60 #include "adg-table.h"
61 #include "adg-table-private.h"
62 #include "adg-dress-builtins.h"
63 #include "adg-path.h"
64 #include "adg-line-style.h"
65 #include "adg-toy-text.h"
66 #include "adg-intl.h"
68 #define PARENT_OBJECT_CLASS ((GObjectClass *) adg_table_parent_class)
69 #define PARENT_ENTITY_CLASS ((AdgEntityClass *) adg_table_parent_class)
72 enum {
73 PROP_0,
74 PROP_TABLE_DRESS,
75 PROP_HAS_FRAME
78 static void dispose (GObject *object);
79 static void finalize (GObject *object);
80 static void get_property (GObject *object,
81 guint param_id,
82 GValue *value,
83 GParamSpec *pspec);
84 static void set_property (GObject *object,
85 guint param_id,
86 const GValue *value,
87 GParamSpec *pspec);
88 static void global_changed (AdgEntity *entity);
89 static void local_changed (AdgEntity *entity);
90 static void invalidate (AdgEntity *entity);
91 static void arrange (AdgEntity *entity);
92 static void arrange_grid (AdgEntity *entity);
93 static void arrange_frame (AdgEntity *entity);
94 static void render (AdgEntity *entity,
95 cairo_t *cr);
96 static gboolean switch_frame (AdgTable *table,
97 gboolean state);
98 static void propagate (AdgTable *table,
99 const gchar *detailed_signal,
100 ...);
101 static AdgTableRow * row_new (AdgTable *table,
102 AdgTableRow *before_row);
103 static void row_arrange_size (AdgTableRow *row);
104 static void row_arrange (AdgTableRow *row);
105 static void row_dispose (AdgTableRow *row);
106 static void row_free (AdgTableRow *row);
107 static AdgTableCell * cell_new (AdgTableRow *row,
108 AdgTableCell *before_cell,
109 gdouble width,
110 gboolean has_frame,
111 const gchar *name,
112 AdgEntity *title,
113 AdgEntity *value);
114 static void cell_set_name (AdgTableCell *cell,
115 const gchar *name);
116 static gboolean cell_set_title (AdgTableCell *cell,
117 AdgEntity *title);
118 static gboolean cell_set_value (AdgTableCell *cell,
119 AdgEntity *value);
120 static void cell_arrange_size (AdgTableCell *cell);
121 static void cell_arrange (AdgTableCell *cell);
122 static void cell_dispose (AdgTableCell *cell);
123 static void cell_free (AdgTableCell *cell);
124 static gboolean value_match (gpointer key,
125 gpointer value,
126 gpointer user_data);
129 G_DEFINE_TYPE(AdgTable, adg_table, ADG_TYPE_ENTITY);
132 static void
133 adg_table_class_init(AdgTableClass *klass)
135 GObjectClass *gobject_class;
136 AdgEntityClass *entity_class;
137 GParamSpec *param;
139 gobject_class = (GObjectClass *) klass;
140 entity_class = (AdgEntityClass *) klass;
142 g_type_class_add_private(klass, sizeof(AdgTablePrivate));
144 gobject_class->dispose = dispose;
145 gobject_class->finalize = finalize;
146 gobject_class->get_property = get_property;
147 gobject_class->set_property = set_property;
149 entity_class->global_changed = global_changed;
150 entity_class->local_changed = local_changed;
151 entity_class->invalidate = invalidate;
152 entity_class->arrange = arrange;
153 entity_class->render = render;
155 param = adg_param_spec_dress("table-dress",
156 P_("Table Dress"),
157 P_("The dress to use for stroking this entity"),
158 ADG_DRESS_TABLE,
159 G_PARAM_READWRITE);
160 g_object_class_install_property(gobject_class, PROP_TABLE_DRESS, param);
162 param = g_param_spec_boolean("has-frame",
163 P_("Has Frame Flag"),
164 P_("If enabled, a frame using the proper dress found in this table style will be drawn around the table extents"),
165 TRUE,
166 G_PARAM_READWRITE);
167 g_object_class_install_property(gobject_class, PROP_HAS_FRAME, param);
170 static void
171 adg_table_init(AdgTable *table)
173 AdgTablePrivate *data = G_TYPE_INSTANCE_GET_PRIVATE(table,
174 ADG_TYPE_TABLE,
175 AdgTablePrivate);
177 data->table_dress = ADG_DRESS_TABLE;
178 data->has_frame = TRUE;
180 data->table_style = NULL;
181 data->grid = NULL;
182 data->frame = NULL;
183 data->rows = NULL;
184 data->cell_names = NULL;
186 table->data = data;
189 static void
190 dispose(GObject *object)
192 AdgTablePrivate *data = ((AdgTable *) object)->data;
194 if (data->grid != NULL) {
195 g_object_unref(data->grid);
196 data->grid = NULL;
199 if (data->frame != NULL) {
200 g_object_unref(data->frame);
201 data->frame = NULL;
204 /* The rows finalization will happen in the finalize() method */
205 if (data->rows != NULL)
206 g_slist_foreach(data->rows, (GFunc) row_dispose, NULL);
208 if (PARENT_OBJECT_CLASS->dispose != NULL)
209 PARENT_OBJECT_CLASS->dispose(object);
212 static void
213 finalize(GObject *object)
215 AdgTable *table;
216 AdgTablePrivate *data;
218 table = (AdgTable *) object;
219 data = table->data;
221 if (data->rows != NULL) {
222 g_slist_foreach(data->rows, (GFunc) row_free, NULL);
223 g_slist_free(data->rows);
226 if (data->cell_names != NULL)
227 g_hash_table_destroy(data->cell_names);
229 if (PARENT_OBJECT_CLASS->finalize != NULL)
230 PARENT_OBJECT_CLASS->finalize(object);
233 static void
234 get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
236 AdgTablePrivate *data = ((AdgTable *) object)->data;
238 switch (prop_id) {
239 case PROP_TABLE_DRESS:
240 g_value_set_int(value, data->table_dress);
241 break;
242 case PROP_HAS_FRAME:
243 g_value_set_boolean(value, data->has_frame);
244 break;
245 default:
246 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
247 break;
251 static void
252 set_property(GObject *object, guint prop_id,
253 const GValue *value, GParamSpec *pspec)
255 AdgTable *table;
256 AdgTablePrivate *data;
258 table = (AdgTable *) object;
259 data = table->data;
261 switch (prop_id) {
262 case PROP_TABLE_DRESS:
263 adg_dress_set(&data->table_dress, g_value_get_int(value));
264 break;
265 case PROP_HAS_FRAME:
266 switch_frame(table, g_value_get_boolean(value));
267 break;
268 default:
269 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
270 break;
276 * adg_table_new:
278 * Creates a new empty table entity. The #AdgEntity:local-method
279 * property is set by default to #ADG_MIX_DISABLED, that is the
280 * table is not subject to any local transformations.
282 * Returns: the newly created table entity
284 AdgTable *
285 adg_table_new(void)
287 return g_object_new(ADG_TYPE_TABLE,
288 "local-method", ADG_MIX_DISABLED, NULL);
292 * adg_table_get_table_dress:
293 * @table: an #AdgTable
295 * Gets the table dress to be used in rendering @table.
297 * Returns: the current table dress
299 AdgDress
300 adg_table_get_table_dress(AdgTable *table)
302 AdgTablePrivate *data;
304 g_return_val_if_fail(ADG_IS_TABLE(table), ADG_DRESS_UNDEFINED);
306 data = table->data;
308 return data->table_dress;
312 * adg_table_set_table_dress:
313 * @table: an #AdgTable
314 * @dress: the new #AdgDress to use
316 * Sets a new table dress for rendering @table. The new dress
317 * must be related to the original dress for this property:
318 * you cannot set a dress used for line styles to a dress
319 * managing fonts.
321 * The check is done by calling adg_dress_are_related() with
322 * @dress and the previous dress as arguments. Check out its
323 * documentation for details on what is a related dress.
325 void
326 adg_table_set_table_dress(AdgTable *table, AdgDress dress)
328 AdgTablePrivate *data;
330 g_return_if_fail(ADG_IS_TABLE(table));
332 data = table->data;
334 if (adg_dress_set(&data->table_dress, dress))
335 g_object_notify((GObject *) table, "table-dress");
339 * adg_table_has_frame:
340 * @table: an #AdgTable
342 * Returns the state of the #AdgTable:has-frame property.
344 * Returns: the current state
346 gboolean
347 adg_table_has_frame(AdgTable *table)
349 AdgTablePrivate *data;
351 g_return_val_if_fail(ADG_IS_TABLE(table), FALSE);
353 data = table->data;
355 return data->has_frame;
359 * adg_table_switch_frame:
360 * @table: an #AdgTable
361 * @state: the new state of the frame
363 * Sets the #AdgTable:has-frame property: %TRUE will draw a
364 * frame around the whole table using the #AdgTableStyle:frame-dress
365 * dress of the table style.
367 void
368 adg_table_switch_frame(AdgTable *table, gboolean state)
370 g_return_if_fail(ADG_IS_TABLE(table));
372 if (switch_frame(table, state))
373 g_object_notify((GObject *) table, "has-frame");
377 * adg_table_get_n_rows:
378 * @table: an #AdgTable
380 * Gets the number of rows stored in @table.
382 * Returns: the number of rows or %0 on empty @table or errors
384 guint
385 adg_table_get_n_rows(AdgTable *table)
387 AdgTablePrivate *data;
389 g_return_val_if_fail(ADG_IS_TABLE(table), 0);
391 data = table->data;
393 if (data->rows == NULL)
394 return 0;
396 return g_slist_length(data->rows);
400 * adg_table_row_new:
401 * @table: an #AdgTable
403 * Creates a new empty row and appends it at the end of the rows
404 * yet present in @table. By default, the height of this new
405 * row will be the fallback value provided by the table style:
406 * you can override it by using adg_table_row_set_height().
408 * Returns: the newly created row or %NULL on errors
410 AdgTableRow *
411 adg_table_row_new(AdgTable *table)
413 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
415 return row_new(table, NULL);
419 * adg_table_row_new_before:
420 * @row: a valid #AdgTableRow
422 * Creates a new empty row with default height and inserts it
423 * just before @row.
425 * Returns: the newly created row or %NULL on errors
427 AdgTableRow *
428 adg_table_row_new_before(AdgTableRow *row)
430 g_return_val_if_fail(row != NULL, NULL);
431 g_return_val_if_fail(ADG_IS_TABLE(row->table), NULL);
433 return row_new(row->table, row);
437 * adg_table_row_delete:
438 * @row: a valid #AdgTableRow
440 * Removes @row from its owner table and frees every resources allocated
441 * by it. This means also the eventual cells owned by @row will be freed.
443 void
444 adg_table_row_delete(AdgTableRow *row)
446 AdgTable *table;
447 AdgTablePrivate *data;
449 g_return_if_fail(row != NULL);
451 table = row->table;
453 g_return_if_fail(ADG_IS_TABLE(table));
455 data = table->data;
457 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
458 g_slist_free(row->cells);
459 data->rows = g_slist_remove(data->rows, row);
461 g_free(row);
465 * adg_table_row_get_n_cells:
466 * @row: a valid #AdgTableRow
468 * Gets the number of cells stored in @row.
470 * Returns: the number of cells or %0 on empty row or errors
472 guint
473 adg_table_row_get_n_cells(const AdgTableRow *row)
475 g_return_val_if_fail(row != NULL, 0);
477 if (row->cells == NULL)
478 return 0;
480 return g_slist_length(row->cells);
484 * adg_table_row_set_height:
485 * @row: a valid #AdgTableRow
486 * @height: the new height
488 * Sets a new height on @row. The extents will be invalidated to
489 * recompute the whole layout of the table. Specifying %0 in
490 * @height will use the default height set in the table style.
492 void
493 adg_table_row_set_height(AdgTableRow *row, gdouble height)
495 g_return_if_fail(row != NULL);
497 row->height = height;
499 adg_entity_invalidate((AdgEntity *) row->table);
503 * adg_table_row_extents:
504 * @row: a valid #AdgTableRow
506 * Gets the extents of @row. This function is useful only after the
507 * arrange() phase as in the other situation the extents will likely
508 * be not up to date.
510 * Returns: the extents of @row or %NULL on errors
512 const CpmlExtents *
513 adg_table_row_extents(AdgTableRow *row)
515 g_return_val_if_fail(row != NULL, NULL);
517 return &row->extents;
521 * adg_table_get_cell:
522 * @table: an #AdgTable
523 * @name: the name of a cell
525 * Gets the cell named @name inside @table. Only named cells can be
526 * retrieved by this method.
528 * Returns: the requested cell or %NULL if not found
530 AdgTableCell *
531 adg_table_get_cell(AdgTable *table, const gchar *name)
533 AdgTablePrivate *data;
535 g_return_val_if_fail(ADG_IS_TABLE(table), NULL);
537 data = table->data;
539 if (data->cell_names == NULL)
540 return NULL;
542 return g_hash_table_lookup(data->cell_names, name);
546 * adg_table_cell_new:
547 * @row: a valid #AdgTableRow
548 * @width: width of the cell
550 * Creates a new empty cell without a frame and appends it at the
551 * end of the cells yet present in @row. You can add content to the
552 * cell by using adg_table_cell_set_title() and
553 * adg_table_cell_set_value() or enable the frame with
554 * adg_table_cell_switch_frame().
556 * A positive @width value specifies the width of this cell in global
557 * space: if the width of its content (that is, either the title or the
558 * value entity) will be greater than @width, it will be rendered
559 * outside the cell boundary box, luckely overwriting the adiacent
560 * cells.
562 * Using %0 as @width means the width of the cell will be automatically
563 * adjusted to the maximum width of its content.
565 * Negative width values are not allowed: this condition will raise
566 * a warning without any further processing.
568 * Returns: the newly created cell or %NULL on errors
570 AdgTableCell *
571 adg_table_cell_new(AdgTableRow *row, gdouble width)
573 g_return_val_if_fail(row != NULL, NULL);
574 g_return_val_if_fail(width >= 0, NULL);
576 return cell_new(row, NULL, width, FALSE, NULL, NULL, NULL);
580 * adg_table_cell_new_before:
581 * @cell: a valid #AdgTableCell
582 * @width: width of the cell
584 * Creates a new cell and inserts it rigthly before the @cell cell.
585 * This works similarily and accepts the same parameters as the
586 * adg_table_cell_new() function.
588 * Returns: the newly created cell or %NULL on errors
590 AdgTableCell *
591 adg_table_cell_new_before(AdgTableCell *cell, gdouble width)
593 g_return_val_if_fail(cell != NULL, NULL);
594 g_return_val_if_fail(cell->row != NULL, NULL);
595 g_return_val_if_fail(width >= 0, NULL);
597 return cell_new(cell->row, cell, width, FALSE, NULL, NULL, NULL);
601 * adg_table_cell_new_full:
602 * @row: a valid #AdgTableRow
603 * @width: width of the cell
604 * @name: name to associate
605 * @title: title to render
606 * @value: value to render
608 * A convenient function to append a framed cell to @row with a
609 * specific title and value text. The font to use for rendering
610 * @title and @value will be picked up from the table style, so
611 * be sure to have the correct table dress set before calling
612 * this function.
614 * @row and @width have the same meanings as in adg_table_cell_new():
615 * check its documentation for details.
617 * @name is an optional identifier to univoquely access this cell
618 * by using adg_table_get_cell(). The identifier must be univoque:
619 * if there is yet a cell with the same name a warning message will
620 * be raised and the function will fail.
622 * Returns: the newly created cell or %NULL on errors
624 AdgTableCell *
625 adg_table_cell_new_full(AdgTableRow *row, gdouble width, const gchar *name,
626 const gchar *title, const gchar *value)
628 AdgEntity *table_entity, *title_entity, *value_entity;
629 AdgTableStyle *table_style;
630 AdgDress table_dress, font_dress;
632 g_return_val_if_fail(row != NULL, NULL);
634 table_entity = (AdgEntity *) row->table;
635 table_dress = adg_table_get_table_dress(row->table);
636 table_style = (AdgTableStyle *) adg_entity_style(table_entity, table_dress);
638 if (title != NULL) {
639 font_dress = adg_table_style_get_title_dress(table_style);
640 title_entity = g_object_new(ADG_TYPE_TOY_TEXT,
641 "label", title,
642 "font-dress", font_dress, NULL);
643 } else {
644 title_entity = NULL;
647 if (value != NULL) {
648 font_dress = adg_table_style_get_value_dress(table_style);
649 value_entity = g_object_new(ADG_TYPE_TOY_TEXT,
650 "label", value,
651 "font-dress", font_dress, NULL);
652 } else {
653 value_entity = NULL;
656 return cell_new(row, NULL, width, TRUE, name, title_entity, value_entity);
660 * adg_table_cell_delete:
661 * @cell: a valid #AdgTableCell
663 * Deletes @cell removing it from the container row and freeing
664 * any resource associated to it.
666 void
667 adg_table_cell_delete(AdgTableCell *cell)
669 AdgTableRow *row;
671 g_return_if_fail(cell != NULL);
673 row = cell->row;
675 g_return_if_fail(row != NULL);
677 cell_free(cell);
678 row->cells = g_slist_remove(row->cells, cell);
682 * adg_table_cell_get_name:
683 * @cell: a valid #AdgTableCell
685 * Gets the name assigned to @cell. This function is highly inefficient
686 * as the cell names are stored in a hash table optimized to get a cell
687 * from a name. Getting the name from a cell involves a full hash table
688 * inspection.
690 * Returns: the name bound of @cell or %NULL on no name or errors
692 const gchar *
693 adg_table_cell_get_name(AdgTableCell *cell)
695 AdgTablePrivate *data;
697 g_return_val_if_fail(cell != NULL, NULL);
699 data = cell->row->table->data;
701 return g_hash_table_find(data->cell_names, value_match, cell);
705 * adg_table_cell_set_name:
706 * @cell: a valid #AdgTableCell
707 * @name: the new name of @cell
709 * Sets a new name on @cell: this will allow to access @cell by
710 * name at a later time using the adg_table_get_cell() API.
712 void
713 adg_table_cell_set_name(AdgTableCell *cell, const gchar *name)
715 AdgTablePrivate *data;
717 g_return_if_fail(cell != NULL);
719 data = cell->row->table->data;
721 cell_set_name(cell, NULL);
722 cell_set_name(cell, name);
726 * adg_table_cell_get_title:
727 * @cell: a valid #AdgTableCell
729 * Gets the current title of @cell. The returned string is owned
730 * by @cell and must not be modified or freed.
732 * Returns: the title entity or %NULL for undefined title
734 AdgEntity *
735 adg_table_cell_get_title(AdgTableCell *cell)
737 g_return_val_if_fail(cell != NULL, NULL);
739 return cell->title;
743 * adg_table_cell_set_title:
744 * @cell: a valid #AdgTableCell
745 * @title: the new title entity
747 * Sets @title as the new title entity of @cell. The top left
748 * corner of the bounding box of @title will be cohincident to
749 * the top left corner of the cell extents, taking into accounts
750 * eventual padding spaces specified by the table style.
752 * The old internal entity is unrefenrenced while the @title (if
753 * not %NULL) is refenenced with g_object_ref_sink().
755 * @title can be %NULL, in which case the old entity is removed.
757 void
758 adg_table_cell_set_title(AdgTableCell *cell, AdgEntity *title)
760 g_return_if_fail(cell != NULL);
761 g_return_if_fail(title == NULL || ADG_IS_ENTITY(title));
763 if (cell_set_title(cell, title))
764 adg_entity_invalidate((AdgEntity *) cell->row->table);
768 * adg_table_cell_set_text_title:
769 * @cell: a valid #AdgTableCell
770 * @title: a text string
772 * Convenient function to set a the title of a cell using an #AdgToyText
773 * entity with the font dress picked from #AdgTable:table-dress with
774 * a call to adg_table_style_get_title_dress().
776 void
777 adg_table_cell_set_text_title(AdgTableCell *cell, const gchar *title)
779 AdgTable *table;
780 AdgEntity *entity;
781 AdgTableStyle *table_style;
782 AdgDress table_dress, font_dress;
784 g_return_if_fail(cell != NULL);
786 if (title == NULL)
787 adg_table_cell_set_title(cell, NULL);
789 if (cell->title != NULL) {
790 const gchar *old_title;
792 if (ADG_IS_TOY_TEXT(cell->title))
793 old_title = adg_toy_text_get_label((AdgToyText *) cell->title);
794 else
795 old_title = NULL;
797 if (adg_strcmp(title, old_title) == 0)
798 return;
801 table = cell->row->table;
802 table_dress = adg_table_get_table_dress(table);
803 table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table,
804 table_dress);
805 font_dress = adg_table_style_get_title_dress(table_style);
806 entity = g_object_new(ADG_TYPE_TOY_TEXT,
807 "local-method", ADG_MIX_PARENT,
808 "label", title,
809 "font-dress", font_dress, NULL);
811 adg_table_cell_set_title(cell, entity);
815 * adg_table_cell_get_value:
816 * @cell: a valid #AdgTableCell
818 * Gets the current value of @cell. The returned string is owned
819 * by @cell and must not be modified or freed.
821 * Returns: the value entity or %NULL for undefined value
823 AdgEntity *
824 adg_table_cell_get_value(AdgTableCell *cell)
826 g_return_val_if_fail(cell != NULL, NULL);
828 return cell->value;
832 * adg_table_cell_set_value:
833 * @cell: a valid #AdgTableCell
834 * @value: the new value entity
836 * Sets @value as the new value entity of @cell. The bottom middle
837 * point of the bounding box of @value will be cohincident to the
838 * bottom middle point of the cell extents, taking into accounts
839 * eventual padding spaces specified by the table style.
841 * The old internal entity is unrefenrenced while the @value (if
842 * not %NULL) is refenenced with g_object_ref_sink().
844 * @value can be %NULL, in which case the old entity is removed.
846 void
847 adg_table_cell_set_value(AdgTableCell *cell, AdgEntity *value)
849 g_return_if_fail(cell != NULL);
850 g_return_if_fail(value == NULL || ADG_IS_ENTITY(value));
852 if (cell_set_value(cell, value))
853 adg_entity_invalidate((AdgEntity *) cell->row->table);
857 * adg_table_cell_set_text_value:
858 * @cell: a valid #AdgTableCell
859 * @value: a text string
861 * Convenient function to set a the value of a cell using an #AdgToyText
862 * entity with a value font dress picked from #AdgTable:table-dress with
863 * a call to adg_table_style_get_value_dress().
865 void
866 adg_table_cell_set_text_value(AdgTableCell *cell, const gchar *value)
868 AdgTable *table;
869 AdgEntity *entity;
870 AdgTableStyle *table_style;
871 AdgDress table_dress, font_dress;
873 g_return_if_fail(cell != NULL);
875 if (value == NULL)
876 adg_table_cell_set_value(cell, NULL);
878 if (cell->value != NULL) {
879 const gchar *old_value;
881 if (ADG_IS_TOY_TEXT(cell->value))
882 old_value = adg_toy_text_get_label((AdgToyText *) cell->value);
883 else
884 old_value = NULL;
886 if (adg_strcmp(value, old_value) == 0)
887 return;
890 table = cell->row->table;
891 table_dress = adg_table_get_table_dress(table);
892 table_style = (AdgTableStyle *) adg_entity_style((AdgEntity *) table,
893 table_dress);
894 font_dress = adg_table_style_get_value_dress(table_style);
895 entity = g_object_new(ADG_TYPE_TOY_TEXT,
896 "local-method", ADG_MIX_PARENT,
897 "label", value,
898 "font-dress", font_dress, NULL);
900 adg_table_cell_set_value(cell, entity);
904 * adg_table_cell_set_width:
905 * @cell: a valid #AdgTableCell
906 * @width: the new width
908 * Sets a new width on @cell. The extents on the whole table
909 * will be invalidated, so will be recomputed in the next
910 * arrange() phase.
912 void
913 adg_table_cell_set_width(AdgTableCell *cell, gdouble width)
915 g_return_if_fail(cell != NULL);
917 cell->width = width;
919 adg_entity_invalidate((AdgEntity *) cell->row->table);
923 * adg_table_cell_has_frame:
924 * @cell: a valid #AdgTableCell
926 * Gets the frame flag of @cell.
928 * Returns: the frame flag
930 gboolean
931 adg_table_cell_has_frame(AdgTableCell *cell)
933 g_return_val_if_fail(cell != NULL, FALSE);
935 return cell->has_frame;
939 * adg_table_cell_switch_frame:
940 * @cell: a valid #AdgTableCell
941 * @state: the new frame state
943 * Sets the frame flag of @cell: if @state is %TRUE, a frame around
944 * @cell will be rendered using the #AdgTableStyle:cell-dress dress
945 * of the table style.
947 void
948 adg_table_cell_switch_frame(AdgTableCell *cell, gboolean state)
950 AdgTablePrivate *data;
952 g_return_if_fail(cell != NULL);
954 if (cell->has_frame == state)
955 return;
957 data = cell->row->table->data;
958 cell->has_frame = state;
960 if (data->grid != NULL) {
961 g_object_unref(data->grid);
962 data->grid = NULL;
967 * adg_table_cell_extents:
968 * @cell: a valid #AdgTableCell
970 * Gets the extents of @cell. This function is useful only after the
971 * arrange() phase as in the other situation the extents will likely
972 * be not up to date.
974 * Returns: the extents of @cell or %NULL on errors
976 const CpmlExtents *
977 adg_table_cell_extents(AdgTableCell *cell)
979 g_return_val_if_fail(cell != NULL, NULL);
981 return &cell->extents;
985 static void
986 global_changed(AdgEntity *entity)
988 PARENT_ENTITY_CLASS->global_changed(entity);
989 propagate((AdgTable *) entity, "global-changed");
992 static void
993 local_changed(AdgEntity *entity)
995 PARENT_ENTITY_CLASS->local_changed(entity);
996 propagate((AdgTable *) entity, "local-changed");
999 static void
1000 invalidate(AdgEntity *entity)
1002 propagate((AdgTable *) entity, "invalidate");
1005 static void
1006 arrange(AdgEntity *entity)
1008 AdgTable *table;
1009 AdgTablePrivate *data;
1010 CpmlExtents extents;
1011 const AdgPair *spacing;
1012 GSList *row_node;
1013 AdgTableRow *row;
1014 gdouble y;
1016 table = (AdgTable *) entity;
1017 data = table->data;
1018 cpml_extents_copy(&extents, adg_entity_extents(entity));
1020 /* Resolve the table style */
1021 if (data->table_style == NULL)
1022 data->table_style = (AdgTableStyle *)
1023 adg_entity_style(entity, data->table_dress);
1025 if (extents.is_defined) {
1026 if (data->grid != NULL)
1027 adg_entity_arrange((AdgEntity *) data->grid);
1028 if (data->frame != NULL)
1029 adg_entity_arrange((AdgEntity *) data->frame);
1030 return;
1033 spacing = adg_table_style_get_cell_spacing(data->table_style);
1034 extents.size.x = 0;
1035 extents.size.y = 0;
1037 for (row_node = data->rows; row_node; row_node = row_node->next) {
1038 row = row_node->data;
1040 row_arrange_size(row);
1042 if (row->extents.size.x > extents.size.x)
1043 extents.size.x = row->extents.size.x;
1044 extents.size.y += row->extents.size.y;
1047 /* TODO: update the org according to the table alignments */
1049 y = extents.org.y + spacing->y;
1050 for (row_node = data->rows; row_node; row_node = row_node->next) {
1051 row = row_node->data;
1053 row->extents.org.x = extents.org.x;
1054 row->extents.org.y = y;
1056 row_arrange(row);
1058 y += row->extents.size.y + spacing->y;
1061 extents.is_defined = TRUE;
1062 adg_entity_set_extents(entity, &extents);
1064 arrange_grid(entity);
1065 arrange_frame(entity);
1068 static void
1069 arrange_grid(AdgEntity *entity)
1071 AdgTablePrivate *data;
1072 AdgPath *path;
1073 AdgTrail *trail;
1074 GSList *row_node, *cell_node;
1075 AdgTableRow *row;
1076 AdgTableCell *cell;
1077 AdgPair pair;
1078 AdgDress dress;
1080 data = ((AdgTable *) entity)->data;
1082 if (data->grid != NULL)
1083 return;
1085 path = adg_path_new();
1086 trail = (AdgTrail *) path;
1088 for (row_node = data->rows; row_node; row_node = row_node->next) {
1089 row = row_node->data;
1091 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1092 cell = cell_node->data;
1094 if (!cell->has_frame)
1095 continue;
1097 cpml_pair_copy(&pair, &cell->extents.org);
1098 adg_path_move_to(path, &pair);
1099 pair.x += cell->extents.size.x;
1100 adg_path_line_to(path, &pair);
1101 pair.y += cell->extents.size.y;
1102 adg_path_line_to(path, &pair);
1103 pair.x -= cell->extents.size.x;
1104 adg_path_line_to(path, &pair);
1105 adg_path_close(path);
1109 if (!adg_trail_extents(trail)->is_defined)
1110 return;
1112 dress = adg_table_style_get_grid_dress(data->table_style);
1113 data->grid = g_object_new(ADG_TYPE_STROKE,
1114 "local-method", ADG_MIX_PARENT,
1115 "line-dress", dress,
1116 "trail", trail,
1117 "parent", entity, NULL);
1118 adg_entity_arrange((AdgEntity *) data->grid);
1121 static void
1122 arrange_frame(AdgEntity *entity)
1124 AdgTablePrivate *data;
1125 AdgPath *path;
1126 const CpmlExtents *extents;
1127 AdgPair pair;
1128 AdgDress dress;
1130 data = ((AdgTable *) entity)->data;
1132 if (data->frame != NULL || !data->has_frame)
1133 return;
1135 path = adg_path_new();
1136 extents = adg_entity_extents(entity);
1138 cpml_pair_copy(&pair, &extents->org);
1139 adg_path_move_to(path, &pair);
1140 pair.x += extents->size.x;
1141 adg_path_line_to(path, &pair);
1142 pair.y += extents->size.y;
1143 adg_path_line_to(path, &pair);
1144 pair.x -= extents->size.x;
1145 adg_path_line_to(path, &pair);
1146 adg_path_close(path);
1148 dress = adg_table_style_get_frame_dress(data->table_style);
1150 data->frame = g_object_new(ADG_TYPE_STROKE,
1151 "local-method", ADG_MIX_PARENT,
1152 "line-dress", dress,
1153 "trail", (AdgTrail *) path,
1154 "parent", entity, NULL);
1155 adg_entity_arrange((AdgEntity *) data->frame);
1158 static void
1159 render(AdgEntity *entity, cairo_t *cr)
1161 cairo_set_matrix(cr, adg_entity_ctm(entity));
1163 propagate((AdgTable *) entity, "render", cr);
1166 static gboolean
1167 switch_frame(AdgTable *table, gboolean state)
1169 AdgTablePrivate *data = table->data;
1171 if (data->has_frame == state)
1172 return FALSE;
1174 data->has_frame = state;
1176 if (data->frame != NULL) {
1177 g_object_unref(data->frame);
1178 data->frame = NULL;
1181 return TRUE;
1184 static void
1185 propagate(AdgTable *table, const gchar *detailed_signal, ...)
1187 guint signal_id;
1188 GQuark detail = 0;
1189 va_list var_args, var_copy;
1190 AdgTablePrivate *data;
1191 GSList *row_node;
1192 AdgTableRow *row;
1193 GSList *cell_node;
1194 AdgTableCell *cell;
1196 if (!g_signal_parse_name(detailed_signal, G_TYPE_FROM_INSTANCE(table),
1197 &signal_id, &detail, FALSE)) {
1198 g_assert_not_reached();
1201 va_start(var_args, detailed_signal);
1202 data = table->data;
1204 if (data->frame != NULL) {
1205 G_VA_COPY(var_copy, var_args);
1206 g_signal_emit_valist(data->frame, signal_id, detail, var_copy);
1209 if (data->grid != NULL) {
1210 G_VA_COPY(var_copy, var_args);
1211 g_signal_emit_valist(data->grid, signal_id, detail, var_copy);
1214 for (row_node = data->rows; row_node; row_node = row_node->next) {
1215 row = row_node->data;
1217 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1218 cell = cell_node->data;
1220 if (cell->title != NULL) {
1221 G_VA_COPY(var_copy, var_args);
1222 g_signal_emit_valist(cell->title, signal_id, detail, var_copy);
1225 if (cell->value != NULL) {
1226 G_VA_COPY(var_copy, var_args);
1227 g_signal_emit_valist(cell->value, signal_id, detail, var_copy);
1232 va_end(var_args);
1235 static AdgTableRow *
1236 row_new(AdgTable *table, AdgTableRow *before_row)
1238 AdgTablePrivate *data;
1239 AdgTableRow *new_row;
1241 data = table->data;
1242 new_row = g_new(AdgTableRow, 1);
1243 new_row->table = table;
1244 new_row->cells = NULL;
1245 new_row->height = 0;
1246 new_row->extents.is_defined = FALSE;
1248 if (before_row == NULL) {
1249 data->rows = g_slist_append(data->rows, new_row);
1250 } else {
1251 GSList *before_node = g_slist_find(data->rows, before_row);
1253 /* This MUST be present, otherwise something really bad happened */
1254 g_assert(before_node != NULL);
1256 data->rows = g_slist_insert_before(data->rows, before_node, new_row);
1259 invalidate((AdgEntity *) table);
1261 return new_row;
1264 static void
1265 row_arrange_size(AdgTableRow *row)
1267 AdgTableStyle *table_style;
1268 const AdgPair *spacing;
1269 CpmlVector *size;
1270 AdgTableCell *cell;
1271 GSList *cell_node;
1273 table_style = GET_TABLE_STYLE(row->table);
1274 spacing = adg_table_style_get_cell_spacing(table_style);
1275 size = &row->extents.size;
1277 size->x = 0;
1278 if (row->height == 0)
1279 size->y = adg_table_style_get_row_height(table_style);
1280 else
1281 size->y = row->height;
1283 /* Compute the row width by summing every cell width */
1284 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1285 cell = cell_node->data;
1287 cell_arrange_size(cell);
1289 size->x += cell->extents.size.x + spacing->x;
1292 if (size->x > 0)
1293 size->x += spacing->x;
1296 /* Before calling this function, row->extents should be updated */
1297 static void
1298 row_arrange(AdgTableRow *row)
1300 AdgTableStyle *table_style;
1301 const AdgPair *spacing;
1302 const AdgPair *org;
1303 AdgTableCell *cell;
1304 GSList *cell_node;
1305 gdouble x;
1307 table_style = GET_TABLE_STYLE(row->table);
1308 spacing = adg_table_style_get_cell_spacing(table_style);
1309 org = &row->extents.org;
1310 x = org->x + spacing->x;
1312 for (cell_node = row->cells; cell_node; cell_node = cell_node->next) {
1313 cell = cell_node->data;
1315 cell->extents.org.x = x;
1316 cell->extents.org.y = org->y;
1318 cell_arrange(cell);
1320 x += cell->extents.size.x + spacing->x;
1323 row->extents.is_defined = TRUE;
1326 static void
1327 row_dispose(AdgTableRow *row)
1329 g_slist_foreach(row->cells, (GFunc) cell_dispose, NULL);
1332 static void
1333 row_free(AdgTableRow *row)
1335 g_slist_foreach(row->cells, (GFunc) cell_free, NULL);
1336 g_slist_free(row->cells);
1338 g_free(row);
1341 static AdgTableCell *
1342 cell_new(AdgTableRow *row, AdgTableCell *before_cell,
1343 gdouble width, gboolean has_frame,
1344 const gchar *name, AdgEntity *title, AdgEntity *value)
1346 AdgTablePrivate *data;
1347 AdgTableCell *new_cell;
1349 data = row->table->data;
1351 if (name != NULL && data->cell_names != NULL &&
1352 g_hash_table_lookup(data->cell_names, name) != NULL) {
1353 g_warning(_("%s: `%s' cell name is yet used"), G_STRLOC, name);
1354 return NULL;
1357 new_cell = g_new(AdgTableCell, 1);
1358 new_cell->row = row;
1359 new_cell->width = width;
1360 new_cell->has_frame = has_frame;
1361 new_cell->title = NULL;
1362 new_cell->value = NULL;
1363 new_cell->extents.is_defined = FALSE;
1365 cell_set_title(new_cell, title);
1366 cell_set_value(new_cell, value);
1368 if (before_cell == NULL) {
1369 row->cells = g_slist_append(row->cells, new_cell);
1370 } else {
1371 GSList *before_node = g_slist_find(row->cells, before_cell);
1373 /* This MUST be not null, otherwise something really bad happened */
1374 g_assert(before_node != NULL);
1376 row->cells = g_slist_insert_before(row->cells, before_node, new_cell);
1379 if (name != NULL)
1380 cell_set_name(new_cell, name);
1382 return new_cell;
1385 static void
1386 cell_set_name(AdgTableCell *cell, const gchar *name)
1388 AdgTablePrivate *data = cell->row->table->data;
1390 if (data->cell_names == NULL && name == NULL)
1391 return;
1393 if (data->cell_names == NULL)
1394 data->cell_names = g_hash_table_new_full(g_str_hash, g_str_equal,
1395 g_free, NULL);
1397 if (name == NULL)
1398 g_hash_table_foreach_remove(data->cell_names, value_match, cell);
1399 else
1400 g_hash_table_insert(data->cell_names, g_strdup(name), cell);
1403 static gboolean
1404 cell_set_title(AdgTableCell *cell, AdgEntity *title)
1406 if (cell->title == title)
1407 return FALSE;
1409 if (cell->title != NULL)
1410 g_object_unref(cell->title);
1412 cell->title = title;
1414 if (title != NULL) {
1415 g_object_ref_sink(title);
1416 adg_entity_set_parent(title, (AdgEntity *) cell->row->table);
1417 adg_entity_set_local_method(title, ADG_MIX_PARENT);
1420 return TRUE;
1423 static gboolean
1424 cell_set_value(AdgTableCell *cell, AdgEntity *value)
1426 if (cell->value == value)
1427 return FALSE;
1429 if (cell->value != NULL)
1430 g_object_unref(cell->value);
1432 cell->value = value;
1434 if (value != NULL) {
1435 g_object_ref_sink(value);
1436 adg_entity_set_parent(value, (AdgEntity *) cell->row->table);
1437 adg_entity_set_local_method(value, ADG_MIX_PARENT);
1440 return TRUE;
1443 static void
1444 cell_arrange_size(AdgTableCell *cell)
1446 AdgTableStyle *table_style;
1447 CpmlVector *size;
1449 table_style = GET_TABLE_STYLE(cell->row->table);
1450 size = &cell->extents.size;
1452 if (cell->title != NULL)
1453 adg_entity_arrange(cell->title);
1455 if (cell->value != NULL)
1456 adg_entity_arrange(cell->value);
1458 size->y = cell->row->extents.size.y;
1460 if (cell->width == 0) {
1461 const CpmlVector *content_size;
1463 /* The width depends on the cell content (default: 0) */
1464 size->x = 0;
1466 if (cell->title != NULL) {
1467 content_size = &adg_entity_extents(cell->title)->size;
1468 size->x = content_size->x;
1471 if (cell->value != NULL) {
1472 content_size = &adg_entity_extents(cell->value)->size;
1473 if (content_size->x > size->x)
1474 size->x = content_size->x;
1477 size->x += adg_table_style_get_cell_padding(table_style)->x * 2;
1478 } else {
1479 size->x = cell->width;
1483 /* Before calling this function, cell->extents should be updated */
1484 static void
1485 cell_arrange(AdgTableCell *cell)
1487 AdgTableStyle *table_style;
1488 const AdgPair *padding;
1489 const CpmlPair *org;
1490 const CpmlVector *size, *content_size;
1491 AdgMatrix map;
1493 table_style = GET_TABLE_STYLE(cell->row->table);
1494 padding = adg_table_style_get_cell_padding(table_style);
1495 org = &cell->extents.org;
1496 size = &cell->extents.size;
1498 if (cell->title != NULL) {
1499 content_size = &adg_entity_extents(cell->title)->size;
1501 cairo_matrix_init_translate(&map,
1502 org->x + padding->x,
1503 org->y + content_size->y + padding->y);
1505 adg_entity_set_global_map(cell->title, &map);
1508 if (cell->value != NULL) {
1509 AdgMatrix global_map;
1510 CpmlExtents extents;
1512 adg_entity_get_global_map(cell->value, &global_map);
1513 cpml_extents_copy(&extents, adg_entity_extents(cell->value));
1514 cpml_extents_transform(&extents, &global_map);
1516 cairo_matrix_init_translate(&map,
1517 org->x + (size->x - extents.size.x) / 2,
1518 org->y + size->y /* - padding->y */);
1520 adg_entity_set_local_map(cell->value, &map);
1523 cell->extents.is_defined = TRUE;
1526 static void
1527 cell_dispose(AdgTableCell *cell)
1529 cell_set_title(cell, NULL);
1530 cell_set_value(cell, NULL);
1533 static void
1534 cell_free(AdgTableCell *cell)
1536 cell_set_name(cell, NULL);
1537 cell_dispose(cell);
1538 g_free(cell);
1541 static gboolean
1542 value_match(gpointer key, gpointer value, gpointer user_data)
1544 return value == user_data;