GnuCash  5.6-150-g038405b370+
gnc-account-sel.c
1 
26 #include <config.h>
27 
28 #include <stdbool.h>
29 #include <gtk/gtk.h>
30 #include <glib/gi18n.h>
31 
32 #include "account-quickfill.h"
33 #include "dialog-account.h"
34 #include "gnc-account-sel.h"
35 #include "gnc-commodity.h"
36 #include "gnc-gtk-utils.h"
37 #include "gnc-ui-util.h"
38 #include "qof.h"
39 #include "gnc-session.h"
40 #include "dialog-utils.h"
41 
42 #define QKEY "gas_shared_quickfill"
43 
44 /* Signal codes */
45 enum
46 {
47  ACCOUNT_SEL_CHANGED,
48  LAST_SIGNAL
49 };
50 
51 enum account_cols
52 {
53  ACCT_COL_NAME = 0,
54  ACCT_COL_PTR,
55  NUM_ACCT_COLS
56 };
57 
58 #define BUFLEN 1024
59 
61 {
62  GtkBox hbox;
63  gboolean isModal;
64  GtkListStore *store;
65  GtkComboBox *combo;
66  GList *acctTypeFilters;
67  GList *acctCommodityFilters;
68  GList *acctExcludeList;
69  gnc_commodity *default_new_commodity;
70 
71  /* The state of this pointer also serves as a flag about what state
72  * the widget is in WRT the new-account-button ability. */
73  GtkWidget *newAccountButton;
74  GtkTreeRowReference *saved_account_ref;
75  gulong row_changed_id;
76  gulong row_deleted_id;
77 
78  char sep_key_prefix[BUFLEN];
79  gboolean hide_placeholder;
80  gboolean hide_hidden;
81 };
82 
83 enum
84 {
85  PROP_0,
86  PROP_HIDE_PLACEHOLDER,
87  PROP_HIDE_HIDDEN,
88  PROP_HORIZONTAL_EXPAND,
89  PROP_COMBO_ENTRY_WIDTH,
90 };
91 
92 static guint account_sel_signals [LAST_SIGNAL] = { 0 };
93 
94 static void gnc_account_sel_finalize (GObject *object);
95 static void gnc_account_sel_dispose (GObject *object);
96 
97 static void gas_set_property (GObject *object,
98  guint param_id,
99  const GValue *value,
100  GParamSpec *pspec);
101 
102 static void gas_get_property (GObject *object,
103  guint param_id,
104  GValue *value,
105  GParamSpec *pspec);
106 
107 static void gas_new_account_click (GtkButton *b, gpointer ud);
108 
109 #define GNC_ACCOUNT_SEL_PATH "gnc-account-sel-path"
110 
111 G_DEFINE_TYPE (GNCAccountSel, gnc_account_sel, GTK_TYPE_BOX)
112 
113 static void
114 gas_set_property (GObject *object, guint param_id,
115  const GValue *value, GParamSpec *pspec)
116 {
117  GNCAccountSel *gas;
118 
119  g_return_if_fail (object != NULL);
120  g_return_if_fail (GNC_IS_ACCOUNT_SEL(object));
121 
122  gas = GNC_ACCOUNT_SEL(object);
123 
124  switch (param_id)
125  {
126  case PROP_HIDE_PLACEHOLDER:
127  gas->hide_placeholder = g_value_get_boolean (value);
128  break;
129 
130  case PROP_HIDE_HIDDEN:
131  gas->hide_hidden = g_value_get_boolean (value);
132  break;
133 
134  case PROP_HORIZONTAL_EXPAND:
135  gtk_widget_set_hexpand (GTK_WIDGET(gas), g_value_get_boolean (value));
136  gtk_widget_set_hexpand (GTK_WIDGET(gas->combo), g_value_get_boolean (value));
137  break;
138 
139  case PROP_COMBO_ENTRY_WIDTH:
140  {
141  GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
142  gboolean expand = FALSE;
143  gint width = g_value_get_int (value);
144 
145  if (width == -1)
146  expand = TRUE;
147 
148  gtk_widget_set_hexpand (GTK_WIDGET(gas), expand);
149  gtk_widget_set_hexpand (GTK_WIDGET(gas->combo), expand);
150 
151  gtk_entry_set_width_chars (entry, width);
152  gtk_widget_queue_resize (GTK_WIDGET(gas));
153  }
154  break;
155 
156  default:
157  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
158  break;
159  }
160 }
161 
162 static void
163 gas_get_property (GObject *object, guint param_id,
164  GValue *value, GParamSpec *pspec)
165 {
166  GNCAccountSel *gas;
167 
168  g_return_if_fail (object != NULL);
169  g_return_if_fail (GNC_IS_ACCOUNT_SEL(object));
170 
171  gas = GNC_ACCOUNT_SEL(object);
172 
173  switch (param_id)
174  {
175  case PROP_HIDE_PLACEHOLDER:
176  g_value_set_boolean (value, gas->hide_placeholder);
177  break;
178 
179  case PROP_HIDE_HIDDEN:
180  g_value_set_boolean (value, gas->hide_hidden);
181  break;
182 
183  case PROP_HORIZONTAL_EXPAND:
184  g_value_set_boolean (value, gtk_widget_get_hexpand (GTK_WIDGET(gas)));
185  break;
186 
187  case PROP_COMBO_ENTRY_WIDTH:
188  {
189  GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
190  g_value_set_int (value, gtk_entry_get_width_chars (entry));
191  }
192  break;
193 
194  default:
195  G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
196  break;
197  }
198 }
199 
200 static void
201 gnc_account_sel_class_init (GNCAccountSelClass *klass)
202 {
203  GObjectClass *object_class = G_OBJECT_CLASS(klass);
204 
205  object_class->finalize = gnc_account_sel_finalize;
206  object_class->dispose = gnc_account_sel_dispose;
207 
208  object_class->set_property = gas_set_property;
209  object_class->get_property = gas_get_property;
210 
211  g_object_class_install_property (
212  object_class, PROP_HIDE_PLACEHOLDER,
213  g_param_spec_boolean("hide-placeholder", "Hide Placeholder",
214  "Placeholder accounts are hidden", TRUE,
215  G_PARAM_READWRITE));
216 
217  g_object_class_install_property (
218  object_class, PROP_HIDE_HIDDEN,
219  g_param_spec_boolean("hide-hidden", "Hide Hidden",
220  "Hidden accounts are hidden", TRUE,
221  G_PARAM_READWRITE));
222 
223  g_object_class_install_property (
224  object_class, PROP_HIDE_HIDDEN,
225  g_param_spec_boolean("horizontal-expand", "Horizontal Expand",
226  "Should GAS take all horizontal space", TRUE,
227  G_PARAM_READWRITE));
228 
229  g_object_class_install_property (
230  object_class, PROP_COMBO_ENTRY_WIDTH,
231  g_param_spec_int("entry-width", "Number of Charactors",
232  "Set the width of the combo entry",
233  -1, 100, -1, G_PARAM_READWRITE));
234 
235  account_sel_signals [ACCOUNT_SEL_CHANGED] =
236  g_signal_new ("account_sel_changed",
237  G_OBJECT_CLASS_TYPE (object_class),
238  G_SIGNAL_RUN_FIRST,
239  0,
240  NULL,
241  NULL,
242  g_cclosure_marshal_VOID__VOID,
243  G_TYPE_NONE,
244  0);
245 }
246 
247 static void
248 combo_changed_cb (GNCAccountSel *gas, gpointer combo)
249 {
250  GtkTreeModel *fmodel;
251  GtkTreeIter fiter;
252  GtkTreeIter iter;
253  GtkTreePath *path = NULL;
254  GtkTreePath *saved_account_path = NULL;
255  gboolean emit_signal = TRUE;
256 
257  if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX(gas->combo), &fiter))
258  return;
259 
260  fmodel = gtk_combo_box_get_model (GTK_COMBO_BOX(gas->combo));
261  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER(fmodel),
262  &iter, &fiter);
263 
264  path = gtk_tree_model_get_path (GTK_TREE_MODEL(gas->store), &iter);
265 
266  if (gas->saved_account_ref)
267  {
268  saved_account_path = gtk_tree_row_reference_get_path (gas->saved_account_ref);
269  gtk_tree_row_reference_free (gas->saved_account_ref);
270  }
271  gas->saved_account_ref = gtk_tree_row_reference_new (GTK_TREE_MODEL(gas->store), path);
272 
273  if (saved_account_path)
274  {
275  if (gtk_tree_path_compare (path, saved_account_path) == 0)
276  emit_signal = FALSE;
277  }
278  gtk_tree_path_free (saved_account_path);
279  gtk_tree_path_free (path);
280 
281  if (emit_signal)
282  g_signal_emit_by_name (gas, "account_sel_changed");
283 }
284 
285 static char*
286 normalize_and_fold (char* utf8_string)
287 {
288  char *normalized, *folded;
289  g_return_val_if_fail (utf8_string && *utf8_string, NULL);
290 
291  normalized = g_utf8_normalize (utf8_string, -1, G_NORMALIZE_NFC);
292  if (!normalized)
293  return NULL;
294  folded = g_utf8_casefold (normalized, -1);
295  g_free (normalized);
296  return folded;
297 }
298 
299 static gboolean
300 completion_function (GtkEntryCompletion *completion, const char *key,
301  GtkTreeIter *iter, gpointer user_data)
302 {
303  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
304  GtkTreeModel *fmodel = gtk_combo_box_get_model (GTK_COMBO_BOX(gas->combo));
305  gchar *full_name = NULL;
306  gboolean ret = FALSE;
307 
308  gtk_tree_model_get (fmodel, iter, ACCT_COL_NAME, &full_name, -1);
309 
310  if (full_name && *full_name)
311  {
312  gchar *full_name_folded = normalize_and_fold (full_name);
313 
314  // key is normalised and casefolded
315  if (g_strrstr (full_name_folded, key) != NULL)
316  ret = TRUE;
317 
318  g_free (full_name_folded);
319  }
320  g_free (full_name);
321  return ret;
322 }
323 
324 static char*
325 normalize_and_lower (const char* utf8_string)
326 {
327  char *normalized, *lowered;
328  g_return_val_if_fail (utf8_string && *utf8_string, NULL);
329 
330  normalized = g_utf8_normalize (utf8_string, -1, G_NORMALIZE_NFC);
331  if (!normalized)
332  return NULL;
333  lowered = g_utf8_strdown (normalized, -1);
334  g_free (normalized);
335  return lowered;
336 }
337 
338 /* Set gas->sep_key_prefix to the account_full_name or to the longest
339  * common characters in the account_full_name.
340  */
341 static void
342 set_prefix_from_account_name (GNCAccountSel *gas, char* account_full_name,
343  gint item_offset_to_sep_char,
344  gint *sep_key_prefix_len)
345 {
346  if (item_offset_to_sep_char < *sep_key_prefix_len)
347  {
348  *sep_key_prefix_len = item_offset_to_sep_char;
349  memset (gas->sep_key_prefix, 0, BUFLEN);
350  g_utf8_strncpy (gas->sep_key_prefix, account_full_name, *sep_key_prefix_len);
351  }
352 
353  if (item_offset_to_sep_char == *sep_key_prefix_len)
354  {
355  char tmp_prefix[BUFLEN];
356 
357  memset (tmp_prefix, 0, BUFLEN);
358  g_utf8_strncpy (tmp_prefix, account_full_name, *sep_key_prefix_len);
359 
360  if (g_strcmp0 (gas->sep_key_prefix, tmp_prefix) != 0)
361  {
362  do
363  {
364  gchar *tmp = g_strdup (gas->sep_key_prefix);
365  (*sep_key_prefix_len)--;
366 
367  memset (tmp_prefix, 0, BUFLEN);
368  g_utf8_strncpy (tmp_prefix, account_full_name, *sep_key_prefix_len);
369  memset (gas->sep_key_prefix, 0, BUFLEN);
370  g_utf8_strncpy (gas->sep_key_prefix, tmp, *sep_key_prefix_len);
371  g_free (tmp);
372 
373  } while (g_strcmp0 (gas->sep_key_prefix, tmp_prefix) != 0);
374  }
375  }
376 }
377 
378 static inline gboolean
379 find_next_separator (char* account_full_name,
380  gint *item_offset_to_sep_char,
381  gunichar sep_unichar)
382 {
383  const char* c;
384  gunichar uc;
385  gboolean found = FALSE;
386 
387  c = g_utf8_offset_to_pointer (account_full_name, *item_offset_to_sep_char);
388  (*item_offset_to_sep_char)++;
389 
390  while (*c)
391  {
392  uc = g_utf8_get_char (c);
393  if (uc == sep_unichar)
394  {
395  found = TRUE;
396  break;
397  }
398  c = g_utf8_next_char (c);
399  (*item_offset_to_sep_char)++;
400  }
401  return found;
402 }
403 
404 /* Callback for Account separator key */
405 static void
406 entry_insert_text_cb (GtkEntry *entry, const gchar *text, gint length,
407  gint *position, gpointer user_data)
408 {
409  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
410  GtkTreeModel *fmodel = gtk_combo_box_get_model (GTK_COMBO_BOX(gas->combo));
411  const gchar *sep_char = gnc_get_account_separator_string ();
412  gchar *lower_entered_text;
413  glong entered_len;
414  gunichar sep_unichar;
415  gint sep_key_prefix_len = G_MAXINT;
416  GtkTreeIter iter;
417  gboolean valid;
418 
419  if (g_strcmp0 (text, sep_char) != 0)
420  return;
421 
422  memset (gas->sep_key_prefix, 0, BUFLEN);
423 
424  const gchar *entered_text = gtk_entry_get_text (entry);
425 
426  if (!(entered_text && *entered_text))
427  return;
428 
429  lower_entered_text = normalize_and_lower (entered_text);
430  entered_len = g_utf8_strlen (lower_entered_text, -1); //characters
431  sep_unichar = gnc_get_account_separator ();
432 
433  // Get the first item in the list
434  valid = gtk_tree_model_get_iter_first (fmodel, &iter);
435 
436  // Walk through the list, reading each full name
437  while (valid)
438  {
439  gchar *account_full_name;
440 
441  gtk_tree_model_get (fmodel, &iter, ACCT_COL_NAME, &account_full_name, -1);
442 
443  if (account_full_name && *account_full_name)
444  {
445  gchar *lower_account_full_name = normalize_and_lower (account_full_name);
446 
447  if (g_str_has_prefix (lower_account_full_name, lower_entered_text))
448  {
449  gint item_offset_to_sep_char = entered_len;
450  gboolean found = find_next_separator (account_full_name,
451  &item_offset_to_sep_char,
452  sep_unichar);
453 
454  if (found)
455  set_prefix_from_account_name (gas, account_full_name,
456  item_offset_to_sep_char,
457  &sep_key_prefix_len);
458  }
459  g_free (lower_account_full_name);
460  }
461  g_free (account_full_name);
462  valid = gtk_tree_model_iter_next (fmodel, &iter);
463  }
464  if (gas->sep_key_prefix[0] == 0)
465  g_utf8_strncpy (gas->sep_key_prefix, entered_text, entered_len);
466 
467  g_free (lower_entered_text);
468 
469  if (gas->sep_key_prefix[0] != 0)
470  {
471  g_signal_handlers_block_by_func (GTK_EDITABLE(entry), (gpointer) entry_insert_text_cb, user_data);
472  gtk_editable_delete_text (GTK_EDITABLE(entry), 0, -1);
473  gtk_editable_set_position (GTK_EDITABLE(entry), 0);
474  gtk_editable_insert_text (GTK_EDITABLE(entry), gas->sep_key_prefix, -1, position);
475  g_signal_handlers_unblock_by_func (GTK_EDITABLE(entry), (gpointer) entry_insert_text_cb, user_data);
476  g_signal_stop_emission_by_name (GTK_EDITABLE(entry), "insert_text");
477  }
478 }
479 
480 static void
481 update_entry_and_refilter (GNCAccountSel *gas)
482 {
483  GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
484  GtkTreeModel *fmodel = gtk_combo_box_get_model (GTK_COMBO_BOX(gas->combo));
485 
486  gtk_editable_delete_text (GTK_EDITABLE(entry), 0, -1);
487  if (gas->saved_account_ref)
488  gtk_tree_row_reference_free (gas->saved_account_ref);
489  gas->saved_account_ref = NULL;
490  gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), -1);
491  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER(fmodel));
492 }
493 
494 static void
495 toggle_placeholder_cb (GtkWidget *widget, gpointer user_data)
496 {
497  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
498  gas->hide_placeholder = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM(widget));
499  update_entry_and_refilter (gas);
500 }
501 
502 static void
503 toggle_hidden_cb (GtkWidget *widget, gpointer user_data)
504 {
505  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
506  gas->hide_hidden = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM(widget));
507  update_entry_and_refilter (gas);
508 }
509 
510 static void
511 icon_release_cb (GtkEntry *entry, GtkEntryIconPosition icon_pos,
512  GdkEvent* event, gpointer user_data)
513 {
514  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
515  GtkWidget *menu, *h_placeholder, *h_hidden;
516 
517  if (icon_pos != GTK_ENTRY_ICON_SECONDARY)
518  return;
519 
520  menu = gtk_menu_new ();
521  h_placeholder = gtk_check_menu_item_new_with_mnemonic (_("Hide _Placeholder Accounts"));
522  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM(h_placeholder), gas->hide_placeholder);
523  h_hidden = gtk_check_menu_item_new_with_mnemonic (_("Hide _Hidden Accounts"));
524  gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM(h_hidden), gas->hide_hidden);
525  gtk_menu_attach_to_widget (GTK_MENU(menu), GTK_WIDGET(gas), NULL);
526  gtk_menu_shell_append (GTK_MENU_SHELL(menu), h_placeholder);
527  gtk_menu_shell_append (GTK_MENU_SHELL(menu), h_hidden);
528  gtk_widget_show_all (menu);
529 
530  g_signal_connect (G_OBJECT(h_placeholder), "toggled",
531  G_CALLBACK(toggle_placeholder_cb), gas);
532  g_signal_connect (G_OBJECT(h_hidden), "toggled",
533  G_CALLBACK(toggle_hidden_cb), gas);
534 
535  gtk_menu_popup_at_pointer (GTK_MENU(menu), (GdkEvent *)event);
536 }
537 
538 /* An account is included if gas->acctTypeFilters or gas->acctCommodityFilters
539  * is populated and the account is in the list (both lists if both are populated)
540  * and not in gas->acctExcludeList
541  *
542  * If no list is populated then all accounts are included.
543  */
544 static gboolean
545 account_is_included (GNCAccountSel *gas, Account *acc)
546 {
547  if (gas->acctExcludeList && g_list_find (gas->acctExcludeList, acc))
548  return false;
549 
550  /* Filter as we've been configured to do. */
551  if (gas->acctTypeFilters && !g_list_find (gas->acctTypeFilters, GINT_TO_POINTER (xaccAccountGetType (acc))))
552  return false;
553 
554  if (gas->acctCommodityFilters && !g_list_find (gas->acctCommodityFilters, xaccAccountGetCommodity (acc)))
555  return false;
556 
557  return true;
558 }
559 
560 static gboolean
561 account_is_visible_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
562 {
563  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
564  Account *acc;
565 
566  gtk_tree_model_get (GTK_TREE_MODEL(gas->store), iter, ACCT_COL_PTR, &acc, -1);
567 
568  if (!acc)
569  return true;
570 
571  if (!account_is_included (gas, acc))
572  return false;
573 
574  if (gas->hide_placeholder && xaccAccountGetPlaceholder (acc))
575  return false;
576 
577  if (gas->hide_placeholder && xaccAccountIsHidden (acc))
578  return false;
579 
580  return true;
581 }
582 
583 static void
584 row_has_been_deleted_in_store_cb (GtkTreeModel *model, GtkTreePath *path, gpointer user_data)
585 {
586  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
587  GtkTreePath *saved_account_path;
588 
589  if (!gas->saved_account_ref)
590  return;
591 
592  saved_account_path = gtk_tree_row_reference_get_path (gas->saved_account_ref);
593 
594  if (saved_account_path == NULL) // path is already invalid after row delete
595  {
596  GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
597 
598  g_signal_handlers_block_by_func (gas->combo, combo_changed_cb , gas);
599  gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), -1);
600  gtk_editable_delete_text (GTK_EDITABLE(entry), 0, -1);
601  gtk_tree_row_reference_free (gas->saved_account_ref);
602  gas->saved_account_ref = NULL;
603  g_signal_emit_by_name (gas, "account_sel_changed");
604  g_signal_handlers_unblock_by_func (gas->combo, combo_changed_cb , gas);
605  }
606  gtk_tree_path_free (saved_account_path);
607 }
608 
609 static void
610 row_has_been_changed_in_store_cb (GtkTreeModel *model, GtkTreePath *path,
611  GtkTreeIter *iter, gpointer user_data)
612 {
613  GNCAccountSel *gas = GNC_ACCOUNT_SEL(user_data);
614  GtkTreePath *saved_account_path;
615 
616  if (!gas->saved_account_ref)
617  return;
618 
619  saved_account_path = gtk_tree_row_reference_get_path (gas->saved_account_ref);
620 
621  if (gtk_tree_path_compare (path, saved_account_path) == 0)
622  {
623  GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
624  gchar *account_full_name = NULL;
625  gint position = 0;
626 
627  g_signal_handlers_block_by_func (gas->combo, combo_changed_cb , gas);
628 
629  gtk_tree_model_get (model, iter, ACCT_COL_NAME, &account_full_name, -1);
630 
631  gtk_editable_delete_text (GTK_EDITABLE(entry), 0, -1);
632  gtk_editable_insert_text (GTK_EDITABLE(entry), account_full_name, -1, &position);
633  gtk_editable_set_position (GTK_EDITABLE(entry), -1);
634  g_free (account_full_name);
635 
636  g_signal_handlers_unblock_by_func (gas->combo, combo_changed_cb , gas);
637 
638  // see if account visibility has changed
639  if (!account_is_visible_func (model, iter, gas))
640  update_entry_and_refilter (gas);
641  }
642  gtk_tree_path_free (saved_account_path);
643 }
644 
645 
646 static void
647 gnc_account_sel_init (GNCAccountSel *gas)
648 {
649  GtkWidget *widget;
650  GtkWidget *entry;
651  GtkEntryCompletion *completion;
652  Account *root = gnc_get_current_root_account ();
653  GtkTreeModel *filter_model;
654 
655  gtk_orientable_set_orientation (GTK_ORIENTABLE(gas), GTK_ORIENTATION_HORIZONTAL);
656 
657  gas->default_new_commodity = NULL;
658  gas->acctTypeFilters = NULL;
659  gas->acctCommodityFilters = NULL;
660  gas->acctExcludeList = NULL;
661  gas->newAccountButton = NULL;
662  gas->hide_placeholder = TRUE;
663  gas->hide_hidden = TRUE;
664  gas->saved_account_ref = NULL;
665  gas->row_changed_id = 0;
666  gas->row_deleted_id = 0;
667 
668  g_object_set (gas, "spacing", 2, (gchar*)NULL);
669 
670  // Set the name for this widget so it can be easily manipulated with css
671  gtk_widget_set_name (GTK_WIDGET(gas), "gnc-id-account-select");
672 
673  // We are just using the quickfill list store which will be the same for all
674  gas->store = gnc_get_shared_account_name_list_store (root, QKEY, NULL, NULL);
675 
676  // set sort order
677  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE(gas->store),
678  ACCT_COL_NAME, GTK_SORT_ASCENDING);
679 
680  // the filter will be unique for each GAS.
681  filter_model = gtk_tree_model_filter_new (GTK_TREE_MODEL(gas->store), NULL);
682  gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER(filter_model),
683  account_is_visible_func, gas, NULL);
684 
685  widget = gtk_combo_box_new_with_model_and_entry (GTK_TREE_MODEL(filter_model));
686  g_object_unref (G_OBJECT(filter_model));
687  gas->combo = GTK_COMBO_BOX(widget);
688  gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX(widget), ACCT_COL_NAME);
689 
690  gtk_container_add (GTK_CONTAINER(gas), widget);
691 
692  // set the default horizontal expansion to TRUE
693  gtk_widget_set_hexpand (GTK_WIDGET(gas), TRUE);
694  gtk_widget_set_hexpand (GTK_WIDGET(gas->combo), TRUE);
695 
696  entry = gtk_bin_get_child (GTK_BIN(gas->combo));
697  gtk_entry_set_icon_from_icon_name (GTK_ENTRY(entry), GTK_ENTRY_ICON_SECONDARY,
698  "preferences-system-symbolic");
699  gtk_entry_set_icon_tooltip_text (GTK_ENTRY(entry), GTK_ENTRY_ICON_SECONDARY,
700  _("Set the visibility of placeholder and hidden accounts."));
701  g_signal_connect (G_OBJECT(entry), "icon-release",
702  G_CALLBACK(icon_release_cb), gas);
703  g_signal_connect (G_OBJECT(entry), "insert_text",
704  G_CALLBACK(entry_insert_text_cb), gas);
705 
706  /* Add completion. */
707  gnc_cbwe_require_list_item (GTK_COMBO_BOX(widget));
708  completion = gtk_entry_get_completion (GTK_ENTRY(entry));
709  gtk_entry_completion_set_match_func (completion,
710  (GtkEntryCompletionMatchFunc)completion_function,
711  gas, NULL);
712 
713  // Set default entry to none and blank entry
714  gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), -1);
715  gtk_editable_delete_text (GTK_EDITABLE(entry), 0, -1);
716 
717  gas->row_deleted_id = g_signal_connect (G_OBJECT(gas->store), "row-deleted",
718  G_CALLBACK(row_has_been_deleted_in_store_cb), gas);
719 
720  gas->row_changed_id = g_signal_connect (G_OBJECT(gas->store), "row-changed",
721  G_CALLBACK(row_has_been_changed_in_store_cb), gas);
722 
723  g_signal_connect_swapped (gas->combo, "changed",
724  G_CALLBACK(combo_changed_cb), gas);
725 }
726 
727 GtkWidget *
728 gnc_account_sel_new (void)
729 {
730  GNCAccountSel *gas = g_object_new (GNC_TYPE_ACCOUNT_SEL, NULL);
731 
732  return GTK_WIDGET(gas);
733 }
734 
735 typedef struct
736 {
737  GNCAccountSel *gas;
738  Account *acct;
739 } gas_find_data;
740 
741 static gboolean
742 gnc_account_sel_find_account (GtkTreeModel *fmodel,
743  GtkTreePath *path,
744  GtkTreeIter *iter,
745  gas_find_data *data)
746 {
747  Account *model_acc;
748 
749  gtk_tree_model_get (fmodel, iter, ACCT_COL_PTR, &model_acc, -1);
750  if (data->acct != model_acc)
751  return FALSE;
752 
753  gtk_combo_box_set_active_iter (GTK_COMBO_BOX(data->gas->combo), iter);
754  return TRUE;
755 }
756 
757 /* If the account is included in the filters, set hide_placeholder
758  * and hide_hidden accordingly to show it.
759  */
760 static void
761 check_account_can_be_seen (GNCAccountSel *gas, GtkTreeModel *fmodel, Account *acct)
762 {
763  gboolean changed = FALSE;
764  gboolean included = account_is_included (gas, acct);
765 
766  if (included)
767  {
768  gboolean test = xaccAccountGetPlaceholder (acct);
769 
770  if (test && gas->hide_placeholder == test)
771  {
772  gas->hide_placeholder = !test;
773  changed = TRUE;
774  }
775 
776  test = xaccAccountIsHidden (acct);
777  if (test && gas->hide_hidden == test)
778  {
779  gas->hide_hidden = !test;
780  changed = TRUE;
781  }
782  if (changed)
783  gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER(fmodel));
784  }
785 }
786 
787 void
788 gnc_account_sel_set_account (GNCAccountSel *gas, Account *acct,
789  gboolean set_default_acct)
790 {
791  GtkTreeModel *fmodel;
792  gas_find_data data;
793 
794  g_return_if_fail (gas != NULL);
795  g_return_if_fail (GNC_IS_ACCOUNT_SEL(gas));
796 
797  fmodel = gtk_combo_box_get_model (GTK_COMBO_BOX(gas->combo));
798 
799  if (acct)
800  check_account_can_be_seen (gas, fmodel, acct);
801 
802  if (set_default_acct)
803  {
804  gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), 0);
805  if (!acct)
806  return;
807  }
808  else
809  {
810  gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), -1);
811  if (!acct)
812  {
813  GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
814  gtk_editable_delete_text (GTK_EDITABLE(entry), 0, -1);
815  return;
816  }
817  }
818  data.gas = gas;
819  data.acct = acct;
820  gtk_tree_model_foreach (GTK_TREE_MODEL(fmodel),
821  (GtkTreeModelForeachFunc)gnc_account_sel_find_account,
822  &data);
823 }
824 
825 Account*
826 gnc_account_sel_get_account (GNCAccountSel *gas)
827 {
828  GtkTreeModel *fmodel;
829  GtkTreeIter fiter;
830  GtkTreeIter iter;
831  Account *acc;
832 
833  g_return_val_if_fail (gas != NULL, NULL);
834  g_return_val_if_fail (GNC_IS_ACCOUNT_SEL(gas), NULL);
835 
836  if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX(gas->combo), &fiter))
837  return NULL;
838 
839  fmodel = gtk_combo_box_get_model (GTK_COMBO_BOX(gas->combo));
840 
841  gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER(fmodel),
842  &iter, &fiter);
843 
844  gtk_tree_model_get (GTK_TREE_MODEL(gas->store), &iter,
845  ACCT_COL_PTR, &acc, -1);
846  return acc;
847 }
848 
849 void
850 gnc_account_sel_set_acct_filters (GNCAccountSel *gas, GList *typeFilters,
851  GList *commodityFilters)
852 {
853  g_return_if_fail (gas != NULL);
854  g_return_if_fail (GNC_IS_ACCOUNT_SEL(gas));
855 
856  if (gas->acctTypeFilters != NULL)
857  {
858  g_list_free (gas->acctTypeFilters);
859  gas->acctTypeFilters = NULL;
860  }
861 
862  if (gas->acctCommodityFilters != NULL)
863  {
864  g_list_free (gas->acctCommodityFilters);
865  gas->acctCommodityFilters = NULL;
866  }
867 
868  /* This works because the GNCAccountTypes in the list are
869  * ints-casted-as-pointers. */
870  if (typeFilters)
871  gas->acctTypeFilters = g_list_copy (typeFilters);
872 
873  /* Save the commodity filter list */
874  if (commodityFilters)
875  gas->acctCommodityFilters = g_list_copy (commodityFilters);
876 
877  update_entry_and_refilter (gas);
878 }
879 
880 
881 void
882 gnc_account_sel_set_acct_exclude_filter (GNCAccountSel *gas,
883  GList *excludeFilter)
884 {
885  g_return_if_fail (gas != NULL);
886  g_return_if_fail (GNC_IS_ACCOUNT_SEL(gas));
887 
888  if (gas->acctExcludeList != NULL)
889  {
890  g_list_free (gas->acctExcludeList);
891  gas->acctExcludeList = NULL;
892  }
893 
894  if (excludeFilter)
895  gas->acctExcludeList = g_list_copy (excludeFilter);
896 
897  update_entry_and_refilter (gas);
898 }
899 
900 void
901 gnc_account_sel_set_default_new_commodity (GNCAccountSel *gas, gnc_commodity *new_commodity)
902 {
903  g_return_if_fail (gas);
904  g_return_if_fail (GNC_IS_COMMODITY (new_commodity));
905  gas->default_new_commodity = new_commodity;
906 }
907 
908 static void
909 gnc_account_sel_finalize (GObject *object)
910 {
911  GNCAccountSel *gas;
912 
913  g_return_if_fail (object != NULL);
914  g_return_if_fail (GNC_IS_ACCOUNT_SEL(object));
915 
916  gas = GNC_ACCOUNT_SEL(object);
917 
918  if (gas->acctTypeFilters)
919  g_list_free (gas->acctTypeFilters);
920 
921  if (gas->acctCommodityFilters)
922  g_list_free (gas->acctCommodityFilters);
923 
924  if (gas->acctExcludeList)
925  g_list_free (gas->acctExcludeList);
926 
927  G_OBJECT_CLASS (gnc_account_sel_parent_class)->finalize (object);
928 }
929 
930 static void
931 gnc_account_sel_dispose (GObject *object)
932 {
933  GNCAccountSel *gas;
934 
935  g_return_if_fail (object != NULL);
936  g_return_if_fail (GNC_IS_ACCOUNT_SEL(object));
937 
938  gas = GNC_ACCOUNT_SEL(object);
939 
940  if (gas->row_changed_id > 0)
941  g_signal_handler_disconnect (G_OBJECT(gas->store), gas->row_changed_id);
942  gas->row_changed_id = 0;
943 
944  if (gas->row_deleted_id > 0)
945  g_signal_handler_disconnect (G_OBJECT(gas->store), gas->row_deleted_id);
946  gas->row_deleted_id = 0;
947 
948  if (gas->saved_account_ref)
949  gtk_tree_row_reference_free (gas->saved_account_ref);
950  gas->saved_account_ref = NULL;
951 
952  G_OBJECT_CLASS (gnc_account_sel_parent_class)->dispose (object);
953 }
954 
955 void
956 gnc_account_sel_set_new_account_ability (GNCAccountSel *gas,
957  gboolean state)
958 {
959  g_return_if_fail (gas != NULL);
960  g_return_if_fail (GNC_IS_ACCOUNT_SEL(gas));
961 
962  if (state == (gas->newAccountButton != NULL))
963  {
964  /* We're already in that state; don't do anything. */
965  return;
966  }
967 
968  if (gas->newAccountButton)
969  {
970  g_assert (state == TRUE);
971  /* destroy the existing button. */
972  gtk_container_remove (GTK_CONTAINER(gas), gas->newAccountButton);
973  gtk_widget_destroy (gas->newAccountButton);
974  gas->newAccountButton = NULL;
975  return;
976  }
977 
978  /* Translators: This is a button label displayed in the account selector
979  * control used in several dialogs. When pressed it opens the New Account
980  * dialog.
981  */
982  gas->newAccountButton = gtk_button_new_with_label (_("New…"));
983  g_signal_connect (gas->newAccountButton,
984  "clicked",
985  G_CALLBACK(gas_new_account_click),
986  gas);
987 
988  gtk_box_pack_start (GTK_BOX(gas), gas->newAccountButton,
989  FALSE, FALSE, 0);
990 }
991 
992 void
993 gnc_account_sel_set_new_account_modal (GNCAccountSel *gas,
994  gboolean state)
995 {
996  g_return_if_fail (gas != NULL);
997  g_return_if_fail (GNC_IS_ACCOUNT_SEL(gas));
998 
999  gas->isModal = state;
1000 }
1001 
1002 static void
1003 gas_new_account_click (GtkButton *b, gpointer user_data)
1004 {
1005  GNCAccountSel *gas = (GNCAccountSel*)user_data;
1006  GtkWindow *parent = GTK_WINDOW(gtk_widget_get_toplevel (GTK_WIDGET(gas)));
1007 
1008  if (gas->isModal)
1009  {
1010  Account *account = gnc_ui_new_accounts_from_name_with_defaults (parent, NULL, gas->acctTypeFilters,
1011  gas->default_new_commodity, NULL);
1012  if (account)
1013  gnc_account_sel_set_account (gas, account, FALSE);
1014  }
1015  else
1016  gnc_ui_new_account_with_types_and_commodity (parent, gnc_get_current_book(),
1017  gas->acctTypeFilters, gas->default_new_commodity);
1018 }
1019 
1020 gint
1021 gnc_account_sel_get_visible_account_num (GNCAccountSel *gas)
1022 {
1023  GtkTreeModel *fmodel;
1024 
1025  g_return_val_if_fail (gas != NULL, 0);
1026  g_return_val_if_fail (GNC_IS_ACCOUNT_SEL(gas), 0);
1027 
1028  fmodel = gtk_combo_box_get_model (GTK_COMBO_BOX(gas->combo));
1029 
1030  return gtk_tree_model_iter_n_children (fmodel, NULL);
1031 }
This file contains the functions to present a gui to the user for creating a new account or editing a...
utility functions for the GnuCash UI
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account&#39;s account type.
Definition: Account.cpp:3237
gtk helper routines.
STRUCTS.
void gnc_ui_new_account_with_types_and_commodity(GtkWindow *parent, QofBook *book, GList *valid_types, gnc_commodity *default_commodity)
Display a window for creating a new account.
Create an account-name quick-fill.
gboolean xaccAccountIsHidden(const Account *acc)
Should this account be "hidden".
Definition: Account.cpp:4157
Account * gnc_ui_new_accounts_from_name_with_defaults(GtkWindow *parent, const char *name, GList *valid_types, const gnc_commodity *default_commodity, Account *parent_acct)
Display a modal window for creating a new account.
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account&#39;s commodity.
Definition: Account.cpp:3371
gboolean xaccAccountGetPlaceholder(const Account *acc)
Get the "placeholder" flag for an account.
Definition: Account.cpp:4074
Commodity handling public routines.
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition: Account.cpp:205