GnuCash c935c2f+
Loading...
Searching...
No Matches
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 */
45enum
46{
47 ACCOUNT_SEL_CHANGED,
48 LAST_SIGNAL
49};
50
51enum 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
83enum
84{
85 PROP_0,
86 PROP_HIDE_PLACEHOLDER,
87 PROP_HIDE_HIDDEN,
88 PROP_HORIZONTAL_EXPAND,
89 PROP_COMBO_ENTRY_WIDTH,
90};
91
92static guint account_sel_signals [LAST_SIGNAL] = { 0 };
93
94static void gnc_account_sel_finalize (GObject *object);
95static void gnc_account_sel_dispose (GObject *object);
96
97static void gas_set_property (GObject *object,
98 guint param_id,
99 const GValue *value,
100 GParamSpec *pspec);
101
102static void gas_get_property (GObject *object,
103 guint param_id,
104 GValue *value,
105 GParamSpec *pspec);
106
107static void gas_new_account_click (GtkButton *b, gpointer ud);
108
109#define GNC_ACCOUNT_SEL_PATH "gnc-account-sel-path"
110
111G_DEFINE_TYPE (GNCAccountSel, gnc_account_sel, GTK_TYPE_BOX)
112
113static void
114gas_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
162static void
163gas_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
200static void
201gnc_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
247static void
248combo_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
285static char*
286normalize_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
299static gboolean
300completion_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
324static char*
325normalize_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 */
341static void
342set_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
378static inline gboolean
379find_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 */
405static void
406entry_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
480static void
481update_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
494static void
495toggle_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
502static void
503toggle_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
510static void
511icon_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 */
544static gboolean
545account_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
560static gboolean
561account_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
583static void
584row_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
609static void
610row_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
646static void
647gnc_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
727GtkWidget *
728gnc_account_sel_new (void)
729{
730 GNCAccountSel *gas = g_object_new (GNC_TYPE_ACCOUNT_SEL, NULL);
731
732 return GTK_WIDGET(gas);
733}
734
735typedef struct
736{
737 GNCAccountSel *gas;
738 Account *acct;
740
741static gboolean
742gnc_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 */
760static void
761check_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
787void
788gnc_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
825Account*
826gnc_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
849void
850gnc_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
881void
882gnc_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
900void
901gnc_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
908static void
909gnc_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
930static void
931gnc_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
955void
956gnc_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
992void
993gnc_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
1002static void
1003gas_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
1020gint
1021gnc_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}
Create an account-name quick-fill.
This file contains the functions to present a gui to the user for creating a new account or editing a...
Commodity handling public routines.
gtk helper routines.
utility functions for the GnuCash UI
GNCAccountType xaccAccountGetType(const Account *acc)
Returns the account's account type.
Definition Account.cpp:3267
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition Account.cpp:205
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
gboolean xaccAccountGetPlaceholder(const Account *acc)
Get the "placeholder" flag for an account.
Definition Account.cpp:4119
gboolean xaccAccountIsHidden(const Account *acc)
Should this account be "hidden".
Definition Account.cpp:4202
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.
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.
STRUCTS.