/* * Copyright (C) 2011 Daiki Ueno * Copyright (C) 2011 Red Hat, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** * SECTION:eek-xml-layout * @short_description: Layout engine which loads layout information from XML */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include #include #include "eek-xml-layout.h" #include "eek-keyboard.h" #include "eek-section.h" #include "eek-key.h" #include "eek-keysym.h" #include "eek-text.h" enum { PROP_0, PROP_SOURCE, PROP_LAST }; G_DEFINE_TYPE (EekXmlLayout, eek_xml_layout, EEK_TYPE_LAYOUT); #define EEK_XML_LAYOUT_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEK_TYPE_XML_LAYOUT, EekXmlLayoutPrivate)) struct _EekXmlLayoutPrivate { GInputStream *source; }; #define BUFSIZE 8192 struct _ParseCallbackData { GSList *element_stack; GString *text; EekLayout *layout; EekKeyboard *keyboard; EekSection *section; EekKey *key; gint num_columns; EekOrientation orientation; gdouble corner_radius; GSList *points; GSList *symbols; gchar *label; gchar *icon; guint keyval; gint groups, levels; EekOutline outline; gchar *oref; GHashTable *key_oref_hash; GHashTable *oref_outline_hash; }; typedef struct _ParseCallbackData ParseCallbackData; static const gchar *valid_path_list[] = { "keyboard", "bounds/keyboard", "section/keyboard", "outline/keyboard", "bounds/section/keyboard", "angle/section/keyboard", "row/section/keyboard", "columns/row/section/keyboard", "orientation/row/section/keyboard", "key/section/keyboard", "bounds/key/section/keyboard", "oref/key/section/keyboard", "symbols/key/section/keyboard", "groups/symbols/key/section/keyboard", "levels/symbols/key/section/keyboard", "text/symbols/key/section/keyboard", "keysym/symbols/key/section/keyboard", "symbol/symbols/key/section/keyboard", "invalid/symbols/key/section/keyboard", "index/key/section/keyboard", "point/outline/keyboard", "corner-radius/outline/keyboard", }; static gchar * strjoin_slist (GSList *slist, const gchar *delimiter) { GString *string = g_string_sized_new (64); if (slist == NULL) return g_strdup (""); else for (; slist; slist = g_slist_next (slist)) { g_string_append (string, slist->data); if (g_slist_next (slist)) g_string_append (string, delimiter); } return g_string_free (string, FALSE); } static void validate (const gchar *element_name, GSList *element_stack, GError **error) { gint i; gchar *element_path; GSList *head; head = g_slist_prepend (element_stack, (gchar *)element_name); element_path = strjoin_slist (head, "/"); g_slist_free1 (head); for (i = 0; i < G_N_ELEMENTS(valid_path_list); i++) { if (*valid_path_list[i] == '@') continue; if (g_strcmp0 (element_path, valid_path_list[i]) == 0) break; } if (i == G_N_ELEMENTS(valid_path_list)) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, "%s cannot appear under %s", element_name, element_path); g_free (element_path); return; } g_free (element_path); } static void start_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, GError **error) { ParseCallbackData *data = user_data; const gchar **names = attribute_names; const gchar **values = attribute_values; gint column = -1, row = -1, groups = -1, levels = -1; guint keyval = EEK_INVALID_KEYSYM; gchar *name = NULL, *label = NULL, *icon = NULL, *id = NULL, *version = NULL; validate (element_name, data->element_stack, error); if (error && *error) return; while (*names && *values) { if (g_strcmp0 (*names, "column") == 0) column = strtol (*values, NULL, 10); else if (g_strcmp0 (*names, "row") == 0) row = strtol (*values, NULL, 10); else if (g_strcmp0 (*names, "id") == 0) id = g_strdup (*values); else if (g_strcmp0 (*names, "name") == 0) name = g_strdup (*values); else if (g_strcmp0 (*names, "label") == 0) label = g_strdup (*values); else if (g_strcmp0 (*names, "icon") == 0) icon = g_strdup (*values); else if (g_strcmp0 (*names, "keyval") == 0) keyval = strtoul (*values, NULL, 10); else if (g_strcmp0 (*names, "version") == 0) version = g_strdup (*values); else if (g_strcmp0 (*names, "groups") == 0) groups = strtol (*values, NULL, 10); else if (g_strcmp0 (*names, "levels") == 0) levels = strtol (*values, NULL, 10); names++; values++; } if (g_strcmp0 (element_name, "keyboard") == 0) { data->keyboard = g_object_new (EEK_TYPE_KEYBOARD, "layout", data->layout, NULL); if (id) eek_element_set_name (EEK_ELEMENT(data->keyboard), id); goto out; } if (g_strcmp0 (element_name, "section") == 0) { data->section = eek_keyboard_create_section (data->keyboard); if (id) eek_element_set_name (EEK_ELEMENT(data->section), id); goto out; } if (g_strcmp0 (element_name, "key") == 0) { data->key = eek_section_create_key (data->section, column, row); if (id) { eek_element_set_name (EEK_ELEMENT(data->key), id); if (g_str_has_prefix (id, "keycode")) eek_key_set_keycode (data->key, strtoul (id + 7, NULL, 10)); } goto out; } if (g_strcmp0 (element_name, "symbols") == 0) { data->groups = groups; data->levels = levels; data->symbols = NULL; goto out; } if (g_strcmp0 (element_name, "symbol") == 0 || g_strcmp0 (element_name, "keysym") == 0) { data->label = g_strdup (label); data->icon = g_strdup (icon); if (g_strcmp0 (element_name, "keysym") == 0) data->keyval = keyval; } if (g_strcmp0 (element_name, "outline") == 0) { data->oref = g_strdup (id); goto out; } out: g_free (name); g_free (label); g_free (icon); g_free (id); g_free (version); data->element_stack = g_slist_prepend (data->element_stack, g_strdup (element_name)); data->text->len = 0; } static void end_element_callback (GMarkupParseContext *pcontext, const gchar *element_name, gpointer user_data, GError **error) { ParseCallbackData *data = user_data; GSList *head = data->element_stack; gchar *text, **strv; gint i; g_free (head->data); data->element_stack = g_slist_next (data->element_stack); g_slist_free1 (head); text = g_strndup (data->text->str, data->text->len); if (g_strcmp0 (element_name, "section") == 0) { data->section = NULL; goto out; } if (g_strcmp0 (element_name, "key") == 0) { data->key = NULL; goto out; } if (g_strcmp0 (element_name, "symbols") == 0) { gint num_symbols = data->groups * data->levels; EekSymbolMatrix *matrix = eek_symbol_matrix_new (data->groups, data->levels); head = data->symbols = g_slist_reverse (data->symbols); for (i = 0; i < num_symbols; i++) { if (head && head->data) { matrix->data[i] = head->data; head = g_slist_next (head); } else matrix->data[i] = NULL; } g_slist_free (data->symbols); data->symbols = NULL; eek_key_set_symbol_matrix (data->key, matrix); eek_symbol_matrix_free (matrix); goto out; } if (g_strcmp0 (element_name, "outline") == 0) { EekOutline *outline = g_slice_new (EekOutline); outline->corner_radius = data->corner_radius; data->corner_radius = 0.0; outline->num_points = g_slist_length (data->points); outline->points = g_slice_alloc0 (sizeof (EekPoint) * outline->num_points); for (head = data->points = g_slist_reverse (data->points), i = 0; head && i < outline->num_points; head = g_slist_next (head), i++) { memcpy (&outline->points[i], head->data, sizeof (EekPoint)); g_slice_free1 (sizeof (EekPoint), head->data); } g_slist_free (data->points); data->points = NULL; g_hash_table_insert (data->oref_outline_hash, g_strdup (data->oref), outline); g_free (data->oref); goto out; } if (g_strcmp0 (element_name, "corner-radius") == 0) { data->corner_radius = g_strtod (text, NULL); goto out; } if (g_strcmp0 (element_name, "point") == 0) { EekPoint *point; strv = g_strsplit (text, ",", -1); if (g_strv_length (strv) != 2) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, "invalid format for %s \"%s\"", element_name, text); goto out; } point = g_slice_new (EekPoint); point->x = g_strtod (strv[0], NULL); point->y = g_strtod (strv[1], NULL); g_strfreev (strv); data->points = g_slist_prepend (data->points, point); goto out; } if (g_strcmp0 (element_name, "bounds") == 0) { EekBounds bounds; strv = g_strsplit (text, ",", -1); if (g_strv_length (strv) != 4) { g_set_error (error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT, "invalid format for %s \"%s\"", element_name, text); goto out; } bounds.x = g_strtod (strv[0], NULL); bounds.y = g_strtod (strv[1], NULL); bounds.width = g_strtod (strv[2], NULL); bounds.height = g_strtod (strv[3], NULL); g_strfreev (strv); if (g_strcmp0 (data->element_stack->data, "keyboard") == 0) eek_element_set_bounds (EEK_ELEMENT(data->keyboard), &bounds); else if (g_strcmp0 (data->element_stack->data, "section") == 0) eek_element_set_bounds (EEK_ELEMENT(data->section), &bounds); else if (g_strcmp0 (data->element_stack->data, "key") == 0) eek_element_set_bounds (EEK_ELEMENT(data->key), &bounds); goto out; } if (g_strcmp0 (element_name, "angle") == 0) { eek_section_set_angle (data->section, strtol (text, NULL, 10)); goto out; } if (g_strcmp0 (element_name, "orientation") == 0) { data->orientation = strtol (text, NULL, 10); goto out; } if (g_strcmp0 (element_name, "columns") == 0) { data->num_columns = strtol (text, NULL, 10); goto out; } if (g_strcmp0 (element_name, "row") == 0) { eek_section_add_row (data->section, data->num_columns, data->orientation); data->num_columns = 0; data->orientation = EEK_ORIENTATION_HORIZONTAL; goto out; } if (g_strcmp0 (element_name, "symbol") == 0 || g_strcmp0 (element_name, "keysym") == 0 || g_strcmp0 (element_name, "text") == 0) { EekSymbol *symbol; if (g_strcmp0 (element_name, "keysym") == 0) { EekKeysym *keysym; if (data->keyval != EEK_INVALID_KEYSYM) keysym = eek_keysym_new (data->keyval); else keysym = eek_keysym_new_from_name (text); symbol = EEK_SYMBOL(keysym); } else if (g_strcmp0 (element_name, "text") == 0) { symbol = EEK_SYMBOL(eek_text_new (text)); } else { symbol = eek_symbol_new (text); eek_symbol_set_category (symbol, EEK_SYMBOL_CATEGORY_KEYNAME); } if (data->label) { eek_symbol_set_label (symbol, data->label); g_free (data->label); } if (data->icon) { eek_symbol_set_icon_name (symbol, data->icon); g_free (data->icon); } data->symbols = g_slist_prepend (data->symbols, symbol); goto out; } if (g_strcmp0 (element_name, "invalid") == 0) { data->symbols = g_slist_prepend (data->symbols, NULL); goto out; } if (g_strcmp0 (element_name, "oref") == 0) { g_hash_table_insert (data->key_oref_hash, data->key, g_strdup (text)); goto out; } out: g_free (text); } static void text_callback (GMarkupParseContext *pcontext, const gchar *text, gsize text_len, gpointer user_data, GError **error) { ParseCallbackData *data = user_data; g_string_append_len (data->text, text, text_len); } static const GMarkupParser parser = { start_element_callback, end_element_callback, text_callback, 0, 0 }; static void scale_bounds_callback (EekElement *element, gpointer user_data); static void scale_bounds (EekElement *element, gdouble scale) { EekBounds bounds; eek_element_get_bounds (element, &bounds); bounds.x *= scale; bounds.y *= scale; bounds.width *= scale; bounds.height *= scale; eek_element_set_bounds (element, &bounds); if (EEK_IS_CONTAINER(element)) eek_container_foreach_child (EEK_CONTAINER(element), scale_bounds_callback, &scale); } static void scale_bounds_callback (EekElement *element, gpointer user_data) { scale_bounds (element, *(gdouble *)user_data); } static EekKeyboard * eek_xml_layout_real_create_keyboard (EekLayout *self, gdouble initial_width, gdouble initial_height) { EekXmlLayoutPrivate *priv = EEK_XML_LAYOUT_GET_PRIVATE (self); GMarkupParseContext *pcontext; GError *error; gchar buffer[BUFSIZE]; ParseCallbackData data; EekBounds bounds; gdouble scale; GHashTableIter iter; gpointer k, v; GHashTable *oref_hash; g_return_val_if_fail (priv->source, NULL); memset (&data, 0, sizeof data); data.layout = self; data.text = g_string_sized_new (BUFSIZE); data.key_oref_hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); data.oref_outline_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify)eek_outline_free); pcontext = g_markup_parse_context_new (&parser, 0, &data, NULL); while (1) { gssize nread; error = NULL; nread = g_input_stream_read (G_INPUT_STREAM(priv->source), buffer, sizeof buffer, NULL, &error); if (nread <= 0) break; error = NULL; if (!g_markup_parse_context_parse (pcontext, buffer, nread, &error)) break; } if (error) g_warning ("%s", error->message); error = NULL; g_markup_parse_context_end_parse (pcontext, &error); if (error) g_warning ("%s", error->message); g_markup_parse_context_free (pcontext); if (!data.keyboard) goto out; eek_element_get_bounds (EEK_ELEMENT(data.keyboard), &bounds); scale = initial_width * bounds.height < initial_height * bounds.width ? initial_width / bounds.width : initial_height / bounds.height; oref_hash = g_hash_table_new (g_str_hash, g_str_equal); g_hash_table_iter_init (&iter, data.oref_outline_hash); while (g_hash_table_iter_next (&iter, &k, &v)) { EekOutline *outline = v; gulong oref; gint i; for (i = 0; i < outline->num_points; i++) { outline->points[i].x *= scale; outline->points[i].y *= scale; } oref = eek_keyboard_add_outline (data.keyboard, outline); g_hash_table_insert (oref_hash, k, (gpointer) oref); } g_hash_table_iter_init (&iter, data.key_oref_hash); while (g_hash_table_iter_next (&iter, &k, &v)) { gulong oref; oref = (gulong) g_hash_table_lookup (oref_hash, v); eek_key_set_oref (EEK_KEY(k), oref); } g_hash_table_destroy (oref_hash); scale_bounds (EEK_ELEMENT(data.keyboard), scale); out: g_string_free (data.text, TRUE); if (data.key_oref_hash) g_hash_table_destroy (data.key_oref_hash); if (data.oref_outline_hash) g_hash_table_destroy (data.oref_outline_hash); /* use pre-defined modifier mask here */ eek_keyboard_set_num_lock_mask (data.keyboard, EEK_MOD2_MASK); eek_keyboard_set_alt_gr_mask (data.keyboard, EEK_MOD5_MASK); return data.keyboard; } static void eek_xml_layout_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_SOURCE: eek_xml_layout_set_source (EEK_XML_LAYOUT(object), g_value_get_object (value)); break; default: g_object_set_property (object, g_param_spec_get_name (pspec), value); break; } } static void eek_xml_layout_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { switch (prop_id) { case PROP_SOURCE: g_value_set_object (value, eek_xml_layout_get_source (EEK_XML_LAYOUT(object))); break; default: g_object_get_property (object, g_param_spec_get_name (pspec), value); break; } } static void eek_xml_layout_dispose (GObject *object) { EekXmlLayoutPrivate *priv = EEK_XML_LAYOUT_GET_PRIVATE (object); if (priv->source) { g_object_unref (priv->source); priv->source = NULL; } G_OBJECT_CLASS (eek_xml_layout_parent_class)->dispose (object); } static void eek_xml_layout_class_init (EekXmlLayoutClass *klass) { EekLayoutClass *layout_class = EEK_LAYOUT_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; g_type_class_add_private (gobject_class, sizeof (EekXmlLayoutPrivate)); layout_class->create_keyboard = eek_xml_layout_real_create_keyboard; gobject_class->set_property = eek_xml_layout_set_property; gobject_class->get_property = eek_xml_layout_get_property; gobject_class->dispose = eek_xml_layout_dispose; pspec = g_param_spec_object ("source", "Source", "XML source input stram", G_TYPE_INPUT_STREAM, G_PARAM_READWRITE); g_object_class_install_property (gobject_class, PROP_SOURCE, pspec); } static void eek_xml_layout_init (EekXmlLayout *self) { self->priv = EEK_XML_LAYOUT_GET_PRIVATE (self); } EekLayout * eek_xml_layout_new (GInputStream *source) { return g_object_new (EEK_TYPE_XML_LAYOUT, "source", source, NULL); } void eek_xml_layout_set_source (EekXmlLayout *layout, GInputStream *source) { EekXmlLayoutPrivate *priv; g_return_if_fail (EEK_IS_XML_LAYOUT(layout)); g_return_if_fail (G_IS_INPUT_STREAM(source)); priv = EEK_XML_LAYOUT_GET_PRIVATE(layout); if (priv->source) g_object_unref (priv->source); priv->source = g_object_ref (source); } GInputStream * eek_xml_layout_get_source (EekXmlLayout *layout) { EekXmlLayoutPrivate *priv; g_assert (EEK_IS_XML_LAYOUT(layout)); priv = EEK_XML_LAYOUT_GET_PRIVATE(layout); return priv->source; }