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