GnuCash  4.11-148-gc20d717b33+
account-quickfill.c
1 /********************************************************************\
2  * account-quickfill.h -- Create an account-name quick-fill *
3  * Copyright (C) 2004 Linas Vepstas <linas@linas.org> *
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 gnu@gnu.org *
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,
34  gpointer qfb);
35 static void listen_for_account_events (QofInstance* entity,
36  QofEventId event_type,
37  gpointer user_data, gpointer event_data);
38 
39 /* Column indices for the list store */
40 #define ACCOUNT_NAME 0
41 #define ACCOUNT_POINTER 1
42 #define NUM_ACCOUNT_COLUMNS 2
43 
44 /* ===================================================================== */
45 /* In order to speed up register starts for registers that have a huge
46  * number of accounts in them (where 'huge' is >500) we build a quickfill
47  * cache of account names. This cache is needed because some users on
48  * some machines experience register open times in the tens of seconds
49  * type timescales. Building the quickfill list accounts for almost
50  * all of that cpu time (about 90% of the xfer_cell build time for 600
51  * accounts).
52  */
53 
54 typedef struct
55 {
56  QuickFill* qf;
57  gboolean load_list_store;
58  GtkListStore* list_store;
59  QofBook* book;
60  Account* root;
61  gint listener;
62  AccountBoolCB dont_add_cb;
63  gpointer dont_add_data;
64 } QFB;
65 
66 static void
67 shared_quickfill_destroy (QofBook* book, gpointer key, gpointer user_data)
68 {
69  QFB* qfb = user_data;
70  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL,
71  GNC_PREF_ACCOUNT_SEPARATOR,
72  shared_quickfill_pref_changed,
73  qfb);
74  gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REGISTER,
75  GNC_PREF_SHOW_LEAF_ACCT_NAMES,
76  shared_quickfill_pref_changed,
77  qfb);
78  gnc_quickfill_destroy (qfb->qf);
79  g_object_unref (qfb->list_store);
80  qof_event_unregister_handler (qfb->listener);
81  g_free (qfb);
82 }
83 
84 
85 typedef struct find_data
86 {
87  GList* accounts;
88  GList* refs;
89 } find_data;
90 
91 static gboolean
92 shared_quickfill_find_accounts (GtkTreeModel* model,
93  GtkTreePath* path,
94  GtkTreeIter* iter,
95  gpointer user_data)
96 {
97  Account* account = NULL;
98  find_data* data = user_data;
99  GtkTreeRowReference* ref;
100  GList* tmp;
101 
102  gtk_tree_model_get (model, iter, ACCOUNT_POINTER, &account, -1);
103  for (tmp = data->accounts; tmp; tmp = g_list_next (tmp))
104  {
105  if (tmp->data == account)
106  {
107  ref = gtk_tree_row_reference_new (model, path);
108  data->refs = g_list_append (data->refs, ref);
109  data->accounts = g_list_remove_link (data->accounts, tmp);
110  return (data->accounts == NULL);
111  }
112  }
113  return FALSE;
114 }
115 
116 
117 /* Splat the account name into the shared quickfill object */
118 static void
119 load_shared_qf_cb (Account* account, gpointer data)
120 {
121  QFB* qfb = data;
122  char* name;
123  GtkTreeIter iter;
124 
125  if (qfb->dont_add_cb)
126  {
127  gboolean skip = (qfb->dont_add_cb) (account, qfb->dont_add_data);
128  if (skip)
129  return;
130  }
131 
132  name = gnc_get_account_name_for_register (account);
133  if (NULL == name)
134  return;
135  gnc_quickfill_insert (qfb->qf, name, QUICKFILL_ALPHA);
136  if (qfb->load_list_store)
137  {
138  gtk_list_store_append (qfb->list_store, &iter);
139  gtk_list_store_set (qfb->list_store, &iter,
140  ACCOUNT_NAME, name,
141  ACCOUNT_POINTER, account,
142  -1);
143  }
144  g_free (name);
145 }
146 
147 
148 static void
149 shared_quickfill_pref_changed (gpointer prefs, gchar* pref, gpointer user_data)
150 {
151  QFB* qfb = user_data;
152 
153  /* Reload the quickfill */
154  gnc_quickfill_purge (qfb->qf);
155  gtk_list_store_clear (qfb->list_store);
156  qfb->load_list_store = TRUE;
157  gnc_account_foreach_descendant (qfb->root, load_shared_qf_cb, qfb);
158  qfb->load_list_store = FALSE;
159 }
160 
161 
162 /* Build the quickfill list out of account names.
163  * Essentially same loop as in gnc_load_xfer_cell() above.
164  */
165 static QFB*
166 build_shared_quickfill (QofBook* book, Account* root, const char* key,
167  AccountBoolCB cb, gpointer data)
168 {
169  QFB* qfb;
170 
171  qfb = g_new0 (QFB, 1);
172  qfb->qf = gnc_quickfill_new();
173  qfb->book = book;
174  qfb->root = root;
175  qfb->listener = 0;
176  qfb->dont_add_cb = cb;
177  qfb->dont_add_data = data;
178  qfb->load_list_store = TRUE;
179  qfb->list_store = gtk_list_store_new (NUM_ACCOUNT_COLUMNS,
180  G_TYPE_STRING, G_TYPE_POINTER);
181 
182  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL,
183  GNC_PREF_ACCOUNT_SEPARATOR,
184  shared_quickfill_pref_changed,
185  qfb);
186 
187  gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REGISTER,
188  GNC_PREF_SHOW_LEAF_ACCT_NAMES,
189  shared_quickfill_pref_changed,
190  qfb);
191 
192  gnc_account_foreach_descendant (root, load_shared_qf_cb, qfb);
193  qfb->load_list_store = FALSE;
194 
195  qfb->listener = qof_event_register_handler (listen_for_account_events, qfb);
196 
197  qof_book_set_data_fin (book, key, qfb, shared_quickfill_destroy);
198 
199  return qfb;
200 }
201 
202 QuickFill*
204  AccountBoolCB cb, gpointer cb_data)
205 {
206  QFB* qfb;
207  QofBook* book;
208 
209  book = gnc_account_get_book (root);
210  qfb = qof_book_get_data (book, key);
211 
212  if (qfb)
213  return qfb->qf;
214 
215  qfb = build_shared_quickfill (book, root, key, cb, cb_data);
216  return qfb->qf;
217 }
218 
219 GtkListStore*
220 gnc_get_shared_account_name_list_store (Account* root, const char* key,
221  AccountBoolCB cb, gpointer cb_data)
222 {
223  QFB* qfb;
224  QofBook* book;
225 
226  book = gnc_account_get_book (root);
227  qfb = qof_book_get_data (book, key);
228 
229  if (qfb)
230  return qfb->list_store;
231 
232  qfb = build_shared_quickfill (book, root, key, cb, cb_data);
233  return qfb->list_store;
234 }
235 
236 /* Since we are maintaining a 'global' quickfill list, we need to
237  * update it whenever the user creates a new account. So listen
238  * for account modification events, and add new accounts.
239  */
240 static void
241 listen_for_account_events (QofInstance* entity, QofEventId event_type,
242  gpointer user_data, gpointer event_data)
243 {
244  QFB* qfb = user_data;
245  QuickFill* qf = qfb->qf;
246  QuickFill* match;
247  char* name;
248  const char* match_str;
249  Account* account;
250  GtkTreeIter iter;
251  find_data data = { 0 };
252  GtkTreePath* path;
253  GList* tmp;
254  gboolean valid;
255 
256  if (0 == (event_type & (QOF_EVENT_MODIFY | QOF_EVENT_ADD | QOF_EVENT_REMOVE)))
257  return;
258 
259  if (!GNC_IS_ACCOUNT (entity))
260  return;
261  account = GNC_ACCOUNT (entity);
262 
263  ENTER ("entity %p, event type %x, user data %p, ecent data %p",
264  entity, event_type, user_data, event_data);
265 
266  if (gnc_account_get_root (account) != qfb->root)
267  {
268  LEAVE ("root account mismatch");
269  return;
270  }
271 
272  name = gnc_get_account_name_for_register (account);
273  if (NULL == name)
274  {
275  LEAVE ("account has no name");
276  return;
277  }
278 
279  switch (event_type)
280  {
281  case QOF_EVENT_MODIFY:
282  DEBUG ("modify %s", name);
283 
284  /* Find the account (and all its descendants) in the model. The
285  * full name of all these accounts has changed. */
286  data.accounts = gnc_account_get_descendants (account);
287  data.accounts = g_list_prepend (data.accounts, account);
288  gtk_tree_model_foreach (GTK_TREE_MODEL (qfb->list_store),
289  shared_quickfill_find_accounts, &data);
290 
291  /* Update the existing items in the list store. Its possible
292  * that the change has caused an existing item to now become
293  * hidden, in which case it needs to be removed from the list
294  * store. Otherwise its a simple update of the name string. */
295  for (tmp = data.refs; tmp; tmp = g_list_next (tmp))
296  {
297  gchar* old_name, *new_name;
298  path = gtk_tree_row_reference_get_path (tmp->data);
299  gtk_tree_row_reference_free (tmp->data);
300  if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (qfb->list_store),
301  &iter, path))
302  {
303  gtk_tree_path_free (path);
304  continue;
305  }
306  gtk_tree_path_free (path);
307  gtk_tree_model_get (GTK_TREE_MODEL (qfb->list_store), &iter,
308  ACCOUNT_POINTER, &account,
309  ACCOUNT_NAME, &old_name,
310  -1);
311 
312  new_name = gnc_get_account_name_for_register (account);
313 
314  /* check if the name has changed */
315  match = gnc_quickfill_get_string_match (qf, old_name);
316  if (match && (g_strcmp0 (old_name, new_name) != 0))
317  gnc_quickfill_remove (qf, old_name, QUICKFILL_ALPHA);
318 
319  if (qfb->dont_add_cb &&
320  qfb->dont_add_cb (account, qfb->dont_add_data))
321  {
322  gnc_quickfill_remove (qf, new_name, QUICKFILL_ALPHA);
323  gtk_list_store_remove (qfb->list_store, &iter);
324  }
325  else
326  {
327  gnc_quickfill_insert (qf, new_name, QUICKFILL_ALPHA);
328  gtk_list_store_set (qfb->list_store, &iter,
329  ACCOUNT_NAME, new_name,
330  -1);
331  }
332  g_free (old_name);
333  g_free (new_name);
334  }
335 
336  /* Any accounts that weren't found in the tree are accounts that
337  * were hidden but have now become visible. Add them to the list
338  * store. */
339  for (tmp = data.accounts; tmp; tmp = g_list_next (tmp))
340  {
341  account = tmp->data;
342  if (qfb->dont_add_cb)
343  {
344  if (qfb->dont_add_cb (account, qfb->dont_add_data))
345  {
346  continue;
347  }
348  }
349  gnc_quickfill_insert (qf, name, QUICKFILL_ALPHA);
350  gtk_list_store_append (qfb->list_store, &iter);
351  gtk_list_store_set (qfb->list_store, &iter,
352  ACCOUNT_NAME, name,
353  ACCOUNT_POINTER, account,
354  -1);
355  }
356  break;
357 
358  case QOF_EVENT_REMOVE:
359  DEBUG ("remove %s", name);
360 
361  /* Remove from qf */
362  gnc_quickfill_remove (qfb->qf, name, QUICKFILL_ALPHA);
363 
364  /* Does the account exist in the model? */
365  data.accounts = g_list_append (NULL, account);
366  gtk_tree_model_foreach (GTK_TREE_MODEL (qfb->list_store),
367  shared_quickfill_find_accounts, &data);
368 
369  /* Remove from list store */
370  for (tmp = data.refs; tmp; tmp = g_list_next (tmp))
371  {
372  path = gtk_tree_row_reference_get_path (tmp->data);
373  gtk_tree_row_reference_free (tmp->data);
374  if (gtk_tree_model_get_iter (GTK_TREE_MODEL (qfb->list_store),
375  &iter, path))
376  {
377  gtk_list_store_remove (qfb->list_store, &iter);
378  }
379  gtk_tree_path_free (path);
380  }
381  break;
382 
383  case QOF_EVENT_ADD:
384  DEBUG ("add %s", name);
385 
386  if (qfb->dont_add_cb &&
387  qfb->dont_add_cb (account, qfb->dont_add_data))
388  break;
389 
390  match = gnc_quickfill_get_string_match (qf, name);
391  if (match)
392  {
393  match_str = gnc_quickfill_string (match);
394  if (match_str && (g_strcmp0 (match_str, name) != 0))
395  {
396  PINFO ("got match for %s", name);
397  break;
398  }
399  }
400 
401  PINFO ("insert new account %s into qf=%p", name, qf);
402  gnc_quickfill_insert (qf, name, QUICKFILL_ALPHA);
403  gtk_list_store_append (qfb->list_store, &iter);
404  gtk_list_store_set (qfb->list_store, &iter,
405  ACCOUNT_NAME, name,
406  ACCOUNT_POINTER, account,
407  -1);
408  break;
409 
410  default:
411  DEBUG ("other %s", name);
412  break;
413  }
414 
415  if (data.accounts)
416  g_list_free (data.accounts);
417  if (data.refs)
418  g_list_free (data.refs);
419  g_free (name);
420  LEAVE (" ");
421 }
422 
423 /* ====================== END OF FILE ================================== */
void gnc_quickfill_insert(QuickFill *qf, const char *text, QuickFillSort sort)
Add the string "text" to the collection of searchable strings.
Definition: QuickFill.c:229
gulong gnc_prefs_register_cb(const char *group, const gchar *pref_name, gpointer func, gpointer user_data)
Register a callback that gets triggered when the given preference changes.
Definition: gnc-prefs.c:128
void gnc_account_foreach_descendant(const Account *acc, AccountCb thunk, gpointer user_data)
This method will traverse all children of this accounts and their descendants, calling &#39;func&#39; on each...
Definition: Account.cpp:3245
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
Create an account-name quick-fill.
#define ENTER(format, args...)
Print a function entry debugging message.
Definition: qoflog.h:272
QuickFill * gnc_get_shared_account_name_quickfill(Account *root, const char *key, AccountBoolCB cb, gpointer cb_data)
Create/fetch a quickfill of account names.
gint qof_event_register_handler(QofEventHandler handler, gpointer user_data)
Register a handler for events.
Definition: qofevent.cpp:73
gchar * gnc_get_account_name_for_register(const Account *account)
Get either the full name of the account or the simple name, depending on the configuration parameter ...
Definition: gnc-ui-util.c:581
gint QofEventId
Define the type of events allowed.
Definition: qofevent.h:45
void qof_book_set_data_fin(QofBook *book, const gchar *key, gpointer data, QofBookFinalCB)
Same as qof_book_set_data(), except that the callback will be called when the book is destroyed...
void qof_event_unregister_handler(gint handler_id)
Unregister an event handler.
Definition: qofevent.cpp:103
QuickFill * gnc_quickfill_get_string_match(QuickFill *qf, const char *str)
Return a subnode in the tree whose strings all match the string &#39;str&#39; as the next substring...
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)
This routine returns a flat list of all of the accounts that are descendants of the specified account...
Definition: Account.cpp:3036
const char * gnc_quickfill_string(QuickFill *qf)
For the given node &#39;qf&#39;, return the best-guess matching string.
Definition: QuickFill.c:123
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition: qoflog.h:282
Account * gnc_account_get_root(Account *acc)
This routine returns the root account of the account tree that the specified account belongs to...
Definition: Account.cpp:2906
gpointer qof_book_get_data(const QofBook *book, const gchar *key)
Retrieves arbitrary pointers to structs stored by qof_book_set_data.
void gnc_prefs_remove_cb_by_func(const gchar *group, const gchar *pref_name, gpointer func, gpointer user_data)
Remove a function that was registered for a callback when the given preference changed.
Definition: gnc-prefs.c:143