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