Commit c6ead69b authored by Julian Sparber's avatar Julian Sparber
Browse files

Keypad: Add a general keypad

This is based on HdyDialer, but with more flexible API.
It also adds a GtkEntry which can be used as the focus widget, it has
the key-press-event already connected and it grabs focus once it's
mapped.
parent f5909a89
Pipeline #33485 failed with stages
in 7 minutes and 36 seconds
......@@ -6,6 +6,7 @@
<file preprocess="xml-stripblanks">hdy-dialer.ui</file>
<file preprocess="xml-stripblanks">hdy-dialer-button.ui</file>
<file preprocess="xml-stripblanks">hdy-expander-row.ui</file>
<file preprocess="xml-stripblanks">hdy-keypad-button.ui</file>
<file preprocess="xml-stripblanks">hdy-paginator.ui</file>
<file preprocess="xml-stripblanks">hdy-preferences-group.ui</file>
<file preprocess="xml-stripblanks">hdy-preferences-page.ui</file>
......
/*
* Copyright (C) 2019 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <glib/gi18n-lib.h>
#include "hdy-keypad-button.h"
/**
* SECTION:hdy-keypad-button
* @short_description: A button on a #HdyKeypad keypad
* @Title: HdyKeypadButton
*
* The #HdyKeypadButton widget is a single button on an #HdyKeypad. It
* can represent a single symbol (typically a digit) plus an arbitrary
* number of symbols that are displayed below it.
*/
enum {
PROP_0,
PROP_DIGIT,
PROP_SYMBOLS,
PROP_SHOW_SYMBOLS,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
typedef struct
{
GtkLabel *label, *secondary_label;
gchar *symbols;
} HdyKeypadButtonPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (HdyKeypadButton, hdy_keypad_button, GTK_TYPE_BUTTON)
static void
format_label(HdyKeypadButton *self)
{
HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self);
gchar *symbols = priv->symbols != NULL ? priv->symbols : "";
g_autofree gchar *text = NULL;
gchar *secondary_text = NULL;
if (*symbols != '\0') {
secondary_text = g_utf8_find_next_char (symbols, NULL);
text = g_strndup (symbols, 1);
}
gtk_label_set_label (priv->label, text);
gtk_label_set_label (priv->secondary_label, secondary_text);
}
static void
hdy_keypad_button_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
HdyKeypadButton *self = HDY_KEYPAD_BUTTON (object);
HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self);
switch (property_id) {
case PROP_SYMBOLS:
g_free (priv->symbols);
priv->symbols = g_value_dup_string (value);
format_label(self);
break;
case PROP_SHOW_SYMBOLS:
hdy_keypad_button_show_symbols (self, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
hdy_keypad_button_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
HdyKeypadButton *self = HDY_KEYPAD_BUTTON (object);
HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self);
switch (property_id) {
case PROP_DIGIT:
g_value_set_schar (value, hdy_keypad_button_get_digit (self));
break;
case PROP_SYMBOLS:
g_value_set_string (value, priv->symbols);
break;
case PROP_SHOW_SYMBOLS:
g_value_set_boolean (value, gtk_widget_is_visible (GTK_WIDGET (priv->secondary_label)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
/* This private method is prefixed by the call name because it will be a virtual
* method in GTK 4.
*/
static void
hdy_keypad_button_measure (GtkWidget *widget,
GtkOrientation orientation,
int for_size,
int *minimum,
int *natural,
int *minimum_baseline,
int *natural_baseline)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (hdy_keypad_button_parent_class);
gint min1, min2, nat1, nat2;
if (for_size < 0) {
widget_class->get_preferred_width (widget, &min1, &nat1);
widget_class->get_preferred_height (widget, &min2, &nat2);
}
else {
if (orientation == GTK_ORIENTATION_HORIZONTAL)
widget_class->get_preferred_width_for_height (widget, for_size, &min1, &nat1);
else
widget_class->get_preferred_height_for_width (widget, for_size, &min1, &nat1);
min2 = nat2 = for_size;
}
if (minimum)
*minimum = MAX (min1, min2);
if (natural)
*natural = MAX (nat1, nat2);
}
static void
hdy_keypad_button_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width)
{
hdy_keypad_button_measure (widget, GTK_ORIENTATION_HORIZONTAL, -1,
minimum_width, natural_width, NULL, NULL);
}
static void
hdy_keypad_button_get_preferred_height (GtkWidget *widget,
gint *minimum_height,
gint *natural_height)
{
hdy_keypad_button_measure (widget, GTK_ORIENTATION_VERTICAL, -1,
minimum_height, natural_height, NULL, NULL);
}
static void
hdy_keypad_button_get_preferred_width_for_height (GtkWidget *widget,
gint height,
gint *minimum_width,
gint *natural_width)
{
hdy_keypad_button_measure (widget, GTK_ORIENTATION_HORIZONTAL, height,
minimum_width, natural_width, NULL, NULL);
}
static void
hdy_keypad_button_get_preferred_height_for_width (GtkWidget *widget,
gint width,
gint *minimum_height,
gint *natural_height)
{
hdy_keypad_button_measure (widget, GTK_ORIENTATION_VERTICAL, width,
minimum_height, natural_height, NULL, NULL);
}
static void
hdy_keypad_button_finalize (GObject *object)
{
HdyKeypadButton *self = HDY_KEYPAD_BUTTON (object);
HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self);
g_clear_pointer (&priv->symbols, g_free);
G_OBJECT_CLASS (hdy_keypad_button_parent_class)->finalize (object);
}
static void
hdy_keypad_button_class_init (HdyKeypadButtonClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->set_property = hdy_keypad_button_set_property;
object_class->get_property = hdy_keypad_button_get_property;
object_class->finalize = hdy_keypad_button_finalize;
widget_class->get_preferred_width = hdy_keypad_button_get_preferred_width;
widget_class->get_preferred_height = hdy_keypad_button_get_preferred_height;
widget_class->get_preferred_width_for_height = hdy_keypad_button_get_preferred_width_for_height;
widget_class->get_preferred_height_for_width = hdy_keypad_button_get_preferred_height_for_width;
props[PROP_DIGIT] =
g_param_spec_char ("digit",
_("Digit"),
_("The keypad digit of the button"),
CHAR_MIN, CHAR_MAX,
'\0',
G_PARAM_READABLE);
props[PROP_SYMBOLS] =
g_param_spec_string ("symbols",
_("Symbols"),
_("The keypad symbols of the button"),
"",
G_PARAM_READWRITE);
props[PROP_SHOW_SYMBOLS] =
g_param_spec_boolean ("show_symbols",
_("Show Symbols"),
_("Whether the second line of symbols should be shown or not"),
TRUE,
G_PARAM_READWRITE);
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
gtk_widget_class_set_template_from_resource (widget_class,
"/sm/puri/handy/ui/hdy-keypad-button.ui");
gtk_widget_class_bind_template_child_private (widget_class, HdyKeypadButton, label);
gtk_widget_class_bind_template_child_private (widget_class, HdyKeypadButton, secondary_label);
}
/**
* hdy_keypad_button_new:
* @symbols: (nullable): the symbols displayed on the #HdyKeypadButton
*
* Create a new #HdyKeypadButton which displays
* @symbols. If
* @symbols is %NULL no symbols will be displayed.
*
* Returns: the newly created #HdyKeypadButton widget
*/
GtkWidget *hdy_keypad_button_new (const gchar *symbols)
{
return g_object_new (HDY_TYPE_KEYPAD_BUTTON, "symbols", symbols, NULL);
}
static void
hdy_keypad_button_init (HdyKeypadButton *self)
{
HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self);
gtk_widget_init_template (GTK_WIDGET (self));
priv->symbols = NULL;
}
/**
* hdy_keypad_button_get_digit:
* @self: a #HdyKeypadButton
*
* Get the #HdyKeypadButton's digit.
*
* Returns: the button's digit
*/
const char
hdy_keypad_button_get_digit (HdyKeypadButton *self)
{
HdyKeypadButtonPrivate *priv;
g_return_val_if_fail (HDY_IS_KEYPAD_BUTTON (self), '\0');
priv = hdy_keypad_button_get_instance_private(self);
return *(priv->symbols);
}
/**
* hdy_keypad_button_get_symbols:
* @self: a #HdyKeypadButton
*
* Get the #HdyKeypadButton's symbols.
*
* Returns: the button's symbols.
*/
const char*
hdy_keypad_button_get_symbols (HdyKeypadButton *self)
{
HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self);
g_return_val_if_fail (HDY_IS_KEYPAD_BUTTON (self), NULL);
return priv->symbols;
}
/**
* hdy_keypad_button_show_symbols:
* @self: a #HdyKeypadButton
* @visible: whether the second line should be shown or not
*
* Sets the visibility of the second line of symbols for #HdyKeypadButton
*
*/
void
hdy_keypad_button_show_symbols (HdyKeypadButton *self, gboolean visible)
{
HdyKeypadButtonPrivate *priv = hdy_keypad_button_get_instance_private(self);
g_return_if_fail (HDY_IS_KEYPAD_BUTTON (self));
gtk_widget_set_visible (GTK_WIDGET (priv->secondary_label), visible);
}
/*
* Copyright (C) 2019 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#pragma once
#if !defined(_HANDY_INSIDE) && !defined(HANDY_COMPILATION)
#error "Only <handy.h> can be included directly."
#endif
#include <gtk/gtk.h>
G_BEGIN_DECLS
#define HDY_TYPE_KEYPAD_BUTTON (hdy_keypad_button_get_type())
G_DECLARE_DERIVABLE_TYPE (HdyKeypadButton, hdy_keypad_button, HDY, KEYPAD_BUTTON, GtkButton)
struct _HdyKeypadButtonClass
{
GtkButtonClass parent_class;
};
GtkWidget *hdy_keypad_button_new (const gchar *symbols);
const char hdy_keypad_button_get_digit (HdyKeypadButton *self);
const char *hdy_keypad_button_get_symbols (HdyKeypadButton *self);
void hdy_keypad_button_show_symbols (HdyKeypadButton *self, gboolean visible);
G_END_DECLS
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="HdyKeypadButton" parent="GtkButton">
<property name="can_focus">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="valign">center</property>
<child>
<object class="GtkLabel" id="label">
<property name="visible">True</property>
<property name="label"></property>
<attributes>
<attribute name="weight" value="bold"/>
<attribute name="scale" value="2"/>
</attributes>
</object>
</child>
<child>
<object class="GtkLabel" id="secondary_label">
<property name="visible">True</property>
<property name="no_show_all">True</property>
<property name="label">ABC</property>
<attributes>
<attribute name="scale" value="0.7"/>
</attributes>
<style>
<class name="dim-label"/>
</style>
</object>
</child>
</object>
</child>
</template>
</interface>
/*
* Copyright (C) 2019 Purism SPC
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include <glib/gi18n-lib.h>
#include "hdy-keypad.h"
#include "hdy-keypad-button.h"
/**
* SECTION:hdy-keypad
* @short_description: A keypad for dialing numbers
* @Title: HdyKeypad
*
* The #HdyKeypad widget is a keypad for entering numbers such as phone numbers
* or PIN codes.
*/
typedef struct
{
GtkWidget *entry;
GtkGesture *long_press_zero_gesture;
gboolean only_digits;
gboolean show_symbols;
} HdyKeypadPrivate;
G_DEFINE_TYPE_WITH_PRIVATE (HdyKeypad, hdy_keypad, GTK_TYPE_GRID)
enum {
PROP_0,
PROP_SHOW_SYMBOLS,
PROP_ONLY_DIGITS,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
enum {
SIGNAL_SYMBOL_CLICKED,
SIGNAL_LAST_SIGNAL,
};
static guint signals [SIGNAL_LAST_SIGNAL];
static char* SYMBOLS[] = {"0+", "1", "2ABC", "3DEF", "4GHI", "5JKL", "6MNO", "7PQRS", "8TUV", "9WXYZ", "*", "#"};
static void
digit_button_clicked (HdyKeypad *self,
HdyKeypadButton *btn)
{
gchar digit;
g_return_if_fail (HDY_IS_KEYPAD (self));
g_return_if_fail (HDY_IS_KEYPAD_BUTTON (btn));
digit = hdy_keypad_button_get_digit (btn);
g_debug ("Button with number %c was pressed", digit);
g_signal_emit(self, signals[SIGNAL_SYMBOL_CLICKED], 0, digit);
}
/**
* hdy_keypad_filter_key_press_event:
* @self: a #HdyKeypad
* @event: nullable: #GdkEventKey to filter
*
* Filter key press events which are possible to enter with the #HdyKeypad
*
* Returns: returns TRUE if the key could be entered with the #HdyKeypad
*/
gboolean
hdy_keypad_filter_key_press_event (HdyKeypad *self,
GdkEventKey *event)
{
HdyKeypadPrivate *priv;
gboolean is_digit = FALSE;
g_return_val_if_fail (HDY_IS_KEYPAD (self), FALSE);
priv = hdy_keypad_get_instance_private (self);
switch (event->keyval) {
case GDK_KEY_KP_0 ... GDK_KEY_KP_9:
case GDK_KEY_0 ... GDK_KEY_9:
is_digit = TRUE;
break;
case GDK_KEY_numbersign:
case GDK_KEY_asterisk:
case GDK_KEY_KP_Multiply:
case GDK_KEY_plus:
break;
default:
return FALSE;
}
return !(priv->only_digits && !is_digit);
}
/* This list of key was extractad from gtkentry */
static gboolean
is_control_key (GdkEventKey *event)
{
GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask ();
if ((event->state & modifiers) == GDK_CONTROL_MASK)
return TRUE;
switch (event->keyval) {
case GDK_KEY_Right:
case GDK_KEY_Left:
case GDK_KEY_KP_Right:
case GDK_KEY_KP_Left:
case GDK_KEY_Home:
case GDK_KEY_End:
case GDK_KEY_KP_Home:
case GDK_KEY_KP_End:
case GDK_KEY_Return:
case GDK_KEY_ISO_Enter:
case GDK_KEY_KP_Enter:
case GDK_KEY_Delete:
case GDK_KEY_KP_Delete:
case GDK_KEY_BackSpace:
case GDK_KEY_Insert:
case GDK_KEY_KP_Insert:
case GDK_KEY_Escape:
case GDK_KEY_Up:
case GDK_KEY_Down:
return TRUE;
case GDK_KEY_a:
return (event->state & modifiers) == GDK_SHIFT_MASK;
default:
return FALSE;
}
}
static gboolean
key_press_event_cb (GtkWidget *widget,
GdkEventKey *event)
{
g_return_val_if_fail (HDY_IS_KEYPAD (widget), FALSE);
return !((is_control_key (event) || hdy_keypad_filter_key_press_event (HDY_KEYPAD (widget), event)));
}
static void
symbol_clicked_cb (GtkWidget *widget,
gchar symbol)
{
HdyKeypadPrivate *priv;
g_autofree gchar *string = g_strdup_printf ("%c", symbol);
g_return_if_fail (HDY_IS_KEYPAD (widget));
priv = hdy_keypad_get_instance_private (HDY_KEYPAD (widget));
g_assert (priv->entry != NULL);
g_signal_emit_by_name(GTK_ENTRY (priv->entry), "insert-at-cursor", string, NULL);
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->entry));
}
static void
map_event_cb (GtkWidget *widget)
{
gtk_entry_grab_focus_without_selecting (GTK_ENTRY (widget));
}
static void
long_press_zero_cb (GtkGesture *gesture,
gdouble x,
gdouble y,
HdyKeypad *self)
{
g_return_if_fail (HDY_IS_KEYPAD (self));
g_debug ("Long press on zero button");
g_signal_emit(self, signals[SIGNAL_SYMBOL_CLICKED], 0, '+');
gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
}
static void
hdy_keypad_finalize (GObject *object)
{
HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (HDY_KEYPAD (object));
if (priv->long_press_zero_gesture != NULL)
g_object_unref (priv->long_press_zero_gesture);
G_OBJECT_CLASS (hdy_keypad_parent_class)->finalize (object);
}
static void
hdy_keypad_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
HdyKeypad *self = HDY_KEYPAD (object);
HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (self);
switch (property_id) {
case PROP_SHOW_SYMBOLS:
hdy_keypad_show_symbols (self, g_value_get_boolean (value));
break;
case PROP_ONLY_DIGITS:
priv->only_digits = g_value_get_boolean (value);
if (priv->only_digits)
priv->show_symbols = FALSE;
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
hdy_keypad_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
HdyKeypad *self = HDY_KEYPAD (object);
HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (self);
switch (property_id) {
case PROP_SHOW_SYMBOLS:
g_value_set_boolean (value, priv->show_symbols);
break;
case PROP_ONLY_DIGITS:
g_value_set_boolean (value, priv->only_digits);