Support entering super/subscript with numeric keypad (Bug #634904)
[gcalctool.git] / src / math-display.c
blobd4668316f296f190cef1d609b471d8222523760d
1 /*
2 * Copyright (C) 2008-2011 Robert Ancell
4 * This program is free software: you can redistribute it and/or modify it under
5 * the terms of the GNU General Public License as published by the Free Software
6 * Foundation, either version 2 of the License, or (at your option) any later
7 * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
8 * license.
9 */
11 #include <string.h>
12 #include <glib/gi18n.h>
13 #include <gdk/gdkkeysyms.h>
15 #include "math-display.h"
17 enum {
18 PROP_0,
19 PROP_EQUATION
22 struct MathDisplayPrivate
24 /* Equation being displayed */
25 MathEquation *equation;
27 /* Display widget */
28 GtkWidget *text_view;
30 /* Buffer that shows errors etc */
31 GtkTextBuffer *info_buffer;
33 /* Spinner widget that shows if we're calculating a response */
34 GtkWidget *spinner;
37 G_DEFINE_TYPE (MathDisplay, math_display, GTK_TYPE_VIEWPORT);
39 #define GET_WIDGET(ui, name) GTK_WIDGET(gtk_builder_get_object(ui, name))
41 MathDisplay *
42 math_display_new()
44 return g_object_new(math_display_get_type(), "equation", math_equation_new(), NULL);
48 MathDisplay *
49 math_display_new_with_equation(MathEquation *equation)
51 return g_object_new(math_display_get_type(), "equation", equation, NULL);
55 MathEquation *
56 math_display_get_equation(MathDisplay *display)
58 return display->priv->equation;
62 static gboolean
63 display_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathDisplay *display)
65 int state;
66 guint32 c;
67 guint new_keyval = 0;
69 /* Treat keypad keys as numbers even when numlock is off */
70 switch(event->keyval)
72 case GDK_KEY_KP_Insert:
73 new_keyval = GDK_KEY_0;
74 break;
75 case GDK_KEY_KP_End:
76 new_keyval = GDK_KEY_1;
77 break;
78 case GDK_KEY_KP_Down:
79 new_keyval = GDK_KEY_2;
80 break;
81 case GDK_KEY_KP_Page_Down:
82 new_keyval = GDK_KEY_3;
83 break;
84 case GDK_KEY_KP_Left:
85 new_keyval = GDK_KEY_4;
86 break;
87 case GDK_KEY_KP_Begin: /* This is apparently what "5" does when numlock is off. */
88 new_keyval = GDK_KEY_5;
89 break;
90 case GDK_KEY_KP_Right:
91 new_keyval = GDK_KEY_6;
92 break;
93 case GDK_KEY_KP_Home:
94 new_keyval = GDK_KEY_7;
95 break;
96 case GDK_KEY_KP_Up:
97 new_keyval = GDK_KEY_8;
98 break;
99 case GDK_KEY_KP_Page_Up:
100 new_keyval = GDK_KEY_9;
101 break;
104 if (new_keyval) {
105 gboolean result;
106 GdkEvent *new_event;
108 new_event = gdk_event_copy((GdkEvent *)event);
109 ((GdkEventKey *)new_event)->keyval = new_keyval;
110 g_signal_emit_by_name(widget, "key-press-event", new_event, &result);
111 gdk_event_free(new_event);
112 return result;
115 state = event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK);
116 c = gdk_keyval_to_unicode(event->keyval);
118 /* Solve on enter */
119 if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) {
120 math_equation_solve(display->priv->equation);
121 return TRUE;
124 /* Clear on escape */
125 if ((event->keyval == GDK_KEY_Escape && state == 0) ||
126 (event->keyval == GDK_KEY_BackSpace && state == GDK_CONTROL_MASK) ||
127 (event->keyval == GDK_KEY_Delete && state == GDK_SHIFT_MASK)) {
128 math_equation_clear(display->priv->equation);
129 return TRUE;
132 /* Numeric keypad will often insert '.' regardless of locale */
133 if (event->keyval == GDK_KEY_KP_Decimal) {
134 math_equation_insert_numeric_point(display->priv->equation);
135 return TRUE;
138 /* Substitute */
139 if (state == 0) {
140 if (c == '*') {
141 math_equation_insert(display->priv->equation, "×");
142 return TRUE;
144 if (c == '/') {
145 math_equation_insert(display->priv->equation, "÷");
146 return TRUE;
148 if (c == '-') {
149 math_equation_insert_subtract(display->priv->equation);
150 return TRUE;
154 /* Shortcuts */
155 if (state == GDK_CONTROL_MASK) {
156 switch(event->keyval)
158 case GDK_KEY_bracketleft:
159 math_equation_insert(display->priv->equation, "⌈");
160 return TRUE;
161 case GDK_KEY_bracketright:
162 math_equation_insert(display->priv->equation, "⌉");
163 return TRUE;
164 case GDK_KEY_e:
165 math_equation_insert_exponent(display->priv->equation);
166 return TRUE;
167 case GDK_KEY_f:
168 math_equation_factorize(display->priv->equation);
169 return TRUE;
170 case GDK_KEY_i:
171 math_equation_insert(display->priv->equation, "⁻¹");
172 return TRUE;
173 case GDK_KEY_p:
174 math_equation_insert(display->priv->equation, "π");
175 return TRUE;
176 case GDK_KEY_r:
177 math_equation_insert(display->priv->equation, "√");
178 return TRUE;
179 case GDK_KEY_u:
180 math_equation_insert(display->priv->equation, "µ");
181 return TRUE;
182 case GDK_KEY_minus:
183 math_equation_insert(display->priv->equation, "⁻");
184 return TRUE;
185 case GDK_KEY_apostrophe:
186 math_equation_insert(display->priv->equation, "°");
187 return TRUE;
190 if (state == GDK_MOD1_MASK) {
191 switch(event->keyval)
193 case GDK_KEY_bracketleft:
194 math_equation_insert(display->priv->equation, "⌊");
195 return TRUE;
196 case GDK_KEY_bracketright:
197 math_equation_insert(display->priv->equation, "⌋");
198 return TRUE;
202 if (state == GDK_CONTROL_MASK || math_equation_get_number_mode(display->priv->equation) == SUPERSCRIPT) {
203 switch(event->keyval)
205 case GDK_KEY_0:
206 case GDK_KEY_KP_0:
207 math_equation_insert(display->priv->equation, "⁰");
208 return TRUE;
209 case GDK_KEY_1:
210 case GDK_KEY_KP_1:
211 math_equation_insert(display->priv->equation, "¹");
212 return TRUE;
213 case GDK_KEY_2:
214 case GDK_KEY_KP_2:
215 math_equation_insert(display->priv->equation, "²");
216 return TRUE;
217 case GDK_KEY_3:
218 case GDK_KEY_KP_3:
219 math_equation_insert(display->priv->equation, "³");
220 return TRUE;
221 case GDK_KEY_4:
222 case GDK_KEY_KP_4:
223 math_equation_insert(display->priv->equation, "⁴");
224 return TRUE;
225 case GDK_KEY_5:
226 case GDK_KEY_KP_5:
227 math_equation_insert(display->priv->equation, "⁵");
228 return TRUE;
229 case GDK_KEY_6:
230 case GDK_KEY_KP_6:
231 math_equation_insert(display->priv->equation, "⁶");
232 return TRUE;
233 case GDK_KEY_7:
234 case GDK_KEY_KP_7:
235 math_equation_insert(display->priv->equation, "⁷");
236 return TRUE;
237 case GDK_KEY_8:
238 case GDK_KEY_KP_8:
239 math_equation_insert(display->priv->equation, "⁸");
240 return TRUE;
241 case GDK_KEY_9:
242 case GDK_KEY_KP_9:
243 math_equation_insert(display->priv->equation, "⁹");
244 return TRUE;
247 else if (state == GDK_MOD1_MASK || math_equation_get_number_mode(display->priv->equation) == SUBSCRIPT) {
248 switch(event->keyval)
250 case GDK_KEY_0:
251 case GDK_KEY_KP_0:
252 math_equation_insert(display->priv->equation, "₀");
253 return TRUE;
254 case GDK_KEY_1:
255 case GDK_KEY_KP_1:
256 math_equation_insert(display->priv->equation, "₁");
257 return TRUE;
258 case GDK_KEY_2:
259 case GDK_KEY_KP_2:
260 math_equation_insert(display->priv->equation, "₂");
261 return TRUE;
262 case GDK_KEY_3:
263 case GDK_KEY_KP_3:
264 math_equation_insert(display->priv->equation, "₃");
265 return TRUE;
266 case GDK_KEY_4:
267 case GDK_KEY_KP_4:
268 math_equation_insert(display->priv->equation, "₄");
269 return TRUE;
270 case GDK_KEY_5:
271 case GDK_KEY_KP_5:
272 math_equation_insert(display->priv->equation, "₅");
273 return TRUE;
274 case GDK_KEY_6:
275 case GDK_KEY_KP_6:
276 math_equation_insert(display->priv->equation, "₆");
277 return TRUE;
278 case GDK_KEY_7:
279 case GDK_KEY_KP_7:
280 math_equation_insert(display->priv->equation, "₇");
281 return TRUE;
282 case GDK_KEY_8:
283 case GDK_KEY_KP_8:
284 math_equation_insert(display->priv->equation, "₈");
285 return TRUE;
286 case GDK_KEY_9:
287 case GDK_KEY_KP_9:
288 math_equation_insert(display->priv->equation, "₉");
289 return TRUE;
293 return FALSE;
297 static gboolean
298 key_press_cb(MathDisplay *display, GdkEventKey *event)
300 gboolean result;
301 g_signal_emit_by_name(display->priv->text_view, "key-press-event", event, &result);
302 return result;
306 static void
307 status_changed_cb(MathEquation *equation, GParamSpec *spec, MathDisplay *display)
309 gtk_text_buffer_set_text(display->priv->info_buffer, math_equation_get_status(equation), -1);
310 if (math_equation_in_solve(equation) && !gtk_widget_get_visible(display->priv->spinner)) {
311 gtk_widget_show(display->priv->spinner);
312 gtk_spinner_start(GTK_SPINNER(display->priv->spinner));
314 else if (!math_equation_in_solve(equation) && gtk_widget_get_visible(display->priv->spinner)) {
315 gtk_widget_hide(display->priv->spinner);
316 gtk_spinner_stop(GTK_SPINNER(display->priv->spinner));
321 static void
322 create_gui(MathDisplay *display)
324 GtkWidget *info_view, *info_box, *main_box;
325 PangoFontDescription *font_desc;
326 int i;
327 GtkStyle *style;
329 main_box = gtk_vbox_new(false, 0);
330 gtk_container_add(GTK_CONTAINER(display), main_box);
332 g_signal_connect(display, "key-press-event", G_CALLBACK(key_press_cb), display);
334 display->priv->text_view = gtk_text_view_new_with_buffer(GTK_TEXT_BUFFER(display->priv->equation));
335 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(display->priv->text_view), GTK_WRAP_WORD);
336 gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(display->priv->text_view), FALSE);
337 gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(display->priv->text_view), 8);
338 gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(display->priv->text_view), 2);
339 /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug #482688 */
340 /*gtk_text_view_set_right_margin(GTK_TEXT_VIEW(display->priv->text_view), 6);*/
341 gtk_text_view_set_justification(GTK_TEXT_VIEW(display->priv->text_view), GTK_JUSTIFY_RIGHT);
342 gtk_widget_ensure_style(display->priv->text_view);
343 font_desc = pango_font_description_copy(gtk_widget_get_style(display->priv->text_view)->font_desc);
344 pango_font_description_set_size(font_desc, 16 * PANGO_SCALE);
345 gtk_widget_modify_font(display->priv->text_view, font_desc);
346 pango_font_description_free(font_desc);
347 gtk_widget_set_name(display->priv->text_view, "displayitem");
348 atk_object_set_role(gtk_widget_get_accessible(display->priv->text_view), ATK_ROLE_EDITBAR);
349 //FIXME:<property name="AtkObject::accessible-description" translatable="yes" comments="Accessible description for the area in which results are displayed">Result Region</property>
350 g_signal_connect(display->priv->text_view, "key-press-event", G_CALLBACK(display_key_press_cb), display);
351 gtk_box_pack_start(GTK_BOX(main_box), display->priv->text_view, TRUE, TRUE, 0);
353 info_box = gtk_hbox_new(false, 6);
354 gtk_box_pack_start(GTK_BOX(main_box), info_box, FALSE, TRUE, 0);
356 info_view = gtk_text_view_new();
357 gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(info_view), GTK_WRAP_WORD);
358 gtk_widget_set_can_focus(info_view, TRUE); // FIXME: This should be FALSE but it locks the cursor inside the main view for some reason
359 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(info_view), FALSE); // FIXME: Just here so when incorrectly gets focus doesn't look editable
360 gtk_text_view_set_editable(GTK_TEXT_VIEW(info_view), FALSE);
361 gtk_text_view_set_justification(GTK_TEXT_VIEW(info_view), GTK_JUSTIFY_RIGHT);
362 /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug #482688 */
363 /*gtk_text_view_set_right_margin(GTK_TEXT_VIEW(info_view), 6);*/
364 gtk_box_pack_start(GTK_BOX(info_box), info_view, TRUE, TRUE, 0);
365 display->priv->info_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(info_view));
367 display->priv->spinner = gtk_spinner_new();
368 gtk_box_pack_end(GTK_BOX(info_box), display->priv->spinner, FALSE, FALSE, 0);
369 style = gtk_widget_get_style(info_view);
370 for (i = 0; i < 5; i++) {
371 gtk_widget_modify_bg(GTK_WIDGET(display), i, &style->base[i]);
374 gtk_widget_show(info_box);
375 gtk_widget_show(info_view);
376 gtk_widget_show(display->priv->text_view);
377 gtk_widget_show(main_box);
379 g_signal_connect(display->priv->equation, "notify::status", G_CALLBACK(status_changed_cb), display);
380 status_changed_cb(display->priv->equation, NULL, display);
384 static void
385 math_display_set_property(GObject *object,
386 guint prop_id,
387 const GValue *value,
388 GParamSpec *pspec)
390 MathDisplay *self;
392 self = MATH_DISPLAY(object);
394 switch (prop_id) {
395 case PROP_EQUATION:
396 self->priv->equation = g_value_get_object(value);
397 create_gui(self);
398 break;
399 default:
400 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
401 break;
406 static void
407 math_display_get_property(GObject *object,
408 guint prop_id,
409 GValue *value,
410 GParamSpec *pspec)
412 MathDisplay *self;
414 self = MATH_DISPLAY(object);
416 switch (prop_id) {
417 case PROP_EQUATION:
418 g_value_set_object(value, self->priv->equation);
419 break;
420 default:
421 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
422 break;
427 static void
428 math_display_class_init(MathDisplayClass *klass)
430 GObjectClass *object_class = G_OBJECT_CLASS(klass);
432 object_class->get_property = math_display_get_property;
433 object_class->set_property = math_display_set_property;
435 g_type_class_add_private(klass, sizeof(MathDisplayPrivate));
437 g_object_class_install_property(object_class,
438 PROP_EQUATION,
439 g_param_spec_object("equation",
440 "equation",
441 "Equation being displayed",
442 math_equation_get_type(),
443 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
447 static void
448 math_display_init(MathDisplay *display)
450 display->priv = G_TYPE_INSTANCE_GET_PRIVATE(display, math_display_get_type(), MathDisplayPrivate);