GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
account-quickfill.c
1 /********************************************************************\
2  * account-quickfill.h -- Create an account-name quick-fill *
3  * Copyright (C) 2004 Linas Vepstas <[email protected]> *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA [email protected] *
21  * *
22 \********************************************************************/
23 
24 #include "config.h"
25 #include "account-quickfill.h"
26 #include "gnc-engine.h"
27 #include "gnc-prefs.h"
28 #include "gnc-ui-util.h"
29 
30 /* This static indicates the debugging module that this .o belongs to. */
31 static QofLogModule log_module = GNC_MOD_REGISTER;
32 
33 static void shared_quickfill_pref_changed (gpointer prefs, gchar *pref, gpointer qfb);
34 static void listen_for_account_events (QofInstance *entity, QofEventId event_type,
35  gpointer user_data, gpointer event_data);
36 
37 /* Column indices for the list store */
38 #define ACCOUNT_NAME 0
39 #define ACCOUNT_POINTER 1
40 #define NUM_ACCOUNT_COLUMNS 2
41 
42 /* ===================================================================== */
43 /* In order to speed up register starts for registers that have a huge
44  * number of accounts in them (where 'huge' is >500) we build a quickfill
45  * cache of account names. This cache is needed because some users on
46  * some machines experience register open times in the tens of seconds
47  * type timescales. Building the quickfill list accounts for almost
48  * all of that cpu time (about 90% of the xfer_cell build time for 600
49  * accounts).
50  */
51 
52 typedef struct
53 {
54  QuickFill *qf;
55  gboolean load_list_store;
56  GtkListStore *list_store;
57  QofBook *book;
58  Account *root;
59  gint listener;
60  AccountBoolCB dont_add_cb;
61  gpointer dont_add_data;
62 } QFB;
63 
64 static void
65 shared_quickfill_destroy (QofBook *book, gpointer key, gpointer user_data)
66 {
67  QFB *qfb = user_data;
68  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REGISTER,
69  GNC_PREF_ACCOUNT_SEPARATOR,
70  shared_quickfill_pref_changed,
71  qfb);
72  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REGISTER,
73  GNC_PREF_SHOW_LEAF_ACCT_NAMES,
74  shared_quickfill_pref_changed,
75  qfb);
76  gnc_quickfill_destroy (qfb->qf);
77  g_object_unref(qfb->list_store);
78  qof_event_unregister_handler (qfb->listener);
79  g_free (qfb);
80 }
81 
82 
83 typedef struct find_data
84 {
85  GList *accounts;
86  GList *refs;
87 } find_data;
88 
89 static gboolean
90 shared_quickfill_find_accounts (GtkTreeModel *model,
91  GtkTreePath *path,
92  GtkTreeIter *iter,
93  gpointer user_data)
94 {
95  Account *account = NULL;
96  find_data *data = user_data;
97  GtkTreeRowReference* ref;
98  GList *tmp;
99 
100  gtk_tree_model_get(model, iter, ACCOUNT_POINTER, &account, -1);
101  for (tmp = data->accounts; tmp; tmp = g_list_next(tmp))
102  {
103  if (tmp->data == account)
104  {
105  ref = gtk_tree_row_reference_new(model, path);
106  data->refs = g_list_append(data->refs, ref);
107  data->accounts = g_list_remove_link(data->accounts, tmp);
108  return (data->accounts == NULL);
109  }
110  }
111 
112  return FALSE;
113 }
114 
115 
116 /* Splat the account name into the shared quickfill object */
117 static void
118 load_shared_qf_cb (Account *account, gpointer data)
119 {
120  QFB *qfb = data;
121  char *name;
122  GtkTreeIter iter;
123 
124  if (qfb->dont_add_cb)
125  {
126  gboolean skip = (qfb->dont_add_cb) (account, qfb->dont_add_data);
127  if (skip) return;
128  }
129 
130  name = gnc_get_account_name_for_register (account);
131  if (NULL == name) return;
132  gnc_quickfill_insert (qfb->qf, name, QUICKFILL_ALPHA);
133  if (qfb->load_list_store)
134  {
135  gtk_list_store_append (qfb->list_store, &iter);
136  gtk_list_store_set (qfb->list_store, &iter,
137  ACCOUNT_NAME, name,
138  ACCOUNT_POINTER, account,
139  -1);
140  }
141  g_free(name);
142 }
143 
144 
145 static void
146 shared_quickfill_pref_changed (gpointer prefs, gchar *pref, gpointer user_data)
147 {
148  QFB *qfb = user_data;
149 
150  /* Reload the quickfill */
151  gnc_quickfill_purge(qfb->qf);
152  gtk_list_store_clear(qfb->list_store);
153  qfb->load_list_store = TRUE;
154  gnc_account_foreach_descendant(qfb->root, load_shared_qf_cb, qfb);
155  qfb->load_list_store = FALSE;
156 }
157 
158 
159 /* Build the quickfill list out of account names.
160  * Essentially same loop as in gnc_load_xfer_cell() above.
161  */
162 static QFB *
163 build_shared_quickfill (QofBook *book, Account *root, const char * key,
164  AccountBoolCB cb, gpointer data)
165 {
166  QFB *qfb;
167 
168  qfb = g_new0(QFB, 1);
169  qfb->qf = gnc_quickfill_new ();
170  qfb->book = book;
171  qfb->root = root;
172  qfb->listener = 0;
173  qfb->dont_add_cb = cb;
174  qfb->dont_add_data = data;
175  qfb->load_list_store = TRUE;
176  qfb->list_store =
177  gtk_list_store_new (NUM_ACCOUNT_COLUMNS, G_TYPE_STRING, G_TYPE_POINTER);
178 
179  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
180  GNC_PREF_ACCOUNT_SEPARATOR,
181  shared_quickfill_pref_changed,
182  qfb);
183 
184  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REGISTER,
185  GNC_PREF_SHOW_LEAF_ACCT_NAMES,
186  shared_quickfill_pref_changed,
187  qfb);
188 
189  gnc_account_foreach_descendant(root, load_shared_qf_cb, qfb);
190  qfb->load_list_store = FALSE;
191 
192  qfb->listener =
193  qof_event_register_handler (listen_for_account_events, qfb);
194 
195  qof_book_set_data_fin (book, key, qfb, shared_quickfill_destroy);
196 
197  return qfb;
198 }
199 
200 QuickFill *
202  const char * key,
203  AccountBoolCB cb, gpointer cb_data)
204 {
205  QFB *qfb;
206  QofBook *book;
207 
208  book = gnc_account_get_book (root);
209  qfb = qof_book_get_data (book, key);
210 
211  if (qfb) return qfb->qf;
212 
213  qfb = build_shared_quickfill (book, root, key, cb, cb_data);
214  return qfb->qf;
215 }
216 
217 GtkListStore *
218 gnc_get_shared_account_name_list_store (Account *root,
219  const char * key,
220  AccountBoolCB cb, gpointer cb_data)
221 {
222  QFB *qfb;
223  QofBook *book;
224 
225  book = gnc_account_get_book (root);
226  qfb = qof_book_get_data (book, key);
227 
228  if (qfb) return qfb->list_store;
229 
230  qfb = build_shared_quickfill (book, root, key, cb, cb_data);
231  return qfb->list_store;
232 }
233 
234 /* Since we are maintaining a 'global' quickfill list, we need to
235  * update it whenever the user creates a new account. So listen
236  * for account modification events, and add new accounts.
237  */
238 static void
239 listen_for_account_events (QofInstance *entity, QofEventId event_type,
240  gpointer user_data, gpointer event_data)
241 {
242  QFB *qfb = user_data;
243  QuickFill *qf = qfb->qf;
244  QuickFill *match;
245  char * name;
246  const char *match_str;
247  Account *account;
248  GtkTreeIter iter;
249  find_data data = { 0 };
250  GtkTreePath *path;
251  GList *tmp;
252 
253  if (0 == (event_type & (QOF_EVENT_MODIFY | QOF_EVENT_ADD | QOF_EVENT_REMOVE)))
254  return;
255 
256  if (!GNC_IS_ACCOUNT (entity))
257  return;
258  account = GNC_ACCOUNT (entity);
259 
260  ENTER("entity %p, event type %x, user data %p, ecent data %p",
261  entity, event_type, user_data, event_data);
262 
263  if (gnc_account_get_root(account) != qfb->root)
264  {
265  LEAVE("root account mismatch");
266  return;
267  }
268 
269  name = gnc_get_account_name_for_register(account);
270  if (NULL == name)
271  {
272  LEAVE("account has no name");
273  return;
274  }
275 
276  switch (event_type)
277  {
278  case QOF_EVENT_MODIFY:
279  DEBUG("modify %s", name);
280 
281  /* Find the account (and all its descendants) in the model. The
282  * full name of all these accounts has changed. */
283  data.accounts = gnc_account_get_descendants(account);
284  data.accounts = g_list_prepend(data.accounts, account);
285  gtk_tree_model_foreach(GTK_TREE_MODEL(qfb->list_store),
286  shared_quickfill_find_accounts, &data);
287 
288  /* Update the existing items in the list store. Its possible
289  * that the change has caused an existing item to now become
290  * hidden, in which case it needs to be removed from the list
291  * store. Otherwise its a simple update of the name string. */
292  for (tmp = data.refs; tmp; tmp = g_list_next(tmp))
293  {
294  path = gtk_tree_row_reference_get_path(tmp->data);
295  gtk_tree_row_reference_free(tmp->data);
296  if (!gtk_tree_model_get_iter(GTK_TREE_MODEL(qfb->list_store),
297  &iter, path))
298  {
299  gtk_tree_path_free(path);
300  continue;
301  }
302  gtk_tree_path_free(path);
303  gtk_tree_model_get(GTK_TREE_MODEL(qfb->list_store), &iter,
304  ACCOUNT_POINTER, &account,
305  -1);
306  if (qfb->dont_add_cb &&
307  qfb->dont_add_cb(account, qfb->dont_add_data))
308  {
309  gtk_list_store_remove(qfb->list_store, &iter);
310  }
311  else
312  {
313  gchar *aname = gnc_get_account_name_for_register(account);
314  gtk_list_store_set(qfb->list_store, &iter,
315  ACCOUNT_NAME, aname,
316  -1);
317  g_free(aname);
318  }
319  }
320 
321  /* Any accounts that weren't found in the tree are accounts that
322  * were hidden but have now become visible. Add them to the list
323  * store. */
324  for (tmp = data.accounts; tmp; tmp = g_list_next(tmp))
325  {
326  account = tmp->data;
327  if (qfb->dont_add_cb)
328  {
329  if (qfb->dont_add_cb(account, qfb->dont_add_data))
330  {
331  continue;
332  }
333  }
334  gtk_list_store_append (qfb->list_store, &iter);
335  gtk_list_store_set (qfb->list_store, &iter,
336  ACCOUNT_NAME, name,
337  ACCOUNT_POINTER, account,
338  -1);
339  }
340  break;
341 
342  case QOF_EVENT_REMOVE:
343  DEBUG("remove %s", name);
344 
345  /* Remove from qf */
346  gnc_quickfill_remove(qfb->qf, name, QUICKFILL_ALPHA);
347 
348  /* Does the account exist in the model? */
349  data.accounts = g_list_append(NULL, account);
350  gtk_tree_model_foreach(GTK_TREE_MODEL(qfb->list_store),
351  shared_quickfill_find_accounts, &data);
352 
353  /* Remove from list store */
354  for (tmp = data.refs; tmp; tmp = g_list_next(tmp))
355  {
356  path = gtk_tree_row_reference_get_path (tmp->data);
357  gtk_tree_row_reference_free (tmp->data);
358  if (gtk_tree_model_get_iter(GTK_TREE_MODEL(qfb->list_store),
359  &iter, path))
360  {
361  gtk_list_store_remove(qfb->list_store, &iter);
362  }
363  gtk_tree_path_free(path);
364  }
365  break;
366 
367  case QOF_EVENT_ADD:
368  DEBUG("add %s", name);
369  if (qfb->dont_add_cb &&
370  qfb->dont_add_cb(account, qfb->dont_add_data))
371  break;
372 
373  match = gnc_quickfill_get_string_match (qf, name);
374  if (match)
375  {
376  match_str = gnc_quickfill_string (match);
377  if (match_str && (g_strcmp0(match_str, name) != 0))
378  {
379  PINFO ("got match for %s", name);
380  break;
381  }
382  }
383 
384  PINFO ("insert new account %s into qf=%p", name, qf);
385  gnc_quickfill_insert (qf, name, QUICKFILL_ALPHA);
386  gtk_list_store_append (qfb->list_store, &iter);
387  gtk_list_store_set (qfb->list_store, &iter,
388  ACCOUNT_NAME, name,
389  ACCOUNT_POINTER, account,
390  -1);
391  break;
392 
393  default:
394  DEBUG("other %s", name);
395  break;
396  }
397 
398  if (data.accounts)
399  g_list_free(data.accounts);
400  if (data.refs)
401  g_list_free(data.refs);
402  g_free(name);
403  LEAVE(" ");
404 }
405 
406 /* ====================== END OF FILE ================================== */
void gnc_quickfill_insert(QuickFill *qf, const char *text, QuickFillSort sort)
Definition: QuickFill.c:229
gulong gnc_prefs_register_cb(const char *group, const gchar *pref_name, gpointer func, gpointer user_data)
Definition: gnc-prefs.c:128
void gnc_account_foreach_descendant(const Account *acc, AccountCb thunk, gpointer user_data)
Definition: Account.c:2958
utility functions for the GnuCash UI
#define PINFO(format, args...)
Definition: qoflog.h:249
#define DEBUG(format, args...)
Definition: qoflog.h:255
Create an account-name quick-fill.
#define ENTER(format, args...)
Definition: qoflog.h:261
QuickFill * gnc_get_shared_account_name_quickfill(Account *root, const char *key, AccountBoolCB cb, gpointer cb_data)
gint qof_event_register_handler(QofEventHandler handler, gpointer handler_data)
Register a handler for events.
gchar * gnc_get_account_name_for_register(const Account *account)
Definition: gnc-ui-util.c:282
gint QofEventId
Definition: qofevent.h:45
void qof_book_set_data_fin(QofBook *book, const gchar *key, gpointer data, QofBookFinalCB)
void qof_event_unregister_handler(gint handler_id)
Unregister an event handler.
QuickFill * gnc_quickfill_get_string_match(QuickFill *qf, const char *str)
Definition: QuickFill.c:179
All type declarations for the whole Gnucash engine.
Generic api to store and retrieve preferences.
GList * gnc_account_get_descendants(const Account *account)
Definition: Account.c:2755
const char * gnc_quickfill_string(QuickFill *qf)
Definition: QuickFill.c:123
#define LEAVE(format, args...)
Definition: qoflog.h:271
Account * gnc_account_get_root(Account *acc)
Definition: Account.c:2630
gpointer qof_book_get_data(const QofBook *book, const gchar *key)
const gchar * QofLogModule
Definition: qofid.h:89
void gnc_prefs_remove_cb_by_func(const gchar *group, const gchar *pref_name, gpointer func, gpointer user_data)
Definition: gnc-prefs.c:148