/* * Copyright (C) 2010-2011 Daiki Ueno * Copyright (C) 2010-2011 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: a #GtkWidget displaying #EekKeyboard */ #ifdef HAVE_CONFIG_H #include "config.h" #endif /* HAVE_CONFIG_H */ #ifdef HAVE_LIBCANBERRA #include #endif #include #include "eek-gtk-keyboard.h" #include "eek-gtk-renderer.h" #include "eek-keyboard.h" #include "eek-section.h" #include "eek-key.h" #include "eek-symbol.h" enum { PROP_0, PROP_KEYBOARD, PROP_LAST }; G_DEFINE_TYPE (EekGtkKeyboard, eek_gtk_keyboard, GTK_TYPE_DRAWING_AREA); #define EEK_GTK_KEYBOARD_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE ((obj), EEK_TYPE_GTK_KEYBOARD, EekGtkKeyboardPrivate)) /* since 2.91.5 GDK_DRAWABLE was removed and gdk_cairo_create takes GdkWindow as the argument */ #ifndef GDK_DRAWABLE #define GDK_DRAWABLE(x) (x) #endif struct _EekGtkKeyboardPrivate { EekRenderer *renderer; EekKeyboard *keyboard; gulong key_pressed_handler; gulong key_released_handler; gulong key_locked_handler; gulong key_unlocked_handler; gulong key_cancelled_handler; gulong symbol_index_changed_handler; EekTheme *theme; }; static EekColor * color_from_gdk_color (GdkColor *gdk_color); static void on_key_pressed (EekKeyboard *keyboard, EekKey *key, guint32 timestamp, gpointer user_data); static void on_key_released (EekKeyboard *keyboard, EekKey *key, gpointer user_data); static void on_key_locked (EekKeyboard *keyboard, EekKey *key, gpointer user_data); static void on_key_unlocked (EekKeyboard *keyboard, EekKey *key, gpointer user_data); static void on_key_cancelled (EekKeyboard *keyboard, EekKey *key, gpointer user_data); static void on_symbol_index_changed (EekKeyboard *keyboard, gint group, gint level, gpointer user_data); static void render_pressed_key (GtkWidget *widget, EekKey *key); static void render_locked_key (GtkWidget *widget, EekKey *key); static void render_released_key (GtkWidget *widget, EekKey *key); static void eek_gtk_keyboard_real_realize (GtkWidget *self) { gtk_widget_set_double_buffered (self, FALSE); gtk_widget_set_events (self, GDK_EXPOSURE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_MOTION_MASK); GTK_WIDGET_CLASS (eek_gtk_keyboard_parent_class)->realize (self); } static gboolean eek_gtk_keyboard_real_draw (GtkWidget *self, cairo_t *cr) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); GtkAllocation allocation; EekColor background; GList *list, *head; gtk_widget_get_allocation (self, &allocation); if (!priv->renderer) { GtkStyle *style; GtkStateType state; PangoContext *pcontext; EekColor *color; pcontext = gtk_widget_get_pango_context (self); priv->renderer = eek_gtk_renderer_new (priv->keyboard, pcontext, self); if (priv->theme) eek_renderer_set_theme (priv->renderer, priv->theme); eek_renderer_set_allocation_size (priv->renderer, allocation.width, allocation.height); style = gtk_widget_get_style (self); state = gtk_widget_get_state (self); color = color_from_gdk_color (&style->text[state]); eek_renderer_set_default_foreground_color (priv->renderer, color); eek_color_free (color); color = color_from_gdk_color (&style->base[state]); eek_renderer_set_default_background_color (priv->renderer, color); eek_color_free (color); } /* blank background */ eek_renderer_get_background_color (priv->renderer, EEK_ELEMENT(priv->keyboard), &background); cairo_set_source_rgba (cr, background.red, background.green, background.blue, background.alpha); cairo_paint (cr); eek_renderer_render_keyboard (priv->renderer, cr); /* redraw pressed key */ list = eek_keyboard_get_pressed_keys (priv->keyboard); for (head = list; head; head = g_list_next (head)) { render_pressed_key (self, head->data); } g_list_free (list); /* redraw locked key */ list = eek_keyboard_get_locked_keys (priv->keyboard); for (head = list; head; head = g_list_next (head)) { render_locked_key (self, ((EekModifierKey *)head->data)->key); } g_list_free (list); return FALSE; } static void eek_gtk_keyboard_real_size_allocate (GtkWidget *self, GtkAllocation *allocation) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); if (priv->renderer) eek_renderer_set_allocation_size (priv->renderer, allocation->width, allocation->height); GTK_WIDGET_CLASS (eek_gtk_keyboard_parent_class)-> size_allocate (self, allocation); } static gboolean eek_gtk_keyboard_real_button_press_event (GtkWidget *self, GdkEventButton *event) { if (event->type != GDK_BUTTON_PRESS || event->button != 1) { return TRUE; } EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); EekKey *key; key = eek_renderer_find_key_by_position (priv->renderer, (gdouble)event->x, (gdouble)event->y); if (key) { g_log("squeek", G_LOG_LEVEL_DEBUG, "emit EekKey pressed"); g_signal_emit_by_name (key, "pressed"); // TODO: set the pressed property on the key instead eek_keyboard_press_key(priv->keyboard, key, event->time); } // TODO: send time return TRUE; } static gboolean eek_gtk_keyboard_real_button_release_event (GtkWidget *self, GdkEventButton *event) { if (event->type != GDK_BUTTON_RELEASE || event->button != 1) { return TRUE; } EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); GList *list, *head; list = eek_keyboard_get_pressed_keys (priv->keyboard); for (head = list; head; head = g_list_next (head)) { g_log("squeek", G_LOG_LEVEL_DEBUG, "emit EekKey released"); g_signal_emit_by_name (head->data, "released", priv->keyboard); } g_list_free (list); return TRUE; } static gboolean eek_gtk_keyboard_real_motion_notify_event (GtkWidget *self, GdkEventMotion *event) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); EekKey *key; if (event->state == 0) return FALSE; key = eek_renderer_find_key_by_position (priv->renderer, (gdouble)event->x, (gdouble)event->y); if (key) { GList *list, *head; gboolean found = FALSE; list = eek_keyboard_get_pressed_keys (priv->keyboard); for (head = list; head; head = g_list_next (head)) { if (head->data == key) found = TRUE; else g_signal_emit_by_name (head->data, "cancelled"); } g_list_free (list); if (!found) { g_log("squeek", G_LOG_LEVEL_DEBUG, "emit EekKey pressed"); g_signal_emit_by_name (key, "pressed"); } } return TRUE; } static void eek_gtk_keyboard_real_unmap (GtkWidget *self) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); if (priv->keyboard) { GList *list, *head; /* Make a copy of HEAD before sending "released" signal on elements, so that the default handler of EekKeyboard::key-released signal can remove elements from its internal copy */ list = eek_keyboard_get_pressed_keys (priv->keyboard); for (head = list; head; head = g_list_next (head)) { g_log("squeek", G_LOG_LEVEL_DEBUG, "emit EekKey released"); g_signal_emit_by_name (head->data, "released"); } g_list_free (list); } GTK_WIDGET_CLASS (eek_gtk_keyboard_parent_class)->unmap (self); } static gboolean eek_gtk_keyboard_real_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_tooltip, GtkTooltip *tooltip) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); EekKey *key; key = eek_renderer_find_key_by_position (priv->renderer, (gdouble)x, (gdouble)y); if (key) { EekSymbol *symbol = eek_key_get_symbol (key); const gchar *text = eek_symbol_get_tooltip (symbol); if (text) { gtk_tooltip_set_text (tooltip, text); return TRUE; } } return FALSE; } static void eek_gtk_keyboard_set_keyboard (EekGtkKeyboard *self, EekKeyboard *keyboard) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); priv->keyboard = g_object_ref (keyboard); priv->key_pressed_handler = g_signal_connect (priv->keyboard, "key-pressed", G_CALLBACK(on_key_pressed), self); priv->key_released_handler = g_signal_connect (priv->keyboard, "key-released", G_CALLBACK(on_key_released), self); priv->key_locked_handler = g_signal_connect (priv->keyboard, "key-locked", G_CALLBACK(on_key_locked), self); priv->key_unlocked_handler = g_signal_connect (priv->keyboard, "key-unlocked", G_CALLBACK(on_key_unlocked), self); priv->key_cancelled_handler = g_signal_connect (priv->keyboard, "key-cancelled", G_CALLBACK(on_key_cancelled), self); priv->symbol_index_changed_handler = g_signal_connect (priv->keyboard, "symbol-index-changed", G_CALLBACK(on_symbol_index_changed), self); } static void eek_gtk_keyboard_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { EekKeyboard *keyboard; switch (prop_id) { case PROP_KEYBOARD: keyboard = g_value_get_object (value); eek_gtk_keyboard_set_keyboard (EEK_GTK_KEYBOARD(object), keyboard); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void eek_gtk_keyboard_dispose (GObject *object) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(object); if (priv->renderer) { g_object_unref (priv->renderer); priv->renderer = NULL; } if (priv->keyboard) { if (g_signal_handler_is_connected (priv->keyboard, priv->key_pressed_handler)) g_signal_handler_disconnect (priv->keyboard, priv->key_pressed_handler); if (g_signal_handler_is_connected (priv->keyboard, priv->key_released_handler)) g_signal_handler_disconnect (priv->keyboard, priv->key_released_handler); if (g_signal_handler_is_connected (priv->keyboard, priv->key_locked_handler)) g_signal_handler_disconnect (priv->keyboard, priv->key_locked_handler); if (g_signal_handler_is_connected (priv->keyboard, priv->key_unlocked_handler)) g_signal_handler_disconnect (priv->keyboard, priv->key_unlocked_handler); if (g_signal_handler_is_connected (priv->keyboard, priv->key_cancelled_handler)) g_signal_handler_disconnect (priv->keyboard, priv->key_cancelled_handler); if (g_signal_handler_is_connected (priv->keyboard, priv->symbol_index_changed_handler)) g_signal_handler_disconnect (priv->keyboard, priv->symbol_index_changed_handler); GList *list, *head; list = eek_keyboard_get_pressed_keys (priv->keyboard); for (head = list; head; head = g_list_next (head)) { g_log("squeek", G_LOG_LEVEL_DEBUG, "emit EekKey pressed"); g_signal_emit_by_name (head->data, "released", priv->keyboard); } g_list_free (list); g_object_unref (priv->keyboard); priv->keyboard = NULL; } if (priv->theme) { g_object_unref (priv->theme); priv->theme = NULL; } G_OBJECT_CLASS (eek_gtk_keyboard_parent_class)->dispose (object); } static void eek_gtk_keyboard_class_init (EekGtkKeyboardClass *klass) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); GObjectClass *gobject_class = G_OBJECT_CLASS (klass); GParamSpec *pspec; g_type_class_add_private (gobject_class, sizeof (EekGtkKeyboardPrivate)); widget_class->realize = eek_gtk_keyboard_real_realize; widget_class->unmap = eek_gtk_keyboard_real_unmap; widget_class->draw = eek_gtk_keyboard_real_draw; widget_class->size_allocate = eek_gtk_keyboard_real_size_allocate; widget_class->button_press_event = eek_gtk_keyboard_real_button_press_event; widget_class->button_release_event = eek_gtk_keyboard_real_button_release_event; widget_class->motion_notify_event = eek_gtk_keyboard_real_motion_notify_event; widget_class->query_tooltip = eek_gtk_keyboard_real_query_tooltip; gobject_class->set_property = eek_gtk_keyboard_set_property; gobject_class->dispose = eek_gtk_keyboard_dispose; pspec = g_param_spec_object ("keyboard", "Keyboard", "Keyboard", EEK_TYPE_KEYBOARD, G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE); g_object_class_install_property (gobject_class, PROP_KEYBOARD, pspec); } static void eek_gtk_keyboard_init (EekGtkKeyboard *self) { self->priv = EEK_GTK_KEYBOARD_GET_PRIVATE(self); } /** * eek_gtk_keyboard_new: * @keyboard: an #EekKeyboard * * Create a new #GtkWidget displaying @keyboard. * Returns: a #GtkWidget */ GtkWidget * eek_gtk_keyboard_new (EekKeyboard *keyboard) { return g_object_new (EEK_TYPE_GTK_KEYBOARD, "keyboard", keyboard, NULL); } static EekColor * color_from_gdk_color (GdkColor *gdk_color) { return eek_color_new (gdk_color->red / (gdouble)0xFFFF, gdk_color->green / (gdouble)0xFFFF, gdk_color->blue / (gdouble)0xFFFF, 1.0); } static void magnify_bounds (GtkWidget *self, EekBounds *bounds, EekBounds *large_bounds, gdouble scale) { GtkAllocation allocation; gdouble x, y; g_assert (scale >= 1.0); gtk_widget_get_allocation (self, &allocation); large_bounds->width = bounds->width * scale; large_bounds->height = bounds->height * scale; x = bounds->x - (large_bounds->width - bounds->width) / 2; y = bounds->y - large_bounds->height; large_bounds->x = CLAMP(x, 0, allocation.width - large_bounds->width); large_bounds->y = CLAMP(y, 0, allocation.height - large_bounds->height); } static void render_pressed_key (GtkWidget *widget, EekKey *key) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); EekBounds bounds, large_bounds; cairo_t *cr; cr = gdk_cairo_create (GDK_DRAWABLE (gtk_widget_get_window (widget))); eek_renderer_get_key_bounds (priv->renderer, key, &bounds, TRUE); magnify_bounds (widget, &bounds, &large_bounds, 1.5); cairo_save (cr); cairo_translate (cr, bounds.x, bounds.y); eek_renderer_render_key (priv->renderer, cr, key, 1.0, TRUE); cairo_restore (cr); cairo_save (cr); cairo_translate (cr, large_bounds.x, large_bounds.y); eek_renderer_render_key (priv->renderer, cr, key, 1.5, TRUE); cairo_restore (cr); cairo_destroy (cr); } static void render_locked_key (GtkWidget *widget, EekKey *key) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); EekBounds bounds; cairo_t *cr; cr = gdk_cairo_create (GDK_DRAWABLE (gtk_widget_get_window (widget))); eek_renderer_get_key_bounds (priv->renderer, key, &bounds, TRUE); cairo_translate (cr, bounds.x, bounds.y); eek_renderer_render_key (priv->renderer, cr, key, 1.0, TRUE); cairo_destroy (cr); } static void render_released_key (GtkWidget *widget, EekKey *key) { EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); EekBounds bounds, large_bounds; cairo_t *cr; cr = gdk_cairo_create (GDK_DRAWABLE (gtk_widget_get_window (widget))); eek_renderer_get_key_bounds (priv->renderer, key, &bounds, TRUE); magnify_bounds (widget, &bounds, &large_bounds, 2.0); cairo_rectangle (cr, large_bounds.x, large_bounds.y, large_bounds.width, large_bounds.height); cairo_rectangle (cr, bounds.x, bounds.y, bounds.width, bounds.height); cairo_clip (cr); eek_renderer_render_keyboard (priv->renderer, cr); cairo_destroy (cr); } static void on_key_pressed (EekKeyboard *keyboard, EekKey *key, guint32 timestamp, gpointer user_data) { (void)keyboard; (void)timestamp; GtkWidget *widget = user_data; EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); /* renderer may have not been set yet if the widget is a popup */ if (!priv->renderer) return; render_pressed_key (widget, key); #if HAVE_LIBCANBERRA ca_gtk_play_for_widget (widget, 0, CA_PROP_EVENT_ID, "button-pressed", CA_PROP_EVENT_DESCRIPTION, "virtual key pressed", CA_PROP_APPLICATION_ID, "org.fedorahosted.Eekboard", NULL); #endif } static void on_key_released (EekKeyboard *keyboard, EekKey *key, gpointer user_data) { GtkWidget *widget = user_data; EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); /* renderer may have not been set yet if the widget is a popup */ if (!priv->renderer) return; render_released_key (widget, key); #if HAVE_LIBCANBERRA ca_gtk_play_for_widget (widget, 0, CA_PROP_EVENT_ID, "button-released", CA_PROP_EVENT_DESCRIPTION, "virtual key pressed", CA_PROP_APPLICATION_ID, "org.fedorahosted.Eekboard", NULL); #endif } static void on_key_cancelled (EekKeyboard *keyboard, EekKey *key, gpointer user_data) { GtkWidget *widget = user_data; EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); /* renderer may have not been set yet if the widget is a popup */ if (!priv->renderer) return; render_released_key (widget, key); } static void on_key_locked (EekKeyboard *keyboard, EekKey *key, gpointer user_data) { GtkWidget *widget = user_data; EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); /* renderer may have not been set yet if the widget is a popup */ if (!priv->renderer) return; render_locked_key (widget, key); } static void on_key_unlocked (EekKeyboard *keyboard, EekKey *key, gpointer user_data) { GtkWidget *widget = user_data; EekGtkKeyboardPrivate *priv = EEK_GTK_KEYBOARD_GET_PRIVATE(widget); /* renderer may have not been set yet if the widget is a popup */ if (!priv->renderer) return; render_released_key (widget, key); } static void on_symbol_index_changed (EekKeyboard *keyboard, gint group, gint level, gpointer user_data) { GtkWidget *widget = user_data; gtk_widget_queue_draw (widget); } void eek_gtk_keyboard_set_theme (EekGtkKeyboard *keyboard, EekTheme *theme) { EekGtkKeyboardPrivate *priv; g_return_if_fail (EEK_IS_GTK_KEYBOARD(keyboard)); g_return_if_fail (EEK_IS_THEME(theme)); priv = EEK_GTK_KEYBOARD_GET_PRIVATE(keyboard); priv->theme = g_object_ref (theme); }