1 /*
2 * e-table-state.c
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program; if not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17
18 #include "e-table-state.h"
19
20 #include <stdlib.h>
21 #include <string.h>
22
23 #include <libxml/parser.h>
24 #include <libxml/xmlmemory.h>
25
26 #include <libedataserver/libedataserver.h>
27
28 #include "e-table-specification.h"
29 #include "e-xml-utils.h"
30
31 #define E_TABLE_STATE_GET_PRIVATE(obj) \
32 (G_TYPE_INSTANCE_GET_PRIVATE \
33 ((obj), E_TYPE_TABLE_STATE, ETableStatePrivate))
34
35 #define STATE_VERSION 0.1
36
37 typedef struct _ParseData ParseData;
38
39 struct _ETableStatePrivate {
40 GWeakRef specification;
41 };
42
43 enum {
44 PROP_0,
45 PROP_SPECIFICATION
46 };
47
48 struct _ParseData {
49 ETableState *state;
50 GVariantBuilder *column_info;
51 };
52
G_DEFINE_TYPE(ETableState,e_table_state,G_TYPE_OBJECT)53 G_DEFINE_TYPE (ETableState, e_table_state, G_TYPE_OBJECT)
54
55 static ParseData *
56 parse_data_new (ETableSpecification *specification)
57 {
58 ParseData *parse_data;
59 const GVariantType *type;
60
61 type = G_VARIANT_TYPE ("a(xd)");
62
63 parse_data = g_slice_new0 (ParseData);
64 parse_data->state = e_table_state_new (specification);
65 parse_data->column_info = g_variant_builder_new (type);
66
67 return parse_data;
68 }
69
70 static void
parse_data_free(ParseData * parse_data)71 parse_data_free (ParseData *parse_data)
72 {
73 g_object_unref (parse_data->state);
74 g_variant_builder_unref (parse_data->column_info);
75 g_slice_free (ParseData, parse_data);
76 }
77
78 static void
table_state_parser_start_column(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,GVariantBuilder * column_info,GError ** error)79 table_state_parser_start_column (GMarkupParseContext *context,
80 const gchar *element_name,
81 const gchar **attribute_names,
82 const gchar **attribute_values,
83 GVariantBuilder *column_info,
84 GError **error)
85 {
86 const gchar *index_str;
87 const gchar *expansion_str;
88 gboolean success;
89
90 success = g_markup_collect_attributes (
91 element_name,
92 attribute_names,
93 attribute_values,
94 error,
95
96 G_MARKUP_COLLECT_STRING,
97 "source",
98 &index_str,
99
100 G_MARKUP_COLLECT_STRING |
101 G_MARKUP_COLLECT_OPTIONAL,
102 "expansion",
103 &expansion_str,
104
105 G_MARKUP_COLLECT_INVALID);
106
107 if (success) {
108 gint64 index;
109 gdouble expansion = 1.0;
110
111 g_return_if_fail (index_str != NULL);
112 index = g_ascii_strtoll (index_str, NULL, 10);
113
114 if (expansion_str != NULL)
115 expansion = g_ascii_strtod (expansion_str, NULL);
116
117 g_variant_builder_add (
118 column_info, "(xd)", index, expansion);
119 }
120 }
121
122 static void
table_state_parser_start_element(GMarkupParseContext * context,const gchar * element_name,const gchar ** attribute_names,const gchar ** attribute_values,gpointer user_data,GError ** error)123 table_state_parser_start_element (GMarkupParseContext *context,
124 const gchar *element_name,
125 const gchar **attribute_names,
126 const gchar **attribute_values,
127 gpointer user_data,
128 GError **error)
129 {
130 ParseData *parse_data = user_data;
131 ETableSpecification *specification;
132
133 specification = e_table_state_ref_specification (parse_data->state);
134
135 if (g_str_equal (element_name, "column"))
136 table_state_parser_start_column (
137 context,
138 element_name,
139 attribute_names,
140 attribute_values,
141 parse_data->column_info,
142 error);
143
144 if (g_str_equal (element_name, "grouping"))
145 e_table_sort_info_parse_context_push (
146 context, specification);
147
148 g_object_unref (specification);
149 }
150
151 static void
table_state_parser_end_element(GMarkupParseContext * context,const gchar * element_name,gpointer user_data,GError ** error)152 table_state_parser_end_element (GMarkupParseContext *context,
153 const gchar *element_name,
154 gpointer user_data,
155 GError **error)
156 {
157 ParseData *parse_data = user_data;
158
159 if (g_str_equal (element_name, "grouping")) {
160 ETableSortInfo *sort_info;
161
162 sort_info = e_table_sort_info_parse_context_pop (context);
163 g_return_if_fail (E_IS_TABLE_SORT_INFO (sort_info));
164
165 g_clear_object (&parse_data->state->sort_info);
166 parse_data->state->sort_info = g_object_ref (sort_info);
167
168 g_object_unref (sort_info);
169 }
170 }
171
172 static void
table_state_parser_error(GMarkupParseContext * context,GError * error,gpointer user_data)173 table_state_parser_error (GMarkupParseContext *context,
174 GError *error,
175 gpointer user_data)
176 {
177 parse_data_free ((ParseData *) user_data);
178 }
179
180 static const GMarkupParser table_state_parser = {
181 table_state_parser_start_element,
182 table_state_parser_end_element,
183 NULL,
184 NULL,
185 table_state_parser_error
186 };
187
188 static void
table_state_set_specification(ETableState * state,ETableSpecification * specification)189 table_state_set_specification (ETableState *state,
190 ETableSpecification *specification)
191 {
192 g_return_if_fail (E_IS_TABLE_SPECIFICATION (specification));
193
194 g_weak_ref_set (&state->priv->specification, specification);
195 }
196
197 static void
table_state_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)198 table_state_set_property (GObject *object,
199 guint property_id,
200 const GValue *value,
201 GParamSpec *pspec)
202 {
203 switch (property_id) {
204 case PROP_SPECIFICATION:
205 table_state_set_specification (
206 E_TABLE_STATE (object),
207 g_value_get_object (value));
208 return;
209 }
210
211 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
212 }
213
214 static void
table_state_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)215 table_state_get_property (GObject *object,
216 guint property_id,
217 GValue *value,
218 GParamSpec *pspec)
219 {
220 switch (property_id) {
221 case PROP_SPECIFICATION:
222 g_value_take_object (
223 value,
224 e_table_state_ref_specification (
225 E_TABLE_STATE (object)));
226 return;
227 }
228
229 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
230 }
231
232 static void
table_state_dispose(GObject * object)233 table_state_dispose (GObject *object)
234 {
235 ETableState *state = E_TABLE_STATE (object);
236 gint ii;
237
238 for (ii = 0; ii < state->col_count; ii++)
239 g_clear_object (&state->column_specs[ii]);
240 state->col_count = 0;
241
242 g_clear_object (&state->sort_info);
243 g_weak_ref_set (&state->priv->specification, NULL);
244
245 /* Chain up to parent's dispose() method. */
246 G_OBJECT_CLASS (e_table_state_parent_class)->dispose (object);
247 }
248
249 static void
table_state_finalize(GObject * object)250 table_state_finalize (GObject *object)
251 {
252 ETableState *state = E_TABLE_STATE (object);
253
254 g_free (state->column_specs);
255 g_free (state->expansions);
256
257 /* Chain up to parent's finalize() method. */
258 G_OBJECT_CLASS (e_table_state_parent_class)->finalize (object);
259 }
260
261 static void
table_state_constructed(GObject * object)262 table_state_constructed (GObject *object)
263 {
264 ETableState *state;
265 ETableSpecification *specification;
266
267 state = E_TABLE_STATE (object);
268
269 specification = e_table_state_ref_specification (state);
270 state->sort_info = e_table_sort_info_new (specification);
271 g_object_unref (specification);
272
273 /* Chain up to parent's constructed() method. */
274 G_OBJECT_CLASS (e_table_state_parent_class)->constructed (object);
275 }
276
277 static void
e_table_state_class_init(ETableStateClass * class)278 e_table_state_class_init (ETableStateClass *class)
279 {
280 GObjectClass *object_class;
281
282 g_type_class_add_private (class, sizeof (ETableStatePrivate));
283
284 object_class = G_OBJECT_CLASS (class);
285 object_class->set_property = table_state_set_property;
286 object_class->get_property = table_state_get_property;
287 object_class->dispose = table_state_dispose;
288 object_class->finalize = table_state_finalize;
289 object_class->constructed = table_state_constructed;
290
291 g_object_class_install_property (
292 object_class,
293 PROP_SPECIFICATION,
294 g_param_spec_object (
295 "specification",
296 "Table Specification",
297 "Specification for the table state",
298 E_TYPE_TABLE_SPECIFICATION,
299 G_PARAM_READWRITE |
300 G_PARAM_CONSTRUCT_ONLY |
301 G_PARAM_STATIC_STRINGS));
302 }
303
304 static void
e_table_state_init(ETableState * state)305 e_table_state_init (ETableState *state)
306 {
307 state->priv = E_TABLE_STATE_GET_PRIVATE (state);
308 }
309
310 ETableState *
e_table_state_new(ETableSpecification * specification)311 e_table_state_new (ETableSpecification *specification)
312 {
313 g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
314
315 return g_object_new (
316 E_TYPE_TABLE_STATE,
317 "specification", specification, NULL);
318 }
319
320 ETableState *
e_table_state_vanilla(ETableSpecification * specification)321 e_table_state_vanilla (ETableSpecification *specification)
322 {
323 ETableState *state;
324 GPtrArray *columns;
325 GString *str;
326 guint ii;
327
328 g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
329
330 columns = e_table_specification_ref_columns (specification);
331
332 str = g_string_new ("<ETableState>\n");
333 for (ii = 0; ii < columns->len; ii++)
334 g_string_append_printf (str, " <column source=\"%d\"/>\n", ii);
335 g_string_append (str, " <grouping></grouping>\n");
336 g_string_append (str, "</ETableState>\n");
337
338 g_ptr_array_unref (columns);
339
340 state = e_table_state_new (specification);
341 e_table_state_load_from_string (state, str->str);
342
343 g_string_free (str, TRUE);
344
345 return state;
346 }
347
348 /**
349 * e_table_state_parse_context_push:
350 * @context: a #GMarkupParseContext
351 * @specification: an #ETableSpecification
352 *
353 * Creates a new #ETableState from a segment of XML data being fed to
354 * @context. Call this function for the appropriate opening tag from the
355 * <structfield>start_element</structfield> callback of a #GMarkupParser,
356 * then call e_table_state_parse_context_pop() for the corresponding
357 * closing tag from the <structfield>end_element</structfield> callback.
358 **/
359 void
e_table_state_parse_context_push(GMarkupParseContext * context,ETableSpecification * specification)360 e_table_state_parse_context_push (GMarkupParseContext *context,
361 ETableSpecification *specification)
362 {
363 g_return_if_fail (context != NULL);
364 g_return_if_fail (E_IS_TABLE_SPECIFICATION (specification));
365
366 g_markup_parse_context_push (
367 context, &table_state_parser,
368 parse_data_new (specification));
369 }
370
371 /**
372 * e_table_state_parse_context_pop:
373 * @context: a #GMarkupParseContext
374 *
375 * Creates a new #ETableState from a segment of XML data being fed to
376 * @context. Call e_table_state_parse_context_push() for the appropriate
377 * opening tag from the <structfield>start_element</structfield> callback of
378 * a #GMarkupParser, then call this function for the corresponding closing
379 * tag from the <structfield>end_element</structfield> callback.
380 *
381 * Unreference the newly-created #ETableState with g_object_unref() when
382 * finished with it.
383 *
384 * Returns: an #ETableState
385 **/
386 ETableState *
e_table_state_parse_context_pop(GMarkupParseContext * context)387 e_table_state_parse_context_pop (GMarkupParseContext *context)
388 {
389 ETableSpecification *specification;
390 ParseData *parse_data;
391 GPtrArray *columns;
392 ETableState *state;
393 GVariant *variant;
394 GVariantIter iter;
395 gint64 index;
396 gdouble expansion;
397 gsize length, ii = 0;
398
399 g_return_val_if_fail (context != NULL, NULL);
400
401 parse_data = g_markup_parse_context_pop (context);
402 g_return_val_if_fail (parse_data != NULL, NULL);
403
404 state = g_object_ref (parse_data->state);
405
406 specification = e_table_state_ref_specification (state);
407 columns = e_table_specification_ref_columns (specification);
408
409 variant = g_variant_builder_end (parse_data->column_info);
410 length = g_variant_iter_init (&iter, variant);
411
412 state->column_specs = g_new0 (ETableColumnSpecification *, length);
413 state->expansions = g_new0 (gdouble, length);
414 state->col_count = length;
415
416 while (g_variant_iter_next (&iter, "(xd)", &index, &expansion)) {
417 if (index < columns->len) {
418 ETableColumnSpecification *column_spec;
419
420 column_spec = g_ptr_array_index (columns, index);
421 state->column_specs[ii] = g_object_ref (column_spec);
422 state->expansions[ii] = expansion;
423
424 ii++;
425 }
426 }
427
428 g_variant_unref (variant);
429
430 g_object_unref (specification);
431 g_ptr_array_unref (columns);
432
433 parse_data_free (parse_data);
434
435 return state;
436 }
437
438 /**
439 * e_table_state_ref_specification:
440 * @state: an #ETableState
441 *
442 * Returns the #ETableSpecification passed to e_table_state_new().
443 *
444 * The returned #ETableSpecification is referenced for thread-safety and must
445 * be unreferenced with g_object_unref() when finished with it.
446 *
447 * Returns: an #ETableSpecification
448 **/
449 ETableSpecification *
e_table_state_ref_specification(ETableState * state)450 e_table_state_ref_specification (ETableState *state)
451 {
452 g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
453
454 return g_weak_ref_get (&state->priv->specification);
455 }
456
457 gboolean
e_table_state_load_from_file(ETableState * state,const gchar * filename)458 e_table_state_load_from_file (ETableState *state,
459 const gchar *filename)
460 {
461 xmlDoc *doc;
462 gboolean success = FALSE;
463
464 g_return_val_if_fail (E_IS_TABLE_STATE (state), FALSE);
465 g_return_val_if_fail (filename != NULL, FALSE);
466
467 doc = e_xml_parse_file (filename);
468 if (doc != NULL) {
469 xmlNode *node = xmlDocGetRootElement (doc);
470 e_table_state_load_from_node (state, node);
471 xmlFreeDoc (doc);
472 success = TRUE;
473 }
474
475 return success;
476 }
477
478 void
e_table_state_load_from_string(ETableState * state,const gchar * xml)479 e_table_state_load_from_string (ETableState *state,
480 const gchar *xml)
481 {
482 xmlDoc *doc;
483
484 g_return_if_fail (E_IS_TABLE_STATE (state));
485 g_return_if_fail (xml != NULL);
486
487 doc = xmlParseMemory ((gchar *) xml, strlen (xml));
488 if (doc != NULL) {
489 xmlNode *node = xmlDocGetRootElement (doc);
490 e_table_state_load_from_node (state, node);
491 xmlFreeDoc (doc);
492 }
493 }
494
495 typedef struct {
496 gint column;
497 gdouble expansion;
498 } int_and_double;
499
500 void
e_table_state_load_from_node(ETableState * state,const xmlNode * node)501 e_table_state_load_from_node (ETableState *state,
502 const xmlNode *node)
503 {
504 ETableSpecification *specification;
505 xmlNode *children;
506 GList *list = NULL, *iterator;
507 GPtrArray *columns;
508 gdouble state_version;
509 gint i;
510 gboolean can_group = TRUE;
511
512 g_return_if_fail (E_IS_TABLE_STATE (state));
513 g_return_if_fail (node != NULL);
514
515 specification = e_table_state_ref_specification (state);
516 columns = e_table_specification_ref_columns (specification);
517
518 state_version = e_xml_get_double_prop_by_name_with_default (
519 node, (const guchar *)"state-version", STATE_VERSION);
520
521 if (state->sort_info) {
522 can_group = e_table_sort_info_get_can_group (state->sort_info);
523 g_object_unref (state->sort_info);
524 }
525
526 state->sort_info = NULL;
527 children = node->xmlChildrenNode;
528 for (; children; children = children->next) {
529 if (!strcmp ((gchar *) children->name, "column")) {
530 int_and_double *column_info = g_new (int_and_double, 1);
531 gint column_source;
532
533 column_source = e_xml_get_integer_prop_by_name (children, (const guchar *) "source");
534 if (column_source < 0 || column_source >= columns->len)
535 continue;
536
537 column_info->column = column_source;
538 column_info->expansion =
539 e_xml_get_double_prop_by_name_with_default (
540 children, (const guchar *)"expansion", 1);
541
542 list = g_list_append (list, column_info);
543 } else if (state->sort_info == NULL &&
544 !strcmp ((gchar *) children->name, "grouping")) {
545 state->sort_info =
546 e_table_sort_info_new (specification);
547 e_table_sort_info_load_from_node (
548 state->sort_info, children, state_version);
549 }
550 }
551
552 for (i = 0; i < state->col_count; i++)
553 g_clear_object (&state->column_specs[i]);
554 g_free (state->column_specs);
555 g_free (state->expansions);
556
557 state->col_count = g_list_length (list);
558 state->column_specs = g_new (
559 ETableColumnSpecification *, state->col_count);
560 state->expansions = g_new (double, state->col_count);
561
562 if (state->sort_info == NULL)
563 state->sort_info = e_table_sort_info_new (specification);
564 e_table_sort_info_set_can_group (state->sort_info, can_group);
565
566 for (iterator = list, i = 0; iterator; i++) {
567 ETableColumnSpecification *column_spec;
568 int_and_double *column_info = iterator->data;
569
570 column_spec = columns->pdata[column_info->column];
571
572 state->column_specs[i] = g_object_ref (column_spec);
573 state->expansions[i] = column_info->expansion;
574
575 g_free (column_info);
576
577 iterator = g_list_next (iterator);
578 }
579 g_list_free (list);
580
581 g_object_unref (specification);
582 g_ptr_array_unref (columns);
583 }
584
585 void
e_table_state_save_to_file(ETableState * state,const gchar * filename)586 e_table_state_save_to_file (ETableState *state,
587 const gchar *filename)
588 {
589 xmlDoc *doc;
590 xmlNode *node;
591
592 doc = xmlNewDoc ((const guchar *)"1.0");
593
594 node = e_table_state_save_to_node (state, NULL);
595 xmlDocSetRootElement (doc, node);
596
597 e_xml_save_file (filename, doc);
598
599 xmlFreeDoc (doc);
600 }
601
602 gchar *
e_table_state_save_to_string(ETableState * state)603 e_table_state_save_to_string (ETableState *state)
604 {
605 gchar *ret_val;
606 xmlChar *string;
607 gint length;
608 xmlDoc *doc;
609 xmlNode *node;
610
611 g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
612
613 doc = xmlNewDoc ((const guchar *)"1.0");
614
615 node = e_table_state_save_to_node (state, NULL);
616 xmlDocSetRootElement (doc, node);
617
618 xmlDocDumpMemory (doc, &string, &length);
619 ret_val = g_strdup ((gchar *) string);
620 xmlFree (string);
621
622 xmlFreeDoc (doc);
623
624 return ret_val;
625 }
626
627 xmlNode *
e_table_state_save_to_node(ETableState * state,xmlNode * parent)628 e_table_state_save_to_node (ETableState *state,
629 xmlNode *parent)
630 {
631 ETableSpecification *specification;
632 xmlNode *node;
633 gint ii;
634
635 g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
636
637 specification = e_table_state_ref_specification (state);
638
639 if (parent)
640 node = xmlNewChild (
641 parent, NULL, (const guchar *) "ETableState", NULL);
642 else
643 node = xmlNewNode (NULL, (const guchar *) "ETableState");
644
645 e_xml_set_double_prop_by_name (
646 node, (const guchar *) "state-version", STATE_VERSION);
647
648 for (ii = 0; ii < state->col_count; ii++) {
649 xmlNode *new_node;
650 gint index;
651
652 index = e_table_specification_get_column_index (
653 specification, state->column_specs[ii]);
654
655 if (index < 0) {
656 g_warn_if_reached ();
657 continue;
658 }
659
660 new_node = xmlNewChild (
661 node, NULL, (const guchar *) "column", NULL);
662 e_xml_set_integer_prop_by_name (
663 new_node, (const guchar *) "source", index);
664 if (state->expansions[ii] >= -1)
665 e_xml_set_double_prop_by_name (
666 new_node, (const guchar *)
667 "expansion", state->expansions[ii]);
668 }
669
670 e_table_sort_info_save_to_node (state->sort_info, node);
671
672 g_object_unref (specification);
673
674 return node;
675 }
676
677 /**
678 * e_table_state_duplicate:
679 * @state: an #ETableState
680 *
681 * Creates a new #ETableState cloned from @state.
682 *
683 * Returns: a new #ETableState
684 */
685 ETableState *
e_table_state_duplicate(ETableState * state)686 e_table_state_duplicate (ETableState *state)
687 {
688 ETableState *new_state;
689 ETableSpecification *specification;
690 gboolean can_group;
691 gchar *copy;
692
693 g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
694
695 specification = e_table_state_ref_specification (state);
696 new_state = e_table_state_new (specification);
697 g_object_unref (specification);
698
699 copy = e_table_state_save_to_string (state);
700 e_table_state_load_from_string (new_state, copy);
701 g_free (copy);
702
703 can_group = e_table_sort_info_get_can_group (state->sort_info);
704 e_table_sort_info_set_can_group (new_state->sort_info, can_group);
705
706 return new_state;
707 }
708