refactor
[pspp] / src / ui / gui / psppire-keypad.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2010, 2011, 2015, 2020 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include <gtk/gtk.h>
19 #include <gdk/gdkkeysyms.h>
20 #include "psppire-keypad.h"
21
22 #include <gettext.h>
23 #define _(msgid) gettext (msgid)
24
25 enum
26   {
27    INSERT_SYNTAX,
28    ERASE,
29    n_SIGNALS
30   };
31
32 static guint keypad_signals [n_SIGNALS] = { 0 };
33
34 G_DEFINE_TYPE (PsppireKeypad, psppire_keypad, GTK_TYPE_EVENT_BOX)
35
36 static GObjectClass * parent_class = NULL;
37
38 static void
39 psppire_keypad_dispose (GObject *obj)
40 {
41   PsppireKeypad *kp = PSPPIRE_KEYPAD (obj);
42
43   if (kp->dispose_has_run)
44     return;
45
46   /* Make sure dispose does not run twice. */
47   kp->dispose_has_run = TRUE;
48
49   g_hash_table_unref (kp->frag_table);
50
51   /* Chain up to the parent class */
52   G_OBJECT_CLASS (parent_class)->dispose (obj);
53 }
54
55 static void
56 psppire_keypad_finalize (GObject *obj)
57 {
58    /* Chain up to the parent class */
59    G_OBJECT_CLASS (parent_class)->finalize (obj);
60 }
61
62 static void
63 psppire_keypad_class_init (PsppireKeypadClass *klass)
64 {
65   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
66
67   parent_class = g_type_class_peek_parent (klass);
68
69   gobject_class->dispose = psppire_keypad_dispose;
70   gobject_class->finalize = psppire_keypad_finalize;
71
72   keypad_signals[INSERT_SYNTAX] = g_signal_new ("insert-syntax",
73                                          G_TYPE_FROM_CLASS (klass),
74                                          G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
75                                          G_STRUCT_OFFSET (PsppireKeypadClass,
76                                                           keypad),
77                                          NULL,
78                                          NULL,
79                                          g_cclosure_marshal_VOID__STRING,
80                                          G_TYPE_NONE, 1,
81                                          G_TYPE_STRING);
82
83   keypad_signals[ERASE] = g_signal_new ("erase",
84                                          G_TYPE_FROM_CLASS (klass),
85                                          G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
86                                          G_STRUCT_OFFSET (PsppireKeypadClass,
87                                                           keypad),
88                                          NULL,
89                                          NULL,
90                                          g_cclosure_marshal_VOID__VOID,
91                                          G_TYPE_NONE, 0);
92 }
93
94
95 /*
96    These are the strings that will be arguments to
97    the emitted signals.
98    The order of these must correspond
99    to the order of the button declarations
100 */
101 static const char * const keypad_insert_text[] =
102   {
103    "0",  "1",  "2", "3", "4", "5", "6", "7", "8", "9",
104    ".", "+", "-", "*", "**", "/", "=", "<>", "<", "<=",
105    ">", ">=", "&", "|", "~", "()", NULL
106   };
107
108
109 /* Callback for any button click.
110    Emits the "insert-syntax" signal for the keypad,
111    with the string corresponding to the clicked button.
112 */
113 static void
114 button_click (GtkButton *b, PsppireKeypad *kp)
115 {
116   const gchar *s = g_hash_table_lookup (kp->frag_table, b);
117
118
119   if (s)
120     g_signal_emit (kp, keypad_signals [INSERT_SYNTAX], 0, s);
121   else
122     g_signal_emit (kp, keypad_signals [ERASE], 0);
123 }
124
125 static const gint cols = 6;
126 static const gint rows = 5;
127
128
129
130 /* Add BUTTON to KP.  The top-left corner at X1,Y1, the
131    botton-right corner at X2,Y2 */
132 static void
133 add_button (PsppireKeypad *kp, GtkWidget **button,
134             gint x1, gint x2,
135             gint y1, gint y2)
136 {
137   g_object_set (G_OBJECT (*button), "focus-on-click", FALSE, NULL);
138
139   gtk_grid_attach (GTK_GRID(kp->table), *button, x1, y1, x2 - x1, y2 - y1);
140
141   gtk_widget_set_size_request (*button,
142                                30 * rows / (float) cols,
143                                30 * cols / (float) rows);
144
145   g_hash_table_insert (kp->frag_table, *button,
146                        (void *) keypad_insert_text[(button - &kp->digit[0])]);
147
148   g_signal_connect (*button, "clicked",
149                     G_CALLBACK (button_click), kp);
150
151   gtk_widget_show (*button);
152 }
153
154
155 /* Return  a  new button with CODE as the unicode character for its label */
156 static inline GtkWidget *
157 button_new_from_unicode (gunichar code)
158 {
159   char s[6] = {0,0,0,0,0,0};
160
161   g_unichar_to_utf8 (code, s);
162
163   return gtk_button_new_with_label (s);
164 }
165
166
167 /* Callback which occurs when the mouse enters the widget.
168    It sets or unsets the focus.
169 */
170 static gboolean
171 enter_leave_notify (GtkWidget   *widget,
172       GdkEventCrossing *event,
173       gpointer     user_data)
174 {
175   /* Do nothing if we're just moving between the widget and
176      its children */
177  if (event->detail == GDK_NOTIFY_INFERIOR)
178    return FALSE;
179
180  if (event->type == GDK_ENTER_NOTIFY)
181    gtk_widget_grab_focus (widget);
182
183  return FALSE;
184 }
185
186 static gboolean
187 key_release_callback (GtkWidget   *widget,
188                       GdkEventKey *event,
189                       gpointer     user_data)
190 {
191   if (! gtk_widget_has_focus (widget))
192     return FALSE;
193
194   switch (event->keyval)
195     {
196     case '(':
197       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "(");
198       break;
199     case ')':
200       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, ")");
201       break;
202     case '>':
203       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, ">");
204       break;
205     case '<':
206       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "<");
207       break;
208     case GDK_KEY_KP_Equal :
209     case '=':
210       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "=");
211       break;
212     case GDK_KEY_KP_Multiply :
213     case '*':
214       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "*");
215       break;
216     case GDK_KEY_KP_Add :
217     case '+':
218       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "+");
219       break;
220     case GDK_KEY_KP_Subtract :
221     case '-':
222       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "-");
223       break;
224     case GDK_KEY_KP_Decimal :
225     case '.':
226       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, ".");
227       break;
228     case GDK_KEY_KP_Divide :
229     case '/':
230       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "/");
231       break;
232     case GDK_KEY_KP_0 :
233     case '0':
234       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "0");
235       break;
236     case GDK_KEY_KP_1 :
237     case '1':
238       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "1");
239       break;
240     case GDK_KEY_KP_2 :
241     case '2':
242       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "2");
243       break;
244     case GDK_KEY_KP_3 :
245     case '3':
246       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "3");
247       break;
248     case GDK_KEY_KP_4 :
249     case '4':
250       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "4");
251       break;
252     case GDK_KEY_KP_5 :
253     case '5':
254       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "5");
255       break;
256     case GDK_KEY_KP_6 :
257     case '6':
258       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "6");
259       break;
260     case GDK_KEY_KP_7 :
261     case '7':
262       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "7");
263       break;
264     case GDK_KEY_KP_8 :
265     case '8':
266       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "8");
267       break;
268     case GDK_KEY_KP_9 :
269     case '9':
270       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "9");
271       break;
272      default:
273        break;
274     };
275
276   return FALSE;
277 }
278
279
280 static void
281 psppire_keypad_init (PsppireKeypad *kp)
282 {
283   gint i;
284   const int digit_voffset = 0;
285   const int digit_hoffset = 3;
286
287   gtk_widget_set_can_focus (GTK_WIDGET (kp), TRUE);
288
289   kp->dispose_has_run = FALSE;
290
291   g_signal_connect (kp, "enter-notify-event", G_CALLBACK (enter_leave_notify),
292                     NULL);
293
294   g_signal_connect (kp, "leave-notify-event", G_CALLBACK (enter_leave_notify),
295                     NULL);
296
297   g_signal_connect (kp, "key-release-event", G_CALLBACK (key_release_callback),
298                     NULL);
299
300   kp->frag_table = g_hash_table_new (g_direct_hash, g_direct_equal);
301
302   kp->table = gtk_grid_new ();
303
304   /* Buttons for the digits */
305   for (i = 0; i < 10; i++)
306     {
307       int j = i - 1;
308       char buf[5];
309       g_snprintf (buf, 5, "%d", i);
310       kp->digit[i] = gtk_button_new_with_label (buf);
311
312       if (i == 0)
313         add_button (kp, &kp->digit[i],
314                     digit_hoffset + 0, digit_hoffset + 2,
315                     digit_voffset + 3, digit_voffset + 4);
316       else
317         add_button (kp, &kp->digit[i],
318                     digit_hoffset + j % 3, digit_hoffset + j % 3 + 1,
319                     digit_voffset + 2 - (j / 3),
320                     digit_voffset + 2 - (j / 3) + 1);
321     }
322
323   /* ... all the other buttons */
324
325   kp->dot = button_new_from_unicode (0xB7);     /* MIDDLE DOT */
326   add_button (kp, &kp->dot, digit_hoffset + 2,
327               digit_hoffset + 3,
328               digit_voffset + 3,
329               digit_voffset + 4);
330
331   kp->plus  = gtk_button_new_with_label ("+");
332   gtk_widget_set_tooltip_text (kp->plus, _("add"));
333   add_button (kp, &kp->plus, 0, 1, 0, 1);
334
335   kp->minus = button_new_from_unicode (0x2212); /* MINUS SIGN */
336   gtk_widget_set_tooltip_text (kp->minus, _("subtract"));
337   add_button (kp, &kp->minus, 0, 1, 1, 2);
338
339   kp->star  = button_new_from_unicode (0xD7);   /* MULTIPLICATION SIGN */
340   gtk_widget_set_tooltip_text (kp->star, _("multiply"));
341   add_button (kp, &kp->star, 0, 1,  2, 3);
342
343   kp->slash = button_new_from_unicode (0xF7);   /* DIVISION SIGN */
344   gtk_widget_set_tooltip_text (kp->slash, _("divide"));
345   add_button (kp, &kp->slash, 0, 1, 3, 4);
346
347   /* Exponention */
348   {
349     GtkWidget *label;
350     char *markup =
351       g_markup_printf_escaped ("<span style=\"italic\">x<sup>y</sup></span>");
352
353     label = gtk_label_new ("**");
354
355     gtk_label_set_markup (GTK_LABEL (label), markup);
356     g_free (markup);
357
358     kp->star_star = gtk_button_new ();
359     gtk_widget_set_tooltip_text (kp->star_star, _("raise x to the power of y"));
360
361     gtk_container_add (GTK_CONTAINER (kp->star_star), label);
362
363     gtk_widget_show (label);
364
365     add_button (kp, &kp->star_star, 0, 1, 4, 5);
366   }
367
368
369   kp->gt = button_new_from_unicode (0x3E); /* GREATER-THAN SIGN*/
370   gtk_widget_set_tooltip_text (kp->gt, _("is greater than"));
371   add_button (kp, &kp->gt, 2, 3,  0, 1);
372
373   kp->lt = button_new_from_unicode (0x3C); /* LESS-THAN SIGN*/
374   gtk_widget_set_tooltip_text (kp->lt, _("is less than"));
375   add_button (kp, &kp->lt, 1, 2, 0, 1);
376
377   kp->ge = button_new_from_unicode (0x2265); /* GREATER-THAN OR EQUAL */
378   gtk_widget_set_tooltip_text (kp->ge, _("is no less than"));
379   add_button (kp, &kp->ge, 2, 3, 1, 2);
380
381   kp->le = button_new_from_unicode (0x2264); /* LESS-THAN OR EQUAL */
382   gtk_widget_set_tooltip_text (kp->le, _("is no greater than"));
383   add_button (kp, &kp->le, 1, 2, 1, 2);
384
385   kp->neq = button_new_from_unicode (0x2260); /* NOT EQUAL */
386   gtk_widget_set_tooltip_text (kp->neq, _("is not equal to"));
387   add_button (kp, &kp->neq, 2, 3, 2, 3);
388
389   kp->eq = gtk_button_new_with_label ("=");
390   gtk_widget_set_tooltip_text (kp->eq, _("is equal to"));
391   add_button (kp, &kp->eq, 1, 2, 2, 3);
392
393   kp->parentheses = gtk_button_new_with_label ("()");
394   add_button (kp, &kp->parentheses, 2, 3, 4, 5);
395
396   kp->delete = gtk_button_new_with_label ("Delete");
397   add_button (kp, &kp->delete, 3, 6, 4, 5);
398
399   kp->and = button_new_from_unicode (0x2227); /* LOGICAL AND */
400   gtk_widget_set_tooltip_text (kp->and, _("and"));
401   add_button (kp, &kp->and, 1, 2, 3, 4);
402
403   kp->or = button_new_from_unicode (0x2228); /* LOGICAL OR */
404   gtk_widget_set_tooltip_text (kp->or, _("or"));
405   add_button (kp, &kp->or, 2, 3, 3, 4);
406
407   kp->not = button_new_from_unicode (0xAC); /* NOT SIGN */
408   gtk_widget_set_tooltip_text (kp->not, _("not"));
409   add_button (kp, &kp->not, 1, 2, 4, 5);
410
411   g_object_set (G_OBJECT (kp->table), "row-spacing", 5, NULL);
412   g_object_set (G_OBJECT (kp->table), "column-spacing", 5, NULL);
413
414   gtk_container_add (GTK_CONTAINER (kp), kp->table);
415   gtk_widget_show (kp->table);
416
417   gtk_widget_add_events (GTK_WIDGET (kp),
418         GDK_KEY_RELEASE_MASK  |
419         GDK_LEAVE_NOTIFY_MASK |
420         GDK_ENTER_NOTIFY_MASK |
421         GDK_FOCUS_CHANGE_MASK);
422 }
423
424
425 GtkWidget*
426 psppire_keypad_new (void)
427 {
428   return GTK_WIDGET (g_object_new (psppire_keypad_get_type (), NULL));
429 }