Merge 'master' into 'gtk3'.
[pspp] / src / ui / gui / psppire-keypad.c
1 /* PSPPIRE - a graphical user interface for PSPP.
2    Copyright (C) 2007, 2010, 2011 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_table_attach_defaults (GTK_TABLE (kp->table),
164                              *button,
165                              x1, x2,
166                              y1, y2);
167
168   gtk_widget_set_size_request (*button,
169                                30 * rows / (float) cols,
170                                30 * cols / (float) rows);
171
172   g_hash_table_insert (kp->frag_table, *button,
173                        (void *) keypad_insert_text[(button - &kp->digit[0])] );
174
175   g_signal_connect (*button, "clicked",
176                     G_CALLBACK (button_click), kp);
177
178   gtk_widget_show (*button);
179 }
180
181
182 /* Return  a  new button with CODE as the unicode character for its label */
183 static inline GtkWidget *
184 button_new_from_unicode (gunichar code)
185 {
186   char s[6] = {0,0,0,0,0,0};
187
188   g_unichar_to_utf8 (code, s);
189
190   return gtk_button_new_with_label (s);
191 }
192
193
194 /* Callback which occurs when the mouse enters the widget.
195    It sets or unsets the focus.
196 */
197 static gboolean
198 enter_leave_notify (GtkWidget   *widget,
199       GdkEventCrossing *event,
200       gpointer     user_data)
201 {
202   /* Do nothing if we're just moving between the widget and
203      its children */
204  if (event->detail == GDK_NOTIFY_INFERIOR)
205    return FALSE;
206
207  if (event->type == GDK_ENTER_NOTIFY)
208    gtk_widget_grab_focus (widget);
209
210  return FALSE;
211 }
212
213 static gboolean
214 key_release_callback (GtkWidget   *widget,
215                       GdkEventKey *event,
216                       gpointer     user_data)
217 {
218   if ( ! gtk_widget_has_focus (widget))
219     return FALSE;
220
221   switch (event->keyval)
222     {
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 '<':
233       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "<");
234       break;
235     case GDK_KP_Equal :
236     case '=':
237       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "=");
238       break;
239     case GDK_KP_Multiply :
240     case '*':
241       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "*");
242       break;
243     case GDK_KP_Add :
244     case '+':
245       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "+");
246       break;
247     case GDK_KP_Subtract :
248     case '-':
249       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "-");
250       break;
251     case GDK_KP_Decimal :
252     case '.':
253       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, ".");
254       break;
255     case GDK_KP_Divide :
256     case '/':
257       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "/");
258       break;
259     case GDK_KP_0 :
260     case '0':
261       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "0");
262       break;
263     case GDK_KP_1 :
264     case '1':
265       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "1");
266       break;
267     case GDK_KP_2 :
268     case '2':
269       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "2");
270       break;
271     case GDK_KP_3 :
272     case '3':
273       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "3");
274       break;
275     case GDK_KP_4 :
276     case '4':
277       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "4");
278       break;
279     case GDK_KP_5 :
280     case '5':
281       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "5");
282       break;
283     case GDK_KP_6 :
284     case '6':
285       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "6");
286       break;
287     case GDK_KP_7 :
288     case '7':
289       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "7");
290       break;
291     case GDK_KP_8 :
292     case '8':
293       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "8");
294       break;
295     case GDK_KP_9 :
296     case '9':
297       g_signal_emit (widget, keypad_signals [INSERT_SYNTAX], 0, "9");
298       break;
299      default:
300        break;
301     };
302
303   return FALSE;
304 }
305
306
307 static void
308 psppire_keypad_init (PsppireKeypad *kp)
309 {
310   gint i;
311   const int digit_voffset = 0;
312   const int digit_hoffset = 3;
313
314   gtk_widget_set_can_focus (GTK_WIDGET (kp), TRUE);
315
316   kp->dispose_has_run = FALSE;
317
318   g_signal_connect (kp, "enter-notify-event", G_CALLBACK (enter_leave_notify),
319                     NULL);
320
321   g_signal_connect (kp, "leave-notify-event", G_CALLBACK (enter_leave_notify),
322                     NULL);
323
324   g_signal_connect (kp, "key-release-event", G_CALLBACK (key_release_callback),
325                     NULL);
326
327   kp->frag_table = g_hash_table_new (g_direct_hash, g_direct_equal);
328
329   kp->table = gtk_table_new (rows, cols, TRUE);
330
331   /* Buttons for the digits */
332   for (i = 0; i < 10; i++)
333     {
334       int j = i - 1;
335       char buf[5];
336       g_snprintf (buf, 5, "%d", i);
337       kp->digit[i] = gtk_button_new_with_label (buf);
338
339       if ( i == 0 )
340         add_button (kp, &kp->digit[i],
341                     digit_hoffset + 0, digit_hoffset + 2,
342                     digit_voffset + 3, digit_voffset + 4);
343       else
344         add_button (kp, &kp->digit[i],
345                     digit_hoffset + j % 3, digit_hoffset + j % 3 + 1,
346                     digit_voffset + 2 - (j / 3),
347                     digit_voffset + 2 - (j / 3) + 1);
348     }
349
350   /* ... all the other buttons */
351
352   kp->dot = button_new_from_unicode (0xB7);     /* MIDDLE DOT */
353   add_button (kp, &kp->dot, digit_hoffset + 2,
354               digit_hoffset + 3,
355               digit_voffset + 3,
356               digit_voffset + 4);
357
358   kp->plus  = gtk_button_new_with_label ("+");
359   add_button (kp, &kp->plus, 0, 1,
360               0,1);
361
362   kp->minus = button_new_from_unicode (0x2212); /* MINUS SIGN */
363   add_button (kp, &kp->minus, 0, 1,
364               1,2);
365
366   kp->star  = button_new_from_unicode (0xD7);   /* MULTIPLICATION SIGN */
367   add_button (kp, &kp->star, 0, 1,
368               2,3);
369
370   kp->slash = button_new_from_unicode (0xF7);   /* DIVISION SIGN */
371   add_button (kp, &kp->slash, 0, 1,
372               3,4);
373
374   {
375     GtkWidget *label;
376     char *markup =
377       g_markup_printf_escaped ("<span style=\"italic\">x<sup>y</sup></span>");
378
379     label = gtk_label_new ("**");
380
381     gtk_label_set_markup (GTK_LABEL (label), markup);
382     g_free (markup);
383
384     kp->star_star = gtk_button_new ();
385     gtk_container_add (GTK_CONTAINER (kp->star_star), label);
386
387     gtk_widget_show (label);
388
389     add_button (kp, &kp->star_star,
390                 0, 1,
391                 4, 5);
392   }
393
394
395   kp->gt = button_new_from_unicode (0x3E); /* GREATER-THAN SIGN*/
396   add_button (kp, &kp->gt, 2, 3,
397               0,1);
398
399   kp->lt = button_new_from_unicode (0x3C); /* LESS-THAN SIGN*/
400   add_button (kp, &kp->lt, 1, 2,
401               0,1);
402
403   kp->ge = button_new_from_unicode (0x2265); /* GREATER-THAN OR EQUAL */
404   add_button (kp, &kp->ge, 2, 3,
405               1,2);
406
407   kp->le = button_new_from_unicode (0x2264); /* LESS-THAN OR EQUAL */
408   add_button (kp, &kp->le, 1, 2,
409               1,2);
410
411   kp->neq = button_new_from_unicode (0x2260); /* NOT EQUAL */
412   add_button (kp, &kp->neq, 2, 3,
413               2,3);
414
415   kp->eq = gtk_button_new_with_label ("=");
416   add_button (kp, &kp->eq, 1, 2,
417               2,3);
418
419   kp->parentheses = gtk_button_new_with_label ("()");
420   add_button (kp, &kp->parentheses, 2, 3,
421               4,5);
422
423
424   kp->delete = gtk_button_new_with_label ("Delete");
425   add_button (kp, &kp->delete, 3, 6,
426               4,5);
427
428
429
430   kp->and = button_new_from_unicode (0x2227); /* LOGICAL AND */
431   add_button (kp, &kp->and, 1, 2,
432               3,4);
433
434
435   kp->or = button_new_from_unicode (0x2228); /* LOGICAL OR */
436   add_button (kp, &kp->or, 2, 3,
437               3,4);
438
439
440   kp->not = button_new_from_unicode (0xAC); /* NOT SIGN */
441   add_button (kp, &kp->not, 1, 2,
442               4,5);
443
444
445
446   g_object_set (G_OBJECT (kp->table), "row-spacing", 5, NULL);
447   g_object_set (G_OBJECT (kp->table), "column-spacing", 5, NULL);
448
449   gtk_container_add (GTK_CONTAINER (kp), kp->table);
450   gtk_widget_show (kp->table);
451
452   gtk_widget_add_events (GTK_WIDGET (kp),
453         GDK_KEY_RELEASE_MASK  |
454         GDK_LEAVE_NOTIFY_MASK |
455         GDK_ENTER_NOTIFY_MASK |
456         GDK_FOCUS_CHANGE_MASK);
457
458 }
459
460
461 GtkWidget*
462 psppire_keypad_new (void)
463 {
464   return GTK_WIDGET (g_object_new (psppire_keypad_get_type (), NULL));
465 }