[gcalctool] Add (rough) currency conversion (Bug #533690)



commit d16a5b07b64239213c9ac985a31cb9c89db6b507
Author: Robin Sonefors <ozamosi flukkost nu>
Date:   Tue Oct 6 01:07:47 2009 +0200

    Add (rough) currency conversion (Bug #533690)

 ChangeLog         |    4 +
 configure.in      |    2 +
 data/financial.ui |  126 +++++++++++++++++++++++++++++++
 data/gcalctool.ui |   45 +++++++++++
 po/POTFILES.in    |    1 +
 src/Makefile.am   |   10 ++-
 src/calctool.c    |    2 +
 src/currency.c    |  213 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/currency.h    |   74 ++++++++++++++++++
 src/gtk.c         |  205 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 10 files changed, 679 insertions(+), 3 deletions(-)
---
diff --git a/ChangeLog b/ChangeLog
index 9ab8d66..6c09242 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -7,6 +7,10 @@
 gcalctool change history.
 =========================
 
+2009-10-06 Robin Sonefors <ozamosi flukkost nu>
+
+    * Add (rough) currency conversion (Bug #533690)
+
 2009-10-05 Robin Sonefors <ozamosi flukkost nu>
 
     * Fix off-by-one in mp_cast_to_double
diff --git a/configure.in b/configure.in
index bc8f0d9..f98df63 100644
--- a/configure.in
+++ b/configure.in
@@ -24,6 +24,8 @@ GCONF_REQUIRED=1.1.9
 PKG_CHECK_MODULES(PACKAGE, [
     gtk+-2.0 >= $GTK_REQUIRED
     gconf-2.0 >= $GCONF_REQUIRED
+    libxml-2.0
+    libsoup-2.4
     gmodule-export-2.0
 ])
 
diff --git a/data/financial.ui b/data/financial.ui
index 8b60ed6..925b6ab 100644
--- a/data/financial.ui
+++ b/data/financial.ui
@@ -1777,4 +1777,130 @@
       <action-widget response="-5">button18</action-widget>
     </action-widgets>
   </object>
+  <object class="GtkDialog" id="currency_dialog">
+    <property name="border_width">5</property>
+    <property name="type_hint">normal</property>
+    <property name="has_separator">False</property>
+    <child internal-child="vbox">
+      <object class="GtkVBox" id="dialog-vbox11">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child>
+          <object class="GtkTable" id="table11">
+            <property name="visible">True</property>
+            <property name="n_rows">3</property>
+            <property name="n_columns">2</property>
+            <property name="column_spacing">6</property>
+            <property name="row_spacing">6</property>
+            <child>
+              <object class="GtkLabel" id="label1">
+                <property name="visible">True</property>
+                <property name="label" translatable="yes">Converts between different currencies. Enter the amount and the currency you want to convert from on the upper row, and the currency you want to convert to on the lower row, and the amount will be displayed on the lower row.</property>
+                <property name="wrap">True</property>
+              </object>
+              <packing>
+                <property name="right_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSpinButton" id="currency_amount_upper">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="invisible_char">&#x25CF;</property>
+                <property name="adjustment">adjustment1</property>
+                <property name="digits">2</property>
+                <property name="numeric">True</property>
+                <signal name="value_changed" handler="currency_amount_cb"/>
+              </object>
+              <packing>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkSpinButton" id="currency_amount_lower">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="invisible_char">&#x25CF;</property>
+                <property name="adjustment">adjustment2</property>
+                <property name="digits">2</property>
+                <property name="numeric">True</property>
+                <signal name="value_changed" handler="currency_amount_cb"/>
+              </object>
+              <packing>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="currency_type_upper">
+                <property name="visible">True</property>
+                <signal name="changed" handler="currency_type_cb"/>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">1</property>
+                <property name="bottom_attach">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkComboBox" id="currency_type_lower">
+                <property name="visible">True</property>
+                <signal name="changed" handler="currency_type_cb"/>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="right_attach">2</property>
+                <property name="top_attach">2</property>
+                <property name="bottom_attach">3</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="position">1</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkHButtonBox" id="dialog-action_area11">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button21">
+                <property name="label">gtk-ok</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-5">button21</action-widget>
+    </action-widgets>
+  </object>
+  <object class="GtkAdjustment" id="adjustment1">
+    <property name="upper">999999999999</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
+  <object class="GtkAdjustment" id="adjustment2">
+    <property name="upper">999999999999</property>
+    <property name="step_increment">1</property>
+    <property name="page_increment">10</property>
+  </object>
 </interface>
diff --git a/data/gcalctool.ui b/data/gcalctool.ui
index a8271c6..7580791 100644
--- a/data/gcalctool.ui
+++ b/data/gcalctool.ui
@@ -3814,6 +3814,7 @@
             <child>
               <object class="GtkTable" id="financial_panel">
                 <property name="visible">True</property>
+                <property name="n_rows">2</property>
                 <property name="n_columns">10</property>
                 <child>
                   <object class="GtkButton" id="calc_finc_straight_line_depreciation_button">
@@ -3993,6 +3994,50 @@
                     <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
                   </packing>
                 </child>
+                <child>
+                  <object class="GtkButton" id="calc_currency_button">
+                    <property name="label" translatable="yes">&#xA4;$&#x20AC;</property>
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="receives_default">True</property>
+                    <property name="border_width">3</property>
+                    <property name="focus_on_click">False</property>
+                    <signal name="clicked" handler="currency_cb"/>
+                  </object>
+                  <packing>
+                    <property name="top_attach">1</property>
+                    <property name="bottom_attach">2</property>
+                    <property name="x_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+                    <property name="y_options">GTK_EXPAND | GTK_SHRINK | GTK_FILL</property>
+                  </packing>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
+                <child>
+                  <placeholder/>
+                </child>
               </object>
               <packing>
                 <property name="right_attach">10</property>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 56d042e..5f53eaf 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,6 +6,7 @@
 data/gcalctool.desktop.in
 data/gcalctool.schemas.in
 src/calctool.c
+src/currency.h
 src/display.c
 src/financial.c
 src/functions.c
diff --git a/src/Makefile.am b/src/Makefile.am
index 4d3b948..860a90b 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -9,13 +9,17 @@ INCLUDES = \
 	-DPACKAGE_PIXMAP_DIR=\""$(prefix)/$(DATADIRNAME)/pixmaps"\" \
 	$(PACKAGE_CFLAGS) \
 	$(GNOME_UTILS_CFLAGS) \
-	$(GCONF_CFLAGS)
+	$(GCONF_CFLAGS) \
+	$(LIBXML_CFLAGS) \
+	$(LIBSOUP_CFLAGS)
 
 bin_PROGRAMS = gcalctool
 
 gcalctool_SOURCES = \
 	calctool.c \
 	calctool.h \
+	currency.c \
+	currency.h \
 	display.c \
 	display.h \
 	get.c \
@@ -28,7 +32,7 @@ gcalctool_SOURCES = \
 	mp-trigonometric.c \
 	mp-equation.c \
 	mp-equation.h \
-	mp-equation-private.h \        
+	mp-equation-private.h \
 	mp-equation-lexer.c \
 	mp-equation-lexer.h \
 	mp-equation-parser.c \
@@ -53,6 +57,8 @@ gcalctool_LDADD = \
 	$(PACKAGE_LIBS) \
 	$(GNOME_UTILS_LIBS) \
 	$(GCONF_LIBS) \
+	$(LIBXML_LIBS) \
+	$(LIBSOUP_LIBS) \
 	libparser.a
 
 libparser.a: \
diff --git a/src/calctool.c b/src/calctool.c
index 062f2dd..7a84d83 100644
--- a/src/calctool.c
+++ b/src/calctool.c
@@ -217,5 +217,7 @@ main(int argc, char **argv)
     ui_load();
     ui_start();
 
+    currency_free_resources();
+
     return(0);
 }
diff --git a/src/currency.c b/src/currency.c
new file mode 100644
index 0000000..01bace5
--- /dev/null
+++ b/src/currency.c
@@ -0,0 +1,213 @@
+#include <time.h>
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+#include <libsoup/soup.h>
+
+#include "currency.h"
+#include "mp.h"
+
+typedef struct {
+    char *short_name;
+    MPNumber value;
+} currency;
+
+static currency *currencies = NULL;
+static int currency_count = 0;
+
+static char*
+get_rate_filepath()
+{
+    return g_build_filename(g_get_user_cache_dir (),
+                            "gcalctool",
+                            "eurofxref-daily.xml",
+                            NULL);
+}
+
+int
+currency_get_index(const char *short_name)
+{
+    int i;
+    for (i = 0; i < currency_count; i++) {
+        if (!strcmp(short_name, currencies[i].short_name)) {
+            if (mp_is_negative(&currencies[i].value) ||
+                mp_is_zero(&currencies[i].value)) {
+                return -1;
+            } else {
+                return i;
+            }
+        }
+    }
+    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.
+ */
+int
+currency_rates_needs_update()
+{
+    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);
+
+    if (difftime(buf.st_mtime, time(NULL)) > (60 * 60 * 24 * 7)) {
+        return 1;
+    }
+
+    return 0;
+}
+
+/* FIXME: This code is synchronous, and should thus be accessed from a thread */
+int
+currency_download_rates()
+{
+    gchar *filename, *directory;
+    GError *e = NULL;
+    SoupSession *session = soup_session_sync_new();
+    SoupMessage *msg = soup_message_new(
+        "GET",
+        "http://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml";);
+    soup_session_send_message(session, msg);
+    if (msg->status_code != 200) {
+        return 0;
+    }
+    filename = get_rate_filepath();
+    directory = g_path_get_dirname(filename);
+    g_mkdir_with_parents(directory, 755);
+    g_free(directory);
+
+    g_file_set_contents(filename,
+                        msg->response_body->data,
+                        msg->response_body->length,
+                        &e);
+    if (e != NULL) {
+        fprintf(stderr, "Couldn't download currency file: %s\n", e->message);
+        return 0;
+    }
+    g_free(filename);
+    g_object_unref(session);
+
+    return 1;
+}
+
+static void
+set_rate (xmlNodePtr node, currency *cur)
+{
+    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, &(cur->value));
+            xmlFree (val);
+        }
+    }
+}
+
+void currency_load_rates()
+{
+    char *filename = get_rate_filepath();
+    xmlDocPtr document;
+    xmlXPathContextPtr xpath_ctx;
+    xmlXPathObjectPtr xpath_obj;
+    int i, 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], &currencies[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();
+}
+
+void
+currency_convert(const MPNumber *from_amount,
+                 int from_index,
+                 int to_index,
+                 MPNumber *to_amount)
+{
+    if (mp_is_zero(&currencies[from_index].value) ||
+        mp_is_zero(&currencies[to_index].value)) {
+        mp_set_from_integer(0, to_amount);
+        return;
+    }
+
+    mp_divide(from_amount, &currencies[from_index].value, to_amount);
+    mp_multiply(to_amount, &currencies[to_index].value, to_amount);
+}
+
+void
+currency_free_resources()
+{
+    int i;
+    for (i = 0; i < currency_count; i++) {
+        if (currencies[i].short_name != NULL)
+            xmlFree(currencies[i].short_name);
+    }
+    g_slice_free1(currency_count * sizeof(currency), currencies);
+    currencies = NULL;
+    currency_count = 0;
+
+}
diff --git a/src/currency.h b/src/currency.h
new file mode 100644
index 0000000..c08c216
--- /dev/null
+++ b/src/currency.h
@@ -0,0 +1,74 @@
+#include <glib/gi18n.h>
+
+#include "mp.h"
+
+struct currency_name {
+    char *short_name;
+    char *long_name;
+};
+
+/*
+ * 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", N_("Brazilian real")},
+    {"CAD", N_("Canadian dollar")},
+    {"CHF", N_("Swiss franc")},
+    {"CNY", N_("Chinese yuan renminbi")},
+    {"CZK", N_("Czech koruna")},
+    {"DKK", N_("Danish krone")},
+    {"EEK", N_("Estonian kroon")},
+    {"EUR", N_("Euro")},
+    {"GBP", N_("Pound sterling")},
+    {"HKD", N_("Hong Kong dollar")},
+    {"HRK", N_("Croatian kuna")},
+    {"HUF", N_("Hungarian forint")},
+    {"IDR", N_("Indonesian rupiah")},
+    {"INR", N_("Indian rupee")},
+    {"ISK", N_("Icelandic krona")},
+    {"JPY", N_("Japanese yen")},
+    {"KRW", N_("South Korean won")},
+    {"LTL", N_("Lithuanian litas")},
+    {"LVL", N_("Latvian lats")},
+    {"MXN", N_("Mexican peso")},
+    {"MYR", N_("Malaysian ringgit")},
+    {"NOK", N_("Norwegian krone")},
+    {"NZD", N_("New Zealand dollar")},
+    {"PHP", N_("Philippine peso")},
+    {"PLN", N_("Polish zloty")},
+    {"RON", N_("New Romanian leu")},
+    {"RUB", N_("Russian rouble")},
+    {"SEK", N_("Swedish krona")},
+    {"SGD", N_("Singapore dollar")},
+    {"THB", N_("Thai baht")},
+    {"TRY", N_("New Turkish lira")},
+    {"USD", N_("US dollar")},
+    {"ZAR", N_("South African rand")},
+    {NULL, NULL}
+};
+
+/* Returns 1 if the user needs to update his/her rates, 0 otherwise */
+int currency_rates_needs_update();
+
+/* Dowloads new rates. Returns 1 on success, 0 otherwise */
+int currency_download_rates();
+
+/* Loads rates from disk. Should be called after having made sure the rates
+ * are not outdated, but before anything else. */
+void currency_load_rates();
+
+/* Returns an internal index for each short name to be used with
+ * currency_convert. A negative value means invalid. */
+int currency_get_index(const char *short_name);
+
+/* Converts an amount of money from one currency to another */
+void currency_convert(const MPNumber *from_amount,
+                      int from_index,
+                      int to_index,
+                      MPNumber *to_amount);
+
+/* Frees up all allocated resources */
+void currency_free_resources();
diff --git a/src/gtk.c b/src/gtk.c
index e05dd64..7b638bd 100644
--- a/src/gtk.c
+++ b/src/gtk.c
@@ -35,6 +35,7 @@
 
 #include "config.h"
 #include "financial.h"
+#include "currency.h"
 #include "mp-equation.h"
 #include "display.h"
 #include "get.h"
@@ -210,6 +211,11 @@ typedef enum {
     POPUP_CENTERED   /* Center popup within baseframe */
 } PopupLocation;
 
+typedef enum {
+    CURRENCY_TARGET_UPPER,
+    CURRENCY_TARGET_LOWER
+} CurrencyTargetRow;
+
 static void load_ui(GtkBuilder *ui, const gchar *filename)
 {
     GError *error = NULL;
@@ -394,7 +400,7 @@ do_button(int function, gpointer arg)
 static void
 do_text(const char *text)
 {
-    do_button(FN_TEXT, (gpointer) text); // FIXME: Not 64 bit safe
+    do_button(FN_TEXT, (gpointer) text);
 }
 
 
@@ -707,6 +713,12 @@ static void
 setup_finc_dialogs(void)
 {
     int i, j;
+    GtkListStore *currency_store;
+    GtkCellRenderer *render;
+    GtkSpinButton *currency_amount_upper;
+    GtkSpinButton *currency_amount_lower;
+    GtkComboBox   *currency_type_upper;
+    GtkComboBox   *currency_type_lower;
 
     X.financial = gtk_builder_new();
     load_ui(X.financial, UI_FINC_FILE);
@@ -731,6 +743,53 @@ setup_finc_dialogs(void)
         }
     }
 
+    currency_amount_upper = GTK_SPIN_BUTTON(gtk_builder_get_object(
+        X.financial,
+        "currency_amount_upper"));
+    currency_amount_lower = GTK_SPIN_BUTTON(gtk_builder_get_object(
+        X.financial,
+        "currency_amount_lower"));
+    currency_type_upper = GTK_COMBO_BOX(gtk_builder_get_object(
+        X.financial,
+        "currency_type_upper"));
+    currency_type_lower = GTK_COMBO_BOX(gtk_builder_get_object(
+        X.financial,
+        "currency_type_lower"));
+
+    currency_store = gtk_list_store_new(2,
+                                                      G_TYPE_INT,
+                                                      G_TYPE_STRING);
+
+    gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(currency_store),
+                                         1,
+                                         GTK_SORT_ASCENDING);
+
+    gtk_combo_box_set_model(currency_type_upper,
+                            GTK_TREE_MODEL(currency_store));
+    gtk_combo_box_set_model(currency_type_lower,
+                            GTK_TREE_MODEL(currency_store));
+
+    render = gtk_cell_renderer_text_new();
+
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(currency_type_upper),
+                               render,
+                               TRUE);
+    gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(currency_type_lower),
+                               render,
+                               TRUE);
+
+    gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(currency_type_upper),
+                                  render,
+                                  "text",
+                                  1);
+    gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(currency_type_lower),
+                                  render,
+                                  "text",
+                                  1);
+
+    set_int_data(X.financial, "currency_amount_upper", "target", CURRENCY_TARGET_LOWER);
+    set_int_data(X.financial, "currency_amount_lower", "target", CURRENCY_TARGET_UPPER);
+
     gtk_builder_connect_signals(X.financial, NULL);
 }
 
@@ -865,6 +924,150 @@ finc_cb(GtkWidget *widget, GdkEventButton *event)
     do_finc(g_object_get_data(G_OBJECT(widget), "finc_dialog"));
 }
 
+static void
+recalculate_currency(CurrencyTargetRow target)
+{
+    int upper_index, lower_index;
+    GtkComboBox *combo_upper = GTK_COMBO_BOX(gtk_builder_get_object(
+        X.financial,
+        "currency_type_upper"));
+    GtkComboBox *combo_lower = GTK_COMBO_BOX(gtk_builder_get_object(
+        X.financial,
+        "currency_type_lower"));
+    GtkSpinButton *spin_upper = GTK_SPIN_BUTTON(gtk_builder_get_object(
+        X.financial,
+        "currency_amount_upper"));
+    GtkSpinButton *spin_lower = GTK_SPIN_BUTTON(gtk_builder_get_object(
+        X.financial,
+        "currency_amount_lower"));
+
+    GtkTreeModel *model = gtk_combo_box_get_model(combo_upper);
+    GtkTreeIter iter;
+
+    if (!gtk_combo_box_get_active_iter(combo_upper, &iter))
+        return;
+    gtk_tree_model_get(model, &iter, 0, &upper_index, -1);
+
+    if (!gtk_combo_box_get_active_iter(combo_lower, &iter))
+        return;
+    gtk_tree_model_get(model, &iter, 0, &lower_index, -1);
+
+    if (target == CURRENCY_TARGET_LOWER) {
+        MPNumber input, output;
+        mp_set_from_double (gtk_spin_button_get_value(spin_upper), &input);
+        currency_convert(&input, upper_index, lower_index, &output);
+        if (!mp_is_zero(&output))
+            gtk_spin_button_set_value(spin_lower, mp_cast_to_double(&output));
+    } else {
+        MPNumber input, output;
+        mp_set_from_double (gtk_spin_button_get_value(spin_lower), &input);
+        currency_convert(&input, lower_index, upper_index, &output);
+        if (!mp_is_zero(&output))
+            gtk_spin_button_set_value(spin_upper, mp_cast_to_double(&output));
+    }
+}
+
+G_MODULE_EXPORT
+void
+currency_type_cb(GtkComboBox *combo, gpointer user_data)
+{
+    recalculate_currency(CURRENCY_TARGET_LOWER);
+}
+
+G_MODULE_EXPORT
+void
+currency_amount_cb (GtkSpinButton *spinbutton, gpointer user_data)
+{
+    recalculate_currency(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(spinbutton),
+                                                           "target")));
+}
+
+static void
+setup_currency_rates ()
+{
+    static int has_run = 0;
+    int i;
+    GtkListStore *currency_store;
+    GObject *currency_type;
+    if (has_run)
+        return;
+
+    if (currency_rates_needs_update()) {
+        GtkWidget *dialog = gtk_message_dialog_new(NULL, 0,
+                                        GTK_MESSAGE_INFO,
+                                        GTK_BUTTONS_YES_NO,
+                                        /* Translators: Title of the error dialog when unable to load the UI files */
+                                        N_("You don't have any recent currency rates. Should I download some now?"));
+        int response = gtk_dialog_run(GTK_DIALOG(dialog));
+        gtk_widget_destroy(dialog);
+
+        if (response == GTK_RESPONSE_YES) {
+            if (!currency_download_rates()) {
+                dialog = gtk_message_dialog_new(NULL, 0,
+                                                GTK_MESSAGE_ERROR,
+                                                GTK_BUTTONS_OK,
+                                                /* Translators: Title of the error dialog when unable to load the UI files */
+                                                N_("I could not download any currency rates. You may receive inaccurate results, or you may not receive any results at all."));
+            }
+        }
+    }
+    currency_load_rates();
+
+    currency_type = gtk_builder_get_object(X.financial, "currency_type_upper");
+    currency_store = GTK_LIST_STORE(gtk_combo_box_get_model(
+        GTK_COMBO_BOX(currency_type)));
+
+    for (i = 0; currency_names[i].short_name; i++) {
+        GtkTreeIter iter;
+        int index;
+
+        if ((index = currency_get_index(currency_names[i].short_name)) < 0) {
+            continue;
+        }
+        gtk_list_store_append(currency_store, &iter);
+        gtk_list_store_set(currency_store, &iter,
+                           0, index,
+                           1, gettext(currency_names[i].long_name),
+                           -1);
+    }
+
+    has_run = 1;
+}
+
+G_MODULE_EXPORT
+void
+currency_cb(GtkWidget *widget, GdkEventButton *event)
+{
+    GtkDialog *win;
+    GtkSpinButton *c_amount_upper, *c_amount_lower;
+    MPNumber display_val;
+    if (X.financial == NULL)
+        setup_finc_dialogs();
+
+    setup_currency_rates();
+
+    win = GTK_DIALOG(gtk_builder_get_object(X.financial, "currency_dialog"));
+    c_amount_upper = GTK_SPIN_BUTTON(gtk_builder_get_object(
+        X.financial,
+        "currency_amount_upper"));
+    c_amount_lower = GTK_SPIN_BUTTON(gtk_builder_get_object(
+        X.financial,
+        "currency_amount_lower"));
+    if (display_is_usable_number(&v->display, &display_val)) {
+        double start_val = mp_cast_to_double(&display_val);
+        gtk_spin_button_set_value(c_amount_upper, start_val);
+    }
+    gtk_widget_grab_focus(GTK_WIDGET(c_amount_upper));
+    gtk_dialog_run(win);
+
+    char* result = g_strdup_printf("%.2f",
+                                   gtk_spin_button_get_value(c_amount_lower));
+    mp_set_from_string(result, &display_val);
+    g_free(result);
+
+    display_set_number(&v->display, &display_val);
+    gtk_widget_hide(GTK_WIDGET(win));
+}
 
 G_MODULE_EXPORT
 void



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]