705 lines
18 KiB
C
705 lines
18 KiB
C
/*
|
|
* e-table-state.c
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) version 3.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with the program; if not, see <http://www.gnu.org/licenses/>
|
|
*
|
|
*/
|
|
|
|
#include "e-table-state.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <libxml/parser.h>
|
|
#include <libxml/xmlmemory.h>
|
|
|
|
#include <libedataserver/libedataserver.h>
|
|
|
|
#include "e-table-specification.h"
|
|
#include "e-xml-utils.h"
|
|
|
|
#define E_TABLE_STATE_GET_PRIVATE(obj) \
|
|
(G_TYPE_INSTANCE_GET_PRIVATE \
|
|
((obj), E_TYPE_TABLE_STATE, ETableStatePrivate))
|
|
|
|
#define STATE_VERSION 0.1
|
|
|
|
typedef struct _ParseData ParseData;
|
|
|
|
struct _ETableStatePrivate {
|
|
GWeakRef specification;
|
|
};
|
|
|
|
enum {
|
|
PROP_0,
|
|
PROP_SPECIFICATION
|
|
};
|
|
|
|
struct _ParseData {
|
|
ETableState *state;
|
|
GVariantBuilder *column_info;
|
|
};
|
|
|
|
G_DEFINE_TYPE (ETableState, e_table_state, G_TYPE_OBJECT)
|
|
|
|
static ParseData *
|
|
parse_data_new (ETableSpecification *specification)
|
|
{
|
|
ParseData *parse_data;
|
|
const GVariantType *type;
|
|
|
|
type = G_VARIANT_TYPE ("a(xd)");
|
|
|
|
parse_data = g_slice_new0 (ParseData);
|
|
parse_data->state = e_table_state_new (specification);
|
|
parse_data->column_info = g_variant_builder_new (type);
|
|
|
|
return parse_data;
|
|
}
|
|
|
|
static void
|
|
parse_data_free (ParseData *parse_data)
|
|
{
|
|
g_object_unref (parse_data->state);
|
|
g_variant_builder_unref (parse_data->column_info);
|
|
g_slice_free (ParseData, parse_data);
|
|
}
|
|
|
|
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)
|
|
{
|
|
const gchar *index_str;
|
|
const gchar *expansion_str;
|
|
gboolean success;
|
|
|
|
success = g_markup_collect_attributes (
|
|
element_name,
|
|
attribute_names,
|
|
attribute_values,
|
|
error,
|
|
|
|
G_MARKUP_COLLECT_STRING,
|
|
"source",
|
|
&index_str,
|
|
|
|
G_MARKUP_COLLECT_STRING |
|
|
G_MARKUP_COLLECT_OPTIONAL,
|
|
"expansion",
|
|
&expansion_str,
|
|
|
|
G_MARKUP_COLLECT_INVALID);
|
|
|
|
if (success) {
|
|
gint64 index;
|
|
gdouble expansion = 1.0;
|
|
|
|
g_return_if_fail (index_str != NULL);
|
|
index = g_ascii_strtoll (index_str, NULL, 10);
|
|
|
|
if (expansion_str != NULL)
|
|
expansion = g_ascii_strtod (expansion_str, NULL);
|
|
|
|
g_variant_builder_add (
|
|
column_info, "(xd)", index, expansion);
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
ParseData *parse_data = user_data;
|
|
ETableSpecification *specification;
|
|
|
|
specification = e_table_state_ref_specification (parse_data->state);
|
|
|
|
if (g_str_equal (element_name, "column"))
|
|
table_state_parser_start_column (
|
|
context,
|
|
element_name,
|
|
attribute_names,
|
|
attribute_values,
|
|
parse_data->column_info,
|
|
error);
|
|
|
|
if (g_str_equal (element_name, "grouping"))
|
|
e_table_sort_info_parse_context_push (
|
|
context, specification);
|
|
|
|
g_object_unref (specification);
|
|
}
|
|
|
|
static void
|
|
table_state_parser_end_element (GMarkupParseContext *context,
|
|
const gchar *element_name,
|
|
gpointer user_data,
|
|
GError **error)
|
|
{
|
|
ParseData *parse_data = user_data;
|
|
|
|
if (g_str_equal (element_name, "grouping")) {
|
|
ETableSortInfo *sort_info;
|
|
|
|
sort_info = e_table_sort_info_parse_context_pop (context);
|
|
g_return_if_fail (E_IS_TABLE_SORT_INFO (sort_info));
|
|
|
|
g_clear_object (&parse_data->state->sort_info);
|
|
parse_data->state->sort_info = g_object_ref (sort_info);
|
|
|
|
g_object_unref (sort_info);
|
|
}
|
|
}
|
|
|
|
static void
|
|
table_state_parser_error (GMarkupParseContext *context,
|
|
GError *error,
|
|
gpointer user_data)
|
|
{
|
|
parse_data_free ((ParseData *) user_data);
|
|
}
|
|
|
|
static const GMarkupParser table_state_parser = {
|
|
table_state_parser_start_element,
|
|
table_state_parser_end_element,
|
|
NULL,
|
|
NULL,
|
|
table_state_parser_error
|
|
};
|
|
|
|
static void
|
|
table_state_set_specification (ETableState *state,
|
|
ETableSpecification *specification)
|
|
{
|
|
g_return_if_fail (E_IS_TABLE_SPECIFICATION (specification));
|
|
|
|
g_weak_ref_set (&state->priv->specification, specification);
|
|
}
|
|
|
|
static void
|
|
table_state_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id) {
|
|
case PROP_SPECIFICATION:
|
|
table_state_set_specification (
|
|
E_TABLE_STATE (object),
|
|
g_value_get_object (value));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
table_state_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
switch (property_id) {
|
|
case PROP_SPECIFICATION:
|
|
g_value_take_object (
|
|
value,
|
|
e_table_state_ref_specification (
|
|
E_TABLE_STATE (object)));
|
|
return;
|
|
}
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
|
|
static void
|
|
table_state_dispose (GObject *object)
|
|
{
|
|
ETableState *state = E_TABLE_STATE (object);
|
|
gint ii;
|
|
|
|
for (ii = 0; ii < state->col_count; ii++)
|
|
g_clear_object (&state->column_specs[ii]);
|
|
state->col_count = 0;
|
|
|
|
g_clear_object (&state->sort_info);
|
|
g_weak_ref_set (&state->priv->specification, NULL);
|
|
|
|
/* Chain up to parent's dispose() method. */
|
|
G_OBJECT_CLASS (e_table_state_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
table_state_finalize (GObject *object)
|
|
{
|
|
ETableState *state = E_TABLE_STATE (object);
|
|
|
|
g_free (state->column_specs);
|
|
g_free (state->expansions);
|
|
|
|
/* Chain up to parent's finalize() method. */
|
|
G_OBJECT_CLASS (e_table_state_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
table_state_constructed (GObject *object)
|
|
{
|
|
ETableState *state;
|
|
ETableSpecification *specification;
|
|
|
|
state = E_TABLE_STATE (object);
|
|
|
|
specification = e_table_state_ref_specification (state);
|
|
state->sort_info = e_table_sort_info_new (specification);
|
|
g_object_unref (specification);
|
|
|
|
/* Chain up to parent's constructed() method. */
|
|
G_OBJECT_CLASS (e_table_state_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
e_table_state_class_init (ETableStateClass *class)
|
|
{
|
|
GObjectClass *object_class;
|
|
|
|
g_type_class_add_private (class, sizeof (ETableStatePrivate));
|
|
|
|
object_class = G_OBJECT_CLASS (class);
|
|
object_class->set_property = table_state_set_property;
|
|
object_class->get_property = table_state_get_property;
|
|
object_class->dispose = table_state_dispose;
|
|
object_class->finalize = table_state_finalize;
|
|
object_class->constructed = table_state_constructed;
|
|
|
|
g_object_class_install_property (
|
|
object_class,
|
|
PROP_SPECIFICATION,
|
|
g_param_spec_object (
|
|
"specification",
|
|
"Table Specification",
|
|
"Specification for the table state",
|
|
E_TYPE_TABLE_SPECIFICATION,
|
|
G_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT_ONLY |
|
|
G_PARAM_STATIC_STRINGS));
|
|
}
|
|
|
|
static void
|
|
e_table_state_init (ETableState *state)
|
|
{
|
|
state->priv = E_TABLE_STATE_GET_PRIVATE (state);
|
|
}
|
|
|
|
ETableState *
|
|
e_table_state_new (ETableSpecification *specification)
|
|
{
|
|
g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
|
|
|
|
return g_object_new (
|
|
E_TYPE_TABLE_STATE,
|
|
"specification", specification, NULL);
|
|
}
|
|
|
|
ETableState *
|
|
e_table_state_vanilla (ETableSpecification *specification)
|
|
{
|
|
ETableState *state;
|
|
GPtrArray *columns;
|
|
GString *str;
|
|
guint ii;
|
|
|
|
g_return_val_if_fail (E_IS_TABLE_SPECIFICATION (specification), NULL);
|
|
|
|
columns = e_table_specification_ref_columns (specification);
|
|
|
|
str = g_string_new ("<ETableState>\n");
|
|
for (ii = 0; ii < columns->len; ii++)
|
|
g_string_append_printf (str, " <column source=\"%d\"/>\n", ii);
|
|
g_string_append (str, " <grouping></grouping>\n");
|
|
g_string_append (str, "</ETableState>\n");
|
|
|
|
g_ptr_array_unref (columns);
|
|
|
|
state = e_table_state_new (specification);
|
|
e_table_state_load_from_string (state, str->str);
|
|
|
|
g_string_free (str, TRUE);
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* e_table_state_parse_context_push:
|
|
* @context: a #GMarkupParseContext
|
|
* @specification: an #ETableSpecification
|
|
*
|
|
* Creates a new #ETableState from a segment of XML data being fed to
|
|
* @context. Call this function for the appropriate opening tag from the
|
|
* <structfield>start_element</structfield> callback of a #GMarkupParser,
|
|
* then call e_table_state_parse_context_pop() for the corresponding
|
|
* closing tag from the <structfield>end_element</structfield> callback.
|
|
**/
|
|
void
|
|
e_table_state_parse_context_push (GMarkupParseContext *context,
|
|
ETableSpecification *specification)
|
|
{
|
|
g_return_if_fail (context != NULL);
|
|
g_return_if_fail (E_IS_TABLE_SPECIFICATION (specification));
|
|
|
|
g_markup_parse_context_push (
|
|
context, &table_state_parser,
|
|
parse_data_new (specification));
|
|
}
|
|
|
|
/**
|
|
* e_table_state_parse_context_pop:
|
|
* @context: a #GMarkupParseContext
|
|
*
|
|
* Creates a new #ETableState from a segment of XML data being fed to
|
|
* @context. Call e_table_state_parse_context_push() for the appropriate
|
|
* opening tag from the <structfield>start_element</structfield> callback of
|
|
* a #GMarkupParser, then call this function for the corresponding closing
|
|
* tag from the <structfield>end_element</structfield> callback.
|
|
*
|
|
* Unreference the newly-created #ETableState with g_object_unref() when
|
|
* finished with it.
|
|
*
|
|
* Returns: an #ETableState
|
|
**/
|
|
ETableState *
|
|
e_table_state_parse_context_pop (GMarkupParseContext *context)
|
|
{
|
|
ETableSpecification *specification;
|
|
ParseData *parse_data;
|
|
GPtrArray *columns;
|
|
ETableState *state;
|
|
GVariant *variant;
|
|
GVariantIter iter;
|
|
gint64 index;
|
|
gdouble expansion;
|
|
gsize length, ii = 0;
|
|
|
|
g_return_val_if_fail (context != NULL, NULL);
|
|
|
|
parse_data = g_markup_parse_context_pop (context);
|
|
g_return_val_if_fail (parse_data != NULL, NULL);
|
|
|
|
state = g_object_ref (parse_data->state);
|
|
|
|
specification = e_table_state_ref_specification (state);
|
|
columns = e_table_specification_ref_columns (specification);
|
|
|
|
variant = g_variant_builder_end (parse_data->column_info);
|
|
length = g_variant_iter_init (&iter, variant);
|
|
|
|
state->column_specs = g_new0 (ETableColumnSpecification *, length);
|
|
state->expansions = g_new0 (gdouble, length);
|
|
state->col_count = length;
|
|
|
|
while (g_variant_iter_next (&iter, "(xd)", &index, &expansion)) {
|
|
if (index < columns->len) {
|
|
ETableColumnSpecification *column_spec;
|
|
|
|
column_spec = g_ptr_array_index (columns, index);
|
|
state->column_specs[ii] = g_object_ref (column_spec);
|
|
state->expansions[ii] = expansion;
|
|
|
|
ii++;
|
|
}
|
|
}
|
|
|
|
g_variant_unref (variant);
|
|
|
|
g_object_unref (specification);
|
|
g_ptr_array_unref (columns);
|
|
|
|
parse_data_free (parse_data);
|
|
|
|
return state;
|
|
}
|
|
|
|
/**
|
|
* e_table_state_ref_specification:
|
|
* @state: an #ETableState
|
|
*
|
|
* Returns the #ETableSpecification passed to e_table_state_new().
|
|
*
|
|
* The returned #ETableSpecification is referenced for thread-safety and must
|
|
* be unreferenced with g_object_unref() when finished with it.
|
|
*
|
|
* Returns: an #ETableSpecification
|
|
**/
|
|
ETableSpecification *
|
|
e_table_state_ref_specification (ETableState *state)
|
|
{
|
|
g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
|
|
|
|
return g_weak_ref_get (&state->priv->specification);
|
|
}
|
|
|
|
gboolean
|
|
e_table_state_load_from_file (ETableState *state,
|
|
const gchar *filename)
|
|
{
|
|
xmlDoc *doc;
|
|
gboolean success = FALSE;
|
|
|
|
g_return_val_if_fail (E_IS_TABLE_STATE (state), FALSE);
|
|
g_return_val_if_fail (filename != NULL, FALSE);
|
|
|
|
doc = e_xml_parse_file (filename);
|
|
if (doc != NULL) {
|
|
xmlNode *node = xmlDocGetRootElement (doc);
|
|
e_table_state_load_from_node (state, node);
|
|
xmlFreeDoc (doc);
|
|
success = TRUE;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
void
|
|
e_table_state_load_from_string (ETableState *state,
|
|
const gchar *xml)
|
|
{
|
|
xmlDoc *doc;
|
|
|
|
g_return_if_fail (E_IS_TABLE_STATE (state));
|
|
g_return_if_fail (xml != NULL);
|
|
|
|
doc = xmlParseMemory ((gchar *) xml, strlen (xml));
|
|
if (doc != NULL) {
|
|
xmlNode *node = xmlDocGetRootElement (doc);
|
|
e_table_state_load_from_node (state, node);
|
|
xmlFreeDoc (doc);
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
gint column;
|
|
gdouble expansion;
|
|
} int_and_double;
|
|
|
|
void
|
|
e_table_state_load_from_node (ETableState *state,
|
|
const xmlNode *node)
|
|
{
|
|
ETableSpecification *specification;
|
|
xmlNode *children;
|
|
GList *list = NULL, *iterator;
|
|
GPtrArray *columns;
|
|
gdouble state_version;
|
|
gint i;
|
|
gboolean can_group = TRUE;
|
|
|
|
g_return_if_fail (E_IS_TABLE_STATE (state));
|
|
g_return_if_fail (node != NULL);
|
|
|
|
specification = e_table_state_ref_specification (state);
|
|
columns = e_table_specification_ref_columns (specification);
|
|
|
|
state_version = e_xml_get_double_prop_by_name_with_default (
|
|
node, (const guchar *)"state-version", STATE_VERSION);
|
|
|
|
if (state->sort_info) {
|
|
can_group = e_table_sort_info_get_can_group (state->sort_info);
|
|
g_object_unref (state->sort_info);
|
|
}
|
|
|
|
state->sort_info = NULL;
|
|
children = node->xmlChildrenNode;
|
|
for (; children; children = children->next) {
|
|
if (!strcmp ((gchar *) children->name, "column")) {
|
|
int_and_double *column_info = g_new (int_and_double, 1);
|
|
|
|
column_info->column = e_xml_get_integer_prop_by_name (
|
|
children, (const guchar *)"source");
|
|
column_info->expansion =
|
|
e_xml_get_double_prop_by_name_with_default (
|
|
children, (const guchar *)"expansion", 1);
|
|
|
|
list = g_list_append (list, column_info);
|
|
} else if (state->sort_info == NULL &&
|
|
!strcmp ((gchar *) children->name, "grouping")) {
|
|
state->sort_info =
|
|
e_table_sort_info_new (specification);
|
|
e_table_sort_info_load_from_node (
|
|
state->sort_info, children, state_version);
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < state->col_count; i++)
|
|
g_clear_object (&state->column_specs[i]);
|
|
g_free (state->column_specs);
|
|
g_free (state->expansions);
|
|
|
|
state->col_count = g_list_length (list);
|
|
state->column_specs = g_new (
|
|
ETableColumnSpecification *, state->col_count);
|
|
state->expansions = g_new (double, state->col_count);
|
|
|
|
if (state->sort_info == NULL)
|
|
state->sort_info = e_table_sort_info_new (specification);
|
|
e_table_sort_info_set_can_group (state->sort_info, can_group);
|
|
|
|
for (iterator = list, i = 0; iterator; i++) {
|
|
ETableColumnSpecification *column_spec;
|
|
int_and_double *column_info = iterator->data;
|
|
|
|
column_spec = columns->pdata[column_info->column];
|
|
|
|
state->column_specs[i] = g_object_ref (column_spec);
|
|
state->expansions[i] = column_info->expansion;
|
|
|
|
g_free (column_info);
|
|
|
|
iterator = g_list_next (iterator);
|
|
}
|
|
g_list_free (list);
|
|
|
|
g_object_unref (specification);
|
|
g_ptr_array_unref (columns);
|
|
}
|
|
|
|
void
|
|
e_table_state_save_to_file (ETableState *state,
|
|
const gchar *filename)
|
|
{
|
|
xmlDoc *doc;
|
|
xmlNode *node;
|
|
|
|
doc = xmlNewDoc ((const guchar *)"1.0");
|
|
|
|
node = e_table_state_save_to_node (state, NULL);
|
|
xmlDocSetRootElement (doc, node);
|
|
|
|
e_xml_save_file (filename, doc);
|
|
|
|
xmlFreeDoc (doc);
|
|
}
|
|
|
|
gchar *
|
|
e_table_state_save_to_string (ETableState *state)
|
|
{
|
|
gchar *ret_val;
|
|
xmlChar *string;
|
|
gint length;
|
|
xmlDoc *doc;
|
|
xmlNode *node;
|
|
|
|
g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
|
|
|
|
doc = xmlNewDoc ((const guchar *)"1.0");
|
|
|
|
node = e_table_state_save_to_node (state, NULL);
|
|
xmlDocSetRootElement (doc, node);
|
|
|
|
xmlDocDumpMemory (doc, &string, &length);
|
|
ret_val = g_strdup ((gchar *) string);
|
|
xmlFree (string);
|
|
|
|
xmlFreeDoc (doc);
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
xmlNode *
|
|
e_table_state_save_to_node (ETableState *state,
|
|
xmlNode *parent)
|
|
{
|
|
ETableSpecification *specification;
|
|
xmlNode *node;
|
|
gint ii;
|
|
|
|
g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
|
|
|
|
specification = e_table_state_ref_specification (state);
|
|
|
|
if (parent)
|
|
node = xmlNewChild (
|
|
parent, NULL, (const guchar *) "ETableState", NULL);
|
|
else
|
|
node = xmlNewNode (NULL, (const guchar *) "ETableState");
|
|
|
|
e_xml_set_double_prop_by_name (
|
|
node, (const guchar *) "state-version", STATE_VERSION);
|
|
|
|
for (ii = 0; ii < state->col_count; ii++) {
|
|
xmlNode *new_node;
|
|
gint index;
|
|
|
|
index = e_table_specification_get_column_index (
|
|
specification, state->column_specs[ii]);
|
|
|
|
if (index < 0) {
|
|
g_warn_if_reached ();
|
|
continue;
|
|
}
|
|
|
|
new_node = xmlNewChild (
|
|
node, NULL, (const guchar *) "column", NULL);
|
|
e_xml_set_integer_prop_by_name (
|
|
new_node, (const guchar *) "source", index);
|
|
if (state->expansions[ii] >= -1)
|
|
e_xml_set_double_prop_by_name (
|
|
new_node, (const guchar *)
|
|
"expansion", state->expansions[ii]);
|
|
}
|
|
|
|
e_table_sort_info_save_to_node (state->sort_info, node);
|
|
|
|
g_object_unref (specification);
|
|
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* e_table_state_duplicate:
|
|
* @state: an #ETableState
|
|
*
|
|
* Creates a new #ETableState cloned from @state.
|
|
*
|
|
* Returns: a new #ETableState
|
|
*/
|
|
ETableState *
|
|
e_table_state_duplicate (ETableState *state)
|
|
{
|
|
ETableState *new_state;
|
|
ETableSpecification *specification;
|
|
gboolean can_group;
|
|
gchar *copy;
|
|
|
|
g_return_val_if_fail (E_IS_TABLE_STATE (state), NULL);
|
|
|
|
specification = e_table_state_ref_specification (state);
|
|
new_state = e_table_state_new (specification);
|
|
g_object_unref (specification);
|
|
|
|
copy = e_table_state_save_to_string (state);
|
|
e_table_state_load_from_string (new_state, copy);
|
|
g_free (copy);
|
|
|
|
can_group = e_table_sort_info_get_can_group (state->sort_info);
|
|
e_table_sort_info_set_can_group (new_state->sort_info, can_group);
|
|
|
|
return new_state;
|
|
}
|