Files
squeekboard/eek/eek-gtk-keyboard.c
2020-03-12 11:34:20 +00:00

384 lines
13 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"
#ifdef HAVE_LIBCANBERRA
#include <canberra-gtk.h>
#endif
#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"
typedef struct _EekGtkKeyboardPrivate
{
EekRenderer *renderer; // owned, nullable
LayoutHolder *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
} 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 gboolean
eek_gtk_keyboard_real_draw (GtkWidget *self,
cairo_t *cr)
{
EekGtkKeyboardPrivate *priv =
eek_gtk_keyboard_get_instance_private (EEK_GTK_KEYBOARD (self));
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);
eek_renderer_set_allocation_size (priv->renderer,
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->submission, cr, priv->keyboard);
return FALSE;
}
// Units of pixel size
static enum squeek_arrangement_kind get_type(uint32_t width, uint32_t height) {
(void)height;
if (width < 1080) {
return ARRANGEMENT_KIND_BASE;
}
return ARRANGEMENT_KIND_WIDE;
}
static void
eek_gtk_keyboard_real_size_allocate (GtkWidget *self,
GtkAllocation *allocation)
{
EekGtkKeyboardPrivate *priv =
eek_gtk_keyboard_get_instance_private (EEK_GTK_KEYBOARD (self));
uint32_t scale = (uint32_t)gtk_widget_get_scale_factor(self);
// check if the change would switch types
enum squeek_arrangement_kind new_type = get_type(
(uint32_t)(allocation->width - allocation->x) * scale,
(uint32_t)(allocation->height - allocation->y) * scale);
if (priv->layout->arrangement != new_type) {
struct squeek_layout_state layout = *priv->layout;
layout.arrangement = new_type;
eek_layout_holder_use_layout(priv->eekboard_context, &layout);
}
if (priv->renderer)
eek_renderer_set_allocation_size (priv->renderer,
priv->keyboard->layout,
allocation->width,
allocation->height);
GTK_WIDGET_CLASS (eek_gtk_keyboard_parent_class)->
size_allocate (self, allocation);
}
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, eek_renderer_get_transformation(priv->renderer), 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(eek_layout_holder_get_keyboard(priv->eekboard_context)->layout,
priv->submission,
x, y, eek_renderer_get_transformation(priv->renderer), 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(eek_layout_holder_get_keyboard(priv->eekboard_context)->layout,
priv->submission,
eek_renderer_get_transformation(priv->renderer), 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;
priv->renderer = NULL;
}
if (priv->keyboard) {
squeek_layout_release_all_only(
priv->keyboard->layout,
priv->submission,
gdk_event_get_time(NULL));
priv->keyboard = 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);
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)
{
(void)self;
}
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 = eek_layout_holder_get_keyboard(LAYOUT_HOLDER(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 (LayoutHolder *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;
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);
}