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