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