From 63b9fea27be3afc1ba72a6bf759fb0cf95ac30bf Mon Sep 17 00:00:00 2001 From: Daiki Ueno Date: Tue, 15 Jun 2010 19:17:05 +0900 Subject: [PATCH] Add experimental GtkDrawingArea based UI. --- docs/reference/eek/Makefile.am | 2 + eek/Makefile.am | 31 ++- eek/eek-clutter-key-actor.c | 359 ++++++------------------ eek/eek-container.c | 4 +- eek/eek-container.h | 1 - eek/eek-drawing.c | 399 +++++++++++++++++++++++++++ eek/eek-drawing.h | 26 ++ eek/eek-element.c | 42 ++- eek/eek-element.h | 18 +- eek/eek-gtk-keyboard.c | 259 ++++++++++++++++++ eek/eek-gtk-keyboard.h | 57 ++++ eek/eek-gtk.h | 26 ++ eek/eek-keysym.h | 9 +- eek/eek-types.h | 8 + eek/eek.h | 2 + src/Makefile.am | 5 +- src/eekboard-gtk.c | 484 +++++++++++++++++++++++++++++++++ 17 files changed, 1429 insertions(+), 303 deletions(-) create mode 100644 eek/eek-drawing.c create mode 100644 eek/eek-drawing.h create mode 100644 eek/eek-gtk-keyboard.c create mode 100644 eek/eek-gtk-keyboard.h create mode 100644 eek/eek-gtk.h create mode 100644 src/eekboard-gtk.c diff --git a/docs/reference/eek/Makefile.am b/docs/reference/eek/Makefile.am index a50cc5dc..353b48d5 100644 --- a/docs/reference/eek/Makefile.am +++ b/docs/reference/eek/Makefile.am @@ -78,6 +78,7 @@ EXTRA_HFILES= # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h IGNORE_HFILES= eek-private.h \ eek-keysym.h \ + config.h \ $(NULL) # Images to copy into HTML directory. @@ -101,6 +102,7 @@ expand_content_files= GTKDOC_CFLAGS = $(GOBJECT2_CFLAGS) GTKDOC_LIBS = $(top_srcdir)/eek/libeek.la \ $(top_srcdir)/eek/libeek-clutter.la \ + $(top_srcdir)/eek/libeek-gtk.la \ $(top_srcdir)/eek/libeek-xkb.la \ $(top_srcdir)/eek/libeek-xkl.la \ $(GOBJECT2_LIBS) \ diff --git a/eek/Makefile.am b/eek/Makefile.am index 1371e956..43913a9c 100644 --- a/eek/Makefile.am +++ b/eek/Makefile.am @@ -16,7 +16,12 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -lib_LTLIBRARIES = libeek.la libeek-clutter.la libeek-xkb.la libeek-xkl.la +lib_LTLIBRARIES = \ + libeek.la \ + libeek-clutter.la \ + libeek-gtk.la \ + libeek-xkb.la \ + libeek-xkl.la libeek_la_SOURCES = \ eek-layout.c \ @@ -37,8 +42,7 @@ libeek_la_SOURCES = \ eek-keysym.c \ eek-special-keysym-labels.h \ eek-unicode-keysym-labels.h \ - eek-keyname-keysym-labels.h \ - $(NULL) + eek-keyname-keysym-labels.h libeek_la_CFLAGS = $(GOBJECT2_CFLAGS) $(CAIRO_CFLAGS) $(PANGO_CFLAGS) libeek_la_LIBADD = $(GOBJECT2_LIBS) $(CAIRO_LIBS) $(PANGO_LIBS) @@ -52,24 +56,33 @@ libeek_clutter_la_SOURCES = \ eek-clutter-key.h \ eek-clutter-key-actor.c \ eek-clutter-key-actor.h \ - eek-clutter.h \ - $(NULL) + eek-drawing.h \ + eek-drawing.c \ + eek-clutter.h libeek_clutter_la_CFLAGS = $(CLUTTER_CFLAGS) libeek_clutter_la_LIBADD = libeek.la $(CLUTTER_LIBS) +libeek_gtk_la_SOURCES = \ + eek-gtk-keyboard.c \ + eek-gtk-keyboard.h \ + eek-drawing.h \ + eek-drawing.c \ + eek-gtk.h + +libeek_gtk_la_CFLAGS = $(GTK2_CFLAGS) +libeek_gtk_la_LIBADD = libeek.la $(GTK2_LIBS) + libeek_xkb_la_SOURCES = \ eek-xkb-layout.h \ - eek-xkb-layout.c \ - $(NULL) + eek-xkb-layout.c libeek_xkb_la_CFLAGS = $(GTK2_CFLAGS) $(XKB_CFLAGS) libeek_xkb_la_LIBADD = libeek.la $(GTK2_LIBS) $(XKB_LIBS) libeek_xkl_la_SOURCES = \ eek-xkl-layout.h \ - eek-xkl-layout.c \ - $(NULL) + eek-xkl-layout.c libeek_xkl_la_CFLAGS = $(GTK2_CFLAGS) $(LIBXKLAVIER_CFLAGS) libeek_xkl_la_LIBADD = libeek-xkb.la $(GTK2_LIBS) $(LIBXKLAVIER_LIBS) diff --git a/eek/eek-clutter-key-actor.c b/eek/eek-clutter-key-actor.c index aae45583..c6fca090 100644 --- a/eek/eek-clutter-key-actor.c +++ b/eek/eek-clutter-key-actor.c @@ -26,13 +26,15 @@ #include #include -#include #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #include "eek-clutter-key-actor.h" #include "eek-keysym.h" +#include "eek-drawing.h" +#include "eek-section.h" +#include "eek-keyboard.h" #define noKBDRAW_DEBUG @@ -54,6 +56,7 @@ struct _EekClutterKeyActorPrivate { EekKey *key; ClutterActor *texture; + PangoFontDescription *font; }; static struct { @@ -61,20 +64,24 @@ static struct { GHashTable *outline_textures; /* manually maintain the ref-count of outline_textures to set it - to NULL on destroy */ + to NULL when all the EekClutterKeyActor are destroyed */ gint outline_textures_ref_count; } texture_cache; +static struct { + /* keysym category -> PangoFontDescription * */ + PangoFontDescription *category_fonts[EEK_KEYSYM_CATEGORY_LAST]; + + /* manually maintain the ref-count of category_fonts to set it to + NULL when all the EekClutterKeyActor are destroyed */ + gint category_fonts_ref_count; +} font_cache; + static ClutterActor *get_texture (EekClutterKeyActor *actor); -static void draw_key_on_layout (EekKey *key, +static void draw_key_on_layout (EekClutterKeyActor *actor, PangoLayout *layout); static void key_enlarge (ClutterActor *actor); static void key_shrink (ClutterActor *actor); -static void draw_rounded_polygon (cairo_t *cr, - gboolean filled, - gdouble radius, - EekPoint *points, - gint num_points); static void eek_clutter_key_actor_real_paint (ClutterActor *self) @@ -104,7 +111,7 @@ eek_clutter_key_actor_real_paint (ClutterActor *self) /* Draw the label on the key. */ layout = clutter_actor_create_pango_layout (self, NULL); - draw_key_on_layout (priv->key, layout); + draw_key_on_layout (EEK_CLUTTER_KEY_ACTOR(self), layout); pango_layout_get_extents (layout, NULL, &logical_rect); /* FIXME: Color should be configurable through a property. */ @@ -129,12 +136,11 @@ eek_clutter_key_actor_real_get_preferred_width (ClutterActor *self, gfloat *min_width_p, gfloat *natural_width_p) { - EekClutterKeyActorPrivate *priv = EEK_CLUTTER_KEY_ACTOR_GET_PRIVATE (self); PangoLayout *layout; /* Draw the label on the key - just to validate the glyph cache. */ layout = clutter_actor_create_pango_layout (self, NULL); - draw_key_on_layout (priv->key, layout); + draw_key_on_layout (EEK_CLUTTER_KEY_ACTOR(self), layout); cogl_pango_ensure_glyph_cache_for_layout (layout); g_object_unref (layout); @@ -189,6 +195,18 @@ eek_clutter_key_actor_dispose (GObject *object) texture_cache.outline_textures = NULL; } } + if (priv->font) { + /* no need to pango_font_description_free (priv->font) */ + priv->font = NULL; + if (--font_cache.category_fonts_ref_count == 0) { + gint i; + + for (i = 0; i < EEK_KEYSYM_CATEGORY_LAST; i++) { + pango_font_description_free (font_cache.category_fonts[i]); + font_cache.category_fonts[i] = NULL; + } + } + } G_OBJECT_CLASS (eek_clutter_key_actor_parent_class)->dispose (object); } @@ -268,6 +286,8 @@ eek_clutter_key_actor_init (EekClutterKeyActor *self) priv = self->priv = EEK_CLUTTER_KEY_ACTOR_GET_PRIVATE(self); priv->key = NULL; priv->texture = NULL; + priv->font = NULL; + clutter_actor_set_reactive (CLUTTER_ACTOR(self), TRUE); g_signal_connect (self, "button-press-event", G_CALLBACK (on_button_press_event), NULL); @@ -343,20 +363,20 @@ create_texture_for_key (EekKey *key) cairo_set_source (cr, pat); - draw_rounded_polygon (cr, - TRUE, - outline->corner_radius, - outline->points, - outline->num_points); + eek_draw_rounded_polygon (cr, + TRUE, + outline->corner_radius, + outline->points, + outline->num_points); cairo_pattern_destroy (pat); cairo_set_source_rgba (cr, 0.3, 0.3, 0.3, 0.5); - draw_rounded_polygon (cr, - FALSE, - outline->corner_radius, - outline->points, - outline->num_points); + eek_draw_rounded_polygon (cr, + FALSE, + outline->corner_radius, + outline->points, + outline->num_points); cairo_destroy (cr); return texture; } @@ -382,272 +402,57 @@ get_texture (EekClutterKeyActor *actor) } static void -draw_text_on_layout (PangoLayout *layout, - const gchar *text, - gdouble scale) -{ - PangoFontDescription *font_desc; - -#define FONT_SIZE (720 * 50) - /* FIXME: Font should be configurable through a property. */ - font_desc = pango_font_description_from_string ("Sans"); - pango_font_description_set_size (font_desc, FONT_SIZE * scale); - pango_layout_set_font_description (layout, font_desc); - pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); - pango_layout_set_text (layout, text, -1); - pango_font_description_free (font_desc); -} - -static void -draw_key_on_layout (EekKey *key, +draw_key_on_layout (EekClutterKeyActor *self, PangoLayout *layout) { - PangoLayout *buffer; - PangoRectangle logical_rect = { 0, }; - EekBounds bounds; + EekClutterKeyActorPrivate *priv = EEK_CLUTTER_KEY_ACTOR_GET_PRIVATE (self); guint keysym; const gchar *label, *empty_label = ""; - gdouble scale_x, scale_y; + EekKeysymCategory category; + EekBounds bounds; - eek_element_get_bounds (EEK_ELEMENT(key), &bounds); - keysym = eek_key_get_keysym (key); + if (font_cache.category_fonts_ref_count == 0) { + EekContainer *keyboard, *section; + PangoFontDescription *font; + PangoLayout *copy; + + section = EEK_ELEMENT_GET_CLASS(priv->key)-> + get_parent (EEK_ELEMENT(priv->key)); + g_return_if_fail (EEK_IS_SECTION(section)); + + keyboard = EEK_ELEMENT_GET_CLASS(section)-> + get_parent (EEK_ELEMENT(section)); + g_return_if_fail (EEK_IS_KEYBOARD(keyboard)); + + copy = pango_layout_copy (layout); + font = pango_font_description_from_string ("Sans"); + pango_layout_set_font_description (copy, font); + + eek_get_fonts (EEK_KEYBOARD(keyboard), copy, + font_cache.category_fonts); + g_object_unref (G_OBJECT(copy)); + } + + keysym = eek_key_get_keysym (priv->key); if (keysym == EEK_INVALID_KEYSYM) return; + category = eek_keysym_get_category (keysym); + if (category == EEK_KEYSYM_CATEGORY_UNKNOWN) + return; + if (!priv->font) { + priv->font = font_cache.category_fonts[category]; + font_cache.category_fonts_ref_count++; + } + pango_layout_set_font_description (layout, priv->font); + + eek_element_get_bounds (EEK_ELEMENT(priv->key), &bounds); + pango_layout_set_width (layout, PANGO_SCALE * bounds.width); + pango_layout_set_ellipsize (layout, PANGO_ELLIPSIZE_END); + label = eek_keysym_to_string (keysym); if (!label) label = empty_label; - - /* Compute the layout extents. */ - buffer = pango_layout_copy (layout); - draw_text_on_layout (buffer, label, 1.0); - pango_layout_get_extents (buffer, NULL, &logical_rect); - scale_x = scale_y = 1.0; - if (PANGO_PIXELS(logical_rect.width) > bounds.width) - scale_x = bounds.width / PANGO_PIXELS(logical_rect.width); - if (PANGO_PIXELS(logical_rect.height) > bounds.height) - scale_y = bounds.height / PANGO_PIXELS(logical_rect.height); - g_object_unref (buffer); - - /* Actually draw on the layout */ - draw_text_on_layout (layout, - label, - (scale_x < scale_y ? scale_x : scale_y) * 0.8); + eek_draw_text_on_layout (layout, label); if (label != empty_label) g_free ((gpointer)label); } - -/* - * The functions below are borrowed from - * libgnomekbd/gkbd-keyboard-drawing.c. - * Copyright (C) 2006 Sergey V. Udaltsov - * - * length(), point_line_distance(), normal_form(), inverse(), multiply(), - * intersect(), rounded_corner(), draw_rounded_polygon() - */ - -static gdouble -length (gdouble x, gdouble y) -{ - return sqrt (x * x + y * y); -} - -static gdouble -point_line_distance (gdouble ax, gdouble ay, gdouble nx, gdouble ny) -{ - return ax * nx + ay * ny; -} - -static void -normal_form (gdouble ax, gdouble ay, - gdouble bx, gdouble by, - gdouble * nx, gdouble * ny, gdouble * d) -{ - gdouble l; - - *nx = by - ay; - *ny = ax - bx; - - l = length (*nx, *ny); - - *nx /= l; - *ny /= l; - - *d = point_line_distance (ax, ay, *nx, *ny); -} - -static void -inverse (gdouble a, gdouble b, gdouble c, gdouble d, - gdouble * e, gdouble * f, gdouble * g, gdouble * h) -{ - gdouble det; - - det = a * d - b * c; - - *e = d / det; - *f = -b / det; - *g = -c / det; - *h = a / det; -} - -static void -multiply (gdouble a, gdouble b, gdouble c, gdouble d, - gdouble e, gdouble f, gdouble * x, gdouble * y) -{ - *x = a * e + b * f; - *y = c * e + d * f; -} - -static void -intersect (gdouble n1x, gdouble n1y, gdouble d1, - gdouble n2x, gdouble n2y, gdouble d2, gdouble * x, gdouble * y) -{ - gdouble e, f, g, h; - - inverse (n1x, n1y, n2x, n2y, &e, &f, &g, &h); - multiply (e, f, g, h, d1, d2, x, y); -} - - -/* draw an angle from the current point to b and then to c, - * with a rounded corner of the given radius. - */ -static void -rounded_corner (cairo_t * cr, - gdouble bx, gdouble by, - gdouble cx, gdouble cy, gdouble radius) -{ - gdouble ax, ay; - gdouble n1x, n1y, d1; - gdouble n2x, n2y, d2; - gdouble pd1, pd2; - gdouble ix, iy; - gdouble dist1, dist2; - gdouble nx, ny, d; - gdouble a1x, a1y, c1x, c1y; - gdouble phi1, phi2; - - cairo_get_current_point (cr, &ax, &ay); -#ifdef KBDRAW_DEBUG - printf (" current point: (%f, %f), radius %f:\n", ax, ay, - radius); -#endif - - /* make sure radius is not too large */ - dist1 = length (bx - ax, by - ay); - dist2 = length (cx - bx, cy - by); - - radius = MIN (radius, MIN (dist1, dist2)); - - /* construct normal forms of the lines */ - normal_form (ax, ay, bx, by, &n1x, &n1y, &d1); - normal_form (bx, by, cx, cy, &n2x, &n2y, &d2); - - /* find which side of the line a,b the point c is on */ - if (point_line_distance (cx, cy, n1x, n1y) < d1) - pd1 = d1 - radius; - else - pd1 = d1 + radius; - - /* find which side of the line b,c the point a is on */ - if (point_line_distance (ax, ay, n2x, n2y) < d2) - pd2 = d2 - radius; - else - pd2 = d2 + radius; - - /* intersect the parallels to find the center of the arc */ - intersect (n1x, n1y, pd1, n2x, n2y, pd2, &ix, &iy); - - nx = (bx - ax) / dist1; - ny = (by - ay) / dist1; - d = point_line_distance (ix, iy, nx, ny); - - /* a1 is the point on the line a-b where the arc starts */ - intersect (n1x, n1y, d1, nx, ny, d, &a1x, &a1y); - - nx = (cx - bx) / dist2; - ny = (cy - by) / dist2; - d = point_line_distance (ix, iy, nx, ny); - - /* c1 is the point on the line b-c where the arc ends */ - intersect (n2x, n2y, d2, nx, ny, d, &c1x, &c1y); - - /* determine the first angle */ - if (a1x - ix == 0) - phi1 = (a1y - iy > 0) ? M_PI_2 : 3 * M_PI_2; - else if (a1x - ix > 0) - phi1 = atan ((a1y - iy) / (a1x - ix)); - else - phi1 = M_PI + atan ((a1y - iy) / (a1x - ix)); - - /* determine the second angle */ - if (c1x - ix == 0) - phi2 = (c1y - iy > 0) ? M_PI_2 : 3 * M_PI_2; - else if (c1x - ix > 0) - phi2 = atan ((c1y - iy) / (c1x - ix)); - else - phi2 = M_PI + atan ((c1y - iy) / (c1x - ix)); - - /* compute the difference between phi2 and phi1 mod 2pi */ - d = phi2 - phi1; - while (d < 0) - d += 2 * M_PI; - while (d > 2 * M_PI) - d -= 2 * M_PI; - -#ifdef KBDRAW_DEBUG - printf (" line 1 to: (%f, %f):\n", a1x, a1y); -#endif - if (!(isnan (a1x) || isnan (a1y))) - cairo_line_to (cr, a1x, a1y); - - /* pick the short arc from phi1 to phi2 */ - if (d < M_PI) - cairo_arc (cr, ix, iy, radius, phi1, phi2); - else - cairo_arc_negative (cr, ix, iy, radius, phi1, phi2); - -#ifdef KBDRAW_DEBUG - printf (" line 2 to: (%f, %f):\n", cx, cy); -#endif - cairo_line_to (cr, cx, cy); -} - -static void -draw_rounded_polygon (cairo_t *cr, - gboolean filled, - gdouble radius, - EekPoint *points, - gint num_points) -{ - gint i, j; - - cairo_move_to (cr, - (gdouble) (points[num_points - 1].x + - points[0].x) / 2, - (gdouble) (points[num_points - 1].y + - points[0].y) / 2); - - -#ifdef KBDRAW_DEBUG - printf (" rounded polygon of radius %f:\n", radius); -#endif - for (i = 0; i < num_points; i++) { - j = (i + 1) % num_points; - rounded_corner (cr, (gdouble) points[i].x, - (gdouble) points[i].y, - (gdouble) (points[i].x + points[j].x) / 2, - (gdouble) (points[i].y + points[j].y) / 2, - radius); -#ifdef KBDRAW_DEBUG - printf (" corner (%d, %d) -> (%d, %d):\n", - points[i].x, points[i].y, points[j].x, - points[j].y); -#endif - }; - cairo_close_path (cr); - - if (filled) - cairo_fill (cr); - else - cairo_stroke (cr); -} diff --git a/eek/eek-container.c b/eek/eek-container.c index 767bcac6..c181cb4f 100644 --- a/eek/eek-container.c +++ b/eek/eek-container.c @@ -59,7 +59,9 @@ eek_container_real_add_child (EekContainer *self, g_return_if_fail (EEK_IS_ELEMENT(child)); g_object_ref_sink (child); + priv->children = g_slist_prepend (priv->children, child); + EEK_ELEMENT_GET_CLASS(child)->set_parent (child, self); } static void @@ -74,6 +76,7 @@ eek_container_real_remove_child (EekContainer *self, g_return_if_fail (head); g_object_unref (child); priv->children = g_slist_remove_link (priv->children, head); + EEK_ELEMENT_GET_CLASS(child)->set_parent (child, NULL); } static void @@ -121,7 +124,6 @@ static void eek_container_finalize (GObject *object) { EekContainerPrivate *priv = EEK_CONTAINER_GET_PRIVATE(object); - GSList *head; g_slist_free (priv->children); G_OBJECT_CLASS(eek_container_parent_class)->finalize (object); diff --git a/eek/eek-container.h b/eek/eek-container.h index 418efd74..dbd9ac83 100644 --- a/eek/eek-container.h +++ b/eek/eek-container.h @@ -31,7 +31,6 @@ G_BEGIN_DECLS #define EEK_IS_CONTAINER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEK_TYPE_CONTAINER)) #define EEK_CONTAINER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEK_TYPE_CONTAINER, EekContainerClass)) -typedef struct _EekContainer EekContainer; typedef struct _EekContainerClass EekContainerClass; typedef struct _EekContainerPrivate EekContainerPrivate; diff --git a/eek/eek-drawing.c b/eek/eek-drawing.c new file mode 100644 index 00000000..52bc3958 --- /dev/null +++ b/eek/eek-drawing.c @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2006 Sergey V. Udaltsov + * 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 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "eek-keyboard.h" +#include "eek-key.h" +#include "eek-drawing.h" +#include "eek-keysym.h" + + +void +eek_draw_text_on_layout (PangoLayout *layout, + const gchar *text) +{ + /* pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); */ + pango_layout_set_text (layout, text, -1); +} + +struct _GetFontSizeCallbackData +{ + PangoLayout *layout; + EekKeysymCategory category; + gint minimum_font_size; + gint font_size; +}; +typedef struct _GetFontSizeCallbackData GetFontSizeCallbackData; + +static gint +get_font_size (const gchar *text, + EekBounds *bounds, + PangoLayout *layout) +{ + gdouble scale_x, scale_y; + const PangoFontDescription *base_font_desc; + PangoFontDescription *font_desc; + PangoRectangle logical_rect = { 0, }; + gint font_size; + + layout = pango_layout_copy (layout); + base_font_desc = pango_layout_get_font_description (layout); + font_desc = pango_font_description_copy (base_font_desc); + + font_size = eek_bounds_long_side (bounds) * PANGO_SCALE; + pango_font_description_set_size (font_desc, font_size); + pango_layout_set_font_description (layout, font_desc); + pango_font_description_free (font_desc); + + eek_draw_text_on_layout (layout, text); + pango_layout_get_extents (layout, NULL, &logical_rect); + + scale_x = scale_y = 1.0; + if (logical_rect.width > bounds->width * PANGO_SCALE) + scale_x = bounds->width * PANGO_SCALE / logical_rect.width; + if (logical_rect.height > bounds->height * PANGO_SCALE) + scale_y = bounds->height * PANGO_SCALE / logical_rect.height; + g_object_unref (layout); + return font_size * (scale_x < scale_y ? scale_x : scale_y); +} + +static void +egf_key_callback (EekElement *element, + gpointer user_data) +{ + EekKey *key = EEK_KEY(element); + GetFontSizeCallbackData *data = user_data; + guint *keysyms; + gint num_groups, num_levels, i; + gdouble font_size; + + eek_key_get_keysyms (key, &keysyms, &num_groups, &num_levels); + for (i = 0; i < num_groups * num_levels; i++) { + guint keysym = keysyms[i]; + EekBounds bounds; + const gchar *label; + + if (keysym == EEK_INVALID_KEYSYM || + eek_keysym_get_category (keysym) != data->category) + continue; + + eek_element_get_bounds (EEK_ELEMENT(key), &bounds); + label = eek_keysym_to_string (keysym); + font_size = get_font_size (label, &bounds, data->layout); + if (font_size < data->font_size && font_size >= data->minimum_font_size) + data->font_size = font_size; + } +} + +static void +egf_section_callback (EekElement *element, + gpointer user_data) +{ + eek_container_foreach_child (EEK_CONTAINER(element), + egf_key_callback, + user_data); +} + +static PangoFontDescription * +get_font_for_category (EekKeyboard *keyboard, + EekKeysymCategory category, + PangoLayout *layout, + gdouble minimum_font_size, + gdouble maximum_font_size) +{ + GetFontSizeCallbackData data; + PangoFontDescription *font_desc; + const PangoFontDescription *base_font_desc; + + data.layout = layout; + data.category = category; + data.minimum_font_size = minimum_font_size; + data.font_size = maximum_font_size; + + eek_container_foreach_child (EEK_CONTAINER(keyboard), + egf_section_callback, + &data); + + base_font_desc = pango_layout_get_font_description (layout); + font_desc = pango_font_description_copy (base_font_desc); + pango_font_description_set_size (font_desc, data.font_size); + + return font_desc; +} + +void +eek_get_fonts (EekKeyboard *keyboard, + PangoLayout *layout, + PangoFontDescription **fonts) +{ + EekBounds bounds; + PangoFontDescription *font_desc; + gint font_size; + + /* font for EEK_KEYSYM_CATEGORY_LETTER */ + eek_element_get_bounds (EEK_ELEMENT(keyboard), &bounds); + font_desc = get_font_for_category (keyboard, + EEK_KEYSYM_CATEGORY_LETTER, + layout, + 0, + eek_bounds_long_side (&bounds) * + PANGO_SCALE); + font_size = pango_font_description_get_size (font_desc); + fonts[EEK_KEYSYM_CATEGORY_LETTER] = font_desc; + + /* font for EEK_KEYSYM_CATEGORY_FUNCTION */ + font_desc = get_font_for_category (keyboard, + EEK_KEYSYM_CATEGORY_FUNCTION, + layout, + font_size * 0.3, + font_size); + fonts[EEK_KEYSYM_CATEGORY_FUNCTION] = font_desc; + + /* font for EEK_KEYSYM_CATEGORY_KEYNAME */ + font_desc = get_font_for_category (keyboard, + EEK_KEYSYM_CATEGORY_KEYNAME, + layout, + font_size * 0.3, + font_size); + fonts[EEK_KEYSYM_CATEGORY_KEYNAME] = font_desc; +} + +/* + * The functions below are borrowed from + * libgnomekbd/gkbd-keyboard-drawing.c. + * Copyright (C) 2006 Sergey V. Udaltsov + * + * length(), point_line_distance(), normal_form(), inverse(), multiply(), + * intersect(), rounded_corner(), draw_rounded_polygon() + */ + +static gdouble +length (gdouble x, gdouble y) +{ + return sqrt (x * x + y * y); +} + +static gdouble +point_line_distance (gdouble ax, gdouble ay, gdouble nx, gdouble ny) +{ + return ax * nx + ay * ny; +} + +static void +normal_form (gdouble ax, gdouble ay, + gdouble bx, gdouble by, + gdouble * nx, gdouble * ny, gdouble * d) +{ + gdouble l; + + *nx = by - ay; + *ny = ax - bx; + + l = length (*nx, *ny); + + *nx /= l; + *ny /= l; + + *d = point_line_distance (ax, ay, *nx, *ny); +} + +static void +inverse (gdouble a, gdouble b, gdouble c, gdouble d, + gdouble * e, gdouble * f, gdouble * g, gdouble * h) +{ + gdouble det; + + det = a * d - b * c; + + *e = d / det; + *f = -b / det; + *g = -c / det; + *h = a / det; +} + +static void +multiply (gdouble a, gdouble b, gdouble c, gdouble d, + gdouble e, gdouble f, gdouble * x, gdouble * y) +{ + *x = a * e + b * f; + *y = c * e + d * f; +} + +static void +intersect (gdouble n1x, gdouble n1y, gdouble d1, + gdouble n2x, gdouble n2y, gdouble d2, gdouble * x, gdouble * y) +{ + gdouble e, f, g, h; + + inverse (n1x, n1y, n2x, n2y, &e, &f, &g, &h); + multiply (e, f, g, h, d1, d2, x, y); +} + + +/* draw an angle from the current point to b and then to c, + * with a rounded corner of the given radius. + */ +static void +rounded_corner (cairo_t * cr, + gdouble bx, gdouble by, + gdouble cx, gdouble cy, gdouble radius) +{ + gdouble ax, ay; + gdouble n1x, n1y, d1; + gdouble n2x, n2y, d2; + gdouble pd1, pd2; + gdouble ix, iy; + gdouble dist1, dist2; + gdouble nx, ny, d; + gdouble a1x, a1y, c1x, c1y; + gdouble phi1, phi2; + + cairo_get_current_point (cr, &ax, &ay); +#ifdef KBDRAW_DEBUG + printf (" current point: (%f, %f), radius %f:\n", ax, ay, + radius); +#endif + + /* make sure radius is not too large */ + dist1 = length (bx - ax, by - ay); + dist2 = length (cx - bx, cy - by); + + radius = MIN (radius, MIN (dist1, dist2)); + + /* construct normal forms of the lines */ + normal_form (ax, ay, bx, by, &n1x, &n1y, &d1); + normal_form (bx, by, cx, cy, &n2x, &n2y, &d2); + + /* find which side of the line a,b the point c is on */ + if (point_line_distance (cx, cy, n1x, n1y) < d1) + pd1 = d1 - radius; + else + pd1 = d1 + radius; + + /* find which side of the line b,c the point a is on */ + if (point_line_distance (ax, ay, n2x, n2y) < d2) + pd2 = d2 - radius; + else + pd2 = d2 + radius; + + /* intersect the parallels to find the center of the arc */ + intersect (n1x, n1y, pd1, n2x, n2y, pd2, &ix, &iy); + + nx = (bx - ax) / dist1; + ny = (by - ay) / dist1; + d = point_line_distance (ix, iy, nx, ny); + + /* a1 is the point on the line a-b where the arc starts */ + intersect (n1x, n1y, d1, nx, ny, d, &a1x, &a1y); + + nx = (cx - bx) / dist2; + ny = (cy - by) / dist2; + d = point_line_distance (ix, iy, nx, ny); + + /* c1 is the point on the line b-c where the arc ends */ + intersect (n2x, n2y, d2, nx, ny, d, &c1x, &c1y); + + /* determine the first angle */ + if (a1x - ix == 0) + phi1 = (a1y - iy > 0) ? M_PI_2 : 3 * M_PI_2; + else if (a1x - ix > 0) + phi1 = atan ((a1y - iy) / (a1x - ix)); + else + phi1 = M_PI + atan ((a1y - iy) / (a1x - ix)); + + /* determine the second angle */ + if (c1x - ix == 0) + phi2 = (c1y - iy > 0) ? M_PI_2 : 3 * M_PI_2; + else if (c1x - ix > 0) + phi2 = atan ((c1y - iy) / (c1x - ix)); + else + phi2 = M_PI + atan ((c1y - iy) / (c1x - ix)); + + /* compute the difference between phi2 and phi1 mod 2pi */ + d = phi2 - phi1; + while (d < 0) + d += 2 * M_PI; + while (d > 2 * M_PI) + d -= 2 * M_PI; + +#ifdef KBDRAW_DEBUG + printf (" line 1 to: (%f, %f):\n", a1x, a1y); +#endif + if (!(isnan (a1x) || isnan (a1y))) + cairo_line_to (cr, a1x, a1y); + + /* pick the short arc from phi1 to phi2 */ + if (d < M_PI) + cairo_arc (cr, ix, iy, radius, phi1, phi2); + else + cairo_arc_negative (cr, ix, iy, radius, phi1, phi2); + +#ifdef KBDRAW_DEBUG + printf (" line 2 to: (%f, %f):\n", cx, cy); +#endif + cairo_line_to (cr, cx, cy); +} + +void +eek_draw_rounded_polygon (cairo_t *cr, + gboolean filled, + gdouble radius, + EekPoint *points, + gint num_points) +{ + gint i, j; + + cairo_move_to (cr, + (gdouble) (points[num_points - 1].x + + points[0].x) / 2, + (gdouble) (points[num_points - 1].y + + points[0].y) / 2); + + +#ifdef KBDRAW_DEBUG + printf (" rounded polygon of radius %f:\n", radius); +#endif + for (i = 0; i < num_points; i++) { + j = (i + 1) % num_points; + rounded_corner (cr, (gdouble) points[i].x, + (gdouble) points[i].y, + (gdouble) (points[i].x + points[j].x) / 2, + (gdouble) (points[i].y + points[j].y) / 2, + radius); +#ifdef KBDRAW_DEBUG + printf (" corner (%d, %d) -> (%d, %d):\n", + points[i].x, points[i].y, points[j].x, + points[j].y); +#endif + }; + cairo_close_path (cr); + + if (filled) + cairo_fill (cr); + else + cairo_stroke (cr); +} diff --git a/eek/eek-drawing.h b/eek/eek-drawing.h new file mode 100644 index 00000000..a4643a04 --- /dev/null +++ b/eek/eek-drawing.h @@ -0,0 +1,26 @@ +#ifndef EEK_DRAWING_H +#define EEK_DRAWING_H 1 + +#include +#include +#include "eek-keyboard.h" +#include "eek-keysym.h" +#include "eek-types.h" + +G_BEGIN_DECLS + +void eek_draw_text_on_layout (PangoLayout *layout, + const gchar *text); + +void eek_get_fonts (EekKeyboard *keyboard, + PangoLayout *layout, + PangoFontDescription **fonts); + +void eek_draw_rounded_polygon (cairo_t *cr, + gboolean filled, + gdouble radius, + EekPoint *points, + gint num_points); + +G_END_DECLS +#endif /* EEK_DRAWING_H */ diff --git a/eek/eek-element.c b/eek/eek-element.c index 10df2ca5..3055a477 100644 --- a/eek/eek-element.c +++ b/eek/eek-element.c @@ -33,6 +33,7 @@ #endif /* HAVE_CONFIG_H */ #include "eek-element.h" +#include "eek-container.h" enum { PROP_0, @@ -51,14 +52,37 @@ struct _EekElementPrivate { gchar *name; EekBounds bounds; + EekContainer *parent; }; static void -eek_element_real_set_name (EekElement *self, - const gchar *name) +eek_element_real_set_parent (EekElement *self, + EekContainer *parent) { EekElementPrivate *priv = EEK_ELEMENT_GET_PRIVATE(self); + if (parent) { + g_return_if_fail (EEK_IS_CONTAINER(parent)); + g_object_ref_sink (G_OBJECT(parent)); + } else if (priv->parent) + g_object_unref (G_OBJECT (priv->parent)); + priv->parent = parent; +} + +static EekContainer * +eek_element_real_get_parent (EekElement *self) +{ + EekElementPrivate *priv = EEK_ELEMENT_GET_PRIVATE(self); + return priv->parent; +} + +static void +eek_element_real_set_name (EekElement *self, + const gchar *name) +{ + EekElementPrivate *priv = EEK_ELEMENT_GET_PRIVATE(self); + + g_free (priv->name); priv->name = g_strdup (name); g_object_notify (G_OBJECT(self), "name"); @@ -93,6 +117,17 @@ eek_element_real_get_bounds (EekElement *self, g_object_notify (G_OBJECT(self), "bounds"); } +static void +eek_element_dispose (GObject *object) +{ + EekElementPrivate *priv = EEK_ELEMENT_GET_PRIVATE(object); + + if (priv->parent) { + g_object_unref (G_OBJECT(priv->parent)); + priv->parent = NULL; + } +} + static void eek_element_finalize (GObject *object) { @@ -155,6 +190,8 @@ eek_element_class_init (EekElementClass *klass) g_type_class_add_private (gobject_class, sizeof (EekElementPrivate)); + klass->set_parent = eek_element_real_set_parent; + klass->get_parent = eek_element_real_get_parent; klass->set_name = eek_element_real_set_name; klass->get_name = eek_element_real_get_name; klass->set_bounds = eek_element_real_set_bounds; @@ -163,6 +200,7 @@ eek_element_class_init (EekElementClass *klass) gobject_class->set_property = eek_element_set_property; gobject_class->get_property = eek_element_get_property; gobject_class->finalize = eek_element_finalize; + gobject_class->dispose = eek_element_dispose; /** * EekElement:name: diff --git a/eek/eek-element.h b/eek/eek-element.h index e54494cf..a672d976 100644 --- a/eek/eek-element.h +++ b/eek/eek-element.h @@ -31,7 +31,6 @@ G_BEGIN_DECLS #define EEK_IS_ELEMENT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEK_TYPE_ELEMENT)) #define EEK_ELEMENT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEK_TYPE_ELEMENT, EekElementClass)) -typedef struct _EekElement EekElement; typedef struct _EekElementClass EekElementClass; typedef struct _EekElementPrivate EekElementPrivate; @@ -48,16 +47,19 @@ struct _EekElementClass /*< private >*/ GInitiallyUnownedClass parent_class; - void (* set_name) (EekElement *element, - const gchar *name); + void (* set_parent) (EekElement *self, + EekContainer *parent); + EekContainer *(* get_parent) (EekElement *self); + void (* set_name) (EekElement *self, + const gchar *name); - G_CONST_RETURN gchar *(* get_name) (EekElement *element); + G_CONST_RETURN gchar *(* get_name) (EekElement *self); - void (* set_bounds) (EekElement *element, - EekBounds *bounds); + void (* set_bounds) (EekElement *self, + EekBounds *bounds); - void (* get_bounds) (EekElement *element, - EekBounds *bounds); + void (* get_bounds) (EekElement *self, + EekBounds *bounds); }; GType eek_element_get_type (void) G_GNUC_CONST; diff --git a/eek/eek-gtk-keyboard.c b/eek/eek-gtk-keyboard.c new file mode 100644 index 00000000..73b4f97a --- /dev/null +++ b/eek/eek-gtk-keyboard.c @@ -0,0 +1,259 @@ +/* + * 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 + */ + +/** + * SECTION:eek-gtk-keyboard + * @short_description: #EekKeyboard embedding a #GtkActor + */ +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "eek-gtk-keyboard.h" +#include "eek-drawing.h" +#include "eek-keyboard.h" +#include "eek-section.h" +#include "eek-key.h" +#include "eek-keysym.h" + +G_DEFINE_TYPE (EekGtkKeyboard, eek_gtk_keyboard, EEK_TYPE_KEYBOARD); + +#define EEK_GTK_KEYBOARD_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEK_TYPE_GTK_KEYBOARD, EekGtkKeyboardPrivate)) + + +struct _EekGtkKeyboardPrivate +{ + GtkWidget *widget; /* GtkDrawingArea */ + GdkPixmap *pixmap; + GdkColor *dark_color; + cairo_t *cr; + PangoLayout *layout; + PangoFontDescription *fonts[EEK_KEYSYM_CATEGORY_LAST]; +}; + +static void +eek_gtk_keyboard_dispose (GObject *object) +{ + EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(object); + + if (priv->widget) { + g_object_unref (priv->widget); + priv->widget = NULL; + } + G_OBJECT_CLASS (eek_gtk_keyboard_parent_class)->dispose (object); +} + +static void +eek_gtk_keyboard_finalize (GObject *object) +{ + EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(object); + gint i; + + for (i = 0; i < EEK_KEYSYM_CATEGORY_LAST; i++) + pango_font_description_free (priv->fonts[i]); +} + +static void +eek_gtk_keyboard_class_init (EekGtkKeyboardClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (gobject_class, + sizeof (EekGtkKeyboardPrivate)); + + gobject_class->dispose = eek_gtk_keyboard_dispose; + gobject_class->finalize = eek_gtk_keyboard_finalize; +} + +static void +eek_gtk_keyboard_init (EekGtkKeyboard *self) +{ + EekGtkKeyboardPrivate *priv; + + priv = self->priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); + priv->widget = NULL; + priv->pixmap = NULL; + memset (priv->fonts, 0, sizeof priv->fonts); +} + +/** + * eek_gtk_keyboard_new: + * + * Create a new #EekGtkKeyboard. + */ +EekKeyboard* +eek_gtk_keyboard_new (void) +{ + return g_object_new (EEK_TYPE_GTK_KEYBOARD, NULL); +} + +static void +draw_key (EekElement *element, gpointer user_data) +{ + EekKeyboard *keyboard = user_data; + EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(keyboard); + EekKey *key = EEK_KEY(element); + EekOutline *outline; + EekBounds bounds; + guint keysym; + + gdk_cairo_set_source_color (priv->cr, priv->dark_color); + cairo_set_line_width (priv->cr, 1); + cairo_set_line_join (priv->cr, CAIRO_LINE_JOIN_ROUND); + + eek_element_get_bounds (EEK_ELEMENT(key), &bounds); + + cairo_save (priv->cr); + cairo_translate (priv->cr, bounds.x, bounds.y); + outline = eek_key_get_outline (key); + eek_draw_rounded_polygon (priv->cr, + FALSE, + outline->corner_radius, + outline->points, + outline->num_points); + cairo_stroke (priv->cr); + + keysym = eek_key_get_keysym (key); + if (keysym != EEK_INVALID_KEYSYM) { + const gchar *label = eek_keysym_to_string (keysym); + PangoRectangle logical_rect = { 0, }; + EekKeysymCategory category = eek_keysym_get_category (keysym); + + if (category != EEK_KEYSYM_CATEGORY_UNKNOWN && label) { + pango_layout_set_font_description (priv->layout, + priv->fonts[category]); + pango_layout_set_text (priv->layout, label, -1); + pango_layout_get_extents (priv->layout, NULL, &logical_rect); + + cairo_move_to + (priv->cr, + (bounds.width - logical_rect.width / PANGO_SCALE) / 2, + (bounds.height - logical_rect.height / PANGO_SCALE) / 2); + + pango_layout_set_width (priv->layout, PANGO_SCALE * bounds.width); + pango_layout_set_ellipsize (priv->layout, PANGO_ELLIPSIZE_END); + pango_cairo_show_layout (priv->cr, priv->layout); + } + } + cairo_restore (priv->cr); +} + +static void +draw_section (EekElement *element, gpointer user_data) +{ + EekKeyboard *keyboard = user_data; + EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(keyboard); + EekBounds bounds; + + gdk_cairo_set_source_color (priv->cr, priv->dark_color); + eek_element_get_bounds (element, &bounds); + + cairo_save (priv->cr); + cairo_translate (priv->cr, bounds.x, bounds.y); + +#if 0 + cairo_rectangle (priv->cr, 0, 0, bounds.width, bounds.height); + cairo_stroke (priv->cr); +#endif + + eek_container_foreach_child (EEK_CONTAINER(element), draw_key, + keyboard); + cairo_restore (priv->cr); +} + +static gboolean +on_gtk_expose_event (GtkWidget *widget, + GdkEventExpose *event, + gpointer user_data) +{ + EekGtkKeyboard *keyboard = user_data; + EekGtkKeyboardPrivate *priv = + EEK_GTK_KEYBOARD_GET_PRIVATE(keyboard); + GtkStateType state = gtk_widget_get_state (GTK_WIDGET (widget)); + + if (!priv->pixmap) { + GtkStateType state = gtk_widget_get_state (GTK_WIDGET (priv->widget)); + GtkAllocation allocation; + PangoContext *context; + PangoFontDescription *default_font_desc; + + context = gtk_widget_get_pango_context (GTK_WIDGET (priv->widget)); + priv->layout = pango_layout_new (context); + + /* compute font sizes */ + default_font_desc = + gtk_widget_get_style (GTK_WIDGET(priv->widget))->font_desc; + pango_layout_set_font_description (priv->layout, default_font_desc); + eek_get_fonts (EEK_KEYBOARD(keyboard), priv->layout, priv->fonts); + + /* create priv->pixmap */ + gtk_widget_set_double_buffered (GTK_WIDGET (priv->widget), FALSE); + gtk_widget_get_allocation (GTK_WIDGET (priv->widget), &allocation); + priv->pixmap = + gdk_pixmap_new (gtk_widget_get_window (GTK_WIDGET (priv->widget)), + allocation.width, allocation.height, -1); + + /* blank background */ + gdk_draw_rectangle + (priv->pixmap, + gtk_widget_get_style (GTK_WIDGET(priv->widget))->base_gc[state], + TRUE, + 0, 0, allocation.width, allocation.height); + + /* draw sections on the canvas */ + priv->cr = gdk_cairo_create (GDK_DRAWABLE (priv->pixmap)); + priv->dark_color = + >k_widget_get_style (GTK_WIDGET (priv->widget))->dark[state]; + + eek_container_foreach_child (EEK_CONTAINER(keyboard), draw_section, + keyboard); + + cairo_destroy (priv->cr); + priv->cr = NULL; + priv->dark_color = NULL; + } + + gdk_draw_drawable (gtk_widget_get_window (widget), + gtk_widget_get_style (widget)->fg_gc[state], + priv->pixmap, + event->area.x, event->area.y, + event->area.x, event->area.y, + event->area.width, event->area.height); + return TRUE; +} + +GtkWidget * +eek_gtk_keyboard_get_widget (EekGtkKeyboard *keyboard) +{ + EekGtkKeyboardPrivate *priv = + EEK_GTK_KEYBOARD_GET_PRIVATE(keyboard); + + if (!priv->widget) { + priv->widget = gtk_drawing_area_new (); + g_object_ref_sink (priv->widget); + g_signal_connect (priv->widget, "expose_event", + G_CALLBACK (on_gtk_expose_event), keyboard); + eek_keyboard_realize (EEK_KEYBOARD(keyboard)); + } + return priv->widget; +} diff --git a/eek/eek-gtk-keyboard.h b/eek/eek-gtk-keyboard.h new file mode 100644 index 00000000..2a24e471 --- /dev/null +++ b/eek/eek-gtk-keyboard.h @@ -0,0 +1,57 @@ +/* + * 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 + */ +#ifndef EEK_GTK_KEYBOARD_H +#define EEK_GTK_KEYBOARD_H 1 + +#include +#include "eek-keyboard.h" + +G_BEGIN_DECLS +#define EEK_TYPE_GTK_KEYBOARD (eek_gtk_keyboard_get_type()) +#define EEK_GTK_KEYBOARD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), EEK_TYPE_GTK_KEYBOARD, EekGtkKeyboard)) +#define EEK_GTK_KEYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), EEK_TYPE_GTK_KEYBOARD, EekGtkKeyboardClass)) +#define EEK_IS_GTK_KEYBOARD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), EEK_TYPE_GTK_KEYBOARD)) +#define EEK_IS_GTK_KEYBOARD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), EEK_TYPE_GTK_KEYBOARD)) +#define EEK_GTK_KEYBOARD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), EEK_TYPE_GTK_KEYBOARD, EekGtkKeyboardClass)) + +typedef struct _EekGtkKeyboard EekGtkKeyboard; +typedef struct _EekGtkKeyboardClass EekGtkKeyboardClass; +typedef struct _EekGtkKeyboardPrivate EekGtkKeyboardPrivate; + +struct _EekGtkKeyboard +{ + /*< private >*/ + EekKeyboard parent; + + EekGtkKeyboardPrivate *priv; +}; + +struct _EekGtkKeyboardClass +{ + /*< private >*/ + EekKeyboardClass parent_class; +}; + +GType eek_gtk_keyboard_get_type (void) G_GNUC_CONST; +EekKeyboard *eek_gtk_keyboard_new (void); +GtkWidget *eek_gtk_keyboard_get_widget (EekGtkKeyboard *keyboard); + +G_END_DECLS +#endif /* EEK_GTK_KEYBOARD_H */ diff --git a/eek/eek-gtk.h b/eek/eek-gtk.h new file mode 100644 index 00000000..327f2be2 --- /dev/null +++ b/eek/eek-gtk.h @@ -0,0 +1,26 @@ +/* + * 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 + */ +#ifndef EEK_GTK_H +#define EEK_GTK_H 1 + +#include "eek.h" +#include "eek-gtk-keyboard.h" + +#endif /* EEK_GTK_H */ diff --git a/eek/eek-keysym.h b/eek/eek-keysym.h index b6314cfe..dcadd773 100644 --- a/eek/eek-keysym.h +++ b/eek/eek-keysym.h @@ -17,8 +17,8 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA */ -#ifndef EEK_KEYSYMS_H -#define EEK_KEYSYMS_H 1 +#ifndef EEK_KEYSYM_H +#define EEK_KEYSYM_H 1 #define EEK_INVALID_KEYSYM ((guint)(-1)) #define EEK_INVALID_KEYCODE ((guint)(-1)) @@ -27,10 +27,11 @@ typedef enum { EEK_KEYSYM_CATEGORY_LETTER, EEK_KEYSYM_CATEGORY_FUNCTION, EEK_KEYSYM_CATEGORY_KEYNAME, - EEK_KEYSYM_CATEGORY_UNKNOWN + EEK_KEYSYM_CATEGORY_UNKNOWN, + EEK_KEYSYM_CATEGORY_LAST = EEK_KEYSYM_CATEGORY_UNKNOWN } EekKeysymCategory; G_CONST_RETURN gchar *eek_keysym_to_string (guint keysym); EekKeysymCategory eek_keysym_get_category (guint keysym); -#endif /* EEK_KEYSYMS_H */ +#endif /* EEK_KEYSYM_H */ diff --git a/eek/eek-types.h b/eek/eek-types.h index fb30bcfd..122692b8 100644 --- a/eek/eek-types.h +++ b/eek/eek-types.h @@ -30,6 +30,8 @@ typedef enum { EEK_ORIENTATION_INVALID = -1 } EekOrientation; +typedef struct _EekElement EekElement; +typedef struct _EekContainer EekContainer; typedef struct _EekKey EekKey; typedef struct _EekSection EekSection; typedef struct _EekKeyboard EekKeyboard; @@ -91,6 +93,12 @@ typedef struct _EekBounds EekBounds; #define EEK_TYPE_BOUNDS (eek_bounds_get_type ()) GType eek_bounds_get_type (void) G_GNUC_CONST; +G_INLINE_FUNC gdouble +eek_bounds_long_side (EekBounds *bounds) +{ + return bounds->width > bounds->height ? bounds->width : bounds->height; +} + /** * EekOutline: * @corner_radius: radius of corners of rounded polygon diff --git a/eek/eek.h b/eek/eek.h index d8170c8a..021eb0a7 100644 --- a/eek/eek.h +++ b/eek/eek.h @@ -21,6 +21,8 @@ #define EEK_H 1 #include "eek-keyboard.h" +#include "eek-section.h" +#include "eek-key.h" #include "eek-layout.h" #include "eek-keysym.h" diff --git a/src/Makefile.am b/src/Makefile.am index 148f1c69..76afeccf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,6 +16,9 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301 USA -bin_PROGRAMS = eekboard +bin_PROGRAMS = eekboard eekboard-gtk eekboard_CFLAGS = -I$(top_srcdir) $(GOBJECT2_CFLAGS) $(CLUTTER_CFLAGS) $(CLUTTER_GTK_CFLAGS) $(GTK2_CFLAGS) $(XKB_CFLAGS) $(LIBXKLAVIER_CFLAGS) $(LIBFAKEKEY_CFLAGS) eekboard_LDFLAGS = $(top_builddir)/eek/libeek.la $(top_builddir)/eek/libeek-xkl.la $(top_builddir)/eek/libeek-clutter.la $(GOBJECT2_LIBS) $(CLUTTER_LIBS) $(CLUTTER_GTK_LIBS) $(GTK2_CFLAGS) $(XKB_LIBS) $(LIBXKLAVIER_LIBS) $(LIBFAKEKEY_LIBS) + +eekboard_gtk_CFLAGS = -I$(top_srcdir) $(GOBJECT2_CFLAGS) $(GTK2_CFLAGS) $(XKB_CFLAGS) $(LIBXKLAVIER_CFLAGS) $(LIBFAKEKEY_CFLAGS) +eekboard_gtk_LDFLAGS = $(top_builddir)/eek/libeek.la $(top_builddir)/eek/libeek-xkl.la $(top_builddir)/eek/libeek-gtk.la $(GOBJECT2_LIBS) $(GTK2_CFLAGS) $(XKB_LIBS) $(LIBXKLAVIER_LIBS) $(LIBFAKEKEY_LIBS) diff --git a/src/eekboard-gtk.c b/src/eekboard-gtk.c new file mode 100644 index 00000000..ce2d618b --- /dev/null +++ b/src/eekboard-gtk.c @@ -0,0 +1,484 @@ +/* + * Copyright (C) 2010 Daiki Ueno + * Copyright (C) 2010 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 . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif /* HAVE_CONFIG_H */ + +#include "eek/eek-gtk.h" +#include "eek/eek-xkl.h" + +#define CSW 640 +#define CSH 480 + +#define LICENSE \ + "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." \ + "\n\n" \ + "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." \ + "\n\n" \ + "You should have received a copy of the GNU General Public License " \ + "along with this program. If not, see . " \ + +struct _EekBoard { + EekKeyboard *keyboard; + EekLayout *layout; /* FIXME: eek_keyboard_get_layout() */ + Display *display; + FakeKey *fakekey; + guint modifiers; + gfloat width, height; +}; +typedef struct _EekBoard EekBoard; + +static void on_about (GtkAction * action, GtkWidget *window); +static void on_monitor_key_event_toggled (GtkToggleAction *action, + GtkWidget *window); + +static const char ui_description[] = + "" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + ""; + +#define SET_LAYOUT_UI_PATH "/MainMenu/KeyboardMenu/SetLayout/LayoutsPH" + +struct _ConfigCallbackData { + EekBoard *eekboard; + XklConfigRec *config; +}; +typedef struct _ConfigCallbackData ConfigCallbackData; + +struct _LayoutCallbackData { + EekBoard *eekboard; + GtkUIManager *ui_manager; + GtkActionGroup *action_group; + guint merge_id; +}; +typedef struct _LayoutCallbackData LayoutCallbackData; + +static const GtkActionEntry action_entry[] = { + {"FileMenu", NULL, N_("_File")}, + {"KeyboardMenu", NULL, N_("_Keyboard")}, + {"HelpMenu", NULL, N_("_Help")}, + {"Quit", GTK_STOCK_QUIT, NULL, NULL, NULL, G_CALLBACK (gtk_main_quit)}, + {"SetLayout", NULL, N_("Set Layout"), NULL, NULL, NULL}, + {"About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK (on_about)} +}; + +static const GtkToggleActionEntry toggle_action_entry[] = { + {"MonitorKeyEvent", NULL, N_("Monitor Key Typing"), NULL, NULL, + G_CALLBACK(on_monitor_key_event_toggled), FALSE} +}; + +static void +on_about (GtkAction * action, GtkWidget *window) +{ + const gchar *authors[] = { "Daiki Ueno", NULL }; + + gtk_show_about_dialog (GTK_WINDOW (window), + "version", VERSION, + "copyright", + "Copyright \xc2\xa9 2010 Daiki Ueno\n" + "Copyright \xc2\xa9 2010 Red Hat, Inc.", + "license", LICENSE, + "comments", + _("A virtual keyboard for GNOME"), + "authors", authors, + "website", + "http://github.com/ueno/eek/", + "website-label", _("EekBoard web site"), + "wrap-license", TRUE, NULL); +} + +static void +on_monitor_key_event_toggled (GtkToggleAction *action, + GtkWidget *window) +{ + g_warning ("not implemented"); +} + +static void +on_key_pressed (EekKeyboard *keyboard, + EekKey *key, + gpointer user_data) +{ + EekBoard *eekboard = user_data; + guint keysym; + + keysym = eek_key_get_keysym (key); +#if DEBUG + g_debug ("%s %X", eek_keysym_to_string (keysym), eekboard->modifiers); +#endif + fakekey_press_keysym (eekboard->fakekey, keysym, eekboard->modifiers); + if (keysym == XK_Shift_L || keysym == XK_Shift_R) { + gint group, level; + + eekboard->modifiers ^= ShiftMask; + eek_keyboard_get_keysym_index (keyboard, &group, &level); + eek_keyboard_set_keysym_index (keyboard, group, + eekboard->modifiers & ShiftMask ? 1 : 0); + } else if (keysym == XK_Control_L || keysym == XK_Control_R) { + eekboard->modifiers ^= ControlMask; + } else if (keysym == XK_Alt_L || keysym == XK_Alt_R) { + eekboard->modifiers ^= Mod1Mask; + } +} + +static void +on_key_released (EekKeyboard *keyboard, + EekKey *key, + gpointer user_data) +{ + EekBoard *eekboard = user_data; + fakekey_release (eekboard->fakekey); +} + +static void +on_activate (GtkAction *action, gpointer user_data) +{ + ConfigCallbackData *data = user_data; + + eek_xkl_layout_set_config (EEK_XKL_LAYOUT(data->eekboard->layout), + data->config); + g_object_unref (data->config); +} + +#if 0 +static void +create_keyboard (EekBoard *eekboard, + ClutterActor *stage, + EekLayout *layout, + gfloat initial_width, + gfloat initial_height) +{ + ClutterActor *actor; + + eekboard->keyboard = eek_clutter_keyboard_new (initial_width, + initial_height); + g_signal_connect (eekboard->keyboard, "key-pressed", + G_CALLBACK(on_key_pressed), eekboard); + g_signal_connect (eekboard->keyboard, "key-released", + G_CALLBACK(on_key_released), eekboard); + eek_keyboard_set_layout (eekboard->keyboard, layout); + actor = eek_clutter_keyboard_get_actor + (EEK_CLUTTER_KEYBOARD(eekboard->keyboard)); + clutter_actor_get_size (actor, + &eekboard->width, + &eekboard->height); + clutter_container_add_actor (CLUTTER_CONTAINER(stage), actor); + clutter_actor_set_size (stage, + eekboard->width, + eekboard->height); +} + +/* FIXME: EekKeyboard should handle relayout by itself. */ +static void +on_changed (EekLayout *layout, gpointer user_data) +{ + EekBoard *eekboard = user_data; + ClutterActor *stage, *actor; + gfloat width, height; + + actor = eek_clutter_keyboard_get_actor + (EEK_CLUTTER_KEYBOARD(eekboard->keyboard)); + stage = clutter_actor_get_stage (actor); + clutter_actor_get_size (stage, &width, &height); + clutter_container_remove_actor (CLUTTER_CONTAINER(stage), actor); + g_object_unref (eekboard->keyboard); + create_keyboard (eekboard, stage, layout, width, height); + clutter_actor_get_size (stage, &eekboard->width, &eekboard->height); +} +#endif + +static void +variant_callback (XklConfigRegistry *registry, + const XklConfigItem *item, + gpointer user_data) +{ + GSList **r_variants = user_data; + XklConfigItem *_item; + + _item = g_slice_dup (XklConfigItem, item); + *r_variants = g_slist_prepend (*r_variants, _item); +} + +static void +layout_callback (XklConfigRegistry *registry, + const XklConfigItem *item, + gpointer user_data) +{ + LayoutCallbackData *data = user_data; + GtkAction *action; + GSList *variants = NULL; + char layout_action_name[128], variant_action_name[128]; + ConfigCallbackData *config; + + g_snprintf (layout_action_name, sizeof (layout_action_name), + "SetLayout%s", item->name); + action = gtk_action_new (layout_action_name, item->description, NULL, NULL); + gtk_action_group_add_action (data->action_group, action); + + xkl_config_registry_foreach_layout_variant (registry, + item->name, + variant_callback, + &variants); + + if (!variants) { + config = g_slice_new (ConfigCallbackData); + config->eekboard = data->eekboard; + config->config = xkl_config_rec_new (); + config->config->layouts = g_new0 (char *, 2); + config->config->layouts[0] = g_strdup (item->name); + config->config->layouts[1] = NULL; + config->config->variants = NULL; + g_signal_connect (action, "activate", G_CALLBACK (on_activate), + config); + g_object_unref (action); + gtk_ui_manager_add_ui (data->ui_manager, data->merge_id, + SET_LAYOUT_UI_PATH, + layout_action_name, layout_action_name, + GTK_UI_MANAGER_MENUITEM, FALSE); + } else { + char layout_path[128]; + GSList *head; + + g_object_unref (action); + gtk_ui_manager_add_ui (data->ui_manager, data->merge_id, + SET_LAYOUT_UI_PATH, + layout_action_name, layout_action_name, + GTK_UI_MANAGER_MENU, FALSE); + g_snprintf (layout_path, sizeof (layout_path), + SET_LAYOUT_UI_PATH "/%s", layout_action_name); + + for (head = variants; head; head = head->next) { + XklConfigItem *_item = head->data; + + g_snprintf (variant_action_name, sizeof (variant_action_name), + "SetVariant%s%s", item->name, _item->name); + action = gtk_action_new (variant_action_name, + _item->description, + NULL, + NULL); + + config = g_slice_new (ConfigCallbackData); + config->eekboard = data->eekboard; + config->config = xkl_config_rec_new (); + config->config->layouts = g_new0 (char *, 2); + config->config->layouts[0] = g_strdup (item->name); + config->config->layouts[1] = NULL; + config->config->variants = g_new0 (char *, 2); + config->config->variants[0] = g_strdup (_item->name); + config->config->variants[1] = NULL; + g_signal_connect (action, "activate", G_CALLBACK (on_activate), + config); + + gtk_action_group_add_action (data->action_group, action); + g_object_unref (action); + + gtk_ui_manager_add_ui (data->ui_manager, data->merge_id, + layout_path, + variant_action_name, variant_action_name, + GTK_UI_MANAGER_MENUITEM, FALSE); + g_slice_free (XklConfigItem, _item); + } + g_slist_free (variants); + } +} + +static void +create_layouts_menu (EekBoard *eekboard, GtkUIManager *ui_manager) +{ + XklEngine *engine; + XklConfigRegistry *registry; + LayoutCallbackData data; + + data.eekboard = eekboard; + data.ui_manager = ui_manager; + data.action_group = gtk_action_group_new ("Layouts"); + gtk_ui_manager_insert_action_group (data.ui_manager, data.action_group, -1); + data.merge_id = gtk_ui_manager_new_merge_id (ui_manager); + + engine = xkl_engine_get_instance (eekboard->display); + registry = xkl_config_registry_get_instance (engine); + xkl_config_registry_load (registry, FALSE); + + xkl_config_registry_foreach_layout (registry, layout_callback, &data); +} + +static void +create_menus (EekBoard *eekboard, + GtkWidget *window, + GtkUIManager * ui_manager) +{ + GtkActionGroup *action_group; + + action_group = gtk_action_group_new ("MenuActions"); + + gtk_action_group_add_actions (action_group, action_entry, + G_N_ELEMENTS (action_entry), window); + gtk_action_group_add_toggle_actions (action_group, toggle_action_entry, + G_N_ELEMENTS (toggle_action_entry), + window); + + gtk_ui_manager_insert_action_group (ui_manager, action_group, 0); + gtk_ui_manager_add_ui_from_string (ui_manager, ui_description, -1, NULL); + create_layouts_menu (eekboard, ui_manager); +} + +#if 0 +static void +on_resize (GObject *object, + GParamSpec *param_spec, + gpointer user_data) +{ + EekBoard *eekboard = user_data; + GValue value = {0}; + gfloat width, height, scale; + ClutterActor *stage, *actor; + + actor = eek_clutter_keyboard_get_actor + (EEK_CLUTTER_KEYBOARD(eekboard->keyboard)); + stage = clutter_actor_get_stage (actor); + + g_object_get (G_OBJECT(stage), "width", &width, NULL); + g_object_get (G_OBJECT(stage), "height", &height, NULL); + + g_value_init (&value, G_TYPE_DOUBLE); + + scale = width > height ? width / eekboard->width : + width / eekboard->height; + + g_value_set_double (&value, scale); + g_object_set_property (G_OBJECT (stage), + "scale-x", + &value); + + g_value_set_double (&value, scale); + g_object_set_property (G_OBJECT (stage), + "scale-y", + &value); +} +#endif + +int +main (int argc, char *argv[]) +{ + EekBoard eekboard; + GtkWidget *menubar, *embed, *vbox, *window; + GtkUIManager *ui_manager; + EekBounds bounds; + + if (!gtk_init_check (&argc, &argv)) { + fprintf (stderr, "Can't init Clutter-Gtk\n"); + exit (1); + } + + memset (&eekboard, 0, sizeof eekboard); + eekboard.display = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ()); + if (!eekboard.display) { + fprintf (stderr, "Can't open display\n"); + exit (1); + } + + eekboard.fakekey = fakekey_init (eekboard.display); + if (!eekboard.fakekey) { + fprintf (stderr, "Can't init fakekey\n"); + exit (1); + } + + eekboard.layout = eek_xkl_layout_new (); + if (!eekboard.layout) { + fprintf (stderr, "Failed to create layout\n"); + exit (1); + } + +#if 0 + g_signal_connect (eekboard.layout, + "changed", + G_CALLBACK(on_changed), + &eekboard); + + g_signal_connect (stage, + "notify::width", + G_CALLBACK (on_resize), + &eekboard); + + g_signal_connect (stage, + "notify::height", + G_CALLBACK (on_resize), + &eekboard); +#endif + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_widget_set_can_focus (window, FALSE); + g_object_set (G_OBJECT(window), "accept_focus", FALSE, NULL); + gtk_window_set_title (GTK_WINDOW(window), "Keyboard"); + g_signal_connect (G_OBJECT (window), "destroy", + G_CALLBACK (gtk_main_quit), NULL); + + bounds.x = bounds.y = 0; + bounds.width = CSW; + bounds.height = CSH; + eekboard.keyboard = eek_gtk_keyboard_new (); + eek_element_set_bounds (eekboard.keyboard, &bounds); + eek_keyboard_set_layout (eekboard.keyboard, eekboard.layout); + embed = eek_gtk_keyboard_get_widget (EEK_GTK_KEYBOARD (eekboard.keyboard)); + + vbox = gtk_vbox_new (FALSE, 0); + ui_manager = gtk_ui_manager_new (); + create_menus (&eekboard, window, ui_manager); + menubar = gtk_ui_manager_get_widget (ui_manager, "/MainMenu"); + gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, FALSE, 0); + gtk_container_add (GTK_CONTAINER(vbox), embed); + gtk_container_add (GTK_CONTAINER(window), vbox); + + gtk_widget_set_size_request (embed, CSW, CSH); + gtk_widget_show_all (window); + //gtk_widget_set_size_request (embed, -1, -1); + + gtk_main (); + + return 0; +}