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