GnuCash  5.6-150-g038405b370+
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 */
46 enum
47 {
48  ACTIVATE,
49  CHANGED,
50  AMOUNT_CHANGED,
51  LAST_SIGNAL
52 };
53 
54 static guint amount_edit_signals [LAST_SIGNAL] = { 0 };
55 
56 static void gnc_amount_edit_changed (GtkEditable *gae,
57  gpointer user_data);
58 static void gnc_amount_edit_paste_clipboard (GtkEntry *entry,
59  gpointer user_data);
60 static 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 
91 G_DEFINE_TYPE (GNCAmountEdit, gnc_amount_edit, GTK_TYPE_BOX)
92 
93 static void
94 gnc_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 
102 static void
103 gnc_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 
126 static void
127 gnc_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 
168 static void
169 gnc_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 
198 static void
199 gnc_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 
216 static void
217 gnc_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 
269 static gint
270 gnc_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 
339 GtkWidget *
340 gnc_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 
355 static gint
356 get_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 
394 static inline GQuark
395 exp_validate_quark (void)
396 {
397  return g_quark_from_static_string ("exp_validate");
398 }
399 
400 gint
401 gnc_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 
478 gboolean
479 gnc_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 
526 gnc_numeric
527 gnc_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 
537 double
538 gnc_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 
548 void
549 gnc_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 
571 void
572 gnc_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 
590 void
591 gnc_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 
601 void
602 gnc_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 
612 GtkWidget *
613 gnc_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 
621 void
622 gnc_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 
631 void
632 gnc_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 
641 void
642 gnc_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 
654 void
655 gnc_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 
663 void
664 gnc_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 }
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
Equivalence predicate: Returns TRUE (1) if a and b represent the same number.
gnc_numeric double_to_gnc_numeric(double in, gint64 denom, gint how)
Convert a floating-point number to a gnc_numeric.
utility functions for the GnuCash UI
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
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.
double gnc_numeric_to_double(gnc_numeric in)
Convert numeric to floating-point value.
gnc_numeric gnc_numeric_convert(gnc_numeric n, gint64 denom, gint how)
Change the denominator of a gnc_numeric value to the specified denominator under standard arguments &#39;...
char * gnc_filter_text_for_control_chars(const char *text)
Returns the incoming text removed of control characters.
Round to the nearest integer, rounding away from zero when there are two equidistant nearest integers...
Definition: gnc-numeric.h:165
GNCNumericErrorCode gnc_numeric_check(gnc_numeric in)
Check for error signal in value.