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