GnuCash c935c2f+
Loading...
Searching...
No Matches
gnc-amount-edit.c
1/********************************************************************\
2 * gnc-amount-edit.h -- amount editor widget *
3 * *
4 * Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
5 * *
6 * This program is free software; you can redistribute it and/or *
7 * modify it under the terms of the GNU General Public License as *
8 * published by the Free Software Foundation; either version 2 of *
9 * the License, or (at your option) any later version. *
10 * *
11 * This program is distributed in the hope that it will be useful, *
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14 * GNU General Public License for more details. *
15 * *
16 * You should have received a copy of the GNU General Public License*
17 * along with this program; if not, contact: *
18 * *
19 * Free Software Foundation Voice: +1-617-542-5942 *
20 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21 * Boston, MA 02110-1301, USA gnu@gnu.org *
22\********************************************************************/
23/*
24 @NOTATION@
25 */
26
27#include <config.h>
28
29#include <gtk/gtk.h>
30#include <gdk/gdkkeysyms.h>
31#include <glib/gi18n.h>
32
33#include "gnc-amount-edit.h"
34#include "gnc-exp-parser.h"
35#include "gnc-locale-utils.h"
36#include "gnc-ui-util.h"
37#include "qof.h"
38#include "dialog-utils.h"
39#include "gnc-ui.h"
40
41#ifdef G_OS_WIN32
42# include <gdk/gdkwin32.h>
43#endif
44
45/* Signal codes */
46enum
47{
48 ACTIVATE,
49 CHANGED,
50 AMOUNT_CHANGED,
51 LAST_SIGNAL
52};
53
54static guint amount_edit_signals [LAST_SIGNAL] = { 0 };
55
56static void gnc_amount_edit_changed (GtkEditable *gae,
57 gpointer user_data);
58static void gnc_amount_edit_paste_clipboard (GtkEntry *entry,
59 gpointer user_data);
60static gint gnc_amount_edit_key_press (GtkWidget *widget,
61 GdkEventKey *event,
62 gpointer user_data);
63
64#define GNC_AMOUNT_EDIT_PATH "gnc-amount-edit-path"
65
67{
68 GtkBox box;
69 GtkEntry *entry;
70 GtkWidget *image;
71
72 gboolean disposed;
73
74 gboolean need_to_parse;
75
76 GNCPrintAmountInfo print_info;
77
78 gboolean block_changed;
79
80 gnc_numeric amount;
81
82 int fraction;
83
84 gboolean evaluate_on_enter;
85 gboolean validate_on_change;
86
87 gboolean show_warning_symbol;
88
89};
90
91G_DEFINE_TYPE (GNCAmountEdit, gnc_amount_edit, GTK_TYPE_BOX)
92
93static void
94gnc_amount_edit_finalize (GObject *object)
95{
96 g_return_if_fail (object != NULL);
97 g_return_if_fail (GNC_IS_AMOUNT_EDIT(object));
98
99 G_OBJECT_CLASS (gnc_amount_edit_parent_class)->finalize (object);
100}
101
102static void
103gnc_amount_edit_dispose (GObject *object)
104{
105 GNCAmountEdit *gae;
106
107 g_return_if_fail (object != NULL);
108 g_return_if_fail (GNC_IS_AMOUNT_EDIT(object));
109
110 gae = GNC_AMOUNT_EDIT(object);
111
112 if (gae->disposed)
113 return;
114
115 gae->disposed = TRUE;
116
117 gtk_widget_destroy (GTK_WIDGET(gae->entry));
118 gae->entry = NULL;
119
120 gtk_widget_destroy (GTK_WIDGET(gae->image));
121 gae->image = NULL;
122
123 G_OBJECT_CLASS (gnc_amount_edit_parent_class)->dispose (object);
124}
125
126static void
127gnc_amount_edit_class_init (GNCAmountEditClass *klass)
128{
129 GObjectClass *object_class = G_OBJECT_CLASS(klass);
130
131 object_class->dispose = gnc_amount_edit_dispose;
132 object_class->finalize = gnc_amount_edit_finalize;
133
134 amount_edit_signals [ACTIVATE] =
135 g_signal_new ("activate",
136 G_OBJECT_CLASS_TYPE(object_class),
137 G_SIGNAL_RUN_FIRST,
138 0,
139 NULL,
140 NULL,
141 g_cclosure_marshal_VOID__VOID,
142 G_TYPE_NONE,
143 0);
144
145 amount_edit_signals [CHANGED] =
146 g_signal_new ("changed",
147 G_OBJECT_CLASS_TYPE(object_class),
148 G_SIGNAL_RUN_FIRST,
149 0,
150 NULL,
151 NULL,
152 g_cclosure_marshal_VOID__VOID,
153 G_TYPE_NONE,
154 0);
155
156 amount_edit_signals [AMOUNT_CHANGED] =
157 g_signal_new ("amount_changed",
158 G_OBJECT_CLASS_TYPE(object_class),
159 G_SIGNAL_RUN_FIRST,
160 0,
161 NULL,
162 NULL,
163 g_cclosure_marshal_VOID__VOID,
164 G_TYPE_NONE,
165 0);
166}
167
168static void
169gnc_amount_edit_init (GNCAmountEdit *gae)
170{
171 gtk_orientable_set_orientation (GTK_ORIENTABLE(gae),
172 GTK_ORIENTATION_HORIZONTAL);
173
174 gae->entry = GTK_ENTRY(gtk_entry_new());
175 gae->need_to_parse = FALSE;
176 gae->amount = gnc_numeric_zero ();
177 gae->print_info = gnc_default_print_info (FALSE);
178 gae->fraction = 0;
179 gae->evaluate_on_enter = FALSE;
180 gae->validate_on_change = FALSE;
181 gae->block_changed = FALSE;
182 gae->show_warning_symbol = TRUE;
183 gae->disposed = FALSE;
184
185 // Set the name for this widget so it can be easily manipulated with css
186 gtk_widget_set_name (GTK_WIDGET(gae), "gnc-id-amount-edit");
187
188 g_signal_connect (G_OBJECT(gae->entry), "key-press-event",
189 G_CALLBACK(gnc_amount_edit_key_press), gae);
190
191 g_signal_connect (G_OBJECT(gae->entry), "changed",
192 G_CALLBACK(gnc_amount_edit_changed), gae);
193
194 g_signal_connect (G_OBJECT(gae->entry), "paste-clipboard",
195 G_CALLBACK(gnc_amount_edit_paste_clipboard), gae);
196}
197
198static void
199gnc_amount_edit_changed (GtkEditable *editable, gpointer user_data)
200{
201 GNCAmountEdit *gae = GNC_AMOUNT_EDIT(user_data);
202
203 gae->need_to_parse = TRUE;
204
205 if (gae->block_changed)
206 return;
207
208 if (gae->validate_on_change)
209 {
210 gnc_numeric amount;
211 gnc_amount_edit_expr_is_valid (gae, &amount, TRUE, NULL);
212 }
213 g_signal_emit (gae, amount_edit_signals [CHANGED], 0);
214}
215
216static void
217gnc_amount_edit_paste_clipboard (GtkEntry *entry, gpointer user_data)
218{
219 GNCAmountEdit *gae = GNC_AMOUNT_EDIT(user_data);
220 GtkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET(entry),
221 GDK_SELECTION_CLIPBOARD);
222 gchar *text = gtk_clipboard_wait_for_text (clipboard);
223 gchar *filtered_text;
224 gint start_pos, end_pos;
225 gint position;
226
227 if (!text)
228 return;
229
230 if (gtk_widget_get_visible (GTK_WIDGET(gae->image)))
231 {
232 gtk_widget_hide (GTK_WIDGET(gae->image));
233 gtk_widget_set_tooltip_text (GTK_WIDGET(gae->image), NULL);
234 }
235
236 filtered_text = gnc_filter_text_for_control_chars (text);
237
238 if (!filtered_text)
239 {
240 g_free (text);
241 return;
242 }
243
244 position = gtk_editable_get_position (GTK_EDITABLE(entry));
245
246 if (gtk_editable_get_selection_bounds (GTK_EDITABLE(entry),
247 &start_pos, &end_pos))
248 {
249 position = start_pos;
250
251 gae->block_changed = TRUE;
252 gtk_editable_delete_selection (GTK_EDITABLE(entry));
253 gae->block_changed = FALSE;
254 gtk_editable_insert_text (GTK_EDITABLE(entry),
255 filtered_text, -1, &position);
256 }
257 else
258 gtk_editable_insert_text (GTK_EDITABLE(entry),
259 filtered_text, -1, &position);
260
261 gtk_editable_set_position (GTK_EDITABLE(entry), position);
262
263 g_signal_stop_emission_by_name (G_OBJECT(entry), "paste-clipboard");
264
265 g_free (text);
266 g_free (filtered_text);
267}
268
269static gint
270gnc_amount_edit_key_press (GtkWidget *widget, GdkEventKey *event, gpointer user_data)
271{
272 GNCAmountEdit *gae = GNC_AMOUNT_EDIT(user_data);
273 gint result;
274
275 if (gtk_widget_get_visible (GTK_WIDGET(gae->image)))
276 {
277 gtk_widget_hide (GTK_WIDGET(gae->image));
278 gtk_widget_set_tooltip_text (GTK_WIDGET(gae->image), NULL);
279 }
280
281 if (event->keyval == GDK_KEY_KP_Decimal)
282 {
283 gchar *decimal;
284
285 if (gae->print_info.monetary)
286 {
287 struct lconv *lc = gnc_localeconv ();
288 event->keyval = lc->mon_decimal_point[0];
289 decimal = g_strdup_printf ("%c", lc->mon_decimal_point[0]);
290 }
291 else
292 decimal = g_strdup_printf ("%c",'.');
293
294 GtkEditable *editable = GTK_EDITABLE(widget);
295 gint start_pos, end_pos;
296 gint position = gtk_editable_get_position (editable);
297
298 if (gtk_editable_get_selection_bounds (editable,
299 &start_pos, &end_pos))
300 {
301 position = start_pos;
302
303 gtk_editable_delete_selection (editable);
304 gtk_editable_insert_text (editable,
305 decimal, -1, &position);
306 }
307 else
308 gtk_editable_insert_text (editable,
309 decimal, -1, &position);
310
311 gtk_editable_set_position (editable, position);
312 g_free (decimal);
313 result = TRUE;
314 }
315 else
316 result = (* GTK_WIDGET_GET_CLASS(widget)->key_press_event)(widget, event);
317
318 switch (event->keyval)
319 {
320 case GDK_KEY_Return:
321 if (event->state & (GDK_MODIFIER_INTENT_DEFAULT_MOD_MASK))
322 break;
323 case GDK_KEY_KP_Enter:
324 if (gae->evaluate_on_enter)
325 break;
326 else
327 g_signal_emit (gae, amount_edit_signals [ACTIVATE], 0);
328 return result;
329 default:
330 return result;
331 }
332
333 gnc_amount_edit_evaluate (gae, NULL);
334 g_signal_emit (gae, amount_edit_signals [ACTIVATE], 0);
335
336 return TRUE;
337}
338
339GtkWidget *
340gnc_amount_edit_new (void)
341{
342 GNCAmountEdit *gae = g_object_new (GNC_TYPE_AMOUNT_EDIT, NULL);
343
344 gtk_box_pack_start (GTK_BOX(gae), GTK_WIDGET(gae->entry), TRUE, TRUE, 0);
345 gtk_entry_set_width_chars (GTK_ENTRY(gae->entry), 12);
346 gae->image = gtk_image_new_from_icon_name ("dialog-warning", GTK_ICON_SIZE_SMALL_TOOLBAR);
347 gtk_box_pack_start (GTK_BOX(gae), GTK_WIDGET(gae->image), FALSE, FALSE, 6);
348 gtk_widget_set_no_show_all (GTK_WIDGET(gae->image), TRUE);
349 gtk_widget_hide (GTK_WIDGET(gae->image));
350 gtk_widget_show_all (GTK_WIDGET(gae));
351
352 return GTK_WIDGET(gae);
353}
354
355static gint
356get_original_error_position (const gchar *string, const gchar *symbol,
357 gint error_pos)
358{
359 gint original_error_pos = error_pos;
360 gint text_len;
361 gint symbol_len;
362
363 if (error_pos == 0)
364 return 0;
365
366 if (!string || !symbol)
367 return error_pos;
368
369 if (g_strrstr (string, symbol) == NULL)
370 return error_pos;
371
372 if (!g_utf8_validate (string, -1, NULL))
373 return error_pos;
374
375 text_len = g_utf8_strlen (string, -1);
376 symbol_len = g_utf8_strlen (symbol, -1);
377
378 for (gint x = 0; x < text_len; x++)
379 {
380 gchar *temp = g_utf8_offset_to_pointer (string, x);
381
382 if (g_str_has_prefix (temp, symbol))
383 original_error_pos = original_error_pos + symbol_len;
384
385 if (x >= original_error_pos)
386 break;
387
388 if (g_strrstr (temp, symbol) == NULL)
389 break;
390 }
391 return original_error_pos;
392}
393
394static inline GQuark
395exp_validate_quark (void)
396{
397 return g_quark_from_static_string ("exp_validate");
398}
399
400gint
401gnc_amount_edit_expr_is_valid (GNCAmountEdit *gae, gnc_numeric *amount,
402 gboolean empty_ok, GError **error)
403{
404 const char *string;
405 char *error_loc;
406 gboolean ok;
407 gchar *err_msg = NULL;
408 gint err_code;
409 const gnc_commodity *comm;
410 char *filtered_string;
411 const gchar *symbol = NULL;
412
413 g_return_val_if_fail (gae != NULL, -1);
414 g_return_val_if_fail (GNC_IS_AMOUNT_EDIT(gae), -1);
415
416 string = gtk_entry_get_text (GTK_ENTRY(gae->entry));
417
418 if (gtk_widget_get_visible (GTK_WIDGET(gae->image)))
419 {
420 gtk_widget_hide (GTK_WIDGET(gae->image));
421 gtk_widget_set_tooltip_text (GTK_WIDGET(gae->image), NULL);
422 }
423
424 comm = gae->print_info.commodity;
425
426 filtered_string = gnc_filter_text_for_currency_commodity (comm, string, &symbol);
427
428 if (!filtered_string || *filtered_string == '\0')
429 {
430 *amount = gnc_numeric_zero ();
431 g_free (filtered_string);
432 if (empty_ok)
433 return -1; /* indicate an empty field */
434 else
435 return 0; /* indicate successfully parsed as 0 */
436 }
437
438 error_loc = NULL;
439 ok = gnc_exp_parser_parse (filtered_string, amount, &error_loc);
440
441 if (ok)
442 {
443 g_free (filtered_string);
444 return 0;
445 }
446
447 /* Not ok */
448 if (error_loc != NULL)
449 {
450 err_code = get_original_error_position (string, symbol,
451 (error_loc - filtered_string));
452
453 err_msg = g_strdup_printf (_("An error occurred while processing '%s' at position %d"),
454 string, err_code);
455 }
456 else
457 {
458 err_code = 1000;
459 err_msg = g_strdup_printf (_("An error occurred while processing '%s'"),
460 string);
461 }
462
463 if (error)
464 g_set_error_literal (error, exp_validate_quark(), err_code, err_msg);
465
466 if (gae->show_warning_symbol)
467 {
468 gtk_widget_set_tooltip_text (GTK_WIDGET(gae->image), err_msg);
469 gtk_widget_show (GTK_WIDGET(gae->image));
470 gtk_widget_queue_resize (GTK_WIDGET(gae->entry));
471 }
472
473 g_free (filtered_string);
474 g_free (err_msg);
475 return 1;
476}
477
478gboolean
479gnc_amount_edit_evaluate (GNCAmountEdit *gae, GError **error)
480{
481 gint result;
482 gnc_numeric amount;
483 GError *tmp_error = NULL;
484
485 g_return_val_if_fail (gae != NULL, FALSE);
486 g_return_val_if_fail (GNC_IS_AMOUNT_EDIT(gae), FALSE);
487
488 if (!gae->need_to_parse)
489 return TRUE;
490
491 result = gnc_amount_edit_expr_is_valid (gae, &amount, FALSE, &tmp_error);
492
493 if (result == -1) /* field was empty and may remain so */
494 return TRUE;
495
496 if (result == 0) /* parsing successful */
497 {
498 gnc_numeric old_amount = gae->amount;
499
500 if (gae->fraction > 0)
501 amount = gnc_numeric_convert (amount, gae->fraction, GNC_HOW_RND_ROUND_HALF_UP);
502
503 gnc_amount_edit_set_amount (gae, amount);
504
505 if (!gnc_numeric_equal (amount, old_amount))
506 g_signal_emit (gae, amount_edit_signals [AMOUNT_CHANGED], 0);
507
508 gtk_editable_set_position (GTK_EDITABLE(gae->entry), -1);
509 return TRUE;
510 }
511
512 /* Parse error */
513 if (tmp_error)
514 {
515 if (tmp_error->code < 1000)
516 gtk_editable_set_position (GTK_EDITABLE(gae->entry), tmp_error->code);
517
518 if (error)
519 g_propagate_error (error, tmp_error);
520 else
521 g_error_free (tmp_error);
522 }
523 return FALSE;
524}
525
526gnc_numeric
527gnc_amount_edit_get_amount (GNCAmountEdit *gae)
528{
529 g_return_val_if_fail (gae != NULL, gnc_numeric_zero ());
530 g_return_val_if_fail (GNC_IS_AMOUNT_EDIT(gae), gnc_numeric_zero ());
531
532 gnc_amount_edit_evaluate (gae, NULL);
533
534 return gae->amount;
535}
536
537double
538gnc_amount_edit_get_damount (GNCAmountEdit *gae)
539{
540 g_return_val_if_fail (gae != NULL, 0.0);
541 g_return_val_if_fail (GNC_IS_AMOUNT_EDIT(gae), 0.0);
542
543 gnc_amount_edit_evaluate (gae, NULL);
544
545 return gnc_numeric_to_double (gae->amount);
546}
547
548void
549gnc_amount_edit_set_amount (GNCAmountEdit *gae, gnc_numeric amount)
550{
551 const char * amount_string;
552
553 g_return_if_fail (gae != NULL);
554 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
555 g_return_if_fail (!gnc_numeric_check (amount));
556
557 if (gtk_widget_get_visible (GTK_WIDGET(gae->image)))
558 {
559 gtk_widget_hide (GTK_WIDGET(gae->image));
560 gtk_widget_set_tooltip_text (GTK_WIDGET(gae->image), NULL);
561 }
562
563 /* Update the display. */
564 amount_string = xaccPrintAmount (amount, gae->print_info);
565 gtk_entry_set_text (GTK_ENTRY(gae->entry), amount_string);
566
567 gae->amount = amount;
568 gae->need_to_parse = FALSE;
569}
570
571void
572gnc_amount_edit_set_damount (GNCAmountEdit *gae, double damount)
573{
574 gnc_numeric amount;
575 int fraction;
576
577 g_return_if_fail (gae != NULL);
578 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
579
580 if (gae->fraction > 0)
581 fraction = gae->fraction;
582 else
583 fraction = 100000;
584
585 amount = double_to_gnc_numeric (damount, fraction, GNC_HOW_RND_ROUND_HALF_UP);
586
587 gnc_amount_edit_set_amount (gae, amount);
588}
589
590void
591gnc_amount_edit_set_print_info (GNCAmountEdit *gae,
592 GNCPrintAmountInfo print_info)
593{
594 g_return_if_fail (gae != NULL);
595 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
596
597 gae->print_info = print_info;
598 gae->print_info.use_symbol = 0;
599}
600
601void
602gnc_amount_edit_set_fraction (GNCAmountEdit *gae, int fraction)
603{
604 g_return_if_fail (gae != NULL);
605 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
606
607 fraction = MAX (0, fraction);
608
609 gae->fraction = fraction;
610}
611
612GtkWidget *
613gnc_amount_edit_gtk_entry (GNCAmountEdit *gae)
614{
615 g_return_val_if_fail (gae != NULL, NULL);
616 g_return_val_if_fail (GNC_IS_AMOUNT_EDIT(gae), NULL);
617
618 return GTK_WIDGET(gae->entry);
619}
620
621void
622gnc_amount_edit_set_evaluate_on_enter (GNCAmountEdit *gae,
623 gboolean evaluate_on_enter)
624{
625 g_return_if_fail (gae != NULL);
626 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
627
628 gae->evaluate_on_enter = evaluate_on_enter;
629}
630
631void
632gnc_amount_edit_set_validate_on_change (GNCAmountEdit *gae,
633 gboolean validate_on_change)
634{
635 g_return_if_fail (gae != NULL);
636 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
637
638 gae->validate_on_change = validate_on_change;
639}
640
641void
642gnc_amount_edit_select_region (GNCAmountEdit *gae,
643 gint start_pos,
644 gint end_pos)
645{
646 g_return_if_fail (gae != NULL);
647 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
648
649 gtk_editable_select_region (GTK_EDITABLE(gae->entry),
650 start_pos,
651 end_pos);
652}
653
654void
655gnc_amount_edit_show_warning_symbol (GNCAmountEdit *gae, gboolean show)
656{
657 g_return_if_fail (gae != NULL);
658 g_return_if_fail (GNC_IS_AMOUNT_EDIT(gae));
659
660 gae->show_warning_symbol = show;
661}
662
663void
664gnc_amount_edit_make_mnemonic_target (GNCAmountEdit *gae, GtkWidget *label)
665{
666 if (!gae)
667 return;
668
669 gtk_label_set_mnemonic_widget (GTK_LABEL(label), GTK_WIDGET(gae->entry));
670}
utility functions for the GnuCash UI
char * gnc_filter_text_for_control_chars(const char *text)
Returns the incoming text removed of control characters.
char * gnc_filter_text_for_currency_commodity(const gnc_commodity *comm, const char *incoming_text, const char **symbol)
Returns the incoming text removed of currency symbol.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
double gnc_numeric_to_double(gnc_numeric in)
Convert numeric to floating-point value.
gnc_numeric double_to_gnc_numeric(double in, gint64 denom, gint how)
Convert a floating-point number to a gnc_numeric.
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
Equivalence predicate: Returns TRUE (1) if a and b represent the same number.