474 lines
16 KiB
C
474 lines
16 KiB
C
/*
|
|
* Copyright (C) 2010-2011 Daiki Ueno <ueno@unixuser.org>
|
|
* 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 <math.h>
|
|
#include <string.h>
|
|
|
|
#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 <libfeedback.h>
|
|
|
|
#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 squeek_state_manager *state_manager; // shared 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;
|
|
|
|
gulong kb_signal;
|
|
} 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)
|
|
{
|
|
(void)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, priv->state_manager, 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, priv->state_manager, 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;
|
|
}
|
|
}
|
|
|
|
// This may actually get called multiple times in a row
|
|
// if both a parent object and its parent get destroyed
|
|
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->kb_signal != 0) {
|
|
g_signal_handler_disconnect(priv->eekboard_context, priv->kb_signal);
|
|
priv->kb_signal = 0;
|
|
}
|
|
|
|
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, struct squeek_state_manager *state_manager)
|
|
{
|
|
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->state_manager = state_manager;
|
|
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;
|
|
|
|
priv->kb_signal = 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);
|
|
}
|
|
}
|