1 /*
2  * gnc-currency-edit.c --  Currency editor widget
3  *
4  * Copyright (C) 1997, 1998, 1999, 2000 Free Software Foundation
5  * All rights reserved.
6  *
7  * Gnucash is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Library General Public License
9  * as published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * Gnucash is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Library General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, contact:
19  *
20  * Free Software Foundation           Voice:  +1-617-542-5942
21  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
22  * Boston, MA  02110-1301,  USA       gnu@gnu.org
23  *
24  */
25 
26 /** @addtogroup GUI
27     @{ */
28 /** @addtogroup GncCurrencyEdit
29  * @{ */
30 /** @file gnc-currency-edit.c
31  *  @brief Currency selection widget.
32  *  @author Dave Peticolas <dave@krondo.com>
33  *  @author David Hampton <hampton@employees.org>
34  *
35  *  This widget is a GtkComboBox that is wrapped with support
36  *  functions for building/selecting from a list of ISO4217 currency
37  *  names.  All data is maintained within the widget itself, which
38  *  makes the name/item lookup functions somewhat complicated.  The
39  *  alternative coding would be to keep an auxiliary list of strings
40  *  attached to the widget for lookup purposes, but that would be 100%
41  *  redundant information.
42  *
43  *  This function currently builds a new GtkListStore for each widget
44  *  created.  It could be optimized to build a single list store and
45  *  share across all extant version of the widget, or even build the
46  *  list store once and maintain for the life of the application.
47  *
48  *  When the GtkComboCellEntry widget supports completion, this Gnucash
49  *  widget should be modified so that it is based upon that widget.
50  *  That would give users the capability to select a currency by typing
51  *  its ISO 4217 code (e.g. USD, GBP, etc).  Moving to that widget
52  *  today, however, would cause more problems that its worth.  There is
53  *  currently no way to get access to the embedded GtkEntry widget, and
54  *  therefore no way to implement completion in gnucash or prevent the
55  *  user from typing in random data.
56  */
57 
58 #include <config.h>
59 
60 #include <gtk/gtk.h>
61 #include <glib/gi18n.h>
62 #include <string.h>
63 #include <ctype.h>
64 #include <stdio.h>
65 
66 #include "gnc-currency-edit.h"
67 #include "gnc-commodity.h"
68 #include "gnc-gtk-utils.h"
69 #include "gnc-ui-util.h"
70 #include "gnc-engine.h"
71 #include "dialog-utils.h"
72 
73 /** The debugging module used by this file. */
74 static QofLogModule log_module = GNC_MOD_GUI;
75 
76 static void gnc_currency_edit_init         (GNCCurrencyEdit      *gce);
77 static void gnc_currency_edit_class_init   (GNCCurrencyEditClass *klass);
78 static void gnc_currency_edit_finalize     (GObject *object);
79 static void gnc_currency_edit_mnemonic_changed (GObject    *gobject,
80         GParamSpec *pspec,
81         gpointer    user_data);
82 static void gnc_currency_edit_active_changed (GtkComboBox *gobject,
83         gpointer     user_data);
84 
85 static GtkComboBoxClass *parent_class;
86 
87 /** The instance private data for a content plugin. */
88 typedef struct _GNCCurrencyEditPrivate
89 {
90     gchar *mnemonic;
91 } GNCCurrencyEditPrivate;
92 
93 G_DEFINE_TYPE_WITH_PRIVATE(GNCCurrencyEdit, gnc_currency_edit, GTK_TYPE_COMBO_BOX)
94 
95 #define GET_PRIVATE(o)  \
96    ((GNCCurrencyEditPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_CURRENCY_EDIT))
97 
98 /** @name Basic Object Implementation */
99 /** @{ */
100 
101 enum
102 {
103     PROP_0,
104 
105     PROP_GCE_MNEMONIC,
106 
107     N_PROPERTIES
108 };
109 
110 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
111 
112 static void
gnc_currency_edit_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)113 gnc_currency_edit_set_property (GObject      *object,
114                                 guint         property_id,
115                                 const GValue *value,
116                                 GParamSpec   *pspec)
117 {
118     GNCCurrencyEdit *self = GNC_CURRENCY_EDIT (object);
119     GNCCurrencyEditPrivate *priv = GET_PRIVATE (self);
120 
121     switch (property_id)
122     {
123     case PROP_GCE_MNEMONIC:
124         g_free (priv->mnemonic);
125         priv->mnemonic = g_value_dup_string (value);
126         DEBUG ("mnemonic: %s\n", priv->mnemonic);
127         break;
128 
129     default:
130         /* We don't have any other property... */
131         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
132         break;
133     }
134 }
135 
136 static void
gnc_currency_edit_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)137 gnc_currency_edit_get_property (GObject    *object,
138                                 guint       property_id,
139                                 GValue     *value,
140                                 GParamSpec *pspec)
141 {
142     GNCCurrencyEdit *self = GNC_CURRENCY_EDIT (object);
143     GNCCurrencyEditPrivate *priv = GET_PRIVATE (self);
144 
145     switch (property_id)
146     {
147     case PROP_GCE_MNEMONIC:
148         g_value_set_string (value, priv->mnemonic);
149         break;
150 
151     default:
152         /* We don't have any other property... */
153         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
154         break;
155     }
156 }
157 
158 /** Initialize the GncCurrencyEdit class object.
159  *
160  *  @internal
161  *
162  *  @param klass A pointer to the newly created class object.
163  */
164 static void
gnc_currency_edit_class_init(GNCCurrencyEditClass * klass)165 gnc_currency_edit_class_init (GNCCurrencyEditClass *klass)
166 {
167     GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
168     parent_class = g_type_class_peek_parent (klass);
169 
170     gobject_class->set_property = gnc_currency_edit_set_property;
171     gobject_class->get_property = gnc_currency_edit_get_property;
172     gobject_class->finalize     = gnc_currency_edit_finalize;
173 
174     obj_properties[PROP_GCE_MNEMONIC] =
175         g_param_spec_string ("mnemonic",
176                              "Active currency's mnemonic",
177                              "Active currency's mnemonic",
178                              "USD" /* default value */,
179                              G_PARAM_READWRITE);
180 
181     g_object_class_install_properties (gobject_class,
182                                        N_PROPERTIES,
183                                        obj_properties);
184 }
185 
186 
187 /** Initialize a GncCurrencyEdit object.
188  *
189  *  @internal
190  *
191  *  @param gce A pointer to the newly created object.
192  */
193 static void
gnc_currency_edit_init(GNCCurrencyEdit * gce)194 gnc_currency_edit_init (GNCCurrencyEdit *gce)
195 {
196     // Set the name for this widget so it can be easily manipulated with css
197     gtk_widget_set_name (GTK_WIDGET(gce), "gnc-id-currency-edit");
198 
199     g_signal_connect (gce, "notify::mnemonic",
200                       G_CALLBACK (gnc_currency_edit_mnemonic_changed), gce);
201     g_signal_connect (gce, "changed",
202                       G_CALLBACK (gnc_currency_edit_active_changed), gce);
203 }
204 
205 
206 /** Finalize the GncCurrencyEdit object.  This function is called from
207  *  the G_Object level to complete the destruction of the object.  It
208  *  should release any memory not previously released by the destroy
209  *  function (i.e. the private data structure), then chain up to the
210  *  parent's destroy function.
211  *
212  *  @param object The object being destroyed.
213  *
214  *  @internal
215  */
216 static void
gnc_currency_edit_finalize(GObject * object)217 gnc_currency_edit_finalize (GObject *object)
218 {
219     GNCCurrencyEditPrivate *priv;
220     GNCCurrencyEdit *period;
221 
222     g_return_if_fail (object != NULL);
223     g_return_if_fail (GNC_IS_CURRENCY_EDIT (object));
224 
225     period = GNC_CURRENCY_EDIT(object);
226     priv = GET_PRIVATE(period);
227 
228     g_free (priv->mnemonic);
229 
230     /* Do not free the private data structure itself. It is part of
231      * a larger memory block allocated by the type system. */
232 
233     if (G_OBJECT_CLASS(parent_class)->finalize)
234         (* G_OBJECT_CLASS(parent_class)->finalize) (object);
235 }
236 
237 
238 static void
gnc_currency_edit_mnemonic_changed(GObject * gobject,GParamSpec * pspec,gpointer user_data)239 gnc_currency_edit_mnemonic_changed (GObject    *gobject,
240                                     GParamSpec *pspec,
241                                     gpointer    user_data)
242 {
243 
244     GNCCurrencyEdit *self = GNC_CURRENCY_EDIT (gobject);
245     GNCCurrencyEditPrivate *priv = GET_PRIVATE (self);
246 
247     gnc_commodity *currency = gnc_commodity_table_lookup (gnc_get_current_commodities (),
248                               GNC_COMMODITY_NS_CURRENCY,
249                               priv->mnemonic);
250 
251     /* If there isn't any such commodity, get the default */
252     if (!currency)
253     {
254         currency = gnc_locale_default_currency();
255         DEBUG("gce %p, default currency mnemonic %s",
256               self, gnc_commodity_get_mnemonic(currency));
257     }
258 
259     g_signal_handlers_block_by_func(G_OBJECT(self),
260                                     G_CALLBACK(gnc_currency_edit_mnemonic_changed), user_data);
261     gnc_currency_edit_set_currency(self, currency);
262     g_signal_handlers_unblock_by_func(G_OBJECT(self),
263                                       G_CALLBACK(gnc_currency_edit_mnemonic_changed), user_data);
264 }
265 
266 
gnc_currency_edit_active_changed(GtkComboBox * gobject,gpointer user_data)267 static void gnc_currency_edit_active_changed (GtkComboBox *gobject,
268         gpointer     user_data)
269 {
270     GNCCurrencyEdit *self = GNC_CURRENCY_EDIT (gobject);
271 
272     /* Check that there is a proper selection before proceeding.  Doing so allows
273      * GTK entry completion to proceed. */
274     if (gtk_combo_box_get_active(GTK_COMBO_BOX(self)) != -1)
275     {
276         gnc_commodity *currency = gnc_currency_edit_get_currency (self);
277         const gchar *mnemonic = gnc_commodity_get_mnemonic (currency);
278 
279         g_signal_handlers_block_by_func(G_OBJECT(self),
280                                         G_CALLBACK(gnc_currency_edit_active_changed), user_data);
281         g_object_set (G_OBJECT (self), "mnemonic", mnemonic, NULL);
282         g_signal_handlers_unblock_by_func(G_OBJECT(self),
283                                         G_CALLBACK(gnc_currency_edit_active_changed), user_data);
284     }
285 }
286 
287 /** This auxiliary function adds a single currency name to the combo
288  *  box.  It is called as an iterator function when running a list of
289  *  currencies.
290  *
291  *  @internal
292  *
293  *  @param commodity The currency to add to the selection widget.
294  *
295  *  @param gce A pointer to the selection widget.
296  */
297 static void
add_item(gnc_commodity * commodity,GNCCurrencyEdit * gce)298 add_item(gnc_commodity *commodity, GNCCurrencyEdit *gce)
299 {
300     GtkTreeModel *model;
301     GtkTreeIter iter;
302     const char *string;
303 
304     model = gtk_combo_box_get_model(GTK_COMBO_BOX(gce));
305 
306     string = gnc_commodity_get_printname(commodity);
307 
308     gtk_list_store_append(GTK_LIST_STORE(model), &iter);
309     gtk_list_store_set (GTK_LIST_STORE(model), &iter, 0, string, -1);
310 
311 }
312 
313 
314 /** This auxiliary function adds all the currency names to a combo
315  *  box.
316  *
317  *  @internal
318  *
319  *  @param gce A pointer to the widget that should be filled with
320  *  currency names.
321  */
322 static void
fill_currencies(GNCCurrencyEdit * gce)323 fill_currencies(GNCCurrencyEdit *gce)
324 {
325     GList *currencies;
326 
327     currencies = gnc_commodity_table_get_commodities
328                  (gnc_get_current_commodities (), GNC_COMMODITY_NS_CURRENCY);
329     g_list_foreach(currencies, (GFunc)add_item, gce);
330     g_list_free(currencies);
331 }
332 
333 
334 /*  Create a new GNCCurrencyEdit widget which can be used to provide
335  *  an easy way to enter ISO currency codes.
336  *
337  *  @return A GNCCurrencyEdit widget.
338  */
339 GtkWidget *
gnc_currency_edit_new(void)340 gnc_currency_edit_new (void)
341 {
342     GNCCurrencyEdit *gce;
343     GtkListStore *store;
344 
345     store = gtk_list_store_new (1, G_TYPE_STRING);
346     gce = g_object_new (GNC_TYPE_CURRENCY_EDIT,
347                         "model", store,
348                         "has-entry", TRUE,
349                         NULL);
350     g_object_unref (store);
351 
352     /* Set the column for the text */
353     gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX(gce), 0);
354 
355     /* Now the signals to make sure the user can't leave the
356        widget without a valid currency. */
357     gnc_cbwe_require_list_item(GTK_COMBO_BOX(gce));
358 
359     /* Fill in all the data. */
360     fill_currencies (gce);
361     gtk_tree_sortable_set_sort_column_id
362     (GTK_TREE_SORTABLE(store), 0, GTK_SORT_ASCENDING);
363 
364     return GTK_WIDGET (gce);
365 }
366 
367 /** @} */
368 
369 /** @name Get/Set Functions */
370 /** @{ */
371 
372 /*  Set the widget to display a certain currency name.
373  *
374  *  @param gce The currency editor widget to set.
375  *
376  *  @param currency The currency to set as the displayed/selected
377  *  value of the widget.
378  */
379 void
gnc_currency_edit_set_currency(GNCCurrencyEdit * gce,const gnc_commodity * currency)380 gnc_currency_edit_set_currency (GNCCurrencyEdit *gce,
381                                 const gnc_commodity *currency)
382 {
383     const gchar *printname;
384 
385     g_return_if_fail(gce != NULL);
386     g_return_if_fail(GNC_IS_CURRENCY_EDIT(gce));
387     g_return_if_fail(currency != NULL);
388 
389     printname = gnc_commodity_get_printname(currency);
390     gnc_cbwe_set_by_string(GTK_COMBO_BOX(gce), printname);
391 }
392 
393 
394 /*  Retrieve the displayed currency of the widget.
395  *
396  *  @param gce The currency editor widget whose values should be retrieved.
397  *
398  *  @return A pointer to the selected currency (a gnc_commodity
399  *  structure).
400  */
401 gnc_commodity *
gnc_currency_edit_get_currency(GNCCurrencyEdit * gce)402 gnc_currency_edit_get_currency (GNCCurrencyEdit *gce)
403 {
404     gnc_commodity *commodity;
405     const char *fullname;
406     char *mnemonic, *name;
407     GtkTreeModel *model;
408     GtkTreeIter iter;
409     GValue value = { 0 };
410 
411     g_return_val_if_fail(gce != NULL, NULL);
412     g_return_val_if_fail(GNC_IS_CURRENCY_EDIT(gce), NULL);
413 
414     if (gtk_combo_box_get_active_iter(GTK_COMBO_BOX(gce), &iter))
415     {
416         model = gtk_combo_box_get_model(GTK_COMBO_BOX(gce));
417         gtk_tree_model_get_value(model, &iter, 0, &value);
418         fullname = g_value_get_string(&value);
419         mnemonic = g_strdup(fullname);
420         g_value_unset(&value);
421 
422         name = strchr(mnemonic, ' ');
423         if (name != NULL)
424             *name = '\0';
425         commodity = gnc_commodity_table_lookup (gnc_get_current_commodities (),
426                                                 GNC_COMMODITY_NS_CURRENCY,
427                                                 mnemonic);
428         g_free(mnemonic);
429     }
430     else
431     {
432         g_warning("Combo box returned 'inactive'. Using locale default currency.");
433         commodity = gnc_locale_default_currency();
434     }
435 
436 
437     return commodity;
438 }
439 
440 /** Clear the displayed currency of the widget.
441  *
442  *  This will clear the currency being displayed just like when first created
443  *  but it still returns the default currency as usual
444  *
445  *  @param gce The currency editor widget whose values should be retrieved.
446  */
447 void
gnc_currency_edit_clear_display(GNCCurrencyEdit * gce)448 gnc_currency_edit_clear_display (GNCCurrencyEdit *gce)
449 {
450     GtkTreeModel *model;
451     GtkWidget *entry;
452 
453     g_return_if_fail(gce != NULL);
454     g_return_if_fail(GNC_IS_CURRENCY_EDIT(gce));
455 
456     model = gtk_combo_box_get_model (GTK_COMBO_BOX(gce));
457 
458     entry = gtk_bin_get_child (GTK_BIN(gce));
459 
460     g_object_ref (model);
461 
462     g_signal_handlers_block_by_func (G_OBJECT(gce),
463                                     G_CALLBACK(gnc_currency_edit_active_changed), gce);
464 
465     gtk_combo_box_set_model (GTK_COMBO_BOX(gce), NULL);
466     gtk_entry_set_text (GTK_ENTRY(entry),"");
467     gtk_combo_box_set_active (GTK_COMBO_BOX(gce), -1);
468     gtk_combo_box_set_model (GTK_COMBO_BOX(gce), model);
469 
470     g_signal_handlers_block_by_func (G_OBJECT(gce),
471                                     G_CALLBACK(gnc_currency_edit_active_changed), gce);
472 
473     g_object_unref (model);
474 }
475 
476 /** @} */
477 /** @} */
478 /** @} */
479 
480