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