1 /*
2  * Copyright (C) 2019 Purism SPC
3  *
4  * SPDX-License-Identifier: LGPL-2.1+
5  */
6 
7 #include "config.h"
8 #include <glib/gi18n-lib.h>
9 
10 #include "hdy-style-private.h"
11 #include "hdy-keypad.h"
12 #include "hdy-keypad-button-private.h"
13 
14 /**
15  * SECTION:hdy-keypad
16  * @short_description: A keypad for dialing numbers
17  * @Title: HdyKeypad
18  *
19  * The #HdyKeypad widget is a keypad for entering numbers such as phone numbers
20  * or PIN codes.
21  *
22  * This widget should not be altered using the #GtkGrid and #GtkContainer APIs,
23  * they are considered internal to this widget, using them externally will lead to unexpected results.
24  */
25 
26 typedef struct
27 {
28   GtkWidget *entry;
29   GtkWidget *label_asterisk;
30   GtkWidget *label_hash;
31   GtkGesture *long_press_zero_gesture;
32   gboolean only_digits;
33   gboolean show_symbols;
34 } HdyKeypadPrivate;
35 
36 G_DEFINE_TYPE_WITH_PRIVATE (HdyKeypad, hdy_keypad, GTK_TYPE_GRID)
37 
38 enum {
39   PROP_0,
40   PROP_SHOW_SYMBOLS,
41   PROP_ONLY_DIGITS,
42   PROP_ENTRY,
43   PROP_RIGHT_ACTION,
44   PROP_LEFT_ACTION,
45   PROP_LAST_PROP,
46 };
47 static GParamSpec *props[PROP_LAST_PROP];
48 
49 static void
symbol_clicked(HdyKeypad * self,gchar symbol)50 symbol_clicked (HdyKeypad     *self,
51                 gchar          symbol)
52 {
53   HdyKeypadPrivate *priv;
54   g_autofree gchar *string = g_strdup_printf ("%c", symbol);
55   g_return_if_fail (HDY_IS_KEYPAD (self));
56   priv = hdy_keypad_get_instance_private (self);
57   g_return_if_fail (priv->entry != NULL);
58   g_signal_emit_by_name(GTK_ENTRY (priv->entry), "insert-at-cursor", string, NULL);
59   /* Set focus to the entry only when it can get focus
60    * https://gitlab.gnome.org/GNOME/gtk/issues/2204
61    */
62   if (gtk_widget_get_can_focus (priv->entry)) {
63     gtk_entry_grab_focus_without_selecting (GTK_ENTRY (priv->entry));
64   }
65 }
66 
67 
68 static void
button_clicked_cb(HdyKeypad * self,HdyKeypadButton * btn)69 button_clicked_cb (HdyKeypad       *self,
70                       HdyKeypadButton *btn)
71 {
72   gchar digit;
73 
74   g_return_if_fail (HDY_IS_KEYPAD (self));
75   g_return_if_fail (HDY_IS_KEYPAD_BUTTON (btn));
76 
77   digit = hdy_keypad_button_get_digit (btn);
78   symbol_clicked (self, digit);
79   g_debug ("Button with number %c was pressed", digit);
80 }
81 
82 
83 static void
asterisk_button_clicked_cb(HdyKeypad * self,GtkWidget * btn)84 asterisk_button_clicked_cb (HdyKeypad *self,
85                             GtkWidget *btn)
86 {
87   g_return_if_fail (HDY_IS_KEYPAD (self));
88 
89   symbol_clicked (self, '*');
90   g_debug ("Button with * was pressed");
91 }
92 
93 
94 static void
hash_button_clicked_cb(HdyKeypad * self,GtkWidget * btn)95 hash_button_clicked_cb (HdyKeypad *self,
96                         GtkWidget *btn)
97 {
98   g_return_if_fail (HDY_IS_KEYPAD (self));
99 
100   symbol_clicked (self, '#');
101   g_debug ("Button with # was pressed");
102 }
103 
104 
105 static void
insert_text_cb(HdyKeypad * self,gchar * text,gint length,gpointer position,GtkEditable * editable)106 insert_text_cb (HdyKeypad *self,
107                gchar       *text,
108                gint         length,
109                gpointer     position,
110                GtkEditable  *editable)
111 {
112   HdyKeypadPrivate *priv;
113 
114   g_return_if_fail (HDY_IS_KEYPAD (self));
115   priv = hdy_keypad_get_instance_private (self);
116 
117   g_return_if_fail (length == 1);
118 
119   if (g_ascii_isdigit (*text))
120      return;
121 
122   if (!priv->only_digits && strchr("#*+", *text))
123      return;
124 
125   g_signal_stop_emission_by_name (editable, "insert-text");
126 }
127 
128 
129 static void
long_press_zero_cb(HdyKeypad * self,gdouble x,gdouble y,GtkGesture * gesture)130 long_press_zero_cb (HdyKeypad  *self,
131                     gdouble     x,
132                     gdouble     y,
133                     GtkGesture *gesture)
134 {
135   HdyKeypadPrivate *priv;
136 
137   g_return_if_fail (HDY_IS_KEYPAD (self));
138   priv = hdy_keypad_get_instance_private (self);
139 
140   if (priv->only_digits)
141     return;
142 
143   g_debug ("Long press on zero button");
144   symbol_clicked (self, '+');
145   gtk_gesture_set_state (GTK_GESTURE (gesture), GTK_EVENT_SEQUENCE_CLAIMED);
146 }
147 
148 
149 static void
hdy_keypad_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)150 hdy_keypad_set_property (GObject      *object,
151                          guint         property_id,
152                          const GValue *value,
153                          GParamSpec   *pspec)
154 {
155   HdyKeypad *self = HDY_KEYPAD (object);
156   HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (self);
157 
158   switch (property_id) {
159   case PROP_SHOW_SYMBOLS:
160     hdy_keypad_show_symbols (self, g_value_get_boolean (value));
161     break;
162 
163   case PROP_ONLY_DIGITS:
164     if (g_value_get_boolean (value) != priv->only_digits) {
165       priv->only_digits = g_value_get_boolean (value);
166       g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ONLY_DIGITS]);
167     }
168     break;
169   case PROP_ENTRY:
170     hdy_keypad_set_entry (self, g_value_get_object (value));
171     break;
172 
173   case PROP_RIGHT_ACTION:
174     hdy_keypad_set_right_action (self, g_value_get_object (value));
175     break;
176 
177   case PROP_LEFT_ACTION:
178     hdy_keypad_set_left_action (self, g_value_get_object (value));
179     break;
180 
181   default:
182     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
183     break;
184   }
185 }
186 
187 
188 static void
hdy_keypad_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)189 hdy_keypad_get_property (GObject    *object,
190                          guint       property_id,
191                          GValue     *value,
192                          GParamSpec *pspec)
193 {
194   HdyKeypad *self = HDY_KEYPAD (object);
195   HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (self);
196 
197   switch (property_id) {
198   case PROP_SHOW_SYMBOLS:
199     g_value_set_boolean (value, priv->show_symbols);
200     break;
201 
202   case PROP_ONLY_DIGITS:
203     g_value_set_boolean (value, priv->only_digits);
204     break;
205 
206   case PROP_ENTRY:
207     g_value_set_object (value, priv->entry);
208     break;
209 
210   default:
211     G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
212     break;
213   }
214 }
215 
216 
217 static void
hdy_keypad_constructed(GObject * object)218 hdy_keypad_constructed (GObject *object)
219 {
220   HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (HDY_KEYPAD (object));
221   g_autoptr (GtkCssProvider) provider = NULL;
222 
223   G_OBJECT_CLASS (hdy_keypad_parent_class)->constructed (object);
224 
225   provider = gtk_css_provider_new ();
226   gtk_css_provider_load_from_resource (provider, "/sm/puri/handy/style/hdy-keypad-symbol.css");
227   gtk_style_context_add_provider (gtk_widget_get_style_context (priv->label_asterisk),
228                                 GTK_STYLE_PROVIDER (provider),
229                                 HDY_STYLE_PROVIDER_PRIORITY);
230   gtk_style_context_add_provider (gtk_widget_get_style_context (priv->label_hash),
231                                 GTK_STYLE_PROVIDER (provider),
232                                 HDY_STYLE_PROVIDER_PRIORITY);
233 }
234 
235 
236 static void
hdy_keypad_finalize(GObject * object)237 hdy_keypad_finalize (GObject *object)
238 {
239   HdyKeypadPrivate *priv = hdy_keypad_get_instance_private (HDY_KEYPAD (object));
240 
241   if (priv->long_press_zero_gesture != NULL)
242     g_object_unref (priv->long_press_zero_gesture);
243 
244   G_OBJECT_CLASS (hdy_keypad_parent_class)->finalize (object);
245 }
246 
247 
248 static void
hdy_keypad_class_init(HdyKeypadClass * klass)249 hdy_keypad_class_init (HdyKeypadClass *klass)
250 {
251   GObjectClass *object_class = G_OBJECT_CLASS (klass);
252   GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
253 
254   object_class->finalize = hdy_keypad_finalize;
255   object_class->constructed = hdy_keypad_constructed;
256 
257   object_class->set_property = hdy_keypad_set_property;
258   object_class->get_property = hdy_keypad_get_property;
259 
260   props[PROP_SHOW_SYMBOLS] =
261     g_param_spec_boolean ("show-symbols",
262                          _("Show Symbols"),
263                          _("Whether the second line of symbols should be shown or not"),
264                          TRUE,
265                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
266 
267   props[PROP_ONLY_DIGITS] =
268     g_param_spec_boolean ("only-digits",
269                          _("Only Digits"),
270                          _("Whether the keypad should show only digits or also extra buttons for #, *"),
271                          FALSE,
272                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
273 
274   props[PROP_ENTRY] =
275    g_param_spec_object ("entry",
276                         _("Entry widget"),
277                         _("The entry widget connected to the keypad"),
278                         GTK_TYPE_WIDGET,
279                         G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
280 
281   props[PROP_RIGHT_ACTION] =
282    g_param_spec_object ("right-action",
283                         _("Right action widget"),
284                         _("The right action widget"),
285                         GTK_TYPE_WIDGET,
286                         G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY);
287 
288   props[PROP_LEFT_ACTION] =
289    g_param_spec_object ("left-action",
290                         _("Left action widget"),
291                         _("The left action widget"),
292                         GTK_TYPE_WIDGET,
293                         G_PARAM_WRITABLE | G_PARAM_EXPLICIT_NOTIFY);
294 
295   g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
296 
297   gtk_widget_class_set_template_from_resource (widget_class,
298                                                "/sm/puri/handy/ui/hdy-keypad.ui");
299   gtk_widget_class_bind_template_child_private (widget_class, HdyKeypad, label_asterisk);
300   gtk_widget_class_bind_template_child_private (widget_class, HdyKeypad, label_hash);
301   gtk_widget_class_bind_template_child_private (widget_class, HdyKeypad, long_press_zero_gesture);
302 
303   gtk_widget_class_bind_template_callback(widget_class, button_clicked_cb);
304   gtk_widget_class_bind_template_callback(widget_class, asterisk_button_clicked_cb);
305   gtk_widget_class_bind_template_callback(widget_class, hash_button_clicked_cb);
306   gtk_widget_class_bind_template_callback(widget_class, long_press_zero_cb);
307 
308   gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_DIAL);
309   gtk_widget_class_set_css_name (widget_class, "hdykeypad");
310 }
311 
312 
313 static void
hdy_keypad_init(HdyKeypad * self)314 hdy_keypad_init (HdyKeypad *self)
315 {
316   gtk_widget_init_template (GTK_WIDGET (self));
317 }
318 
319 
320 /**
321  * hdy_keypad_new:
322  * @only_digits: whether the keypad should show only digits or also extra buttons for #, *
323  * @show_symbols: whether the keypad should show the second line or only the main digit
324  *
325  * Create a new #HdyKeypad widget.
326  *
327  * Returns: the newly created #HdyKeypad widget
328  *
329  */
hdy_keypad_new(gboolean only_digits,gboolean show_symbols)330 GtkWidget *hdy_keypad_new (gboolean only_digits, gboolean show_symbols)
331 {
332   return g_object_new (HDY_TYPE_KEYPAD,
333                        "only-digits", only_digits,
334                        "show-symbols", show_symbols,
335                        NULL);
336 }
337 
338 
339 /**
340  * hdy_keypad_show_symbols:
341  * @self: a #HdyKeypad
342  * @visible: whether the second line on buttons should be shown or not
343  *
344  * Sets the visibility of symbols (excluding the main digit) on each button in the #HdyKeypad
345  *
346  */
347 void
hdy_keypad_show_symbols(HdyKeypad * self,gboolean visible)348 hdy_keypad_show_symbols (HdyKeypad *self, gboolean visible)
349 {
350   HdyKeypadPrivate *priv = hdy_keypad_get_instance_private(self);
351 
352   g_return_if_fail (HDY_IS_KEYPAD (self));
353 
354   if (visible == priv->show_symbols)
355     return;
356 
357   priv->show_symbols = visible;
358 
359   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_SHOW_SYMBOLS]);
360 }
361 
362 
363 /**
364  * hdy_keypad_set_entry:
365  * @self: a #HdyKeypad
366  * @entry: a #GtkEntry
367  *
368  * Binds a #GtkEntry to the keypad and it blocks every
369  * input which wouldn't be possible to type with with the keypad
370  *
371  */
372 void
hdy_keypad_set_entry(HdyKeypad * self,GtkEntry * entry)373 hdy_keypad_set_entry (HdyKeypad *self, GtkEntry *entry)
374 {
375   HdyKeypadPrivate *priv;
376 
377   g_return_if_fail (HDY_IS_KEYPAD (self));
378   g_return_if_fail (GTK_IS_ENTRY (entry));
379 
380   priv = hdy_keypad_get_instance_private(self);
381   if (priv->entry != NULL) {
382     g_object_unref (priv->entry);
383   }
384 
385   if (entry == NULL) {
386     priv->entry = NULL;
387     return;
388   }
389 
390   priv->entry = GTK_WIDGET (g_object_ref (entry));
391 
392   gtk_widget_show (priv->entry);
393   /* Workaround: To keep the osk closed
394    * https://gitlab.gnome.org/GNOME/gtk/merge_requests/978#note_546576 */
395   g_object_set (priv->entry, "im-module", "gtk-im-context-none", NULL);
396 
397   g_signal_connect_swapped (G_OBJECT (priv->entry),
398                             "insert-text",
399                             G_CALLBACK (insert_text_cb),
400                             self);
401 
402   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ENTRY]);
403 }
404 
405 
406 /**
407  * hdy_keypad_get_entry:
408  * @self: a #HdyKeypad
409  *
410  * Get the connected entry. See hdy_keypad_set_entry () for details
411  *
412  * Returns: (transfer none): the set #GtkEntry or NULL if no widget was set
413  *
414  */
415 GtkWidget *
hdy_keypad_get_entry(HdyKeypad * self)416 hdy_keypad_get_entry (HdyKeypad *self)
417 {
418   HdyKeypadPrivate *priv;
419 
420   g_return_val_if_fail (HDY_IS_KEYPAD (self), NULL);
421 
422   priv = hdy_keypad_get_instance_private(self);
423 
424   return priv->entry;
425 }
426 
427 
428 /**
429  * hdy_keypad_set_left_action:
430  * @self: a #HdyKeypad
431  * @widget: nullable: the widget which should be show in the left lower corner of #HdyKeypad
432  *
433  * Sets the widget for the left lower corner of #HdyKeypad replacing the existing widget, if NULL it just removes whatever widget is there
434  *
435  */
436 void
hdy_keypad_set_left_action(HdyKeypad * self,GtkWidget * widget)437 hdy_keypad_set_left_action (HdyKeypad *self, GtkWidget *widget)
438 {
439   GtkWidget *old_widget;
440   g_return_if_fail (HDY_IS_KEYPAD (self));
441 
442   old_widget = gtk_grid_get_child_at (GTK_GRID (self), 0, 3);
443 
444   if (old_widget == widget)
445     return;
446 
447   if (old_widget != NULL)
448     gtk_container_remove (GTK_CONTAINER (self), old_widget);
449 
450   if (widget != NULL)
451     gtk_grid_attach (GTK_GRID (self), widget, 0, 3, 1, 1);
452 
453   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LEFT_ACTION]);
454 }
455 
456 
457 /**
458  * hdy_keypad_set_right_action:
459  * @self: a #HdyKeypad
460  * @widget: nullable: the widget which should be show in the right lower corner of #HdyKeypad
461  *
462  * Sets the widget for the right lower corner of #HdyKeypad replacing the existing widget, if NULL it just removes whatever widget is there
463  *
464  */
465 void
hdy_keypad_set_right_action(HdyKeypad * self,GtkWidget * widget)466 hdy_keypad_set_right_action (HdyKeypad *self, GtkWidget *widget)
467 {
468   GtkWidget *old_widget;
469   g_return_if_fail (HDY_IS_KEYPAD (self));
470 
471   old_widget = gtk_grid_get_child_at (GTK_GRID (self), 2, 3);
472 
473   if (old_widget == widget)
474     return;
475 
476   if (old_widget != NULL)
477     gtk_container_remove (GTK_CONTAINER (self), old_widget);
478 
479   if (widget != NULL)
480     gtk_grid_attach (GTK_GRID (self), widget, 2, 3, 1, 1);
481 
482   g_object_notify_by_pspec (G_OBJECT (self), props[PROP_RIGHT_ACTION]);
483 }
484