GnuCash  5.6-150-g038405b370+
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 
255  if (0 == (event_type & (QOF_EVENT_MODIFY | QOF_EVENT_ADD | QOF_EVENT_REMOVE)))
256  return;
257 
258  if (!GNC_IS_ACCOUNT (entity))
259  return;
260  account = GNC_ACCOUNT (entity);
261 
262  ENTER ("entity %p, event type %x, user data %p, ecent data %p",
263  entity, event_type, user_data, event_data);
264 
265  if (gnc_account_get_root (account) != qfb->root)
266  {
267  LEAVE ("root account mismatch");
268  return;
269  }
270 
271  name = gnc_get_account_name_for_register (account);
272  if (NULL == name)
273  {
274  LEAVE ("account has no name");
275  return;
276  }
277 
278  switch (event_type)
279  {
280  case QOF_EVENT_MODIFY:
281  DEBUG ("modify %s", name);
282 
283  /* Find the account (and all its descendants) in the model. The
284  * full name of all these accounts has changed. */
285  data.accounts = gnc_account_get_descendants (account);
286  data.accounts = g_list_prepend (data.accounts, account);
287  gtk_tree_model_foreach (GTK_TREE_MODEL (qfb->list_store),
288  shared_quickfill_find_accounts, &data);
289 
290  /* Update the existing items in the list store. Its possible
291  * that the change has caused an existing item to now become
292  * hidden, in which case it needs to be removed from the list
293  * store. Otherwise its a simple update of the name string. */
294  for (tmp = data.refs; tmp; tmp = g_list_next (tmp))
295  {
296  gchar* old_name, *new_name;
297  path = gtk_tree_row_reference_get_path (tmp->data);
298  gtk_tree_row_reference_free (tmp->data);
299  if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (qfb->list_store),
300  &iter, path))
301  {
302  gtk_tree_path_free (path);
303  continue;
304  }
305  gtk_tree_path_free (path);
306  gtk_tree_model_get (GTK_TREE_MODEL (qfb->list_store), &iter,
307  ACCOUNT_POINTER, &account,
308  ACCOUNT_NAME, &old_name,
309  -1);
310 
311  new_name = gnc_get_account_name_for_register (account);
312 
313  /* check if the name has changed */
314  match = gnc_quickfill_get_string_match (qf, old_name);
315  if (match && (g_strcmp0 (old_name, new_name) != 0))
316  gnc_quickfill_remove (qf, old_name, QUICKFILL_ALPHA);
317 
318  if (qfb->dont_add_cb &&
319  qfb->dont_add_cb (account, qfb->dont_add_data))
320  {
321  gnc_quickfill_remove (qf, new_name, QUICKFILL_ALPHA);
322  gtk_list_store_remove (qfb->list_store, &iter);
323  }
324  else
325  {
326  gnc_quickfill_insert (qf, new_name, QUICKFILL_ALPHA);
327  gtk_list_store_set (qfb->list_store, &iter,
328  ACCOUNT_NAME, new_name,
329  -1);
330  }
331  g_free (old_name);
332  g_free (new_name);
333  }
334 
335  /* Any accounts that weren't found in the tree are accounts that
336  * were hidden but have now become visible. Add them to the list
337  * store. */
338  for (tmp = data.accounts; tmp; tmp = g_list_next (tmp))
339  {
340  account = tmp->data;
341  if (qfb->dont_add_cb)
342  {
343  if (qfb->dont_add_cb (account, qfb->dont_add_data))
344  {
345  continue;
346  }
347  }
348  gnc_quickfill_insert (qf, name, QUICKFILL_ALPHA);
349  gtk_list_store_append (qfb->list_store, &iter);
350  gtk_list_store_set (qfb->list_store, &iter,
351  ACCOUNT_NAME, name,
352  ACCOUNT_POINTER, account,
353  -1);
354  }
355  break;
356 
357  case QOF_EVENT_REMOVE:
358  DEBUG ("remove %s", name);
359 
360  /* Remove from qf */
361  gnc_quickfill_remove (qfb->qf, name, QUICKFILL_ALPHA);
362 
363  /* Does the account exist in the model? */
364  data.accounts = g_list_append (NULL, account);
365  gtk_tree_model_foreach (GTK_TREE_MODEL (qfb->list_store),
366  shared_quickfill_find_accounts, &data);
367 
368  /* Remove from list store */
369  for (tmp = data.refs; tmp; tmp = g_list_next (tmp))
370  {
371  path = gtk_tree_row_reference_get_path (tmp->data);
372  gtk_tree_row_reference_free (tmp->data);
373  if (gtk_tree_model_get_iter (GTK_TREE_MODEL (qfb->list_store),
374  &iter, path))
375  {
376  gtk_list_store_remove (qfb->list_store, &iter);
377  }
378  gtk_tree_path_free (path);
379  }
380  break;
381 
382  case QOF_EVENT_ADD:
383  DEBUG ("add %s", name);
384 
385  if (qfb->dont_add_cb &&
386  qfb->dont_add_cb (account, qfb->dont_add_data))
387  break;
388 
389  match = gnc_quickfill_get_string_match (qf, name);
390  if (match)
391  {
392  match_str = gnc_quickfill_string (match);
393  if (match_str && (g_strcmp0 (match_str, name) != 0))
394  {
395  PINFO ("got match for %s", name);
396  break;
397  }
398  }
399 
400  PINFO ("insert new account %s into qf=%p", name, qf);
401  gnc_quickfill_insert (qf, name, QUICKFILL_ALPHA);
402  gtk_list_store_append (qfb->list_store, &iter);
403  gtk_list_store_set (qfb->list_store, &iter,
404  ACCOUNT_NAME, name,
405  ACCOUNT_POINTER, account,
406  -1);
407  break;
408 
409  default:
410  DEBUG ("other %s", name);
411  break;
412  }
413 
414  if (data.accounts)
415  g_list_free (data.accounts);
416  if (data.refs)
417  g_list_free (data.refs);
418  g_free (name);
419  LEAVE (" ");
420 }
421 
422 /* ====================== 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:3195
utility functions for the GnuCash UI
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
STRUCTS.
#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
char * 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 ...
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:3003
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:2882
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