From b811796bbcf3926478ce4d5c1d411be5bc294b38 Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Thu, 27 Jan 2011 16:27:06 +0900 Subject: [PATCH] Finish XML layout engine. --- eek/eek-key.c | 1 - eek/eek-keyboard.c | 14 ++ eek/eek-xml-layout.c | 353 +++++++++++++++++++++++++++++++++++++++---- eek/eek-xml.c | 40 ++++- eek/eek-xml.h | 2 +- tests/Makefile.am | 5 +- tests/eek-xml-test.c | 71 +++++++++ 7 files changed, 448 insertions(+), 38 deletions(-) create mode 100644 tests/eek-xml-test.c diff --git a/eek/eek-key.c b/eek/eek-key.c index d8159276..3d57a520 100644 --- a/eek/eek-key.c +++ b/eek/eek-key.c @@ -133,7 +133,6 @@ eek_key_real_get_keysyms (EekKey *self, if (num_levels) *num_levels = priv->keysyms.num_levels; if (keysyms && num_keysyms > 0) { - *keysyms = g_slice_alloc (num_keysyms * sizeof(guint)); memcpy (*keysyms, priv->keysyms.data, num_keysyms * sizeof(guint)); } } diff --git a/eek/eek-keyboard.c b/eek/eek-keyboard.c index ed99eaff..2fcdd448 100644 --- a/eek/eek-keyboard.c +++ b/eek/eek-keyboard.c @@ -290,6 +290,19 @@ eek_keyboard_real_key_released (EekKeyboard *self, set_level_from_modifiers (self); } +static void +eek_keyboard_dispose (GObject *object) +{ + EekKeyboardPrivate *priv = EEK_KEYBOARD_GET_PRIVATE(object); + + if (priv->layout) { + g_object_unref (priv->layout); + priv->layout = NULL; + } + + G_OBJECT_CLASS (eek_keyboard_parent_class)->dispose (object); +} + static void eek_keyboard_class_init (EekKeyboardClass *klass) { @@ -311,6 +324,7 @@ eek_keyboard_class_init (EekKeyboardClass *klass) gobject_class->get_property = eek_keyboard_get_property; gobject_class->set_property = eek_keyboard_set_property; + gobject_class->dispose = eek_keyboard_dispose; /** * EekKeyboard:group: diff --git a/eek/eek-xml-layout.c b/eek/eek-xml-layout.c index 795deb97..60116e93 100644 --- a/eek/eek-xml-layout.c +++ b/eek/eek-xml-layout.c @@ -4,6 +4,7 @@ */ #include +#include #ifdef HAVE_CONFIG_H #include "config.h" @@ -12,6 +13,7 @@ #include "eek-xml-layout.h" #include "eek-keyboard.h" #include "eek-section.h" +#include "eek-key.h" enum { PROP_0, @@ -27,6 +29,7 @@ G_DEFINE_TYPE (EekXmlLayout, eek_xml_layout, EEK_TYPE_LAYOUT); struct _EekXmlLayoutPrivate { GInputStream *source; + GHashTable *outline_hash; }; #define BUFSIZE 8192 @@ -35,43 +38,57 @@ struct _ParseCallbackData { GSList *element_stack; GString *text; EekLayout *layout; + EekKeyboard *keyboard; EekSection *section; EekKey *key; + gint num_columns; + EekOrientation orientation; + GSList *points; + guint keycode; + GSList *keysyms; + 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", - "keyboard/bounds", - "keyboard/section", - "keyboard/outline", - "section/bounds", - "section/angle", - "section/key", - "bounds/key", - "outline/key", - "keysyms/key", - "keycode/key", - "index/key", - "groups/keysyms", - "levels/keysyms", - "keysym/keysyms", + "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", + "outline-ref/key/section/keyboard", + "keysyms/key/section/keyboard", + "keycode/key/section/keyboard", + "index/key/section/keyboard", + "groups/keysyms/key/section/keyboard", + "levels/keysyms/key/section/keyboard", + "keysym/keysyms/key/section/keyboard", "point/outline/keyboard" }; static gchar * -join_element_names (GSList *element_names) +strjoin_slist (GSList *slist, const gchar *delimiter) { GString *string = g_string_sized_new (64); - if (element_names == NULL) + if (slist == NULL) return g_strdup (""); else - for (; element_names; element_names = g_slist_next (element_names)) { - g_string_append (string, element_names->data); - if (g_slist_next (element_names)) - g_string_append (string, "/"); + 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); } @@ -86,7 +103,7 @@ validate (const gchar *element_name, GSList *head; head = g_slist_prepend (element_stack, element_name); - element_path = join_element_names (head); + element_path = strjoin_slist (head, "/"); g_slist_free1 (head); for (i = 0; i < G_N_ELEMENTS(valid_path_list); i++) { @@ -120,8 +137,8 @@ start_element_callback (GMarkupParseContext *pcontext, ParseCallbackData *data = user_data; const gchar **names = attribute_names; const gchar **values = attribute_values; - gint column = -1, row = -1; - gchar *name = NULL; + gint column = -1, row = -1, groups = -1, levels = -1; + gchar *name = NULL, *id = NULL; validate (element_name, data->element_stack, error); if (error && *error) @@ -134,6 +151,12 @@ start_element_callback (GMarkupParseContext *pcontext, row = strtol (*values, NULL, 10); else if (g_strcmp0 (*names, "name") == 0) name = g_strdup (*values); + else if (g_strcmp0 (*names, "id") == 0) + id = 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++; } @@ -144,19 +167,41 @@ start_element_callback (GMarkupParseContext *pcontext, NULL); if (name) eek_element_set_name (EEK_ELEMENT(data->keyboard), name); - } else if (g_strcmp0 (element_name, "section") == 0) { + goto out; + } + + if (g_strcmp0 (element_name, "section") == 0) { data->section = eek_keyboard_create_section (data->keyboard); if (name) eek_element_set_name (EEK_ELEMENT(data->section), name); - } else if (g_strcmp0 (element_name, "key") == 0) { + goto out; + } + + if (g_strcmp0 (element_name, "key") == 0) { data->key = eek_section_create_key (data->section, column, row); if (name) eek_element_set_name (EEK_ELEMENT(data->key), name); + goto out; } + + if (g_strcmp0 (element_name, "keysyms") == 0) { + data->groups = groups; + data->levels = levels; + data->keysyms = NULL; + goto out; + } + + if (g_strcmp0 (element_name, "outline") == 0) { + data->oref = g_strdup (id); + goto out; + } + out: g_free (name); + g_free (id); data->element_stack = g_slist_prepend (data->element_stack, g_strdup (element_name)); + data->text->len = 0; } static void @@ -167,10 +212,161 @@ end_element_callback (GMarkupParseContext *pcontext, { 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, "keysyms") == 0) { + gint num_keysyms = data->groups * data->levels; + guint *keysyms = g_slice_alloc0 (sizeof(guint) * num_keysyms); + + head = data->keysyms = g_slist_reverse (data->keysyms); + for (i = 0; i < num_keysyms; i++) { + if (head) { + keysyms[i] = (guint)head->data; + head = g_slist_next (head); + } else + keysyms[i] = EEK_INVALID_KEYSYM; + } + + eek_key_set_keysyms (data->key, keysyms, data->groups, data->levels); + g_slice_free1 (sizeof(guint) * num_keysyms, keysyms); + g_slist_free (data->keysyms); + data->keysyms = NULL; + goto out; + } + + if (g_strcmp0 (element_name, "outline") == 0) { + EekOutline *outline = g_slice_new (EekOutline); + + 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; + 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, "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, "columns") == 0) { + data->num_columns = 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, "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, "keycode") == 0) { + eek_key_set_keycode (data->key, strtoul (text, NULL, 10)); + goto out; + } + + if (g_strcmp0 (element_name, "keysym") == 0) { + guint keysym = strtoul (text, NULL, 10); + data->keysyms = g_slist_prepend (data->keysyms, (gpointer)keysym); + goto out; + } + + if (g_strcmp0 (element_name, "outline-ref") == 0) { + g_hash_table_insert (data->key_oref_hash, data->key, g_strdup (text)); + goto out; + } + + out: + g_free (text); } static void @@ -180,6 +376,8 @@ text_callback (GMarkupParseContext *pcontext, gpointer user_data, GError **error) { + ParseCallbackData *data = user_data; + g_string_append_len (data->text, text, text_len); } static const GMarkupParser parser = { @@ -190,6 +388,42 @@ static const GMarkupParser parser = { 0 }; +static void +outline_free (gpointer data) +{ + EekOutline *outline = data; + g_slice_free1 (sizeof (EekPoint) * outline->num_points, outline->points); + g_boxed_free (EEK_TYPE_OUTLINE, outline); +} + +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; + + 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, @@ -200,12 +434,25 @@ eek_xml_layout_real_create_keyboard (EekLayout *self, GError *error; gchar buffer[BUFSIZE]; ParseCallbackData data; + EekBounds bounds; + gdouble scale; + GHashTableIter iter; + gpointer k, v; g_return_val_if_fail (priv->source, NULL); - data.element_stack = NULL; + memset (&data, 0, sizeof data); + data.layout = self; data.text = g_string_sized_new (BUFSIZE); - data.keyboard = NULL; + 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, + outline_free); + pcontext = g_markup_parse_context_new (&parser, 0, &data, NULL); while (1) { gssize nread; @@ -221,11 +468,50 @@ eek_xml_layout_real_create_keyboard (EekLayout *self, 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 < initial_height ? bounds.width / initial_width : + bounds.height / initial_height; + + g_hash_table_iter_init (&iter, data.key_oref_hash); + while (g_hash_table_iter_next (&iter, &k, &v)) { + EekOutline *outline = g_hash_table_lookup (data.oref_outline_hash, v); + g_assert (outline); + eek_key_set_outline (EEK_KEY(k), outline); + } + +#if 0 + g_hash_table_iter_init (&iter, data.oref_outline_hash); + while (g_hash_table_iter_next (&iter, &k, &v)) { + EekOutline *outline = v; + gint i; + + for (i = 0; i < outline->num_points; i++) { + outline->points[i].x *= scale; + outline->points[i].y *= scale; + } + } + + scale_bounds (EEK_ELEMENT(data.keyboard), scale); +#endif + + out: g_string_free (data.text, TRUE); + if (data.key_oref_hash) + g_hash_table_destroy (data.key_oref_hash); + priv->outline_hash = data.oref_outline_hash; return data.keyboard; } @@ -277,6 +563,18 @@ eek_xml_layout_dispose (GObject *object) g_object_unref (priv->source); priv->source = NULL; } + G_OBJECT_CLASS (eek_xml_layout_parent_class)->dispose (object); +} + +static void +eek_xml_layout_finalize (GObject *object) +{ + EekXmlLayoutPrivate *priv = EEK_XML_LAYOUT_GET_PRIVATE (object); + + if (priv->outline_hash) + g_hash_table_unref (priv->outline_hash); + + G_OBJECT_CLASS (eek_xml_layout_parent_class)->finalize (object); } static void @@ -293,6 +591,7 @@ eek_xml_layout_class_init (EekXmlLayoutClass *klass) 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; + gobject_class->finalize = eek_xml_layout_finalize; pspec = g_param_spec_object ("source", "Source", diff --git a/eek/eek-xml.c b/eek/eek-xml.c index 3afa68d1..7ca817c4 100644 --- a/eek/eek-xml.c +++ b/eek/eek-xml.c @@ -18,7 +18,7 @@ } \ } -G_INLINE_FUNC void +void g_string_markup_printf (GString *output, const gchar *format, ...) { gchar *escaped_text; @@ -56,7 +56,7 @@ output_key_callback (EekElement *element, gpointer user_data) OutputCallbackData *data = user_data; EekBounds bounds; EekOutline *outline; - gint i, num_groups, num_levels; + gint i, num_groups, num_levels, num_keysyms; guint *keysyms; gint column, row; @@ -86,7 +86,7 @@ output_key_callback (EekElement *element, gpointer user_data) g_array_append_val (data->outline_array, outline); g_string_append_indent (data->output, data->indent + 1); g_string_markup_printf (data->output, - "outline%d\n", + "outline%d\n", i); } @@ -94,9 +94,11 @@ output_key_callback (EekElement *element, gpointer user_data) g_string_markup_printf (data->output, "%u\n", eek_key_get_keycode (EEK_KEY(element))); - keysyms = NULL; - eek_key_get_keysyms (EEK_KEY(element), &keysyms, &num_groups, &num_levels); - if (keysyms) { + eek_key_get_keysyms (EEK_KEY(element), NULL, &num_groups, &num_levels); + num_keysyms = num_groups * num_levels; + if (num_keysyms > 0) { + keysyms = g_slice_alloc (num_keysyms * sizeof(guint)); + eek_key_get_keysyms (EEK_KEY(element), &keysyms, NULL, NULL); g_string_append_indent (data->output, data->indent + 1); g_string_markup_printf (data->output, "\n", @@ -118,6 +120,7 @@ output_key_callback (EekElement *element, gpointer user_data) } g_string_append_indent (data->output, data->indent + 1); g_string_markup_printf (data->output, "\n"); + g_slice_free1 (num_keysyms * sizeof(guint), keysyms); } g_string_append_indent (data->output, data->indent); @@ -129,7 +132,7 @@ output_section_callback (EekElement *element, gpointer user_data) { OutputCallbackData *data = user_data; EekBounds bounds; - gint angle; + gint angle, n_rows, i; g_string_append_indent (data->output, data->indent); if (eek_element_get_name (element)) @@ -145,6 +148,27 @@ output_section_callback (EekElement *element, gpointer user_data) angle = eek_section_get_angle (EEK_SECTION(element)); g_string_append_indent (data->output, data->indent + 1); g_string_markup_printf (data->output, "%d\n", angle); + + n_rows = eek_section_get_n_rows (EEK_SECTION(element)); + for (i = 0; i < n_rows; i++) { + gint num_columns; + EekOrientation orientation; + + eek_section_get_row (EEK_SECTION(element), + i, + &num_columns, + &orientation); + g_string_append_indent (data->output, data->indent + 1); + g_string_markup_printf (data->output, "\n"); + g_string_append_indent (data->output, data->indent + 2); + g_string_markup_printf (data->output, "%d\n", + num_columns); + g_string_append_indent (data->output, data->indent + 2); + g_string_markup_printf (data->output, "%d\n", + orientation); + g_string_append_indent (data->output, data->indent + 1); + g_string_markup_printf (data->output, "\n"); + } data->indent++; eek_container_foreach_child (EEK_CONTAINER(element), @@ -200,7 +224,7 @@ eek_keyboard_output (EekKeyboard *keyboard, GString *output, gint indent) g_string_append_indent (output, indent + 1); g_string_markup_printf (output, "\n"); } - g_array_free (data.outline_array, FALSE); + g_array_free (data.outline_array, TRUE); g_string_append_indent (output, indent); g_string_markup_printf (output, "\n"); diff --git a/eek/eek-xml.h b/eek/eek-xml.h index 65496d6e..843f2da2 100644 --- a/eek/eek-xml.h +++ b/eek/eek-xml.h @@ -7,7 +7,7 @@ G_BEGIN_DECLS -void eek_keyboard_print (EekKeyboard *keyboard, gint indent); +void eek_keyboard_output (EekKeyboard *keyboard, GString *output, gint indent); G_END_DECLS #endif /* EEK_XML_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index f806522f..1d74c674 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -18,7 +18,7 @@ INCLUDES = -I$(top_srcdir) $(GIO2_CFLAGS) $(GTK_CFLAGS) $(XKB_CFLAGS) -TESTS = eek-simple-test eek-xkb-test +TESTS = eek-simple-test eek-xkb-test eek-xml-test noinst_PROGRAMS = $(TESTS) eek_simple_test_SOURCES = eek-simple-test.c @@ -26,3 +26,6 @@ eek_simple_test_LDADD = $(top_builddir)/eek/libeek.la eek_xkb_test_SOURCES = eek-xkb-test.c eek_xkb_test_LDADD = $(top_builddir)/eek/libeek.la $(top_builddir)/eek/libeek-xkb.la + +eek_xml_test_SOURCES = eek-xml-test.c +eek_xml_test_LDADD = $(top_builddir)/eek/libeek.la $(top_builddir)/eek/libeek-xkl.la diff --git a/tests/eek-xml-test.c b/tests/eek-xml-test.c new file mode 100644 index 00000000..e7739546 --- /dev/null +++ b/tests/eek-xml-test.c @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2010 Daiki Ueno + * Copyright (C) 2010 Red Hat, Inc. + * + * This library 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) any later version. + * + * This library 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#include "eek/eek-xml.h" +#include "eek/eek-xkl.h" + +/* For gdk_x11_display_get_xdisplay(). See main(). */ +#include + +static void +test_output_parse (void) +{ + GString *output; + GInputStream *input; + EekLayout *layout; + EekKeyboard *keyboard; + + output = g_string_sized_new (8192); + + layout = eek_xkl_layout_new (); + + keyboard = eek_keyboard_new (layout, 640, 480); + g_object_unref (layout); + + eek_keyboard_output (keyboard, output, 0); + g_object_unref (keyboard); + +#if 0 + fwrite (output->str, sizeof(gchar), output->len, stdout); +#endif + + input = g_memory_input_stream_new_from_data (output->str, + output->len, + NULL); + layout = eek_xml_layout_new (input); + g_object_unref (input); + + keyboard = eek_keyboard_new (layout, 640, 480); + g_object_unref (layout); + g_object_unref (keyboard); + + g_string_free (output, TRUE); +} + +int +main (int argc, char **argv) +{ + g_type_init (); + g_test_init (&argc, &argv, NULL); + gtk_init (&argc, &argv); /* for gdk_x11_display_get_xdisplay() */ + + g_test_add_func ("/eek-xml-test/output-parse", test_output_parse); + + return g_test_run (); +}