/* * 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 */ #include "config.h" #include #include #include "eek-renderer.h" #include "eek-keyboard.h" #include "eek-gtk-keyboard.h" #include "eekboard/eekboard-context-service.h" #include "src/layout.h" #include "src/submission.h" #define LIBFEEDBACK_USE_UNSTABLE_API #include #define SQUEEKBOARD_APP_ID "sm.puri.squeekboard" typedef struct _EekGtkKeyboardPrivate { EekRenderer *renderer; // owned, nullable struct render_geometry render_geometry; // mutable EekboardContextService *eekboard_context; // unowned reference struct submission *submission; // unowned reference struct squeek_layout_state *layout; // unowned LevelKeyboard *keyboard; // unowned reference; it's kept in server-context GdkEventSequence *sequence; // unowned reference LfbEvent *event; } EekGtkKeyboardPrivate; G_DEFINE_TYPE_WITH_PRIVATE (EekGtkKeyboard, eek_gtk_keyboard, GTK_TYPE_DRAWING_AREA) static void eek_gtk_keyboard_real_realize (GtkWidget *self) { 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 | GDK_TOUCH_MASK); GTK_WIDGET_CLASS (eek_gtk_keyboard_parent_class)->realize (self); } static void set_allocation_size(EekGtkKeyboard *gtk_keyboard, struct squeek_layout *layout, gdouble width, gdouble height) { // This is where size-dependent surfaces would be released EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (gtk_keyboard); priv->render_geometry = eek_render_geometry_from_allocation_size( layout, width, height); } static gboolean eek_gtk_keyboard_real_draw (GtkWidget *self, cairo_t *cr) { EekGtkKeyboard *keyboard = EEK_GTK_KEYBOARD (self); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (keyboard); GtkAllocation allocation; gtk_widget_get_allocation (self, &allocation); if (!priv->keyboard) { return FALSE; } if (!priv->renderer) { PangoContext *pcontext = gtk_widget_get_pango_context (self); priv->renderer = eek_renderer_new ( priv->keyboard, pcontext); set_allocation_size (keyboard, priv->keyboard->layout, allocation.width, allocation.height); eek_renderer_set_scale_factor (priv->renderer, gtk_widget_get_scale_factor (self)); } eek_renderer_render_keyboard (priv->renderer, priv->render_geometry, priv->submission, cr, priv->keyboard); return FALSE; } // Units of virtual pixels size static enum squeek_arrangement_kind get_type(uint32_t width, uint32_t height) { (void)height; if (width < 540) { return ARRANGEMENT_KIND_BASE; } return ARRANGEMENT_KIND_WIDE; } static void eek_gtk_keyboard_real_size_allocate (GtkWidget *self, GtkAllocation *allocation) { EekGtkKeyboard *keyboard = EEK_GTK_KEYBOARD (self); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (keyboard); // check if the change would switch types enum squeek_arrangement_kind new_type = get_type( (uint32_t)(allocation->width - allocation->x), (uint32_t)(allocation->height - allocation->y)); if (priv->layout->arrangement != new_type) { priv->layout->arrangement = new_type; uint32_t time = gdk_event_get_time(NULL); eekboard_context_service_use_layout(priv->eekboard_context, priv->layout, time); } if (priv->renderer) { set_allocation_size (keyboard, priv->keyboard->layout, allocation->width, allocation->height); } GTK_WIDGET_CLASS (eek_gtk_keyboard_parent_class)-> size_allocate (self, allocation); } static void on_event_triggered (LfbEvent *event, GAsyncResult *res, gpointer unused) { g_autoptr (GError) err = NULL; if (!lfb_event_trigger_feedback_finish (event, res, &err)) { g_warning ("Failed to trigger feedback for '%s': %s", lfb_event_get_event (event), err->message); } } static void depress(EekGtkKeyboard *self, gdouble x, gdouble y, guint32 time) { EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); if (!priv->keyboard) { return; } squeek_layout_depress(priv->keyboard->layout, priv->submission, x, y, priv->render_geometry.widget_to_layout, time, self); } static void drag(EekGtkKeyboard *self, gdouble x, gdouble y, guint32 time) { EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); if (!priv->keyboard) { return; } squeek_layout_drag(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout, priv->submission, x, y, priv->render_geometry.widget_to_layout, time, priv->eekboard_context, self); } static void release(EekGtkKeyboard *self, guint32 time) { EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); if (!priv->keyboard) { return; } squeek_layout_release(eekboard_context_service_get_keyboard(priv->eekboard_context)->layout, priv->submission, priv->render_geometry.widget_to_layout, time, priv->eekboard_context, self); } static gboolean eek_gtk_keyboard_real_button_press_event (GtkWidget *self, GdkEventButton *event) { if (event->type == GDK_BUTTON_PRESS && event->button == 1) { depress(EEK_GTK_KEYBOARD(self), event->x, event->y, event->time); } return TRUE; } // TODO: this belongs more in gtk_keyboard, with a way to find out which key to re-render static gboolean eek_gtk_keyboard_real_button_release_event (GtkWidget *self, GdkEventButton *event) { if (event->type == GDK_BUTTON_RELEASE && event->button == 1) { // TODO: can the event have different coords than the previous move event? release(EEK_GTK_KEYBOARD(self), event->time); } return TRUE; } static gboolean eek_gtk_keyboard_leave_event (GtkWidget *self, GdkEventCrossing *event) { if (event->type == GDK_LEAVE_NOTIFY) { // TODO: can the event have different coords than the previous move event? release(EEK_GTK_KEYBOARD(self), event->time); } return TRUE; } static gboolean eek_gtk_keyboard_real_motion_notify_event (GtkWidget *self, GdkEventMotion *event) { if (event->state & GDK_BUTTON1_MASK) { drag(EEK_GTK_KEYBOARD(self), event->x, event->y, event->time); } return TRUE; } // Only one touch stream at a time allowed. Others will be completely ignored. static gboolean handle_touch_event (GtkWidget *widget, GdkEventTouch *event) { EekGtkKeyboard *self = EEK_GTK_KEYBOARD (widget); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); /* For each new touch, release the previous one and record the new event sequence. */ if (event->type == GDK_TOUCH_BEGIN) { release(self, event->time); priv->sequence = event->sequence; depress(self, event->x, event->y, event->time); return TRUE; } /* Only allow the latest touch point to be dragged. */ if (event->type == GDK_TOUCH_UPDATE && event->sequence == priv->sequence) { drag(self, event->x, event->y, event->time); } else if (event->type == GDK_TOUCH_END || event->type == GDK_TOUCH_CANCEL) { // TODO: can the event have different coords than the previous update event? /* Only respond to the release of the latest touch point. Previous touches have already been released. */ if (event->sequence == priv->sequence) { release(self, event->time); priv->sequence = NULL; } } return TRUE; } static void eek_gtk_keyboard_real_unmap (GtkWidget *self) { EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (EEK_GTK_KEYBOARD (self)); if (priv->keyboard) { squeek_layout_release_all_only( priv->keyboard->layout, priv->submission, gdk_event_get_time(NULL)); } GTK_WIDGET_CLASS (eek_gtk_keyboard_parent_class)->unmap (self); } static void eek_gtk_keyboard_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { (void)value; switch (prop_id) { default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void eek_gtk_keyboard_dispose (GObject *object) { EekGtkKeyboard *self = EEK_GTK_KEYBOARD (object); EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (self); if (priv->renderer) { eek_renderer_free(priv->renderer); priv->renderer = NULL; } if (priv->keyboard) { squeek_layout_release_all_only( priv->keyboard->layout, priv->submission, gdk_event_get_time(NULL)); priv->keyboard = NULL; } if (priv->event) { g_clear_object (&priv->event); lfb_uninit (); } 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); 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->leave_notify_event = eek_gtk_keyboard_leave_event; widget_class->touch_event = handle_touch_event; gobject_class->set_property = eek_gtk_keyboard_set_property; gobject_class->dispose = eek_gtk_keyboard_dispose; } static void eek_gtk_keyboard_init (EekGtkKeyboard *self) { EekGtkKeyboardPrivate *priv = eek_gtk_keyboard_get_instance_private (EEK_GTK_KEYBOARD (self)); g_autoptr(GError) err = NULL; if (lfb_init(SQUEEKBOARD_APP_ID, &err)) { priv->event = lfb_event_new ("button-pressed"); } else { g_warning ("Failed to init libfeedback: %s", err->message); } GtkIconTheme *theme = gtk_icon_theme_get_default (); gtk_icon_theme_add_resource_path (theme, "/sm/puri/squeekboard/icons"); } static void on_notify_keyboard (GObject *object, GParamSpec *spec, EekGtkKeyboard *self) { (void)spec; EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (self); priv->keyboard = eekboard_context_service_get_keyboard(EEKBOARD_CONTEXT_SERVICE(object)); if (priv->renderer) { eek_renderer_free(priv->renderer); } priv->renderer = NULL; gtk_widget_queue_draw(GTK_WIDGET(self)); } /** * Create a new #GtkWidget displaying @keyboard. * Returns: a #GtkWidget */ GtkWidget * eek_gtk_keyboard_new (EekboardContextService *eekservice, struct submission *submission, struct squeek_layout_state *layout) { EekGtkKeyboard *ret = EEK_GTK_KEYBOARD(g_object_new (EEK_TYPE_GTK_KEYBOARD, NULL)); EekGtkKeyboardPrivate *priv = (EekGtkKeyboardPrivate*)eek_gtk_keyboard_get_instance_private (ret); priv->eekboard_context = eekservice; priv->submission = submission; priv->layout = layout; priv->renderer = NULL; // This should really be done on initialization. // Before the widget is allocated, // we don't really know what geometry it takes. // When it's off the screen, we also kinda don't. struct render_geometry initial_geometry = { // Set to 100 just to make sure if there's any attempt to use it, // it actually gives plausible results instead of blowing up, // e.g. on zero division. .allocation_width = 100, .allocation_height = 100, .widget_to_layout = { .origin_x = 0, .origin_y = 0, .scale_x = 1, .scale_y = 1, }, }; priv->render_geometry = initial_geometry; g_signal_connect (eekservice, "notify::keyboard", G_CALLBACK(on_notify_keyboard), ret); on_notify_keyboard(G_OBJECT(eekservice), NULL, ret); /* TODO: this is how a compound keyboard * made out of a layout and a suggestion bar could start. * GtkBox *box = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)); GtkEntry *fill = GTK_ENTRY(gtk_entry_new()); gtk_box_pack_start(box, GTK_WIDGET(fill), FALSE, FALSE, 0); gtk_box_pack_start(box, GTK_WIDGET(ret), TRUE, TRUE, 0); return GTK_WIDGET(box);*/ return GTK_WIDGET(ret); } /** * eek_gtk_keyboard_emit_feedback: * * Emit button press haptic feedback via libfeedack. */ void eek_gtk_keyboard_emit_feedback (EekGtkKeyboard *self) { EekGtkKeyboardPrivate *priv; g_return_if_fail (EEK_IS_GTK_KEYBOARD (self)); priv = eek_gtk_keyboard_get_instance_private (EEK_GTK_KEYBOARD (self)); if (priv->event) { lfb_event_trigger_feedback_async (priv->event, NULL, (GAsyncReadyCallback)on_event_triggered, NULL); } }