Due to the way the panel size is calculated, there might be a small empty space on the sides or top of the layout. This can be an issue, especially when this empty space is located on the sides, as touch events in this area are not taken into account. By allowing a small difference in horizontal and vertical scaling, we can ensure the panel occupies the whole display width in cases where this would be problematic.
462 lines
15 KiB
C
462 lines
15 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 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);
|
|
}
|
|
}
|