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