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