Add a unit-manager object to track units
[gcalctool.git] / src / math-converter.c
blobfeb2f73999ab538a67d83bc6c14083a5153ec365
1 /* Copyright (c) 2008-2009 Robert Ancell
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2, or (at your option)
6 * any later version.
8 * This program is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public License
14 * along with this program; if not, write to the Free Software
15 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
16 * 02111-1307, USA.
19 #include <glib/gi18n.h>
21 #include "math-converter.h"
22 #include "mp-serializer.h"
23 #include "unit-manager.h"
24 #include "currency.h"
26 enum {
27 PROP_0,
28 PROP_EQUATION
31 struct MathConverterPrivate
33 MathEquation *equation;
35 GtkWidget *from_combo;
36 GtkWidget *to_combo;
38 GtkWidget *result_label;
39 MpSerializer *serializer;
42 #define MAX_UNITS 20
43 struct Unit {
44 char *ui_name;
45 char *internal_name;
48 struct UnitCategory {
49 char *name;
50 struct Unit units[MAX_UNITS];
53 static struct UnitCategory categories[] = {
54 {N_("Angle"), {
55 /* Angle unit */
56 {N_("Degrees"), "degrees"},
57 /* Angle unit */
58 {N_("Radians"), "radians"},
59 /* Angle unit */
60 {N_("Gradians"), "gradians"},
61 {NULL, NULL}}},
62 {N_("Length"), {
63 /* Length unit */
64 {N_("Parsecs"), "parsecs"},
65 /* Length unit */
66 {N_("Light Years"), "lightyears"},
67 /* Length unit */
68 {N_("Astronomical Units"), "au"},
69 /* Length unit */
70 {N_("Nautical Miles"), "nm"},
71 /* Length unit */
72 {N_("Miles"), "miles"},
73 /* Length unit */
74 {N_("Kilometers"), "kilometers"},
75 /* Length unit */
76 {N_("Cables"), "cables"},
77 /* Length unit */
78 {N_("Fathoms"), "fathoms"},
79 /* Length unit */
80 {N_("Meters"), "meters"},
81 /* Length unit */
82 {N_("Yards"), "yards"},
83 /* Length unit */
84 {N_("Feet"), "feet"},
85 /* Length unit */
86 {N_("Inches"), "inches"},
87 /* Length unit */
88 {N_("Centimeters"), "centimeters"},
89 /* Length unit */
90 {N_("Millimeters"), "millimeters"},
91 /* Length unit */
92 {N_("Micrometers"), "micrometers"},
93 /* Length unit */
94 {N_("Nanometers"), "nanometers"},
95 {NULL, NULL}}},
96 {N_("Area"), {
97 /* Area unit */
98 {N_("Hectares"), "hectares"},
99 /* Area unit */
100 {N_("Acres"), "acres"},
101 /* Area unit */
102 {N_("m²"), "m²"},
103 /* Area unit */
104 {N_("cm²"), "cm²"},
105 /* Area unit */
106 {N_("mm²"), "mm²"},
107 {NULL, NULL}}},
108 {N_("Volume"), {
109 /* Volume unit */
110 {N_("m³"), "m³"},
111 /* Volume unit */
112 {N_("Gallons"), "gallons"},
113 /* Volume unit */
114 {N_("Liters"), "liters"},
115 /* Volume unit */
116 {N_("Quarts"), "quarts"},
117 /* Volume unit */
118 {N_("Pints"), "pints"},
119 /* Volume unit */
120 {N_("Milliliters"), "milliliters"},
121 /* Volume unit */
122 {N_("cm³"), "cm³"},
123 /* Volume unit */
124 {N_("mm³"), "mm³"},
125 {NULL, NULL}}},
126 {N_("Weight"), {
127 /* Weight unit */
128 {N_("Tonnes"), "tonnes"},
129 /* Weight unit */
130 {N_("Kilograms"), "kilograms"},
131 /* Weight unit */
132 {N_("Pounds"), "pounds"},
133 /* Weight unit */
134 {N_("Ounces"), "ounces"},
135 /* Weight unit */
136 {N_("Grams"), "grams"},
137 {NULL, NULL}}},
138 {N_("Duration"), {
139 /* Time unit */
140 {N_("Years"), "years"},
141 /* Time unit */
142 {N_("Days"), "days"},
143 /* Time unit */
144 {N_("Hours"), "hours"},
145 /* Time unit */
146 {N_("Minutes"), "minutes"},
147 /* Time unit */
148 {N_("Seconds"), "seconds"},
149 /* Time unit */
150 {N_("Milliseconds"), "milliseconds"},
151 /* Time unit */
152 {N_("Microseconds"), "microseconds"},
153 {NULL, NULL}}}
156 G_DEFINE_TYPE (MathConverter, math_converter, GTK_TYPE_HBOX);
159 MathConverter *
160 math_converter_new(MathEquation *equation)
162 return g_object_new(math_converter_get_type(), "equation", equation, NULL);
166 void
167 math_converter_set_category(MathEquation *equation, const gchar *category)
172 const gchar *
173 math_converter_get_category(MathEquation *equation)
175 return NULL;
179 static void
180 update_result_label(MathConverter *converter)
182 MPNumber x, z;
183 gboolean enabled;
184 gchar *label;
185 const gchar *source_units, *target_units;
186 char *source_value, *target_value;
188 if (!converter->priv->result_label)
189 return;
191 enabled = math_equation_get_number(converter->priv->equation, &x);
193 source_units = math_equation_get_source_units(converter->priv->equation);
194 target_units = math_equation_get_target_units(converter->priv->equation);
195 if (!source_units || !target_units)
196 enabled = FALSE;
197 else if (!unit_manager_convert(math_equation_get_unit_manager(converter->priv->equation), &x, source_units, target_units, &z)) {
198 if (!currency_convert(&x, source_units, target_units, &z))
199 enabled = FALSE;
202 gtk_widget_set_sensitive(converter->priv->result_label, enabled);
203 if (!enabled)
204 return;
206 source_value = mp_serializer_to_string(converter->priv->serializer, &x);
207 target_value = mp_serializer_to_string(converter->priv->serializer, &z);
209 // FIXME: Use currency symbols for currency
210 label = g_strdup_printf("%s %s = %s %s", source_value, source_units, target_value, target_units);
211 gtk_label_set_text(GTK_LABEL(converter->priv->result_label), label);
212 g_free(source_value);
213 g_free(target_value);
214 g_free(label);
218 static void
219 source_units_changed_cb(MathEquation *equation, GParamSpec *spec, MathConverter *converter)
221 GtkTreeModel *model;
222 GtkTreeIter iter;
224 model = gtk_combo_box_get_model(GTK_COMBO_BOX(converter->priv->from_combo));
225 if (!gtk_tree_model_get_iter_first(model, &iter))
226 return;
227 do {
228 GtkTreeIter child_iter;
230 if (gtk_tree_model_iter_children(model, &child_iter, &iter)) {
231 do {
232 gint i, j;
234 gtk_tree_model_get(model, &child_iter, 1, &i, 2, &j, -1);
235 if ((i == -1 && strcmp(currency_info[j].short_name, math_equation_get_source_units(equation)) == 0) ||
236 (i >= 0 && strcmp(categories[i].units[j].internal_name, math_equation_get_source_units(equation)) == 0)) {
237 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(converter->priv->from_combo), &child_iter);
238 update_result_label(converter);
239 return;
241 } while (gtk_tree_model_iter_next(model, &child_iter));
243 } while (gtk_tree_model_iter_next(model, &iter));
247 static void
248 target_units_changed_cb(MathEquation *equation, GParamSpec *spec, MathConverter *converter)
250 GtkTreeModel *model;
251 GtkTreeIter iter;
253 model = gtk_combo_box_get_model(GTK_COMBO_BOX(converter->priv->to_combo));
254 if (!gtk_tree_model_get_iter_first(model, &iter))
255 return;
256 do {
257 gint i, j;
259 gtk_tree_model_get(model, &iter, 1, &i, 2, &j, -1);
260 if ((i == -1 && strcmp(currency_info[j].short_name, math_equation_get_source_units(equation)) == 0) ||
261 (i >= 0 && strcmp(categories[i].units[j].internal_name, math_equation_get_source_units(equation)) == 0)) {
262 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(converter->priv->to_combo), &iter);
263 update_result_label(converter);
264 return;
266 } while (gtk_tree_model_iter_next(model, &iter));
270 static void
271 display_changed_cb(MathEquation *equation, GParamSpec *spec, MathConverter *converter)
273 update_result_label(converter);
277 static void
278 math_converter_set_property(GObject *object,
279 guint prop_id,
280 const GValue *value,
281 GParamSpec *pspec)
283 MathConverter *self;
285 self = MATH_CONVERTER(object);
287 switch (prop_id) {
288 case PROP_EQUATION:
289 self->priv->equation = g_value_get_object(value);
290 g_signal_connect(self->priv->equation, "notify::source-units", G_CALLBACK(source_units_changed_cb), self);
291 g_signal_connect(self->priv->equation, "notify::target-units", G_CALLBACK(target_units_changed_cb), self);
292 g_signal_connect(self->priv->equation, "notify::display", G_CALLBACK(display_changed_cb), self);
293 source_units_changed_cb(self->priv->equation, NULL, self);
294 target_units_changed_cb(self->priv->equation, NULL, self);
295 display_changed_cb(self->priv->equation, NULL, self);
296 break;
297 default:
298 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
299 break;
304 static void
305 math_converter_get_property(GObject *object,
306 guint prop_id,
307 GValue *value,
308 GParamSpec *pspec)
310 MathConverter *self;
312 self = MATH_CONVERTER(object);
314 switch (prop_id) {
315 case PROP_EQUATION:
316 g_value_set_object(value, self->priv->equation);
317 break;
318 default:
319 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
320 break;
325 static void
326 math_converter_class_init(MathConverterClass *klass)
328 GObjectClass *object_class = G_OBJECT_CLASS(klass);
330 object_class->get_property = math_converter_get_property;
331 object_class->set_property = math_converter_set_property;
333 g_type_class_add_private(klass, sizeof(MathConverterPrivate));
335 g_object_class_install_property(object_class,
336 PROP_EQUATION,
337 g_param_spec_object("equation",
338 "equation",
339 "Equation being controlled",
340 math_equation_get_type(),
341 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
345 static void
346 from_combobox_changed_cb(GtkWidget *combo, MathConverter *converter)
348 GtkTreeModel *model;
349 GtkTreeIter iter;
350 int category_index, unit_index;
351 const gchar *unit_name;
353 model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
354 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
355 gtk_tree_model_get(model, &iter, 1, &category_index, 2, &unit_index, -1);
357 model = GTK_TREE_MODEL(gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT));
359 if (category_index == -1) {
360 int i;
361 for (i = 0; currency_info[i].short_name != NULL; i++) {
362 if (i == unit_index)
363 continue;
364 gtk_list_store_append(GTK_LIST_STORE(model), &iter);
365 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, currency_info[i].short_name, 1, -1, 2, i, -1);
367 unit_name = currency_info[unit_index].short_name;
369 else {
370 int i;
371 for (i = 0; categories[category_index].units[i].ui_name != NULL; i++) {
372 if (i == unit_index)
373 continue;
374 gtk_list_store_append(GTK_LIST_STORE(model), &iter);
375 gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, _(categories[category_index].units[i].ui_name), 1, category_index, 2, i, -1);
377 unit_name = categories[category_index].units[unit_index].internal_name;
380 gtk_combo_box_set_model(GTK_COMBO_BOX(converter->priv->to_combo), model);
382 math_equation_set_source_units(converter->priv->equation, unit_name);
384 gtk_combo_box_set_active(GTK_COMBO_BOX(converter->priv->to_combo), 0);
388 static void
389 to_combobox_changed_cb(GtkWidget *combo, MathConverter *converter)
391 GtkTreeModel *model;
392 GtkTreeIter iter;
393 int category_index, unit_index;
395 model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo));
396 gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter);
397 gtk_tree_model_get(model, &iter, 1, &category_index, 2, &unit_index, -1);
398 if (category_index == -1)
399 math_equation_set_target_units(converter->priv->equation, currency_info[unit_index].short_name);
400 else
401 math_equation_set_target_units(converter->priv->equation, categories[category_index].units[unit_index].internal_name);
405 static void
406 from_cell_data_func(GtkCellLayout *cell_layout,
407 GtkCellRenderer *cell,
408 GtkTreeModel *tree_model,
409 GtkTreeIter *iter,
410 gpointer data)
412 g_object_set(cell, "sensitive", !gtk_tree_model_iter_has_child(tree_model, iter), NULL);
416 static void
417 math_converter_init(MathConverter *converter)
419 GtkWidget *hbox, *label;
420 GtkTreeStore *from_model;
421 GtkTreeIter parent;
422 GtkCellRenderer *renderer;
423 int i, j;
425 converter->priv = G_TYPE_INSTANCE_GET_PRIVATE(converter, math_converter_get_type(), MathConverterPrivate);
427 gtk_box_set_spacing(GTK_BOX(converter), 6);
429 hbox = gtk_hbox_new(FALSE, 0);
430 gtk_widget_show(hbox);
431 gtk_box_pack_start(GTK_BOX(converter), hbox, FALSE, TRUE, 0);
433 converter->priv->from_combo = gtk_combo_box_new ();
434 from_model = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
435 gtk_combo_box_set_model(GTK_COMBO_BOX(converter->priv->from_combo), GTK_TREE_MODEL(from_model));
437 for (i = 0; i < sizeof(categories) / sizeof(categories[0]); i++) {
438 gtk_tree_store_append(from_model, &parent, NULL);
439 gtk_tree_store_set(from_model, &parent, 0, _(categories[i].name), 1, i, -1);
440 for (j = 0; categories[i].units[j].ui_name != NULL; j++) {
441 GtkTreeIter iter;
443 gtk_tree_store_append(from_model, &iter, &parent);
444 gtk_tree_store_set(from_model, &iter, 0, _(categories[i].units[j].ui_name), 1, i, 2, j, -1);
448 gtk_tree_store_append(from_model, &parent, NULL);
449 gtk_tree_store_set(from_model, &parent, 0, _("Currency"), 1, i, -1);
450 for (i = 0; currency_info[i].short_name != NULL; i++) {
451 GtkTreeIter iter;
453 gtk_tree_store_append(from_model, &iter, &parent);
454 gtk_tree_store_set(from_model, &iter, 0, currency_info[i].short_name, 1, -1, 2, i, -1);
457 renderer = gtk_cell_renderer_text_new();
458 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(converter->priv->from_combo), renderer, TRUE);
459 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(converter->priv->from_combo), renderer, "text", 0);
460 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(converter->priv->from_combo),
461 renderer,
462 from_cell_data_func,
463 NULL, NULL);
464 g_signal_connect(converter->priv->from_combo, "changed", G_CALLBACK(from_combobox_changed_cb), converter);
465 gtk_widget_show(converter->priv->from_combo);
466 gtk_box_pack_start(GTK_BOX(hbox), converter->priv->from_combo, FALSE, TRUE, 0);
468 label = gtk_label_new(/* Label that is displayed between the two conversion combo boxes, e.g. "[degrees] in [radians]" */
469 _(" in "));
470 gtk_widget_show(label);
471 gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 0);
473 converter->priv->to_combo = gtk_combo_box_new();
474 renderer = gtk_cell_renderer_text_new();
475 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(converter->priv->to_combo), renderer, TRUE);
476 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(converter->priv->to_combo), renderer, "text", 0);
477 g_signal_connect(converter->priv->to_combo, "changed", G_CALLBACK(to_combobox_changed_cb), converter);
478 gtk_widget_show(converter->priv->to_combo);
479 gtk_box_pack_start(GTK_BOX(hbox), converter->priv->to_combo, FALSE, TRUE, 0);
481 converter->priv->result_label = gtk_label_new("");
482 gtk_misc_set_alignment(GTK_MISC(converter->priv->result_label), 1.0, 0.5);
483 gtk_widget_set_sensitive(converter->priv->result_label, FALSE);
484 gtk_widget_show(converter->priv->result_label);
485 gtk_box_pack_start(GTK_BOX(converter), converter->priv->result_label, TRUE, TRUE, 0);
487 converter->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 2);