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