From 6b24c91d3aa81fdb99500c8c2c12f830fabaefb6 Mon Sep 17 00:00:00 2001 From: Steve Zesch Date: Sat, 24 Nov 2012 20:44:45 -0500 Subject: Update codebase. --- src/Makefile.am | 147 ++++-- src/currency-manager.c | 626 +++++++++++++++++++++++ src/currency-manager.h | 47 ++ src/currency.c | 300 +++-------- src/currency.h | 103 ++-- src/financial.c | 26 +- src/financial.h | 46 +- src/lexer.c | 587 ++++++++++++++++++++++ src/lexer.h | 40 ++ src/mate-calc-cmd.c | 44 +- src/mate-calc.c | 433 +++++++++++++--- src/math-buttons.c | 864 ++++++++----------------------- src/math-buttons.h | 52 +- src/math-converter.c | 440 ++++++++++++++++ src/math-converter.h | 53 ++ src/math-display.c | 229 ++++++--- src/math-display.h | 42 +- src/math-equation.c | 1002 ++++++++++++++++++++++-------------- src/math-equation.h | 205 ++++---- src/math-preferences.c | 74 ++- src/math-preferences.h | 36 +- src/math-variable-popup.c | 308 ++++++++++++ src/math-variable-popup.h | 40 ++ src/math-variables.c | 64 ++- src/math-variables.h | 46 +- src/math-window.c | 502 +++++------------- src/math-window.h | 50 +- src/mp-binary.c | 70 +-- src/mp-convert.c | 246 +-------- src/mp-enums.c.template | 36 ++ src/mp-enums.h.template | 25 + src/mp-equation.c | 273 ++-------- src/mp-equation.h | 97 ++-- src/mp-private.h | 34 +- src/mp-serializer.c | 617 +++++++++++++++++++++++ src/mp-serializer.h | 79 +++ src/mp-trigonometric.c | 40 +- src/mp.c | 86 ++-- src/mp.h | 71 +-- src/parser.c | 1228 +++++++++++++++++++++++++++++++++++++++++++++ src/parser.h | 79 +++ src/parserfunc.c | 967 +++++++++++++++++++++++++++++++++++ src/parserfunc.h | 80 +++ src/prelexer.c | 214 ++++++++ src/prelexer.h | 93 ++++ src/test-mp-equation.c | 630 +++++++++++++++++++++++ src/test-mp.c | 240 +++++++++ src/unit-category.c | 136 +++++ src/unit-category.h | 56 +++ src/unit-manager.c | 268 ++++++++++ src/unit-manager.h | 54 ++ src/unit.c | 212 ++++++++ src/unit.h | 60 +++ 53 files changed, 9515 insertions(+), 2882 deletions(-) create mode 100644 src/currency-manager.c create mode 100644 src/currency-manager.h create mode 100644 src/lexer.c create mode 100644 src/lexer.h create mode 100644 src/math-converter.c create mode 100644 src/math-converter.h create mode 100644 src/math-variable-popup.c create mode 100644 src/math-variable-popup.h create mode 100644 src/mp-enums.c.template create mode 100644 src/mp-enums.h.template create mode 100644 src/mp-serializer.c create mode 100644 src/mp-serializer.h create mode 100644 src/parser.c create mode 100644 src/parser.h create mode 100644 src/parserfunc.c create mode 100644 src/parserfunc.h create mode 100644 src/prelexer.c create mode 100644 src/prelexer.h create mode 100644 src/test-mp-equation.c create mode 100644 src/test-mp.c create mode 100644 src/unit-category.c create mode 100644 src/unit-category.h create mode 100644 src/unit-manager.c create mode 100644 src/unit-manager.h create mode 100644 src/unit.c create mode 100644 src/unit.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index c01c037..03dbb78 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,4 +1,7 @@ bin_PROGRAMS = mate-calc mate-calc-cmd +noinst_PROGRAMS = test-mp test-mp-equation + +TESTS = test-mp test-mp-equation INCLUDES = \ -DUI_DIR=\""$(datadir)/mate-calc"\" \ @@ -9,11 +12,15 @@ INCLUDES = \ $(MATE_CALC_CFLAGS) mate_calc_SOURCES = \ + mate-calc.c \ currency.c \ currency.h \ - mate-calc.c \ + currency-manager.c \ + currency-manager.h \ math-buttons.c \ math-buttons.h \ + math-converter.c \ + math-converter.h \ math-display.c \ math-display.h \ math-equation.c \ @@ -22,61 +29,139 @@ mate_calc_SOURCES = \ math-preferences.h \ math-variables.c \ math-variables.h \ + math-variable-popup.c \ + math-variable-popup.h \ math-window.c \ math-window.h \ mp.c \ mp.h \ mp-binary.c \ mp-convert.c \ - mp-private.h \ - mp-trigonometric.c \ + mp-enums.c \ + mp-enums.h \ mp-equation.c \ mp-equation.h \ - mp-equation-private.h \ - mp-equation-lexer.c \ - mp-equation-lexer.h \ - mp-equation-parser.c \ - mp-equation-parser.h \ + mp-private.h \ + mp-serializer.c \ + mp-serializer.h \ + mp-trigonometric.c \ financial.c \ financial.h \ - unittest.c \ - unittest.h + unit.c \ + unit.h \ + unit-category.c \ + unit-category.h \ + unit-manager.c \ + unit-manager.h \ + prelexer.c \ + prelexer.h \ + lexer.c \ + lexer.h \ + parserfunc.c \ + parserfunc.h \ + parser.c \ + parser.h mate_calc_LDADD = \ - $(MATE_CALC_LIBS) + $(MATE_CALC_LIBS) mate_calc_cmd_SOURCES = \ mate-calc-cmd.c \ + currency.c \ + currency.h \ + currency-manager.c \ + currency-manager.h \ mp.c \ - mp-convert.c \ mp-binary.c \ - mp-trigonometric.c \ + mp-convert.c \ + mp-enums.c \ + mp-enums.h \ mp-equation.c \ - mp-equation-parser.c \ - mp-equation-lexer.c + mp-serializer.c \ + mp-serializer.h\ + mp-trigonometric.c \ + unit.c \ + unit.h \ + unit-category.c \ + unit-category.h \ + unit-manager.c \ + unit-manager.h \ + prelexer.c \ + prelexer.h \ + lexer.c \ + lexer.h \ + parserfunc.c \ + parserfunc.h \ + parser.c \ + parser.h mate_calc_cmd_LDADD = \ $(MATE_CALC_CMD_LIBS) \ -lm +test_mp_SOURCES = \ + test-mp.c \ + mp.c \ + mp-binary.c \ + mp-convert.c \ + mp-enums.c \ + mp-enums.h \ + mp-serializer.c \ + mp-serializer.h \ + mp-trigonometric.c + +test_mp_LDADD = \ + $(MATE_CALC_CMD_LIBS) \ + -lm + +test_mp_equation_SOURCES = \ + test-mp-equation.c \ + currency.c \ + currency.h \ + currency-manager.c \ + currency-manager.h \ + mp.c \ + mp-convert.c \ + mp-binary.c \ + mp-enums.c \ + mp-enums.h \ + mp-equation.c \ + mp-serializer.c \ + mp-serializer.h \ + mp-trigonometric.c \ + unit.c \ + unit.h \ + unit-category.c \ + unit-category.h \ + unit-manager.c \ + unit-manager.h \ + prelexer.c \ + prelexer.h \ + lexer.c \ + lexer.h \ + parserfunc.c \ + parserfunc.h \ + parser.c \ + parser.h + +test_mp_equation_LDADD = \ + $(MATE_CALC_CMD_LIBS) \ + -lm + CLEANFILES = \ - mp-equation-parser.h \ - mp-equation-parser.c \ - mp-equation-lexer.c \ - mp-equation-lexer.h + mp-enums.c \ + mp-enums.h -# Generate parser files -mp-equation-parser.c mp-equation-parser.h: mp-equation-parser.y mp-equation-lexer.h - $(AM_V_GEN)$(YACC) -d -o mp-equation-parser.c $(srcdir)/mp-equation-parser.y +# Generate enum types +mp-enums.h: mp-enums.h.template mp-serializer.h + $(AM_V_GEN)$(GLIB_MKENUMS) --template $(srcdir)/mp-enums.h.template $(srcdir)/mp-serializer.h > mp-enums.h -# Generate lexer files -mp-equation-lexer.c mp-equation-lexer.h: mp-equation-lexer.l - $(AM_V_GEN)$(LEX) $(srcdir)/mp-equation-lexer.l +mp-enums.c: mp-enums.c.template mp-enums.h mp-serializer.h + $(AM_V_GEN)$(GLIB_MKENUMS) --template $(srcdir)/mp-enums.c.template $(srcdir)/mp-serializer.h > mp-enums.c -# Rebuild parser when source files change -mp-equation-parser.o: mp-equation-lexer.h -mp-equation-lexer.o: mp-equation-parser.h -mp-equation.c: mp-equation-lexer.h mp-equation-parser.h +# Fix dependencies +math-serializer.c: mp-enums.h +math-equation.c: mp-enums.h # Install a symlink between mate-calc and mate-calculator install-exec-hook: @@ -89,8 +174,8 @@ uninstall-local: && rm -f "$(DESTDIR)$(bindir)/mate-calculator" EXTRA_DIST = \ - mp-equation-parser.y \ - mp-equation-lexer.l + mp-enums.c.template \ + mp-enums.h.template DISTCLEANFILES = \ Makefile.in diff --git a/src/currency-manager.c b/src/currency-manager.c new file mode 100644 index 0000000..cd57ab6 --- /dev/null +++ b/src/currency-manager.c @@ -0,0 +1,626 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "currency-manager.h" +#include "mp.h" + +typedef struct { + char *short_name; + char *symbol; + char *long_name; +} CurrencyInfo; +static const CurrencyInfo currency_info[] = { + {"AED", "إ.د", N_("UAE Dirham")}, + {"AUD", "$", N_("Australian Dollar")}, + {"BGN", "лв", N_("Bulgarian Lev")}, + {"BHD", ".ب.د", N_("Bahraini Dinar")}, + {"BND", "$", N_("Brunei Dollar")}, + {"BRL", "R$", N_("Brazilian Real")}, + {"BWP", "P", N_("Botswana Pula")}, + {"CAD", "$", N_("Canadian Dollar")}, + {"CFA", "Fr", N_("CFA Franc")}, + {"CHF", "Fr", N_("Swiss Franc")}, + {"CLP", "$", N_("Chilean Peso")}, + {"CNY", "元", N_("Chinese Yuan")}, + {"COP", "$", N_("Colombian Peso")}, + {"CZK", "Kč", N_("Czech Koruna")}, + {"DKK", "kr", N_("Danish Krone")}, + {"DZD", "ج.د", N_("Algerian Dinar")}, + {"EEK", "KR", N_("Estonian Kroon")}, + {"EUR", "€", N_("Euro")}, + {"GBP", "£", N_("Pound Sterling")}, + {"HKD", "$", N_("Hong Kong Dollar")}, + {"HRK", "kn", N_("Croatian Kuna")}, + {"HUF", "Ft", N_("Hungarian Forint")}, + {"IDR", "Rp", N_("Indonesian Rupiah")}, + {"ILS", "₪", N_("Israeli New Shekel")}, + {"INR", "₹", N_("Indian Rupee")}, + {"IRR", "﷼", N_("Iranian Rial")}, + {"ISK", "kr", N_("Icelandic Krona")}, + {"JPY", "¥", N_("Japanese Yen")}, + {"KRW", "₩", N_("South Korean Won")}, + {"KWD", "ك.د", N_("Kuwaiti Dinar")}, + {"KZT", "₸", N_("Kazakhstani Tenge")}, + {"LKR", "Rs", N_("Sri Lankan Rupee")}, + {"LTL", "Lt", N_("Lithuanian Litas")}, + {"LVL", "Ls", N_("Latvian Lats")}, + {"LYD", "د.ل", N_("Libyan Dinar")}, + {"MUR", "Rs", N_("Mauritian Rupee")}, + {"MXN", "$", N_("Mexican Peso")}, + {"MYR", "RM", N_("Malaysian Ringgit")}, + {"NOK", "kr", N_("Norwegian Krone")}, + {"NPR", "Rs", N_("Nepalese Rupee")}, + {"NZD", "$", N_("New Zealand Dollar")}, + {"OMR", "ع.ر.", N_("Omani Rial")}, + {"PEN", "S/.", N_("Peruvian Nuevo Sol")}, + {"PHP", "₱", N_("Philippine Peso")}, + {"PKR", "Rs", N_("Pakistani Rupee")}, + {"PLN", "zł", N_("Polish Zloty")}, + {"QAR", "ق.ر", N_("Qatari Riyal")}, + {"RON", "L", N_("New Romanian Leu")}, + {"RUB", "руб.", N_("Russian Rouble")}, + {"SAR", "س.ر", N_("Saudi Riyal")}, + {"SEK", "kr", N_("Swedish Krona")}, + {"SGD", "$", N_("Singapore Dollar")}, + {"THB", "฿", N_("Thai Baht")}, + {"TND", "ت.د", N_("Tunisian Dinar")}, + {"TRY", "TL", N_("New Turkish Lira")}, + {"TTD", "$", N_("T&T Dollar (TTD)")}, + {"USD", "$", N_("US Dollar")}, + {"UYU", "$", N_("Uruguayan Peso")}, + {"VEF", "Bs F", N_("Venezuelan Bolívar")}, + {"ZAR", "R", N_("South African Rand")}, + {NULL, NULL} +}; + +static gboolean downloading_imf_rates = FALSE, downloading_ecb_rates = FALSE; +static gboolean loaded_rates = FALSE; +static gboolean load_rates(CurrencyManager *manager); + +struct CurrencyManagerPrivate +{ + GList *currencies; +}; + +G_DEFINE_TYPE (CurrencyManager, currency_manager, G_TYPE_OBJECT); + + +enum { + UPDATED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +static CurrencyManager *default_currency_manager = NULL; + + +CurrencyManager * +currency_manager_get_default(void) +{ + int i; + + if (default_currency_manager) + return default_currency_manager; + + default_currency_manager = g_object_new(currency_manager_get_type(), NULL); + + for (i = 0; currency_info[i].short_name; i++) { + Currency *c = currency_new(currency_info[i].short_name, + _(currency_info[i].long_name), + currency_info[i].symbol); + default_currency_manager->priv->currencies = g_list_append(default_currency_manager->priv->currencies, c); + } + + return default_currency_manager; +} + + +GList * +currency_manager_get_currencies(CurrencyManager *manager) +{ + g_return_val_if_fail(manager != NULL, NULL); + return manager->priv->currencies; +} + + +Currency * +currency_manager_get_currency(CurrencyManager *manager, const gchar *name) +{ + g_return_val_if_fail(manager != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + GList *link; + for (link = manager->priv->currencies; link; link = link->next) { + Currency *c = link->data; + const MPNumber *value; + + value = currency_get_value(c); + + if (!strcmp(name, currency_get_name(c))) { + if (mp_is_negative(value) || + mp_is_zero(value)) { + return NULL; + } + else + return c; + } + } + return NULL; +} + + +static char * +get_imf_rate_filepath() +{ + return g_build_filename(g_get_user_cache_dir (), + "mate-calc", + "rms_five.xls", + NULL); +} + + +static char * +get_ecb_rate_filepath() +{ + return g_build_filename(g_get_user_cache_dir (), + "mate-calc", + "eurofxref-daily.xml", + NULL); +} + + +static Currency * +add_currency(CurrencyManager *manager, const gchar *short_name) +{ + GList *iter; + Currency *c; + + for (iter = manager->priv->currencies; iter; iter = iter->next) { + c = iter->data; + if (strcmp(short_name, currency_get_name(c)) == 0) + return c; + } + + g_warning("Currency %s is not in the currency table", short_name); + c = currency_new(short_name, short_name, short_name); + manager->priv->currencies = g_list_append(manager->priv->currencies, c); + + return c; +} + + +/* A file needs to be redownloaded if it doesn't exist, or is too old. + * When an error occur, it probably won't hurt to try to download again. + */ +static gboolean +file_needs_update(gchar *filename, double max_age) +{ + struct stat buf; + + if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) + return TRUE; + + if (g_stat(filename, &buf) == -1) + return TRUE; + + if (difftime(time(NULL), buf.st_mtime) > max_age) + return TRUE; + + return FALSE; +} + + +static void +download_imf_cb(GObject *object, GAsyncResult *result, gpointer user_data) +{ + CurrencyManager *manager = user_data; + GError *error = NULL; + + if (g_file_copy_finish(G_FILE(object), result, &error)) + g_debug("IMF rates updated"); + else + g_warning("Couldn't download IMF currency rate file: %s", error->message); + g_clear_error(&error); + downloading_imf_rates = FALSE; + load_rates(manager); +} + + +static void +download_ecb_cb(GObject *object, GAsyncResult *result, gpointer user_data) +{ + CurrencyManager *manager = user_data; + GError *error = NULL; + + if (g_file_copy_finish(G_FILE(object), result, &error)) + g_debug("ECB rates updated"); + else + g_warning("Couldn't download ECB currency rate file: %s", error->message); + g_clear_error(&error); + downloading_ecb_rates = FALSE; + load_rates(manager); +} + + +static void +download_file(CurrencyManager *manager, gchar *uri, gchar *filename, GAsyncReadyCallback callback) +{ + gchar *directory; + GFile *source, *dest; + + directory = g_path_get_dirname(filename); + g_mkdir_with_parents(directory, 0755); + g_free(directory); + + source = g_file_new_for_uri(uri); + dest = g_file_new_for_path(filename); + + g_file_copy_async(source, dest, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, callback, manager); + g_object_unref(source); + g_object_unref(dest); +} + + +static void +load_imf_rates(CurrencyManager *manager) +{ + gchar *filename; + gchar *data, **lines; + gsize length; + GError *error = NULL; + int i; + gboolean result, in_data = FALSE; + struct + { + const gchar *name, *symbol; + } name_map[] = + { + {"Euro", "EUR"}, + {"Japanese Yen", "JPY"}, + {"U.K. Pound Sterling", "GBP"}, + {"U.S. Dollar", "USD"}, + {"Algerian Dinar", "DZD"}, + {"Australian Dollar", "AUD"}, + {"Bahrain Dinar", "BHD"}, + {"Botswana Pula", "BWP"}, + {"Brazilian Real", "BRL"}, + {"Brunei Dollar", "BND"}, + {"Canadian Dollar", "CAD"}, + {"Chilean Peso", "CLP"}, + {"Chinese Yuan", "CNY"}, + {"Colombian Peso", "COP"}, + {"Czech Koruna", "CZK"}, + {"Danish Krone", "DKK"}, + {"Hungarian Forint", "HUF"}, + {"Icelandic Krona", "ISK"}, + {"Indian Rupee", "INR"}, + {"Indonesian Rupiah", "IDR"}, + {"Iranian Rial", "IRR"}, + {"Israeli New Sheqel", "ILS"}, + {"Kazakhstani Tenge", "KZT"}, + {"Korean Won", "KRW"}, + {"Kuwaiti Dinar", "KWD"}, + {"Libyan Dinar", "LYD"}, + {"Malaysian Ringgit", "MYR"}, + {"Mauritian Rupee", "MUR"}, + {"Mexican Peso", "MXN"}, + {"Nepalese Rupee", "NPR"}, + {"New Zealand Dollar", "NZD"}, + {"Norwegian Krone", "NOK"}, + {"Rial Omani", "OMR"}, + {"Pakistani Rupee", "PKR"}, + {"Nuevo Sol", "PEN"}, + {"Philippine Peso", "PHP"}, + {"Polish Zloty", "PLN"}, + {"Qatar Riyal", "QAR"}, + {"Russian Ruble", "RUB"}, + {"Saudi Arabian Riyal", "SAR"}, + {"Singapore Dollar", "SGD"}, + {"South African Rand", "ZAR"}, + {"Sri Lanka Rupee", "LKR"}, + {"Swedish Krona", "SEK"}, + {"Swiss Franc", "CHF"}, + {"Thai Baht", "THB"}, + {"Trinidad And Tobago Dollar", "TTD"}, + {"Tunisian Dinar", "TND"}, + {"U.A.E. Dirham", "AED"}, + {"Peso Uruguayo", "UYU"}, + {"Bolivar Fuerte", "VEF"}, + {NULL, NULL} + }; + + filename = get_imf_rate_filepath(); + result = g_file_get_contents(filename, &data, &length, &error); + g_free(filename); + if (!result) + { + g_warning("Failed to read exchange rates: %s", error->message); + g_clear_error(&error); + return; + } + + lines = g_strsplit(data, "\n", 0); + g_free(data); + + for (i = 0; lines[i]; i++) { + gchar *line, **tokens; + + line = g_strchug(lines[i]); + + /* Start after first blank line, stop on next */ + if (line[0] == '\0') { + if (!in_data) { + in_data = TRUE; + continue; + } + else + break; + } + if (!in_data) + continue; + + tokens = g_strsplit(line, "\t", 0); + if (strcmp(tokens[0], "Currency") != 0) { + gint value_index, name_index; + + for (value_index = 1; tokens[value_index]; value_index++) { + gchar *value = g_strchug (tokens[value_index]); + if (value[0] != '\0') + break; + } + if (tokens[value_index]) { + for (name_index = 0; name_map[name_index].name; name_index++) { + if (strcmp(name_map[name_index].name, tokens[0]) == 0) + break; + } + if (name_map[name_index].name) { + Currency *c = currency_manager_get_currency(manager, name_map[name_index].symbol); + MPNumber value; + + if (!c) { + g_debug ("Using IMF rate of %s for %s", tokens[value_index], name_map[name_index].symbol); + c = add_currency(manager, name_map[name_index].symbol); + } + mp_set_from_string(tokens[value_index], 10, &value); + mp_reciprocal(&value, &value); + currency_set_value(c, &value); + } + else + g_warning("Unknown currency '%s'", tokens[0]); + } + } + g_strfreev(tokens); + } + g_strfreev(lines); +} + + +static void +set_ecb_rate(CurrencyManager *manager, xmlNodePtr node, Currency *eur_rate) +{ + xmlAttrPtr attribute; + gchar *name = NULL, *value = NULL; + + for (attribute = node->properties; attribute; attribute = attribute->next) { + if (strcmp((char *)attribute->name, "currency") == 0) { + if (name) + xmlFree(name); + name = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute); + } else if (strcmp ((char *)attribute->name, "rate") == 0) { + if (value) + xmlFree(value); + value = (gchar *)xmlNodeGetContent((xmlNodePtr)attribute); + } + } + + /* Use data if value and no rate currently defined */ + if (name && value && !currency_manager_get_currency(manager, name)) { + Currency *c; + MPNumber r, v; + + g_debug ("Using ECB rate of %s for %s", value, name); + c = add_currency(manager, name); + mp_set_from_string(value, 10, &r); + mp_set_from_mp(currency_get_value(eur_rate), &v); + mp_multiply(&v, &r, &v); + currency_set_value(c, &v); + } + + if (name) + xmlFree(name); + if (value) + xmlFree(value); +} + + +static void +set_ecb_fixed_rate(CurrencyManager *manager, const gchar *name, const gchar *value, Currency *eur_rate) +{ + Currency *c; + MPNumber r, v; + + g_debug ("Using ECB fixed rate of %s for %s", value, name); + c = add_currency(manager, name); + mp_set_from_string(value, 10, &r); + mp_set_from_mp(currency_get_value(eur_rate), &v); + mp_divide(&v, &r, &v); + currency_set_value(c, &v); +} + + +static void +load_ecb_rates(CurrencyManager *manager) +{ + Currency *eur_rate; + char *filename; + xmlDocPtr document; + xmlXPathContextPtr xpath_ctx; + xmlXPathObjectPtr xpath_obj; + int i, len; + + /* Scale rates to the EUR value */ + eur_rate = currency_manager_get_currency(manager, "EUR"); + if (!eur_rate) { + g_warning("Cannot use ECB rates as don't have EUR rate"); + return; + } + + /* Set some fixed rates */ + set_ecb_fixed_rate(manager, "EEK", "0.06391", eur_rate); + set_ecb_fixed_rate(manager, "CFA", "0.152449", eur_rate); + + xmlInitParser(); + filename = get_ecb_rate_filepath(); + document = xmlReadFile(filename, NULL, 0); + if (!document) + g_warning("Couldn't parse ECB rate file %s", filename); + g_free (filename); + if (!document) + return; + + xpath_ctx = xmlXPathNewContext(document); + if (xpath_ctx == NULL) { + xmlFreeDoc(document); + g_warning("Couldn't create XPath context"); + return; + } + + xmlXPathRegisterNs(xpath_ctx, + BAD_CAST("xref"), + BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref")); + xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"), + xpath_ctx); + if (xpath_obj == NULL) { + xmlXPathFreeContext(xpath_ctx); + xmlFreeDoc(document); + g_warning("Couldn't create XPath object"); + return; + } + len = (xpath_obj->nodesetval) ? xpath_obj->nodesetval->nodeNr : 0; + for (i = 0; i < len; i++) { + if (xpath_obj->nodesetval->nodeTab[i]->type == XML_ELEMENT_NODE) + set_ecb_rate(manager, xpath_obj->nodesetval->nodeTab[i], eur_rate); + + /* Avoid accessing removed elements */ + if (xpath_obj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL) + xpath_obj->nodesetval->nodeTab[i] = NULL; + } + + xmlXPathFreeObject(xpath_obj); + xmlXPathFreeContext(xpath_ctx); + xmlFreeDoc(document); + xmlCleanupParser(); +} + + +static gboolean +load_rates(CurrencyManager *manager) +{ + int i; + + /* Already loaded */ + if (loaded_rates) + return TRUE; + + /* In process */ + if (downloading_imf_rates || downloading_ecb_rates) + return FALSE; + + /* Use the IMF provided values and top up with currencies tracked by the ECB and not the IMF */ + load_imf_rates(manager); + load_ecb_rates(manager); + + for (i = 0; currency_info[i].short_name; i++) { + GList *link; + for (link = manager->priv->currencies; link; link = link->next) { + Currency *c = link->data; + if (strcmp(currency_get_name(c), currency_info[i].short_name) == 0) + break; + } + if (!link) + g_warning("Currency %s is not provided by IMF or ECB", currency_info[i].short_name); + } + + g_debug("Rates loaded"); + loaded_rates = TRUE; + + g_signal_emit(manager, signals[UPDATED], 0); + + return TRUE; +} + + +const MPNumber * +currency_manager_get_value(CurrencyManager *manager, const gchar *currency) +{ + gchar *path; + Currency *c; + + g_return_val_if_fail(manager != NULL, NULL); + g_return_val_if_fail(currency != NULL, NULL); + + /* Update rates if necessary */ + path = get_imf_rate_filepath(); + if (!downloading_imf_rates && file_needs_update(path, 60 * 60 * 24 * 7)) { + downloading_imf_rates = TRUE; + g_debug("Downloading rates from the IMF..."); + download_file(manager, "http://www.imf.org/external/np/fin/data/rms_five.aspx?tsvflag=Y", path, download_imf_cb); + } + g_free(path); + path = get_ecb_rate_filepath(); + if (!downloading_ecb_rates && file_needs_update(path, 60 * 60 * 24 * 7)) { + downloading_ecb_rates = TRUE; + g_debug("Downloading rates from the ECB..."); + download_file(manager, "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml", path, download_ecb_cb); + } + g_free(path); + + if (!load_rates(manager)) + return NULL; + + c = currency_manager_get_currency(manager, currency); + if (c) + return currency_get_value(c); + else + return NULL; +} + + +static void +currency_manager_class_init(CurrencyManagerClass *klass) +{ + g_type_class_add_private(klass, sizeof(CurrencyManagerPrivate)); + + signals[UPDATED] = + g_signal_new("updated", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (CurrencyManagerClass, updated), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + + +static void +currency_manager_init(CurrencyManager *manager) +{ + manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, currency_manager_get_type(), CurrencyManagerPrivate); +} diff --git a/src/currency-manager.h b/src/currency-manager.h new file mode 100644 index 0000000..c5e8e91 --- /dev/null +++ b/src/currency-manager.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef CURRENCY_MANAGER_H +#define CURRENCY_MANAGER_H + +#include "currency.h" +#include "mp.h" + +G_BEGIN_DECLS + +#define CURRENCY_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), currency_manager_get_type(), CurrencyManager)) + +typedef struct CurrencyManagerPrivate CurrencyManagerPrivate; + +typedef struct +{ + GObject parent_instance; + CurrencyManagerPrivate *priv; +} CurrencyManager; + +typedef struct +{ + GObjectClass parent_class; + void (*updated)(CurrencyManager *manager); +} CurrencyManagerClass; + +GType currency_manager_get_type(void); + +CurrencyManager *currency_manager_get_default(void); + +GList *currency_manager_get_currencies(CurrencyManager *manager); + +Currency *currency_manager_get_currency(CurrencyManager *manager, const gchar *name); + +const MPNumber *currency_manager_get_value(CurrencyManager *manager, const gchar *currency); + +G_END_DECLS + +#endif /* CURRENCY_MANAGER_H */ diff --git a/src/currency.c b/src/currency.c index 2add81b..ea81761 100644 --- a/src/currency.c +++ b/src/currency.c @@ -1,276 +1,96 @@ -#include +/* + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ -#include -#include -#include -#include -#include -#include +#include +#include #include "currency.h" -#include "mp.h" - -typedef struct { - char* short_name; - MPNumber value; -} currency; - -static currency* currencies = NULL; -static int currency_count = 0; - -static gboolean downloading_rates = FALSE; -static gboolean loaded_rates = FALSE; - -static char* get_rate_filepath() -{ - return g_build_filename(g_get_user_cache_dir(), "mate-calc", "eurofxref-daily.xml", NULL); -} +#include "mp-serializer.h" +#include "currency-manager.h" // FIXME: Move out of here -static int currency_get_index(const char *short_name) +struct CurrencyPrivate { - int i; + gchar *name; + gchar *display_name; + gchar *symbol; + MPNumber value; +}; - for (i = 0; i < currency_count; i++) - { - if (!strcmp(short_name, currencies[i].short_name)) - { - if (mp_is_negative(¤cies[i].value) || mp_is_zero(¤cies[i].value)) - { - return -1; - } - else - { - return i; - } - } - } +G_DEFINE_TYPE (Currency, currency, G_TYPE_OBJECT); - return -1; -} -/* A file needs to be redownloaded if it doesn't exist, or every 7 days. - * When an error occur, it probably won't hurt to try to download again. - */ -static int currency_rates_needs_update() +Currency * +currency_new(const gchar *name, + const gchar *display_name, + const gchar *symbol) { - gchar* filename = get_rate_filepath(); - struct stat buf; - - if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) - { - g_free(filename); - return 1; - } - - if (g_stat(filename, &buf) == -1) - { - g_free(filename); - return 1; - } - - g_free(filename); + Currency *currency = g_object_new(currency_get_type(), NULL); - if (difftime(time(NULL), buf.st_mtime) > (60 * 60 * 24 * 7)) - { - return 1; - } + currency->priv->name = g_strdup(name); + currency->priv->display_name = g_strdup(display_name); + currency->priv->symbol = g_strdup(symbol); - return 0; + return currency; } -static void download_cb(GObject* object, GAsyncResult* result, gpointer user_data) +const gchar * +currency_get_name(Currency *currency) { - GError* error = NULL; - - if (g_file_copy_finish(G_FILE(object), result, &error)) - { - g_debug("Rates updated"); - } - else - { - g_warning("Couldn't download currency file: %s", error->message); - } - - g_clear_error(&error); - downloading_rates = FALSE; + g_return_val_if_fail (currency != NULL, NULL); + return currency->priv->name; } -static void currency_download_rates() +const gchar * +currency_get_display_name(Currency *currency) { - gchar* filename; - gchar* directory; - GFile* source; - GFile* dest; - - downloading_rates = TRUE; - g_debug("Downloading rates..."); - - filename = get_rate_filepath(); - directory = g_path_get_dirname(filename); - g_mkdir_with_parents(directory, 0755); - g_free(directory); - - /* HACK: It is safe? */ - source = g_file_new_for_uri("http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"); - dest = g_file_new_for_path(filename); - g_free(filename); - - g_file_copy_async(source, dest, G_FILE_COPY_OVERWRITE, G_PRIORITY_DEFAULT, NULL, NULL, NULL, download_cb, NULL); - g_object_unref(source); - g_object_unref(dest); + g_return_val_if_fail (currency != NULL, NULL); + return currency->priv->display_name; } -static void set_rate(xmlNodePtr node, currency* cur) +const gchar * +currency_get_symbol(Currency *currency) { - xmlAttrPtr attribute; - - for (attribute = node->properties; attribute; attribute = attribute->next) - { - if (strcmp((char*) attribute->name, "currency") == 0) - { - cur->short_name = (char*) xmlNodeGetContent((xmlNodePtr) attribute); - } - else if (strcmp ((char*) attribute->name, "rate") == 0) - { - char* val = (char*) xmlNodeGetContent((xmlNodePtr) attribute); - mp_set_from_string(val, 10, &(cur->value)); - xmlFree(val); - } - } + g_return_val_if_fail (currency != NULL, NULL); + return currency->priv->symbol; } -static void currency_load_rates() -{ - char* filename = get_rate_filepath(); - xmlDocPtr document; - xmlXPathContextPtr xpath_ctx; - xmlXPathObjectPtr xpath_obj; - int i; - int len; - - g_return_if_fail(g_file_test(filename, G_FILE_TEST_IS_REGULAR)); - - xmlInitParser(); - document = xmlReadFile(filename, NULL, 0); - g_free (filename); - - if (document == NULL) - { - fprintf(stderr, "Couldn't parse data file\n"); - return; - } - xpath_ctx = xmlXPathNewContext(document); - - if (xpath_ctx == NULL) - { - xmlFreeDoc(document); - fprintf(stderr, "Couldn't create XPath context\n"); - return; - } - - xmlXPathRegisterNs(xpath_ctx, BAD_CAST("xref"), BAD_CAST("http://www.ecb.int/vocabulary/2002-08-01/eurofxref")); - xpath_obj = xmlXPathEvalExpression(BAD_CAST("//xref:Cube[@currency][@rate]"), xpath_ctx); - - if (xpath_obj == NULL) - { - xmlXPathFreeContext(xpath_ctx); - xmlFreeDoc(document); - fprintf(stderr, "Couldn't create XPath object\n"); - return; - } - - len = (xpath_obj->nodesetval) ? xpath_obj->nodesetval->nodeNr : 0; - currency_count = len + 1; - currencies = g_slice_alloc0(sizeof(currency) * currency_count); - - for (i = 0; i < len; i++) - { - if (xpath_obj->nodesetval->nodeTab[i]->type == XML_ELEMENT_NODE) - { - set_rate(xpath_obj->nodesetval->nodeTab[i], ¤cies[i]); - } - - // Avoid accessing removed elements - if (xpath_obj->nodesetval->nodeTab[i]->type != XML_NAMESPACE_DECL) - { - xpath_obj->nodesetval->nodeTab[i] = NULL; - } - } - - currencies[len].short_name = g_strdup("EUR"); - MPNumber foo; - mp_set_from_integer(1, &foo); - currencies[len].value = foo; - - xmlXPathFreeObject(xpath_obj); - xmlXPathFreeContext(xpath_ctx); - xmlFreeDoc(document); - xmlCleanupParser(); - - g_debug("Rates loaded"); - loaded_rates = TRUE; +void +currency_set_value(Currency *currency, MPNumber *value) +{ + g_return_if_fail (currency != NULL); + g_return_if_fail (value != NULL); + mp_set_from_mp (value, ¤cy->priv->value); } -gboolean currency_convert(const MPNumber* from_amount, const char* source_currency, const char* target_currency, MPNumber* to_amount) +const MPNumber * +currency_get_value(Currency *currency) { - int from_index, to_index; - - if (downloading_rates) - { - return FALSE; - } - - /* Update currency if necessary */ - if (currency_rates_needs_update()) - { - currency_download_rates(); - return FALSE; - } - - if (!loaded_rates) - { - currency_load_rates(); - } - - from_index = currency_get_index(source_currency); - to_index = currency_get_index(target_currency); - - if (from_index < 0 || to_index < 0) - { - return FALSE; - } - - if (mp_is_zero(¤cies[from_index].value) || mp_is_zero(¤cies[to_index].value)) - { - mp_set_from_integer(0, to_amount); - return FALSE; - } - - mp_divide(from_amount, ¤cies[from_index].value, to_amount); - mp_multiply(to_amount, ¤cies[to_index].value, to_amount); - - return TRUE; + g_return_val_if_fail (currency != NULL, NULL); + return ¤cy->priv->value; } -void currency_free_resources() -{ - int i; - for (i = 0; i < currency_count; i++) - { - if (currencies[i].short_name != NULL) - { - xmlFree(currencies[i].short_name); - } - } +static void +currency_class_init(CurrencyClass *klass) +{ + g_type_class_add_private(klass, sizeof(CurrencyPrivate)); +} - g_slice_free1(currency_count * sizeof(currency), currencies); - currencies = NULL; - currency_count = 0; +static void +currency_init(Currency *currency) +{ + currency->priv = G_TYPE_INSTANCE_GET_PRIVATE(currency, currency_get_type(), CurrencyPrivate); } diff --git a/src/currency.h b/src/currency.h index f29239e..663ece7 100644 --- a/src/currency.h +++ b/src/currency.h @@ -1,65 +1,54 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + #ifndef CURRENCY_H #define CURRENCY_H -#include - +#include #include "mp.h" -struct currency_name { - char* short_name; - char* symbol; - char* long_name; -}; +G_BEGIN_DECLS -/* - * List taken from http://www.ecb.int/press/pr/date/2008/html/pr081205.en.html - * with euro added. - */ -static const struct currency_name currency_names[] = { - {"AUD", "$", N_("Australian dollar")}, - {"BGN", "лв", N_("Bulgarian lev")}, - {"BRL", "R$", N_("Brazilian real")}, - {"CAD", "$", N_("Canadian dollar")}, - {"CHF", "Fr", N_("Swiss franc")}, - {"CNY", "元", N_("Chinese yuan renminbi")}, - {"CZK", "Kč", N_("Czech koruna")}, - {"DKK", "kr", N_("Danish krone")}, - {"EEK", "KR", N_("Estonian kroon")}, - {"EUR", "€", N_("Euro")}, - {"GBP", "£", N_("Pound sterling")}, - {"HKD", "$", N_("Hong Kong dollar")}, - {"HRK", "kn", N_("Croatian kuna")}, - {"HUF", "Ft", N_("Hungarian forint")}, - {"IDR", "Rp", N_("Indonesian rupiah")}, - {"INR", "Rs", N_("Indian rupee")}, - {"ISK", "kr", N_("Icelandic krona")}, - {"JPY", "¥", N_("Japanese yen")}, - {"KRW", "₩", N_("South Korean won")}, - {"LTL", "Lt", N_("Lithuanian litas")}, - {"LVL", "Ls", N_("Latvian lats")}, - {"MXN", "$", N_("Mexican peso")}, - {"MYR", "RM", N_("Malaysian ringgit")}, - {"NOK", "kr", N_("Norwegian krone")}, - {"NZD", "$", N_("New Zealand dollar")}, - {"PHP", "₱", N_("Philippine peso")}, - {"PLN", "zł", N_("Polish zloty")}, - {"RON", "L", N_("New Romanian leu")}, - {"RUB", "руб.", N_("Russian rouble")}, - {"SEK", "kr", N_("Swedish krona")}, - {"SGD", "$", N_("Singapore dollar")}, - {"THB", "฿", N_("Thai baht")}, - {"TRY", "TL", N_("New Turkish lira")}, - {"USD", "$", N_("US dollar")}, - {"ZAR", "R", N_("South African rand")}, - {NULL, NULL} -}; - -// FIXME: Should indicate when rates are updated to UI - -/* Converts an amount of money from one currency to another */ -gboolean currency_convert(const MPNumber* from_amount, const char* source_currency, const char *target_currency, MPNumber* to_amount); - -/* Frees up all allocated resources */ -void currency_free_resources(void); +#define CURRENCY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), currency_get_type(), Currency)) + +typedef struct CurrencyPrivate CurrencyPrivate; + +typedef struct +{ + GObject parent_instance; + CurrencyPrivate *priv; +} Currency; + +typedef struct +{ + GObjectClass parent_class; +} CurrencyClass; + +GType currency_get_type(void); + +Currency *currency_new(const gchar *name, + const gchar *display_name, + const gchar *symbol); + +const gchar *currency_get_name(Currency *currency); + +const gchar *currency_get_short_display_name(Currency *currency); + +const gchar *currency_get_display_name(Currency *currency); + +const gchar *currency_get_symbol(Currency *currency); + +void currency_set_value(Currency *currency, MPNumber *value); + +const MPNumber *currency_get_value(Currency *currency); + +G_END_DECLS #endif /* CURRENCY_H */ diff --git a/src/financial.c b/src/financial.c index f388be3..fa0e33d 100644 --- a/src/financial.c +++ b/src/financial.c @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -75,7 +67,7 @@ calc_ddb(MathEquation *equation, MPNumber *t, MPNumber *cost, MPNumber *life, MP } if (len >= 0) { - math_equation_set_status (equation, ("Error: the number of periods must be positive")); + math_equation_set_status (equation, _("Error: the number of periods must be positive")); mp_set_from_integer(0, t); } } diff --git a/src/financial.h b/src/financial.h index 53022d0..ec9f513 100644 --- a/src/financial.h +++ b/src/financial.h @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef FINANCIAL_H @@ -23,19 +15,19 @@ #include "mp.h" #include "math-equation.h" -void do_finc_expression(MathEquation* equation, int function, MPNumber* arg1, MPNumber* arg2, MPNumber* arg3, MPNumber* arg4); +void do_finc_expression(MathEquation *equation, int function, MPNumber *arg1, MPNumber *arg2, MPNumber *arg3, MPNumber *arg4); enum finc_dialogs { - FINC_CTRM_DIALOG, - FINC_DDB_DIALOG, - FINC_FV_DIALOG, - FINC_GPM_DIALOG, - FINC_PMT_DIALOG, - FINC_PV_DIALOG, - FINC_RATE_DIALOG, - FINC_SLN_DIALOG, - FINC_SYD_DIALOG, - FINC_TERM_DIALOG + FINC_CTRM_DIALOG, + FINC_DDB_DIALOG, + FINC_FV_DIALOG, + FINC_GPM_DIALOG, + FINC_PMT_DIALOG, + FINC_PV_DIALOG, + FINC_RATE_DIALOG, + FINC_SLN_DIALOG, + FINC_SYD_DIALOG, + FINC_TERM_DIALOG }; #endif /* FINANCIAL_H */ diff --git a/src/lexer.c b/src/lexer.c new file mode 100644 index 0000000..176c773 --- /dev/null +++ b/src/lexer.c @@ -0,0 +1,587 @@ +#include +#include +#include + +#include "lexer.h" +#include "parserfunc.h" +#include "mp-equation.h" + +static gboolean +l_check_if_function(LexerState* state) +{ + gchar* name = pl_get_marked_substring(state->prelexer); + if(!state->parent->function_is_defined) + { + free(name); + return FALSE; + } + if ((*(state->parent->function_is_defined))(state->parent, name)) + { + free(name); + return TRUE; + } + else + { + free(name); + return FALSE; + } +} + +static gboolean +l_check_if_number(LexerState* state) +{ + MPNumber tmp; + int count = 0; + gchar* text = pl_get_marked_substring(state->prelexer); + if(mp_set_from_string(text, state->parent->options->base, &tmp) == 0) + { + free(text); + return TRUE; + } + else + { + /* Try to rollback several characters to see, if that yeilds any number. */ + while(strlen (text) > 0) + { + if(mp_set_from_string(text, state->parent->options->base, &tmp) == 0) + { + free(text); + return TRUE; + } + free(text); + count++; + pl_roll_back(state->prelexer); + text = pl_get_marked_substring(state->prelexer); + } + /* Undo all rollbacks. */ + while(count--) + pl_get_next_token (state->prelexer); + free(text); + return FALSE; + } +} + +/* Insert generated token to the LexerState structure. */ +static LexerToken* +l_insert_token(LexerState* state, const LexerTokenType type) +{ + state->tokens = (LexerToken *) realloc(state->tokens, (state->token_count + 1) * sizeof(LexerToken)); + assert(state->tokens != NULL); + state->tokens[state->token_count].string = pl_get_marked_substring(state->prelexer); + state->tokens[state->token_count].start_index = state->prelexer->mark_index; + state->tokens[state->token_count].end_index = state->prelexer->next_index; + state->tokens[state->token_count].token_type = type; + state->token_count++; + return &state->tokens[state->token_count - 1]; +} + +/* Generates next token from pre-lexer stream and call l_insert_token() to insert it at the end. */ +static LexerToken* +l_insert_next_token(LexerState* lstate) +{ + PreLexerState* state = lstate->prelexer; + LexerTokenType type; + gchar* tmp; + pl_set_marker(state); + /* Ignore all blank spaces. :) */ + while((type = pl_get_next_token(state)) == PL_SKIP) + /* Set marker. Beginning of new token. */ + pl_set_marker(state); + if(type == T_AND + ||type == T_OR + ||type == T_XOR + ||type == T_NOT + ||type == T_ADD + ||type == T_SUBTRACT + ||type == T_MULTIPLY + ||type == T_DIV + ||type == T_L_FLOOR + ||type == T_R_FLOOR + ||type == T_L_CEILING + ||type == T_R_CEILING + ||type == T_ROOT + ||type == T_ROOT_3 + ||type == T_ROOT_4 + ||type == T_ASSIGN + ||type == T_L_R_BRACKET + ||type == T_R_R_BRACKET + ||type == T_L_S_BRACKET + ||type == T_R_S_BRACKET + ||type == T_L_C_BRACKET + ||type == T_R_C_BRACKET + ||type == T_ABS + ||type == T_POWER + ||type == T_FACTORIAL + ||type == T_PERCENTAGE) + { + return l_insert_token(lstate, type); + } + /* [PL_SUPER_MINUS][PL_SUPER_DIGIT]+ */ + if(type == PL_SUPER_MINUS) + { + if((type = pl_get_next_token(state)) != PL_SUPER_DIGIT) + { + /* ERROR: expected PL_SUP_DIGIT */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring (state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + /* Get all PL_SUPER_DIGITs. */ + while (pl_get_next_token(state) == PL_SUPER_DIGIT); + pl_roll_back(state); + return l_insert_token(lstate, T_NSUP_NUMBER); + } + /* [PL_SUPER_DIGIT]+ */ + if(type == PL_SUPER_DIGIT) + { + while(pl_get_next_token(state) == PL_SUPER_DIGIT); + pl_roll_back(state); + return l_insert_token(lstate, T_SUP_NUMBER); + } + /* [PL_SUB_DIGIT]+ */ + if(type == PL_SUB_DIGIT) + { + while(pl_get_next_token(state) == PL_SUB_DIGIT); + pl_roll_back(state); + return l_insert_token(lstate, T_SUB_NUMBER); + } + /* [PL_FRACTION] */ + if(type == PL_FRACTION) + { + return l_insert_token(lstate, T_NUMBER); + } + if(type == PL_DIGIT) + { + while((type = pl_get_next_token(state)) == PL_DIGIT); + if(type == PL_FRACTION) + { + return l_insert_token(lstate, T_NUMBER); + } + else if(type == PL_SUB_DIGIT) + { + while(pl_get_next_token(state) == PL_SUB_DIGIT); + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + else if(type == PL_DEGREE) + { + type = pl_get_next_token(state); + if(type == PL_DIGIT) + { + while((type = pl_get_next_token(state)) == PL_DIGIT); + if(type == PL_DECIMAL) + { + goto ANGLE_NUM_DM_STATE; + } + else if(type == PL_MINUTE) + { + type = pl_get_next_token(state); + if(type == PL_DIGIT) + { + while((type = pl_get_next_token(state)) == PL_DIGIT); + if(type == PL_DECIMAL) + { + goto ANGLE_NUM_DMS_STATE; + } + else if(type == PL_SECOND) + { + return l_insert_token(lstate, T_NUMBER); + } + else + { + /* ERROR: expected PL_SECOND */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring (state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + } + else if(type == PL_DECIMAL) + { +ANGLE_NUM_DMS_STATE: + if((type = pl_get_next_token (state)) != PL_DIGIT) + { + /* ERROR: expected PL_DIGIT */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + while((type = pl_get_next_token(state)) == PL_DIGIT); + if(type == PL_SECOND) + { + return l_insert_token(lstate, T_NUMBER); + } + else + { + /* ERROR: expected PL_SECOND */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + } + else + { + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + } + else + { + /* ERROR: expected PL_MINUTE | PL_DIGIT */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + } + else if(type == PL_DECIMAL) + { +ANGLE_NUM_DM_STATE: + if((type = pl_get_next_token(state)) != PL_DIGIT) + { + /* ERROR: expected PL_DIGIT */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + while((type = pl_get_next_token(state)) == PL_DIGIT); + if(type == PL_MINUTE) + { + return l_insert_token(lstate, T_NUMBER); + } + else + { + /* ERROR: expected PL_MINUTE */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + } + else + { + return l_insert_token(lstate, T_NUMBER); + } + } + else if(type == PL_DECIMAL) + { + goto DECIMAL_STATE; + } + else if(type == PL_HEX) + { + goto HEX_DEC_STATE; + } + else + { + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + } + if(type == PL_DECIMAL) + { +DECIMAL_STATE: + type = pl_get_next_token(state); + if(type == PL_DIGIT) + { + while((type = pl_get_next_token(state)) == PL_DIGIT); + if(type == PL_DEGREE) + { + return l_insert_token(lstate, T_NUMBER); + } + else if(type == PL_HEX) + { + goto DECIMAL_HEX_STATE; + } + else if(type == PL_SUB_DIGIT) + { + while(pl_get_next_token(state) == PL_SUB_DIGIT); + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + else + { + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + } + else if(type == PL_HEX) + { + goto DECIMAL_HEX_STATE; + } + else + { + /* ERROR: expected PL_DIGIT | PL_HEX */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + } + if(type == PL_HEX) + { + while((type = pl_get_next_token(state)) == PL_HEX); + if(type == PL_DIGIT) + { +HEX_DEC_STATE: + while(1) + { + type = pl_get_next_token(state); + if(type == PL_DIGIT || type == PL_HEX) + { + continue; + } + else if(type == PL_DECIMAL) + { + goto DECIMAL_HEX_STATE; + } + else if(type == PL_SUB_DIGIT) + { + while(pl_get_next_token(state) == PL_SUB_DIGIT); + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + else + { + if(l_check_if_number(lstate)) + return l_insert_token(lstate, T_NUMBER); + /* ERROR: expected PL_DECIMAL | PL_DIGIT | PL_HEX */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + } + } + else if(type == PL_DECIMAL) + { +DECIMAL_HEX_STATE: + type = pl_get_next_token(state); + if(!(type == PL_DIGIT || type == PL_HEX)) + { + /* ERROR: expected PL_DIGIT | PL_HEX */ + set_error(lstate->parent, PARSER_ERR_MP, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); + } + while(1) + { + type = pl_get_next_token(state); + if(type == PL_DIGIT || type == PL_HEX) + { + continue; + } + else if(type == PL_SUB_DIGIT) + { + while(pl_get_next_token(state) == PL_SUB_DIGIT); + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + else + { + pl_roll_back(state); + return l_insert_token(lstate, T_NUMBER); + } + } + } + else if(type == PL_SUB_DIGIT) + { + while(pl_get_next_token(state) == PL_SUB_DIGIT); + pl_roll_back(state); + if(l_check_if_number(lstate)) + { + /* NUMBER */ + return l_insert_token(lstate, T_NUMBER); + } + else + { + /* VARIABLE */ + if(l_check_if_function(lstate)) + { + return l_insert_token(lstate, T_FUNCTION); + } + else + { + return l_insert_token(lstate, T_VARIABLE); + } + } + } + else if(type == PL_LETTER) + { + goto LETTER_STATE; + } + else + { + pl_roll_back(state); + if(l_check_if_number(lstate)) + { + /* NUMBER */ + return l_insert_token(lstate, T_NUMBER); + } + else + { + /* VARIABLE */ + if(l_check_if_function(lstate)) + { + return l_insert_token(lstate, T_FUNCTION); + } + else + { + return l_insert_token(lstate, T_VARIABLE); + } + } + } + } + if(type == PL_LETTER) + { +LETTER_STATE: + while(1) + { + type = pl_get_next_token(state); + if(type == PL_LETTER || type == PL_HEX) + { + continue; + } + else if(type == PL_SUB_DIGIT) + { + while(pl_get_next_token(state) == PL_SUB_DIGIT); + pl_roll_back(state); + tmp = g_ascii_strdown(pl_get_marked_substring(state), -1); + if(g_strcmp0(tmp, "mod") == 0) + { + return l_insert_token(lstate, T_MOD); + } + if(g_strcmp0(tmp, "and") == 0) + { + return l_insert_token(lstate, T_AND); + } + if(g_strcmp0(tmp, "or") == 0) + { + return l_insert_token(lstate, T_OR); + } + if(g_strcmp0(tmp, "xor") == 0) + { + return l_insert_token(lstate, T_XOR); + } + if(g_strcmp0(tmp, "not") == 0) + { + return l_insert_token(lstate, T_NOT); + } + if(g_strcmp0(tmp, "in") == 0) + { + return l_insert_token(lstate, T_IN); + } + if(l_check_if_function(lstate)) + { + return l_insert_token(lstate, T_FUNCTION); + } + else + { + return l_insert_token(lstate, T_VARIABLE); + } + } + else + { + pl_roll_back(state); + tmp = g_ascii_strdown(pl_get_marked_substring(state), -1); + if(g_strcmp0(tmp, "mod") == 0) + { + return l_insert_token(lstate, T_MOD); + } + if(g_strcmp0(tmp, "and") == 0) + { + return l_insert_token(lstate, T_AND); + } + if(g_strcmp0(tmp, "or") == 0) + { + return l_insert_token(lstate, T_OR); + } + if(g_strcmp0(tmp, "xor") == 0) + { + return l_insert_token(lstate, T_XOR); + } + if(g_strcmp0(tmp, "not") == 0) + { + return l_insert_token(lstate, T_NOT); + } + if(g_strcmp0(tmp, "in") == 0) + { + return l_insert_token(lstate, T_IN); + } + if(l_check_if_function(lstate)) + { + return l_insert_token(lstate, T_FUNCTION); + } + else + { + return l_insert_token(lstate, T_VARIABLE); + } + } + } + } + if(type == PL_EOS) + { + return l_insert_token(lstate, PL_EOS); + } + /* ERROR: Unexpected token.. X( */ + set_error(lstate->parent, PARSER_ERR_INVALID, tmp = pl_get_marked_substring(state)); + free(tmp); + return l_insert_token(lstate, T_UNKNOWN); +} + +/* Call l_insert_next_token() as many times as needed to completely tokenize the string. */ +void +l_insert_all_tokens(LexerState* state) +{ + LexerToken* token; + while(1) + { + token = l_insert_next_token(state); + assert(token != NULL); + if(token->token_type == PL_EOS) + { + break; + } + } +} + +/* Create a lexer state from given input string. This will take care of pre-lexer state. */ +LexerState* +l_create_lexer(const gchar* input, struct parser_state* parent) +{ + LexerState* ret; + ret = (LexerState *) malloc(sizeof(LexerState)); + assert(ret != NULL); + ret->prelexer = pl_create_scanner(input); + ret->tokens = NULL; + ret->token_count = 0; + ret->next_token = 0; + ret->parent = parent; + return ret; +} + +/* Destroy lexer state and free memory. */ +void +l_destroy_lexer(LexerState* state) +{ + int l; + pl_destroy_scanner(state->prelexer); + for(l = 0; l < state->token_count; l++) + { + free(state->tokens[l].string); + } + free(state->tokens); + free(state); +} + +/* Get next token interface. Will be called by parser to get pointer to next token in token stream. */ +LexerToken* +l_get_next_token(LexerState* state) +{ + /* Return PL_EOS token after token stream reaches to its end. */ + if(state->next_token >= state->token_count) + return &state->tokens[state->token_count - 1]; + return &state->tokens[state->next_token++]; +} + +/* Roll back one lexer token. */ +void +l_roll_back(LexerState* state) +{ + if(state->next_token > 0) + state->next_token--; +} diff --git a/src/lexer.h b/src/lexer.h new file mode 100644 index 0000000..2fd7fa7 --- /dev/null +++ b/src/lexer.h @@ -0,0 +1,40 @@ +#ifndef LEXER_H +#define LEXER_H + +#include "prelexer.h" + +/* Structure to hold single token. */ +typedef struct +{ + gchar* string; /* Poniter to local copy of token string. */ + guint start_index; /* Start index in original stream. */ + guint end_index; /* End index in original stream. */ + LexerTokenType token_type; /* Type of token. */ +} LexerToken; + +/* Structure to hold lexer state and all the tokens. */ +typedef struct +{ + PreLexerState *prelexer; /* Pre-lexer state. Pre-lexer is part of lexer. */ + LexerToken *tokens; /* Pointer to the dynamic array of LexerTokens. */ + guint token_count; /* Count of tokens in array. */ + guint next_token; /* Index of next, to be sent, token. */ + struct parser_state *parent; /* Pointer to the parent parser. */ +} LexerState; + +/* Create a new LexerState object and fill the dynamic array with tokens. */ +LexerState* l_create_lexer(const gchar*, struct parser_state*); + +/* Destroy LexerState object and free up space. */ +void l_destroy_lexer(LexerState*); + +/* Tokanize complete string. */ +void l_insert_all_tokens(LexerState*); + +/* Return next, to be sent, token. */ +LexerToken* l_get_next_token(LexerState*); + +/* Roll back one token. */ +void l_roll_back(LexerState*); + +#endif /* LEXER_H */ diff --git a/src/mate-calc-cmd.c b/src/mate-calc-cmd.c index 796941b..e65ac5a 100644 --- a/src/mate-calc-cmd.c +++ b/src/mate-calc-cmd.c @@ -1,21 +1,11 @@ -/* $Header$ +/* + * Copyright (C) 2009 Rich Burridge * - * Copyright (c) 2009 Rich Burridge - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -23,34 +13,39 @@ #include #include #include +#include #include "mp-equation.h" +#include "mp-serializer.h" #define MAXLINE 1024 +static MpSerializer *result_serializer; + static void solve(const char *equation) { int ret; MPEquationOptions options; MPNumber z; - char result_str[MAXLINE]; - + gchar *result_str = NULL; + memset(&options, 0, sizeof(options)); options.base = 10; options.wordlen = 32; options.angle_units = MP_DEGREES; - + ret = mp_equation_parse(equation, &options, &z, NULL); if (ret == PARSER_ERR_MP) fprintf(stderr, "Error %s\n", mp_get_error()); - else if (ret) + else if (ret) fprintf(stderr, "Error %d\n", ret); else { - mp_cast_to_string(&z, 10, 10, 9, 1, result_str, MAXLINE); + result_str = mp_serializer_to_string(result_serializer, &z); printf("%s\n", result_str); } + g_free(result_str); } @@ -79,6 +74,11 @@ main(int argc, char **argv) /* Seed random number generator. */ srand48((long) time((time_t *) 0)); + g_type_init (); + setlocale(LC_ALL, ""); + + result_serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9); + equation = (char *) malloc(MAXLINE * sizeof(char)); while (1) { printf("> "); diff --git a/src/mate-calc.c b/src/mate-calc.c index c5439af..15efc97 100644 --- a/src/mate-calc.c +++ b/src/mate-calc.c @@ -1,34 +1,29 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include #include #include +#include +#include -#include "currency.h" -#include "unittest.h" #include "math-window.h" +#include "math-preferences.h" #include "mp-equation.h" +#include "unit-manager.h" static GSettings *settings = NULL; static MathWindow *window; +static MathPreferencesDialog *preferences_dialog; static void version(const gchar *progname) @@ -38,18 +33,26 @@ version(const gchar *progname) } +static int +do_convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data) +{ + return unit_manager_convert_by_symbol(unit_manager_get_default(), x, x_units, z_units, z); +} + + static void solve(const char *equation) { MPEquationOptions options; MPErrorCode error; MPNumber result; - char result_str[1024]; + char *result_str; memset(&options, 0, sizeof(options)); options.base = 10; options.wordlen = 32; options.angle_units = MP_DEGREES; + options.convert = do_convert; error = mp_equation_parse(equation, &options, &result, NULL); if(error == PARSER_ERR_MP) { @@ -61,7 +64,7 @@ solve(const char *equation) exit(1); } else { - mp_cast_to_string(&result, 10, 10, 9, 1, result_str, 1024); + result_str = mp_serializer_to_string(mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9), &result); printf("%s\n", result_str); exit(0); } @@ -107,7 +110,6 @@ usage(const gchar *progname, gboolean show_application, gboolean show_gtk) fprintf(stderr, /* Description on mate-calc application options displayed on command-line */ _("Application Options:\n" - " -u, --unittest Perform unit tests\n" " -s, --solve Solve the given equation")); fprintf(stderr, "\n\n"); @@ -158,10 +160,6 @@ get_options(int argc, char *argv[]) else solve(argv[i]); } - else if (strcmp(arg, "-u") == 0 || - strcmp(arg, "--unittest") == 0) { - unittest(); - } else { fprintf(stderr, /* Error printed to stderr when user provides an unknown command-line argument */ @@ -175,52 +173,291 @@ get_options(int argc, char *argv[]) static void -quit_cb(MathWindow *window) +accuracy_cb(MathEquation *equation, GParamSpec *spec) { - MathEquation *equation; - MathButtons *buttons; + g_settings_set_int(settings, "accuracy", math_equation_get_accuracy(equation)); +} - equation = math_window_get_equation(window); - buttons = math_window_get_buttons(window); - g_settings_set_int(settings, "accuracy", math_equation_get_accuracy(equation)); +static void +word_size_cb(MathEquation *equation, GParamSpec *spec) +{ g_settings_set_int(settings, "word-size", math_equation_get_word_size(equation)); - g_settings_set_int(settings, "base", math_buttons_get_programming_base(buttons)); +} + + +static void +show_thousands_separators_cb(MathEquation *equation, GParamSpec *spec) +{ g_settings_set_boolean(settings, "show-thousands", math_equation_get_show_thousands_separators(equation)); +} + + +static void +show_trailing_zeroes_cb(MathEquation *equation, GParamSpec *spec) +{ g_settings_set_boolean(settings, "show-zeroes", math_equation_get_show_trailing_zeroes(equation)); +} + + +static void +number_format_cb(MathEquation *equation, GParamSpec *spec) +{ g_settings_set_enum(settings, "number-format", math_equation_get_number_format(equation)); +} + + +static void +angle_unit_cb(MathEquation *equation, GParamSpec *spec) +{ g_settings_set_enum(settings, "angle-units", math_equation_get_angle_units(equation)); - g_settings_set_enum(settings, "button-mode", math_buttons_get_mode(buttons)); +} + + +static void +source_currency_cb(MathEquation *equation, GParamSpec *spec) +{ g_settings_set_string(settings, "source-currency", math_equation_get_source_currency(equation)); +} + + +static void +target_currency_cb(MathEquation *equation, GParamSpec *spec) +{ g_settings_set_string(settings, "target-currency", math_equation_get_target_currency(equation)); - g_settings_sync(); +} + + +static void +source_units_cb(MathEquation *equation, GParamSpec *spec) +{ + g_settings_set_string(settings, "source-units", math_equation_get_source_units(equation)); +} - currency_free_resources(); - gtk_main_quit(); + +static void +target_units_cb(MathEquation *equation, GParamSpec *spec) +{ + g_settings_set_string(settings, "target-units", math_equation_get_target_units(equation)); } -int -main(int argc, char **argv) +static void +programming_base_cb(MathButtons *buttons, GParamSpec *spec) +{ + g_settings_set_int(settings, "base", math_buttons_get_programming_base(buttons)); +} + + +static void +mode_cb(MathButtons *buttons, GParamSpec *spec, GApplication *app) +{ + const char *state; + GAction *action; + + g_settings_set_enum(settings, "button-mode", math_buttons_get_mode(buttons)); + + switch(math_buttons_get_mode(buttons)) + { + default: + case BASIC: + state = "basic"; + //FIXME: Should it revert to decimal mode? math_equation_set_number_format(window->priv->equation, DEC); + break; + + case ADVANCED: + state = "advanced"; + break; + + case FINANCIAL: + state = "financial"; + break; + + case PROGRAMMING: + state = "programming"; + break; + } + + action = g_action_map_lookup_action(G_ACTION_MAP(app), "mode"); + g_simple_action_set_state(G_SIMPLE_ACTION(action), + g_variant_new_string(state)); +} + + +static void +copy_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + math_equation_copy(math_window_get_equation(window)); +} + + +static void +paste_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + math_equation_paste(math_window_get_equation(window)); +} + + +static void +undo_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + math_equation_undo(math_window_get_equation(window)); +} + + +static void +redo_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + math_equation_redo(math_window_get_equation(window)); +} + + +static void +mode_changed_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + const char *mode_str; + int mode = BASIC; + + mode_str = g_variant_get_string(parameter, NULL); + if (strcmp(mode_str, "basic") == 0) + mode = BASIC; + else if (strcmp(mode_str, "advanced") == 0) + mode = ADVANCED; + else if (strcmp(mode_str, "financial") == 0) + mode = FINANCIAL; + else if (strcmp(mode_str, "programming") == 0) + mode = PROGRAMMING; + math_buttons_set_mode(math_window_get_buttons(window), mode); +} + + +static void +show_preferences_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + if (!preferences_dialog) { + preferences_dialog = math_preferences_dialog_new(math_window_get_equation(window)); + gtk_window_set_transient_for(GTK_WINDOW(preferences_dialog), GTK_WINDOW(window)); + } + gtk_window_present(GTK_WINDOW(preferences_dialog)); +} + + +static void +help_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + GdkScreen *screen; + GError *error = NULL; + + screen = gtk_widget_get_screen(GTK_WIDGET(window)); + gtk_show_uri(screen, "help:mate-calc", gtk_get_current_event_time(), &error); + + if (error != NULL) + { + GtkWidget *d; + /* Translators: Error message displayed when unable to launch help browser */ + const char *message = _("Unable to open help file"); + + d = gtk_message_dialog_new(GTK_WINDOW (window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, + "%s", message); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG (d), + "%s", error->message); + g_signal_connect(d, "response", G_CALLBACK(gtk_widget_destroy), NULL); + gtk_window_present(GTK_WINDOW(d)); + + g_error_free(error); + } +} + + +static void +about_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + const gchar *authors[] = { + "Rich Burridge ", + "Robert Ancell ", + "Klaus Niederkrüger ", + "Robin Sonefors ", + NULL + }; + const gchar *documenters[] = { + "Sun Microsystems", + NULL + }; + + /* The translator credits. Please translate this with your name(s). */ + const gchar *translator_credits = _("translator-credits"); + + /* The license this software is under (GPL2+) */ + char *license = _("mate-calc is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "mate-calc is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with mate-calc; if not, write to the Free Software Foundation, Inc.,\n" + "151 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA"); + + gtk_show_about_dialog(GTK_WINDOW(window), + "name", + /* Program name in the about dialog */ + _("mate-calc"), + "version", VERSION, + "copyright", + /* Copyright notice in the about dialog */ + _("\xc2\xa9 1986–2010 The gcalctool authors\n 2011-2012 mate-calc authors"), + "license", license, + "comments", + /* Short description in the about dialog */ + _("Calculator with financial and scientific modes."), + "authors", authors, + "documenters", documenters, + "translator_credits", translator_credits, + "logo-icon-name", "accessories-calculator", + "website", "http://mate-desktop.org", + NULL); +} + + +static void +quit_cb(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + gtk_widget_destroy(GTK_WIDGET(window)); +} + + +static GActionEntry app_entries[] = { + { "copy", copy_cb, NULL, NULL, NULL }, + { "paste", paste_cb, NULL, NULL, NULL }, + { "undo", undo_cb, NULL, NULL, NULL }, + { "redo", redo_cb, NULL, NULL, NULL }, + { "mode", mode_changed_cb, "s", "\"basic\"", NULL }, + { "preferences", show_preferences_cb, NULL, NULL, NULL }, + { "help", help_cb, NULL, NULL, NULL }, + { "about", about_cb, NULL, NULL, NULL }, + { "quit", quit_cb, NULL, NULL, NULL }, +}; + + +static void +startup_cb(GApplication *application) { MathEquation *equation; + MathButtons *buttons; int accuracy = 9, word_size = 64, base = 10; gboolean show_tsep = FALSE, show_zeroes = FALSE; - DisplayFormat number_format; + MpDisplayFormat number_format; MPAngleUnit angle_units; ButtonMode button_mode; gchar *source_currency, *target_currency; - - g_type_init(); - - bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR); - bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); - textdomain(GETTEXT_PACKAGE); - - /* Seed random number generator. */ - srand48((long) time((time_t *) 0)); - - get_options(argc, argv); + gchar *source_units, *target_units; + GMenu *menu, *section; settings = g_settings_new ("org.mate.calc"); accuracy = g_settings_get_int(settings, "accuracy"); @@ -233,6 +470,8 @@ main(int argc, char **argv) button_mode = g_settings_get_enum(settings, "button-mode"); source_currency = g_settings_get_string(settings, "source-currency"); target_currency = g_settings_get_string(settings, "target-currency"); + source_units = g_settings_get_string(settings, "source-units"); + target_units = g_settings_get_string(settings, "target-units"); equation = math_equation_new(); math_equation_set_accuracy(equation, accuracy); @@ -243,18 +482,98 @@ main(int argc, char **argv) math_equation_set_angle_units(equation, angle_units); math_equation_set_source_currency(equation, source_currency); math_equation_set_target_currency(equation, target_currency); + math_equation_set_source_units(equation, source_units); + math_equation_set_target_units(equation, target_units); g_free(source_currency); g_free(target_currency); + g_free(source_units); + g_free(target_units); + + g_signal_connect(equation, "notify::accuracy", G_CALLBACK(accuracy_cb), NULL); + g_signal_connect(equation, "notify::word-size", G_CALLBACK(word_size_cb), NULL); + g_signal_connect(equation, "notify::show-thousands-separators", G_CALLBACK(show_thousands_separators_cb), NULL); + g_signal_connect(equation, "notify::show-trailing-zeroes", G_CALLBACK(show_trailing_zeroes_cb), NULL); + g_signal_connect(equation, "notify::number-format", G_CALLBACK(number_format_cb), NULL); + g_signal_connect(equation, "notify::angle-units", G_CALLBACK(angle_unit_cb), NULL); + g_signal_connect(equation, "notify::source-currency", G_CALLBACK(source_currency_cb), NULL); + g_signal_connect(equation, "notify::target-currency", G_CALLBACK(target_currency_cb), NULL); + g_signal_connect(equation, "notify::source-units", G_CALLBACK(source_units_cb), NULL); + g_signal_connect(equation, "notify::target-units", G_CALLBACK(target_units_cb), NULL); + + g_action_map_add_action_entries(G_ACTION_MAP(application), app_entries, G_N_ELEMENTS(app_entries), NULL); + + window = math_window_new(GTK_APPLICATION(application), equation); + buttons = math_window_get_buttons(window); + math_buttons_set_programming_base(buttons, base); + math_buttons_set_mode(buttons, button_mode); // FIXME: We load the basic buttons even if we immediately switch to the next type + g_signal_connect(buttons, "notify::programming-base", G_CALLBACK(programming_base_cb), NULL); + g_signal_connect(buttons, "notify::mode", G_CALLBACK(mode_cb), application); + mode_cb (buttons, NULL, application); + + menu = g_menu_new(); + + section = g_menu_new(); + g_menu_append(section, _("Basic"), "app.mode::basic"); + g_menu_append(section, _("Advanced"), "app.mode::advanced"); + g_menu_append(section, _("Financial"), "app.mode::financial"); + g_menu_append(section, _("Programming"), "app.mode::programming"); + g_menu_append_section(menu, _("Mode"), G_MENU_MODEL(section)); + + section = g_menu_new(); + g_menu_append(section, _("Preferences"), "app.preferences"); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + + section = g_menu_new(); + g_menu_append(section, _("About Calculator"), "app.about"); + g_menu_append(section, _("Help"), "app.help"); + g_menu_append(section, _("Quit"), "app.quit"); + g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); + + gtk_application_set_app_menu(GTK_APPLICATION(application), G_MENU_MODEL(menu)); + + gtk_application_add_accelerator(GTK_APPLICATION(application), "Q", "app.quit", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(application), "F1", "app.help", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(application), "C", "app.copy", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(application), "V", "app.paste", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(application), "Z", "app.undo", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(application), "Z", "app.redo", NULL); +} + + +static void +activate_cb(GApplication *application) +{ + gtk_window_present(GTK_WINDOW(window)); +} + + +int +main(int argc, char **argv) +{ + GtkApplication *app; + int status; + + setlocale(LC_ALL, ""); + bindtextdomain(GETTEXT_PACKAGE, LOCALE_DIR); + bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); + textdomain(GETTEXT_PACKAGE); + + /* Seed random number generator. */ + srand48((long) time((time_t *) 0)); + + g_type_init(); + + get_options(argc, argv); gtk_init(&argc, &argv); - window = math_window_new(equation); - g_signal_connect(G_OBJECT(window), "quit", G_CALLBACK(quit_cb), NULL); - math_buttons_set_programming_base(math_window_get_buttons(window), base); - math_buttons_set_mode(math_window_get_buttons(window), button_mode); // FIXME: We load the basic buttons even if we immediately switch to the next type + gtk_window_set_default_icon_name("accessories-calculator"); + + app = gtk_application_new(NULL, G_APPLICATION_NON_UNIQUE); + g_signal_connect(app, "startup", G_CALLBACK(startup_cb), NULL); + g_signal_connect(app, "activate", G_CALLBACK(activate_cb), NULL); - gtk_widget_show(GTK_WIDGET(window)); - gtk_main(); + status = g_application_run(G_APPLICATION(app), argc, argv); - return(0); + return status; } diff --git a/src/math-buttons.c b/src/math-buttons.c index d33422b..cd28ee7 100644 --- a/src/math-buttons.c +++ b/src/math-buttons.c @@ -1,31 +1,26 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include #include "math-buttons.h" +#include "math-converter.h" +#include "math-variable-popup.h" #include "financial.h" -#include "currency.h" +#include "mp-serializer.h" enum { PROP_0, PROP_EQUATION, - PROP_MODE + PROP_MODE, + PROP_PROGRAMMING_BASE }; static GType button_mode_type; @@ -39,9 +34,9 @@ struct MathButtonsPrivate ButtonMode mode; gint programming_base; - GtkBuilder *basic_ui, *advanced_ui, *financial_ui, *programming_ui; + MathConverter *converter; - GdkColor color_numbers, color_action, color_operator, color_function, color_memory, color_group; + GtkBuilder *basic_ui, *advanced_ui, *financial_ui, *programming_ui; GtkWidget *bas_panel, *adv_panel, *fin_panel, *prog_panel; GtkWidget *active_panel; @@ -53,18 +48,11 @@ struct MathButtonsPrivate GList *superscript_toggles; GList *subscript_toggles; - GtkWidget *angle_combo; - GtkWidget *angle_label; - GtkWidget *base_combo; GtkWidget *base_label; GtkWidget *bit_panel; GtkWidget *bit_labels[MAXBITS]; - GtkWidget *source_currency_combo; - GtkWidget *target_currency_combo; - GtkWidget *currency_label; - GtkWidget *character_code_dialog; GtkWidget *character_code_entry; }; @@ -178,15 +166,6 @@ static ButtonData button_data[] = { {"tangent", "tan ", FUNCTION, /* Tooltip for the tangent button */ N_("Tangent")}, - {"inverse_sine", "asin", FUNCTION, - /* Tooltip for the inverse sine button */ - N_("Inverse Sine")}, - {"inverse_cosine", "acos", FUNCTION, - /* Tooltip for the inverse cosine button */ - N_("Inverse Cosine")}, - {"inverse_tangent", "atan", FUNCTION, - /* Tooltip for the inverse tangent button */ - N_("Inverse Tangent")}, {"hyperbolic_sine", "sinh ", FUNCTION, /* Tooltip for the hyperbolic sine button */ N_("Hyperbolic Sine")}, @@ -224,11 +203,11 @@ static ButtonData button_data[] = { /* Tooltip for the imaginary component button */ N_("Imaginary Component")}, {"ones_complement", "ones ", FUNCTION, - /* Tooltip for the ones complement button */ - N_("Ones Complement")}, + /* Tooltip for the ones' complement button */ + N_("Ones' Complement")}, {"twos_complement", "twos ", FUNCTION, - /* Tooltip for the twos complement button */ - N_("Twos Complement")}, + /* Tooltip for the two's complement button */ + N_("Two's Complement")}, {"trunc", "trunc ", FUNCTION, /* Tooltip for the truncate button */ N_("Truncate")}, @@ -238,12 +217,9 @@ static ButtonData button_data[] = { {"end_group", ")", GROUP, /* Tooltip for the end group button */ N_("End Group [)]")}, - {"store", NULL, MEMORY, - /* Tooltip for the assign variable button */ - N_("Assign Variable")}, - {"recall", NULL, MEMORY, - /* Tooltip for the insert variable button */ - N_("Insert Variable")}, + {"memory", NULL, MEMORY, + /* Tooltip for the memory button */ + N_("Memory")}, {"character", NULL, MEMORY, /* Tooltip for the insert character code button */ N_("Insert Character Code")}, @@ -261,10 +237,10 @@ static ButtonData button_data[] = { N_("Undo [Ctrl+Z]")}, {"shift_left", NULL, ACTION, /* Tooltip for the shift left button */ - N_("Shift Left [<<]")}, + N_("Shift Left")}, {"shift_right", NULL, ACTION, /* Tooltip for the shift right button */ - N_("Shift Right [>>]")}, + N_("Shift Right")}, {"finc_compounding_term", NULL, FUNCTION, /* Tooltip for the compounding term button */ N_("Compounding Term")}, @@ -298,11 +274,6 @@ static ButtonData button_data[] = { {NULL, NULL, 0, NULL} }; -typedef enum { - CURRENCY_TARGET_UPPER, - CURRENCY_TARGET_LOWER -} CurrencyTargetRow; - /* The names of each field in the dialogs for the financial functions */ static char *finc_dialog_fields[][5] = { {"ctrm_pint", "ctrm_fv", "ctrm_pv", NULL, NULL}, @@ -322,37 +293,9 @@ static char *finc_dialog_fields[][5] = { MathButtons * math_buttons_new(MathEquation *equation) { - return g_object_new (math_buttons_get_type(), "equation", equation, NULL); -} - - -static void -set_tint (GtkWidget *widget, GdkColor *tint, gint alpha) -{ - // hell no. It's a calculator, not a rainbow - return; - - GtkStyle *style; - int j; - - if (!widget) - return; - - gtk_widget_ensure_style(widget); - style = gtk_widget_get_style(widget); - - for (j = 0; j < 5; j++) { - GdkColor color; - - color.red = (style->bg[j].red * (10 - alpha) + tint->red * alpha) / 10; - color.green = (style->bg[j].green * (10 - alpha) + tint->green * alpha) / 10; - color.blue = (style->bg[j].blue * (10 - alpha) + tint->blue * alpha) / 10; - gdk_colormap_alloc_color(gdk_colormap_get_system(), &color, FALSE, TRUE); - gtk_widget_modify_bg(widget, j, &color); - } + return g_object_new(math_buttons_get_type(), "equation", equation, NULL); } - static void set_data(GtkBuilder *ui, const gchar *object_name, const gchar *name, const char *value) { @@ -366,7 +309,7 @@ set_data(GtkBuilder *ui, const gchar *object_name, const gchar *name, const char static void set_int_data(GtkBuilder *ui, const gchar *object_name, const gchar *name, gint value) { - GObject *object; + GObject *object; object = gtk_builder_get_object(ui, object_name); if (object) g_object_set_data(object, name, GINT_TO_POINTER(value)); @@ -392,9 +335,7 @@ load_finc_dialogs(MathButtons *buttons) for (i = 0; finc_dialog_fields[i][0] != NULL; i++) { for (j = 0; finc_dialog_fields[i][j]; j++) { GObject *o; - o = gtk_builder_get_object(buttons->priv->financial_ui, finc_dialog_fields[i][j]); - if(!o) - printf("missing '%s'\n", finc_dialog_fields[i][j]); + o = gtk_builder_get_object (buttons->priv->financial_ui, finc_dialog_fields[i][j]); g_object_set_data(o, "finc_field", GINT_TO_POINTER(j)); g_object_set_data(o, "finc_dialog", GINT_TO_POINTER(i)); } @@ -402,71 +343,6 @@ load_finc_dialogs(MathButtons *buttons) } -static void -update_angle_label (MathButtons *buttons) -{ - MPNumber x; - MPNumber pi, max_value, min_value, fraction, input, output; - char *label, input_text[1024], output_text[1024]; - - if (!buttons->priv->angle_label) - return; - - if (!math_equation_get_number(buttons->priv->equation, &x)) - return; - - mp_get_pi(&pi); - switch (math_equation_get_angle_units(buttons->priv->equation)) { - default: - case MP_DEGREES: - label = g_strdup(""); - break; - case MP_RADIANS: - /* Clip to the range ±2π */ - mp_multiply_integer(&pi, 2, &max_value); - mp_invert_sign(&max_value, &min_value); - if (!mp_is_equal(&x, &max_value) && !mp_is_equal(&x, &min_value)) { - mp_divide(&x, &max_value, &fraction); - mp_fractional_component(&fraction, &fraction); - mp_multiply(&fraction, &max_value, &input); - } - else { - mp_set_from_mp(&x, &input); - mp_set_from_integer(mp_is_negative(&input) ? -1 : 1, &fraction); - } - mp_cast_to_string(&input, 10, 10, 2, false, input_text, 1024); - - mp_multiply_integer(&fraction, 360, &output); - mp_cast_to_string(&output, 10, 10, 2, false, output_text, 1024); - label = g_strdup_printf(_("%s radians = %s degrees"), input_text, output_text); - break; - case MP_GRADIANS: - /* Clip to the range ±400 */ - mp_set_from_integer(400, &max_value); - mp_invert_sign(&max_value, &min_value); - if (!mp_is_equal(&x, &max_value) && !mp_is_equal(&x, &min_value)) { - mp_divide(&x, &max_value, &fraction); - mp_fractional_component(&fraction, &fraction); - mp_multiply(&fraction, &max_value, &input); - } - else { - mp_set_from_mp(&x, &input); - mp_set_from_integer(mp_is_negative(&input) ? -1 : 1, &fraction); - } - - mp_cast_to_string(&input, 10, 10, 2, false, input_text, 1024); - - mp_multiply_integer(&fraction, 360, &output); - mp_cast_to_string(&output, 10, 10, 2, false, output_text, 1024); - label = g_strdup_printf(_("%s gradians = %s degrees"), input_text, output_text); - break; - } - - gtk_label_set_text(GTK_LABEL(buttons->priv->angle_label), label); - g_free(label); -} - - static void update_bit_panel(MathButtons *buttons) { @@ -479,7 +355,7 @@ update_bit_panel(MathButtons *buttons) if (!buttons->priv->bit_panel) return; - + enabled = math_equation_get_number(buttons->priv->equation, &x); if (enabled) { @@ -495,7 +371,7 @@ update_bit_panel(MathButtons *buttons) gtk_widget_set_sensitive(buttons->priv->bit_panel, enabled); gtk_widget_set_sensitive(buttons->priv->base_label, enabled); - + if (!enabled) return; @@ -509,24 +385,24 @@ update_bit_panel(MathButtons *buttons) gtk_label_set_text(GTK_LABEL(buttons->priv->bit_labels[i]), label); } - base = math_equation_get_base(buttons->priv->equation); + base = math_equation_get_base(buttons->priv->equation); label = g_string_new(""); if (base != 8) { if (label->len != 0) g_string_append(label, " = "); - g_string_append_printf(label, "%lo", bits); + g_string_append_printf(label, "%" G_GINT64_MODIFIER "o", bits); g_string_append(label, "₈"); } if (base != 10) { if (label->len != 0) g_string_append(label, " = "); - g_string_append_printf(label, "%lu", bits); + g_string_append_printf(label, "%" G_GINT64_MODIFIER "u", bits); g_string_append(label, "₁₀"); } if (base != 16) { if (label->len != 0) g_string_append(label, " = "); - g_string_append_printf(label, "%lX", bits); + g_string_append_printf(label, "%" G_GINT64_MODIFIER "X", bits); g_string_append(label, "₁₆"); } @@ -535,97 +411,13 @@ update_bit_panel(MathButtons *buttons) } -static void -update_currency_label(MathButtons *buttons) -{ - MPNumber x, value; - char *label; - - if (!buttons->priv->currency_label) - return; - - if (!math_equation_get_number(buttons->priv->equation, &x)) - return; - - if (currency_convert(&x, - math_equation_get_source_currency(buttons->priv->equation), - math_equation_get_target_currency(buttons->priv->equation), - &value)) { - char input_text[1024], output_text[1024]; - const char *source_symbol, *target_symbol; - int i; - - mp_cast_to_string(&x, 10, 10, 2, false, input_text, 1024); - mp_cast_to_string(&value, 10, 10, 2, false, output_text, 1024); - - for (i = 0; strcmp(math_equation_get_source_currency(buttons->priv->equation), currency_names[i].short_name) != 0; i++); - source_symbol = currency_names[i].symbol; - for (i = 0; strcmp(math_equation_get_target_currency(buttons->priv->equation), currency_names[i].short_name) != 0; i++); - target_symbol = currency_names[i].symbol; - - /* Translators: first and third %s are currency symbols, second - * and fourth are amounts in these currencies, you may want to change - * the order of these, example: $100 = €100 */ - label = g_strdup_printf(_("%s%s = %s%s"), - source_symbol, input_text, - target_symbol, output_text); - } - else - label = g_strdup(""); - - gtk_label_set_text(GTK_LABEL(buttons->priv->currency_label), label); - g_free(label); -} - - static void display_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons) { - update_angle_label(buttons); - update_currency_label(buttons); update_bit_panel(buttons); } -static void -angle_unit_combobox_changed_cb(GtkWidget *combo, MathButtons *buttons) -{ - MPAngleUnit value; - GtkTreeModel *model; - GtkTreeIter iter; - - model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); - gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter); - gtk_tree_model_get(model, &iter, 1, &value, -1); - math_equation_set_angle_units(buttons->priv->equation, value); -} - - -static void -angle_unit_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons) -{ - GtkTreeModel *model; - GtkTreeIter iter; - gboolean valid; - - model = gtk_combo_box_get_model(GTK_COMBO_BOX(buttons->priv->angle_combo)); - valid = gtk_tree_model_get_iter_first(model, &iter); - - while (valid) { - gint v; - - gtk_tree_model_get(model, &iter, 1, &v, -1); - if (v == math_equation_get_angle_units(buttons->priv->equation)) - break; - valid = gtk_tree_model_iter_next(model, &iter); - } - if (!valid) - valid = gtk_tree_model_get_iter_first(model, &iter); - - gtk_combo_box_set_active_iter(GTK_COMBO_BOX(buttons->priv->angle_combo), &iter); -} - - static void base_combobox_changed_cb(GtkWidget *combo, MathButtons *buttons) { @@ -637,7 +429,7 @@ base_combobox_changed_cb(GtkWidget *combo, MathButtons *buttons) gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter); gtk_tree_model_get(model, &iter, 1, &value, -1); - math_equation_set_base(buttons->priv->equation, value); + math_buttons_set_programming_base(buttons, value); } @@ -647,7 +439,7 @@ base_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons) GtkTreeModel *model; GtkTreeIter iter; gboolean valid; - + if (buttons->priv->mode != PROGRAMMING) return; @@ -670,102 +462,6 @@ base_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons) } -static void -source_currency_combo_changed_cb(GtkWidget *combo, MathButtons *buttons) -{ - gchar *value; - GtkTreeModel *model; - GtkTreeIter iter; - - model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); - gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter); - gtk_tree_model_get(model, &iter, 0, &value, -1); - - math_equation_set_source_currency(buttons->priv->equation, value); - g_free (value); -} - - -static void -source_currency_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons) -{ - GtkTreeModel *model; - GtkTreeIter iter; - gboolean valid; - - if (buttons->priv->mode != FINANCIAL) - return; - - model = gtk_combo_box_get_model(GTK_COMBO_BOX(buttons->priv->source_currency_combo)); - valid = gtk_tree_model_get_iter_first(model, &iter); - - while (valid) { - gchar *v; - gboolean matched; - - gtk_tree_model_get(model, &iter, 0, &v, -1); - matched = strcmp (math_equation_get_source_currency(buttons->priv->equation), v) == 0; - g_free (v); - if (matched) - break; - valid = gtk_tree_model_iter_next(model, &iter); - } - if (!valid) - valid = gtk_tree_model_get_iter_first(model, &iter); - - gtk_combo_box_set_active_iter(GTK_COMBO_BOX(buttons->priv->source_currency_combo), &iter); - update_currency_label(buttons); -} - - -static void -target_currency_combo_changed_cb(GtkWidget *combo, MathButtons *buttons) -{ - gchar *value; - GtkTreeModel *model; - GtkTreeIter iter; - - model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); - gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter); - gtk_tree_model_get(model, &iter, 0, &value, -1); - - math_equation_set_target_currency(buttons->priv->equation, value); - g_free (value); -} - - -static void -target_currency_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *buttons) -{ - GtkTreeModel *model; - GtkTreeIter iter; - gboolean valid; - - if (buttons->priv->mode != FINANCIAL) - return; - - model = gtk_combo_box_get_model(GTK_COMBO_BOX(buttons->priv->target_currency_combo)); - valid = gtk_tree_model_get_iter_first(model, &iter); - - while (valid) { - gchar *v; - gboolean matched; - - gtk_tree_model_get(model, &iter, 0, &v, -1); - matched = strcmp (math_equation_get_target_currency(buttons->priv->equation), v) == 0; - g_free (v); - if (matched) - break; - valid = gtk_tree_model_iter_next(model, &iter); - } - if (!valid) - valid = gtk_tree_model_get_iter_first(model, &iter); - - gtk_combo_box_set_active_iter(GTK_COMBO_BOX(buttons->priv->target_currency_combo), &iter); - update_currency_label(buttons); -} - - static GtkWidget * load_mode(MathButtons *buttons, ButtonMode mode) { @@ -781,6 +477,7 @@ load_mode(MathButtons *buttons, ButtonMode mode) GError *error = NULL; switch (mode) { + default: case BASIC: builder_ptr = &buttons->priv->basic_ui; builder_file = UI_BASIC_FILE; @@ -802,7 +499,7 @@ load_mode(MathButtons *buttons, ButtonMode mode) panel = &buttons->priv->prog_panel; break; } - + if (*panel) return *panel; @@ -814,7 +511,7 @@ load_mode(MathButtons *buttons, ButtonMode mode) g_clear_error(&error); } *panel = GET_WIDGET(builder, "button_panel"); - gtk_box_pack_end(GTK_BOX(buttons), *panel, FALSE, TRUE, 0); + gtk_box_pack_end(GTK_BOX(buttons), *panel, TRUE, TRUE, 0); /* Configure buttons */ for (i = 0; button_data[i].widget_name != NULL; i++) { @@ -833,32 +530,8 @@ load_mode(MathButtons *buttons, ButtonMode mode) if (button_data[i].tooltip) gtk_widget_set_tooltip_text(button, _(button_data[i].tooltip)); - - atk_object_set_name (gtk_widget_get_accessible (button), button_data[i].widget_name); - - switch (button_data[i].class) { - case NUMBER: - set_tint(button, &buttons->priv->color_numbers, 1); - break; - case NUMBER_BOLD: - set_tint(button, &buttons->priv->color_numbers, 2); - break; - case OPERATOR: - set_tint(button, &buttons->priv->color_operator, 1); - break; - case FUNCTION: - set_tint(button, &buttons->priv->color_function, 1); - break; - case MEMORY: - set_tint(button, &buttons->priv->color_memory, 1); - break; - case GROUP: - set_tint(button, &buttons->priv->color_group, 1); - break; - case ACTION: - set_tint(button, &buttons->priv->color_action, 2); - break; - } + + atk_object_set_name(gtk_widget_get_accessible(button), button_data[i].widget_name); } /* Set special button data */ @@ -868,16 +541,26 @@ load_mode(MathButtons *buttons, ButtonMode mode) name = g_strdup_printf("calc_%d_button", i); button = GET_WIDGET(builder, name); if (button) { + gchar buffer[7]; + gint len; + g_object_set_data(G_OBJECT(button), "calc_digit", GINT_TO_POINTER(i)); - set_tint(button, &buttons->priv->color_numbers, 1); - gtk_button_set_label(GTK_BUTTON(button), math_equation_get_digit_text(buttons->priv->equation, i)); + len = g_unichar_to_utf8(math_equation_get_digit_text(buttons->priv->equation, i), buffer); + buffer[len] = '\0'; + gtk_button_set_label(GTK_BUTTON(button), buffer); } g_free(name); } widget = GET_WIDGET(builder, "calc_numeric_point_button"); - if (widget) - gtk_button_set_label(GTK_BUTTON(widget), math_equation_get_numeric_point_text(buttons->priv->equation)); - + if (widget) { + MpSerializer *serializer = math_equation_get_serializer(buttons->priv->equation); + gchar buffer[7]; + gint len; + len = g_unichar_to_utf8(mp_serializer_get_radix(serializer), buffer); + buffer[len] = '\0'; + gtk_button_set_label(GTK_BUTTON(widget), buffer); + } + widget = GET_WIDGET(builder, "calc_superscript_button"); if (widget) { buttons->priv->superscript_toggles = g_list_append(buttons->priv->superscript_toggles, widget); @@ -891,37 +574,6 @@ load_mode(MathButtons *buttons, ButtonMode mode) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE); } - if (mode == ADVANCED) { - GtkListStore *model; - GtkTreeIter iter; - GtkCellRenderer *renderer; - - buttons->priv->angle_label = GET_WIDGET(builder, "angle_label"); - - buttons->priv->angle_combo = GET_WIDGET(builder, "angle_units_combo"); - model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT); - gtk_combo_box_set_model(GTK_COMBO_BOX(buttons->priv->angle_combo), GTK_TREE_MODEL(model)); - gtk_list_store_append(GTK_LIST_STORE(model), &iter); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, - /* Advanced buttons: Angle unit combo box: Use degrees for trigonometric calculations */ - _("Degrees"), 1, MP_DEGREES, -1); - gtk_list_store_append(GTK_LIST_STORE(model), &iter); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, - /* Advanced buttons: Angle unit combo box: Use radians for trigonometric calculations */ - _("Radians"), 1, MP_RADIANS, -1); - gtk_list_store_append(GTK_LIST_STORE(model), &iter); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, - /* Advanced buttons: Angle unit combo box: Use gradians for trigonometric calculations */ - _("Gradians"), 1, MP_GRADIANS, -1); - renderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(buttons->priv->angle_combo), renderer, TRUE); - gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(buttons->priv->angle_combo), renderer, "text", 0); - - g_signal_connect(buttons->priv->angle_combo, "changed", G_CALLBACK(angle_unit_combobox_changed_cb), buttons); - g_signal_connect(buttons->priv->equation, "notify::angle-units", G_CALLBACK(angle_unit_cb), buttons); - angle_unit_cb(buttons->priv->equation, NULL, buttons); - } - if (mode == PROGRAMMING) { GtkListStore *model; GtkTreeIter iter; @@ -970,41 +622,8 @@ load_mode(MathButtons *buttons, ButtonMode mode) /* Setup financial functions */ if (mode == FINANCIAL) { - GtkListStore *model; - GtkCellRenderer *renderer; - load_finc_dialogs(buttons); - buttons->priv->source_currency_combo = GET_WIDGET(builder, "source_currency_combo"); - buttons->priv->target_currency_combo = GET_WIDGET(builder, "target_currency_combo"); - buttons->priv->currency_label = GET_WIDGET(builder, "currency_label"); - - model = gtk_list_store_new(1, G_TYPE_STRING); - - for (i = 0; currency_names[i].short_name != NULL; i++) { - GtkTreeIter iter; - - gtk_list_store_append(GTK_LIST_STORE(model), &iter); - gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, currency_names[i].short_name, -1); - } - - gtk_combo_box_set_model(GTK_COMBO_BOX(buttons->priv->source_currency_combo), GTK_TREE_MODEL(model)); - gtk_combo_box_set_model(GTK_COMBO_BOX(buttons->priv->target_currency_combo), GTK_TREE_MODEL(model)); - - renderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(buttons->priv->source_currency_combo), renderer, TRUE); - gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(buttons->priv->source_currency_combo), renderer, "text", 0); - renderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(buttons->priv->target_currency_combo), renderer, TRUE); - gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(buttons->priv->target_currency_combo), renderer, "text", 0); - - g_signal_connect(buttons->priv->source_currency_combo, "changed", G_CALLBACK(source_currency_combo_changed_cb), buttons); - g_signal_connect(buttons->priv->target_currency_combo, "changed", G_CALLBACK(target_currency_combo_changed_cb), buttons); - g_signal_connect(buttons->priv->equation, "notify::source-currency", G_CALLBACK(source_currency_changed_cb), buttons); - g_signal_connect(buttons->priv->equation, "notify::target-currency", G_CALLBACK(target_currency_changed_cb), buttons); - source_currency_changed_cb(buttons->priv->equation, NULL, buttons); - target_currency_changed_cb(buttons->priv->equation, NULL, buttons); - set_data(builder, "calc_finc_compounding_term_button", "finc_dialog", "ctrm_dialog"); set_data(builder, "calc_finc_double_declining_depreciation_button", "finc_dialog", "ddb_dialog"); set_data(builder, "calc_finc_future_value_button", "finc_dialog", "fv_dialog"); @@ -1020,11 +639,30 @@ load_mode(MathButtons *buttons, ButtonMode mode) gtk_builder_connect_signals(builder, buttons); display_changed_cb(buttons->priv->equation, NULL, buttons); - + return *panel; } +static void +converter_changed_cb(MathConverter *converter, MathButtons *buttons) +{ + Unit *from_unit, *to_unit; + + math_converter_get_conversion(converter, &from_unit, &to_unit); + if (buttons->priv->mode == FINANCIAL) { + math_equation_set_source_currency(buttons->priv->equation, unit_get_name(from_unit)); + math_equation_set_target_currency(buttons->priv->equation, unit_get_name(to_unit)); + } + else { + math_equation_set_source_units(buttons->priv->equation, unit_get_name(from_unit)); + math_equation_set_target_units(buttons->priv->equation, unit_get_name(to_unit)); + } + + g_object_unref(from_unit); + g_object_unref(to_unit); +} + static void load_buttons(MathButtons *buttons) @@ -1034,6 +672,12 @@ load_buttons(MathButtons *buttons) if (!gtk_widget_get_visible(GTK_WIDGET(buttons))) return; + if (!buttons->priv->converter) { + buttons->priv->converter = math_converter_new(buttons->priv->equation); + g_signal_connect(buttons->priv->converter, "changed", G_CALLBACK(converter_changed_cb), buttons); + gtk_box_pack_start(GTK_BOX(buttons), GTK_WIDGET(buttons->priv->converter), FALSE, TRUE, 0); + } + panel = load_mode(buttons, buttons->priv->mode); if (buttons->priv->active_panel == panel) return; @@ -1052,14 +696,13 @@ load_buttons(MathButtons *buttons) void math_buttons_set_mode(MathButtons *buttons, ButtonMode mode) { - ButtonMode old_mode; - + g_return_if_fail(buttons != NULL); + if (buttons->priv->mode == mode) return; - old_mode = buttons->priv->mode; buttons->priv->mode = mode; - + if (mode == PROGRAMMING) math_equation_set_base(buttons->priv->equation, buttons->priv->programming_base); else @@ -1067,6 +710,20 @@ math_buttons_set_mode(MathButtons *buttons, ButtonMode mode) load_buttons(buttons); + gtk_widget_set_visible(GTK_WIDGET(buttons->priv->converter), mode == ADVANCED || mode == FINANCIAL); + if (mode == ADVANCED) { + math_converter_set_category(buttons->priv->converter, NULL); + math_converter_set_conversion(buttons->priv->converter, + math_equation_get_source_units(buttons->priv->equation), + math_equation_get_target_units(buttons->priv->equation)); + } + else if (mode == FINANCIAL) { + math_converter_set_category(buttons->priv->converter, "currency"); + math_converter_set_conversion(buttons->priv->converter, + math_equation_get_source_currency(buttons->priv->equation), + math_equation_get_target_currency(buttons->priv->equation)); + } + g_object_notify(G_OBJECT(buttons), "mode"); } @@ -1081,13 +738,23 @@ math_buttons_get_mode(MathButtons *buttons) void math_buttons_set_programming_base(MathButtons *buttons, gint base) { + g_return_if_fail(buttons != NULL); + + if (base == buttons->priv->programming_base) + return; + buttons->priv->programming_base = base; + g_object_notify(G_OBJECT(buttons), "programming-base"); + + if (buttons->priv->mode == PROGRAMMING) + math_equation_set_base(buttons->priv->equation, base); } gint math_buttons_get_programming_base(MathButtons *buttons) { + g_return_val_if_fail(buttons != NULL, 10); return buttons->priv->programming_base; } @@ -1106,7 +773,7 @@ G_MODULE_EXPORT void subtract_cb(GtkWidget *widget, MathButtons *buttons) { - math_equation_insert_subtract(buttons->priv->equation); + math_equation_insert_subtract(buttons->priv->equation); } @@ -1170,7 +837,7 @@ button_menu_position_func(GtkMenu *menu, gint *x, gint *y, GtkAllocation allocation; GdkPoint loc; gint border; - + gdk_window_get_origin(gtk_widget_get_window(button), &loc.x, &loc.y); border = gtk_container_get_border_width(GTK_CONTAINER(button)); gtk_widget_get_allocation(button, &allocation); @@ -1187,161 +854,22 @@ popup_button_menu(GtkWidget *widget, GtkMenu *menu) } -static void -save_variable_cb(GtkWidget *widget, MathButtons *buttons) -{ - printf("save\n"); -} - - -static void -delete_variable_cb(GtkWidget *widget, MathButtons *buttons) -{ - printf("delete\n"); -} - - -static GtkWidget * -make_register_menu_item(MathButtons *buttons, const gchar *name, const MPNumber *value, gboolean can_modify, GCallback callback) -{ - gchar text[1024] = "", *mstr; - GtkWidget *item, *label; - - if (value) { - display_make_number(buttons->priv->equation, text, 1024, value); - mstr = g_strdup_printf("%s = %s", name, text); - } - else - mstr = g_strdup_printf("%s", name); - label = gtk_label_new(mstr); - gtk_label_set_use_markup(GTK_LABEL(label), TRUE); - gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); - g_free(mstr); - - item = gtk_menu_item_new(); - - // FIXME: Buttons don't work inside menus... - if (0) {//can_modify) { - GtkWidget *hbox, *button; - - hbox = gtk_hbox_new(FALSE, 6); - gtk_container_add(GTK_CONTAINER(item), hbox); - - gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0); - - button = gtk_button_new(); - gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_MENU)); - gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); - gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0); - g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(delete_variable_cb), buttons); - - button = gtk_button_new(); - gtk_button_set_image(GTK_BUTTON(button), gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_MENU)); - gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); - gtk_box_pack_end(GTK_BOX(hbox), button, FALSE, TRUE, 0); - g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(save_variable_cb), buttons); - } - else - gtk_container_add(GTK_CONTAINER(item), label); - - g_object_set_data(G_OBJECT(item), "register_id", g_strdup(name)); // FIXME: Memory leak - g_signal_connect(item, "activate", callback, buttons); - - return item; -} - - -static void -store_menu_cb(GtkMenuItem *menu, MathButtons *buttons) -{ - math_equation_store(buttons->priv->equation, g_object_get_data(G_OBJECT(menu), "register_id")); -} - - -void store_cb(GtkWidget *widget, MathButtons *buttons); +void memory_cb(GtkWidget *widget, MathButtons *buttons); G_MODULE_EXPORT void -store_cb(GtkWidget *widget, MathButtons *buttons) +memory_cb(GtkWidget *widget, MathButtons *buttons) { - int i; - GtkWidget *menu; - GtkWidget *item; - gchar **names; - - menu = gtk_menu_new(); - gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE); - set_tint(menu, &buttons->priv->color_memory, 1); - - names = math_variables_get_names(math_equation_get_variables(buttons->priv->equation)); - if (names[0] == NULL) { - item = gtk_menu_item_new_with_label(/* Text shown in store menu when no variables defined */ - _("No variables defined")); - gtk_widget_set_sensitive(item, FALSE); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - } - for (i = 0; names[i]; i++) { - MPNumber *value; - value = math_variables_get_value(math_equation_get_variables(buttons->priv->equation), names[i]); - item = make_register_menu_item(buttons, names[i], value, TRUE, G_CALLBACK(store_menu_cb)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - } - - g_strfreev(names); - - // FIXME - //item = gtk_menu_item_new_with_label(_("Add variable")); - //gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - - gtk_widget_show_all(menu); - popup_button_menu(widget, GTK_MENU(menu)); -} - - -static void -recall_menu_cb(GtkMenuItem *menu, MathButtons *buttons) -{ - math_equation_recall(buttons->priv->equation, g_object_get_data(G_OBJECT(menu), "register_id")); -} - - -void recall_cb(GtkWidget *widget, MathButtons *buttons); -G_MODULE_EXPORT -void -recall_cb(GtkWidget *widget, MathButtons *buttons) -{ - int i; - GtkWidget *menu; - GtkWidget *item; - gchar **names; - - menu = gtk_menu_new(); - gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE); - set_tint(menu, &buttons->priv->color_memory, 1); - - names = math_variables_get_names(math_equation_get_variables(buttons->priv->equation)); - if (names[0] == NULL) { - item = gtk_menu_item_new_with_label(/* Text shown in recall menu when no variables defined */ - _("No variables defined")); - gtk_widget_set_sensitive(item, FALSE); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - } - for (i = 0; names[i]; i++) { - MPNumber *value; - value = math_variables_get_value(math_equation_get_variables(buttons->priv->equation), names[i]); - item = make_register_menu_item(buttons, names[i], value, TRUE, G_CALLBACK(recall_menu_cb)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - } - - g_strfreev(names); + MathVariablePopup *popup; + GtkAllocation allocation; + gint x, y; - gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new()); - item = make_register_menu_item(buttons, "ans", math_equation_get_answer(buttons->priv->equation), FALSE, G_CALLBACK(recall_menu_cb)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); - item = make_register_menu_item(buttons, "rand", NULL, FALSE, G_CALLBACK(recall_menu_cb)); - gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); + popup = math_variable_popup_new(buttons->priv->equation); + gtk_window_set_transient_for(GTK_WINDOW(popup), GTK_WINDOW(gtk_widget_get_toplevel(widget))); - gtk_widget_show_all(menu); - popup_button_menu(widget, GTK_MENU(menu)); + gtk_widget_get_allocation(widget, &allocation); + gdk_window_get_root_coords(gtk_widget_get_window(widget), allocation.x, allocation.y, &x, &y); + gtk_window_move(GTK_WINDOW(popup), x, y); + gtk_widget_show(GTK_WIDGET(popup)); } @@ -1356,7 +884,6 @@ shift_left_cb(GtkWidget *widget, MathButtons *buttons) menu = buttons->priv->shift_left_menu = gtk_menu_new(); gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE); - set_tint(menu, &buttons->priv->color_action, 1); for (i = 1; i < 16; i++) { GtkWidget *item, *label; @@ -1400,7 +927,6 @@ shift_right_cb(GtkWidget *widget, MathButtons *buttons) menu = buttons->priv->shift_right_menu = gtk_menu_new(); gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE); - set_tint(menu, &buttons->priv->color_action, 1); for (i = 1; i < 16; i++) { GtkWidget *item, *label; @@ -1429,7 +955,7 @@ shift_right_cb(GtkWidget *widget, MathButtons *buttons) } } - popup_button_menu(widget, GTK_MENU(buttons->priv->shift_right_menu)); + popup_button_menu(widget, GTK_MENU(buttons->priv->shift_right_menu)); } @@ -1448,10 +974,10 @@ function_cb(GtkWidget *widget, MathButtons *buttons) if (!buttons->priv->function_menu) { gint i; GtkWidget *menu; - struct + struct { gchar *name, *function; - } functions[] = + } functions[] = { { /* Tooltip for the integer component button */ N_("Integer Component"), "int " }, @@ -1470,20 +996,19 @@ function_cb(GtkWidget *widget, MathButtons *buttons) menu = buttons->priv->function_menu = gtk_menu_new(); gtk_menu_set_reserve_toggle_size(GTK_MENU(menu), FALSE); - set_tint(menu, &buttons->priv->color_function, 1); for (i = 0; functions[i].name != NULL; i++) { GtkWidget *item; - + item = gtk_menu_item_new_with_label(_(functions[i].name)); - g_object_set_data(G_OBJECT(item), "function", g_strdup (functions[i].function)); + g_object_set_data(G_OBJECT(item), "function", g_strdup(functions[i].function)); gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); g_signal_connect(item, "activate", G_CALLBACK(insert_function_cb), buttons); gtk_widget_show(item); } } - popup_button_menu(widget, GTK_MENU(buttons->priv->function_menu)); + popup_button_menu(widget, GTK_MENU(buttons->priv->function_menu)); } @@ -1492,7 +1017,7 @@ G_MODULE_EXPORT void factorize_cb(GtkWidget *widget, MathButtons *buttons) { - math_equation_factorize (buttons->priv->equation); + math_equation_factorize(buttons->priv->equation); } @@ -1550,7 +1075,7 @@ finc_activate_cb(GtkWidget *widget, MathButtons *buttons) if (finc_dialog_fields[dialog][field+1] == NULL) { GtkWidget *dialog_widget; dialog_widget = gtk_widget_get_toplevel(widget); - if (gtk_widget_is_toplevel (dialog_widget)) { + if (gtk_widget_is_toplevel(dialog_widget)) { gtk_dialog_response(GTK_DIALOG(dialog_widget), GTK_RESPONSE_OK); return; @@ -1577,7 +1102,7 @@ finc_response_cb(GtkWidget *widget, gint response_id, MathButtons *buttons) if (response_id != GTK_RESPONSE_OK) return; - dialog = GPOINTER_TO_INT (g_object_get_data(G_OBJECT(widget), "finc_dialog")); + dialog = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "finc_dialog")); for (i = 0; i < 4; i++) { if (finc_dialog_fields[dialog][i] == NULL) { @@ -1602,7 +1127,7 @@ character_code_dialog_response_cb(GtkWidget *dialog, gint response_id, MathButto text = gtk_entry_get_text(GTK_ENTRY(buttons->priv->character_code_entry)); - if (response_id == GTK_RESPONSE_OK) { + if (response_id == GTK_RESPONSE_OK) { MPNumber x; int i = 0; @@ -1653,16 +1178,35 @@ bit_toggle_cb(GtkWidget *event_box, GdkEventButton *event, MathButtons *buttons) } +static void +remove_trailing_spaces(MathButtons *buttons) +{ + GtkTextMark *insert_mark; + GtkTextIter start, end; + insert_mark = gtk_text_buffer_get_insert (GTK_TEXT_BUFFER(buttons->priv->equation)); + gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(buttons->priv->equation), &end, insert_mark); + start = end; + while (gtk_text_iter_backward_char(&start)) { + if (!g_unichar_isspace(gtk_text_iter_get_char(&start))) + break; + gtk_text_buffer_delete(GTK_TEXT_BUFFER(buttons->priv->equation), &start, &end); + } +} + void set_superscript_cb(GtkWidget *widget, MathButtons *buttons); G_MODULE_EXPORT void set_superscript_cb(GtkWidget *widget, MathButtons *buttons) { - if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) - math_equation_set_number_mode(buttons->priv->equation, SUPERSCRIPT); + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { + math_equation_set_number_mode(buttons->priv->equation, SUPERSCRIPT); + if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(buttons->priv->equation))) { + remove_trailing_spaces(buttons); + } + } else if (math_equation_get_number_mode(buttons->priv->equation) == SUPERSCRIPT) - math_equation_set_number_mode(buttons->priv->equation, NORMAL); + math_equation_set_number_mode(buttons->priv->equation, NORMAL); } @@ -1671,10 +1215,14 @@ G_MODULE_EXPORT void set_subscript_cb(GtkWidget *widget, MathButtons *buttons) { - if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) - math_equation_set_number_mode(buttons->priv->equation, SUBSCRIPT); + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) { + math_equation_set_number_mode(buttons->priv->equation, SUBSCRIPT); + if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(buttons->priv->equation))) { + remove_trailing_spaces(buttons); + } + } else if (math_equation_get_number_mode(buttons->priv->equation) == SUBSCRIPT) - math_equation_set_number_mode(buttons->priv->equation, NORMAL); + math_equation_set_number_mode(buttons->priv->equation, NORMAL); } @@ -1683,7 +1231,7 @@ number_mode_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *bu { GList *i; NumberMode mode; - + mode = math_equation_get_number_mode(equation); for (i = buttons->priv->superscript_toggles; i; i = i->next) { @@ -1698,18 +1246,18 @@ number_mode_changed_cb(MathEquation *equation, GParamSpec *spec, MathButtons *bu static void -math_buttons_set_property (GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) +math_buttons_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { MathButtons *self; - self = MATH_BUTTONS (object); + self = MATH_BUTTONS(object); switch (prop_id) { case PROP_EQUATION: - self->priv->equation = g_value_get_object (value); + self->priv->equation = g_value_get_object(value); math_buttons_set_mode(self, self->priv->mode); g_signal_connect(self->priv->equation, "notify::display", G_CALLBACK(display_changed_cb), self); g_signal_connect(self->priv->equation, "notify::number-mode", G_CALLBACK(number_mode_changed_cb), self); @@ -1719,41 +1267,47 @@ math_buttons_set_property (GObject *object, display_changed_cb(self->priv->equation, NULL, self); break; case PROP_MODE: - math_buttons_set_mode(self, g_value_get_int (value)); + math_buttons_set_mode(self, g_value_get_int(value)); + break; + case PROP_PROGRAMMING_BASE: + math_buttons_set_programming_base(self, g_value_get_int(value)); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void -math_buttons_get_property (GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) +math_buttons_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { MathButtons *self; - self = MATH_BUTTONS (object); + self = MATH_BUTTONS(object); switch (prop_id) { case PROP_EQUATION: - g_value_set_object (value, self->priv->equation); + g_value_set_object(value, self->priv->equation); break; case PROP_MODE: - g_value_set_int (value, self->priv->mode); + g_value_set_int(value, self->priv->mode); + break; + case PROP_PROGRAMMING_BASE: + g_value_set_int(value, math_buttons_get_programming_base(self)); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void -math_buttons_class_init (MathButtonsClass *klass) +math_buttons_class_init(MathButtonsClass *klass) { static GEnumValue button_mode_values[] = { @@ -1763,43 +1317,45 @@ math_buttons_class_init (MathButtonsClass *klass) {PROGRAMMING, "programming", "programming"}, {0, NULL, NULL} }; - GObjectClass *object_class = G_OBJECT_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->get_property = math_buttons_get_property; object_class->set_property = math_buttons_set_property; - g_type_class_add_private (klass, sizeof (MathButtonsPrivate)); + g_type_class_add_private(klass, sizeof(MathButtonsPrivate)); button_mode_type = g_enum_register_static("ButtonMode", button_mode_values); - g_object_class_install_property (object_class, - PROP_EQUATION, - g_param_spec_object ("equation", - "equation", - "Equation being controlled", - math_equation_get_type(), - G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - g_object_class_install_property (object_class, - PROP_MODE, - g_param_spec_enum ("mode", - "mode", - "Button mode", - button_mode_type, - BASIC, - G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_EQUATION, + g_param_spec_object("equation", + "equation", + "Equation being controlled", + math_equation_get_type(), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property(object_class, + PROP_MODE, + g_param_spec_enum("mode", + "mode", + "Button mode", + button_mode_type, + BASIC, + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_PROGRAMMING_BASE, + g_param_spec_int("programming-base", + "programming-base", + "Base to use in programming mode", + 2, 16, 10, + G_PARAM_READWRITE)); } static void -math_buttons_init (MathButtons *buttons) +math_buttons_init(MathButtons *buttons) { - buttons->priv = G_TYPE_INSTANCE_GET_PRIVATE (buttons, math_buttons_get_type(), MathButtonsPrivate); + buttons->priv = G_TYPE_INSTANCE_GET_PRIVATE(buttons, math_buttons_get_type(), MathButtonsPrivate); + gtk_box_set_spacing(GTK_BOX(buttons), 6); buttons->priv->programming_base = 10; - gdk_color_parse("#0000FF", &buttons->priv->color_numbers); - gdk_color_parse("#00FF00", &buttons->priv->color_action); - gdk_color_parse("#FF0000", &buttons->priv->color_operator); - gdk_color_parse("#00FFFF", &buttons->priv->color_function); - gdk_color_parse("#FF00FF", &buttons->priv->color_memory); - gdk_color_parse("#FFFFFF", &buttons->priv->color_group); g_signal_connect(G_OBJECT(buttons), "show", G_CALLBACK(load_buttons), NULL); } diff --git a/src/math-buttons.h b/src/math-buttons.h index e09c4c2..405a3de 100644 --- a/src/math-buttons.h +++ b/src/math-buttons.h @@ -1,19 +1,11 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MATH_BUTTONS_H @@ -29,32 +21,36 @@ G_BEGIN_DECLS typedef struct MathButtonsPrivate MathButtonsPrivate; -typedef struct { +typedef struct +{ GtkVBox parent_instance; - MathButtonsPrivate* priv; + MathButtonsPrivate *priv; } MathButtons; -typedef struct { - GtkVBoxClass parent_class; +typedef struct +{ + GtkVBoxClass parent_class; } MathButtonsClass; typedef enum { - BASIC, - ADVANCED, - FINANCIAL, - PROGRAMMING + BASIC, + ADVANCED, + FINANCIAL, + PROGRAMMING } ButtonMode; GType math_buttons_get_type(void); -MathButtons* math_buttons_new(MathEquation* equation); +MathButtons *math_buttons_new(MathEquation *equation); + +void math_buttons_set_mode(MathButtons *buttons, ButtonMode mode); -void math_buttons_set_mode(MathButtons* buttons, ButtonMode mode); +ButtonMode math_buttons_get_mode(MathButtons *buttons); -ButtonMode math_buttons_get_mode(MathButtons* buttons); +void math_buttons_set_programming_base(MathButtons *buttons, gint base); -void math_buttons_set_programming_base(MathButtons* buttons, gint base); +gint math_buttons_get_programming_base(MathButtons *buttons); -gint math_buttons_get_programming_base(MathButtons* buttons); +G_END_DECLS #endif /* MATH_BUTTONS_H */ diff --git a/src/math-converter.c b/src/math-converter.c new file mode 100644 index 0000000..ff28500 --- /dev/null +++ b/src/math-converter.c @@ -0,0 +1,440 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include + +#include "math-converter.h" +#include "unit-manager.h" +#include "currency-manager.h" + +enum { + CHANGED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = { 0, }; + +struct MathConverterPrivate +{ + MathEquation *equation; + + gchar *category; + + GtkWidget *from_combo; + GtkWidget *to_combo; + + GtkWidget *result_label; +}; + + +G_DEFINE_TYPE (MathConverter, math_converter, GTK_TYPE_HBOX); + +static void display_changed_cb(MathEquation *equation, GParamSpec *spec, MathConverter *converter); +static void update_from_model(MathConverter *converter); + + +MathConverter * +math_converter_new(MathEquation *equation) +{ + MathConverter *converter = g_object_new(math_converter_get_type(), NULL); + converter->priv->equation = g_object_ref(equation); + g_signal_connect(converter->priv->equation, "notify::display", G_CALLBACK(display_changed_cb), converter); + update_from_model(converter); + return converter; +} + + +static gboolean +convert_equation(MathConverter *converter, const MPNumber *x, MPNumber *z) +{ + GtkTreeIter from_iter, to_iter; + UnitCategory *category = NULL; + Unit *source_unit = NULL, *target_unit = NULL; + gboolean result; + + if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(converter->priv->from_combo), &from_iter) || + !gtk_combo_box_get_active_iter(GTK_COMBO_BOX(converter->priv->to_combo), &to_iter)) + return FALSE; + + gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(converter->priv->from_combo)), &from_iter, 1, &category, 2, &source_unit, -1); + gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(converter->priv->to_combo)), &to_iter, 2, &target_unit, -1); + + result = unit_category_convert(category, x, source_unit, target_unit, z); + + if (category) + g_object_unref(category); + if (source_unit) + g_object_unref(source_unit); + if (target_unit) + g_object_unref(target_unit); + + return result; +} + + +static void +update_result_label(MathConverter *converter) +{ + MPNumber x, z; + gboolean enabled; + + if (!converter->priv->result_label) + return; + + if (math_equation_get_number(converter->priv->equation, &x)) + enabled = convert_equation(converter, &x, &z); + else + enabled = FALSE; + + gtk_widget_set_sensitive(converter->priv->result_label, enabled); + if (enabled) { + gchar *source_text, *target_text, *label; + Unit *source_unit, *target_unit; + + math_converter_get_conversion(converter, &source_unit, &target_unit); + + source_text = unit_format(source_unit, &x); + target_text = unit_format(target_unit, &z); + label = g_strdup_printf("%s = %s", source_text, target_text); + gtk_label_set_text(GTK_LABEL(converter->priv->result_label), label); + + g_free(source_text); + g_free(target_text); + g_free(label); + + g_object_unref(source_unit); + g_object_unref(target_unit); + } +} + + +static void +display_changed_cb(MathEquation *equation, GParamSpec *spec, MathConverter *converter) +{ + update_result_label(converter); +} + + +static void +update_from_model(MathConverter *converter) +{ + GtkTreeStore *from_model; + + from_model = gtk_tree_store_new(3, G_TYPE_STRING, G_TYPE_OBJECT, G_TYPE_OBJECT); + + if (converter->priv->category == NULL) { + const GList *categories, *iter; + + categories = unit_manager_get_categories(unit_manager_get_default()); + for (iter = categories; iter; iter = iter->next) { + UnitCategory *category = iter->data; + GtkTreeIter parent; + const GList *unit_iter; + + gtk_tree_store_append(from_model, &parent, NULL); + gtk_tree_store_set(from_model, &parent, 0, unit_category_get_display_name(category), 1, category, -1); + + for (unit_iter = unit_category_get_units(category); unit_iter; unit_iter = unit_iter->next) { + Unit *unit = unit_iter->data; + GtkTreeIter iter; + + gtk_tree_store_append(from_model, &iter, &parent); + gtk_tree_store_set(from_model, &iter, 0, unit_get_display_name(unit), 1, category, 2, unit, -1); + } + } + } + else { + UnitCategory *category; + const GList *unit_iter; + + category = unit_manager_get_category(unit_manager_get_default(), converter->priv->category); + for (unit_iter = unit_category_get_units(category); unit_iter; unit_iter = unit_iter->next) { + Unit *unit = unit_iter->data; + GtkTreeIter iter; + + gtk_tree_store_append(from_model, &iter, NULL); + gtk_tree_store_set(from_model, &iter, 0, unit_get_display_name(unit), 1, category, 2, unit, -1); + } + } + + gtk_combo_box_set_model(GTK_COMBO_BOX(converter->priv->from_combo), GTK_TREE_MODEL(from_model)); +} + + +void +math_converter_set_category(MathConverter *converter, const gchar *category) +{ + g_return_if_fail (converter != NULL); + + if (category == NULL && converter->priv->category == NULL) + return; + if (category != NULL && converter->priv->category != NULL && strcmp(category, converter->priv->category) == 0) + return; + + g_free(converter->priv->category); + converter->priv->category = g_strdup(category); + + update_from_model(converter); +} + + +const gchar * +math_converter_get_category(MathConverter *converter) +{ + g_return_val_if_fail (converter != NULL, NULL); + return converter->priv->category; +} + + +static gboolean +iter_is_unit(GtkTreeModel *model, GtkTreeIter *iter, Unit *unit) +{ + Unit *u; + + gtk_tree_model_get(model, iter, 2, &u, -1); + + if (!u) + return FALSE; + + g_object_unref(u); + if (u == unit) + return TRUE; + + return FALSE; +} + + +static gboolean +set_active_unit(GtkComboBox *combo, GtkTreeIter *iter, Unit *unit) +{ + GtkTreeModel *model; + GtkTreeIter child_iter; + + model = gtk_combo_box_get_model(combo); + + if (iter && iter_is_unit(model, iter, unit)) { + gtk_combo_box_set_active_iter(combo, iter); + return TRUE; + } + + if (!gtk_tree_model_iter_children(model, &child_iter, iter)) + return FALSE; + + do { + if (set_active_unit(combo, &child_iter, unit)) + return TRUE; + } while (gtk_tree_model_iter_next(model, &child_iter)); + + return FALSE; +} + + +void +math_converter_set_conversion(MathConverter *converter, /*const gchar *category,*/ const gchar *unit_a, const gchar *unit_b) +{ + Unit *ua; + Unit *ub; + + g_return_if_fail (converter != NULL); + g_return_if_fail (unit_a != NULL); + g_return_if_fail (unit_b != NULL); + + ua = unit_manager_get_unit_by_name(unit_manager_get_default(), unit_a); + ub = unit_manager_get_unit_by_name(unit_manager_get_default(), unit_b); + if (!ua || !ub) + { + GtkTreeModel *model; + GtkTreeIter iter; + + /* Select the first unit */ + model = gtk_combo_box_get_model(GTK_COMBO_BOX(converter->priv->from_combo)); + if (gtk_tree_model_get_iter_first(model, &iter)) { + GtkTreeIter child_iter; + while (gtk_tree_model_iter_children(model, &child_iter, &iter)) + iter = child_iter; + gtk_combo_box_set_active_iter(GTK_COMBO_BOX(converter->priv->from_combo), &iter); + } + return; + } + + set_active_unit(GTK_COMBO_BOX(converter->priv->from_combo), NULL, ua); + set_active_unit(GTK_COMBO_BOX(converter->priv->to_combo), NULL, ub); +} + + +void +math_converter_get_conversion(MathConverter *converter, Unit **from_unit, Unit **to_unit) +{ + GtkTreeIter from_iter, to_iter; + + g_return_if_fail (converter != NULL); + g_return_if_fail (from_unit != NULL); + g_return_if_fail (to_unit != NULL); + + gtk_combo_box_get_active_iter(GTK_COMBO_BOX(converter->priv->from_combo), &from_iter); + gtk_combo_box_get_active_iter(GTK_COMBO_BOX(converter->priv->to_combo), &to_iter); + + gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(converter->priv->from_combo)), &from_iter, 2, from_unit, -1); + gtk_tree_model_get(gtk_combo_box_get_model(GTK_COMBO_BOX(converter->priv->to_combo)), &to_iter, 2, to_unit, -1); +} + + +static void +math_converter_class_init(MathConverterClass *klass) +{ + g_type_class_add_private(klass, sizeof(MathConverterPrivate)); + + signals[CHANGED] = + g_signal_new("changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (MathConverterClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); +} + + +static void +from_combobox_changed_cb(GtkWidget *combo, MathConverter *converter) +{ + GtkTreeModel *model; + GtkTreeIter iter; + UnitCategory *category; + Unit *unit; + const GList *unit_iter; + + model = gtk_combo_box_get_model(GTK_COMBO_BOX(combo)); + if (!gtk_combo_box_get_active_iter(GTK_COMBO_BOX(combo), &iter)) + return; + gtk_tree_model_get(model, &iter, 1, &category, 2, &unit, -1); + + /* Set the to combobox to be the list of units can be converted to */ + model = GTK_TREE_MODEL(gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_OBJECT, G_TYPE_OBJECT)); + for (unit_iter = unit_category_get_units(category); unit_iter; unit_iter = unit_iter->next) { + Unit *u = unit_iter->data; + if (u == unit) + continue; + gtk_list_store_append(GTK_LIST_STORE(model), &iter); + gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, unit_get_display_name(u), 1, category, 2, u, -1); + } + gtk_combo_box_set_model(GTK_COMBO_BOX(converter->priv->to_combo), model); + + /* Select the first possible unit */ + gtk_combo_box_set_active(GTK_COMBO_BOX(converter->priv->to_combo), 0); + + g_object_unref(category); + g_object_unref(unit); +} + + +static void +to_combobox_changed_cb(GtkWidget *combo, MathConverter *converter) +{ + /* Conversion must have changed */ + update_result_label(converter); + + g_signal_emit(converter, signals[CHANGED], 0); +} + + +static void +from_cell_data_func(GtkCellLayout *cell_layout, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + g_object_set(cell, "sensitive", !gtk_tree_model_iter_has_child(tree_model, iter), NULL); +} + + +static void +currency_updated_cb(CurrencyManager *manager, MathConverter *converter) +{ + update_result_label(converter); +} + +static void +swap_button_clicked_cb(GtkButton *button, MathConverter *converter) +{ + Unit *from_unit, *to_unit; + MPNumber x, z; + + if (math_equation_get_number(converter->priv->equation, &x) && + convert_equation(converter, &x, &z)) + math_equation_set_number(converter->priv->equation, &z); + + math_converter_get_conversion(converter, &from_unit, &to_unit); + set_active_unit(GTK_COMBO_BOX(converter->priv->from_combo), NULL, to_unit); + set_active_unit(GTK_COMBO_BOX(converter->priv->to_combo), NULL, from_unit); + + update_result_label(converter); + + g_object_unref(from_unit); + g_object_unref(to_unit); +} + +static void +math_converter_init(MathConverter *converter) +{ + GtkWidget *hbox, *label, *swap_button; + GtkCellRenderer *renderer; + + converter->priv = G_TYPE_INSTANCE_GET_PRIVATE(converter, math_converter_get_type(), MathConverterPrivate); + + gtk_box_set_spacing(GTK_BOX(converter), 6); + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_show(hbox); + gtk_box_pack_start(GTK_BOX(converter), hbox, FALSE, TRUE, 0); + + converter->priv->from_combo = gtk_combo_box_new (); + + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(converter->priv->from_combo), renderer, TRUE); + gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(converter->priv->from_combo), renderer, "text", 0); + gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(converter->priv->from_combo), + renderer, + from_cell_data_func, + NULL, NULL); + g_signal_connect(converter->priv->from_combo, "changed", G_CALLBACK(from_combobox_changed_cb), converter); + gtk_widget_show(converter->priv->from_combo); + gtk_box_pack_start(GTK_BOX(hbox), converter->priv->from_combo, FALSE, TRUE, 0); + + label = gtk_label_new(/* Label that is displayed between the two conversion combo boxes, e.g. "[degrees] in [radians]" */ + _(" in ")); + gtk_widget_show(label); + gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, TRUE, 5); + + converter->priv->to_combo = gtk_combo_box_new(); + renderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(converter->priv->to_combo), renderer, TRUE); + gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(converter->priv->to_combo), renderer, "text", 0); + g_signal_connect(converter->priv->to_combo, "changed", G_CALLBACK(to_combobox_changed_cb), converter); + gtk_widget_show(converter->priv->to_combo); + gtk_box_pack_start(GTK_BOX(hbox), converter->priv->to_combo, FALSE, TRUE, 0); + + swap_button = gtk_button_new_with_label ("⇆"); + gtk_widget_set_tooltip_text (swap_button, + /* Tooltip for swap conversion button */ + _("Switch conversion units")); + gtk_button_set_relief (GTK_BUTTON (swap_button), GTK_RELIEF_NONE); + g_signal_connect (swap_button, "clicked", G_CALLBACK (swap_button_clicked_cb), converter); + gtk_widget_show(swap_button); + gtk_box_pack_start(GTK_BOX(hbox), swap_button, FALSE, TRUE, 0); + + converter->priv->result_label = gtk_label_new(""); + gtk_misc_set_alignment(GTK_MISC(converter->priv->result_label), 1.0, 0.5); + gtk_widget_set_sensitive(converter->priv->result_label, FALSE); + gtk_widget_show(converter->priv->result_label); + gtk_box_pack_start(GTK_BOX(converter), converter->priv->result_label, TRUE, TRUE, 0); + + g_signal_connect(currency_manager_get_default(), "updated", G_CALLBACK(currency_updated_cb), converter); +} diff --git a/src/math-converter.h b/src/math-converter.h new file mode 100644 index 0000000..f67b425 --- /dev/null +++ b/src/math-converter.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef MATH_CONVERTER_H +#define MATH_CONVERTER_H + +#include +#include + +#include "math-equation.h" +#include "unit.h" + +G_BEGIN_DECLS + +#define MATH_CONVERTER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_converter_get_type(), MathConverter)) + +typedef struct MathConverterPrivate MathConverterPrivate; + +typedef struct +{ + GtkHBox parent_instance; + MathConverterPrivate *priv; +} MathConverter; + +typedef struct +{ + GtkHBoxClass parent_class; + + void (*changed)(MathConverter *converter); +} MathConverterClass; + +GType math_converter_get_type(void); + +MathConverter *math_converter_new(MathEquation *equation); + +void math_converter_set_category(MathConverter *converter, const gchar *category); + +const gchar *math_converter_get_category(MathConverter *converter); + +void math_converter_set_conversion(MathConverter *converter, /*const gchar *category,*/ const gchar *unit_a, const gchar *unit_b); + +void math_converter_get_conversion(MathConverter *converter, Unit **from_unit, Unit **to_unit); + +G_END_DECLS + +#endif /* MATH_CONVERTER_H */ diff --git a/src/math-display.c b/src/math-display.c index b809695..6718665 100644 --- a/src/math-display.c +++ b/src/math-display.c @@ -1,19 +1,11 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -37,23 +29,26 @@ struct MathDisplayPrivate /* Buffer that shows errors etc */ GtkTextBuffer *info_buffer; + + /* Spinner widget that shows if we're calculating a response */ + GtkWidget *spinner; }; -G_DEFINE_TYPE (MathDisplay, math_display, GTK_TYPE_VBOX); +G_DEFINE_TYPE (MathDisplay, math_display, GTK_TYPE_VIEWPORT); #define GET_WIDGET(ui, name) GTK_WIDGET(gtk_builder_get_object(ui, name)) MathDisplay * math_display_new() { - return g_object_new (math_display_get_type(), "equation", math_equation_new(), NULL); + return g_object_new(math_display_get_type(), "equation", math_equation_new(), NULL); } MathDisplay * math_display_new_with_equation(MathEquation *equation) { - return g_object_new (math_display_get_type(), "equation", equation, NULL); + return g_object_new(math_display_get_type(), "equation", equation, NULL); } @@ -69,24 +64,77 @@ display_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathDisplay *display { int state; guint32 c; + guint new_keyval = 0; + + /* Treat keypad keys as numbers even when numlock is off */ + switch(event->keyval) + { + case GDK_KEY_KP_Insert: + new_keyval = GDK_KEY_0; + break; + case GDK_KEY_KP_End: + new_keyval = GDK_KEY_1; + break; + case GDK_KEY_KP_Down: + new_keyval = GDK_KEY_2; + break; + case GDK_KEY_KP_Page_Down: + new_keyval = GDK_KEY_3; + break; + case GDK_KEY_KP_Left: + new_keyval = GDK_KEY_4; + break; + case GDK_KEY_KP_Begin: /* This is apparently what "5" does when numlock is off. */ + new_keyval = GDK_KEY_5; + break; + case GDK_KEY_KP_Right: + new_keyval = GDK_KEY_6; + break; + case GDK_KEY_KP_Home: + new_keyval = GDK_KEY_7; + break; + case GDK_KEY_KP_Up: + new_keyval = GDK_KEY_8; + break; + case GDK_KEY_KP_Page_Up: + new_keyval = GDK_KEY_9; + break; + } + + if (new_keyval) { + gboolean result; + GdkEvent *new_event; + + new_event = gdk_event_copy((GdkEvent *)event); + ((GdkEventKey *)new_event)->keyval = new_keyval; + g_signal_emit_by_name(widget, "key-press-event", new_event, &result); + gdk_event_free(new_event); + return result; + } state = event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK); c = gdk_keyval_to_unicode(event->keyval); /* Solve on enter */ - if (event->keyval == GDK_Return || event->keyval == GDK_KP_Enter) { + if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) { math_equation_solve(display->priv->equation); return TRUE; } /* Clear on escape */ - if ((event->keyval == GDK_Escape && state == 0) || - (event->keyval == GDK_BackSpace && state == GDK_CONTROL_MASK) || - (event->keyval == GDK_Delete && state == GDK_SHIFT_MASK)) { + if ((event->keyval == GDK_KEY_Escape && state == 0) || + (event->keyval == GDK_KEY_BackSpace && state == GDK_CONTROL_MASK) || + (event->keyval == GDK_KEY_Delete && state == GDK_SHIFT_MASK)) { math_equation_clear(display->priv->equation); return TRUE; } + /* Numeric keypad will often insert '.' regardless of locale */ + if (event->keyval == GDK_KEY_KP_Decimal) { + math_equation_insert_numeric_point(display->priv->equation); + return TRUE; + } + /* Substitute */ if (state == 0) { if (c == '*') { @@ -107,34 +155,34 @@ display_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathDisplay *display if (state == GDK_CONTROL_MASK) { switch(event->keyval) { - case GDK_bracketleft: + case GDK_KEY_bracketleft: math_equation_insert(display->priv->equation, "⌈"); return TRUE; - case GDK_bracketright: + case GDK_KEY_bracketright: math_equation_insert(display->priv->equation, "⌉"); return TRUE; - case GDK_e: + case GDK_KEY_e: math_equation_insert_exponent(display->priv->equation); return TRUE; - case GDK_f: + case GDK_KEY_f: math_equation_factorize(display->priv->equation); return TRUE; - case GDK_i: + case GDK_KEY_i: math_equation_insert(display->priv->equation, "⁻¹"); return TRUE; - case GDK_p: + case GDK_KEY_p: math_equation_insert(display->priv->equation, "π"); return TRUE; - case GDK_r: + case GDK_KEY_r: math_equation_insert(display->priv->equation, "√"); return TRUE; - case GDK_u: + case GDK_KEY_u: math_equation_insert(display->priv->equation, "µ"); return TRUE; - case GDK_minus: + case GDK_KEY_minus: math_equation_insert(display->priv->equation, "⁻"); return TRUE; - case GDK_apostrophe: + case GDK_KEY_apostrophe: math_equation_insert(display->priv->equation, "°"); return TRUE; } @@ -142,10 +190,10 @@ display_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathDisplay *display if (state == GDK_MOD1_MASK) { switch(event->keyval) { - case GDK_bracketleft: + case GDK_KEY_bracketleft: math_equation_insert(display->priv->equation, "⌊"); return TRUE; - case GDK_bracketright: + case GDK_KEY_bracketright: math_equation_insert(display->priv->equation, "⌋"); return TRUE; } @@ -154,34 +202,44 @@ display_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathDisplay *display if (state == GDK_CONTROL_MASK || math_equation_get_number_mode(display->priv->equation) == SUPERSCRIPT) { switch(event->keyval) { - case GDK_0: + case GDK_KEY_0: + case GDK_KEY_KP_0: math_equation_insert(display->priv->equation, "⁰"); return TRUE; - case GDK_1: + case GDK_KEY_1: + case GDK_KEY_KP_1: math_equation_insert(display->priv->equation, "¹"); return TRUE; - case GDK_2: + case GDK_KEY_2: + case GDK_KEY_KP_2: math_equation_insert(display->priv->equation, "²"); return TRUE; - case GDK_3: + case GDK_KEY_3: + case GDK_KEY_KP_3: math_equation_insert(display->priv->equation, "³"); return TRUE; - case GDK_4: + case GDK_KEY_4: + case GDK_KEY_KP_4: math_equation_insert(display->priv->equation, "⁴"); return TRUE; - case GDK_5: + case GDK_KEY_5: + case GDK_KEY_KP_5: math_equation_insert(display->priv->equation, "⁵"); return TRUE; - case GDK_6: + case GDK_KEY_6: + case GDK_KEY_KP_6: math_equation_insert(display->priv->equation, "⁶"); return TRUE; - case GDK_7: + case GDK_KEY_7: + case GDK_KEY_KP_7: math_equation_insert(display->priv->equation, "⁷"); return TRUE; - case GDK_8: + case GDK_KEY_8: + case GDK_KEY_KP_8: math_equation_insert(display->priv->equation, "⁸"); return TRUE; - case GDK_9: + case GDK_KEY_9: + case GDK_KEY_KP_9: math_equation_insert(display->priv->equation, "⁹"); return TRUE; } @@ -189,34 +247,44 @@ display_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathDisplay *display else if (state == GDK_MOD1_MASK || math_equation_get_number_mode(display->priv->equation) == SUBSCRIPT) { switch(event->keyval) { - case GDK_0: + case GDK_KEY_0: + case GDK_KEY_KP_0: math_equation_insert(display->priv->equation, "₀"); return TRUE; - case GDK_1: + case GDK_KEY_1: + case GDK_KEY_KP_1: math_equation_insert(display->priv->equation, "₁"); return TRUE; - case GDK_2: + case GDK_KEY_2: + case GDK_KEY_KP_2: math_equation_insert(display->priv->equation, "₂"); return TRUE; - case GDK_3: + case GDK_KEY_3: + case GDK_KEY_KP_3: math_equation_insert(display->priv->equation, "₃"); return TRUE; - case GDK_4: + case GDK_KEY_4: + case GDK_KEY_KP_4: math_equation_insert(display->priv->equation, "₄"); return TRUE; - case GDK_5: + case GDK_KEY_5: + case GDK_KEY_KP_5: math_equation_insert(display->priv->equation, "₅"); return TRUE; - case GDK_6: + case GDK_KEY_6: + case GDK_KEY_KP_6: math_equation_insert(display->priv->equation, "₆"); return TRUE; - case GDK_7: + case GDK_KEY_7: + case GDK_KEY_KP_7: math_equation_insert(display->priv->equation, "₇"); return TRUE; - case GDK_8: + case GDK_KEY_8: + case GDK_KEY_KP_8: math_equation_insert(display->priv->equation, "₈"); return TRUE; - case GDK_9: + case GDK_KEY_9: + case GDK_KEY_KP_9: math_equation_insert(display->priv->equation, "₉"); return TRUE; } @@ -239,14 +307,27 @@ static void status_changed_cb(MathEquation *equation, GParamSpec *spec, MathDisplay *display) { gtk_text_buffer_set_text(display->priv->info_buffer, math_equation_get_status(equation), -1); + if (math_equation_in_solve(equation) && !gtk_widget_get_visible(display->priv->spinner)) { + gtk_widget_show(display->priv->spinner); + gtk_spinner_start(GTK_SPINNER(display->priv->spinner)); + } + else if (!math_equation_in_solve(equation) && gtk_widget_get_visible(display->priv->spinner)) { + gtk_widget_hide(display->priv->spinner); + gtk_spinner_stop(GTK_SPINNER(display->priv->spinner)); + } } static void create_gui(MathDisplay *display) { - GtkWidget *info_view; + GtkWidget *info_view, *info_box, *main_box; PangoFontDescription *font_desc; + int i; + GtkStyle *style; + + main_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); + gtk_container_add(GTK_CONTAINER(display), main_box); g_signal_connect(display, "key-press-event", G_CALLBACK(key_press_cb), display); @@ -267,7 +348,10 @@ create_gui(MathDisplay *display) atk_object_set_role(gtk_widget_get_accessible(display->priv->text_view), ATK_ROLE_EDITBAR); //FIXME:Result Region g_signal_connect(display->priv->text_view, "key-press-event", G_CALLBACK(display_key_press_cb), display); - gtk_box_pack_start(GTK_BOX(display), display->priv->text_view, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(main_box), display->priv->text_view, TRUE, TRUE, 0); + + info_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_box_pack_start(GTK_BOX(main_box), info_box, FALSE, TRUE, 0); info_view = gtk_text_view_new(); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(info_view), GTK_WRAP_WORD); @@ -277,11 +361,20 @@ create_gui(MathDisplay *display) gtk_text_view_set_justification(GTK_TEXT_VIEW(info_view), GTK_JUSTIFY_RIGHT); /* TEMP: Disabled for now as GTK+ doesn't properly render a right aligned right margin, see bug #482688 */ /*gtk_text_view_set_right_margin(GTK_TEXT_VIEW(info_view), 6);*/ - gtk_box_pack_start(GTK_BOX(display), info_view, FALSE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(info_box), info_view, TRUE, TRUE, 0); display->priv->info_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(info_view)); + display->priv->spinner = gtk_spinner_new(); + gtk_box_pack_end(GTK_BOX(info_box), display->priv->spinner, FALSE, FALSE, 0); + style = gtk_widget_get_style(info_view); + for (i = 0; i < 5; i++) { + gtk_widget_modify_bg(GTK_WIDGET(display), i, &style->base[i]); + } + + gtk_widget_show(info_box); gtk_widget_show(info_view); gtk_widget_show(display->priv->text_view); + gtk_widget_show(main_box); g_signal_connect(display->priv->equation, "notify::status", G_CALLBACK(status_changed_cb), display); status_changed_cb(display->priv->equation, NULL, display); @@ -296,15 +389,15 @@ math_display_set_property(GObject *object, { MathDisplay *self; - self = MATH_DISPLAY (object); + self = MATH_DISPLAY(object); switch (prop_id) { case PROP_EQUATION: - self->priv->equation = g_value_get_object (value); + self->priv->equation = g_value_get_object(value); create_gui(self); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } @@ -318,28 +411,28 @@ math_display_get_property(GObject *object, { MathDisplay *self; - self = MATH_DISPLAY (object); + self = MATH_DISPLAY(object); switch (prop_id) { case PROP_EQUATION: - g_value_set_object (value, self->priv->equation); + g_value_set_object(value, self->priv->equation); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void -math_display_class_init (MathDisplayClass *klass) +math_display_class_init(MathDisplayClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->get_property = math_display_get_property; object_class->set_property = math_display_set_property; - g_type_class_add_private (klass, sizeof (MathDisplayPrivate)); + g_type_class_add_private(klass, sizeof(MathDisplayPrivate)); g_object_class_install_property(object_class, PROP_EQUATION, @@ -351,8 +444,8 @@ math_display_class_init (MathDisplayClass *klass) } -static void +static void math_display_init(MathDisplay *display) { - display->priv = G_TYPE_INSTANCE_GET_PRIVATE (display, math_display_get_type(), MathDisplayPrivate); + display->priv = G_TYPE_INSTANCE_GET_PRIVATE(display, math_display_get_type(), MathDisplayPrivate); } diff --git a/src/math-display.h b/src/math-display.h index 0179f17..1f37bd1 100644 --- a/src/math-display.h +++ b/src/math-display.h @@ -1,19 +1,11 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MATH_DISPLAY_H @@ -30,21 +22,25 @@ G_BEGIN_DECLS typedef struct MathDisplayPrivate MathDisplayPrivate; -typedef struct { - GtkVBox parent_instance; - MathDisplayPrivate* priv; +typedef struct +{ + GtkViewport parent_instance; + MathDisplayPrivate *priv; } MathDisplay; -typedef struct { - GtkVBoxClass parent_class; +typedef struct +{ + GtkViewportClass parent_class; } MathDisplayClass; GType math_display_get_type(void); -MathDisplay* math_display_new(void); +MathDisplay *math_display_new(void); + +MathDisplay *math_display_new_with_equation(MathEquation *equation); -MathDisplay* math_display_new_with_equation(MathEquation* equation); +MathEquation *math_display_get_equation(MathDisplay *display); -MathEquation* math_display_get_equation(MathDisplay* display); +G_END_DECLS #endif /* MATH_DISPLAY_H */ diff --git a/src/math-equation.c b/src/math-equation.c index ef86221..5bcae64 100644 --- a/src/math-equation.c +++ b/src/math-equation.c @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -24,14 +16,15 @@ #include #include #include -#include -#include +#include #include "math-equation.h" #include "mp.h" #include "mp-equation.h" -#include "currency.h" +#include "mp-serializer.h" +#include "mp-enums.h" +#include "unit-manager.h" enum { @@ -48,7 +41,10 @@ enum { PROP_WORD_SIZE, PROP_ANGLE_UNITS, PROP_SOURCE_CURRENCY, - PROP_TARGET_CURRENCY + PROP_TARGET_CURRENCY, + PROP_SOURCE_UNITS, + PROP_TARGET_UNITS, + PROP_SERIALIZER }; static GType number_mode_type, number_format_type, angle_unit_type; @@ -71,44 +67,49 @@ struct MathEquationPrivate { GtkTextTag *ans_tag; - gint show_tsep; /* Set if the thousands separator should be shown. */ - gint show_zeroes; /* Set if trailing zeroes should be shown. */ - DisplayFormat format; /* Number display mode. */ - gint accuracy; /* Number of digits to show */ gint word_size; /* Word size in bits */ MPAngleUnit angle_units; /* Units for trigonometric functions */ char *source_currency; char *target_currency; - gint base; /* Numeric base */ + char *source_units; + char *target_units; NumberMode number_mode; /* ??? */ gboolean can_super_minus; /* TRUE if entering minus can generate a superscript minus */ - const char *digits[16]; /* Localized digit values */ - const char *radix; /* Locale specific radix string. */ - const char *tsep; /* Locale specific thousands separator. */ - gint tsep_count; /* Number of digits between separator. */ + gunichar digits[16]; /* Localized digits */ GtkTextMark *ans_start, *ans_end; MathEquationState state; /* Equation state */ - GList *undo_stack; /* History of expression mode states */ + GList *undo_stack; /* History of expression mode states */ GList *redo_stack; gboolean in_undo_operation; - + gboolean in_reformat; gboolean in_delete; + gboolean in_solve; + MathVariables *variables; + MpSerializer *serializer; + + GAsyncQueue *queue; }; +typedef struct { + MPNumber *number_result; + gchar *text_result; + gchar *error; +} SolveData; + G_DEFINE_TYPE (MathEquation, math_equation, GTK_TYPE_TEXT_BUFFER); MathEquation * math_equation_new() { - return g_object_new (math_equation_get_type(), NULL); + return g_object_new(math_equation_get_type(), NULL); } @@ -123,7 +124,7 @@ static void get_ans_offsets(MathEquation *equation, gint *start, gint *end) { GtkTextIter iter; - + if (!equation->priv->ans_start) { *start = *end = -1; return; @@ -143,13 +144,13 @@ reformat_ans(MathEquation *equation) return; gchar *orig_ans_text; - gchar ans_text[MAX_DIGITS]; + gchar *ans_text; GtkTextIter ans_start, ans_end; gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start); gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end); orig_ans_text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &ans_start, &ans_end, FALSE); - display_make_number(equation, ans_text, MAX_DIGITS, &equation->priv->state.ans); + ans_text = mp_serializer_to_string(equation->priv->serializer, &equation->priv->state.ans); if (strcmp(orig_ans_text, ans_text) != 0) { gint start; @@ -171,166 +172,122 @@ reformat_ans(MathEquation *equation) gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_start, equation->priv->ans_start); gtk_text_buffer_get_iter_at_mark(GTK_TEXT_BUFFER(equation), &ans_end, equation->priv->ans_end); g_free(orig_ans_text); + g_free(ans_text); +} + + +static gint +count_digits(MathEquation *equation, const gchar *text) +{ + const gchar *read_iter; + gint count = 0; + + read_iter = text; + while (*read_iter != '\0') { + if (!g_unichar_isdigit(g_utf8_get_char(read_iter))) + return count; + + read_iter = g_utf8_next_char(read_iter); + + /* Allow a thousands separator between digits follow a digit */ + if (g_utf8_get_char(read_iter) == mp_serializer_get_thousands_separator(equation->priv->serializer)) { + read_iter = g_utf8_next_char(read_iter); + if (!g_unichar_isdigit(g_utf8_get_char(read_iter))) + return count; + } + + count++; + } + + return count; } -/* NOTE: Not efficent but easy to write */ -// FIXME: This is just a lexer - use the same lexer as the solver static void -reformat_base(MathEquation *equation, gint old_base) +reformat_separators(MathEquation *equation) { - gunichar sub_zero, sub_nine; gchar *text, *read_iter; - gboolean in_number = FALSE, have_radix = FALSE; - gint offset = 0, offset_step = 0, max_digit = 0, base = -1, base_offset = 0; gint ans_start, ans_end; + gint offset, digit_offset = 0; + gboolean in_number = FALSE, in_radix = FALSE, last_is_tsep = FALSE; - if (equation->priv->base == old_base) - return; - - sub_zero = g_utf8_get_char("₀"); - sub_nine = g_utf8_get_char("₉"); + equation->priv->in_undo_operation = TRUE; + equation->priv->in_reformat = TRUE; - read_iter = text = math_equation_get_display(equation); + text = math_equation_get_display(equation); get_ans_offsets(equation, &ans_start, &ans_end); - while (TRUE) { + for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) { gunichar c; - gint digit = -1, sub_digit = -1; + gboolean expect_tsep; /* See what digit this character is */ c = g_utf8_get_char(read_iter); - if (c >= sub_zero && c <= sub_nine) - sub_digit = c - sub_zero; - else if (c >= 'a' && c <= 'z') - digit = c - 'a'; - else if (c >= 'A' && c <= 'Z') - digit = c - 'A'; - else - digit = g_unichar_digit_value(c); + + expect_tsep = math_equation_get_base(equation) == 10 && + mp_serializer_get_show_thousands_separators(equation->priv->serializer) && + in_number && !in_radix && !last_is_tsep && + digit_offset > 0 && digit_offset % mp_serializer_get_thousands_separator_count(equation->priv->serializer) == 0; + last_is_tsep = FALSE; /* Don't mess with ans */ if (offset >= ans_start && offset <= ans_end) { - digit = -1; - sub_digit = -1; + in_number = in_radix = FALSE; + continue; } + if (g_unichar_isdigit(c)) { + if (!in_number) + digit_offset = count_digits(equation, read_iter); + in_number = TRUE; - if (in_number && digit >= 0) { - if (digit > max_digit) - max_digit = digit; - } - else if (in_number && sub_digit >= 0) { - if (base < 0) { - base_offset = offset; - base = 0; + /* Expected a thousands separator between these digits - insert it */ + if (expect_tsep) { + GtkTextIter iter; + gchar buffer[7]; + gint len; + + gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &iter, offset); + len = g_unichar_to_utf8(mp_serializer_get_thousands_separator(equation->priv->serializer), buffer); + buffer[len] = '\0'; + gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation), &iter, buffer, -1); + offset++; + last_is_tsep = TRUE; } - base = base * 10 + sub_digit; + digit_offset--; } - else if (in_number) { - /* Allow one radix inside a number */ - if (!have_radix && base < 0 && strncmp(read_iter, equation->priv->radix, strlen(equation->priv->radix)) == 0) { - have_radix = TRUE; - read_iter += strlen(equation->priv->radix); - offset += g_utf8_strlen(equation->priv->radix, -1); - continue; - } - - /* If had no base then insert it */ - if (base < 0) { - GtkTextIter iter; - gint multiplier = 1; - gint b = old_base; - const char *digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"}; - - equation->priv->in_undo_operation = TRUE; - equation->priv->in_reformat = TRUE; - - while (b / multiplier != 0) - multiplier *= 10; - while (multiplier != 1) { - int d; - multiplier /= 10; - d = b / multiplier; - gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &iter, offset + offset_step); - gtk_text_buffer_insert(GTK_TEXT_BUFFER(equation), &iter, digits[d], -1); - offset_step++; - b -= d * multiplier; - } - - equation->priv->in_reformat = FALSE; - equation->priv->in_undo_operation = FALSE; - } - /* Remove the base if the current value */ - else if (max_digit < base && base == equation->priv->base) { + else if (c == mp_serializer_get_radix(equation->priv->serializer)) { + in_number = in_radix = TRUE; + } + else if (c == mp_serializer_get_thousands_separator(equation->priv->serializer)) { + /* Didn't expect thousands separator - delete it */ + if (!expect_tsep && in_number) { GtkTextIter start, end; - - equation->priv->in_undo_operation = TRUE; - equation->priv->in_reformat = TRUE; - - gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, base_offset + offset_step); - gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, offset + offset_step); + gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &start, offset); + gtk_text_buffer_get_iter_at_offset(GTK_TEXT_BUFFER(equation), &end, offset + 1); gtk_text_buffer_delete(GTK_TEXT_BUFFER(equation), &start, &end); - offset_step -= offset - base_offset; - - equation->priv->in_reformat = FALSE; - equation->priv->in_undo_operation = FALSE; + offset--; } - - in_number = FALSE; + else + last_is_tsep = TRUE; } - else if (digit >= 0) { - in_number = TRUE; - have_radix = FALSE; - base = -1; - max_digit = digit; + else { + in_number = in_radix = FALSE; } - - if (c == '\0') - break; - - read_iter = g_utf8_next_char(read_iter); - offset++; } g_free(text); -} - - -static void -reformat_separators(MathEquation *equation) -{ -#if 0 - gchar *text, *read_iter; - gboolean in_number = FALSE, in_fraction = FALSE; - - text = math_equation_get_display(equation); - - /* Find numbers in display, and modify if necessary */ - read_iter = text; - while(*read_iter != '\0') { - gunichar c; - c = g_utf8_get_char(read_iter); - - if (strncmp(read_iter, equation->priv->tsep, strlen(equation->priv->tsep)) == 0) - ; - read_iter = g_utf8_next_char(read_iter); - } - - g_free(text); -#endif + equation->priv->in_reformat = FALSE; + equation->priv->in_undo_operation = FALSE; } static void -reformat_display(MathEquation *equation, gint old_base) +reformat_display(MathEquation *equation) { /* Change ans */ reformat_ans(equation); - /* Add/remove base suffixes if have changed base */ - reformat_base(equation, old_base); - /* Add/remove thousands separators */ reformat_separators(equation); } @@ -362,7 +319,7 @@ get_current_state(MathEquation *equation) state->can_super_minus = equation->priv->can_super_minus; state->entered_multiply = equation->priv->state.entered_multiply; state->status = g_strdup(equation->priv->state.status); - + return state; } @@ -396,7 +353,7 @@ math_equation_push_undo_stack(MathEquation *equation) equation->priv->redo_stack = NULL; state = get_current_state(equation); - equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, state); + equation->priv->undo_stack = g_list_prepend(equation->priv->undo_stack, state); } @@ -425,7 +382,7 @@ static void apply_state(MathEquation *equation, MathEquationState *state) { GtkTextIter cursor; - + /* Disable undo detection */ equation->priv->in_undo_operation = TRUE; @@ -459,13 +416,15 @@ math_equation_copy(MathEquation *equation) { GtkTextIter start, end; gchar *text; + + g_return_if_fail(equation != NULL); if (!gtk_text_buffer_get_selection_bounds(GTK_TEXT_BUFFER(equation), &start, &end)) gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end); text = gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE); gtk_clipboard_set_text(gtk_clipboard_get(GDK_NONE), text, -1); - g_free (text); + g_free(text); } @@ -473,7 +432,6 @@ static void on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data) { MathEquation *equation = data; - if (text != NULL) math_equation_insert(equation, text); } @@ -482,6 +440,7 @@ on_paste(GtkClipboard *clipboard, const gchar *text, gpointer data) void math_equation_paste(MathEquation *equation) { + g_return_if_fail(equation != NULL); gtk_clipboard_request_text(gtk_clipboard_get(GDK_NONE), on_paste, equation); } @@ -492,6 +451,8 @@ math_equation_undo(MathEquation *equation) GList *link; MathEquationState *state; + g_return_if_fail(equation != NULL); + if (!equation->priv->undo_stack) { math_equation_set_status(equation, /* Error shown when trying to undo with no undo history */ @@ -517,6 +478,8 @@ math_equation_redo(MathEquation *equation) GList *link; MathEquationState *state; + g_return_if_fail(equation != NULL); + if (!equation->priv->redo_stack) { math_equation_set_status(equation, /* Error shown when trying to redo with no redo history */ @@ -536,33 +499,25 @@ math_equation_redo(MathEquation *equation) } -const gchar * +gunichar math_equation_get_digit_text(MathEquation *equation, guint digit) { - return equation->priv->digits[digit]; -} - - -const gchar * -math_equation_get_numeric_point_text(MathEquation *equation) -{ - return equation->priv->radix; -} + g_return_val_if_fail(equation != NULL, '?'); + g_return_val_if_fail(digit < 16, '?'); - -const gchar *math_equation_get_thousands_separator_text(MathEquation *equation) -{ - return equation->priv->tsep; + return equation->priv->digits[digit]; } void math_equation_set_accuracy(MathEquation *equation, gint accuracy) { - if (equation->priv->accuracy == accuracy) + g_return_if_fail(equation != NULL); + + if (mp_serializer_get_trailing_digits(equation->priv->serializer) == accuracy) return; - equation->priv->accuracy = accuracy; - reformat_display(equation, equation->priv->base); + mp_serializer_set_trailing_digits(equation->priv->serializer, accuracy); + reformat_display(equation); g_object_notify(G_OBJECT(equation), "accuracy"); } @@ -570,17 +525,22 @@ math_equation_set_accuracy(MathEquation *equation, gint accuracy) gint math_equation_get_accuracy(MathEquation *equation) { - return equation->priv->accuracy; + g_return_val_if_fail(equation != NULL, 0); + + return mp_serializer_get_trailing_digits(equation->priv->serializer); } void math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible) { - if ((equation->priv->show_tsep && visible) || (!equation->priv->show_tsep && !visible)) + g_return_if_fail(equation != NULL); + + if (mp_serializer_get_show_thousands_separators(equation->priv->serializer) == visible) return; - equation->priv->show_tsep = visible; - reformat_display(equation, equation->priv->base); + + mp_serializer_set_show_thousands_separators(equation->priv->serializer, visible); + reformat_display(equation); g_object_notify(G_OBJECT(equation), "show-thousands-separators"); } @@ -588,17 +548,21 @@ math_equation_set_show_thousands_separators(MathEquation *equation, gboolean vis gboolean math_equation_get_show_thousands_separators(MathEquation *equation) { - return equation->priv->show_tsep; + g_return_val_if_fail(equation != NULL, FALSE); + return mp_serializer_get_show_thousands_separators(equation->priv->serializer); } void math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible) { - if ((equation->priv->show_zeroes && visible) || (!equation->priv->show_zeroes && !visible)) + g_return_if_fail(equation != NULL); + + if (mp_serializer_get_show_trailing_zeroes(equation->priv->serializer) == visible) return; - equation->priv->show_zeroes = visible; - reformat_display(equation, equation->priv->base); + + mp_serializer_set_show_trailing_zeroes(equation->priv->serializer, visible); + reformat_display(equation); g_object_notify(G_OBJECT(equation), "show-trailing-zeroes"); } @@ -606,40 +570,43 @@ math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible) gboolean math_equation_get_show_trailing_zeroes(MathEquation *equation) { - return equation->priv->show_zeroes; + g_return_val_if_fail(equation != NULL, FALSE); + return mp_serializer_get_show_trailing_zeroes(equation->priv->serializer); } void -math_equation_set_number_format(MathEquation *equation, DisplayFormat format) +math_equation_set_number_format(MathEquation *equation, MpDisplayFormat format) { - if (equation->priv->format == format) + g_return_if_fail(equation != NULL); + + if (mp_serializer_get_number_format(equation->priv->serializer) == format) return; - equation->priv->format = format; - reformat_display(equation, equation->priv->base); + mp_serializer_set_number_format(equation->priv->serializer, format); + reformat_display(equation); g_object_notify(G_OBJECT(equation), "number-format"); } -DisplayFormat +MpDisplayFormat math_equation_get_number_format(MathEquation *equation) { - return equation->priv->format; + g_return_val_if_fail(equation != NULL, MP_DISPLAY_FORMAT_AUTOMATIC); + return mp_serializer_get_number_format(equation->priv->serializer); } void math_equation_set_base(MathEquation *equation, gint base) { - gint old_base; + g_return_if_fail(equation != NULL); - if (equation->priv->base == base) + if (mp_serializer_get_base(equation->priv->serializer) == base) return; - old_base = equation->priv->base; - equation->priv->base = base; - reformat_display(equation, old_base); + mp_serializer_set_base(equation->priv->serializer, base); + reformat_display(equation); g_object_notify(G_OBJECT(equation), "base"); } @@ -647,15 +614,19 @@ math_equation_set_base(MathEquation *equation, gint base) gint math_equation_get_base(MathEquation *equation) { - return equation->priv->base; + g_return_val_if_fail(equation != NULL, 10); + return mp_serializer_get_base(equation->priv->serializer); } void math_equation_set_word_size(MathEquation *equation, gint word_size) { + g_return_if_fail(equation != NULL); + if (equation->priv->word_size == word_size) return; + equation->priv->word_size = word_size; g_object_notify(G_OBJECT(equation), "word-size"); } @@ -664,6 +635,7 @@ math_equation_set_word_size(MathEquation *equation, gint word_size) gint math_equation_get_word_size(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, 64); return equation->priv->word_size; } @@ -671,8 +643,11 @@ math_equation_get_word_size(MathEquation *equation) void math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units) { + g_return_if_fail(equation != NULL); + if (equation->priv->angle_units == angle_units) return; + equation->priv->angle_units = angle_units; g_object_notify(G_OBJECT(equation), "angle-units"); } @@ -681,6 +656,7 @@ math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_units) MPAngleUnit math_equation_get_angle_units(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, MP_DEGREES); return equation->priv->angle_units; } @@ -688,9 +664,8 @@ math_equation_get_angle_units(MathEquation *equation) void math_equation_set_source_currency(MathEquation *equation, const gchar *currency) { - // FIXME: Pick based on locale - if (!currency || currency[0] == '\0') - currency = currency_names[0].short_name; + g_return_if_fail(equation != NULL); + g_return_if_fail(currency != NULL); if (strcmp(equation->priv->source_currency, currency) == 0) return; @@ -699,9 +674,11 @@ math_equation_set_source_currency(MathEquation *equation, const gchar *currency) g_object_notify(G_OBJECT(equation), "source-currency"); } + const gchar * math_equation_get_source_currency(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, NULL); return equation->priv->source_currency; } @@ -709,9 +686,8 @@ math_equation_get_source_currency(MathEquation *equation) void math_equation_set_target_currency(MathEquation *equation, const gchar *currency) { - // FIXME: Pick based on locale - if (!currency || currency[0] == '\0') - currency = currency_names[0].short_name; + g_return_if_fail(equation != NULL); + g_return_if_fail(currency != NULL); if (strcmp(equation->priv->target_currency, currency) == 0) return; @@ -724,25 +700,75 @@ math_equation_set_target_currency(MathEquation *equation, const gchar *currency) const gchar * math_equation_get_target_currency(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, NULL); return equation->priv->target_currency; } +void +math_equation_set_source_units(MathEquation *equation, const gchar *units) +{ + g_return_if_fail(equation != NULL); + g_return_if_fail(units != NULL); + + if (strcmp(equation->priv->source_units, units) == 0) + return; + + g_free(equation->priv->source_units); + equation->priv->source_units = g_strdup(units); + g_object_notify(G_OBJECT(equation), "source-units"); +} + +const gchar * +math_equation_get_source_units(MathEquation *equation) +{ + g_return_val_if_fail(equation != NULL, NULL); + return equation->priv->source_units; +} + + +void +math_equation_set_target_units(MathEquation *equation, const gchar *units) +{ + g_return_if_fail(equation != NULL); + g_return_if_fail(units != NULL); + + if (strcmp(equation->priv->target_units, units) == 0) + return; + + g_free(equation->priv->target_units); + equation->priv->target_units = g_strdup(units); + g_object_notify(G_OBJECT(equation), "target-units"); +} + + +const gchar * +math_equation_get_target_units(MathEquation *equation) +{ + g_return_val_if_fail(equation != NULL, NULL); + return equation->priv->target_units; +} + + void math_equation_set_status(MathEquation *equation, const gchar *status) { + g_return_if_fail(equation != NULL); + g_return_if_fail(status != NULL); + if (strcmp(equation->priv->state.status, status) == 0) return; g_free(equation->priv->state.status); equation->priv->state.status = g_strdup(status); - g_object_notify(G_OBJECT(equation), "status"); + g_object_notify(G_OBJECT(equation), "status"); } const gchar * math_equation_get_status(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, NULL); return equation->priv->state.status; } @@ -750,6 +776,7 @@ math_equation_get_status(MathEquation *equation) gboolean math_equation_is_empty(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, FALSE); return gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation)) == 0; } @@ -759,6 +786,8 @@ math_equation_is_result(MathEquation *equation) { char *text; gboolean result; + + g_return_val_if_fail(equation != NULL, FALSE); text = math_equation_get_equation(equation); result = strcmp(text, "ans") == 0; @@ -773,6 +802,8 @@ math_equation_get_display(MathEquation *equation) { GtkTextIter start, end; + g_return_val_if_fail(equation != NULL, NULL); + gtk_text_buffer_get_bounds(GTK_TEXT_BUFFER(equation), &start, &end); return gtk_text_buffer_get_text(GTK_TEXT_BUFFER(equation), &start, &end, FALSE); } @@ -781,19 +812,54 @@ math_equation_get_display(MathEquation *equation) gchar * math_equation_get_equation(MathEquation *equation) { - char *text, *t; - gint ans_start, ans_end; + gchar *text; + GString *eq_text; + gint ans_start = -1, ans_end = -1, offset; + const gchar *read_iter; + gboolean last_is_digit = FALSE; + + g_return_val_if_fail(equation != NULL, NULL); text = math_equation_get_display(equation); + eq_text = g_string_sized_new(strlen(text)); + + if (equation->priv->ans_start) + get_ans_offsets(equation, &ans_start, &ans_end); - /* No ans to substitute */ - if(!equation->priv->ans_start) - return text; + for (read_iter = text, offset = 0; *read_iter != '\0'; read_iter = g_utf8_next_char(read_iter), offset++) { + gunichar c; + gboolean is_digit, next_is_digit; - get_ans_offsets(equation, &ans_start, &ans_end); - t = g_strdup_printf("%.*sans%s", (int)(g_utf8_offset_to_pointer(text, ans_start) - text), text, g_utf8_offset_to_pointer(text, ans_end)); + c = g_utf8_get_char(read_iter); + is_digit = g_unichar_isdigit(c); + next_is_digit = g_unichar_isdigit(g_utf8_get_char(g_utf8_next_char(read_iter))); + + /* Replace ans text with variable */ + if (offset == ans_start) { + g_string_append(eq_text, "ans"); + read_iter = g_utf8_offset_to_pointer(read_iter, ans_end - ans_start - 1); + offset += ans_end - ans_start - 1; + is_digit = FALSE; + continue; + } + + /* Ignore thousands separators */ + if (c == mp_serializer_get_thousands_separator(equation->priv->serializer) && last_is_digit && next_is_digit) + ; + /* Substitute radix character */ + else if (c == mp_serializer_get_radix(equation->priv->serializer) && (last_is_digit || next_is_digit)) + g_string_append_unichar(eq_text, '.'); + else + g_string_append_unichar(eq_text, c); + + last_is_digit = is_digit; + } g_free(text); - return t; + + text = eq_text->str; + g_string_free(eq_text, FALSE); + + return text; } @@ -803,17 +869,35 @@ math_equation_get_number(MathEquation *equation, MPNumber *z) gchar *text; gboolean result; - text = math_equation_get_display(equation); - result = !mp_set_from_string(text, equation->priv->base, z); - g_free (text); + g_return_val_if_fail(equation != NULL, FALSE); + g_return_val_if_fail(z != NULL, FALSE); - return result; + if (math_equation_is_result(equation)) { + mp_set_from_mp(math_equation_get_answer(equation), z); + return TRUE; + } + else { + text = math_equation_get_equation(equation); + result = !mp_serializer_from_string(equation->priv->serializer, text, z); + g_free(text); + return result; + } +} + + +MpSerializer * +math_equation_get_serializer(MathEquation *equation) +{ + g_return_val_if_fail(equation != NULL, NULL); + return equation->priv->serializer; } void math_equation_set_number_mode(MathEquation *equation, NumberMode mode) { + g_return_if_fail(equation != NULL); + if (equation->priv->number_mode == mode) return; @@ -827,13 +911,23 @@ math_equation_set_number_mode(MathEquation *equation, NumberMode mode) NumberMode math_equation_get_number_mode(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, NORMAL); return equation->priv->number_mode; } +gboolean +math_equation_in_solve(MathEquation *equation) +{ + g_return_val_if_fail(equation != NULL, FALSE); + return equation->priv->in_solve; +} + + const MPNumber * math_equation_get_answer(MathEquation *equation) { + g_return_val_if_fail(equation != NULL, FALSE); return &equation->priv->state.ans; } @@ -842,25 +936,31 @@ void math_equation_store(MathEquation *equation, const gchar *name) { MPNumber t; + + g_return_if_fail(equation != NULL); + g_return_if_fail(name != NULL); if (!math_equation_get_number(equation, &t)) math_equation_set_status(equation, _("No sane value to store")); else - math_variables_set_value(equation->priv->variables, name, &t); + math_variables_set(equation->priv->variables, name, &t); } void math_equation_recall(MathEquation *equation, const gchar *name) { + g_return_if_fail(equation != NULL); + g_return_if_fail(name != NULL); math_equation_insert(equation, name); } void math_equation_set(MathEquation *equation, const gchar *text) - { + g_return_if_fail(equation != NULL); + g_return_if_fail(text != NULL); gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1); clear_ans(equation, FALSE); } @@ -869,11 +969,14 @@ math_equation_set(MathEquation *equation, const gchar *text) void math_equation_set_number(MathEquation *equation, const MPNumber *x) { - char text[MAX_DIGITS]; + char *text; GtkTextIter start, end; + g_return_if_fail(equation != NULL); + g_return_if_fail(x != NULL); + /* Show the number in the user chosen format */ - display_make_number(equation, text, MAX_DIGITS, x); + text = mp_serializer_to_string(equation->priv->serializer, x); gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), text, -1); mp_set_from_mp(x, &equation->priv->state.ans); @@ -883,12 +986,16 @@ math_equation_set_number(MathEquation *equation, const MPNumber *x) equation->priv->ans_start = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &start, FALSE); equation->priv->ans_end = gtk_text_buffer_create_mark(GTK_TEXT_BUFFER(equation), NULL, &end, TRUE); gtk_text_buffer_apply_tag(GTK_TEXT_BUFFER(equation), equation->priv->ans_tag, &start, &end); + g_free(text); } void math_equation_insert(MathEquation *equation, const gchar *text) { + g_return_if_fail(equation != NULL); + g_return_if_fail(text != NULL); + /* Replace ** with ^ (not on all keyboards) */ if (!gtk_text_buffer_get_has_selection(GTK_TEXT_BUFFER(equation)) && strcmp(text, "×") == 0 && equation->priv->state.entered_multiply) { @@ -900,10 +1007,6 @@ math_equation_insert(MathEquation *equation, const gchar *text) return; } - /* Start new equation when entering digits after existing result */ - if(math_equation_is_result(equation) && g_unichar_isdigit(g_utf8_get_char(text))) - gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1); - /* Can't enter superscript minus after entering digits */ if (strstr("⁰¹²³⁴⁵⁶⁷⁸⁹", text) != NULL || strcmp("⁻", text) == 0) equation->priv->can_super_minus = FALSE; @@ -912,8 +1015,6 @@ math_equation_insert(MathEquation *equation, const gchar *text) if (strstr("⁻⁰¹²³⁴⁵⁶⁷⁸⁹₀₁₂₃₄₅₆₇₈₉", text) == NULL) math_equation_set_number_mode(equation, NORMAL); - // FIXME: Add thousands separators - gtk_text_buffer_delete_selection(GTK_TEXT_BUFFER(equation), FALSE, FALSE); gtk_text_buffer_insert_at_cursor(GTK_TEXT_BUFFER(equation), text, -1); } @@ -925,8 +1026,16 @@ math_equation_insert_digit(MathEquation *equation, guint digit) static const char *subscript_digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉", NULL}; static const char *superscript_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹", NULL}; - if (equation->priv->number_mode == NORMAL || digit >= 10) - math_equation_insert(equation, math_equation_get_digit_text(equation, digit)); + g_return_if_fail(equation != NULL); + g_return_if_fail(digit < 16); + + if (equation->priv->number_mode == NORMAL || digit >= 10) { + gchar buffer[7]; + gint len; + len = g_unichar_to_utf8(math_equation_get_digit_text(equation, digit), buffer); + buffer[len] = '\0'; + math_equation_insert(equation, buffer); + } else if (equation->priv->number_mode == SUPERSCRIPT) math_equation_insert(equation, superscript_digits[digit]); else if (equation->priv->number_mode == SUBSCRIPT) @@ -937,22 +1046,35 @@ math_equation_insert_digit(MathEquation *equation, guint digit) void math_equation_insert_numeric_point(MathEquation *equation) { - math_equation_insert(equation, math_equation_get_numeric_point_text(equation)); + gchar buffer[7]; + gint len; + + g_return_if_fail(equation != NULL); + + len = g_unichar_to_utf8(mp_serializer_get_radix(equation->priv->serializer), buffer); + buffer[len] = '\0'; + math_equation_insert(equation, buffer); } void math_equation_insert_number(MathEquation *equation, const MPNumber *x) { - char text[MAX_DIGITS]; - display_make_number(equation, text, MAX_DIGITS, x); + char *text; + + g_return_if_fail(equation != NULL); + g_return_if_fail(x != NULL); + + text = mp_serializer_to_string(equation->priv->serializer, x); math_equation_insert(equation, text); + g_free(text); } void math_equation_insert_exponent(MathEquation *equation) { + g_return_if_fail(equation != NULL); math_equation_insert(equation, "×10"); math_equation_set_number_mode(equation, SUPERSCRIPT); } @@ -961,6 +1083,7 @@ math_equation_insert_exponent(MathEquation *equation) void math_equation_insert_subtract(MathEquation *equation) { + g_return_if_fail(equation != NULL); if (equation->priv->number_mode == SUPERSCRIPT && equation->priv->can_super_minus) { math_equation_insert(equation, "⁻"); equation->priv->can_super_minus = FALSE; @@ -982,14 +1105,14 @@ variable_is_defined(const char *name, void *data) for (c = lower_name; *c; c++) *c = tolower(*c); - if (strcmp(lower_name, "rand") == 0 || + if (strcmp(lower_name, "rand") == 0 || strcmp(lower_name, "ans") == 0) { - g_free (lower_name); + g_free(lower_name); return 1; } - g_free (lower_name); + g_free(lower_name); - return math_variables_get_value(equation->priv->variables, name) != NULL; + return math_variables_get(equation->priv->variables, name) != NULL; } @@ -1010,7 +1133,7 @@ get_variable(const char *name, MPNumber *z, void *data) else if (strcmp(lower_name, "ans") == 0) mp_set_from_mp(&equation->priv->state.ans, z); else { - t = math_variables_get_value(equation->priv->variables, name); + t = math_variables_get(equation->priv->variables, name); if (t) mp_set_from_mp(t, z); else @@ -1028,14 +1151,14 @@ set_variable(const char *name, const MPNumber *x, void *data) { MathEquation *equation = data; /* FIXME: Don't allow writing to built-in variables, e.g. ans, rand, sin, ... */ - math_variables_set_value(equation->priv->variables, name, x); + math_variables_set(equation->priv->variables, name, x); } static int convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data) { - return currency_convert(x, x_units, z_units, z); + return unit_manager_convert_by_symbol(unit_manager_get_default(), x, x_units, z_units, z); } @@ -1045,7 +1168,7 @@ parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token) MPEquationOptions options; memset(&options, 0, sizeof(options)); - options.base = equation->priv->base; + options.base = mp_serializer_get_base(equation->priv->serializer); options.wordlen = equation->priv->word_size; options.angle_units = equation->priv->angle_units; options.variable_is_defined = variable_is_defined; @@ -1058,25 +1181,20 @@ parse(MathEquation *equation, const char *text, MPNumber *z, char **error_token) } -void -math_equation_solve(MathEquation *equation) +/* + * Executed in separate thread. It is thus not a good idea to write to anything + * in MathEquation but the async queue from here. + */ +static gpointer +math_equation_solve_real(gpointer data) { - MPNumber z; - gint result, n_brackets = 0; - gchar *c, *text, *error_token = NULL, *message = NULL; - GString *equation_text; - - if (math_equation_is_empty(equation)) - return; - - /* If showing a result return to the equation that caused it */ - // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans") - if (math_equation_is_result(equation)) { - math_equation_undo(equation); - return; - } + MathEquation *equation = MATH_EQUATION(data); + SolveData *solvedata = g_slice_new0(SolveData); - math_equation_set_number_mode(equation, NORMAL); + gint n_brackets = 0, result; + gchar *c, *text, *error_token; + GString *equation_text; + MPNumber z; text = math_equation_get_equation(equation); equation_text = g_string_new(text); @@ -1099,81 +1217,183 @@ math_equation_solve(MathEquation *equation) switch (result) { case PARSER_ERR_NONE: - math_equation_set_number(equation, &z); + solvedata->number_result = g_slice_new(MPNumber); + mp_set_from_mp(&z, solvedata->number_result); break; case PARSER_ERR_OVERFLOW: - message = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */ + solvedata->error = g_strdup(/* Error displayed to user when they perform a bitwise operation on numbers greater than the current word */ _("Overflow. Try a bigger word size")); break; case PARSER_ERR_UNKNOWN_VARIABLE: - message = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */ + solvedata->error = g_strdup_printf(/* Error displayed to user when they an unknown variable is entered */ _("Unknown variable '%s'"), error_token); break; case PARSER_ERR_UNKNOWN_FUNCTION: - message = g_strdup_printf(/* Error displayed to user when an unknown function is entered */ + solvedata->error = g_strdup_printf(/* Error displayed to user when an unknown function is entered */ _("Function '%s' is not defined"), error_token); break; case PARSER_ERR_UNKNOWN_CONVERSION: - message = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */ + solvedata->error = g_strdup(/* Error displayed to user when an conversion with unknown units is attempted */ _("Unknown conversion")); break; case PARSER_ERR_MP: - message = g_strdup(mp_get_error()); + if (mp_get_error()) + solvedata->error = g_strdup(mp_get_error()); + else if (error_token) + solvedata->error = g_strdup_printf(/* Uncategorized error. Show error token to user*/ + _("Malformed expression at token '%s'"), error_token); + else + solvedata->error = g_strdup (/* Unknown error. */ + _("Malformed expression")); break; default: - message = g_strdup(/* Error displayed to user when they enter an invalid calculation */ + solvedata->error = g_strdup(/* Error displayed to user when they enter an invalid calculation */ _("Malformed expression")); break; } + g_async_queue_push(equation->priv->queue, solvedata); + + return NULL; +} + - if (error_token) - free(error_token); +static gboolean +math_equation_show_in_progress(gpointer data) +{ + MathEquation *equation = MATH_EQUATION(data); + if (equation->priv->in_solve) + math_equation_set_status(equation, _("Calculating")); + return false; +} - if (message) { - math_equation_set_status(equation, message); - g_free(message); + +static gboolean +math_equation_look_for_answer(gpointer data) +{ + MathEquation *equation = MATH_EQUATION(data); + SolveData *result = g_async_queue_try_pop(equation->priv->queue); + + if (result == NULL) + return true; + + equation->priv->in_solve = false; + + if (!result->error) + math_equation_set_status(equation, ""); + + if (result->error != NULL) { + math_equation_set_status(equation, result->error); + g_free(result->error); + } + else if (result->number_result != NULL) { + math_equation_set_number(equation, result->number_result); + g_slice_free(MPNumber, result->number_result); } + else if (result->text_result != NULL) { + math_equation_set(equation, result->text_result); + g_free(result->text_result); + } + g_slice_free(SolveData, result); + + return false; } void -math_equation_factorize(MathEquation *equation) +math_equation_solve(MathEquation *equation) { - MPNumber x; - GList *factors, *factor; - GString *text; + g_return_if_fail(equation != NULL); - if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) { - /* Error displayed when trying to factorize a non-integer value */ - math_equation_set_status(equation, _("Need an integer to factorize")); + // FIXME: should replace calculation or give error message + if (equation->priv->in_solve) + return; + + if (math_equation_is_empty(equation)) + return; + + /* If showing a result return to the equation that caused it */ + // FIXME: Result may not be here due to solve (i.e. the user may have entered "ans") + if (math_equation_is_result(equation)) { + math_equation_undo(equation); return; } + equation->priv->in_solve = true; + + math_equation_set_number_mode(equation, NORMAL); + + g_thread_new("", math_equation_solve_real, equation); + + g_timeout_add(50, math_equation_look_for_answer, equation); + g_timeout_add(100, math_equation_show_in_progress, equation); +} + + +static gpointer +math_equation_factorize_real(gpointer data) +{ + GString *text; + GList *factors, *factor; + MPNumber x; + MathEquation *equation = MATH_EQUATION(data); + SolveData *result = g_slice_new0(SolveData); + + math_equation_get_number(equation, &x); factors = mp_factorize(&x); text = g_string_new(""); for (factor = factors; factor; factor = factor->next) { - gchar temp[MAX_DIGITS]; + gchar *temp; MPNumber *n; n = factor->data; - display_make_number(equation, temp, MAX_DIGITS, n); + temp = mp_serializer_to_string(equation->priv->serializer, n); g_string_append(text, temp); if (factor->next) g_string_append(text, "×"); g_slice_free(MPNumber, n); + g_free(temp); } g_list_free(factors); - math_equation_set(equation, text->str); + result->text_result = g_strndup(text->str, text->len); + g_async_queue_push(equation->priv->queue, result); g_string_free(text, TRUE); + + return NULL; +} + + +void +math_equation_factorize(MathEquation *equation) +{ + MPNumber x; + + g_return_if_fail(equation != NULL); + + // FIXME: should replace calculation or give error message + if (equation->priv->in_solve) + return; + + if (!math_equation_get_number(equation, &x) || !mp_is_integer(&x)) { + /* Error displayed when trying to factorize a non-integer value */ + math_equation_set_status(equation, _("Need an integer to factorize")); + return; + } + + equation->priv->in_solve = true; + + g_thread_new("", math_equation_factorize_real, equation); + + g_timeout_add(50, math_equation_look_for_answer, equation); + g_timeout_add(100, math_equation_show_in_progress, equation); } @@ -1183,6 +1403,8 @@ math_equation_delete(MathEquation *equation) gint cursor; GtkTextIter start, end; + g_return_if_fail(equation != NULL); + g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL); if (cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation))) return; @@ -1196,6 +1418,8 @@ math_equation_delete(MathEquation *equation) void math_equation_backspace(MathEquation *equation) { + g_return_if_fail(equation != NULL); + /* Can't delete empty display */ if (math_equation_is_empty(equation)) return; @@ -1213,6 +1437,8 @@ math_equation_backspace(MathEquation *equation) void math_equation_clear(MathEquation *equation) { + g_return_if_fail(equation != NULL); + math_equation_set_number_mode(equation, NORMAL); gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1); clear_ans(equation, FALSE); @@ -1224,6 +1450,8 @@ math_equation_shift(MathEquation *equation, gint count) { MPNumber z; + g_return_if_fail(equation != NULL); + if (!math_equation_get_number(equation, &z)) { math_equation_set_status(equation, /* This message is displayed in the status bar when a bit @@ -1244,6 +1472,8 @@ math_equation_toggle_bit(MathEquation *equation, guint bit) guint64 bits; gboolean result; + g_return_if_fail(equation != NULL); + result = math_equation_get_number(equation, &x); if (result) { MPNumber max; @@ -1270,25 +1500,6 @@ math_equation_toggle_bit(MathEquation *equation, guint bit) } -/* Convert MP number to character string. */ -//FIXME: What to do with this? -void -display_make_number(MathEquation *equation, char *target, int target_len, const MPNumber *x) -{ - switch(equation->priv->format) { - case FIX: - mp_cast_to_string(x, equation->priv->base, equation->priv->base, equation->priv->accuracy, !equation->priv->show_zeroes, target, target_len); - break; - case SCI: - mp_cast_to_exponential_string(x, equation->priv->base, equation->priv->base, equation->priv->accuracy, !equation->priv->show_zeroes, false, target, target_len); - break; - case ENG: - mp_cast_to_exponential_string(x, equation->priv->base, equation->priv->base, equation->priv->accuracy, !equation->priv->show_zeroes, true, target, target_len); - break; - } -} - - static void math_equation_set_property(GObject *object, guint prop_id, @@ -1297,7 +1508,7 @@ math_equation_set_property(GObject *object, { MathEquation *self; - self = MATH_EQUATION (object); + self = MATH_EQUATION(object); switch (prop_id) { case PROP_STATUS: @@ -1336,8 +1547,14 @@ math_equation_set_property(GObject *object, case PROP_TARGET_CURRENCY: math_equation_set_target_currency(self, g_value_get_string(value)); break; + case PROP_SOURCE_UNITS: + math_equation_set_source_units(self, g_value_get_string(value)); + break; + case PROP_TARGET_UNITS: + math_equation_set_target_units(self, g_value_get_string(value)); + break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } @@ -1352,14 +1569,14 @@ math_equation_get_property(GObject *object, MathEquation *self; gchar *text; - self = MATH_EQUATION (object); + self = MATH_EQUATION(object); switch (prop_id) { case PROP_STATUS: g_value_set_string(value, self->priv->state.status); break; case PROP_DISPLAY: - text = math_equation_get_display(self); + text = math_equation_get_display(self); g_value_set_string(value, text); g_free(text); break; @@ -1372,16 +1589,16 @@ math_equation_get_property(GObject *object, g_value_set_enum(value, self->priv->number_mode); break; case PROP_ACCURACY: - g_value_set_int(value, self->priv->accuracy); + g_value_set_int(value, mp_serializer_get_trailing_digits(self->priv->serializer)); break; case PROP_SHOW_THOUSANDS_SEPARATORS: - g_value_set_boolean(value, self->priv->show_tsep); + g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self->priv->serializer)); break; case PROP_SHOW_TRAILING_ZEROES: - g_value_set_boolean(value, self->priv->show_zeroes); + g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self->priv->serializer)); break; case PROP_NUMBER_FORMAT: - g_value_set_enum(value, self->priv->format); + g_value_set_enum(value, mp_serializer_get_number_format(self->priv->serializer)); break; case PROP_BASE: g_value_set_int(value, math_equation_get_base(self)); @@ -1398,15 +1615,36 @@ math_equation_get_property(GObject *object, case PROP_TARGET_CURRENCY: g_value_set_string(value, self->priv->target_currency); break; + case PROP_SOURCE_UNITS: + g_value_set_string(value, self->priv->source_units); + break; + case PROP_TARGET_UNITS: + g_value_set_string(value, self->priv->target_units); + break; + case PROP_SERIALIZER: + g_value_set_object(value, self->priv->serializer); + break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void -math_equation_class_init (MathEquationClass *klass) +math_equation_constructed(GObject *object) +{ + GtkTextBuffer *parent_class; + parent_class = g_type_class_peek_parent(MATH_EQUATION_GET_CLASS(object)); + if (G_OBJECT_CLASS(parent_class)->constructed) + G_OBJECT_CLASS(parent_class)->constructed(object); + + MATH_EQUATION(object)->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(object), NULL, "weight", PANGO_WEIGHT_BOLD, NULL); +} + + +static void +math_equation_class_init(MathEquationClass *klass) { static GEnumValue number_mode_values[] = { @@ -1415,13 +1653,6 @@ math_equation_class_init (MathEquationClass *klass) {SUBSCRIPT, "subscript", "subscript"}, {0, NULL, NULL} }; - static GEnumValue number_format_values[] = - { - {FIX, "fixed-point", "fixed-point"}, - {SCI, "scientific", "scientific"}, - {ENG, "engineering", "engineering"}, - {0, NULL, NULL} - }; static GEnumValue angle_unit_values[] = { {MP_RADIANS, "radians", "radians"}, @@ -1429,15 +1660,16 @@ math_equation_class_init (MathEquationClass *klass) {MP_GRADIANS, "gradians", "gradians"}, {0, NULL, NULL} }; - GObjectClass *object_class = G_OBJECT_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->get_property = math_equation_get_property; object_class->set_property = math_equation_set_property; + object_class->constructed = math_equation_constructed; - g_type_class_add_private (klass, sizeof (MathEquationPrivate)); - + g_type_class_add_private(klass, sizeof(MathEquationPrivate)); + number_mode_type = g_enum_register_static("NumberMode", number_mode_values); - number_format_type = g_enum_register_static("DisplayFormat", number_format_values); + number_format_type = math_mp_display_format_get_type(); angle_unit_type = g_enum_register_static("AngleUnit", angle_unit_values); g_object_class_install_property(object_class, @@ -1496,14 +1728,14 @@ math_equation_class_init (MathEquationClass *klass) "number-format", "Display format", number_format_type, - FIX, + MP_DISPLAY_FORMAT_FIXED, G_PARAM_READWRITE)); g_object_class_install_property(object_class, PROP_BASE, g_param_spec_int("base", "base", "Default number base (derived from number-format)", - 2, 16, 10, + 2, 16, 10, G_PARAM_READWRITE)); g_object_class_install_property(object_class, PROP_WORD_SIZE, @@ -1534,30 +1766,54 @@ math_equation_class_init (MathEquationClass *klass) "target Currency", "", G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_SOURCE_UNITS, + g_param_spec_string("source-units", + "source-units", + "Source Units", + "", + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_TARGET_UNITS, + g_param_spec_string("target-units", + "target-units", + "target Units", + "", + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_SERIALIZER, + g_param_spec_object("serializer", + "serializer", + "Serializer", + MP_TYPE_SERIALIZER, + G_PARAM_READABLE)); } static void -pre_insert_text_cb (MathEquation *equation, - GtkTextIter *location, - gchar *text, - gint len, - gpointer user_data) +pre_insert_text_cb(MathEquation *equation, + GtkTextIter *location, + gchar *text, + gint len, + gpointer user_data) { gunichar c; - + gint cursor; + if (equation->priv->in_reformat) return; - + /* If following a delete then have already pushed undo stack (GtkTextBuffer doesn't indicate replace operations so we have to infer them) */ if (!equation->priv->in_delete) math_equation_push_undo_stack(equation); /* Clear result on next digit entered if cursor at end of line */ - // FIXME Cursor c = g_utf8_get_char(text); - if (g_unichar_isdigit(c) && math_equation_is_result(equation)) { + g_object_get(G_OBJECT(equation), "cursor-position", &cursor, NULL); + if ((g_unichar_isdigit(c) || c == mp_serializer_get_radix(equation->priv->serializer)) && + math_equation_is_result(equation) && + cursor >= gtk_text_buffer_get_char_count(GTK_TEXT_BUFFER(equation))) { gtk_text_buffer_set_text(GTK_TEXT_BUFFER(equation), "", -1); clear_ans(equation, FALSE); gtk_text_buffer_get_end_iter(GTK_TEXT_BUFFER(equation), location); @@ -1569,7 +1825,7 @@ pre_insert_text_cb (MathEquation *equation, offset = gtk_text_iter_get_offset(location); get_ans_offsets(equation, &ans_start, &ans_end); - + /* Inserted inside ans */ if (offset > ans_start && offset < ans_end) clear_ans(equation, TRUE); @@ -1580,17 +1836,17 @@ pre_insert_text_cb (MathEquation *equation, static gboolean on_delete(MathEquation *equation) { - equation->priv->in_delete = FALSE; - return FALSE; + equation->priv->in_delete = FALSE; + return FALSE; } static void -pre_delete_range_cb (MathEquation *equation, - GtkTextIter *start, - GtkTextIter *end, - gpointer user_data) -{ +pre_delete_range_cb(MathEquation *equation, + GtkTextIter *start, + GtkTextIter *end, + gpointer user_data) +{ if (equation->priv->in_reformat) return; @@ -1606,7 +1862,7 @@ pre_delete_range_cb (MathEquation *equation, start_offset = gtk_text_iter_get_offset(start); end_offset = gtk_text_iter_get_offset(end); get_ans_offsets(equation, &ans_start, &ans_end); - + /* Deleted part of ans */ if (start_offset < ans_end && end_offset > ans_start) clear_ans(equation, TRUE); @@ -1615,29 +1871,38 @@ pre_delete_range_cb (MathEquation *equation, static void -insert_text_cb (MathEquation *equation, - GtkTextIter *location, - gchar *text, - gint len, - gpointer user_data) +insert_text_cb(MathEquation *equation, + GtkTextIter *location, + gchar *text, + gint len, + gpointer user_data) { if (equation->priv->in_reformat) return; equation->priv->state.entered_multiply = strcmp(text, "×") == 0; + + /* Update thousands separators */ + reformat_separators(equation); + g_object_notify(G_OBJECT(equation), "display"); } static void -delete_range_cb (MathEquation *equation, - GtkTextIter *start, - GtkTextIter *end, - gpointer user_data) +delete_range_cb(MathEquation *equation, + GtkTextIter *start, + GtkTextIter *end, + gpointer user_data) { if (equation->priv->in_reformat) return; + equation->priv->state.entered_multiply = FALSE; + + /* Update thousands separators */ + reformat_separators(equation); + // FIXME: A replace will emit this both for delete-range and insert-text, can it be avoided? g_object_notify(G_OBJECT(equation), "display"); } @@ -1649,19 +1914,16 @@ math_equation_init(MathEquation *equation) /* Digits localized for the given language */ const char *digit_values = _("0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F"); const char *default_digits[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; - gchar *radix, *tsep; gchar **digits; - gboolean use_default_digits = FALSE; + /* Default to using untranslated digits, this is because it doesn't make sense in most languages and we need to make this optional. + * See https://bugzilla.gnome.org/show_bug.cgi?id=632661 */ + gboolean use_default_digits = TRUE; int i; - equation->priv = G_TYPE_INSTANCE_GET_PRIVATE (equation, math_equation_get_type(), MathEquationPrivate); - - // FIXME: Causes error - // (process:18573): Gtk-CRITICAL **: set_table: assertion buffer->tag_table == NULL' failed - equation->priv->ans_tag = gtk_text_buffer_create_tag(GTK_TEXT_BUFFER(equation), NULL, "weight", PANGO_WEIGHT_BOLD, NULL); + equation->priv = G_TYPE_INSTANCE_GET_PRIVATE(equation, math_equation_get_type(), MathEquationPrivate); g_signal_connect(equation, "insert-text", G_CALLBACK(pre_insert_text_cb), equation); - g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation); + g_signal_connect(equation, "delete-range", G_CALLBACK(pre_delete_range_cb), equation); g_signal_connect_after(equation, "insert-text", G_CALLBACK(insert_text_cb), equation); g_signal_connect_after(equation, "delete-range", G_CALLBACK(delete_range_cb), equation); @@ -1669,35 +1931,25 @@ math_equation_init(MathEquation *equation) for (i = 0; i < 16; i++) { if (use_default_digits || digits[i] == NULL) { use_default_digits = TRUE; - equation->priv->digits[i] = strdup(default_digits[i]); + equation->priv->digits[i] = g_utf8_get_char(default_digits[i]); } else - equation->priv->digits[i] = strdup(digits[i]); + equation->priv->digits[i] = g_utf8_get_char(digits[i]); } g_strfreev(digits); - setlocale(LC_NUMERIC, ""); - - radix = nl_langinfo(RADIXCHAR); - equation->priv->radix = radix ? g_locale_to_utf8(radix, -1, NULL, NULL, NULL) : g_strdup("."); - tsep = nl_langinfo(THOUSEP); - equation->priv->tsep = tsep ? g_locale_to_utf8(tsep, -1, NULL, NULL, NULL) : g_strdup(","); - - equation->priv->tsep_count = 3; - equation->priv->variables = math_variables_new(); equation->priv->state.status = g_strdup(""); - equation->priv->show_zeroes = FALSE; - equation->priv->show_tsep = FALSE; - equation->priv->format = FIX; - equation->priv->accuracy = 9; equation->priv->word_size = 32; equation->priv->angle_units = MP_DEGREES; // FIXME: Pick based on locale - equation->priv->source_currency = g_strdup(currency_names[0].short_name); - equation->priv->target_currency = g_strdup(currency_names[0].short_name); - equation->priv->base = 10; + equation->priv->source_currency = g_strdup(""); + equation->priv->target_currency = g_strdup(""); + equation->priv->source_units = g_strdup(""); + equation->priv->target_units = g_strdup(""); + equation->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 9); + equation->priv->queue = g_async_queue_new(); mp_set_from_integer(0, &equation->priv->state.ans); } diff --git a/src/math-equation.h b/src/math-equation.h index c9ebe0c..954334c 100644 --- a/src/math-equation.h +++ b/src/math-equation.h @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MATH_EQUATION_H @@ -22,110 +14,115 @@ #include #include + #include "mp.h" #include "math-variables.h" +#include "mp-serializer.h" G_BEGIN_DECLS +#define MATH_TYPE_EQUATION (math_equation_get_type ()) #define MATH_EQUATION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_equation_get_type(), MathEquation)) +#define MATH_EQUATION_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), MATH_TYPE_EQUATION, MathEquationClass)) typedef struct MathEquationPrivate MathEquationPrivate; -typedef struct { - GtkTextBuffer parent_instance; - MathEquationPrivate* priv; +typedef struct +{ + GtkTextBuffer parent_instance; + MathEquationPrivate *priv; } MathEquation; -typedef struct { - GtkTextBufferClass parent_class; +typedef struct +{ + GtkTextBufferClass parent_class; } MathEquationClass; -/* Number display mode. */ typedef enum { - FIX, - SCI, - ENG -} DisplayFormat; - -typedef enum { - NORMAL, - SUPERSCRIPT, - SUBSCRIPT + NORMAL, + SUPERSCRIPT, + SUBSCRIPT } NumberMode; GType math_equation_get_type(void); -MathEquation* math_equation_new(void); - -MathVariables* math_equation_get_variables(MathEquation* equation); - -const gchar* math_equation_get_digit_text(MathEquation* equation, guint digit); -const gchar* math_equation_get_numeric_point_text(MathEquation* equation); -const gchar* math_equation_get_thousands_separator_text(MathEquation* equation); - -void math_equation_set_status(MathEquation* equation, const gchar* status); -const gchar* math_equation_get_status(MathEquation* equation); - -gboolean math_equation_is_empty(MathEquation* equation); -gboolean math_equation_is_result(MathEquation* equation); -gchar* math_equation_get_display(MathEquation* equation); -gchar* math_equation_get_equation(MathEquation* equation); -gboolean math_equation_get_number(MathEquation* equation, MPNumber* z); - -void math_equation_set_number_mode(MathEquation* equation, NumberMode mode); -NumberMode math_equation_get_number_mode(MathEquation* equation); - -void math_equation_set_accuracy(MathEquation* equation, gint accuracy); -gint math_equation_get_accuracy(MathEquation* equation); - -void math_equation_set_show_thousands_separators(MathEquation* equation, gboolean visible); -gboolean math_equation_get_show_thousands_separators(MathEquation* equation); - -void math_equation_set_show_trailing_zeroes(MathEquation* equation, gboolean visible); -gboolean math_equation_get_show_trailing_zeroes(MathEquation* equation); - -void math_equation_set_number_format(MathEquation* equation, DisplayFormat format); -DisplayFormat math_equation_get_number_format(MathEquation* equation); - -void math_equation_set_base(MathEquation* equation, gint base); -gint math_equation_get_base(MathEquation* equation); - -void math_equation_set_word_size(MathEquation* equation, gint word_size); -gint math_equation_get_word_size(MathEquation* equation); - -void math_equation_set_angle_units(MathEquation* equation, MPAngleUnit angle_unit); -MPAngleUnit math_equation_get_angle_units(MathEquation* equation); - -void math_equation_set_source_currency(MathEquation* equation, const gchar* currency); -const gchar* math_equation_get_source_currency(MathEquation* equation); - -void math_equation_set_target_currency(MathEquation* equation, const gchar* currency); -const gchar* math_equation_get_target_currency(MathEquation* equation); - -const MPNumber* math_equation_get_answer(MathEquation* equation); - -void math_equation_copy(MathEquation* equation); -void math_equation_paste(MathEquation* equation); -void math_equation_undo(MathEquation* equation); -void math_equation_redo(MathEquation* equation); -void math_equation_store(MathEquation* equation, const gchar* name); -void math_equation_recall(MathEquation* equation, const gchar* name); -void math_equation_set(MathEquation* equation, const gchar* text); -void math_equation_set_number(MathEquation* equation, const MPNumber* x); -void math_equation_insert(MathEquation* equation, const gchar* text); -void math_equation_insert_digit(MathEquation* equation, guint digit); -void math_equation_insert_numeric_point(MathEquation* equation); -void math_equation_insert_number(MathEquation* equation, const MPNumber* x); -void math_equation_insert_subtract(MathEquation* equation); -void math_equation_insert_exponent(MathEquation* equation); -void math_equation_solve(MathEquation* equation); -void math_equation_factorize(MathEquation* equation); -void math_equation_delete(MathEquation* equation); -void math_equation_backspace(MathEquation* equation); -void math_equation_clear(MathEquation* equation); -void math_equation_shift(MathEquation* equation, gint count); -void math_equation_toggle_bit(MathEquation* equation, guint bit); - -//FIXME: Obsolete -void display_make_number(MathEquation* equation, char* target, int target_len, const MPNumber* x); +MathEquation *math_equation_new(void); + +MathVariables *math_equation_get_variables(MathEquation *equation); + +gunichar math_equation_get_digit_text(MathEquation *equation, guint digit); + +void math_equation_set_status(MathEquation *equation, const gchar *status); +const gchar *math_equation_get_status(MathEquation *equation); + +gboolean math_equation_is_empty(MathEquation *equation); +gboolean math_equation_is_result(MathEquation *equation); +gchar *math_equation_get_display(MathEquation *equation); +gchar *math_equation_get_equation(MathEquation *equation); +gboolean math_equation_get_number(MathEquation *equation, MPNumber *z); + +void math_equation_set_number_mode(MathEquation *equation, NumberMode mode); +NumberMode math_equation_get_number_mode(MathEquation *equation); + +void math_equation_set_accuracy(MathEquation *equation, gint accuracy); +gint math_equation_get_accuracy(MathEquation *equation); + +void math_equation_set_show_thousands_separators(MathEquation *equation, gboolean visible); +gboolean math_equation_get_show_thousands_separators(MathEquation *equation); + +void math_equation_set_show_trailing_zeroes(MathEquation *equation, gboolean visible); +gboolean math_equation_get_show_trailing_zeroes(MathEquation *equation); + +void math_equation_set_number_format(MathEquation *equation, MpDisplayFormat format); +MpDisplayFormat math_equation_get_number_format(MathEquation *equation); + +void math_equation_set_base(MathEquation *equation, gint base); +gint math_equation_get_base(MathEquation *equation); + +void math_equation_set_word_size(MathEquation *equation, gint word_size); +gint math_equation_get_word_size(MathEquation *equation); + +void math_equation_set_angle_units(MathEquation *equation, MPAngleUnit angle_unit); +MPAngleUnit math_equation_get_angle_units(MathEquation *equation); + +void math_equation_set_source_currency(MathEquation *equation, const gchar *currency); +const gchar *math_equation_get_source_currency(MathEquation *equation); + +void math_equation_set_target_currency(MathEquation *equation, const gchar *currency); +const gchar *math_equation_get_target_currency(MathEquation *equation); + +void math_equation_set_source_units(MathEquation *equation, const gchar *units); +const gchar *math_equation_get_source_units(MathEquation *equation); + +void math_equation_set_target_units(MathEquation *equation, const gchar *units); +const gchar *math_equation_get_target_units(MathEquation *equation); + +gboolean math_equation_in_solve(MathEquation *equation); + +const MPNumber *math_equation_get_answer(MathEquation *equation); +MpSerializer *math_equation_get_serializer(MathEquation *equation); + +void math_equation_copy(MathEquation *equation); +void math_equation_paste(MathEquation *equation); +void math_equation_undo(MathEquation *equation); +void math_equation_redo(MathEquation *equation); +void math_equation_store(MathEquation *equation, const gchar *name); +void math_equation_recall(MathEquation *equation, const gchar *name); +void math_equation_set(MathEquation *equation, const gchar *text); +void math_equation_set_number(MathEquation *equation, const MPNumber *x); +void math_equation_insert(MathEquation *equation, const gchar *text); +void math_equation_insert_digit(MathEquation *equation, guint digit); +void math_equation_insert_numeric_point(MathEquation *equation); +void math_equation_insert_number(MathEquation *equation, const MPNumber *x); +void math_equation_insert_subtract(MathEquation *equation); +void math_equation_insert_exponent(MathEquation *equation); +void math_equation_solve(MathEquation *equation); +void math_equation_factorize(MathEquation *equation); +void math_equation_delete(MathEquation *equation); +void math_equation_backspace(MathEquation *equation); +void math_equation_clear(MathEquation *equation); +void math_equation_shift(MathEquation *equation, gint count); +void math_equation_toggle_bit(MathEquation *equation, guint bit); + +G_END_DECLS #endif /* MATH_EQUATION_H */ diff --git a/src/math-preferences.c b/src/math-preferences.c index ab651df..07190de 100644 --- a/src/math-preferences.c +++ b/src/math-preferences.c @@ -1,19 +1,11 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -41,8 +33,8 @@ struct MathPreferencesDialogPrivate MathPreferencesDialog * math_preferences_dialog_new(MathEquation *equation) -{ - return g_object_new (math_preferences_get_type(), "equation", equation, NULL); +{ + return g_object_new(math_preferences_get_type(), "equation", equation, NULL); } @@ -66,7 +58,7 @@ G_MODULE_EXPORT void number_format_combobox_changed_cb(GtkWidget *combo, MathPreferencesDialog *dialog) { - DisplayFormat value; + MpDisplayFormat value; GtkTreeModel *model; GtkTreeIter iter; @@ -211,7 +203,7 @@ word_size_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *di static void angle_unit_cb(MathEquation *equation, GParamSpec *spec, MathPreferencesDialog *dialog) { - set_combo_box_from_int(GET_WIDGET(dialog->priv->ui, "angle_unit_combobox"), math_equation_get_angle_units(equation)); + set_combo_box_from_int(GET_WIDGET(dialog->priv->ui, "angle_unit_combobox"), math_equation_get_angle_units(equation)); } @@ -237,9 +229,7 @@ create_gui(MathPreferencesDialog *dialog) gtk_window_set_title(GTK_WINDOW(dialog), /* Title of preferences dialog */ _("Preferences")); - gtk_window_set_icon_name(GTK_WINDOW(dialog), "accessories-calculator"); gtk_container_set_border_width(GTK_CONTAINER(dialog), 8); - gtk_dialog_set_has_separator(GTK_DIALOG(dialog), FALSE); gtk_dialog_add_button(GTK_DIALOG(dialog), /* Label on close button in preferences dialog */ _("_Close"), 0); @@ -268,17 +258,21 @@ create_gui(MathPreferencesDialog *dialog) widget = GET_WIDGET(dialog->priv->ui, "number_format_combobox"); model = gtk_combo_box_get_model(GTK_COMBO_BOX(widget)); gtk_list_store_append(GTK_LIST_STORE(model), &iter); + gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, + /* Number display mode combo: Automatic, e.g. 1234 (or scientific for large number 1.234×10^99) */ + _("Automatic"), 1, MP_DISPLAY_FORMAT_AUTOMATIC, -1); + gtk_list_store_append(GTK_LIST_STORE(model), &iter); gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, /* Number display mode combo: Fixed, e.g. 1234 */ - _("Fixed"), 1, FIX, -1); + _("Fixed"), 1, MP_DISPLAY_FORMAT_FIXED, -1); gtk_list_store_append(GTK_LIST_STORE(model), &iter); gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, /* Number display mode combo: Scientific, e.g. 1.234×10^3 */ - _("Scientific"), 1, SCI, -1); + _("Scientific"), 1, MP_DISPLAY_FORMAT_SCIENTIFIC, -1); gtk_list_store_append(GTK_LIST_STORE(model), &iter); gtk_list_store_set(GTK_LIST_STORE(model), &iter, 0, /* Number display mode combo: Engineering, e.g. 1.234k */ - _("Engineering"), 1, ENG, -1); + _("Engineering"), 1, MP_DISPLAY_FORMAT_ENGINEERING, -1); renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(widget), renderer, TRUE); gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(widget), renderer, "text", 0); @@ -333,21 +327,21 @@ create_gui(MathPreferencesDialog *dialog) static void math_preferences_set_property(GObject *object, - guint prop_id, - const GValue *value, - GParamSpec *pspec) + guint prop_id, + const GValue *value, + GParamSpec *pspec) { MathPreferencesDialog *self; - self = MATH_PREFERENCES (object); + self = MATH_PREFERENCES(object); switch (prop_id) { case PROP_EQUATION: - self->priv->equation = g_value_get_object (value); + self->priv->equation = g_value_get_object(value); create_gui(self); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } @@ -355,34 +349,34 @@ math_preferences_set_property(GObject *object, static void math_preferences_get_property(GObject *object, - guint prop_id, - GValue *value, - GParamSpec *pspec) + guint prop_id, + GValue *value, + GParamSpec *pspec) { MathPreferencesDialog *self; - self = MATH_PREFERENCES (object); + self = MATH_PREFERENCES(object); switch (prop_id) { case PROP_EQUATION: - g_value_set_object (value, self->priv->equation); + g_value_set_object(value, self->priv->equation); break; default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } static void -math_preferences_class_init (MathPreferencesDialogClass *klass) +math_preferences_class_init(MathPreferencesDialogClass *klass) { - GObjectClass *object_class = G_OBJECT_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->get_property = math_preferences_get_property; object_class->set_property = math_preferences_set_property; - g_type_class_add_private (klass, sizeof (MathPreferencesDialogPrivate)); + g_type_class_add_private(klass, sizeof(MathPreferencesDialogPrivate)); g_object_class_install_property(object_class, PROP_EQUATION, @@ -397,5 +391,5 @@ math_preferences_class_init (MathPreferencesDialogClass *klass) static void math_preferences_init(MathPreferencesDialog *dialog) { - dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE (dialog, math_preferences_get_type(), MathPreferencesDialogPrivate); + dialog->priv = G_TYPE_INSTANCE_GET_PRIVATE(dialog, math_preferences_get_type(), MathPreferencesDialogPrivate); } diff --git a/src/math-preferences.h b/src/math-preferences.h index ca7430e..0cfd6e1 100644 --- a/src/math-preferences.h +++ b/src/math-preferences.h @@ -1,19 +1,11 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MATH_PREFERENCES_H @@ -29,18 +21,20 @@ G_BEGIN_DECLS typedef struct MathPreferencesDialogPrivate MathPreferencesDialogPrivate; -typedef struct { - GtkDialog parent_instance; - MathPreferencesDialogPrivate* priv; +typedef struct +{ + GtkDialog parent_instance; + MathPreferencesDialogPrivate *priv; } MathPreferencesDialog; -typedef struct { - GtkDialogClass parent_class; +typedef struct +{ + GtkDialogClass parent_class; } MathPreferencesDialogClass; GType math_preferences_get_type(void); -MathPreferencesDialog* math_preferences_dialog_new(MathEquation* equation); +MathPreferencesDialog *math_preferences_dialog_new(MathEquation *equation); G_END_DECLS diff --git a/src/math-variable-popup.c b/src/math-variable-popup.c new file mode 100644 index 0000000..d033571 --- /dev/null +++ b/src/math-variable-popup.c @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include +#include + +#include "math-variable-popup.h" + +enum { + PROP_0, + PROP_EQUATION +}; + +struct MathVariablePopupPrivate +{ + MathEquation *equation; + + GtkWidget *vbox; + GtkWidget *variable_name_entry; + GtkWidget *add_variable_button; +}; + +G_DEFINE_TYPE (MathVariablePopup, math_variable_popup, GTK_TYPE_WINDOW); + +MathVariablePopup * +math_variable_popup_new(MathEquation *equation) +{ + return g_object_new(math_variable_popup_get_type(), "equation", equation, NULL); +} + + +static void +variable_focus_out_event_cb(GtkWidget *widget, GdkEventFocus *event, MathVariablePopup *popup) +{ + gtk_widget_destroy(widget); +} + + +static void +insert_variable_cb(GtkWidget *widget, MathVariablePopup *popup) +{ + const gchar *name; + + name = g_object_get_data(G_OBJECT(widget), "variable_name"); + math_equation_insert(popup->priv->equation, name); + + gtk_widget_destroy(gtk_widget_get_toplevel(widget)); +} + + +static gboolean +variable_name_key_press_cb(GtkWidget *widget, GdkEventKey *event, MathVariablePopup *popup) +{ + /* Can't have whitespace in names, so replace with underscores */ + if (event->keyval == GDK_KEY_space || event->keyval == GDK_KEY_KP_Space) + event->keyval = GDK_KEY_underscore; + + return FALSE; +} + + +static void +variable_name_changed_cb(GtkWidget *widget, MathVariablePopup *popup) +{ + const gchar *text = gtk_entry_get_text(GTK_ENTRY(popup->priv->variable_name_entry)); + gtk_widget_set_sensitive(popup->priv->add_variable_button, text[0] != '\0'); +} + + +static void +add_variable_cb(GtkWidget *widget, MathVariablePopup *popup) +{ + const gchar *name; + MPNumber z; + + name = gtk_entry_get_text(GTK_ENTRY(popup->priv->variable_name_entry)); + if (name[0] == '\0') + return; + + if (math_equation_get_number(popup->priv->equation, &z)) + math_variables_set(math_equation_get_variables(popup->priv->equation), name, &z); + else if (math_equation_is_result(popup->priv->equation)) + math_variables_set(math_equation_get_variables(popup->priv->equation), name, math_equation_get_answer(popup->priv->equation)); + else + g_warning("Can't add variable %s, the display is not a number", name); + + gtk_widget_destroy(gtk_widget_get_toplevel(widget)); +} + + +static void +save_variable_cb(GtkWidget *widget, MathVariablePopup *popup) +{ + const gchar *name; + MPNumber z; + + name = g_object_get_data(G_OBJECT(widget), "variable_name"); + if (math_equation_get_number(popup->priv->equation, &z)) + math_variables_set(math_equation_get_variables(popup->priv->equation), name, &z); + else if (math_equation_is_result(popup->priv->equation)) + math_variables_set(math_equation_get_variables(popup->priv->equation), name, math_equation_get_answer(popup->priv->equation)); + else + g_warning("Can't save variable %s, the display is not a number", name); + + gtk_widget_destroy(gtk_widget_get_toplevel(widget)); +} + + +static void +delete_variable_cb(GtkWidget *widget, MathVariablePopup *popup) +{ + const gchar *name; + + name = g_object_get_data(G_OBJECT(widget), "variable_name"); + math_variables_delete(math_equation_get_variables(popup->priv->equation), name); + + gtk_widget_destroy(gtk_widget_get_toplevel(widget)); +} + + +static GtkWidget * +make_variable_entry(MathVariablePopup *popup, const gchar *name, const MPNumber *value, gboolean writable) +{ + GtkWidget *hbox, *button, *label; + gchar *text; + + hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + + if (value) + { + gchar *value_text = mp_serializer_to_string(math_equation_get_serializer(popup->priv->equation), value); + text = g_strdup_printf("%s = %s", name, value_text); + g_free (value_text); + } + else + text = g_strdup_printf("%s", name); + + button = gtk_button_new(); + g_object_set_data(G_OBJECT(button), "variable_name", g_strdup(name)); // FIXME: These will all leak memory + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(insert_variable_cb), popup); + gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); + gtk_box_pack_start(GTK_BOX(hbox), button, TRUE, TRUE, 0); + gtk_widget_show(button); + + label = gtk_label_new(text); + g_free(text); + gtk_label_set_use_markup(GTK_LABEL(label), TRUE); + gtk_misc_set_alignment(GTK_MISC(label), 0.0, 0.5); + gtk_container_add(GTK_CONTAINER(button), label); + gtk_widget_show(label); + + if (writable) + { + GtkWidget *image; + + button = gtk_button_new(); + g_object_set_data(G_OBJECT(button), "variable_name", g_strdup(name)); + image = gtk_image_new_from_stock(GTK_STOCK_SAVE, GTK_ICON_SIZE_BUTTON); + gtk_container_add(GTK_CONTAINER(button), image); + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(save_variable_cb), popup); + gtk_widget_show(image); + gtk_widget_show(button); + + button = gtk_button_new(); + g_object_set_data(G_OBJECT(button), "variable_name", g_strdup(name)); + image = gtk_image_new_from_stock(GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON); + gtk_container_add(GTK_CONTAINER(button), image); + gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, TRUE, 0); + g_signal_connect(G_OBJECT(button), "clicked", G_CALLBACK(delete_variable_cb), popup); + gtk_widget_show(image); + gtk_widget_show(button); + } + + return hbox; +} + + +static void +math_variable_popup_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MathVariablePopup *self; + gchar **names; + int i; + GtkWidget *entry, *image; + + self = MATH_VARIABLE_POPUP(object); + + switch (prop_id) { + case PROP_EQUATION: + self->priv->equation = g_value_get_object(value); + + names = math_variables_get_names(math_equation_get_variables(self->priv->equation)); + for (i = 0; names[i]; i++) { + MPNumber *value; + + value = math_variables_get(math_equation_get_variables(self->priv->equation), names[i]); + entry = make_variable_entry(self, names[i], value, TRUE); + gtk_widget_show(entry); + gtk_box_pack_start(GTK_BOX(self->priv->vbox), entry, FALSE, TRUE, 0); + } + g_strfreev(names); + + entry = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); + gtk_widget_show(entry); + + // TODO: Show greyed "variable name" text to give user a hint how to use + self->priv->variable_name_entry = gtk_entry_new(); + g_signal_connect(G_OBJECT(self->priv->variable_name_entry), "key-press-event", G_CALLBACK(variable_name_key_press_cb), self); + g_signal_connect(G_OBJECT(self->priv->variable_name_entry), "changed", G_CALLBACK(variable_name_changed_cb), self); + g_signal_connect(G_OBJECT(self->priv->variable_name_entry), "activate", G_CALLBACK(add_variable_cb), self); + gtk_box_pack_start(GTK_BOX(entry), self->priv->variable_name_entry, TRUE, TRUE, 0); + gtk_widget_show(self->priv->variable_name_entry); + + self->priv->add_variable_button = gtk_button_new(); + gtk_widget_set_sensitive(self->priv->add_variable_button, FALSE); + g_signal_connect(G_OBJECT(self->priv->add_variable_button), "clicked", G_CALLBACK(add_variable_cb), self); + image = gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_BUTTON); + gtk_container_add(GTK_CONTAINER(self->priv->add_variable_button), image); + gtk_box_pack_start(GTK_BOX(entry), self->priv->add_variable_button, FALSE, TRUE, 0); + gtk_widget_show(image); + gtk_widget_show(self->priv->add_variable_button); + gtk_box_pack_end(GTK_BOX(self->priv->vbox), entry, FALSE, TRUE, 0); + + entry = make_variable_entry(self, "rand", NULL, FALSE); + gtk_widget_show(entry); + gtk_box_pack_end(GTK_BOX(self->priv->vbox), entry, FALSE, TRUE, 0); + + entry = make_variable_entry(self, "ans", math_equation_get_answer(self->priv->equation), FALSE); + gtk_widget_show(entry); + gtk_box_pack_end(GTK_BOX(self->priv->vbox), entry, FALSE, TRUE, 0); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + + +static void +math_variable_popup_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MathVariablePopup *self; + + self = MATH_VARIABLE_POPUP(object); + + switch (prop_id) { + case PROP_EQUATION: + g_value_set_object(value, self->priv->equation); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + + +static void +math_variable_popup_class_init(MathVariablePopupClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + object_class->get_property = math_variable_popup_get_property; + object_class->set_property = math_variable_popup_set_property; + + g_type_class_add_private(klass, sizeof(MathVariablePopupPrivate)); + + g_object_class_install_property(object_class, + PROP_EQUATION, + g_param_spec_object("equation", + "equation", + "Equation being controlled", + math_equation_get_type(), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + + +static void +math_variable_popup_init(MathVariablePopup *popup) +{ + popup->priv = G_TYPE_INSTANCE_GET_PRIVATE(popup, math_variable_popup_get_type(), MathVariablePopupPrivate); + + gtk_window_set_decorated(GTK_WINDOW(popup), FALSE); + gtk_window_set_skip_taskbar_hint(GTK_WINDOW(popup), TRUE); + + gtk_container_set_border_width(GTK_CONTAINER(popup), 6); + + /* Destroy this window when it loses focus */ + g_signal_connect(G_OBJECT(popup), "focus-out-event", G_CALLBACK(variable_focus_out_event_cb), popup); + + popup->priv->vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); + gtk_box_set_homogeneous(GTK_BOX(popup->priv->vbox), TRUE); + gtk_container_add(GTK_CONTAINER(popup), popup->priv->vbox); + gtk_widget_show(popup->priv->vbox); +} diff --git a/src/math-variable-popup.h b/src/math-variable-popup.h new file mode 100644 index 0000000..3c24b54 --- /dev/null +++ b/src/math-variable-popup.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef MATH_VARIABLE_POPUP_H +#define MATH_VARIABLE_POPUP_H + +#include +#include "math-equation.h" + +G_BEGIN_DECLS + +#define MATH_VARIABLE_POPUP(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), math_variable_popup_get_type(), MathVariablePopup)) + +typedef struct MathVariablePopupPrivate MathVariablePopupPrivate; + +typedef struct +{ + GtkWindow parent_instance; + MathVariablePopupPrivate *priv; +} MathVariablePopup; + +typedef struct +{ + GtkWindowClass parent_class; +} MathVariablePopupClass; + +GType math_variable_popup_get_type(void); + +MathVariablePopup *math_variable_popup_new(MathEquation *equation); + +G_END_DECLS + +#endif /* MATH_VARIABLE_POPUP_H */ diff --git a/src/math-variables.c b/src/math-variables.c index a76261c..7e20cdc 100644 --- a/src/math-variables.c +++ b/src/math-variables.c @@ -1,31 +1,25 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include #include #include "math-variables.h" +#include "mp-serializer.h" struct MathVariablesPrivate { gchar *file_name; GHashTable *registers; + MpSerializer *serializer; }; G_DEFINE_TYPE (MathVariables, math_variables, G_TYPE_OBJECT); @@ -43,24 +37,24 @@ registers_load(MathVariables *variables) { FILE *f; char line[1024]; - + f = fopen(variables->priv->file_name, "r"); if (!f) return; - + g_hash_table_remove_all(variables->priv->registers); while (fgets(line, 1024, f) != NULL) { char *name, *value; MPNumber *t; - + value = strchr(line, '='); if (!value) continue; *value = '\0'; value = value + 1; - + name = g_strstrip(line); value = g_strstrip(value); @@ -89,16 +83,17 @@ registers_save(MathVariables *variables) f = fopen(variables->priv->file_name, "w"); if (!f) return; - + g_hash_table_iter_init(&iter, variables->priv->registers); while (g_hash_table_iter_next(&iter, &key, &val)) { gchar *name = key; MPNumber *value = val; - char number[1024]; + char *number; - mp_cast_to_string(value, 10, 10, 50, TRUE, number, 1024); + number = mp_serializer_to_string(variables->priv->serializer, value); fprintf(f, "%s=%s\n", name, number); + g_free(number); } fclose(f); } @@ -112,6 +107,8 @@ math_variables_get_names(MathVariables *variables) gpointer key; gint i = 0; gchar **names; + + g_return_val_if_fail(variables != NULL, NULL); names = g_malloc0(sizeof(gchar *) * (g_hash_table_size(variables->priv->registers) + 1)); @@ -129,9 +126,14 @@ math_variables_get_names(MathVariables *variables) void -math_variables_set_value(MathVariables *variables, const char *name, const MPNumber *value) +math_variables_set(MathVariables *variables, const char *name, const MPNumber *value) { MPNumber *t; + + g_return_if_fail(variables != NULL); + g_return_if_fail(name != NULL); + g_return_if_fail(value != NULL); + t = g_malloc(sizeof(MPNumber)); mp_set_from_mp(value, t); g_hash_table_insert(variables->priv->registers, g_strdup(name), t); @@ -140,12 +142,24 @@ math_variables_set_value(MathVariables *variables, const char *name, const MPNum MPNumber * -math_variables_get_value(MathVariables *variables, const char *name) +math_variables_get(MathVariables *variables, const char *name) { + g_return_val_if_fail(variables != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); return g_hash_table_lookup(variables->priv->registers, name); } +void +math_variables_delete(MathVariables *variables, const char *name) +{ + g_return_if_fail(variables != NULL); + g_return_if_fail(name != NULL); + g_hash_table_remove(variables->priv->registers, name); + registers_save(variables); +} + + static void math_variables_class_init (MathVariablesClass *klass) { @@ -159,5 +173,7 @@ math_variables_init(MathVariables *variables) variables->priv = G_TYPE_INSTANCE_GET_PRIVATE (variables, math_variables_get_type(), MathVariablesPrivate); variables->priv->registers = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); variables->priv->file_name = g_build_filename(g_get_user_data_dir(), "mate-calc", "registers", NULL); + variables->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_SCIENTIFIC, 10, 50); + mp_serializer_set_radix(variables->priv->serializer, '.'); registers_load(variables); } diff --git a/src/math-variables.h b/src/math-variables.h index e846ace..ade4134 100644 --- a/src/math-variables.h +++ b/src/math-variables.h @@ -1,19 +1,11 @@ -/* Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MATH_VARIABLES_H @@ -28,23 +20,29 @@ G_BEGIN_DECLS typedef struct MathVariablesPrivate MathVariablesPrivate; -typedef struct { - GObject parent_instance; - MathVariablesPrivate* priv; +typedef struct +{ + GObject parent_instance; + MathVariablesPrivate *priv; } MathVariables; -typedef struct { - GObjectClass parent_class; +typedef struct +{ + GObjectClass parent_class; } MathVariablesClass; GType math_variables_get_type(void); -MathVariables* math_variables_new(void); +MathVariables *math_variables_new(void); + +gchar **math_variables_get_names(MathVariables *variables); + +void math_variables_set(MathVariables *variables, const char *name, const MPNumber *value); -gchar** math_variables_get_names(MathVariables* variables); +MPNumber *math_variables_get(MathVariables *variables, const char *name); -void math_variables_set_value(MathVariables* variables, const char* name, const MPNumber* value); +void math_variables_delete(MathVariables *variables, const char *name); -MPNumber* math_variables_get_value(MathVariables* variables, const char* name); +G_END_DECLS #endif /* MATH_VARIABLES_H */ diff --git a/src/math-window.c b/src/math-window.c index 1caccab..4ecc5f7 100644 --- a/src/math-window.c +++ b/src/math-window.c @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -22,472 +14,234 @@ #include #include "math-window.h" -#include "math-preferences.h" enum { PROP_0, PROP_EQUATION }; -struct MathWindowPrivate { - GtkWidget* menu_bar; - MathEquation* equation; - MathDisplay* display; - MathButtons* buttons; - MathPreferencesDialog* preferences_dialog; +struct MathWindowPrivate +{ + MathEquation *equation; + MathDisplay *display; + MathButtons *buttons; gboolean right_aligned; - GtkWidget* mode_basic_menu_item; - GtkWidget* mode_advanced_menu_item; - GtkWidget* mode_financial_menu_item; - GtkWidget* mode_programming_menu_item; -}; - -G_DEFINE_TYPE(MathWindow, math_window, GTK_TYPE_WINDOW); - -enum { - QUIT, - LAST_SIGNAL }; -static guint signals[LAST_SIGNAL] = {0, }; +G_DEFINE_TYPE (MathWindow, math_window, GTK_TYPE_APPLICATION_WINDOW); -MathWindow* math_window_new(MathEquation* equation) +MathWindow * +math_window_new(GtkApplication *app, MathEquation *equation) { - return g_object_new(math_window_get_type(), "equation", equation, NULL); + return g_object_new(math_window_get_type(), + "application", app, + "equation", equation, NULL); } -GtkWidget* math_window_get_menu_bar(MathWindow* window) -{ - return window->priv->menu_bar; -} - - -MathEquation* math_window_get_equation(MathWindow* window) +MathEquation * +math_window_get_equation(MathWindow *window) { + g_return_val_if_fail(window != NULL, NULL); return window->priv->equation; } -MathDisplay* math_window_get_display(MathWindow* window) +MathDisplay * +math_window_get_display(MathWindow *window) { + g_return_val_if_fail(window != NULL, NULL); return window->priv->display; } -MathButtons* math_window_get_buttons(MathWindow* window) +MathButtons * +math_window_get_buttons(MathWindow *window) { + g_return_val_if_fail(window != NULL, NULL); return window->priv->buttons; } -void math_window_critical_error(MathWindow* window, const gchar* title, const gchar* contents) +void +math_window_critical_error(MathWindow *window, const gchar *title, const gchar *contents) { - GtkWidget* dialog; + GtkWidget *dialog; - dialog = gtk_message_dialog_new(NULL, 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_NONE, "%s", title); - gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), "%s", contents); + g_return_if_fail(window != NULL); + g_return_if_fail(title != NULL); + g_return_if_fail(contents != NULL); + + dialog = gtk_message_dialog_new(NULL, 0, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_NONE, + "%s", title); + gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog), + "%s", contents); gtk_dialog_add_buttons(GTK_DIALOG(dialog), GTK_STOCK_QUIT, GTK_RESPONSE_ACCEPT, NULL); gtk_dialog_run(GTK_DIALOG(dialog)); - g_signal_emit(window, signals[QUIT], 0); -} - - -static void copy_cb(GtkWidget* widget, MathWindow* window) -{ - math_equation_copy(window->priv->equation); -} - - -static void paste_cb(GtkWidget* widget, MathWindow* window) -{ - math_equation_paste(window->priv->equation); -} - - -static void undo_cb(GtkWidget* widget, MathWindow* window) -{ - math_equation_undo(window->priv->equation); -} - - -static void redo_cb(GtkWidget* widget, MathWindow* window) -{ - math_equation_redo(window->priv->equation); -} - - -static void mode_changed_cb(GtkWidget* menu, MathWindow* window) -{ - int mode; - - if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu))) - { - return; - } - - mode = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(menu), "calcmode")); - math_buttons_set_mode(window->priv->buttons, mode); -} - - -static void show_preferences_cb(GtkMenuItem* menu, MathWindow* window) -{ - if (!window->priv->preferences_dialog) - { - window->priv->preferences_dialog = math_preferences_dialog_new(window->priv->equation); - gtk_window_set_transient_for(GTK_WINDOW(window->priv->preferences_dialog), GTK_WINDOW(window)); - } - - gtk_window_present(GTK_WINDOW(window->priv->preferences_dialog)); -} - - -static void help_cb(GtkWidget* widget, MathWindow* window) -{ - GdkScreen* screen; - GError* error = NULL; - - screen = gtk_widget_get_screen(GTK_WIDGET(window)); - gtk_show_uri(screen, "ghelp:mate-calc", gtk_get_current_event_time(), &error); - - if (error != NULL) - { - GtkWidget* d; - /* Translators: Error message displayed when unable to launch help browser */ - const char* message = _("Unable to open help file"); - - d = gtk_message_dialog_new(GTK_WINDOW(window), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message); - gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(d), "%s", error->message); - g_signal_connect(d, "response", G_CALLBACK(gtk_widget_destroy), NULL); - gtk_window_present(GTK_WINDOW (d)); - - g_error_free(error); - } -} - - -static void about_cb(GtkWidget* widget, MathWindow* window) -{ - char* authors[] = { - "Rich Burridge ", - "Robert Ancell ", - "Klaus Niederkrüger ", - NULL - }; - - char* documenters[] = { - "Sun Microsystems", - NULL - }; - - /* The translator credits. Please translate this with your name(s). */ - char* translator_credits = _("translator-credits"); - - char copyright[] = \ - "Copyright \xc2\xa9 1986–2010 The GCalctool authors\n" - "Copyright \xc2\xa9 2011-2012 MATE developers"; - - /* The license this software is under (GPL2+) */ - char* license = _("mate-calc is free software; you can redistribute it and/or modify\n" - "it under the terms of the GNU General Public License as published by\n" - "the Free Software Foundation; either version 2 of the License, or\n" - "(at your option) any later version.\n" - "\n" - "mate-calc is distributed in the hope that it will be useful,\n" - "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" - "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" - "GNU General Public License for more details.\n" - "\n" - "You should have received a copy of the GNU General Public License\n" - "along with mate-calc; if not, write to the Free Software Foundation, Inc.,\n" - "51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA"); - - gtk_show_about_dialog(GTK_WINDOW(window), - "name", _("mate-calc"), - "version", VERSION, - "copyright", copyright, - "license", license, - "comments", _("Calculator with financial and scientific modes."), - "authors", authors, - "documenters", documenters, - "translator_credits", translator_credits, - "logo-icon-name", "accessories-calculator", - "website", "http://mate-desktop.org", - NULL); + gtk_widget_destroy(GTK_WIDGET(window)); } -static void quit_cb(GtkWidget* widget, MathWindow* window) -{ - g_signal_emit(window, signals[QUIT], 0); -} - - -static gboolean key_press_cb(MathWindow* window, GdkEventKey* event) +static gboolean +key_press_cb(MathWindow *window, GdkEventKey *event) { gboolean result; - g_signal_emit_by_name(window->priv->display, "key-press-event", event, &result); - return result; -} - + if (math_buttons_get_mode (window->priv->buttons) == PROGRAMMING && (event->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) { + switch(event->keyval) + { + /* Binary */ + case GDK_KEY_b: + math_equation_set_base (window->priv->equation, 2); + return TRUE; + /* Octal */ + case GDK_KEY_o: + math_equation_set_base (window->priv->equation, 8); + return TRUE; + /* Decimal */ + case GDK_KEY_d: + math_equation_set_base (window->priv->equation, 10); + return TRUE; + /* Hexdecimal */ + case GDK_KEY_h: + math_equation_set_base (window->priv->equation, 16); + return TRUE; + } + } -static void delete_cb(MathWindow* window, GdkEvent* event) -{ - g_signal_emit(window, signals[QUIT], 0); + return result; } -static void scroll_changed_cb(GtkAdjustment* adjustment, MathWindow* window) +static void +scroll_changed_cb(GtkAdjustment *adjustment, MathWindow *window) { if (window->priv->right_aligned) - { gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment)); - } } -static void scroll_value_changed_cb(GtkAdjustment* adjustment, MathWindow* window) +static void +scroll_value_changed_cb(GtkAdjustment *adjustment, MathWindow *window) { if (gtk_adjustment_get_value(adjustment) == gtk_adjustment_get_upper(adjustment) - gtk_adjustment_get_page_size(adjustment)) - { window->priv->right_aligned = TRUE; - } else - { window->priv->right_aligned = FALSE; - } -} - - -static void button_mode_changed_cb(MathButtons* buttons, GParamSpec* spec, MathWindow* window) -{ - GtkWidget* menu; - - switch(math_buttons_get_mode(buttons)) - { - default: - case BASIC: - menu = window->priv->mode_basic_menu_item; - //FIXME: Should it revert to decimal mode? math_equation_set_number_format(window->priv->equation, DEC); - break; - - case ADVANCED: - menu = window->priv->mode_advanced_menu_item; - break; - - case FINANCIAL: - menu = window->priv->mode_financial_menu_item; - break; - - case PROGRAMMING: - menu = window->priv->mode_programming_menu_item; - break; - } - - gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu), TRUE); -} - - -static GtkWidget* add_menu(GtkWidget* menu_bar, const gchar* name) -{ - GtkWidget* menu_item; - GtkWidget* menu; - - menu_item = gtk_menu_item_new_with_mnemonic(name); - gtk_menu_shell_append(GTK_MENU_SHELL(menu_bar), menu_item); - gtk_widget_show(menu_item); - menu = gtk_menu_new(); - gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu); - - return menu; } -static GtkWidget* add_menu_item(GtkWidget* menu, GtkWidget* menu_item, GCallback callback, gpointer callback_data) +static void +create_gui(MathWindow *window) { - gtk_menu_shell_append(GTK_MENU_SHELL(menu), menu_item); - gtk_widget_show(menu_item); - - if (callback) - { - g_signal_connect(G_OBJECT(menu_item), "activate", callback, callback_data); - } - - return menu_item; -} - - -static GtkWidget* radio_menu_item_new(GSList** group, const gchar* name) -{ - GtkWidget* menu_item = gtk_radio_menu_item_new_with_mnemonic(*group, name); - - *group = gtk_radio_menu_item_get_group(GTK_RADIO_MENU_ITEM(menu_item)); - - return menu_item; -} - - -static void create_menu(MathWindow* window) -{ - GtkAccelGroup* accel_group; - GtkWidget* menu; - GtkWidget* menu_item; - GSList* group = NULL; - - accel_group = gtk_accel_group_new(); - gtk_window_add_accel_group(GTK_WINDOW(window), accel_group); - - /* Calculator menu */ - #define CALCULATOR_MENU_LABEL _("_Calculator") - /* Mode menu */ - #define MODE_MENU_LABEL _("_Mode") - /* Help menu label */ - #define HELP_MENU_LABEL _("_Help") - /* Basic menu label */ - #define MODE_BASIC_LABEL _("_Basic") - /* Advanced menu label */ - #define MODE_ADVANCED_LABEL _("_Advanced") - /* Financial menu label */ - #define MODE_FINANCIAL_LABEL _("_Financial") - /* Programming menu label */ - #define MODE_PROGRAMMING_LABEL _("_Programming") - /* Help>Contents menu label */ - #define HELP_CONTENTS_LABEL _("_Contents") - - menu = add_menu(window->priv->menu_bar, CALCULATOR_MENU_LABEL); - add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_COPY, accel_group), G_CALLBACK(copy_cb), window); - add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_PASTE, accel_group), G_CALLBACK(paste_cb), window); - menu_item = add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_UNDO, accel_group), G_CALLBACK(undo_cb), window); - gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_z, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); - menu_item = add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_REDO, accel_group), G_CALLBACK(redo_cb), window); - gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_z, GDK_CONTROL_MASK | GDK_SHIFT_MASK, GTK_ACCEL_VISIBLE); - add_menu_item(menu, gtk_separator_menu_item_new(), NULL, NULL); - add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_PREFERENCES, accel_group), G_CALLBACK(show_preferences_cb), window); - add_menu_item(menu, gtk_separator_menu_item_new(), NULL, NULL); - menu_item = add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group), G_CALLBACK(quit_cb), window); - gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_w, GDK_CONTROL_MASK, 0); - - menu = add_menu(window->priv->menu_bar, MODE_MENU_LABEL); - window->priv->mode_basic_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_BASIC_LABEL), G_CALLBACK(mode_changed_cb), window); - g_object_set_data(G_OBJECT(window->priv->mode_basic_menu_item), "calcmode", GINT_TO_POINTER(BASIC)); - window->priv->mode_advanced_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_ADVANCED_LABEL), G_CALLBACK(mode_changed_cb), window); - g_object_set_data(G_OBJECT(window->priv->mode_advanced_menu_item), "calcmode", GINT_TO_POINTER(ADVANCED)); - window->priv->mode_financial_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_FINANCIAL_LABEL), G_CALLBACK(mode_changed_cb), window); - g_object_set_data(G_OBJECT(window->priv->mode_financial_menu_item), "calcmode", GINT_TO_POINTER(FINANCIAL)); - window->priv->mode_programming_menu_item = add_menu_item(menu, radio_menu_item_new(&group, MODE_PROGRAMMING_LABEL), G_CALLBACK(mode_changed_cb), window); - g_object_set_data(G_OBJECT(window->priv->mode_programming_menu_item), "calcmode", GINT_TO_POINTER(PROGRAMMING)); - - menu = add_menu(window->priv->menu_bar, HELP_MENU_LABEL); - menu_item = add_menu_item(menu, gtk_menu_item_new_with_mnemonic(HELP_CONTENTS_LABEL), G_CALLBACK(help_cb), window); - gtk_widget_add_accelerator(menu_item, "activate", accel_group, GDK_F1, 0, GTK_ACCEL_VISIBLE); - add_menu_item(menu, gtk_image_menu_item_new_from_stock(GTK_STOCK_ABOUT, accel_group), G_CALLBACK(about_cb), window); -} - - -static void create_gui(MathWindow* window) -{ - GtkWidget* main_vbox; - GtkWidget* vbox; - GtkWidget* scrolled_window; - - main_vbox = gtk_vbox_new(FALSE, 0); + GtkWidget *main_vbox, *vbox; + GtkWidget *scrolled_window; + + main_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); gtk_container_add(GTK_CONTAINER(window), main_vbox); gtk_widget_show(main_vbox); - window->priv->menu_bar = gtk_menu_bar_new(); - gtk_box_pack_start(GTK_BOX(main_vbox), window->priv->menu_bar, TRUE, TRUE, 0); - gtk_widget_show(window->priv->menu_bar); - - create_menu(window); - - vbox = gtk_vbox_new(FALSE, 6); + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6); gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); - gtk_box_pack_start(GTK_BOX(main_vbox), vbox, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(main_vbox), vbox, TRUE, TRUE, 0); gtk_widget_show(vbox); scrolled_window = gtk_scrolled_window_new(NULL, NULL); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER); gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled_window), GTK_SHADOW_IN); - gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrolled_window), TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(scrolled_window), FALSE, FALSE, 0); g_signal_connect(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window)), "changed", G_CALLBACK(scroll_changed_cb), window); g_signal_connect(gtk_scrolled_window_get_hadjustment(GTK_SCROLLED_WINDOW(scrolled_window)), "value-changed", G_CALLBACK(scroll_value_changed_cb), window); window->priv->right_aligned = TRUE; gtk_widget_show(scrolled_window); window->priv->display = math_display_new_with_equation(window->priv->equation); - gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), GTK_WIDGET(window->priv->display)); + gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(window->priv->display)); gtk_widget_show(GTK_WIDGET(window->priv->display)); window->priv->buttons = math_buttons_new(window->priv->equation); - g_signal_connect(window->priv->buttons, "notify::mode", G_CALLBACK(button_mode_changed_cb), window); - button_mode_changed_cb(window->priv->buttons, NULL, window); gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(window->priv->buttons), TRUE, TRUE, 0); gtk_widget_show(GTK_WIDGET(window->priv->buttons)); } -static void math_window_set_property(GObject* object, guint prop_id, const GValue* value, GParamSpec* pspec) +static void +math_window_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) { - MathWindow* self = MATH_WINDOW(object); - - switch (prop_id) - { - case PROP_EQUATION: - self->priv->equation = g_value_get_object(value); - create_gui(self); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); - break; + MathWindow *self; + + self = MATH_WINDOW(object); + + switch (prop_id) { + case PROP_EQUATION: + self->priv->equation = g_value_get_object(value); + create_gui(self); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; } } -static void math_window_get_property(GObject* object, guint prop_id, GValue* value, GParamSpec* pspec) +static void +math_window_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - MathWindow* self = MATH_WINDOW(object); - - switch (prop_id) - { - case PROP_EQUATION: - g_value_set_object (value, self->priv->equation); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; + MathWindow *self; + + self = MATH_WINDOW(object); + + switch (prop_id) { + case PROP_EQUATION: + g_value_set_object(value, self->priv->equation); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; } } -static void math_window_class_init(MathWindowClass* klass) +static void +math_window_class_init(MathWindowClass *klass) { - GObjectClass* object_class = G_OBJECT_CLASS(klass); + GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->get_property = math_window_get_property; object_class->set_property = math_window_set_property; g_type_class_add_private(klass, sizeof(MathWindowPrivate)); - g_object_class_install_property(object_class, PROP_EQUATION, g_param_spec_object("equation", "equation", "Equation being calculated", math_equation_get_type(), G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); - - signals[QUIT] = g_signal_new("quit", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(MathWindowClass, quit), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + g_object_class_install_property(object_class, + PROP_EQUATION, + g_param_spec_object("equation", + "equation", + "Equation being calculated", + math_equation_get_type(), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } -static void math_window_init(MathWindow* window) +static void +math_window_init(MathWindow *window) { window->priv = G_TYPE_INSTANCE_GET_PRIVATE(window, math_window_get_type(), MathWindowPrivate); - - gtk_window_set_title(GTK_WINDOW(window), _("Calculator")); /* Title of main window */ - gtk_window_set_icon_name(GTK_WINDOW(window), "accessories-calculator"); + gtk_window_set_title(GTK_WINDOW(window), + /* Title of main window */ + _("Calculator")); gtk_window_set_role(GTK_WINDOW(window), "mate-calc"); gtk_window_set_resizable(GTK_WINDOW(window), FALSE); g_signal_connect_after(G_OBJECT(window), "key-press-event", G_CALLBACK(key_press_cb), NULL); - g_signal_connect(G_OBJECT(window), "delete-event", G_CALLBACK(delete_cb), NULL); } diff --git a/src/math-window.h b/src/math-window.h index 463b078..22360fa 100644 --- a/src/math-window.h +++ b/src/math-window.h @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MATH_WINDOW_H @@ -31,29 +23,31 @@ G_BEGIN_DECLS typedef struct MathWindowPrivate MathWindowPrivate; -typedef struct { - GtkWindow parent_instance; - MathWindowPrivate* priv; +typedef struct +{ + GtkApplicationWindow parent_instance; + MathWindowPrivate *priv; } MathWindow; -typedef struct { - GtkWindowClass parent_class; +typedef struct +{ + GtkApplicationWindowClass parent_class; - void (*quit) (MathWindow* window); + void (*quit)(MathWindow *window); } MathWindowClass; GType math_window_get_type(void); -MathWindow* math_window_new(MathEquation* equation); +MathWindow *math_window_new(GtkApplication *app, MathEquation *equation); -GtkWidget* math_window_get_menu_bar(MathWindow* window); +MathEquation *math_window_get_equation(MathWindow *window); -MathEquation* math_window_get_equation(MathWindow* window); +MathDisplay *math_window_get_display(MathWindow *window); -MathDisplay* math_window_get_display(MathWindow* window); +MathButtons *math_window_get_buttons(MathWindow *window); -MathButtons* math_window_get_buttons(MathWindow* window); +void math_window_critical_error(MathWindow *window, const gchar *title, const gchar *contents); -void math_window_critical_error(MathWindow* window, const gchar* title, const gchar* contents); +G_END_DECLS #endif /* MATH_WINDOW_H */ diff --git a/src/mp-binary.c b/src/mp-binary.c index fd78c78..4c8eafb 100644 --- a/src/mp-binary.c +++ b/src/mp-binary.c @@ -1,26 +1,19 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include #include "mp.h" #include "mp-private.h" +#include "mp-serializer.h" // FIXME: Make dynamic #define MAX_DIGITS 1000 @@ -39,14 +32,28 @@ static int hex_to_int(char digit) } +static gchar * +to_hex_string(const MPNumber *x) +{ + MpSerializer *serializer; + gchar *result; + + serializer = mp_serializer_new(MP_DISPLAY_FORMAT_FIXED, 16, 0); + result = mp_serializer_to_string(serializer, x); + g_object_unref(serializer); + + return result; +} + + static void mp_bitwise(const MPNumber *x, const MPNumber *y, int (*bitwise_operator)(int, int), MPNumber *z, int wordlen) { - char text1[MAX_DIGITS], text2[MAX_DIGITS], text_out[MAX_DIGITS], text_out2[MAX_DIGITS]; + char *text1, *text2, text_out[MAX_DIGITS], text_out2[MAX_DIGITS]; int offset1, offset2, offset_out; - mp_cast_to_string(x, 16, 16, 0, 0, text1, MAX_DIGITS); - mp_cast_to_string(y, 16, 16, 0, 0, text2, MAX_DIGITS); + text1 = to_hex_string(x); + text2 = to_hex_string(y); offset1 = strlen(text1) - 1; offset2 = strlen(text2) - 1; offset_out = wordlen / 4 - 1; @@ -54,6 +61,9 @@ mp_bitwise(const MPNumber *x, const MPNumber *y, int (*bitwise_operator)(int, in offset_out = offset1 > offset2 ? offset1 : offset2; } if (offset_out > 0 && (offset_out < offset1 || offset_out < offset2)) { + g_free(text1); + g_free(text2); + mp_set_from_integer(0, z); mperr("Overflow. Try a bigger word size"); return; } @@ -75,6 +85,8 @@ mp_bitwise(const MPNumber *x, const MPNumber *y, int (*bitwise_operator)(int, in snprintf(text_out2, MAX_DIGITS, "%s", text_out); mp_set_from_string(text_out2, 16, z); + g_free(text1); + g_free(text2); } @@ -152,24 +164,23 @@ mp_not(const MPNumber *x, int wordlen, MPNumber *z) void mp_mask(const MPNumber *x, int wordlen, MPNumber *z) { - char text[MAX_DIGITS]; + char *text; size_t len, offset; /* Convert to a hexadecimal string and use last characters */ - mp_cast_to_string(x, 16, 16, 0, 0, text, MAX_DIGITS); + text = to_hex_string(x); len = strlen(text); offset = wordlen / 4; offset = len > offset ? len - offset: 0; mp_set_from_string(text + offset, 16, z); + g_free(text); } void mp_shift(const MPNumber *x, int count, MPNumber *z) { - int i; - MPNumber multiplier; - mp_set_from_integer(1, &multiplier); + int i, multiplier = 1; if (!mp_is_integer(x)) { /* Translators: Error displayed when bit shift attempted on non-integer values */ @@ -179,14 +190,15 @@ mp_shift(const MPNumber *x, int count, MPNumber *z) if (count >= 0) { for (i = 0; i < count; i++) - mp_multiply_integer(&multiplier, 2, &multiplier); - mp_multiply(x, &multiplier, z); + multiplier *= 2; + mp_multiply_integer(x, multiplier, z); } else { + MPNumber temp; for (i = 0; i < -count; i++) - mp_multiply_integer(&multiplier, 2, &multiplier); - mp_divide(x, &multiplier, z); - mp_floor(z, z); + multiplier *= 2; + mp_divide_integer(x, multiplier, &temp); + mp_floor(&temp, z); } } diff --git a/src/mp-convert.c b/src/mp-convert.c index 7ff2fca..1052351 100644 --- a/src/mp-convert.c +++ b/src/mp-convert.c @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -22,6 +14,7 @@ #include #include #include +#include #include "mp.h" #include "mp-private.h" @@ -295,7 +288,7 @@ mp_cast_to_int(const MPNumber *x) { int i; int64_t z = 0, v; - + /* |x| <= 1 */ if (x->sign == 0 || x->exponent <= 0) return 0; @@ -306,7 +299,7 @@ mp_cast_to_int(const MPNumber *x) t = z; z = z * MP_BASE + x->fraction[i]; - + /* Check for overflow */ if (z <= t) return 0; @@ -336,7 +329,7 @@ mp_cast_to_unsigned_int(const MPNumber *x) { int i; uint64_t z = 0, v; - + /* x <= 1 */ if (x->sign <= 0 || x->exponent <= 0) return 0; @@ -347,7 +340,7 @@ mp_cast_to_unsigned_int(const MPNumber *x) t = z; z = z * MP_BASE + x->fraction[i]; - + /* Check for overflow */ if (z <= t) return 0; @@ -506,221 +499,12 @@ mp_cast_to_double(const MPNumber *x) } } - -static void -mp_cast_to_string_real(const MPNumber *x, int default_base, int base, int accuracy, bool trim_zeroes, bool force_sign, GString *string) -{ - static char digits[] = "0123456789ABCDEF"; - MPNumber number, integer_component, fractional_component, temp; - int i, last_non_zero; - - if (mp_is_negative(x)) - mp_abs(x, &number); - else - mp_set_from_mp(x, &number); - - /* Add rounding factor */ - mp_set_from_integer(base, &temp); - mp_xpowy_integer(&temp, -(accuracy+1), &temp); - mp_multiply_integer(&temp, base, &temp); - mp_divide_integer(&temp, 2, &temp); - mp_add(&number, &temp, &number); - - /* Split into integer and fractional component */ - mp_floor(&number, &integer_component); - mp_fractional_component(&number, &fractional_component); - - /* Write out the integer component least significant digit to most */ - mp_set_from_mp(&integer_component, &temp); - do { - MPNumber t, t2, t3; - int64_t d; - - mp_divide_integer(&temp, base, &t); - mp_floor(&t, &t); - mp_multiply_integer(&t, base, &t2); - - mp_subtract(&temp, &t2, &t3); - - d = mp_cast_to_int(&t3); - g_string_prepend_c(string, d < 16 ? digits[d] : '?'); - - mp_set_from_mp(&t, &temp); - } while (!mp_is_zero(&temp)); - - last_non_zero = string->len; - g_string_append_c(string, '.'); - - /* Write out the fractional component */ - mp_set_from_mp(&fractional_component, &temp); - for (i = accuracy; i > 0 && !mp_is_zero(&temp); i--) { - int d; - MPNumber digit; - - mp_multiply_integer(&temp, base, &temp); - mp_floor(&temp, &digit); - d = mp_cast_to_int(&digit); - - g_string_append_c(string, digits[d]); - - if(d != 0) - last_non_zero = string->len; - mp_subtract(&temp, &digit, &temp); - } - - /* Strip trailing zeroes */ - if (trim_zeroes || accuracy == 0) - g_string_truncate(string, last_non_zero); - - /* Add sign on non-zero values */ - if (strcmp(string->str, "0") != 0 || force_sign) { - if (mp_is_negative(x)) - g_string_prepend(string, "−"); - else if (force_sign) - g_string_prepend(string, "+"); - } - - /* Append base suffix if not in default base */ - if (base != default_base) { - const char *digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"}; - int multiplier = 1; - int b = base; - - while (base / multiplier != 0) - multiplier *= 10; - while (multiplier != 1) { - int d; - multiplier /= 10; - d = b / multiplier; - g_string_append(string, digits[d]); - b -= d * multiplier; - } - } -} - - -void -mp_cast_to_string(const MPNumber *x, int default_base, int base, int accuracy, bool trim_zeroes, char *buffer, int buffer_length) -{ - GString *string; - MPNumber x_real; - - string = g_string_sized_new(buffer_length); - - mp_real_component(x, &x_real); - mp_cast_to_string_real(&x_real, default_base, base, accuracy, trim_zeroes, FALSE, string); - if (mp_is_complex(x)) { - GString *s; - gboolean force_sign = TRUE; - MPNumber x_im; - - mp_imaginary_component(x, &x_im); - - if (strcmp(string->str, "0") == 0) { - g_string_assign(string, ""); - force_sign = false; - } - - s = g_string_sized_new(buffer_length); - mp_cast_to_string_real(&x_im, default_base, 10, accuracy, trim_zeroes, force_sign, s); - if (strcmp(s->str, "0") == 0 || strcmp(s->str, "+0") == 0 || strcmp(s->str, "−0") == 0) { - /* Ignore */ - } - else if (strcmp(s->str, "1") == 0) { - g_string_append(string, "i"); - } - else if (strcmp(s->str, "+1") == 0) { - g_string_append(string, "+i"); - } - else if (strcmp(s->str, "−1") == 0) { - g_string_append(string, "−i"); - } - else { - if (strcmp(s->str, "+0") == 0) - g_string_append(string, "+"); - else if (strcmp(s->str, "0") != 0) - g_string_append(string, s->str); - - g_string_append(string, "i"); - } - g_string_free(s, TRUE); - } - - // FIXME: Check for truncation - strncpy(buffer, string->str, buffer_length); - g_string_free(string, TRUE); -} - - -void -mp_cast_to_exponential_string(const MPNumber *x, int default_base, int base_, int max_digits, bool trim_zeroes, bool eng_format, char *buffer, int buffer_length) -{ - char fixed[1024], *c; - MPNumber t, z, base, base3, base10, base10inv, mantissa; - int exponent = 0; - GString *string; - const char *super_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"}; - - string = g_string_sized_new(buffer_length); - - mp_abs(x, &z); - if (mp_is_negative(x)) - g_string_append(string, "−"); - mp_set_from_mp(&z, &mantissa); - - mp_set_from_integer(base_, &base); - mp_xpowy_integer(&base, 3, &base3); - mp_xpowy_integer(&base, 10, &base10); - mp_set_from_integer(1, &t); - mp_divide(&t, &base10, &base10inv); - - if (!mp_is_zero(&mantissa)) { - while (!eng_format && mp_is_greater_equal(&mantissa, &base10)) { - exponent += 10; - mp_multiply(&mantissa, &base10inv, &mantissa); - } - - while ((!eng_format && mp_is_greater_equal(&mantissa, &base)) || - (eng_format && (mp_is_greater_equal(&mantissa, &base3) || exponent % 3 != 0))) { - exponent += 1; - mp_divide(&mantissa, &base, &mantissa); - } - - while (!eng_format && mp_is_less_than(&mantissa, &base10inv)) { - exponent -= 10; - mp_multiply(&mantissa, &base10, &mantissa); - } - - mp_set_from_integer(1, &t); - while (mp_is_less_than(&mantissa, &t) || (eng_format && exponent % 3 != 0)) { - exponent -= 1; - mp_multiply(&mantissa, &base, &mantissa); - } - } - - mp_cast_to_string(&mantissa, default_base, base_, max_digits, trim_zeroes, fixed, 1024); - g_string_append(string, fixed); - if (exponent != 0) { - g_string_append_printf(string, "×10"); // FIXME: Use the current base - if (exponent < 0) { - exponent = -exponent; - g_string_append(string, "⁻"); - } - snprintf(fixed, 1024, "%d", exponent); - for (c = fixed; *c; c++) - g_string_append(string, super_digits[*c - '0']); - } - - strncpy(buffer, string->str, buffer_length); - g_string_free(string, TRUE); -} - - static int char_val(char **c, int base) { int i, j, value, offset; const char *digits[][10] = {{"٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"}, + {"〇", "〡", "〢", "〣", "〤", "〥", "〦", "〧", "〨", "〩"}, {"۰", "۱", "۲", "۳", "۴", "۵", "۶", "۷", "۸", "۹"}, {"߀", "߁", "߂", "߃", "߄", "߅", "߆", "߇", "߈", "߉"}, {"०", "१", "२", "३", "४", "५", "६", "७", "८", "९"}, @@ -895,7 +679,7 @@ mp_set_from_string(const char *str, int default_base, MPNumber *z) mp_add(z, &fraction, z); } - if (*c == '.' || *c == ',') { + if (*c == '.') { has_fraction = TRUE; c++; } diff --git a/src/mp-enums.c.template b/src/mp-enums.c.template new file mode 100644 index 0000000..d10ea75 --- /dev/null +++ b/src/mp-enums.c.template @@ -0,0 +1,36 @@ +/*** BEGIN file-header ***/ +#include "mp-serializer.h" +#include "mp-enums.h" + +/*** END file-header ***/ + +/*** BEGIN file-production ***/ +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType +math_@enum_name@_get_type (void) +{ + static GType etype = 0; + if (G_UNLIKELY(etype == 0)) { + static const G@Type@Value values[] = { +/*** END value-header ***/ + +/*** BEGIN value-production ***/ + { @VALUENAME@, "@VALUENAME@", "@valuenick@" }, +/*** END value-production ***/ + +/*** BEGIN value-tail ***/ + { 0, NULL, NULL } + }; + etype = g_@type@_register_static (g_intern_static_string ("@EnumName@"), values); + } + return etype; +} + +/*** END value-tail ***/ + +/*** BEGIN file-tail ***/ + +/*** END file-tail ***/ diff --git a/src/mp-enums.h.template b/src/mp-enums.h.template new file mode 100644 index 0000000..f192bf8 --- /dev/null +++ b/src/mp-enums.h.template @@ -0,0 +1,25 @@ +/*** BEGIN file-header ***/ + +#ifndef __MATH_ENUMS_H__ +#define __MATH_ENUMS_H__ + +#include + +G_BEGIN_DECLS +/*** END file-header ***/ + +/*** BEGIN file-production ***/ + +/* enumerations from "@filename@" */ +/*** END file-production ***/ + +/*** BEGIN value-header ***/ +GType math_@enum_name@_get_type (void) G_GNUC_CONST; +#define @ENUMPREFIX@_TYPE_@ENUMSHORT@ (math_@enum_name@_get_type ()) +/*** END value-header ***/ + +/*** BEGIN file-tail ***/ +G_END_DECLS + +#endif /* __MATH_ENUMS_H__ */ +/*** END file-tail ***/ diff --git a/src/mp-equation.c b/src/mp-equation.c index cec5536..9b6a195 100644 --- a/src/mp-equation.c +++ b/src/mp-equation.c @@ -1,33 +1,21 @@ -/* Copyright (c) 2004-2008 Sami Pietila - * Copyright (c) 2008-2009 Robert Ancell - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. +/* + * Copyright (C) 2004-2008 Sami Pietila + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include - -#include "mp-equation-private.h" -#include "mp-equation-parser.h" -#include "mp-equation-lexer.h" - -extern int _mp_equation_parse(yyscan_t yyscanner); - +#include +#include +#include "parser.h" static int -variable_is_defined(MPEquationParserState *state, const char *name) +variable_is_defined(ParserState *state, const char *name) { /* FIXME: Make more generic */ if (strcmp(name, "e") == 0 || strcmp(name, "i") == 0 || strcmp(name, "π") == 0) @@ -39,7 +27,7 @@ variable_is_defined(MPEquationParserState *state, const char *name) static int -get_variable(MPEquationParserState *state, const char *name, MPNumber *z) +get_variable(ParserState *state, const char *name, MPNumber *z) { int result = 1; @@ -58,7 +46,7 @@ get_variable(MPEquationParserState *state, const char *name, MPNumber *z) } static void -set_variable(MPEquationParserState *state, const char *name, const MPNumber *x) +set_variable(ParserState *state, const char *name, const MPNumber *x) { // Reserved words, e, π, mod, and, or, xor, not, abs, log, ln, sqrt, int, frac, sin, cos, ... if (strcmp(name, "e") == 0 || strcmp(name, "i") == 0 || strcmp(name, "π") == 0) @@ -115,7 +103,7 @@ super_atoi(const char *data) static int -function_is_defined(MPEquationParserState *state, const char *name) +function_is_defined(ParserState *state, const char *name) { char *c, *lower_name; @@ -140,8 +128,8 @@ function_is_defined(MPEquationParserState *state, const char *name) strcmp(lower_name, "re") == 0 || strcmp(lower_name, "im") == 0 || strcmp(lower_name, "sin") == 0 || strcmp(lower_name, "cos") == 0 || strcmp(lower_name, "tan") == 0 || - strcmp(lower_name, "sin⁻¹") == 0 || strcmp(lower_name, "cos⁻¹") == 0 || strcmp(lower_name, "tan⁻¹") == 0 || strcmp(lower_name, "asin") == 0 || strcmp(lower_name, "acos") == 0 || strcmp(lower_name, "atan") == 0 || + strcmp(lower_name, "sin⁻¹") == 0 || strcmp(lower_name, "cos⁻¹") == 0 || strcmp(lower_name, "tan⁻¹") == 0 || strcmp(lower_name, "sinh") == 0 || strcmp(lower_name, "cosh") == 0 || strcmp(lower_name, "tanh") == 0 || strcmp(lower_name, "sinh⁻¹") == 0 || strcmp(lower_name, "cosh⁻¹") == 0 || strcmp(lower_name, "tanh⁻¹") == 0 || strcmp(lower_name, "asinh") == 0 || strcmp(lower_name, "acosh") == 0 || strcmp(lower_name, "atanh") == 0 || @@ -159,7 +147,7 @@ function_is_defined(MPEquationParserState *state, const char *name) static int -get_function(MPEquationParserState *state, const char *name, const MPNumber *x, MPNumber *z) +get_function(ParserState *state, const char *name, const MPNumber *x, MPNumber *z) { char *c, *lower_name; int result = 1; @@ -247,168 +235,12 @@ get_function(MPEquationParserState *state, const char *name, const MPNumber *x, static int -do_convert(const char *units[][2], const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z) -{ - int x_index, z_index; - MPNumber x_factor, z_factor; - - for (x_index = 0; units[x_index][0] != NULL && strcmp(units[x_index][0], x_units) != 0; x_index++); - if (units[x_index][0] == NULL) - return 0; - for (z_index = 0; units[z_index][0] != NULL && strcmp(units[z_index][0], z_units) != 0; z_index++); - if (units[z_index][0] == NULL) - return 0; - - mp_set_from_string(units[x_index][1], 10, &x_factor); - mp_set_from_string(units[z_index][1], 10, &z_factor); - mp_multiply(x, &x_factor, z); - mp_divide(z, &z_factor, z); - - return 1; -} - - -static int -convert(MPEquationParserState *state, const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z) +convert(ParserState *state, const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z) { - const char *length_units[][2] = { - {"parsec", "30857000000000000"}, - {"parsecs", "30857000000000000"}, - {"pc", "30857000000000000"}, - {"lightyear", "9460730472580800"}, - {"lightyears", "9460730472580800"}, - {"ly", "9460730472580800"}, - {"au", "149597870691"}, - {"nm", "1852000"}, - {"mile", "1609.344"}, - {"miles", "1609.344"}, - {"mi", "1609.344"}, - {"kilometer", "1000"}, - {"kilometers", "1000"}, - {"km", "1000"}, - {"kms", "1000"}, - {"cable", "219.456"}, - {"cables", "219.456"}, - {"cb", "219.456"}, - {"fathom", "1.8288"}, - {"fathoms", "1.8288"}, - {"ftm", "1.8288"}, - {"meter", "1"}, - {"meters", "1"}, - {"m", "1"}, - {"yard", "0.9144"}, - {"yd", "0.9144"}, - {"foot", "0.3048"}, - {"feet", "0.3048"}, - {"ft", "0.3048"}, - {"inch", "0.0254"}, - {"inches", "0.0254"}, - {"centimeter", "0.01"}, - {"centimeters", "0.01"}, - {"cm", "0.01"}, - {"cms", "0.01"}, - {"millimeter", "0.001"}, - {"millimeters", "0.001"}, - {"mm", "0.001"}, - {"micrometer", "0.000001"}, - {"micrometers", "0.000001"}, - {"um", "0.000001"}, - {"nanometer", "0.000000001"}, - {"nanometers", "0.000000001"}, - {NULL, NULL} - }; - - const char *area_units[][2] = { - {"hectare", "10000"}, - {"hectares", "10000"}, - {"acre", "4046.8564224"}, - {"acres", "4046.8564224"}, - {"m²", "1"}, - {"cm²", "0.001"}, - {"mm²", "0.000001"}, - {NULL, NULL} - }; - - const char *volume_units[][2] = { - {"m³", "1000"}, - {"gallon", "3.785412"}, - {"gallons", "3.785412"}, - {"gal", "3.785412"}, - {"litre", "1"}, - {"litres", "1"}, - {"liter", "1"}, - {"liters", "1"}, - {"L", "1"}, - {"quart", "0.9463529"}, - {"quarts", "0.9463529"}, - {"qt", "0.9463529"}, - {"pint", "0.4731765"}, - {"pints", "0.4731765"}, - {"pt", "0.4731765"}, - {"millilitre", "0.001"}, - {"millilitres", "0.001"}, - {"milliliter", "0.001"}, - {"milliliters", "0.001"}, - {"mL", "0.001"}, - {"cm³", "0.001"}, - {"mm³", "0.000001"}, - {NULL, NULL} - }; - - const char *weight_units[][2] = { - {"tonne", "1000"}, - {"tonnes", "1000"}, - {"kilograms", "1"}, - {"kilogramme", "1"}, - {"kilogrammes", "1"}, - {"kg", "1"}, - {"kgs", "1"}, - {"pound", "0.45359237"}, - {"pounds", "0.45359237"}, - {"lb", "0.45359237"}, - {"ounce", "0.002834952"}, - {"ounces", "0.002834952"}, - {"oz", "0.002834952"}, - {"gram", "0.001"}, - {"grams", "0.001"}, - {"gramme", "0.001"}, - {"grammes", "0.001"}, - {"g", "0.001"}, - {NULL, NULL} - }; - - const char *time_units[][2] = { - {"year", "31557600"}, - {"years", "31557600"}, - {"day", "86400"}, - {"days", "86400"}, - {"hour", "3600"}, - {"hours", "3600"}, - {"minute", "60"}, - {"minutes", "60"}, - {"second", "1"}, - {"seconds", "1"}, - {"s", "1"}, - {"millisecond", "0.001"}, - {"milliseconds", "0.001"}, - {"ms", "0.001"}, - {"microsecond", "0.000001"}, - {"microseconds", "0.000001"}, - {"us", "0.000001"}, - {NULL, NULL} - }; - - if (do_convert(length_units, x, x_units, z_units, z) || - do_convert(area_units, x, x_units, z_units, z) || - do_convert(volume_units, x, x_units, z_units, z) || - do_convert(weight_units, x, x_units, z_units, z) || - do_convert(time_units, x, x_units, z_units, z)) - return 1; - if (state->options->convert) return state->options->convert(x, x_units, z_units, z, state->options->callback_data); - - return 0; + else + return 0; } @@ -416,49 +248,44 @@ MPErrorCode mp_equation_parse(const char *expression, MPEquationOptions *options, MPNumber *result, char **error_token) { int ret; - MPEquationParserState state; - yyscan_t yyscanner; - YY_BUFFER_STATE buffer; + int err; + ParserState* state; + state = p_create_parser (expression, options); if (!(expression && result) || strlen(expression) == 0) return PARSER_ERR_INVALID; - memset(&state, 0, sizeof(MPEquationParserState)); - state.options = options; - state.variable_is_defined = variable_is_defined; - state.get_variable = get_variable; - state.set_variable = set_variable; - state.function_is_defined = function_is_defined; - state.get_function = get_function; - state.convert = convert; - state.error = 0; - + state->variable_is_defined = variable_is_defined; + state->get_variable = get_variable; + state->set_variable = set_variable; + state->function_is_defined = function_is_defined; + state->get_function = get_function; + state->convert = convert; + state->error = 0; mp_clear_error(); - - _mp_equation_lex_init_extra(&state, &yyscanner); - buffer = _mp_equation__scan_string(expression, yyscanner); - - ret = _mp_equation_parse(yyscanner); - if (state.error_token != NULL && error_token != NULL) { - *error_token = state.error_token; + ret = p_parse (state); + if (state->error_token != NULL && error_token != NULL) { + *error_token = state->error_token; } - - _mp_equation__delete_buffer(buffer, yyscanner); - _mp_equation_lex_destroy(yyscanner); - /* Error during parsing */ - if (state.error) - return state.error; + if (state->error) { + err = state->error; + p_destroy_parser (state); + return err; + } - if (mp_get_error()) + if (mp_get_error()) { + p_destroy_parser (state); return PARSER_ERR_MP; + } /* Failed to parse */ - if (ret) + if (ret) { + p_destroy_parser (state); return PARSER_ERR_INVALID; - - mp_set_from_mp(&state.ret, result); - + } + mp_set_from_mp(&state->ret, result); + p_destroy_parser (state); return PARSER_ERR_NONE; } @@ -486,9 +313,3 @@ mp_error_code_to_string(MPErrorCode error_code) return "Unknown parser error"; } } - - -int _mp_equation_error(void *yylloc, MPEquationParserState *state, char *text) -{ - return 0; -} diff --git a/src/mp-equation.h b/src/mp-equation.h index bb06de6..d6c0f24 100644 --- a/src/mp-equation.h +++ b/src/mp-equation.h @@ -1,20 +1,12 @@ -/* Copyright (c) 2004-2008 Sami Pietila - * Copyright (c) 2008-2009 Robert Ancell - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. +/* + * Copyright (C) 2004-2008 Sami Pietila + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MP_EQUATION_H @@ -22,55 +14,56 @@ #include "mp.h" -typedef enum { - PARSER_ERR_NONE = 0, - PARSER_ERR_INVALID, - PARSER_ERR_OVERFLOW, - PARSER_ERR_UNKNOWN_VARIABLE, - PARSER_ERR_UNKNOWN_FUNCTION, - PARSER_ERR_UNKNOWN_CONVERSION, - PARSER_ERR_MP +typedef enum +{ + PARSER_ERR_NONE = 0, + PARSER_ERR_INVALID, + PARSER_ERR_OVERFLOW, + PARSER_ERR_UNKNOWN_VARIABLE, + PARSER_ERR_UNKNOWN_FUNCTION, + PARSER_ERR_UNKNOWN_CONVERSION, + PARSER_ERR_MP } MPErrorCode; /* Options for parser */ typedef struct { - /* Default number base */ - int base; + /* Default number base */ + int base; - /* The wordlength for binary operations in bits (e.g. 8, 16, 32) */ - int wordlen; + /* The wordlength for binary operations in bits (e.g. 8, 16, 32) */ + int wordlen; - /* Units for angles (e.g. radians, degrees) */ - MPAngleUnit angle_units; + /* Units for angles (e.g. radians, degrees) */ + MPAngleUnit angle_units; - // FIXME: - // int enable_builtins; + // FIXME: + // int enable_builtins; - /* Data to pass to callbacks */ - void *callback_data; + /* Data to pass to callbacks */ + void *callback_data; + + /* Function to check if a variable is defined */ + int (*variable_is_defined)(const char *name, void *data); - /* Function to check if a variable is defined */ - int (*variable_is_defined)(const char* name, void* data); + /* Function to get variable values */ + int (*get_variable)(const char *name, MPNumber *z, void *data); - /* Function to get variable values */ - int (*get_variable)(const char* name, MPNumber* z, void* data); + /* Function to set variable values */ + void (*set_variable)(const char *name, const MPNumber *x, void *data); - /* Function to set variable values */ - void (*set_variable)(const char* name, const MPNumber *x, void* data); + /* Function to check if a function is defined */ + int (*function_is_defined)(const char *name, void *data); - /* Function to check if a function is defined */ - int (*function_is_defined)(const char* name, void* data); + /* Function to solve functions */ + int (*get_function)(const char *name, const MPNumber *x, MPNumber *z, void *data); - /* Function to solve functions */ - int (*get_function)(const char* name, const MPNumber* x, MPNumber* z, void* data); - - /* Function to convert units */ - int (*convert)(const MPNumber* x, const char* x_units, const char* z_units, MPNumber* z, void* data); + /* Function to convert units */ + int (*convert)(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data); } MPEquationOptions; -MPErrorCode mp_equation_parse(const char* expression, MPEquationOptions* options, MPNumber* result, char** error_token); -const char* mp_error_code_to_string(MPErrorCode error_code); +MPErrorCode mp_equation_parse(const char *expression, MPEquationOptions *options, MPNumber *result, char **error_token); +const char *mp_error_code_to_string(MPErrorCode error_code); -int sub_atoi(const char* data); -int super_atoi(const char* data); +int sub_atoi(const char *data); +int super_atoi(const char *data); #endif diff --git a/src/mp-private.h b/src/mp-private.h index f715b34..3211ea8 100644 --- a/src/mp-private.h +++ b/src/mp-private.h @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2009 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #ifndef MP_INTERNAL_H @@ -38,10 +30,10 @@ //} #define MP_T 100 -void mperr(const char* format, ...) __attribute__((format(printf, 1, 2))); -void mp_gcd(int64_t*, int64_t*); -void mp_normalize(MPNumber*); -void convert_to_radians(const MPNumber* x, MPAngleUnit unit, MPNumber* z); -void convert_from_radians(const MPNumber* x, MPAngleUnit unit, MPNumber* z); +void mperr(const char *format, ...) __attribute__((format(printf, 1, 2))); +void mp_gcd(int64_t *, int64_t *); +void mp_normalize(MPNumber *); +void convert_to_radians(const MPNumber *x, MPAngleUnit unit, MPNumber *z); +void convert_from_radians(const MPNumber *x, MPAngleUnit unit, MPNumber *z); #endif /* MP_INTERNAL_H */ diff --git a/src/mp-serializer.c b/src/mp-serializer.c new file mode 100644 index 0000000..356fc55 --- /dev/null +++ b/src/mp-serializer.c @@ -0,0 +1,617 @@ +/* + * Copyright (C) 2010 Robin Sonefors + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include +#include +#include +#include +#include + +#include "mp-serializer.h" +#include "mp-enums.h" + +enum { + PROP_0, + PROP_SHOW_THOUSANDS_SEPARATORS, + PROP_SHOW_TRAILING_ZEROES, + PROP_NUMBER_FORMAT, + PROP_BASE, +}; + +struct MpSerializerPrivate +{ + gint leading_digits; /* Number of digits to show before radix */ + gint trailing_digits; /* Number of digits to show after radix */ + MpDisplayFormat format; /* Number display mode. */ + gint show_tsep; /* Set if the thousands separator should be shown. */ + gint show_zeroes; /* Set if trailing zeroes should be shown. */ + + gint base; /* Numeric base */ + + gunichar tsep; /* Locale specific thousands separator. */ + gunichar radix; /* Locale specific radix string. */ + gint tsep_count; /* Number of digits between separator. */ +}; + + +G_DEFINE_TYPE(MpSerializer, mp_serializer, G_TYPE_OBJECT); + +MpSerializer * +mp_serializer_new(MpDisplayFormat format, int base, int trailing_digits) +{ + MpSerializer *serializer = g_object_new(mp_serializer_get_type(), /*"number-format", format,*/ NULL); + mp_serializer_set_number_format(serializer, format); + mp_serializer_set_base(serializer, base); + mp_serializer_set_trailing_digits(serializer, trailing_digits); + return serializer; +} + + +static void +mp_cast_to_string_real(MpSerializer *serializer, const MPNumber *x, int base, gboolean force_sign, int *n_digits, GString *string) +{ + static gchar digits[] = "0123456789ABCDEF"; + MPNumber number, integer_component, fractional_component, temp; + int i, last_non_zero; + + if (mp_is_negative(x)) + mp_abs(x, &number); + else + mp_set_from_mp(x, &number); + + /* Add rounding factor */ + mp_set_from_integer(base, &temp); + mp_xpowy_integer(&temp, -(serializer->priv->trailing_digits+1), &temp); + mp_multiply_integer(&temp, base, &temp); + mp_divide_integer(&temp, 2, &temp); + mp_add(&number, &temp, &temp); + + /* If trying to add rounding factor causes overflow, don't add it */ + if (!mp_get_error()) + mp_set_from_mp(&temp, &number); + + /* Split into integer and fractional component */ + mp_floor(&number, &integer_component); + mp_fractional_component(&number, &fractional_component); + + /* Write out the integer component least significant digit to most */ + mp_set_from_mp(&integer_component, &temp); + i = 0; + do { + MPNumber t, t2, t3; + int64_t d; + + if (serializer->priv->base == 10 && serializer->priv->show_tsep && i == serializer->priv->tsep_count) { + g_string_prepend_unichar(string, serializer->priv->tsep); + i = 0; + } + i++; + + mp_divide_integer(&temp, base, &t); + mp_floor(&t, &t); + mp_multiply_integer(&t, base, &t2); + + mp_subtract(&temp, &t2, &t3); + + d = mp_cast_to_int(&t3); + g_string_prepend_c(string, d < 16 ? digits[d] : '?'); + (*n_digits)++; + + mp_set_from_mp(&t, &temp); + } while (!mp_is_zero(&temp)); + + last_non_zero = string->len; + + g_string_append_unichar(string, serializer->priv->radix); + + /* Write out the fractional component */ + mp_set_from_mp(&fractional_component, &temp); + for (i = serializer->priv->trailing_digits; i > 0 && !mp_is_zero(&temp); i--) { + int d; + MPNumber digit; + + mp_multiply_integer(&temp, base, &temp); + mp_floor(&temp, &digit); + d = mp_cast_to_int(&digit); + + g_string_append_c(string, digits[d]); + + if(d != 0) + last_non_zero = string->len; + mp_subtract(&temp, &digit, &temp); + } + + /* Strip trailing zeroes */ + if (!serializer->priv->show_zeroes || serializer->priv->trailing_digits == 0) + g_string_truncate(string, last_non_zero); + + /* Add sign on non-zero values */ + if (strcmp(string->str, "0") != 0 || force_sign) { + if (mp_is_negative(x)) + g_string_prepend(string, "−"); + else if (force_sign) + g_string_prepend(string, "+"); + } + + /* Append base suffix if not in default base */ + if (base != serializer->priv->base) { + const gchar *digits[] = {"₀", "₁", "₂", "₃", "₄", "₅", "₆", "₇", "₈", "₉"}; + int multiplier = 1; + int b = base; + + while (base / multiplier != 0) + multiplier *= 10; + while (multiplier != 1) { + int d; + multiplier /= 10; + d = b / multiplier; + g_string_append(string, digits[d]); + b -= d * multiplier; + } + } +} + + +static gchar * +mp_cast_to_string(MpSerializer *serializer, const MPNumber *x, int *n_digits) +{ + GString *string; + MPNumber x_real; + gchar *result; + + string = g_string_sized_new(1024); + + mp_real_component(x, &x_real); + mp_cast_to_string_real(serializer, &x_real, serializer->priv->base, FALSE, n_digits, string); + if (mp_is_complex(x)) { + GString *s; + gboolean force_sign = TRUE; + MPNumber x_im; + int n_complex_digits; + + mp_imaginary_component(x, &x_im); + + if (strcmp(string->str, "0") == 0) { + g_string_assign(string, ""); + force_sign = FALSE; + } + + s = g_string_sized_new(1024); + mp_cast_to_string_real(serializer, &x_im, 10, force_sign, &n_complex_digits, s); + if (n_complex_digits > *n_digits) + *n_digits = n_complex_digits; + if (strcmp(s->str, "0") == 0 || strcmp(s->str, "+0") == 0 || strcmp(s->str, "−0") == 0) { + /* Ignore */ + } + else if (strcmp(s->str, "1") == 0) { + g_string_append(string, "i"); + } + else if (strcmp(s->str, "+1") == 0) { + g_string_append(string, "+i"); + } + else if (strcmp(s->str, "−1") == 0) { + g_string_append(string, "−i"); + } + else { + if (strcmp(s->str, "+0") == 0) + g_string_append(string, "+"); + else if (strcmp(s->str, "0") != 0) + g_string_append(string, s->str); + + g_string_append(string, "i"); + } + g_string_free(s, TRUE); + } + + result = g_strndup(string->str, string->len + 1); + g_string_free(string, TRUE); + + return result; +} + + +static int +mp_cast_to_exponential_string_real(MpSerializer *serializer, const MPNumber *x, GString *string, gboolean eng_format, int *n_digits) +{ + gchar *fixed; + MPNumber t, z, base, base3, base10, base10inv, mantissa; + int exponent = 0; + + mp_abs(x, &z); + if (mp_is_negative(x)) + g_string_append(string, "−"); + mp_set_from_mp(&z, &mantissa); + + mp_set_from_integer(serializer->priv->base, &base); + mp_xpowy_integer(&base, 3, &base3); + mp_xpowy_integer(&base, 10, &base10); + mp_set_from_integer(1, &t); + mp_divide(&t, &base10, &base10inv); + + if (!mp_is_zero(&mantissa)) { + while (!eng_format && mp_is_greater_equal(&mantissa, &base10)) { + exponent += 10; + mp_multiply(&mantissa, &base10inv, &mantissa); + } + + while ((!eng_format && mp_is_greater_equal(&mantissa, &base)) || + (eng_format && (mp_is_greater_equal(&mantissa, &base3) || exponent % 3 != 0))) { + exponent += 1; + mp_divide(&mantissa, &base, &mantissa); + } + + while (!eng_format && mp_is_less_than(&mantissa, &base10inv)) { + exponent -= 10; + mp_multiply(&mantissa, &base10, &mantissa); + } + + mp_set_from_integer(1, &t); + while (mp_is_less_than(&mantissa, &t) || (eng_format && exponent % 3 != 0)) { + exponent -= 1; + mp_multiply(&mantissa, &base, &mantissa); + } + } + + fixed = mp_cast_to_string(serializer, &mantissa, n_digits); + g_string_append(string, fixed); + g_free(fixed); + + return exponent; +} + + +static void +append_exponent(GString *string, int exponent) +{ + const gchar *super_digits[] = {"⁰", "¹", "²", "³", "⁴", "⁵", "⁶", "⁷", "⁸", "⁹"}; + gchar *super_value, *c; + + if (exponent == 0) + return; + + g_string_append_printf(string, "×10"); // FIXME: Use the current base + if (exponent < 0) { + exponent = -exponent; + g_string_append(string, "⁻"); + } + + super_value = g_strdup_printf("%d", exponent); + for (c = super_value; *c; c++) + g_string_append(string, super_digits[*c - '0']); + g_free (super_value); +} + + +static gchar * +mp_cast_to_exponential_string(MpSerializer *serializer, const MPNumber *x, gboolean eng_format, int *n_digits) +{ + GString *string; + MPNumber x_real; + gchar *result; + int exponent; + + string = g_string_sized_new(1024); + + mp_real_component(x, &x_real); + exponent = mp_cast_to_exponential_string_real(serializer, &x_real, string, eng_format, n_digits); + append_exponent(string, exponent); + + if (mp_is_complex(x)) { + GString *s; + MPNumber x_im; + int n_complex_digits = 0; + + mp_imaginary_component(x, &x_im); + + if (strcmp(string->str, "0") == 0) + g_string_assign(string, ""); + + s = g_string_sized_new(1024); + exponent = mp_cast_to_exponential_string_real(serializer, &x_im, s, eng_format, &n_complex_digits); + if (n_complex_digits > *n_digits) + *n_digits = n_complex_digits; + if (strcmp(s->str, "0") == 0 || strcmp(s->str, "+0") == 0 || strcmp(s->str, "−0") == 0) { + /* Ignore */ + } + else if (strcmp(s->str, "1") == 0) { + g_string_append(string, "i"); + } + else if (strcmp(s->str, "+1") == 0) { + g_string_append(string, "+i"); + } + else if (strcmp(s->str, "−1") == 0) { + g_string_append(string, "−i"); + } + else { + if (strcmp(s->str, "+0") == 0) + g_string_append(string, "+"); + else if (strcmp(s->str, "0") != 0) + g_string_append(string, s->str); + + g_string_append(string, "i"); + } + g_string_free(s, TRUE); + append_exponent(string, exponent); + } + + result = g_strndup(string->str, string->len + 1); + g_string_free(string, TRUE); + + return result; +} + + +gchar * +mp_serializer_to_string(MpSerializer *serializer, const MPNumber *x) +{ + gchar *s0; + int n_digits = 0; + + switch(serializer->priv->format) { + default: + case MP_DISPLAY_FORMAT_AUTOMATIC: + s0 = mp_cast_to_string(serializer, x, &n_digits); + if (n_digits <= serializer->priv->leading_digits) + return s0; + else { + g_free (s0); + return mp_cast_to_exponential_string(serializer, x, FALSE, &n_digits); + } + break; + case MP_DISPLAY_FORMAT_FIXED: + return mp_cast_to_string(serializer, x, &n_digits); + case MP_DISPLAY_FORMAT_SCIENTIFIC: + return mp_cast_to_exponential_string(serializer, x, FALSE, &n_digits); + case MP_DISPLAY_FORMAT_ENGINEERING: + return mp_cast_to_exponential_string(serializer, x, TRUE, &n_digits); + } +} + + +gboolean +mp_serializer_from_string(MpSerializer *serializer, const gchar *str, MPNumber *z) +{ + return mp_set_from_string(str, serializer->priv->base, z); +} + + +void +mp_serializer_set_base(MpSerializer *serializer, gint base) +{ + serializer->priv->base = base; +} + + +int +mp_serializer_get_base(MpSerializer *serializer) +{ + return serializer->priv->base; +} + + +void +mp_serializer_set_radix(MpSerializer *serializer, gunichar radix) +{ + serializer->priv->radix = radix; +} + + +gunichar +mp_serializer_get_radix(MpSerializer *serializer) +{ + return serializer->priv->radix; +} + + +void +mp_serializer_set_thousands_separator(MpSerializer *serializer, gunichar separator) +{ + serializer->priv->tsep = separator; +} + + +gunichar +mp_serializer_get_thousands_separator(MpSerializer *serializer) +{ + return serializer->priv->tsep; +} + + +gint +mp_serializer_get_thousands_separator_count(MpSerializer *serializer) +{ + return serializer->priv->tsep_count; +} + + +void +mp_serializer_set_show_thousands_separators(MpSerializer *serializer, gboolean visible) +{ + serializer->priv->show_tsep = visible; +} + + +gboolean +mp_serializer_get_show_thousands_separators(MpSerializer *serializer) +{ + return serializer->priv->show_tsep; +} + + +void +mp_serializer_set_show_trailing_zeroes(MpSerializer *serializer, gboolean visible) +{ + serializer->priv->show_zeroes = visible; +} + + +gboolean +mp_serializer_get_show_trailing_zeroes(MpSerializer *serializer) +{ + return serializer->priv->show_zeroes; +} + + +int +mp_serializer_get_leading_digits(MpSerializer *serializer) +{ + return serializer->priv->leading_digits; +} + + +void +mp_serializer_set_leading_digits(MpSerializer *serializer, int leading_digits) +{ + serializer->priv->leading_digits = leading_digits; +} + + +int +mp_serializer_get_trailing_digits(MpSerializer *serializer) +{ + return serializer->priv->trailing_digits; +} + + +void +mp_serializer_set_trailing_digits(MpSerializer *serializer, int trailing_digits) +{ + serializer->priv->trailing_digits = trailing_digits; +} + + +MpDisplayFormat +mp_serializer_get_number_format(MpSerializer *serializer) +{ + return serializer->priv->format; +} + + +void +mp_serializer_set_number_format(MpSerializer *serializer, MpDisplayFormat format) +{ + serializer->priv->format = format; +} + + +static void +mp_serializer_set_property(GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + MpSerializer *self = MP_SERIALIZER(object); + + switch (prop_id) { + case PROP_SHOW_THOUSANDS_SEPARATORS: + mp_serializer_set_show_thousands_separators(self, g_value_get_boolean(value)); + break; + case PROP_SHOW_TRAILING_ZEROES: + mp_serializer_set_show_trailing_zeroes(self, g_value_get_boolean(value)); + break; + case PROP_BASE: + mp_serializer_set_base(self, g_value_get_int(value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + + +static void +mp_serializer_get_property(GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + MpSerializer *self = MP_SERIALIZER(object); + + switch (prop_id) { + case PROP_SHOW_THOUSANDS_SEPARATORS: + g_value_set_boolean(value, mp_serializer_get_show_thousands_separators(self)); + break; + case PROP_SHOW_TRAILING_ZEROES: + g_value_set_boolean(value, mp_serializer_get_show_trailing_zeroes(self)); + break; + case PROP_BASE: + g_value_set_int(value, mp_serializer_get_base(self)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); + break; + } +} + + +static void +mp_serializer_class_init(MpSerializerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + object_class->get_property = mp_serializer_get_property; + object_class->set_property = mp_serializer_set_property; + + g_type_class_add_private(klass, sizeof(MpSerializerPrivate)); + + g_object_class_install_property(object_class, + PROP_SHOW_THOUSANDS_SEPARATORS, + g_param_spec_boolean("show-thousands-separators", + "show-thousands-separators", + "Show thousands separators", + TRUE, + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_SHOW_TRAILING_ZEROES, + g_param_spec_boolean("show-trailing-zeroes", + "show-trailing-zeroes", + "Show trailing zeroes", + FALSE, + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_NUMBER_FORMAT, + g_param_spec_enum("number-format", + "number-format", + "Display format", + math_mp_display_format_get_type(), + MP_DISPLAY_FORMAT_AUTOMATIC, + G_PARAM_READWRITE)); + g_object_class_install_property(object_class, + PROP_BASE, + g_param_spec_int("base", + "base", + "Default number base", + 2, 16, 10, + G_PARAM_READWRITE)); +} + + +static void +mp_serializer_init(MpSerializer *serializer) +{ + gchar *radix, *tsep; + serializer->priv = G_TYPE_INSTANCE_GET_PRIVATE(serializer, mp_serializer_get_type(), MpSerializerPrivate); + + radix = nl_langinfo(RADIXCHAR); + serializer->priv->radix = radix ? g_utf8_get_char(g_locale_to_utf8(radix, -1, NULL, NULL, NULL)) : '.'; + tsep = nl_langinfo(THOUSEP); + if (tsep && tsep[0] != '\0') + serializer->priv->tsep = g_utf8_get_char(g_locale_to_utf8(tsep, -1, NULL, NULL, NULL)); + else + serializer->priv->tsep = ' '; + serializer->priv->tsep_count = 3; + + serializer->priv->base = 10; + serializer->priv->leading_digits = 12; + serializer->priv->trailing_digits = 9; + serializer->priv->show_zeroes = FALSE; + serializer->priv->show_tsep = FALSE; + serializer->priv->format = MP_DISPLAY_FORMAT_AUTOMATIC; +} diff --git a/src/mp-serializer.h b/src/mp-serializer.h new file mode 100644 index 0000000..63e5937 --- /dev/null +++ b/src/mp-serializer.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2010 Robin Sonefors + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef MP_SERIALIZER_H +#define MP_SERIALIZER_H + +#include +#include + +#include "mp.h" + +G_BEGIN_DECLS + +#define MP_TYPE_SERIALIZER (mp_serializer_get_type()) +#define MP_SERIALIZER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), mp_serializer_get_type(), MpSerializer)) + +typedef struct MpSerializerPrivate MpSerializerPrivate; + +typedef struct { + GObject parent; + MpSerializerPrivate *priv; +} MpSerializer; + +typedef struct { + GObjectClass parent; +} MpSerializerClass; + +/* Number display mode. */ +typedef enum { + MP_DISPLAY_FORMAT_AUTOMATIC, + MP_DISPLAY_FORMAT_FIXED, + MP_DISPLAY_FORMAT_SCIENTIFIC, + MP_DISPLAY_FORMAT_ENGINEERING +} MpDisplayFormat; + +GType mp_serializer_get_type(void); + +MpSerializer *mp_serializer_new(MpDisplayFormat format, int base, int trailing_digits); + +gchar *mp_serializer_to_string(MpSerializer *serializer, const MPNumber *z); +gboolean mp_serializer_from_string(MpSerializer *serializer, const gchar *str, MPNumber *z); + +void mp_serializer_set_number_format(MpSerializer *serializer, MpDisplayFormat format); +MpDisplayFormat mp_serializer_get_number_format(MpSerializer *serializer); + +void mp_serializer_set_base(MpSerializer *serializer, int base); +int mp_serializer_get_base(MpSerializer *serializer); + +void mp_serializer_set_leading_digits(MpSerializer *serializer, int leading_digits); +int mp_serializer_get_leading_digits(MpSerializer *serializer); + +void mp_serializer_set_trailing_digits(MpSerializer *serializer, int trailing_digits); +int mp_serializer_get_trailing_digits(MpSerializer *serializer); + +void mp_serializer_set_radix(MpSerializer *serializer, gunichar radix); +gunichar mp_serializer_get_radix(MpSerializer *serializer); + +void mp_serializer_set_thousands_separator(MpSerializer *serializer, gunichar separator); +gunichar mp_serializer_get_thousands_separator(MpSerializer *serializer); + +gint mp_serializer_get_thousands_separator_count(MpSerializer *serializer); + +void mp_serializer_set_show_trailing_zeroes(MpSerializer *serializer, gboolean visible); +gboolean mp_serializer_get_show_trailing_zeroes(MpSerializer *serializer); + +void mp_serializer_set_show_thousands_separators(MpSerializer *serializer, gboolean visible); +gboolean mp_serializer_get_show_thousands_separators(MpSerializer *serializer); + +G_END_DECLS + +#endif /* MP_SERIALIZER_H */ diff --git a/src/mp-trigonometric.c b/src/mp-trigonometric.c index 4982041..3ad60c4 100644 --- a/src/mp-trigonometric.c +++ b/src/mp-trigonometric.c @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -25,6 +17,9 @@ #include "mp.h" #include "mp-private.h" +static MPNumber pi; +static gboolean have_pi = FALSE; + static int mp_compare_mp_to_int(const MPNumber *x, int i) { @@ -64,7 +59,11 @@ convert_to_radians(const MPNumber *x, MPAngleUnit unit, MPNumber *z) void mp_get_pi(MPNumber *z) { - mp_set_from_string("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 10, z); + if (mp_is_zero(&pi)) { + mp_set_from_string("3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679", 10, &pi); + have_pi = TRUE; + } + mp_set_from_mp(&pi, z); } @@ -420,7 +419,7 @@ mp_atan(const MPNumber *x, MPAngleUnit unit, MPNumber *z) break; q *= 2; - + /* t = t / (√(t² + 1) + 1) */ mp_multiply(&t2, &t2, z); mp_add_integer(z, 1, z); @@ -557,6 +556,7 @@ mp_tanh(const MPNumber *x, MPNumber *z) } else { mp_epowy(&t, &t); mp_add_integer(&t, 1, z); + mp_add_integer(&t, -1, &t); mp_divide(&t, z, z); } @@ -588,7 +588,7 @@ mp_acosh(const MPNumber *x, MPNumber *z) mp_set_from_integer(1, &t); if (mp_is_less_than(x, &t)) { /* Translators: Error displayed when inverse hyperbolic cosine value is undefined */ - mperr(_("Inverse hyperbolic cosine is undefined for values less than or equal to one")); + mperr(_("Inverse hyperbolic cosine is undefined for values less than one")); mp_set_from_integer(0, z); return; } diff --git a/src/mp.c b/src/mp.c index 0037135..3003202 100644 --- a/src/mp.c +++ b/src/mp.c @@ -1,20 +1,12 @@ -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ #include @@ -25,6 +17,9 @@ #include "mp.h" #include "mp-private.h" +static MPNumber eulers_number; +static gboolean have_eulers_number = FALSE; + // FIXME: Re-add overflow and underflow detection char *mp_error = NULL; @@ -101,9 +96,13 @@ mp_ext(int i, int j, MPNumber *x) void mp_get_eulers(MPNumber *z) { - MPNumber t; - mp_set_from_integer(1, &t); - mp_epowy(&t, z); + if (!have_eulers_number) { + MPNumber t; + mp_set_from_integer(1, &t); + mp_epowy(&t, &eulers_number); + have_eulers_number = TRUE; + } + mp_set_from_mp(&eulers_number, z); } @@ -212,8 +211,8 @@ mp_imaginary_component(const MPNumber *x, MPNumber *z) z->sign = x->im_sign; z->exponent = x->im_exponent; memcpy(z->fraction, x->im_fraction, sizeof(int) * MP_SIZE); - - /* Clear (old) imaginary component */ + + /* Clear (old) imaginary component */ z->im_sign = 0; z->im_exponent = 0; memset(z->im_fraction, 0, sizeof(int) * MP_SIZE); @@ -235,7 +234,7 @@ mp_add_real(const MPNumber *x, int y_sign, const MPNumber *y, MPNumber *z) z->sign = y_sign; return; } - /* x + 0 = x */ + /* x + 0 = x */ else if (mp_is_zero(y)) { mp_set_from_mp(x, z); return; @@ -293,6 +292,15 @@ mp_add_real(const MPNumber *x, int y_sign, const MPNumber *y, MPNumber *z) z->fraction[MP_T + i] = 0; if (sign_prod >= 0) { + /* This is probably insufficient overflow detection, but it makes us + * not crash at least. + */ + if (MP_T + 3 < med) { + mperr(_("Overflow: the result couldn't be calculated")); + mp_set_from_integer(0, z); + return; + } + /* HERE DO ADDITION, EXPONENT(Y) >= EXPONENT(X) */ for (i = MP_T + 3; i >= MP_T; i--) z->fraction[i] = small_fraction[i - med]; @@ -450,7 +458,7 @@ mp_sgn(const MPNumber *x, MPNumber *z) else if (mp_is_negative(x)) mp_set_from_integer(-1, z); else - mp_set_from_integer(1, z); + mp_set_from_integer(1, z); } void @@ -829,7 +837,7 @@ bool mp_is_integer(const MPNumber *x) { MPNumber t1, t2, t3; - + if (mp_is_complex(x)) return false; @@ -978,7 +986,7 @@ mp_epowy_real(const MPNumber *x, MPNumber *z) { float r__1; int i, ix, xs, tss; - float rx, rz, rlb; + float rx, rz; MPNumber t1, t2; /* e^0 = 1 */ @@ -993,11 +1001,6 @@ mp_epowy_real(const MPNumber *x, MPNumber *z) return; } - /* SEE IF ABS(X) SO LARGE THAT EXP(X) WILL CERTAINLY OVERFLOW - * OR UNDERFLOW. 1.01 IS TO ALLOW FOR ERRORS IN ALOG. - */ - rlb = log((float)MP_BASE) * 1.01f; - /* NOW SAFE TO CONVERT X TO REAL */ rx = mp_cast_to_float(x); @@ -1303,7 +1306,7 @@ void mp_logarithm(int64_t n, const MPNumber *x, MPNumber *z) { MPNumber t1, t2; - + /* log(0) undefined */ if (mp_is_zero(x)) { /* Translators: Error displayed when attempting to take logarithm of zero */ @@ -1333,7 +1336,7 @@ mp_multiply_real(const MPNumber *x, const MPNumber *y, MPNumber *z) int c, i, xi; MPNumber r; - /* x*0 = 0*y = 0 */ + /* x*0 = 0*y = 0 */ if (x->sign == 0 || y->sign == 0) { mp_set_from_integer(0, z); return; @@ -1418,7 +1421,7 @@ mp_multiply_real(const MPNumber *x, const MPNumber *y, MPNumber *z) /* Clear complex part */ z->im_sign = 0; z->im_exponent = 0; - memset(z->im_fraction, 0, sizeof(int) * MP_SIZE); + memset(z->im_fraction, 0, sizeof(int) * MP_SIZE); /* NORMALIZE AND ROUND RESULT */ // FIXME: Use stack variable because of mp_normalize brokeness @@ -1445,11 +1448,11 @@ mp_multiply(const MPNumber *x, const MPNumber *y, MPNumber *z) mp_imaginary_component(x, &im_x); mp_real_component(y, &real_y); mp_imaginary_component(y, &im_y); - + mp_multiply_real(&real_x, &real_y, &t1); mp_multiply_real(&im_x, &im_y, &t2); mp_subtract(&t1, &t2, &real_z); - + mp_multiply_real(&real_x, &im_y, &t1); mp_multiply_real(&im_x, &real_y, &t2); mp_add(&t1, &t2, &im_z); @@ -1574,7 +1577,7 @@ mp_multiply_integer_real(const MPNumber *x, int64_t y, MPNumber *z) z->im_sign = 0; z->im_exponent = 0; - memset(z->im_fraction, 0, sizeof(int) * MP_SIZE); + memset(z->im_fraction, 0, sizeof(int) * MP_SIZE); mp_normalize(z); } @@ -1670,10 +1673,11 @@ mp_pwr(const MPNumber *x, const MPNumber *y, MPNumber *z) return; }*/ - /* 0^-y illegal */ - if (mp_is_zero(x) && y->sign < 0) { - mperr(_("The power of zero is undefined for a negative exponent")); + /* 0^y = 0, 0^-y undefined */ + if (mp_is_zero(x)) { mp_set_from_integer(0, z); + if (y->sign < 0) + mperr(_("The power of zero is undefined for a negative exponent")); return; } @@ -1746,7 +1750,7 @@ mp_reciprocal_real(const MPNumber *x, MPNumber *z) void mp_reciprocal(const MPNumber *x, MPNumber *z) -{ +{ if (mp_is_complex(x)) { MPNumber t1, t2; MPNumber real_x, im_x; @@ -2003,7 +2007,7 @@ mp_xpowy_integer(const MPNumber *x, int64_t n, MPNumber *z) mp_set_from_integer(0, z); return; } - + /* x^1 = x */ if (n == 1) { mp_set_from_mp(x, z); diff --git a/src/mp.h b/src/mp.h index 3f99c60..634bb01 100644 --- a/src/mp.h +++ b/src/mp.h @@ -1,21 +1,12 @@ - -/* Copyright (c) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. - * Copyright (c) 2008-2009 Robert Ancell - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2, or (at your option) - * any later version. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - * 02110-1301, USA. +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. */ /* This maths library is based on the MP multi-precision floating-point @@ -53,26 +44,28 @@ * * x = sign * (MP_BASE^(exponent-1) + MP_BASE^(exponent-2) + ...) */ -typedef struct { - /* Sign (+1, -1) or 0 for the value zero */ - int sign, im_sign; +typedef struct +{ + /* Sign (+1, -1) or 0 for the value zero */ + int sign, im_sign; - /* Exponent (to base MP_BASE) */ - int exponent, im_exponent; + /* Exponent (to base MP_BASE) */ + int exponent, im_exponent; - /* Normalized fraction */ - int fraction[MP_SIZE], im_fraction[MP_SIZE]; + /* Normalized fraction */ + int fraction[MP_SIZE], im_fraction[MP_SIZE]; } MPNumber; -typedef enum { - MP_RADIANS, - MP_DEGREES, - MP_GRADIANS +typedef enum +{ + MP_RADIANS, + MP_DEGREES, + MP_GRADIANS } MPAngleUnit; /* Returns error string or NULL if no error */ // FIXME: Global variable -const char* mp_get_error(void); +const char *mp_get_error(void); /* Clear any current error */ void mp_clear_error(void); @@ -268,24 +261,6 @@ int64_t mp_cast_to_int(const MPNumber *x); /* Returns x as a native unsigned integer */ uint64_t mp_cast_to_unsigned_int(const MPNumber *x); -/* Converts x to a string representation. - * The string is written into 'buffer' which is guaranteed to be at least 'buffer_length' octets in size. - * If not enough space is available the string is truncated. - * The numbers are written in 'base' (e.g. 10). - * If 'trim_zeroes' is non-zero then strip off trailing zeroes. - * Fractional components are truncated at 'max_digits' digits. - */ -void mp_cast_to_string(const MPNumber *x, int default_base, int base, int max_digits, bool trim_zeroes, char *buffer, int buffer_length); - -/* Converts x to a string representation in exponential form. - * The string is written into 'buffer' which is guaranteed to be at least 'buffer_length' octets in size. - * If not enough space is available the string is truncated. - * The numbers are written in 'base' (e.g. 10). - * If 'trim_zeroes' is non-zero then strip off trailing zeroes. - * Fractional components are truncated at 'max_digits' digits. - */ -void mp_cast_to_exponential_string(const MPNumber *x, int default_base, int base, int max_digits, bool trim_zeroes, bool eng_format, char *buffer, int buffer_length); - /* Sets z = sin x */ void mp_sin(const MPNumber *x, MPAngleUnit unit, MPNumber *z); diff --git a/src/parser.c b/src/parser.c new file mode 100644 index 0000000..fb4fd12 --- /dev/null +++ b/src/parser.c @@ -0,0 +1,1228 @@ +#include +#include +#include +#include + +#include "parser.h" +#include "parserfunc.h" +#include "mp-equation.h" + +/* Converts LexerTokenType to Precedence value. */ +static guint +p_get_precedence(LexerTokenType type) +{ + /* WARNING: This function doesn't work for Unary Plus and Unary Minus. Use their precedence directly while inserting them in tree. */ + if(type == T_ADD + ||type == T_SUBTRACT) + return P_AddSubtract; + if(type == T_MULTIPLY) + return P_Multiply; + if(type == T_MOD) + return P_Mod; + if(type == T_DIV) + return P_Divide; + if(type == T_NOT) + return P_Not; + if(type == T_ROOT + ||type == T_ROOT_3 + ||type == T_ROOT_4) + return P_Root; + if(type == T_FUNCTION) + return P_Function; + if(type == T_AND + ||type == T_OR + ||type == T_XOR) + return P_Boolean; + if(type == T_PERCENTAGE) + return P_Percentage; + if(type == T_POWER) + return P_Power; + if(type == T_FACTORIAL) + return P_Factorial; + if(type == T_NUMBER + ||type == T_VARIABLE) + return P_NumberVariable; + return P_Unknown; +} + +/* Return associativity of specific token type from precedence. */ +static Associativity +p_get_associativity_p(Precedence type) +{ + if(type == P_Boolean + ||type == P_Divide + ||type == P_Mod + ||type == P_Multiply + ||type == P_AddSubtract) + return LEFT_ASSOCIATIVE; + if(type == P_Power) + return RIGHT_ASSOCIATIVE; + /* For all remaining / non-associative operators, return Left Associativity. */ + return LEFT_ASSOCIATIVE; +} + +/* Return associativity of specific token by converting it to precedence first. */ +static Associativity +p_get_associativity(LexerToken* token) +{ + return p_get_associativity_p(p_get_precedence(token->token_type)); +} + +/* Generate precedence for a node from precedence value. Includes depth_level. */ +static guint +p_make_precedence_p(ParserState* state, Precedence p) +{ + return (p + (state->depth_level * P_Depth)); +} + +/* Generate precedence for a node from lexer token type. Includes depth_level. */ +static guint +p_make_precedence_t(ParserState* state, LexerTokenType type) +{ + return (p_get_precedence(type) + (state->depth_level * P_Depth)); +} + +/* Allocate and create a new node. */ +static ParseNode* +p_create_node(ParserState* state, LexerToken* token, guint precedence, Associativity associativity, void* value, void* (*function)(ParseNode*)) +{ + ParseNode* new; + new = (ParseNode*) malloc(sizeof(ParseNode)); + assert(new != NULL); + new->parent = NULL; + new->left = NULL; + new->right = NULL; + new->token = token; + new->precedence = precedence; + new->associativity = associativity; + new->value = value; + new->state = state; + new->evaluate = function; + return new; +} + +/* Compares two nodes to decide, which will be parent and which willbe child. */ +static gint +p_cmp_nodes(ParseNode* left, ParseNode* right) +{ + /* Return values. + 1 = right goes up (near root) in parse tree. + 0 = left goes up (near root) in parse tree. + */ + if(left == NULL) + return 0; + if(left->precedence > right->precedence) + { + return 1; + } + else if(left->precedence < right->precedence) + { + return 0; + } + else + { + if(right->associativity == RIGHT_ASSOCIATIVE) + { + return 0; + } + else + { + return 1; + } + } +} + +/* Unified interface (unary and binary nodes) to insert node into parse tree. */ +static void +p_insert_into_tree_all(ParserState* state, ParseNode* node, guint unary_function) +{ + if(state->root == NULL) + { + state->root = node; + state->right_most = state->root; + return; + } + ParseNode* tmp = state->right_most; + while(p_cmp_nodes(tmp, node)) + tmp = tmp->parent; + if(unary_function) + { + /* If tmp is null, that means, we have to insert new node at root. */ + if(tmp == NULL) + { + node->right = state->root; + node->right->parent = node; + + state->root = node; + } + else + { + node->right = tmp->right; + if(node->right) + node->right->parent = node; + + tmp->right = node; + if(tmp->right) + tmp->right->parent = tmp; + + } + state->right_most = node; + while(state->right_most->right != NULL) + state->right_most = state->right_most->right; + } + else + { + /* If tmp is null, that means, we have to insert new node at root. */ + if(tmp == NULL) + { + node->left = state->root; + node->left->parent = node; + + state->root = node; + } + else + { + node->left = tmp->right; + if(node->left) + node->left->parent = node; + + tmp->right = node; + if(tmp->right) + tmp->right->parent = tmp; + + } + state->right_most = node; + } +} + +/* Insert binary node into the parse tree. */ +static void +p_insert_into_tree(ParserState* state, ParseNode* node) +{ + p_insert_into_tree_all(state, node, 0); +} + +/* Insert unary node into the parse tree. */ +static void +p_insert_into_tree_unary(ParserState* state, ParseNode* node) +{ + p_insert_into_tree_all(state, node, 1); +} + +/* Recursive call to free every node of parse-tree. */ +static void +p_destroy_all_nodes(ParseNode* node) +{ + if(node == NULL) + return; + p_destroy_all_nodes(node->left); + p_destroy_all_nodes(node->right); + /* Don't call free for tokens, as they are allocated and freed in lexer. */ + /* WARNING: If node->value is freed elsewhere, please assign it NULL before calling p_destroy_all_nodes(). */ + if(node->value) + free(node->value); + free(node); +} + +/* Create parser state. */ +ParserState* +p_create_parser(const gchar* input, MPEquationOptions* options) +{ + ParserState* state; + state = (ParserState*) malloc(sizeof(ParserState)); + assert(state != NULL); + state->lexer = l_create_lexer(input, state); + state->root = NULL; + state->depth_level = 0; + state->right_most = NULL; + state->options = options; + state->error = 0; + state->error_token = NULL; + return state; +} + +static guint statement (ParserState*); +/* Start parsing input string. And call evaluate on success. */ +guint +p_parse(ParserState* state) +{ + guint ret; + LexerToken* token; + MPNumber* ans; + l_insert_all_tokens(state->lexer); + ret = statement(state); + token = l_get_next_token(state->lexer); + if(token->token_type == T_ASSIGN) + { + token = l_get_next_token(state->lexer); + if(token->token_type != PL_EOS) + { + /* Full string is not parsed. */ + if(!state->error) + set_error(state, PARSER_ERR_INVALID, token->string); + return PARSER_ERR_INVALID; + } + } + if(token->token_type != PL_EOS) + { + /* Full string is not parsed. */ + if(!state->error) + set_error(state, PARSER_ERR_INVALID, token->string); + return PARSER_ERR_INVALID; + } + if(ret == 0) + /* Input can't be parsed with grammar. */ + return PARSER_ERR_INVALID; + ans = (MPNumber *) (*(state->root->evaluate))(state->root); + if(ans) + { + mp_set_from_mp(ans, &state->ret); + free(ans); + return PARSER_ERR_NONE; + } + return PARSER_ERR_INVALID; +} + +/* Destroy parser state. */ +void +p_destroy_parser(ParserState* state) +{ + /* If state has a parse tree, destroy it first. */ + if(state->root) + { + p_destroy_all_nodes(state->root); + } + l_destroy_lexer(state->lexer); + free(state); +} + +/* LL (*) parser. Lookahead count depends on tokens. Handle with care. :P */ + +static guint expression(ParserState* state); +static guint expression_1(ParserState* state); +static guint expression_2(ParserState* state); +static guint unit(ParserState* state); +static guint variable(ParserState* state); +static guint term(ParserState* state); +static guint term_2(ParserState* state); + +/* Helping function to p_check_variable. */ +static gchar* +utf8_next_char(const gchar* c) +{ + c++; + while((*c & 0xC0) == 0x80) + c++; + return (gchar *)c; +} + +/* Check if string "name" is a valid variable for given ParserState. It is the same code, used to get the value of variable in parserfunc.c. */ +static gboolean +p_check_variable(ParserState* state, gchar* name) +{ + gint result = 0; + + const gchar *c, *next; + gchar *buffer; + MPNumber temp; + + if(!(state->get_variable)) + { + return FALSE; + } + + /* If defined, then get the variable */ + if((*(state->get_variable))(state, name, &temp)) + { + return TRUE; + } + + /* If has more than one character then assume a multiplication of variables */ + if(utf8_next_char(name)[0] != '\0') + { + result = 1; + buffer = (gchar*) malloc(sizeof(gchar) * strlen(name)); + for(c = name; *c != '\0'; c = next) + { + next = utf8_next_char(c); + snprintf(buffer, next - c + 1, "%s", c); + if(!(*(state->get_variable))(state, buffer, &temp)) + { + result = 0; + break; + } + } + free(buffer); + } + if(!result) + { + return FALSE; + } + return TRUE; +} + +static guint +statement(ParserState* state) +{ + LexerToken* token; + LexerToken* token_old; + ParseNode* node; + token = l_get_next_token(state->lexer); + if(token->token_type == T_VARIABLE) + { + token_old = token; + token = l_get_next_token(state->lexer); + if(token->token_type == T_ASSIGN) + { + /* VARIABLE = expression. */ + + node = p_create_node(state, token_old, p_make_precedence_p(state, P_NumberVariable), p_get_associativity(token_old), NULL, pf_none); + p_insert_into_tree(state, node); + + node = p_create_node(state, token, 0, p_get_associativity(token), NULL, pf_set_var); + p_insert_into_tree(state, node); + + if(!expression(state)) + return 0; + return 1; + } + else if(token->token_type == T_IN) + { + /* UNIT in UNIT. */ + l_roll_back(state->lexer); + l_roll_back(state->lexer); + if(!unit(state)) + return 0; + l_get_next_token(state->lexer); + + node = p_create_node(state, token, 0, p_get_associativity(token), NULL, pf_convert_1); + p_insert_into_tree(state, node); + + if(!unit(state)) + return 0; + return 1; + } + else if(token->token_type == T_SUP_NUMBER) + { + token = l_get_next_token(state->lexer); + if(token->token_type == T_IN) + { + /* UNIT in UNIT */ + l_roll_back(state->lexer); + l_roll_back(state->lexer); + l_roll_back(state->lexer); + if(!unit(state)) + return 0; + l_get_next_token(state->lexer); + + node = p_create_node(state, token, 0, p_get_associativity(token), NULL, pf_convert_1); + p_insert_into_tree(state, node); + + if(!unit(state)) + return 0; + return 1; + } + else + { + l_roll_back(state->lexer); + l_roll_back(state->lexer); + l_roll_back(state->lexer); + if(!expression(state)) + return 0; + return 1; + } + } + else + { + l_roll_back(state->lexer); + l_roll_back(state->lexer); + if(!expression(state)) + return 0; + return 1; + } + } + else if(token->token_type == T_NUMBER) + { + token_old = token; + token = l_get_next_token(state->lexer); + if(token->token_type == T_VARIABLE) + { + token = l_get_next_token(state->lexer); + if(token->token_type == T_IN) + { + /* NUMBER UNIT in UNIT */ + l_roll_back(state->lexer); + l_roll_back(state->lexer); + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token), NULL, pf_constant); + p_insert_into_tree(state, node); + + if(!unit(state)) + return 0; + token = l_get_next_token(state->lexer); + + node = p_create_node(state, token, 0, p_get_associativity(token), NULL, pf_convert_number); + p_insert_into_tree(state, node); + + if(!unit(state)) + return 0; + return 1; + } + else if(token->token_type == T_SUP_NUMBER) + { + token = l_get_next_token(state->lexer); + if(token->token_type == T_IN) + { + /* NUMBER UNIT in UNIT */ + l_roll_back(state->lexer); + l_roll_back(state->lexer); + l_roll_back(state->lexer); + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token), NULL, pf_constant); + p_insert_into_tree(state, node); + + if(!unit(state)) + return 0; + token = l_get_next_token(state->lexer); + + node = p_create_node(state, token, 0, p_get_associativity(token), NULL, pf_convert_number); + p_insert_into_tree(state, node); + + if(!unit(state)) + return 0; + return 1; + } + else + { + l_roll_back(state->lexer); + l_roll_back(state->lexer); + l_roll_back(state->lexer); + l_roll_back(state->lexer); + if(!expression(state)) + return 0; + return 1; + } + } + else + { + l_roll_back(state->lexer); + l_roll_back(state->lexer); + l_roll_back(state->lexer); + if(!expression(state)) + return 0; + return 1; + } + } + else + { + l_roll_back(state->lexer); + l_roll_back(state->lexer); + if(!expression(state)) + return 0; + return 1; + } + } + else + { + l_roll_back(state->lexer); + if(!expression(state)) + return 0; + return 1; + } +} + +static guint +unit(ParserState* state) +{ + LexerToken* token; + LexerToken* token_old; + ParseNode* node; + token = l_get_next_token(state->lexer); + if(token->token_type == T_VARIABLE) + { + token_old = token; + token = l_get_next_token(state->lexer); + if(token->token_type == T_SUP_NUMBER) + { + /* VARIABLE POWER */ + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token_old), pf_make_unit(token_old->string, token->string), pf_none); + p_insert_into_tree(state, node); + + return 1; + } + else + { + l_roll_back(state->lexer); + /* VARIABLE */ + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token_old), NULL, pf_none); + p_insert_into_tree(state, node); + + return 1; + } + } + else + { + l_roll_back(state->lexer); + return 0; + } +} + +static guint +expression(ParserState* state) +{ + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; +} + +static guint +expression_1(ParserState* state) +{ + LexerToken* token; + ParseNode* node; + token = l_get_next_token(state->lexer); + if(token->token_type == PL_EOS + ||token->token_type == T_ASSIGN) + { + l_roll_back(state->lexer); + return 0; + } + if(token->token_type == T_L_R_BRACKET) + { + state->depth_level++; + if(!expression(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_R_R_BRACKET) + { + state->depth_level--; + return 1; + } + else + //Expected ")" here... + return 0; + } + else if(token->token_type == T_L_S_BRACKET) + { + state->depth_level++; + + /* Give round, preference of P_Unknown aka 0, to keep it on the top of expression. */ + + node = p_create_node(state, token, p_make_precedence_p(state, P_Unknown), p_get_associativity(token), NULL, pf_do_round); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_R_S_BRACKET) + { + state->depth_level--; + return 1; + } + else + //Expected "]" here... + return 0; + } + else if(token->token_type == T_L_C_BRACKET) + { + state->depth_level++; + + /* Give fraction, preference of P_Unknown aka 0, to keep it on the top of expression. */ + + node = p_create_node(state, token, p_make_precedence_p(state, P_Unknown), p_get_associativity(token), NULL, pf_do_fraction); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_R_C_BRACKET) + { + state->depth_level--; + return 1; + } + else + //Expected "}" here... + return 0; + } + else if(token->token_type == T_ABS) + { + state->depth_level++; + + /* Give abs, preference of P_Unknown aka 0, to keep it on the top of expression. */ + + node = p_create_node(state, token, p_make_precedence_p(state, P_Unknown), p_get_associativity(token), NULL, pf_do_abs); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_ABS) + { + state->depth_level--; + return 1; + } + else + //Expected "|" here... + return 0; + } + else if(token->token_type == T_NOT) + { + /* NOT expression */ + + node = p_create_node(state, token, p_make_precedence_p(state, P_Not), p_get_associativity(token), NULL, pf_do_not); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + return 1; + } + else if(token->token_type == T_NUMBER) + { + /* NUMBER */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_constant); + p_insert_into_tree(state, node); + + token = l_get_next_token(state->lexer); + l_roll_back(state->lexer); + + if(token->token_type == T_FUNCTION + ||token->token_type == T_VARIABLE + ||token->token_type == T_SUB_NUMBER + ||token->token_type == T_ROOT + ||token->token_type == T_ROOT_3 + ||token->token_type == T_ROOT_4) + { + /* NUMBER variable. */ + + node = p_create_node(state, NULL, p_make_precedence_p(state, P_Multiply), p_get_associativity_p(P_Multiply), NULL, pf_do_multiply); + p_insert_into_tree(state, node); + + if(!variable(state)) + return 0; + else + return 1; + } + else + { + return 1; + } + } + else if(token->token_type == T_L_FLOOR) + { + state->depth_level++; + /* Give floor, preference of P_Unknown aka 0, to keep it on the top of expression. */ + + node = p_create_node(state, NULL, p_make_precedence_p(state, P_Unknown), p_get_associativity_p(P_Unknown), NULL, pf_do_floor); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_R_FLOOR) + { + state->depth_level--; + return 1; + } + else + //Expected ⌋ here... + return 0; + } + else if(token->token_type == T_L_CEILING) + { + state->depth_level++; + /* Give ceiling, preference of P_Unknown aka 0, to keep it on the top of expression. */ + + node = p_create_node(state, NULL, p_make_precedence_p(state, P_Unknown), p_get_associativity_p(P_Unknown), NULL, pf_do_ceiling); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_R_CEILING) + { + state->depth_level--; + return 1; + } + else + //Expected ⌉ here... + return 0; + } + else if(token->token_type == T_SUBTRACT) + { + /* UnaryMinus expression */ + + node = p_create_node(state, token, p_make_precedence_p(state, P_UnaryMinus), p_get_associativity_p(P_UnaryMinus), NULL, pf_unary_minus); + p_insert_into_tree_unary(state, node); + + if(!expression_1(state)) + return 0; + return 1; + } + else if(token->token_type == T_ADD) + { + token = l_get_next_token(state->lexer); + if(token->token_type == T_NUMBER) + { + /* UnaryPlus expression */ + /* Ignore T_ADD. It is not required. */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_constant); + p_insert_into_tree(state, node); + return 1; + } + else + { + return 0; + } + } + else + { + l_roll_back(state->lexer); + if(!variable(state)) + return 0; + else + return 1; + } +} + +static guint +expression_2(ParserState* state) +{ + LexerToken* token; + ParseNode* node; + token = l_get_next_token(state->lexer); + if(token->token_type == T_L_R_BRACKET) + { + /* expression "(" expression ")" */ + + node = p_create_node(state, NULL, p_make_precedence_p(state, P_Multiply), p_get_associativity_p(P_Multiply), NULL, pf_do_multiply); + p_insert_into_tree(state, node); + + state->depth_level++; + if(!expression(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_R_R_BRACKET) + { + state->depth_level--; + if(!expression_2(state)) + return 0; + return 1; + } + else + { + return 0; + } + } + else if(token->token_type == T_POWER) + { + /* expression "^" expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_x_pow_y); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_SUP_NUMBER) + { + /* expression T_SUP_NUMBER */ + + node = p_create_node(state, NULL, p_make_precedence_p(state, P_Power), p_get_associativity_p(P_Power), NULL, pf_do_x_pow_y_int); + p_insert_into_tree(state, node); + + node = p_create_node(state, token, p_make_precedence_p(state, P_NumberVariable), p_get_associativity_p(P_NumberVariable), NULL, pf_none); + p_insert_into_tree(state, node); + + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_NSUP_NUMBER) + { + /* expression T_NSUP_NUMBER */ + + node = p_create_node(state, NULL, p_make_precedence_p(state, P_Power), p_get_associativity_p(P_Power), NULL, pf_do_x_pow_y_int); + p_insert_into_tree(state, node); + + node = p_create_node(state, token, p_make_precedence_p(state, P_NumberVariable), p_get_associativity_p(P_NumberVariable), NULL, pf_none); + p_insert_into_tree(state, node); + + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_FACTORIAL) + { + /* expression T_FACTORIAL */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_factorial); + p_insert_into_tree_unary(state, node); + + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_MULTIPLY) + { + /* expression T_MULTIPLY expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_multiply); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_PERCENTAGE) + { + /* expression % */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_percent); + p_insert_into_tree_unary(state, node); + + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_AND) + { + /* expression T_AND expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_and); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_OR) + { + /* expression T_OR expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_or); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_XOR) + { + /* expression T_XOR expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_xor); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_DIV) + { + /* expression T_DIV expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_divide); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_MOD) + { + /* expression T_MOD expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_mod); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_ADD) + { + /* expression T_ADD expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_add); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_PERCENTAGE) + { + //FIXME: This condition needs to be verified for all cases.. :( + if(node->right->precedence > P_Percentage) + { + node->precedence = P_Percentage; + node->evaluate = pf_do_add_percent; + return 1; + } + else + { + /* Assume '%' to be part of 'expression T_PERCENTAGE' statement. */ + l_roll_back(state->lexer); + if(!expression_2(state)) + return 1; + } + } + else + { + l_roll_back(state->lexer); + } + if(!expression_2(state)) + return 0; + return 1; + } + else if(token->token_type == T_SUBTRACT) + { + /* expression T_SUBTRACT expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_subtract); + p_insert_into_tree(state, node); + + if(!expression_1(state)) + return 0; + token = l_get_next_token(state->lexer); + if(token->token_type == T_PERCENTAGE) + { + //FIXME: This condition needs to be verified for all cases.. :( + if(node->right->precedence > P_Percentage) + { + node->precedence = P_Percentage; + node->evaluate = pf_do_subtract_percent; + return 1; + } + else + { + /* Assume '%' to be part of 'expression T_PERCENTAGE' statement. */ + l_roll_back(state->lexer); + if(!expression_2 (state)) + return 1; + } + } + else + { + l_roll_back(state->lexer); + } + if(!expression_2(state)) + return 0; + return 1; + } + else + { + l_roll_back(state->lexer); + return 1; + } +} + +static guint +variable(ParserState* state) +{ + LexerToken* token; + LexerToken* token_old; + ParseNode* node; + token = l_get_next_token(state->lexer); + if(token->token_type == T_FUNCTION) + { + token_old = token; + token = l_get_next_token(state->lexer); + if(token->token_type == T_SUP_NUMBER) + { + /* FUNCTION SUP_NUMBER expression */ + /* Pass power as void * value. That will be taken care in pf_apply_func_with_powre. */ + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token_old), token, pf_apply_func_with_power); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + return 1; + } + else if(token->token_type == T_NSUP_NUMBER) + { + /* FUNCTION NSUP_NUMBER expression */ + /* Pass power as void * value. That will be taken care in pf_apply_func_with_npowre. */ + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token_old), token, pf_apply_func_with_npower); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + return 1; + } + else + { + l_roll_back(state->lexer); + /* FUNCTION expression */ + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token_old), NULL, pf_apply_func); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + return 1; + } + } + else if(token->token_type == T_SUB_NUMBER) + { + token_old = token; + token = l_get_next_token(state->lexer); + if(token->token_type == T_ROOT) + { + /* SUB_NUM ROOT expression */ + /* Pass SUB_NUM as void* value in node. pf_do_nth_root will take care of it. */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), token_old, pf_do_nth_root); + p_insert_into_tree_unary(state, node); + + if(!expression (state)) + return 0; + return 1; + } + else + { + return 0; + } + } + else if(token->token_type == T_ROOT) + { + /* ROOT expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_sqrt); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + return 1; + } + else if(token->token_type == T_ROOT_3) + { + /* ROOT_3 expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_root_3); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + return 1; + } + else if(token->token_type == T_ROOT_4) + { + /* ROOT_4 expression */ + + node = p_create_node(state, token, p_make_precedence_t(state, token->token_type), p_get_associativity(token), NULL, pf_do_root_4); + p_insert_into_tree_unary(state, node); + + if(!expression(state)) + return 0; + return 1; + } + else if(token->token_type == T_VARIABLE) + { + l_roll_back(state->lexer); + //TODO: unknown function ERROR for (T_VARIABLE T_SUP_NUMBER expression). + if(!term(state)) + return 0; + return 1; + } + else + { + return 0; + } +} + +static guint +term(ParserState* state) +{ + LexerToken* token; + LexerToken* token_old; + ParseNode* node; + token = l_get_next_token(state->lexer); + if(token->token_type == T_VARIABLE) + { + token_old = token; + /* Check if the token is a valid variable or not. */ + if(!p_check_variable(state, token->string)) + { + set_error(state, PARSER_ERR_UNKNOWN_VARIABLE, token->string); + return 0; + } + token = l_get_next_token(state->lexer); + if(token->token_type == T_SUP_NUMBER) + { + /* VARIABLE SUP_NUMBER */ + /* Pass power as void* value. pf_get_variable_with_power will take care of it. */ + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token_old), token, pf_get_variable_with_power); + p_insert_into_tree(state, node); + + } + else + { + l_roll_back(state->lexer); + /* VARIABLE */ + + node = p_create_node(state, token_old, p_make_precedence_t(state, token_old->token_type), p_get_associativity(token_old), NULL, pf_get_variable); + p_insert_into_tree(state, node); + + } + if(!term_2(state)) + return 0; + return 1; + } + else + { + return 0; + } +} + +static guint +term_2(ParserState* state) +{ + LexerToken* token; + ParseNode* node; + token = l_get_next_token(state->lexer); + l_roll_back(state->lexer); + if(token->token_type == PL_EOS + ||token->token_type == T_ASSIGN) + { + return 1; + } + if(token->token_type == T_VARIABLE) + { + /* Insert multiply in between two distinct (variable). */ + + node = p_create_node(state, NULL, p_make_precedence_p(state, P_Multiply), p_get_associativity_p(P_Multiply), NULL, pf_do_multiply); + p_insert_into_tree(state, node); + + if(!term(state)) + return 0; + return 1; + } + else + { + return 1; + } +} diff --git a/src/parser.h b/src/parser.h new file mode 100644 index 0000000..3efaa03 --- /dev/null +++ b/src/parser.h @@ -0,0 +1,79 @@ +#ifndef PARSER_H +#define PARSER_H + +#include + +#include "mp-equation.h" +#include "mp.h" + +/* Operator Associativity. */ +typedef enum +{ + LEFT_ASSOCIATIVE, + RIGHT_ASSOCIATIVE +} Associativity; + +/* Operator Precedence. */ +typedef enum +{ + P_Unknown = 0, + P_AddSubtract=1, + P_Multiply=2, + P_Mod=3, + P_Divide=4, + P_Not=5, + P_Root=6, + P_Function=7, + P_Boolean=8, + P_Percentage=9, + /* UnaryMinus and Power must have same precedence. */ + P_UnaryMinus=10, + P_Power=10, + P_Factorial=11, + P_NumberVariable=12, + /* P_Depth should be always at the bottom. It stops node jumping off the current depth level. */ + P_Depth +} Precedence; + +/* ParseNode structure for parse tree. */ +typedef struct parse_node +{ + struct parse_node *parent; + struct parse_node *left, *right; + LexerToken *token; + guint precedence; + Associativity associativity; + void* value; + struct parser_state* state; + void* (*evaluate) (struct parse_node* self); +} ParseNode; + +/* ParserState structure. Stores parser state. */ +typedef struct parser_state +{ + ParseNode *root; + ParseNode *right_most; + LexerState *lexer; + guint depth_level; + MPEquationOptions *options; + int error; + char *error_token; + MPNumber ret; + int (*variable_is_defined)(struct parser_state *state, const char *name); + int (*get_variable)(struct parser_state *state, const char *name, MPNumber *z); + void (*set_variable)(struct parser_state *state, const char *name, const MPNumber *x); + int (*function_is_defined)(struct parser_state *state, const char *name); + int (*get_function)(struct parser_state *state, const char *name, const MPNumber *x, MPNumber *z); + int (*convert)(struct parser_state *state, const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z); +} ParserState; + +/* Create ParserState object. */ +ParserState* p_create_parser(const gchar*, MPEquationOptions*); + +/* Destroy ParserState object. */ +void p_destroy_parser(ParserState*); + +/* Parse string from ParserState. */ +guint p_parse(ParserState*); + +#endif /* PARSER_H */ diff --git a/src/parserfunc.c b/src/parserfunc.c new file mode 100644 index 0000000..edd34f6 --- /dev/null +++ b/src/parserfunc.c @@ -0,0 +1,967 @@ +#include +#include +#include +#include + +#include "parser.h" +#include "parserfunc.h" + +/* Register error variables in ParserState structure. */ +void +set_error(ParserState* state, gint errorno, const gchar *token) +{ + state->error = errorno; + if(token) + state->error_token = strdup(token); +} + +/* Unused function pointer. This won't be called anytime. */ +void* +pf_none(ParseNode* self) +{ + return NULL; +} + +/* Set variable. */ +void* +pf_set_var(ParseNode* self) +{ + MPNumber* val; + val = (MPNumber *) (*(self->right->evaluate))(self->right); + if(!val || !(self->state->set_variable)) + { + if(val) + free(val); + return NULL; + } + (*(self->state->set_variable))(self->state, self->left->token->string, val); + return val; +} + +/* Converts Number from one unit to other. */ +void* +pf_convert_number(ParseNode* self) +{ + gchar* from; + gchar* to; + gint free_from = 0; + gint free_to = 0; + MPNumber tmp; + MPNumber* ans; + ans = (MPNumber *) malloc(sizeof(MPNumber)); + if(self->left->value) + { + from = (gchar*) self->left->value; + free_from = 1; + } + else + from = self->left->token->string; + if(self->right->value) + { + to = (gchar*) self->right->value; + free_to = 1; + } + else + to = self->right->token->string; + + if(mp_set_from_string(self->left->left->token->string, self->state->options->base, &tmp) != 0) + { + free(ans); + ans = NULL; + goto END_PF_CONVERT_NUMBER; + } + if(!(self->state->convert)) + { + free(ans); + ans = NULL; + goto END_PF_CONVERT_NUMBER; + } + if(!(*(self->state->convert))(self->state, &tmp, from, to, ans)) + { + set_error(self->state, PARSER_ERR_UNKNOWN_CONVERSION, NULL); + free(ans); + ans = NULL; + } +END_PF_CONVERT_NUMBER: + if(free_from) + { + g_free(self->left->value); + self->left->value = NULL; + } + if(free_to) + { + g_free(self->right->value); + self->right->value = NULL; + } + return ans; +} + +/* Conversion rate. */ +void* +pf_convert_1(ParseNode* self ) +{ + gchar* from; + gchar* to; + gint free_from = 0; + gint free_to = 0; + MPNumber tmp; + MPNumber* ans; + ans = (MPNumber *) malloc(sizeof(MPNumber)); + if(self->left->value) + { + from = (gchar*) self->left->value; + free_from = 1; + } + else + from = self->left->token->string; + if(self->right->value) + { + to = (gchar*) self->right->value; + free_to = 1; + } + else + to = self->right->token->string; + mp_set_from_integer(1, &tmp); + if(!(self->state->convert)) + { + free(ans); + return NULL; + } + if(!(*(self->state->convert))(self->state, &tmp, from, to, ans)) + { + set_error(self->state, PARSER_ERR_UNKNOWN_CONVERSION, NULL); + free(ans); + ans = NULL; + } + if(free_from) + { + g_free(self->left->value); + self->left->value = NULL; + } + if(free_to) + { + g_free(self->right->value); + self->right->value = NULL; + } + return ans; +} + +/* Join source unit and power. */ +gchar* +pf_make_unit(gchar* source, gchar* power) +{ + return g_strjoin(NULL, source, power, NULL); +} + +static gchar * +utf8_next_char(const gchar *c) +{ + c++; + while((*c & 0xC0) == 0x80) + c++; + return(gchar *) c; +} + +/* Get value of variable. */ +void* +pf_get_variable(ParseNode* self) +{ + gint result = 0; + + const gchar *c, *next; + gchar *buffer; + MPNumber value; + + MPNumber t; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + + if(!(self->state->get_variable)) + { + free(ans); + return NULL; + } + + /* If defined, then get the variable */ + if((*(self->state->get_variable))(self->state, self->token->string, ans)) + { + return ans; + } + + /* If has more than one character then assume a multiplication of variables */ + if(utf8_next_char(self->token->string)[0] != '\0') + { + result = 1; + buffer = (gchar*) malloc(sizeof(gchar) * strlen(self->token->string)); + mp_set_from_integer(1, &value); + for(c = self->token->string; *c != '\0'; c = next) + { + next = utf8_next_char(c); + snprintf(buffer, next - c + 1, "%s", c); + if(!(*(self->state->get_variable))(self->state, buffer, &t)) + { + result = 0; + break; + } + mp_multiply(&value, &t, &value); + } + free(buffer); + if(result) + mp_set_from_mp(&value, ans); + } + if(!result) + { + free (ans); + ans = NULL; + set_error(self->state, PARSER_ERR_UNKNOWN_VARIABLE, self->token->string); + } + return ans; +} + +/* Get value of variable with power. */ +void* +pf_get_variable_with_power(ParseNode* self) +{ + gint result = 0; + gint pow; + + const gchar *c, *next; + gchar *buffer; + MPNumber value; + + MPNumber t; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + pow = super_atoi(((LexerToken*) self->value)->string); + + /* No need to free the memory. It is allocated and freed somewhere else. */ + self->value = NULL; + + if(!(self->state->get_variable)) + { + free(ans); + return NULL; + } + + /* If defined, then get the variable */ + if((*(self->state->get_variable))(self->state, self->token->string, ans)) + { + mp_xpowy_integer(ans, pow, ans); + return ans; + } + + /* If has more than one character then assume a multiplication of variables */ + if(utf8_next_char(self->token->string)[0] != '\0') + { + result = 1; + buffer = (gchar*) malloc(sizeof(gchar) * strlen(self->token->string)); + mp_set_from_integer(1, &value); + for(c = self->token->string; *c != '\0'; c = next) + { + next = utf8_next_char(c); + snprintf(buffer, next - c + 1, "%s", c); + if(!(*(self->state->get_variable))(self->state, buffer, &t)) + { + result = 0; + break; + } + + /* If last term do power */ + if(*next == '\0') + mp_xpowy_integer(&t, pow, &t); + mp_multiply(&value, &t, &value); + } + free(buffer); + if(result) + mp_set_from_mp(&value, ans); + } + if(!result) + { + free(ans); + ans = NULL; + set_error(self->state, PARSER_ERR_UNKNOWN_VARIABLE, self->token->string); + } + return ans; +} + +/* Apply function on child. */ +void* +pf_apply_func(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!(self->state->get_function)) + { + free(val); + free(ans); + return NULL; + } + if(!val) + { + free(ans); + return NULL; + } + if(!(*(self->state->get_function))(self->state, self->token->string, val, ans)) + { + free(val); + free(ans); + set_error(self->state, PARSER_ERR_UNKNOWN_FUNCTION, self->token->string); + return NULL; + } + free(val); + return ans; +} + +/* Apply function with +ve power. */ +void* +pf_apply_func_with_power(ParseNode* self) +{ + MPNumber* val; + MPNumber* tmp; + MPNumber* ans; + gint pow; + tmp = (MPNumber*) malloc(sizeof(MPNumber)); + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!(self->state->get_function)) + { + free(tmp); + free(ans); + free(val); + self->value = NULL; + return NULL; + } + if(!val) + { + free(tmp); + free(ans); + self->value = NULL; + return NULL; + } + if(!(*(self->state->get_function))(self->state, self->token->string, val, tmp)) + { + free(tmp); + free(ans); + free(val); + self->value = NULL; + set_error(self->state, PARSER_ERR_UNKNOWN_FUNCTION, self->token->string); + return NULL; + } + pow = super_atoi(((LexerToken*) self->value)->string); + mp_xpowy_integer(tmp, pow, ans); + free(val); + free(tmp); + self->value = NULL; + return ans; +} + +/* Apply function with -ve power. */ +void* +pf_apply_func_with_npower(ParseNode* self) +{ + MPNumber* val; + MPNumber* tmp; + MPNumber* ans; + gint pow; + gchar* inv_name; + inv_name = (gchar*) malloc(sizeof(gchar) * strlen(self->token->string) + strlen("⁻¹") + 1); + strcpy(inv_name, self->token->string); + strcat(inv_name, "⁻¹"); + tmp = (MPNumber*) malloc(sizeof(MPNumber)); + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(tmp); + free(inv_name); + free(ans); + self->value = NULL; + return NULL; + } + if(!(self->state->get_function)) + { + free(tmp); + free(ans); + free(inv_name); + self->value = NULL; + return NULL; + } + if(!(*(self->state->get_function))(self->state, inv_name, val, tmp)) + { + free(tmp); + free(ans); + free(val); + free(inv_name); + self->value = NULL; + set_error(self->state, PARSER_ERR_UNKNOWN_FUNCTION, self->token->string); + return NULL; + } + pow = super_atoi(((LexerToken*) self->value)->string); + mp_xpowy_integer(tmp, -pow, ans); + free(val); + free(tmp); + free(inv_name); + self->value = NULL; + return ans; +} + +/* Find nth root of child. */ +void* +pf_do_nth_root(ParseNode* self) +{ + MPNumber* val; + gint pow; + MPNumber* ans; + pow = sub_atoi(((LexerToken*) self->value)->string); + self->value = NULL; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_root(val, pow, ans); + free(val); + return ans; +} + +/* Find sqrt of child. */ +void* +pf_do_sqrt(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_sqrt(val, ans); + free(val); + return ans; +} + +/* Find 3rd root of child. */ +void* +pf_do_root_3(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_root(val, 3, ans); + free(val); + return ans; +} + +/* Find 4th root of child. */ +void* +pf_do_root_4(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_root(val, 4, ans); + free(val); + return ans; +} + +/* Apply floor function. */ +void* +pf_do_floor(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_floor(val, ans); + free(val); + return ans; +} + +/* Apply ceiling function. */ +void* pf_do_ceiling (ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_ceiling(val, ans); + free(val); + return ans; +} + +/* Round off. */ +void* +pf_do_round(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_round(val, ans); + free(val); + return ans; +} + +/* Fraction. */ +void* +pf_do_fraction(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_fractional_part(val, ans); + free(val); + return ans; +} + +/* Absolute value. */ +void* +pf_do_abs(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_abs(val, ans); + free(val); + return ans; +} + +/* Find x^y for x and y being MPNumber. */ +void* +pf_do_x_pow_y(ParseNode* self) +{ + MPNumber* val; + MPNumber* pow; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->left->evaluate))(self->left); + pow = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val || !pow) + { + if(val) + free(val); + if(pow) + free(pow); + free(ans); + return NULL; + } + mp_xpowy(val, pow, ans); + free(val); + free(pow); + return ans; +} + +/* Find x^y for MPNumber x and integer y. */ +void* +pf_do_x_pow_y_int(ParseNode* self) +{ + MPNumber* val; + gint pow; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->left->evaluate))(self->left); + pow = super_atoi(self->right->token->string); + if(!val) + { + free(ans); + return NULL; + } + mp_xpowy_integer(val, pow, ans); + free(val); + return ans; +} + +/* Find factorial. */ +void* +pf_do_factorial(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_factorial(val, ans); + free(val); + return ans; +} + +/* Apply unary minus. */ +void* +pf_unary_minus(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_invert_sign(val, ans); + free(val); + return ans; +} + +/* Divide. */ +void* +pf_do_divide(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free(right); + free(ans); + return NULL; + } + mp_divide(left, right, ans); + free(left); + free(right); + return ans; +} + +/* Modulus. */ +void* +pf_do_mod(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free(right); + free(ans); + return NULL; + } + mp_modulus_divide(left, right, ans); + free(left); + free(right); + return ans; +} + +/* Multiply two numbers. */ +void* +pf_do_multiply(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free(right); + free(ans); + return NULL; + } + mp_multiply(left, right, ans); + free(left); + free(right); + return ans; +} + +/* Subtract two numbers. */ +void* +pf_do_subtract(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free (right); + free(ans); + return NULL; + } + mp_subtract(left, right, ans); + free(left); + free(right); + return ans; +} + +/* Add two numbers. */ +void* +pf_do_add(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free(right); + free(ans); + return NULL; + } + mp_add(left, right, ans); + free(left); + free(right); + return ans; +} + +/*Add (x) Percentage to value. */ +void* +pf_do_add_percent(ParseNode* self) +{ + MPNumber* ans; + MPNumber* val; + MPNumber* per; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->left->evaluate))(self->left); + per = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val || !per) + { + if(val) + free(val); + if(per) + free(per); + free(ans); + return NULL; + } + mp_add_integer(per, 100, per); + mp_divide_integer(per, 100, per); + mp_multiply(val, per, ans); + free(val); + free(per); + return ans; +} + +/* Subtract (x) Percentage to value. */ +void* +pf_do_subtract_percent(ParseNode* self) +{ + MPNumber* ans; + MPNumber* val; + MPNumber* per; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->left->evaluate))(self->left); + per = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val || !per) + { + if(val) + free(val); + if(per) + free(per); + free(ans); + return NULL; + } + mp_add_integer(per, -100, per); + mp_divide_integer(per, -100, per); + mp_multiply(val, per, ans); + free(val); + free(per); + return ans; +} + +/* Converts a constant into percentage. */ +void* +pf_do_percent(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + mp_divide_integer(val, 100, ans); + free(val); + return ans; +} + +/* NOT. */ +void* +pf_do_not(ParseNode* self) +{ + MPNumber* val; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + val = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!val) + { + free(ans); + return NULL; + } + if(!mp_is_overflow(val, self->state->options->wordlen)) + { + set_error(self->state, PARSER_ERR_OVERFLOW, NULL); + free(ans); + ans = NULL; + } + mp_not(val, self->state->options->wordlen, ans); + free(val); + return ans; +} + +/* AND. */ +void* +pf_do_and(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free(right); + free(ans); + return NULL; + } + mp_and(left, right, ans); + free(left); + free(right); + return ans; +} + +/* OR. */ +void* +pf_do_or(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free(right); + free(ans); + return NULL; + } + mp_or(left, right, ans); + free(left); + free(right); + return ans; +} + +/* XOR. */ +void* +pf_do_xor(ParseNode* self) +{ + MPNumber* left; + MPNumber* right; + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + left = (MPNumber*) (*(self->left->evaluate))(self->left); + right = (MPNumber*) (*(self->right->evaluate))(self->right); + if(!left || !right) + { + if(left) + free(left); + if(right) + free(right); + free(ans); + return NULL; + } + mp_xor(left, right, ans); + free(left); + free(right); + return ans; +} + +/* Constant value. Convert into MPNumber and return. */ +void* +pf_constant(ParseNode* self) +{ + MPNumber* ans; + ans = (MPNumber*) malloc(sizeof(MPNumber)); + if(mp_set_from_string(self->token->string, self->state->options->base, ans) != 0) + { + /* This should never happen, as l_check_if_number() has already passed the string once. */ + /* If the code reaches this point, something is really wrong. X( */ + free(ans); + set_error(self->state, PARSER_ERR_INVALID, self->token->string); + return NULL; + } + return ans; +} + diff --git a/src/parserfunc.h b/src/parserfunc.h new file mode 100644 index 0000000..cf049b0 --- /dev/null +++ b/src/parserfunc.h @@ -0,0 +1,80 @@ +#ifndef PAESERFUNC_H +#define PARSERFUNC_H + +#include "parser.h" + +void set_error(ParserState*, gint, const gchar*); + +void* pf_none(ParseNode*); + +void* pf_set_var(ParseNode*); + +void* pf_convert_number(ParseNode*); + +void* pf_convert_1(ParseNode*); + +gchar* pf_make_unit(gchar* source, gchar* power); + +void* pf_get_variable(ParseNode*); + +void* pf_get_variable_with_power(ParseNode*); + +void* pf_apply_func(ParseNode*); + +void* pf_apply_func_with_power(ParseNode*); + +void* pf_apply_func_with_npower(ParseNode*); + +void* pf_do_nth_root(ParseNode*); + +void* pf_do_sqrt(ParseNode*); + +void* pf_do_root_3(ParseNode*); + +void* pf_do_root_4(ParseNode*); + +void* pf_do_floor(ParseNode*); + +void* pf_do_ceiling(ParseNode*); + +void* pf_do_round(ParseNode*); + +void* pf_do_fraction(ParseNode*); + +void* pf_do_abs(ParseNode*); + +void* pf_do_x_pow_y(ParseNode*); + +void* pf_do_x_pow_y_int(ParseNode*); + +void* pf_do_factorial(ParseNode*); + +void* pf_unary_minus(ParseNode*); + +void* pf_do_divide(ParseNode*); + +void* pf_do_mod(ParseNode*); + +void* pf_do_multiply(ParseNode*); + +void* pf_do_subtract(ParseNode*); + +void* pf_do_add(ParseNode*); + +void* pf_do_add_percent(ParseNode*); + +void* pf_do_subtract_percent(ParseNode*); + +void* pf_do_percent(ParseNode*); + +void* pf_do_not(ParseNode*); + +void* pf_do_and(ParseNode*); + +void* pf_do_or(ParseNode*); + +void* pf_do_xor(ParseNode*); + +void* pf_constant(ParseNode*); + +#endif /* PARSERFUNC_H */ diff --git a/src/prelexer.c b/src/prelexer.c new file mode 100644 index 0000000..225d499 --- /dev/null +++ b/src/prelexer.c @@ -0,0 +1,214 @@ +#include +#include +#include +#include + +#include "prelexer.h" + +/* Creates a scanner state which will be useful for accessing the lexer later. */ +PreLexerState* +pl_create_scanner(const gchar* input) +{ + PreLexerState* state; + assert(input != NULL); + assert(g_utf8_validate(input, -1, NULL)); + state = (PreLexerState *) malloc(sizeof(PreLexerState)); + assert(state != NULL); + state->stream = g_strdup(input); + state->length = strlen(state->stream); /* Can't find a GLib replacement of strlen. The mailing list discussion says, it is not implemented because strlen is perfectly capable. :) */ + state->next_index = 0; + state->mark_index = 0; + return state; +} + +/* Destroy and free memory used by LexerState object. */ +void +pl_destroy_scanner(PreLexerState* state) +{ + free(state->stream); + free(state); +} + +/* Roll back last scanned unichar. */ +void +pl_roll_back(PreLexerState* state) +{ + gchar* tmp; + tmp = g_utf8_find_prev_char(state->stream, state->stream + state->next_index); + if(tmp == NULL) + /* Already at the beginning of the stram. Reset index. */ + state->next_index = 0; + else + state->next_index = tmp - state->stream; +} + +/* Get validated gunichar from input stream. */ +gunichar +pl_get_next_gunichar(PreLexerState* state) +{ + gunichar ret; + if(state->next_index >= state->length) + { + /* To prevent scanning last letter multiple times, when a single unconditional rollback is used. */ + if(state->next_index == state->length) + state->next_index++; + return 0; + } + ret = g_utf8_get_char_validated(state->stream + state->next_index, -1); + state->next_index = g_utf8_next_char(state->stream + state->next_index) - state->stream; + return ret; +} + +/* Set marker index. To be used for highlighting and error reporting. */ +void +pl_set_marker(PreLexerState* state) +{ + state->mark_index = state->next_index; +} + +/* Get marked substring. To be used for error reporting. */ +gchar* +pl_get_marked_substring(const PreLexerState* state) +{ + return g_strndup(state->stream + state->mark_index, state->next_index - state->mark_index); +} + +/* Compares a list of strings with given unichar. To be used by pl_get_next_token() only. */ +static gboolean +pl_compare_all(const gunichar ch, const gint count, gchar *arr[]) +{ + gint l; + for(l = 0; l < count; l++) + { + if(ch == g_utf8_get_char_validated(arr[l], -1)) + return TRUE; + } + return FALSE; +} + +/* Pre-Lexer tokanizer. To be called only by Lexer. */ +LexerTokenType +pl_get_next_token(PreLexerState* state) +{ + gunichar ch = pl_get_next_gunichar(state); + if(pl_compare_all(ch, 2, (gchar*[]){",","."})) + return PL_DECIMAL; + + if(g_unichar_isdigit(ch) || pl_compare_all(ch, 10, (gchar*[]){"〇","〡","〢","〣","〤","〥","〦","〧","〨","〩"})) + return PL_DIGIT; /* 0-9 */ + + if(g_unichar_isxdigit(ch)) + return PL_HEX; /* This is supposed to report just the A-F. */ + + if(pl_compare_all(ch, 10, (gchar*[]){"⁰","¹","²","³","⁴","⁵","⁶","⁷","⁸","⁹"})) + return PL_SUPER_DIGIT; + + if(pl_compare_all(ch, 1, (gchar*[]){"⁻"})) + return PL_SUPER_MINUS; + + if(pl_compare_all(ch, 10, (gchar*[]){"₀","₁","₂","₃","₄","₅","₆","₇","₈","₉"})) + return PL_SUB_DIGIT; + + if(pl_compare_all(ch, 15, (gchar*[]){"½","⅓","⅔","¼","¾","⅕","⅖","⅗","⅘","⅙","⅚","⅛","⅜","⅝","⅞"})) + return PL_FRACTION; + + if(pl_compare_all(ch, 1, (gchar*[]){"°"})) + return PL_DEGREE; + + if(pl_compare_all(ch, 1, (gchar*[]){"'"})) + return PL_MINUTE; + + if(pl_compare_all(ch, 1, (gchar*[]){"\""})) + return PL_SECOND; + + if(g_unichar_isalpha(ch)) + return PL_LETTER; /* All alphabets excluding A-F. [a-fA-F] are reported as PL_HEX. */ + + if(pl_compare_all(ch, 1, (gchar*[]){"∧"})) + return T_AND; + + if(pl_compare_all(ch, 1, (gchar*[]){"∨"})) + return T_OR; + + if(pl_compare_all(ch, 2, (gchar*[]){"⊻","⊕"})) + return T_XOR; + + if(pl_compare_all(ch, 2, (gchar*[]){"¬","~"})) + return T_NOT; + + if(pl_compare_all(ch, 1, (gchar*[]){"+"})) + return T_ADD; + + if(pl_compare_all(ch, 3, (gchar*[]){"-","−","–"})) + return T_SUBTRACT; + + if(pl_compare_all(ch, 2, (gchar*[]){"*","×"})) + return T_MULTIPLY; + + if(pl_compare_all(ch, 3, (gchar*[]){"/","∕","÷"})) + return T_DIV; + + if(pl_compare_all(ch, 1, (gchar*[]){"⌊"})) + return T_L_FLOOR; + + if(pl_compare_all(ch, 1, (gchar*[]){"⌋"})) + return T_R_FLOOR; + + if(pl_compare_all(ch, 1, (gchar*[]){"⌈"})) + return T_L_CEILING; + + if(pl_compare_all(ch, 1, (gchar*[]){"⌉"})) + return T_R_CEILING; + + if(pl_compare_all(ch, 1, (gchar*[]){"√"})) + return T_ROOT; + + if(pl_compare_all(ch, 1, (gchar*[]){"∛"})) + return T_ROOT_3; + + if(pl_compare_all(ch, 1, (gchar*[]){"∜"})) + return T_ROOT_4; + + if(pl_compare_all(ch, 1, (gchar*[]){"="})) + return T_ASSIGN; + + if(pl_compare_all(ch, 1, (gchar*[]){"("})) + return T_L_R_BRACKET; + + if(pl_compare_all(ch, 1, (gchar*[]){")"})) + return T_R_R_BRACKET; + + if(pl_compare_all(ch, 1, (gchar*[]){"["})) + return T_L_S_BRACKET; + + if(pl_compare_all(ch, 1, (gchar*[]){"]"})) + return T_R_S_BRACKET; + + if(pl_compare_all(ch, 1, (gchar*[]){"{"})) + return T_L_C_BRACKET; + + if(pl_compare_all(ch, 1, (gchar*[]){"}"})) + return T_R_C_BRACKET; + + if(pl_compare_all(ch, 1, (gchar*[]){"|"})) + return T_ABS; + + if(pl_compare_all(ch, 1, (gchar*[]){"^"})) + return T_POWER; + + if(pl_compare_all(ch, 1, (gchar*[]){"!"})) + return T_FACTORIAL; + + if(pl_compare_all(ch, 1, (gchar*[]){"%"})) + return T_PERCENTAGE; + + if(pl_compare_all(ch, 4, (gchar*[]){" ","\r","\t","\n"})) + /* Gotta ignore'Em all!!! ;) */ + return PL_SKIP; + + if(ch == 0) + return PL_EOS; + + /* There is no spoon. */ + return T_UNKNOWN; +} diff --git a/src/prelexer.h b/src/prelexer.h new file mode 100644 index 0000000..c206656 --- /dev/null +++ b/src/prelexer.h @@ -0,0 +1,93 @@ +#ifndef PRE_LEXER_H +#define PRE_LEXER_H + +#include + +/* Structure to store lexer state. */ +typedef struct +{ + gchar* stream; /* Pointer to the local copy of input string. */ + guint length; /* Length of input string; stored to reduce calls to strlen(). */ + guint next_index; /* Index of next (to be read) character from input. */ + guint mark_index; /* Location, last marked. Useful for getting substrings as part of highlighting */ +} PreLexerState; + +/* Enum for tokens generated by pre-lexer and lexer. */ +typedef enum +{ + T_UNKNOWN=0, //Unknown + + /* These are all Pre-Lexer tokens, returned by pre-lexer */ + PL_DECIMAL, //Decimal saperator + PL_DIGIT, //Decimal digit + PL_HEX, //A-F of Hex digits + PL_SUPER_DIGIT, //Super digits + PL_SUPER_MINUS, //Super minus + PL_SUB_DIGIT, //Sub digits + PL_FRACTION, //Fractions + PL_DEGREE, //Degree + PL_MINUTE, //Minutes + PL_SECOND, //10 //Seconds + PL_LETTER, //Alphabets + PL_EOS, //End of stream. Yay!! + PL_SKIP, //Skip this symbol (whitespace or newline). + + /* These are all tokens, returned by Lexer. */ + T_ADD, //Plus + T_SUBTRACT, //Minus + T_MULTIPLY, //Multiply + T_DIV, //Divide (note can't use T_DIVIDE as it is defined in *BSD as an "integer divide fault" trap type value) + T_MOD, //Modulus + T_L_FLOOR, //Floor ( Left ) + T_R_FLOOR, //20 //Floor ( Right ) + T_L_CEILING, //Ceiling ( Left ) + T_R_CEILING, //Ceiling ( Right ) + T_ROOT, //Square root + T_ROOT_3, //Cube root + T_ROOT_4, //Fourth root + T_NOT, //Bitwise NOT + T_AND, //Bitwise AND + T_OR, //Bitwise OR + T_XOR, //Bitwise XOR + T_IN, //30 //IN ( for converter ) + T_NUMBER, //Number + T_SUP_NUMBER, //Super Number + T_NSUP_NUMBER, //Negative Super Number + T_SUB_NUMBER, //Sub Number + T_FUNCTION, //Function + T_VARIABLE, //Variable name + T_ASSIGN, //= + T_L_R_BRACKET, //40 //( + T_R_R_BRACKET, //) + T_L_S_BRACKET, //[ + T_R_S_BRACKET, //] + T_L_C_BRACKET, //{ + T_R_C_BRACKET, //} + T_ABS, //| + T_POWER, //^ + T_FACTORIAL, //! + T_PERCENTAGE //49 //% +} LexerTokenType; + +/* Creates a scanner state. Useful when multiple scanners are in action. */ +PreLexerState* pl_create_scanner(const gchar*); + +/* Destroy and free memory used by LexerState object. */ +void pl_destroy_scanner(PreLexerState*); + +/* Roll back last scanned unichar. */ +void pl_roll_back(PreLexerState*); + +/* Get validated gunichar from input stream. */ +gunichar pl_get_next_gunichar(PreLexerState*); + +/* Set marker index. To be used for highlighting and error reporting. */ +void pl_set_marker(PreLexerState*); + +/* Get marked substring. To be used for error reporting. */ +gchar* pl_get_marked_substring(const PreLexerState*); + +/* Get next Pre-Lexer token from stream. */ +LexerTokenType pl_get_next_token(PreLexerState*); + +#endif /* PRE_LEXER_H */ diff --git a/src/test-mp-equation.c b/src/test-mp-equation.c new file mode 100644 index 0000000..95bb4a8 --- /dev/null +++ b/src/test-mp-equation.c @@ -0,0 +1,630 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include +#include +#include +#include +#include + +#include "mp-equation.h" +#include "mp-serializer.h" +#include "unit-manager.h" + +static MPEquationOptions options; + +static int fails = 0; +static int passes = 0; + +/* If we're not using GNU C, elide __attribute__ */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +static void pass(const char *format, ...) __attribute__((format(printf, 1, 2))); +static void fail(const char *format, ...) __attribute__((format(printf, 1, 2))); + + +static void pass(const char *format, ...) +{ +/* va_list args; + + printf(" PASS: "); + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); +*/ + passes += 1; +} + +static void fail(const char *format, ...) +{ + va_list args; + + printf("*FAIL: "); + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + fails++; +} + + +static const char * +error_code_to_string(MPErrorCode error) +{ + static char error_string[1024]; + + if (error != PARSER_ERR_MP) + return mp_error_code_to_string(error); + + snprintf(error_string, 1024, "PARSER_ERR_MP(\"%s\")", mp_get_error()); + return error_string; +} + + +static void +test(char *expression, char *expected, int expected_error) +{ + MPErrorCode error; + MPNumber result; + + error = mp_equation_parse(expression, &options, &result, NULL); + + if (error == 0) { + char *result_str; + MpSerializer *serializer; + + serializer = mp_serializer_new(MP_DISPLAY_FORMAT_FIXED, options.base, 9); + result_str = mp_serializer_to_string(serializer, &result); + g_object_unref(serializer); + + if(expected_error != PARSER_ERR_NONE) + fail("'%s' -> %s, expected error %s", expression, result_str, error_code_to_string(expected_error)); + else if(strcmp(result_str, expected) != 0) + fail("'%s' -> '%s', expected '%s'", expression, result_str, expected); + else + pass("'%s' -> '%s'", expression, result_str); + g_free(result_str); + } + else { + if(error == expected_error) + pass("'%s' -> error %s", expression, error_code_to_string(error)); + else if(expected_error == PARSER_ERR_NONE) + fail("'%s' -> error %s, expected result %s", expression, + error_code_to_string(error), expected); + else + fail("'%s' -> error %s, expected error %s", expression, + error_code_to_string(error), error_code_to_string(expected_error)); + } +} + + +static int +do_convert(const MPNumber *x, const char *x_units, const char *z_units, MPNumber *z, void *data) +{ + return unit_manager_convert_by_symbol(unit_manager_get_default(), x, x_units, z_units, z); +} + + +static void +test_conversions() +{ + memset(&options, 0, sizeof(options)); + options.base = 10; + options.wordlen = 32; + options.angle_units = MP_DEGREES; + options.convert = do_convert; + + /* Angle units */ + //test("π radians in degrees", "180", 0); + test("100 gradians in degrees", "90", 0); + + /* Length */ + test("1 meter in mm", "1000", 0); + test("1m in mm", "1000", 0); + test("1 inch in cm", "2.54", 0); + + /* Area */ + test("1m² in mm²", "1000000", 0); + + /* Volume */ + test("1m³ in mm³", "1000000000", 0); + + /* Weight */ + test("1 kg in pounds", "2.204622622", 0); + + /* Duration */ + test("1 minute in seconds", "60", 0); + test("1s in ms", "1000", 0); + + /* Temperature */ + //test("100˚C in ˚F", "", 0); + //test("0˚C in ˚F", "32", 0); + //test("0˚K in ˚C", "−273.15", 0); + test("100degC in degF", "212", 0); + test("0degC in degF", "32", 0); + test("0 K in degC", "−273.15", 0); +} + + +static int +variable_is_defined(const char *name, void *data) +{ + return strcmp (name, "x") == 0 || strcmp (name, "y") == 0; +} + + +static int +get_variable(const char *name, MPNumber *z, void *data) +{ + if (strcmp (name, "x") == 0) { + mp_set_from_integer (2, z); + return 1; + } + if (strcmp (name, "y") == 0) { + mp_set_from_integer (3, z); + return 1; + } + return 0; +} + + +static void +set_variable(const char *name, const MPNumber *x, void *data) +{ +} + +static void +test_equations() +{ + memset(&options, 0, sizeof(options)); + options.base = 10; + options.wordlen = 32; + options.angle_units = MP_DEGREES; + options.variable_is_defined = variable_is_defined; + options.get_variable = get_variable; + options.set_variable = set_variable; + + options.base = 2; + test("2₁₀", "10", 0); + + options.base = 8; + test("16434824₁₀", "76543210", 0); + + options.base = 16; + test("FF", "FF", 0); + test("18364758544493064720₁₀", "FEDCBA9876543210", 0); + + options.base = 10; + test("0₂", "0", 0); test("0₈", "0", 0); test("0", "0", 0); test("0₁₆", "0", 0); + test("1₂", "1", 0); test("1₈", "1", 0); test("1", "1", 0); test("1₁₆", "1", 0); + test("2₂", "", PARSER_ERR_INVALID); test("2₈", "2", 0); test("2", "2", 0); test("2₁₆", "2", 0); + test("3₂", "", PARSER_ERR_INVALID); test("3₈", "3", 0); test("3", "3", 0); test("3₁₆", "3", 0); + test("4₂", "", PARSER_ERR_INVALID); test("4₈", "4", 0); test("4", "4", 0); test("4₁₆", "4", 0); + test("5₂", "", PARSER_ERR_INVALID); test("5₈", "5", 0); test("5", "5", 0); test("5₁₆", "5", 0); + test("6₂", "", PARSER_ERR_INVALID); test("6₈", "6", 0); test("6", "6", 0); test("6₁₆", "6", 0); + test("7₂", "", PARSER_ERR_INVALID); test("7₈", "7", 0); test("7", "7", 0); test("7₁₆", "7", 0); + test("8₂", "", PARSER_ERR_INVALID); test("8₈", "", PARSER_ERR_INVALID); test("8", "8", 0); test("8₁₆", "8", 0); + test("9₂", "", PARSER_ERR_INVALID); test("9₈", "", PARSER_ERR_INVALID); test("9", "9", 0); test("9₁₆", "9", 0); + test("A₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("A₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("A", "", PARSER_ERR_UNKNOWN_VARIABLE); test("A₁₆", "10", 0); + test("B₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("B₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("B", "", PARSER_ERR_UNKNOWN_VARIABLE); test("B₁₆", "11", 0); + test("C₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("C₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("C", "", PARSER_ERR_UNKNOWN_VARIABLE); test("C₁₆", "12", 0); + test("D₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("D₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("D", "", PARSER_ERR_UNKNOWN_VARIABLE); test("D₁₆", "13", 0); + test("E₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("E₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("E", "", PARSER_ERR_UNKNOWN_VARIABLE); test("E₁₆", "14", 0); + test("F₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("F₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("F", "", PARSER_ERR_UNKNOWN_VARIABLE); test("F₁₆", "15", 0); + test("a₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("a₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("a", "", PARSER_ERR_UNKNOWN_VARIABLE); test("a₁₆", "10", 0); + test("b₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("b₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("b", "", PARSER_ERR_UNKNOWN_VARIABLE); test("b₁₆", "11", 0); + test("c₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("c₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("c", "", PARSER_ERR_UNKNOWN_VARIABLE); test("c₁₆", "12", 0); + test("d₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("d₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("d", "", PARSER_ERR_UNKNOWN_VARIABLE); test("d₁₆", "13", 0); + test("e₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("e₈", "", PARSER_ERR_UNKNOWN_VARIABLE); /* e is a built-in variable */ test("e₁₆", "14", 0); + test("f₂", "", PARSER_ERR_UNKNOWN_VARIABLE); test("f₈", "", PARSER_ERR_UNKNOWN_VARIABLE); test("f", "", PARSER_ERR_UNKNOWN_VARIABLE); test("f₁₆", "15", 0); + + test("+1", "1", 0); + test("−1", "−1", 0); + test("+ 1", "1", 0); // FIXME: Should this be allowed? + test("− 1", "−1", 0); // FIXME: Should this be allowed? + test("++1", "1", PARSER_ERR_INVALID); + test("−−1", "1", 0); + test("255", "255", 0); + test("256", "256", 0); + test("½", "0.5", 0); + test("1½", "1.5", 0); + test("0°", "0", 0); + test("1°", "1", 0); + test("0°30'", "0.5", 0); + //test("0°0.1'", "1", 0); // FIXME: Not yet supported + test("0°0'1\"", "0.000277778", 0); + test("0°0'0.1\"", "0.000027778", 0); + test("1.00", "1", 0); + test("1.01", "1.01", 0); + + test("١٢٣٤٥٦٧٨٩٠", "1234567890", 0); + test("۱۲۳۴۵۶۷۸۹۰", "1234567890", 0); + +/* + //test("2A", "2000000000000000", 0); + test("2T", "2000000000000", 0); + test("2G", "2000000000", 0); + test("2M", "2000000", 0); + test("2k", "2000", 0); + test("2c", "0.02", 0); + test("2d", "0.2", 0); + test("2c", "0.02", 0); + test("2m", "0.002", 0); + test("2u", "0.000002", 0); + test("2µ", "0.000002", 0); + test("2n", "0.000000002", 0); + //test("2p", "0.000000000002", 0); // FIXME: Need to print out significant figures, not decimal places + //test("2f", "0.000000000000002", 0); // FIXME: Need to print out significant figures, not decimal places + //test("2A3", "2300000000000000", 0); + test("2T3", "2300000000000", 0); + test("2G3", "2300000000", 0); + test("2M3", "2300000", 0); + test("2k3", "2300", 0); + test("2c3", "0.023", 0); + test("2d3", "0.23", 0); + test("2c3", "0.023", 0); + test("2m3", "0.0023", 0); + test("2u3", "0.0000023", 0); + test("2µ3", "0.0000023", 0); + //test("2n3", "0.0000000023", 0); // FIXME: Need to print out significant figures, not decimal places + //test("2p3", "0.0000000000023", 0); // FIXME: Need to print out significant figures, not decimal places + //test("2f3", "0.0000000000000023", 0); // FIXME: Need to print out significant figures, not decimal places +*/ + + test("2×10^3", "2000", 0); + test("2×10^−3", "0.002", 0); + + test("x", "2", 0); + test("y", "3", 0); + test("z", "", PARSER_ERR_UNKNOWN_VARIABLE); + test("2y", "6", 0); + test("y2", "", PARSER_ERR_INVALID); + test("y 2", "", PARSER_ERR_INVALID); + test("2z", "", PARSER_ERR_UNKNOWN_VARIABLE); + test("z2", "", PARSER_ERR_UNKNOWN_VARIABLE); + test("z 2", "", PARSER_ERR_UNKNOWN_VARIABLE); + test("z(2)", "", PARSER_ERR_UNKNOWN_VARIABLE); + test("y²", "9", 0); + test("2y²", "18", 0); + test("x×y", "6", 0); + test("xy", "6", 0); + test("yx", "6", 0); + test("2xy", "12", 0); + test("x²y", "12", 0); + test("xy²", "18", 0); + test("(xy)²", "36", 0); + test("2x²y", "24", 0); + test("2xy²", "36", 0); + test("2x²y²", "72", 0); + test("x²yx²y", "144", 0); + test("x³+2x²−5", "11", 0); + test("2(x+3y)", "22", 0); + test("x(x+3y)", "22", 0); + test("(x+3y)(2x-4y)", "−88", 0); + test("2x²+2xy−12y²", "−88", 0); + + test("π", "3.141592654", 0); + test("e", "2.718281828", 0); + + test("z=99", "99", 0); + test("longname=99", "99", 0); + //test("e=99", "", PARSER_ERR_BUILTIN_VARIABLE); + + test("0+0", "0", 0); + test("1+1", "2", 0); + test("1+4", "5", 0); + test("4+1", "5", 0); + test("40000+0.001", "40000.001", 0); + test("0.001+40000", "40000.001", 0); + test("2-3", "−1", 0); + test("2−3", "−1", 0); + test("3−2", "1", 0); + test("40000−0.001", "39999.999", 0); + test("0.001−40000", "−39999.999", 0); + test("2*3", "6", 0); + test("2×3", "6", 0); + test("−2×3", "−6", 0); + test("2×−3", "−6", 0); + test("−2×−3", "6", 0); + test("6/3", "2", 0); + test("6÷3", "2", 0); + test("1÷2", "0.5", 0); + test("−6÷3", "−2", 0); + test("6÷−3", "−2", 0); + test("−6÷−3", "2", 0); + test("(−3)÷(−6)", "0.5", 0); + test("2÷2", "1", 0); + test("1203÷1", "1203", 0); + test("−0÷32352.689", "0", 0); + test("1÷4", "0.25", 0); + test("1÷3", "0.333333333", 0); + test("2÷3", "0.666666667", 0); + test("1÷0", "", PARSER_ERR_MP); + test("0÷0", "", PARSER_ERR_MP); + + /* Precision */ + test("1000000000000000−1000000000000000", "0", 0); + test("1000000000000000÷1000000000000000", "1", 0); + test("1000000000000000×0.000000000000001", "1", 0); + + /* Order of operations */ + test("1−0.9−0.1", "0", 0); + test("1+2×3", "7", 0); + test("1+(2×3)", "7", 0); + test("(1+2)×3", "9", 0); + test("(1+2×3)", "7", 0); + test("2(1+1)", "4", 0); + test("4÷2(1+1)", "4", 0); + + /* Percentage */ + test("100%", "1", 0); + test("1%", "0.01", 0); + test("100+1%", "101", 0); + test("100−1%", "99", 0); + test("100×1%", "1", 0); + test("100÷1%", "10000", 0); + + /* Factorial */ + test("0!", "1", 0); + test("1!", "1", 0); + test("5!", "120", 0); + test("69!", "171122452428141311372468338881272839092270544893520369393648040923257279754140647424000000000000000", 0); + test("0.1!", "", PARSER_ERR_MP); + test("−1!", "−1", 0); + test("(−1)!", "", PARSER_ERR_MP); + test("−(1!)", "−1", 0); + + /* Powers */ + test("2²", "4", 0); + test("2³", "8", 0); + test("2¹⁰", "1024", 0); + test("(1+2)²", "9", 0); + test("(x)²", "4", 0); + test("|1−3|²", "4", 0); + test("|x|²", "4", 0); + test("0^0", "1", 0); + test("0^0.5", "0", 0); + test("2^0", "1", 0); + test("2^1", "2", 0); + test("2^2", "4", 0); + test("2⁻¹", "0.5", 0); + test("2⁻", "", PARSER_ERR_MP); + test("2^−1", "0.5", 0); + test("2^(−1)", "0.5", 0); + test("x⁻¹", "0.5", 0); + test("−10^2", "−100", 0); + test("(−10)^2", "100", 0); + test("−(10^2)", "−100", 0); + test("2^100", "1267650600228229401496703205376", 0); + test("4^3^2", "262144", 0); + test("4^(3^2)", "262144", 0); + test("(4^3)^2", "4096", 0); + test("√4", "2", 0); + test("√4−2", "0", 0); + test("∛8", "2", 0); + test("∜16", "2", 0); + test("₃√8", "2", 0); + test("₁₀√1024", "2", 0); + test("√(2+2)", "2", 0); + test("2√4", "4", 0); + test("2×√4", "4", 0); + test("Sqrt(4)", "2", 0); + test("Sqrt(2)", "1.414213562", 0); + test("4^0.5", "2", 0); + test("2^0.5", "1.414213562", 0); + test("₃√−8", "−2", 0); + test("(−8)^(1÷3)", "−2", 0); + + test("0 mod 7", "0", 0); + test("6 mod 7", "6", 0); + test("7 mod 7", "0", 0); + test("8 mod 7", "1", 0); + test("−1 mod 7", "6", 0); + + test("sgn 0", "0", 0); + test("sgn 3", "1", 0); + test("sgn −3", "−1", 0); + test("⌊3⌋", "3", 0); + test("⌈3⌉", "3", 0); + test("[3]", "3", 0); + test("⌊−3⌋", "−3", 0); + test("⌈−3⌉", "−3", 0); + test("[−3]", "−3", 0); + test("⌊3.2⌋", "3", 0); + test("⌈3.2⌉", "4", 0); + test("[3.2]", "3", 0); + test("⌊−3.2⌋", "−4", 0); + test("⌈−3.2⌉", "−3", 0); + test("[−3.2]", "−3", 0); + test("⌊3.5⌋", "3", 0); + test("⌈3.5⌉", "4", 0); + test("[3.5]", "4", 0); + test("⌊−3.5⌋", "−4", 0); + test("⌈−3.5⌉", "−3", 0); + test("[−3.5]", "−4", 0); + test("⌊3.7⌋", "3", 0); + test("⌈3.7⌉", "4", 0); + test("[3.7]", "4", 0); + test("⌊−3.7⌋", "−4", 0); + test("⌈−3.7⌉", "−3", 0); + test("[−3.7]", "−4", 0); + test("{3.2}", "0.2", 0); + test("{−3.2}", "0.8", 0); + + test("|1|", "1", 0); + test("|−1|", "1", 0); + test("|3−5|", "2", 0); + test("|x|", "2", 0); + test("abs 1", "1", 0); + test("abs (−1)", "1", 0); + + test("log 0", "", PARSER_ERR_MP); + test("log 1", "0", 0); + test("log 2", "0.301029996", 0); + test("log 10", "1", 0); + test("log₁₀ 10", "1", 0); + test("log₂ 2", "1", 0); + test("2 log 2", "0.602059991", 0); + + test("ln 0", "", PARSER_ERR_MP); + test("ln 1", "0", 0); + test("ln 2", "0.693147181", 0); + test("ln e", "1", 0); + test("2 ln 2", "1.386294361", 0); + + options.angle_units = MP_DEGREES; + test("sin 0", "0", 0); + test("sin 45 − 1÷√2", "0", 0); + test("sin 20 + sin(−20)", "0", 0); + test("sin 90", "1", 0); + test("sin 180", "0", 0); + test("2 sin 90", "2", 0); + test("sin²45", "0.5", 0); + + test("cos 0", "1", 0); + test("cos 45 − 1÷√2", "0", 0); + test("cos 20 − cos (−20)", "0", 0); + test("cos 90", "0", 0); + test("cos 180", "−1", 0); + test("2 cos 0", "2", 0); + test("cos²45", "0.5", 0); + + test("tan 0", "0", 0); + test("tan 10 − sin 10÷cos 10", "0", 0); + test("tan 90", "", PARSER_ERR_MP); + test("tan 10", "0.176326981", 0); + test("tan²10", "0.031091204", 0); + + test("cos⁻¹ 0", "90", 0); + test("cos⁻¹ 1", "0", 0); + test("cos⁻¹ (−1)", "180", 0); + test("cos⁻¹ (1÷√2)", "45", 0); + test("acos 0", "90", 0); + test("acos 1", "0", 0); + + test("sin⁻¹ 0", "0", 0); + test("sin⁻¹ 1", "90", 0); + test("sin⁻¹ (−1)", "−90", 0); + test("sin⁻¹ (1÷√2)", "45", 0); + test("asin 0", "0", 0); + test("asin 1", "90", 0); + + test("cosh 0", "1", 0); + test("cosh 10 − (e^10 + e^−10)÷2", "0", 0); + + test("sinh 0", "0", 0); + test("sinh 10 − (e^10 − e^−10)÷2", "0", 0); + test("sinh (−10) + sinh 10", "0", 0); + + test("cosh² (−5) − sinh² (−5)", "1", 0); + test("tanh 0", "0", 0); + test("tanh 10 − sinh 10 ÷ cosh 10", "0", 0); + + test("atanh 0", "0", 0); + test("atanh (1÷10) − 0.5 ln(11÷9)", "0", 0); + + options.angle_units = MP_DEGREES; + test("sin 90", "1", 0); + + options.angle_units = MP_RADIANS; + test("sin (π÷2)", "1", 0); // FIXME: Shouldn't need brackets + + options.angle_units = MP_GRADIANS; + test("sin 100", "1", 0); + + /* Complex numbers */ + options.angle_units = MP_DEGREES; + test("i", "i", 0); + test("−i", "−i", 0); + test("2i", "2i", 0); + test("1+i", "1+i", 0); + test("i+1", "1+i", 0); + test("1−i", "1−i", 0); + test("i−1", "−1+i", 0); + test("i×i", "−1", 0); + test("i÷i", "1", 0); + test("1÷i", "−i", 0); + test("|i|", "1", 0); + test("|3+4i|", "5", 0); + test("arg 0", "", PARSER_ERR_MP); + test("arg 1", "0", 0); + test("arg (1+i)", "45", 0); + test("arg i", "90", 0); + test("arg (−1+i)", "135", 0); + test("arg −1", "180", 0); + test("arg (1+−i)", "−45", 0); + test("arg −i", "−90", 0); + test("arg (−1−i)", "−135", 0); + test("i⁻¹", "−i", 0); + test("√−1", "i", 0); + test("(−1)^0.5", "i", 0); + test("√−4", "2i", 0); + test("e^iπ", "−1", 0); + test("log (−10) − (1 + πi÷ln(10))", "0", 0); + test("ln (−e) − (1 + πi)", "0", 0); + test("sin(iπ÷4) − i×sinh(π÷4)", "0", 0); + test("cos(iπ÷4) − cosh(π÷4)", "0", 0); + + /* Boolean */ + test("0 and 0", "0", 0); + test("1 and 0", "0", 0); + test("0 and 1", "0", 0); + test("1 and 1", "1", 0); + test("3 and 5", "1", 0); + + test("0 or 0", "0", 0); + test("1 or 0", "1", 0); + test("0 or 1", "1", 0); + test("1 or 1", "1", 0); + test("3 or 5", "7", 0); + + test("0 xor 0", "0", 0); + test("1 xor 0", "1", 0); + test("0 xor 1", "1", 0); + test("1 xor 1", "0", 0); + test("3 xor 5", "6", 0); + + options.base = 16; + test("ones 1", "FFFFFFFE", 0); + test("ones 7FFFFFFF", "80000000", 0); + test("twos 1", "FFFFFFFF", 0); + test("twos 7FFFFFFF", "80000001", 0); + test("~7A₁₆", "FFFFFF85", 0); + + options.base = 2; + options.wordlen = 4; + test("1100∧1010", "1000", 0); + test("1100∨1010", "1110", 0); + test("1100⊻1010", "110", 0); + test("1100⊕1010", "110", 0); + //test("1100⊼1010", "0111", 0); + //test("1100⊽1010", "0001", 0); + //options.wordlen = 2; + //test("¬01₂", "10₂", 0); + //test("¬¬10₂", "10₂", 0); +} + + +int +main (int argc, char **argv) +{ + setlocale(LC_ALL, "C"); + g_type_init (); + + test_conversions(); + test_equations(); + if (fails == 0) + printf("Passed all %i tests\n", passes); + + return fails; +} diff --git a/src/test-mp.c b/src/test-mp.c new file mode 100644 index 0000000..a750f2a --- /dev/null +++ b/src/test-mp.c @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include +#include +#include +#include +#include + +#include "mp.h" +#include "mp-private.h" + +static int fails = 0; +static int passes = 0; + +/* If we're not using GNU C, elide __attribute__ */ +#ifndef __GNUC__ +# define __attribute__(x) /*NOTHING*/ +#endif + +static void pass(const char *format, ...) __attribute__((format(printf, 1, 2))); +static void fail(const char *format, ...) __attribute__((format(printf, 1, 2))); + + +static void pass(const char *format, ...) +{ +/* va_list args; + + printf(" PASS: "); + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); +*/ + passes += 1; +} + +static void fail(const char *format, ...) +{ + va_list args; + + printf("*FAIL: "); + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); + fails++; +} + + +static void +print_number(MPNumber *x) +{ + int i, j; + + printf("sign=%d exponent=%d fraction=%d", x->sign, x->exponent, x->fraction[0]); + for (i = 1; i < MP_SIZE; i++) { + for (j = i; j < MP_SIZE && x->fraction[j] == 0; j++); + if (j == MP_SIZE) { + printf(",..."); + break; + } + printf(",%d", x->fraction[i]); + } +} + +static void +test_string(const char *number) +{ + MPNumber t; + + mp_set_from_string(number, 10, &t); + + printf("MPNumber(%s) -> {", number); + print_number(&t); + printf("}\n"); +} + +static void +test_integer(int number) +{ + MPNumber t; + + mp_set_from_integer(number, &t); + + printf("MPNumber(%d) -> {", number); + print_number(&t); + printf("}\n"); +} + + +static void +test_numbers() +{ + printf("base=%d\n", MP_BASE); + test_integer(0); + test_integer(1); + test_integer(-1); + test_integer(2); + test_integer(9999); + test_integer(10000); + test_integer(10001); + test_integer(2147483647); + + test_string("0"); + test_string("1"); + test_string("-1"); + test_string("16383"); + test_string("16384"); + test_string("16385"); + test_string("268435456"); + + test_string("0.1"); + test_string("0.5"); + test_string("0.25"); + test_string("0.125"); + test_string("0.0625"); + test_string("0.00006103515625"); + test_string("0.000030517578125"); + + test_string("1.00006103515625"); + test_string("16384.00006103515625"); +} + + +static void +try(const char *string, bool result, bool expected) +{ + if ((result && !expected) || (!result && expected)) + fail("%s -> %s, expected %s", string, expected ? "true" : "false", result ? "true" : "false"); + else + pass("%s -> %s", string, result ? "true" : "false"); +} + + +static void +test_mp() +{ + MPNumber zero, one, minus_one; + + mp_set_from_integer(0, &zero); + mp_set_from_integer(1, &one); + mp_set_from_integer(-1, &minus_one); + + try("mp_is_zero(-1)", mp_is_zero(&minus_one), false); + try("mp_is_zero(0)", mp_is_zero(&zero), true); + try("mp_is_zero(1)", mp_is_zero(&one), false); + + try("mp_is_negative(-1)", mp_is_negative(&minus_one), true); + try("mp_is_negative(0)", mp_is_negative(&zero), false); + try("mp_is_negative(1)", mp_is_negative(&one), false); + + try("mp_is_integer(-1)", mp_is_integer(&minus_one), true); + try("mp_is_integer(0)", mp_is_integer(&zero), true); + try("mp_is_integer(1)", mp_is_integer(&one), true); + + try("mp_is_positive_integer(-1)", mp_is_positive_integer(&minus_one), false); + try("mp_is_positive_integer(0)", mp_is_positive_integer(&zero), true); + try("mp_is_positive_integer(1)", mp_is_positive_integer(&one), true); + + try("mp_is_natural(-1)", mp_is_natural(&minus_one), false); + try("mp_is_natural(0)", mp_is_natural(&zero), false); + try("mp_is_natural(1)", mp_is_natural(&one), true); + + try("mp_is_complex(-1)", mp_is_complex(&minus_one), false); + try("mp_is_complex(0)", mp_is_complex(&zero), false); + try("mp_is_complex(1)", mp_is_complex(&one), false); + + try("mp_is_equal(-1, -1)", mp_is_equal(&minus_one, &minus_one), true); + try("mp_is_equal(-1, 0)", mp_is_equal(&minus_one, &zero), false); + try("mp_is_equal(-1, 1)", mp_is_equal(&minus_one, &one), false); + try("mp_is_equal(0, -1)", mp_is_equal(&zero, &minus_one), false); + try("mp_is_equal(0, 0)", mp_is_equal(&zero, &zero), true); + try("mp_is_equal(0, 1)", mp_is_equal(&zero, &one), false); + try("mp_is_equal(1, -1)", mp_is_equal(&one, &minus_one), false); + try("mp_is_equal(1, 0)", mp_is_equal(&one, &zero), false); + try("mp_is_equal(1, 1)", mp_is_equal(&one, &one), true); + + try("mp_is_greater_than(0, -1)", mp_is_greater_than (&zero, &minus_one), true); + try("mp_is_greater_than(0, 0)", mp_is_greater_than (&zero, &zero), false); + try("mp_is_greater_than(0, 1)", mp_is_greater_than (&zero, &one), false); + try("mp_is_greater_than(1, -1)", mp_is_greater_than (&one, &minus_one), true); + try("mp_is_greater_than(1, 0)", mp_is_greater_than (&one, &zero), true); + try("mp_is_greater_than(1, 1)", mp_is_greater_than (&one, &one), false); + try("mp_is_greater_than(-1, -1)", mp_is_greater_than (&minus_one, &minus_one), false); + try("mp_is_greater_than(-1, 0)", mp_is_greater_than (&minus_one, &zero), false); + try("mp_is_greater_than(-1, 1)", mp_is_greater_than (&minus_one, &one), false); + + try("mp_is_greater_equal(0, -1)", mp_is_greater_equal (&zero, &minus_one), true); + try("mp_is_greater_equal(0, 0)", mp_is_greater_equal (&zero, &zero), true); + try("mp_is_greater_equal(0, 1)", mp_is_greater_equal (&zero, &one), false); + try("mp_is_greater_equal(1, -1)", mp_is_greater_equal (&one, &minus_one), true); + try("mp_is_greater_equal(1, 0)", mp_is_greater_equal (&one, &zero), true); + try("mp_is_greater_equal(1, 1)", mp_is_greater_equal (&one, &one), true); + try("mp_is_greater_equal(-1, -1)", mp_is_greater_equal (&minus_one, &minus_one), true); + try("mp_is_greater_equal(-1, 0)", mp_is_greater_equal (&minus_one, &zero), false); + try("mp_is_greater_equal(-1, 1)", mp_is_greater_equal (&minus_one, &one), false); + + try("mp_is_less_than(0, -1)", mp_is_less_than (&zero, &minus_one), false); + try("mp_is_less_than(0, 0)", mp_is_less_than (&zero, &zero), false); + try("mp_is_less_than(0, 1)", mp_is_less_than (&zero, &one), true); + try("mp_is_less_than(1, -1)", mp_is_less_than (&one, &minus_one), false); + try("mp_is_less_than(1, 0)", mp_is_less_than (&one, &zero), false); + try("mp_is_less_than(1, 1)", mp_is_less_than (&one, &one), false); + try("mp_is_less_than(-1, -1)", mp_is_less_than (&minus_one, &minus_one), false); + try("mp_is_less_than(-1, 0)", mp_is_less_than (&minus_one, &zero), true); + try("mp_is_less_than(-1, 1)", mp_is_less_than (&minus_one, &one), true); + + try("mp_is_less_equal(0, -1)", mp_is_less_equal (&zero, &minus_one), false); + try("mp_is_less_equal(0, 0)", mp_is_less_equal (&zero, &zero), true); + try("mp_is_less_equal(0, 1)", mp_is_less_equal (&zero, &one), true); + try("mp_is_less_equal(1, -1)", mp_is_less_equal (&one, &minus_one), false); + try("mp_is_less_equal(1, 0)", mp_is_less_equal (&one, &zero), false); + try("mp_is_less_equal(1, 1)", mp_is_less_equal (&one, &one), true); + try("mp_is_less_equal(-1, -1)", mp_is_less_equal (&minus_one, &minus_one), true); + try("mp_is_less_equal(-1, 0)", mp_is_less_equal (&minus_one, &zero), true); + try("mp_is_less_equal(-1, 1)", mp_is_less_equal (&minus_one, &one), true); +} + + +int +main (int argc, char **argv) +{ + setlocale(LC_ALL, "C"); + g_type_init (); + + test_mp(); + test_numbers(); + if (fails == 0) + printf("Passed all %i tests\n", passes); + + return fails; +} diff --git a/src/unit-category.c b/src/unit-category.c new file mode 100644 index 0000000..56a90e2 --- /dev/null +++ b/src/unit-category.c @@ -0,0 +1,136 @@ +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include + +#include "unit-category.h" + +struct UnitCategoryPrivate +{ + gchar *name; + gchar *display_name; + GList *units; +}; + +G_DEFINE_TYPE (UnitCategory, unit_category, G_TYPE_OBJECT); + + +UnitCategory * +unit_category_new(const gchar *name, const gchar *display_name) +{ + UnitCategory *category = g_object_new(unit_category_get_type(), NULL); + category->priv->name = g_strdup(name); + category->priv->display_name = g_strdup(display_name); + return category; +} + + +const gchar * +unit_category_get_name(UnitCategory *category) +{ + g_return_val_if_fail (category != NULL, NULL); + return category->priv->name; +} + + +const gchar * +unit_category_get_display_name(UnitCategory *category) +{ + g_return_val_if_fail (category != NULL, NULL); + return category->priv->display_name; +} + + +void +unit_category_add_unit(UnitCategory *category, Unit *unit) +{ + g_return_if_fail (category != NULL); + g_return_if_fail (unit != NULL); + category->priv->units = g_list_append(category->priv->units, g_object_ref(unit)); +} + + +Unit * +unit_category_get_unit_by_name(UnitCategory *category, const gchar *name) +{ + GList *iter; + + g_return_val_if_fail (category != NULL, NULL); + g_return_val_if_fail (name != NULL, NULL); + + for (iter = category->priv->units; iter; iter = iter->next) + { + Unit *unit = iter->data; + if (strcmp(unit_get_name(unit), name) == 0) + return unit; + } + + return NULL; +} + + +Unit * +unit_category_get_unit_by_symbol(UnitCategory *category, const gchar *symbol) +{ + GList *iter; + + g_return_val_if_fail (category != NULL, NULL); + g_return_val_if_fail (symbol != NULL, NULL); + + for (iter = category->priv->units; iter; iter = iter->next) { + Unit *unit = iter->data; + if (unit_matches_symbol(unit, symbol)) + return unit; + } + + return NULL; +} + + +const GList * +unit_category_get_units(UnitCategory *category) +{ + g_return_val_if_fail (category != NULL, NULL); + return category->priv->units; +} + + +gboolean +unit_category_convert(UnitCategory *category, const MPNumber *x, Unit *x_units, Unit *z_units, MPNumber *z) +{ + MPNumber t; + + g_return_val_if_fail (category != NULL, FALSE); + g_return_val_if_fail (x_units != NULL, FALSE); + g_return_val_if_fail (z_units != NULL, FALSE); + g_return_val_if_fail (z != NULL, FALSE); + + if (!unit_convert_from(x_units, x, &t)) + return FALSE; + if (!unit_convert_to(z_units, &t, z)) + return FALSE; + + return TRUE; +} + + +static void +unit_category_class_init(UnitCategoryClass *klass) +{ + g_type_class_add_private(klass, sizeof(UnitCategoryPrivate)); +} + + +static void +unit_category_init(UnitCategory *category) +{ + category->priv = G_TYPE_INSTANCE_GET_PRIVATE(category, unit_category_get_type(), UnitCategoryPrivate); +} diff --git a/src/unit-category.h b/src/unit-category.h new file mode 100644 index 0000000..fc2c739 --- /dev/null +++ b/src/unit-category.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef UNIT_CATEGORY_H +#define UNIT_CATEGORY_H + +#include +#include "unit.h" +#include "mp.h" + +G_BEGIN_DECLS + +#define UNIT_CATEGORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), unit_category_get_type(), UnitCategory)) + +typedef struct UnitCategoryPrivate UnitCategoryPrivate; + +typedef struct +{ + GObject parent_instance; + UnitCategoryPrivate *priv; +} UnitCategory; + +typedef struct +{ + GObjectClass parent_class; +} UnitCategoryClass; + +GType unit_category_get_type(void); + +UnitCategory *unit_category_new(const gchar *name, const gchar *display_name); + +const gchar *unit_category_get_name(UnitCategory *category); + +const gchar *unit_category_get_display_name(UnitCategory *category); + +Unit *unit_category_get_unit_by_name(UnitCategory *category, const gchar *name); + +Unit *unit_category_get_unit_by_symbol(UnitCategory *category, const gchar *symbol); + +void unit_category_add_unit(UnitCategory *category, Unit *unit); + +const GList *unit_category_get_units(UnitCategory *category); + +gboolean unit_category_convert(UnitCategory *category, const MPNumber *x, Unit *x_units, Unit *z_units, MPNumber *z); + +G_END_DECLS + +#endif /* UNIT_CATEGORY_H */ diff --git a/src/unit-manager.c b/src/unit-manager.c new file mode 100644 index 0000000..9943a31 --- /dev/null +++ b/src/unit-manager.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include +#include // FIXME: Move out of here + +#include "unit-manager.h" +#include "currency-manager.h" // FIXME: Move out of here + +struct UnitManagerPrivate +{ + GList *categories; +}; + +G_DEFINE_TYPE (UnitManager, unit_manager, G_TYPE_OBJECT); + + +static UnitManager *default_unit_manager = NULL; + + +static gint +compare_currencies(gconstpointer a, gconstpointer b) +{ + return strcmp(currency_get_display_name((Currency *)a), currency_get_display_name((Currency *)b)); +} + + +UnitManager * +unit_manager_get_default(void) +{ + UnitCategory *category = NULL; + GList *currencies, *iter; + int i; + const struct + { + gchar *category; + gchar *name; + gchar *display_name; + gchar *format; + gchar *from_function; + gchar *to_function; + gchar *symbols; + } units[] = + { + /* FIXME: Approximations of 1/(units in a circle), therefore, 360 deg != 400 grads */ + {"angle", "degree", N_("Degrees"), NC_("unit-format", "%s degrees"), "π*x/180", "180x/π", NC_("unit-symbols", "degree,degrees,deg")}, + {NULL, "radian", N_("Radians"), NC_("unit-format", "%s radians"), "x", "x", NC_("unit-symbols", "radian,radians,rad")}, + {NULL, "gradian", N_("Gradians"), NC_("unit-format", "%s gradians"), "π*x/200", "200x/π", NC_("unit-symbols", "gradian,gradians,grad")}, + {"length", "parsec", N_("Parsecs"), NC_("unit-format", "%s pc"), "30857000000000000x", "x/30857000000000000", NC_("unit-symbols", "parsec,parsecs,pc")}, + {NULL, "lightyear", N_("Light Years"), NC_("unit-format", "%s ly"), "9460730472580800x", "x/9460730472580800", NC_("unit-symbols", "lightyear,lightyears,ly")}, + {NULL, "astronomical-unit", N_("Astronomical Units"), NC_("unit-format", "%s au"), "149597870691x", "x/149597870691", NC_("unit-symbols", "au")}, + {NULL, "nautical-mile", N_("Nautical Miles"), NC_("unit-format", "%s nmi"), "1852x", "x/1852", NC_("unit-symbols", "nmi")}, + {NULL, "mile", N_("Miles"), NC_("unit-format", "%s mi"), "1609.344x", "x/1609.344", NC_("unit-symbols", "mile,miles,mi")}, + {NULL, "kilometer", N_("Kilometers"), NC_("unit-format", "%s km"), "1000x", "x/1000", NC_("unit-symbols", "kilometer,kilometers,km,kms")}, + {NULL, "cable", N_("Cables"), NC_("unit-format", "%s cb"), "219.456x", "x/219.456", NC_("unit-symbols", "cable,cables,cb")}, + {NULL, "fathom", N_("Fathoms"), NC_("unit-format", "%s ftm"), "1.8288x", "x/1.8288", NC_("unit-symbols", "fathom,fathoms,ftm")}, + {NULL, "meter", N_("Meters"), NC_("unit-format", "%s m"), "x", "x", NC_("unit-symbols", "meter,meters,m")}, + {NULL, "yard", N_("Yards"), NC_("unit-format", "%s yd"), "0.9144x", "x/0.9144", NC_("unit-symbols", "yard,yards,yd")}, + {NULL, "foot", N_("Feet"), NC_("unit-format", "%s ft"), "0.3048x", "x/0.3048", NC_("unit-symbols", "foot,feet,ft")}, + {NULL, "inch", N_("Inches"), NC_("unit-format", "%s in"), "0.0254x", "x/0.0254", NC_("unit-symbols", "inch,inches,in")}, + {NULL, "centimeter", N_("Centimeters"), NC_("unit-format", "%s cm"), "x/100", "100x", NC_("unit-symbols", "centimeter,centimeters,cm,cms")}, + {NULL, "millimeter", N_("Millimeters"), NC_("unit-format", "%s mm"), "x/1000", "1000x", NC_("unit-symbols", "millimeter,millimeters,mm")}, + {NULL, "micrometer", N_("Micrometers"), NC_("unit-format", "%s μm"), "x/1000000", "1000000x", NC_("unit-symbols", "micrometer,micrometers,um")}, + {NULL, "nanometer", N_("Nanometers"), NC_("unit-format", "%s nm"), "x/1000000000", "1000000000x", NC_("unit-symbols", "nanometer,nanometers,nm")}, + {"area", "hectare", N_("Hectares"), NC_("unit-format", "%s ha"), "10000x", "x/10000", NC_("unit-symbols", "hectare,hectares,ha")}, + {NULL, "acre", N_("Acres"), NC_("unit-format", "%s acres"), "4046.8564224x", "x/4046.8564224", NC_("unit-symbols", "acre,acres")}, + {NULL, "square-meter", N_("Square Meters"), NC_("unit-format", "%s m²"), "x", "x", NC_("unit-symbols", "m²")}, + {NULL, "square-centimeter", N_("Square Centimeters"), NC_("unit-format", "%s cm²"), "0.0001x", "10000x", NC_("unit-symbols", "cm²")}, + {NULL, "square-millimeter", N_("Square Millimeters"), NC_("unit-format", "%s mm²"), "0.000001x", "1000000x", NC_("unit-symbols", "mm²")}, + {"volume", "cubic-meter", N_("Cubic Meters"), NC_("unit-format", "%s m³"), "1000x", "x/1000", NC_("unit-symbols", "m³")}, + {NULL, "gallon", N_("Gallons"), NC_("unit-format", "%s gal"), "3.785412x", "x/3.785412", NC_("unit-symbols", "gallon,gallons,gal")}, + {NULL, "litre", N_("Litres"), NC_("unit-format", "%s L"), "x", "x", NC_("unit-symbols", "litre,litres,liter,liters,L")}, + {NULL, "quart", N_("Quarts"), NC_("unit-format", "%s qt"), "0.9463529x", "x/0.9463529", NC_("unit-symbols", "quart,quarts,qt")}, + {NULL, "pint", N_("Pints"), NC_("unit-format", "%s pt"), "0.4731765x", "x/0.4731765", NC_("unit-symbols", "pint,pints,pt")}, + {NULL, "millilitre", N_("Millilitres"), NC_("unit-format", "%s mL"), "0.001x", "1000x", NC_("unit-symbols", "millilitre,millilitres,milliliter,milliliters,mL,cm³")}, + {NULL, "microlitre", N_("Microlitres"), NC_("unit-format", "%s μL"), "0.000001x", "1000000x", NC_("unit-symbols", "mm³,μL,uL")}, + {"weight", "tonne", N_("Tonnes"), NC_("unit-format", "%s T"), "1000x", "x/1000", NC_("unit-symbols", "tonne,tonnes")}, + {NULL, "kilograms", N_("Kilograms"), NC_("unit-format", "%s kg"), "x", "x", NC_("unit-symbols", "kilogram,kilograms,kilogramme,kilogrammes,kg,kgs")}, + {NULL, "pound", N_("Pounds"), NC_("unit-format", "%s lb"), "0.45359237x", "x/0.45359237", NC_("unit-symbols", "pound,pounds,lb")}, + {NULL, "ounce", N_("Ounces"), NC_("unit-format", "%s oz"), "0.02834952x", "x/0.02834952", NC_("unit-symbols", "ounce,ounces,oz")}, + {NULL, "gram", N_("Grams"), NC_("unit-format", "%s g"), "0.001x", "1000x", NC_("unit-symbols", "gram,grams,gramme,grammes,g")}, + {"duration", "year", N_("Years"), NC_("unit-format", "%s years"), "31557600x", "x/31557600", NC_("unit-symbols", "year,years")}, + {NULL, "day", N_("Days"), NC_("unit-format", "%s days"), "86400x", "x/86400", NC_("unit-symbols", "day,days")}, + {NULL, "hour", N_("Hours"), NC_("unit-format", "%s hours"), "3600x", "x/3600", NC_("unit-symbols", "hour,hours")}, + {NULL, "minute", N_("Minutes"), NC_("unit-format", "%s minutes"), "60x", "x/60", NC_("unit-symbols", "minute,minutes")}, + {NULL, "second", N_("Seconds"), NC_("unit-format", "%s s"), "x", "x", NC_("unit-symbols", "second,seconds,s")}, + {NULL, "millisecond", N_("Milliseconds"), NC_("unit-format", "%s ms"), "0.001x", "1000x", NC_("unit-symbols", "millisecond,milliseconds,ms")}, + {NULL, "microsecond", N_("Microseconds"), NC_("unit-format", "%s μs"), "0.000001x", "1000000x", NC_("unit-symbols", "microsecond,microseconds,us,μs")}, + {"temperature", "degree-celcius", N_("Celsius"), NC_("unit-format", "%s ˚C"), "x+273.15", "x-273.15", NC_("unit-symbols", "degC,˚C")}, + {NULL, "degree-farenheit", N_("Farenheit"), NC_("unit-format", "%s ˚F"), "(x+459.67)*5/9", "x*9/5-459.67", NC_("unit-symbols", "degF,˚F")}, + {NULL, "degree-kelvin", N_("Kelvin"), NC_("unit-format", "%s K"), "x", "x", NC_("unit-symbols", "K")}, + {NULL, "degree-rankine", N_("Rankine"), NC_("unit-format", "%s ˚R"), "x*5/9", "x*9/5", NC_("unit-symbols", "degR,˚R,˚Ra")}, + { NULL, NULL, NULL, NULL, NULL, NULL } + }; + + if (default_unit_manager) + return default_unit_manager; + + default_unit_manager = g_object_new(unit_manager_get_type(), NULL); + + unit_manager_add_category(default_unit_manager, "angle", _("Angle")); + unit_manager_add_category(default_unit_manager, "length", _("Length")); + unit_manager_add_category(default_unit_manager, "area", _("Area")); + unit_manager_add_category(default_unit_manager, "volume", _("Volume")); + unit_manager_add_category(default_unit_manager, "weight", _("Weight")); + unit_manager_add_category(default_unit_manager, "duration", _("Duration")); + unit_manager_add_category(default_unit_manager, "temperature", _("Temperature")); + + for (i = 0; units[i].name; i++) { + if (units[i].category) + category = unit_manager_get_category(default_unit_manager, units[i].category); + unit_category_add_unit(category, unit_new(units[i].name, + _(units[i].display_name), + g_dpgettext2(NULL, "unit-format", units[i].format), + units[i].from_function, units[i].to_function, + g_dpgettext2(NULL, "unit-symbols", units[i].symbols))); + } + + category = unit_manager_add_category(default_unit_manager, "currency", _("Currency")); + currencies = g_list_copy(currency_manager_get_currencies(currency_manager_get_default())); + currencies = g_list_sort(currencies, compare_currencies); + for (iter = currencies; iter; iter = iter->next) + { + Currency *currency = iter->data; + gchar *format; + Unit *unit; + + /* Translators: result of currency conversion, %s is the symbol, %%s is the placeholder for amount, i.e.: USD100 */ + format = g_strdup_printf(_("%s%%s"), currency_get_symbol(currency)); + unit = unit_new(currency_get_name(currency), currency_get_display_name(currency), format, NULL, NULL, currency_get_name(currency)); + g_free(format); + + unit_category_add_unit(category, unit); + } + g_list_free(currencies); + + return default_unit_manager; +} + + +UnitCategory * +unit_manager_add_category(UnitManager *manager, const gchar *name, const gchar *display_name) +{ + UnitCategory *category; + + g_return_val_if_fail(manager != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + g_return_val_if_fail(display_name != NULL, NULL); + g_return_val_if_fail(unit_manager_get_category(manager, name) == NULL, NULL); + + category = unit_category_new(name, display_name); + manager->priv->categories = g_list_append(manager->priv->categories, category); + + return category; +} + + +const GList * +unit_manager_get_categories(UnitManager *manager) +{ + g_return_val_if_fail(manager != NULL, NULL); + return manager->priv->categories; +} + + +UnitCategory * +unit_manager_get_category(UnitManager *manager, const gchar *category) +{ + GList *iter; + + g_return_val_if_fail(manager != NULL, NULL); + g_return_val_if_fail(category != NULL, NULL); + + for (iter = manager->priv->categories; iter; iter = iter->next) { + UnitCategory *c = iter->data; + if (strcmp(unit_category_get_name(c), category) == 0) + return c; + } + + return NULL; +} + + +Unit * +unit_manager_get_unit_by_name(UnitManager *manager, const gchar *name) +{ + GList *iter; + Unit *u; + + g_return_val_if_fail(manager != NULL, NULL); + g_return_val_if_fail(name != NULL, NULL); + + for (iter = manager->priv->categories; iter; iter = iter->next) { + UnitCategory *c = iter->data; + u = unit_category_get_unit_by_name(c, name); + if (u) + return u; + } + + return NULL; +} + + +Unit * +unit_manager_get_unit_by_symbol(UnitManager *manager, const gchar *symbol) +{ + GList *iter; + Unit *u; + + g_return_val_if_fail(manager != NULL, NULL); + g_return_val_if_fail(symbol != NULL, NULL); + + for (iter = manager->priv->categories; iter; iter = iter->next) { + UnitCategory *c = iter->data; + u = unit_category_get_unit_by_symbol(c, symbol); + if (u) + return u; + } + + return NULL; +} + + +gboolean +unit_manager_convert_by_symbol(UnitManager *manager, const MPNumber *x, const char *x_symbol, const char *z_symbol, MPNumber *z) +{ + GList *iter; + + g_return_val_if_fail(manager != NULL, FALSE); + g_return_val_if_fail(x != NULL, FALSE); + g_return_val_if_fail(x_symbol != NULL, FALSE); + g_return_val_if_fail(z_symbol != NULL, FALSE); + g_return_val_if_fail(z != NULL, FALSE); + + for (iter = manager->priv->categories; iter; iter = iter->next) { + UnitCategory *c = iter->data; + Unit *x_units, *z_units; + + x_units = unit_category_get_unit_by_symbol(c, x_symbol); + z_units = unit_category_get_unit_by_symbol(c, z_symbol); + if (x_units && z_units && unit_category_convert(c, x, x_units, z_units, z)) + return TRUE; + } + + return FALSE; +} + + +static void +unit_manager_class_init(UnitManagerClass *klass) +{ + g_type_class_add_private(klass, sizeof(UnitManagerPrivate)); +} + + +static void +unit_manager_init(UnitManager *manager) +{ + manager->priv = G_TYPE_INSTANCE_GET_PRIVATE(manager, unit_manager_get_type(), UnitManagerPrivate); +} diff --git a/src/unit-manager.h b/src/unit-manager.h new file mode 100644 index 0000000..db1b4f2 --- /dev/null +++ b/src/unit-manager.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef UNIT_MANAGER_H +#define UNIT_MANAGER_H + +#include +#include "unit-category.h" +#include "mp.h" + +G_BEGIN_DECLS + +#define UNIT_MANAGER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), unit_manager_get_type(), UnitManager)) + +typedef struct UnitManagerPrivate UnitManagerPrivate; + +typedef struct +{ + GObject parent_instance; + UnitManagerPrivate *priv; +} UnitManager; + +typedef struct +{ + GObjectClass parent_class; +} UnitManagerClass; + +GType unit_manager_get_type(void); + +UnitManager *unit_manager_get_default(void); + +UnitCategory *unit_manager_add_category(UnitManager *manager, const gchar *name, const gchar *display_name); + +const GList *unit_manager_get_categories(UnitManager *manager); + +UnitCategory *unit_manager_get_category(UnitManager *manager, const gchar *category); + +Unit *unit_manager_get_unit_by_name(UnitManager *manager, const gchar *name); + +Unit *unit_manager_get_unit_by_symbol(UnitManager *manager, const gchar *symbol); + +gboolean unit_manager_convert_by_symbol(UnitManager *manager, const MPNumber *x, const char *x_symbol, const char *z_symbol, MPNumber *z); + +G_END_DECLS + +#endif /* UNIT_MANAGER_H */ diff --git a/src/unit.c b/src/unit.c new file mode 100644 index 0000000..f7c0277 --- /dev/null +++ b/src/unit.c @@ -0,0 +1,212 @@ +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#include + +#include "unit.h" +#include "mp-serializer.h" +#include "mp-equation.h" +#include "currency-manager.h" // FIXME: Move out of here + +struct UnitPrivate +{ + gchar *name; + gchar *display_name; + gchar *format; + GList *symbols; + gchar *from_function; + gchar *to_function; + MpSerializer *serializer; +}; + +G_DEFINE_TYPE (Unit, unit, G_TYPE_OBJECT); + + +Unit * +unit_new(const gchar *name, + const gchar *display_name, + const gchar *format, + const gchar *from_function, + const gchar *to_function, + const gchar *symbols) +{ + Unit *unit = g_object_new(unit_get_type(), NULL); + gchar **symbol_names; + int i; + + unit->priv->name = g_strdup(name); + unit->priv->display_name = g_strdup(display_name); + unit->priv->format = g_strdup(format); + unit->priv->from_function = g_strdup(from_function); + unit->priv->to_function = g_strdup(to_function); + symbol_names = g_strsplit(symbols, ",", 0); + for (i = 0; symbol_names[i]; i++) + unit->priv->symbols = g_list_append(unit->priv->symbols, g_strdup(symbol_names[i])); + g_free(symbol_names); + + return unit; +} + + +const gchar * +unit_get_name(Unit *unit) +{ + g_return_val_if_fail (unit != NULL, NULL); + return unit->priv->name; +} + + +const gchar * +unit_get_display_name(Unit *unit) +{ + g_return_val_if_fail (unit != NULL, NULL); + return unit->priv->display_name; +} + + +gboolean +unit_matches_symbol(Unit *unit, const gchar *symbol) +{ + GList *iter; + + g_return_val_if_fail (unit != NULL, FALSE); + g_return_val_if_fail (symbol != NULL, FALSE); + + for (iter = unit->priv->symbols; iter; iter = iter->next) { + gchar *s = iter->data; + if (strcmp(s, symbol) == 0) + return TRUE; + } + + return FALSE; +} + + +const GList * +unit_get_symbols(Unit *unit) +{ + g_return_val_if_fail (unit != NULL, NULL); + return unit->priv->symbols; +} + + +static int +variable_is_defined(const char *name, void *data) +{ + return TRUE; +} + + +static int +get_variable(const char *name, MPNumber *z, void *data) +{ + MPNumber *x = data; + mp_set_from_mp(x, z); + return TRUE; +} + + +static gboolean +solve_function(const gchar *function, const MPNumber *x, MPNumber *z) +{ + MPEquationOptions options; + int ret; + + memset(&options, 0, sizeof(options)); + options.base = 10; + options.wordlen = 32; + options.variable_is_defined = variable_is_defined; + options.get_variable = get_variable; + options.callback_data = (void *)x; + ret = mp_equation_parse(function, &options, z, NULL); + if (ret) { + g_warning("Failed to convert value: %s", function); + return FALSE; + } + + return TRUE; +} + + +gboolean +unit_convert_from(Unit *unit, const MPNumber *x, MPNumber *z) +{ + g_return_val_if_fail(unit != NULL, FALSE); + g_return_val_if_fail(x != NULL, FALSE); + g_return_val_if_fail(x != NULL, FALSE); + + if (unit->priv->from_function) + return solve_function(unit->priv->from_function, x, z); + else { + // FIXME: Hack to make currency work + const MPNumber *r; + r = currency_manager_get_value(currency_manager_get_default(), unit->priv->name); + if (!r) + return FALSE; + mp_divide(x, r, z); + + return TRUE; + } +} + + +gboolean +unit_convert_to(Unit *unit, const MPNumber *x, MPNumber *z) +{ + g_return_val_if_fail(unit != NULL, FALSE); + g_return_val_if_fail(x != NULL, FALSE); + g_return_val_if_fail(x != NULL, FALSE); + + if (unit->priv->from_function) + return solve_function(unit->priv->to_function, x, z); + else { + // FIXME: Hack to make currency work + const MPNumber *r; + r = currency_manager_get_value(currency_manager_get_default(), unit->priv->name); + if (!r) + return FALSE; + mp_multiply(x, r, z); + + return TRUE; + } +} + + +gchar * +unit_format(Unit *unit, MPNumber *x) +{ + gchar *number_text, *text; + + g_return_val_if_fail(unit != NULL, FALSE); + g_return_val_if_fail(x != NULL, FALSE); + + number_text = mp_serializer_to_string(unit->priv->serializer, x); + text = g_strdup_printf(unit->priv->format, number_text); + g_free(number_text); + + return text; +} + + +static void +unit_class_init(UnitClass *klass) +{ + g_type_class_add_private(klass, sizeof(UnitPrivate)); +} + + +static void +unit_init(Unit *unit) +{ + unit->priv = G_TYPE_INSTANCE_GET_PRIVATE(unit, unit_get_type(), UnitPrivate); + unit->priv->serializer = mp_serializer_new(MP_DISPLAY_FORMAT_AUTOMATIC, 10, 2); + mp_serializer_set_leading_digits(unit->priv->serializer, 6); +} diff --git a/src/unit.h b/src/unit.h new file mode 100644 index 0000000..3c9087f --- /dev/null +++ b/src/unit.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 1987-2008 Sun Microsystems, Inc. All Rights Reserved. + * Copyright (C) 2008-2011 Robert Ancell. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, either version 2 of the License, or (at your option) any later + * version. See http://www.gnu.org/copyleft/gpl.html the full text of the + * license. + */ + +#ifndef UNIT_H +#define UNIT_H + +#include +#include "mp.h" + +G_BEGIN_DECLS + +#define UNIT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), unit_get_type(), Unit)) + +typedef struct UnitPrivate UnitPrivate; + +typedef struct +{ + GObject parent_instance; + UnitPrivate *priv; +} Unit; + +typedef struct +{ + GObjectClass parent_class; +} UnitClass; + +GType unit_get_type(void); + +Unit *unit_new(const gchar *name, + const gchar *display_name, + const gchar *format, + const gchar *from_function, + const gchar *to_function, + const gchar *symbols); + +const gchar *unit_get_name(Unit *unit); + +const gchar *unit_get_display_name(Unit *unit); + +gboolean unit_matches_symbol(Unit *unit, const gchar *symbol); + +const GList *unit_get_symbols(Unit *unit); + +gboolean unit_convert_from(Unit *unit, const MPNumber *x, MPNumber *z); + +gboolean unit_convert_to(Unit *unit, const MPNumber *x, MPNumber *z); + +gchar *unit_format(Unit *unit, MPNumber *x); + +G_END_DECLS + +#endif /* UNIT_H */ -- cgit v1.2.1