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