1 /**
2  * gnc-account-sel.c -- combobox style account selection widget
3  *
4  * Copyright (C) 2002 Joshua Sled <jsled@asynchronous.org>
5  * All rights reserved.
6  * Copyright (C) 2006 David Hampton <hampton@employees.org>
7  *
8  * Gnucash is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Library General Public License
10  * as published by the Free Software Foundation; either version 2 of the
11  * License, or (at your option) any later version.
12  *
13  * Gnucash is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Library General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, contact:
20  *
21  * Free Software Foundation           Voice:  +1-617-542-5942
22  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652
23  * Boston, MA  02110-1301,  USA       gnu@gnu.org
24  **/
25 
26 #include <config.h>
27 
28 #include <gtk/gtk.h>
29 #include <glib/gi18n.h>
30 
31 #include "dialog-account.h"
32 #include "gnc-account-sel.h"
33 #include "gnc-commodity.h"
34 #include "gnc-gtk-utils.h"
35 #include "gnc-ui-util.h"
36 #include "qof.h"
37 #include "gnc-session.h"
38 #include "dialog-utils.h"
39 
40 #define ACCT_DATA_TAG "gnc-account-sel_acct"
41 
42 /* Signal codes */
43 enum
44 {
45     ACCOUNT_SEL_CHANGED,
46     LAST_SIGNAL
47 };
48 
49 enum account_cols
50 {
51     ACCT_COL_NAME = 0,
52     ACCT_COL_PTR,
53     NUM_ACCT_COLS
54 };
55 
56 static guint account_sel_signals [LAST_SIGNAL] = { 0 };
57 
58 static void gnc_account_sel_init (GNCAccountSel *gas);
59 static void gnc_account_sel_class_init (GNCAccountSelClass *klass);
60 static void gnc_account_sel_finalize (GObject *object);
61 static void gnc_account_sel_dispose (GObject *object);
62 
63 static void gas_filter_accounts (gpointer data, gpointer user_data);
64 
65 static void gas_populate_list (GNCAccountSel *gas);
66 
67 static void gas_new_account_click (GtkButton *b, gpointer ud);
68 
69 static GtkBox *parent_class;
70 
71 GType
gnc_account_sel_get_type(void)72 gnc_account_sel_get_type (void)
73 {
74     static GType account_sel_type = 0;
75 
76     if (account_sel_type == 0)
77     {
78         GTypeInfo account_sel_info =
79         {
80             sizeof (GNCAccountSelClass),
81             NULL,
82             NULL,
83             (GClassInitFunc) gnc_account_sel_class_init,
84             NULL,
85             NULL,
86             sizeof (GNCAccountSel),
87             0,
88             (GInstanceInitFunc) gnc_account_sel_init
89         };
90 
91         account_sel_type = g_type_register_static (GTK_TYPE_BOX,
92                                                    "GNCAccountSel",
93                                                    &account_sel_info, 0);
94     }
95     return account_sel_type;
96 }
97 
98 static void
gnc_account_sel_event_cb(QofInstance * entity,QofEventId event_type,gpointer user_data,gpointer event_data)99 gnc_account_sel_event_cb (QofInstance *entity,
100                           QofEventId event_type,
101                           gpointer user_data,
102                           gpointer event_data)
103 {
104     if (!(event_type == QOF_EVENT_CREATE
105        || event_type == QOF_EVENT_MODIFY
106        || event_type == QOF_EVENT_DESTROY)
107        || !GNC_IS_ACCOUNT(entity))
108     {
109         return;
110     }
111     gas_populate_list ((GNCAccountSel*)user_data);
112 }
113 
114 static void
gnc_account_sel_class_init(GNCAccountSelClass * klass)115 gnc_account_sel_class_init (GNCAccountSelClass *klass)
116 {
117     GObjectClass *object_class = G_OBJECT_CLASS(klass);
118 
119     parent_class = g_type_class_peek_parent (klass);
120 
121     object_class->finalize = gnc_account_sel_finalize;
122     object_class->dispose = gnc_account_sel_dispose;
123 
124     account_sel_signals [ACCOUNT_SEL_CHANGED] =
125         g_signal_new ("account_sel_changed",
126                       G_OBJECT_CLASS_TYPE (object_class),
127                       G_SIGNAL_RUN_FIRST,
128                       G_STRUCT_OFFSET (GNCAccountSelClass, account_sel_changed),
129                       NULL,
130                       NULL,
131                       g_cclosure_marshal_VOID__VOID,
132                       G_TYPE_NONE,
133                       0);
134 }
135 
136 static void
combo_changed_cb(GNCAccountSel * gas,gpointer combo)137 combo_changed_cb (GNCAccountSel *gas, gpointer combo)
138 {
139     gint selected = gtk_combo_box_get_active (GTK_COMBO_BOX(combo));
140     if (selected == gas->currentSelection)
141         return;
142 
143     gas->currentSelection = selected;
144     g_signal_emit_by_name (gas, "account_sel_changed");
145 }
146 
147 static void
gnc_account_sel_init(GNCAccountSel * gas)148 gnc_account_sel_init (GNCAccountSel *gas)
149 {
150     GtkWidget *widget;
151 
152     gtk_orientable_set_orientation (GTK_ORIENTABLE(gas), GTK_ORIENTATION_HORIZONTAL);
153 
154     gas->initDone = FALSE;
155     gas->acctTypeFilters = FALSE;
156     gas->newAccountButton = NULL;
157     gas->currentSelection = -1;
158 
159     g_object_set (gas, "spacing", 2, (gchar*)NULL);
160 
161     // Set the name for this widget so it can be easily manipulated with css
162     gtk_widget_set_name (GTK_WIDGET(gas), "gnc-id-account-select");
163 
164     gas->store = gtk_list_store_new (NUM_ACCT_COLS, G_TYPE_STRING, G_TYPE_POINTER);
165     widget = gtk_combo_box_new_with_model_and_entry (GTK_TREE_MODEL(gas->store));
166     gas->combo = GTK_COMBO_BOX(widget);
167     gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX(widget), ACCT_COL_NAME);
168     g_signal_connect_swapped (gas->combo, "changed",
169                               G_CALLBACK(combo_changed_cb), gas);
170     gtk_container_add (GTK_CONTAINER(gas), widget);
171 
172     /* Add completion. */
173     gnc_cbwe_require_list_item (GTK_COMBO_BOX(widget));
174 
175     /* Get the accounts, place into combo list */
176     gas_populate_list (gas);
177 
178     gas->eventHandlerId =
179         qof_event_register_handler (gnc_account_sel_event_cb, gas);
180 
181     gas->initDone = TRUE;
182 }
183 
184 void
gnc_account_sel_set_hexpand(GNCAccountSel * gas,gboolean expand)185 gnc_account_sel_set_hexpand (GNCAccountSel *gas, gboolean expand)
186 {
187     gtk_widget_set_hexpand (GTK_WIDGET(gas), expand);
188     gtk_widget_set_hexpand (GTK_WIDGET(gas->combo), expand);
189 }
190 
191 typedef struct
192 {
193     GNCAccountSel *gas;
194     GList **outList;
195 } account_filter_data;
196 
197 static void
gas_populate_list(GNCAccountSel * gas)198 gas_populate_list (GNCAccountSel *gas)
199 {
200     account_filter_data atnd;
201     Account *root;
202     Account *acc;
203     GtkTreeIter iter;
204     GtkEntry *entry;
205     gint i, active = -1;
206     GList *accts, *ptr, *filteredAccts;
207     gchar *currentSel, *name;
208 
209     entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
210     currentSel = gtk_editable_get_chars (GTK_EDITABLE(entry), 0, -1 );
211 
212     g_signal_handlers_block_by_func (gas->combo, combo_changed_cb , gas);
213 
214     root = gnc_book_get_root_account (gnc_get_current_book ());
215     accts = gnc_account_get_descendants_sorted (root);
216 
217     filteredAccts   = NULL;
218     atnd.gas        = gas;
219     atnd.outList    = &filteredAccts;
220 
221     g_list_foreach (accts, gas_filter_accounts, (gpointer)&atnd);
222     g_list_free (accts);
223 
224     gtk_list_store_clear (gas->store);
225     for (ptr = filteredAccts, i = 0; ptr; ptr = g_list_next(ptr), i++)
226     {
227         acc = ptr->data;
228         name = gnc_account_get_full_name (acc);
229         gtk_list_store_append (gas->store, &iter);
230         gtk_list_store_set (gas->store, &iter,
231                             ACCT_COL_NAME, name,
232                             ACCT_COL_PTR,  acc,
233                             -1);
234         if (g_utf8_collate (name, currentSel) == 0)
235             active = i;
236         g_free (name);
237     }
238 
239     /* If the account which was in the text box before still exists, then
240      * reset to it. */
241     if (active != -1)
242         gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), active);
243 
244     g_signal_handlers_unblock_by_func (gas->combo, combo_changed_cb , gas);
245 
246     g_list_free (filteredAccts);
247     if (currentSel)
248         g_free (currentSel);
249 }
250 
251 static void
gas_filter_accounts(gpointer data,gpointer user_data)252 gas_filter_accounts (gpointer data, gpointer user_data)
253 {
254     account_filter_data *atnd;
255     Account *a;
256 
257     atnd = (account_filter_data*)user_data;
258     a = (Account*)data;
259     /* Filter as we've been configured to do. */
260     if (atnd->gas->acctTypeFilters)
261     {
262         /* g_list_find is the poor-mans '(member ...)', especially
263          * easy when the data pointers in the list are just casted
264          * account type identifiers. */
265         if (g_list_find (atnd->gas->acctTypeFilters,
266                           GINT_TO_POINTER(xaccAccountGetType (a)))
267                 == NULL)
268         {
269             return;
270         }
271     }
272 
273     if (atnd->gas->acctCommodityFilters)
274     {
275         if (g_list_find_custom (atnd->gas->acctCommodityFilters,
276                                 GINT_TO_POINTER(xaccAccountGetCommodity (a)),
277                                 gnc_commodity_compare_void)
278                 == NULL)
279         {
280             return;
281         }
282     }
283     *atnd->outList = g_list_append (*atnd->outList, a);
284 }
285 
286 GtkWidget *
gnc_account_sel_new(void)287 gnc_account_sel_new (void)
288 {
289     GNCAccountSel *gas = g_object_new (GNC_TYPE_ACCOUNT_SEL, NULL);
290 
291     return GTK_WIDGET(gas);
292 }
293 
294 typedef struct
295 {
296     GNCAccountSel *gas;
297     Account       *acct;
298 } gas_find_data;
299 
300 static gboolean
gnc_account_sel_find_account(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gas_find_data * data)301 gnc_account_sel_find_account (GtkTreeModel *model,
302                               GtkTreePath *path,
303                               GtkTreeIter *iter,
304                               gas_find_data *data)
305 {
306     Account *model_acc;
307 
308     gtk_tree_model_get (model, iter, ACCT_COL_PTR, &model_acc, -1);
309     if (data->acct != model_acc)
310         return FALSE;
311 
312     gtk_combo_box_set_active_iter (GTK_COMBO_BOX(data->gas->combo), iter);
313     return TRUE;
314 }
315 
316 void
gnc_account_sel_set_account(GNCAccountSel * gas,Account * acct,gboolean set_default_acct)317 gnc_account_sel_set_account (GNCAccountSel *gas, Account *acct,
318                              gboolean set_default_acct)
319 {
320     gas_find_data data;
321 
322     if (set_default_acct)
323     {
324         gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), 0);
325         if (!acct)
326             return;
327     }
328     else
329     {
330         gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), -1 );
331         if (!acct)
332         {
333             GtkEntry *entry = GTK_ENTRY(gtk_bin_get_child (GTK_BIN(gas->combo)));
334             gtk_editable_delete_text (GTK_EDITABLE(entry), 0, -1);
335             return;
336         }
337     }
338     data.gas = gas;
339     data.acct = acct;
340     gtk_tree_model_foreach (GTK_TREE_MODEL(gas->store),
341                             (GtkTreeModelForeachFunc)gnc_account_sel_find_account,
342                             &data);
343 }
344 
345 Account*
gnc_account_sel_get_account(GNCAccountSel * gas)346 gnc_account_sel_get_account (GNCAccountSel *gas)
347 {
348     GtkTreeIter iter;
349     Account *acc;
350 
351     if (!gtk_combo_box_get_active_iter (GTK_COMBO_BOX(gas->combo), &iter))
352         return NULL;
353 
354     gtk_tree_model_get (GTK_TREE_MODEL(gas->store), &iter,
355                         ACCT_COL_PTR, &acc,
356                         -1);
357     return acc;
358 }
359 
360 void
gnc_account_sel_set_acct_filters(GNCAccountSel * gas,GList * typeFilters,GList * commodityFilters)361 gnc_account_sel_set_acct_filters (GNCAccountSel *gas, GList *typeFilters,
362                                   GList *commodityFilters)
363 {
364 
365     if (gas->acctTypeFilters != NULL)
366     {
367         g_list_free (gas->acctTypeFilters);
368         gas->acctTypeFilters = NULL;
369     }
370 
371     if (gas->acctCommodityFilters != NULL)
372     {
373         g_list_free (gas->acctCommodityFilters);
374         gas->acctCommodityFilters = NULL;
375     }
376 
377     /* If both filters are null, then no filters exist. */
378     if ((!typeFilters) && (!commodityFilters))
379         return;
380 
381     /* This works because the GNCAccountTypes in the list are
382      * ints-casted-as-pointers. */
383     if (typeFilters)
384         gas->acctTypeFilters = g_list_copy (typeFilters);
385 
386     /* Save the commodity filter list */
387     if (commodityFilters)
388         gas->acctCommodityFilters = g_list_copy (commodityFilters);
389 
390     gas_populate_list (gas);
391 }
392 
393 static void
gnc_account_sel_finalize(GObject * object)394 gnc_account_sel_finalize (GObject *object)
395 {
396     GNCAccountSel *gas;
397 
398     g_return_if_fail (object != NULL);
399     g_return_if_fail (GNC_IS_ACCOUNT_SEL(object));
400 
401     gas = GNC_ACCOUNT_SEL(object);
402 
403     if (gas->acctTypeFilters)
404         g_list_free (gas->acctTypeFilters);
405 
406     G_OBJECT_CLASS (parent_class)->finalize (object);
407 }
408 
409 static void
gnc_account_sel_dispose(GObject * object)410 gnc_account_sel_dispose (GObject *object)
411 {
412     GNCAccountSel *gas;
413 
414     g_return_if_fail (object != NULL);
415     g_return_if_fail (GNC_IS_ACCOUNT_SEL(object));
416 
417     gas = GNC_ACCOUNT_SEL(object);
418 
419     if (gas->store)
420     {
421         g_object_unref (gas->store);
422         gas->store = NULL;
423     }
424 
425     if (gas->eventHandlerId)
426     {
427         qof_event_unregister_handler (gas->eventHandlerId);
428         gas->eventHandlerId = 0;
429     }
430 
431     G_OBJECT_CLASS (parent_class)->dispose (object);
432 }
433 
434 void
gnc_account_sel_set_new_account_ability(GNCAccountSel * gas,gboolean state)435 gnc_account_sel_set_new_account_ability (GNCAccountSel *gas,
436                                          gboolean state)
437 {
438     g_return_if_fail (gas != NULL);
439 
440     if (state == (gas->newAccountButton != NULL))
441     {
442         /* We're already in that state; don't do anything. */
443         return;
444     }
445 
446     if (gas->newAccountButton)
447     {
448         g_assert (state == TRUE);
449         /* destroy the existing button. */
450         gtk_container_remove (GTK_CONTAINER(gas), gas->newAccountButton);
451         gtk_widget_destroy (gas->newAccountButton);
452         gas->newAccountButton = NULL;
453         return;
454     }
455 
456     /* create the button. */
457     gas->newAccountButton = gtk_button_new_with_label (_("New..."));
458     g_signal_connect (gas->newAccountButton,
459                       "clicked",
460                       G_CALLBACK(gas_new_account_click),
461                       gas);
462 
463     gtk_box_pack_start (GTK_BOX(gas), gas->newAccountButton,
464                         FALSE, FALSE, 0);
465 }
466 
467 void
gnc_account_sel_set_new_account_modal(GNCAccountSel * gas,gboolean state)468 gnc_account_sel_set_new_account_modal (GNCAccountSel *gas,
469                                        gboolean state)
470 {
471     g_return_if_fail (gas != NULL);
472     gas->isModal = state;
473 }
474 
475 static void
gas_new_account_click(GtkButton * b,gpointer ud)476 gas_new_account_click (GtkButton *b, gpointer ud)
477 {
478     GNCAccountSel *gas = (GNCAccountSel*)ud;
479     Account *account = NULL;
480     GtkWindow *parent = GTK_WINDOW(gtk_widget_get_toplevel (GTK_WIDGET(gas)));
481     if (gas->isModal)
482     {
483         account = gnc_ui_new_accounts_from_name_window_with_types (parent, NULL,
484                                                                    gas->acctTypeFilters);
485         if (account)
486             gnc_account_sel_set_account (gas, account, FALSE);
487     }
488     else
489         gnc_ui_new_account_with_types (parent, gnc_get_current_book(),
490                                        gas->acctTypeFilters);
491 }
492 
493 gint
gnc_account_sel_get_num_account(GNCAccountSel * gas)494 gnc_account_sel_get_num_account (GNCAccountSel *gas)
495 {
496     if (NULL == gas)
497         return 0;
498 
499     return gtk_tree_model_iter_n_children (GTK_TREE_MODEL(gas->store), NULL);
500 }
501 
502 void
gnc_account_sel_purge_account(GNCAccountSel * gas,Account * target,gboolean recursive)503 gnc_account_sel_purge_account (GNCAccountSel *gas,
504                                Account *target,
505                                gboolean recursive)
506 {
507     GtkTreeModel *model = GTK_TREE_MODEL(gas->store);
508     GtkTreeIter iter;
509     Account *acc;
510     gboolean more;
511 
512     if (!gtk_tree_model_get_iter_first (model, &iter))
513         return;
514 
515     if (!recursive)
516     {
517         do
518         {
519             gtk_tree_model_get (model, &iter, ACCT_COL_PTR, &acc, -1);
520             if (acc == target)
521             {
522                 gtk_list_store_remove (gas->store, &iter);
523                 break;
524             }
525         }
526         while (gtk_tree_model_iter_next (model, &iter));
527     }
528     else
529     {
530         do
531         {
532             gtk_tree_model_get (model, &iter, ACCT_COL_PTR, &acc, -1);
533             while (acc)
534             {
535                 if (acc == target)
536                     break;
537                 acc = gnc_account_get_parent (acc);
538             }
539 
540             if (acc == target)
541                 more = gtk_list_store_remove (gas->store, &iter);
542             else
543                 more = gtk_tree_model_iter_next (model, &iter);
544         }
545         while (more);
546     }
547     gtk_combo_box_set_active (GTK_COMBO_BOX(gas->combo), 0);
548 }
549