GnuCash  5.6-150-g038405b370+
combocell-gnome.c
1 /********************************************************************\
2  * combocell-gnome.c -- implement combobox pull down cell for gnome *
3  * *
4  * This program is free software; you can redistribute it and/or *
5  * modify it under the terms of the GNU General Public License as *
6  * published by the Free Software Foundation; either version 2 of *
7  * the License, or (at your option) any later version. *
8  * *
9  * This program is distributed in the hope that it will be useful, *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12  * GNU General Public License for more details. *
13  * *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact: *
16  * *
17  * Free Software Foundation Voice: +1-617-542-5942 *
18  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19  * Boston, MA 02110-1301, USA gnu@gnu.org *
20  * *
21 \********************************************************************/
22 
23 /*
24  * FILE: combocell-gnome.c
25  *
26  * FUNCTION: Implement gnome portion of a pull-down combo widget
27  * embedded in a table cell.
28  *
29  * HISTORY:
30  * Copyright (c) 1998 Linas Vepstas <linas@linas.org>
31  * Copyright (c) 1998-1999 Rob Browning <rlb@cs.utexas.edu>
32  * Copyright (c) 2000 Linas Vepstas <linas@linas.org>
33  * Copyright (c) 2006 David Hampton <hampton@employees.org>
34  */
35 
36 #include <config.h>
37 
38 #include <string.h>
39 #include <gdk/gdkkeysyms.h>
40 
41 #include "QuickFill.h"
42 #include "combocell.h"
43 #include "gnc-prefs.h"
44 #include "gnucash-item-edit.h"
45 #include "gnucash-item-list.h"
46 #include "gnucash-sheet.h"
47 #include "gnucash-sheetP.h"
48 #include "table-allgui.h"
49 #include "Account.h"
50 #include "gnc-glib-utils.h"
51 
52 #define GNC_PREF_AUTO_RAISE_LISTS "auto-raise-lists"
53 
54 typedef struct _PopBox
55 {
56  GnucashSheet* sheet;
57  GncItemEdit* item_edit;
58  GncItemList* item_list;
59  GtkListStore* tmp_store;
60 
61  gboolean signals_connected; /* list signals connected? */
62 
63  gboolean list_popped; /* list is popped up? */
64 
65  gboolean autosize;
66 
67  QuickFill* qf;
68  gboolean use_quickfill_cache; /* If TRUE, we don't own the qf */
69 
70  gboolean in_list_select;
71 
72  gboolean strict;
73 
74  gunichar complete_char; /* char to be used for auto-completion */
75 
76  GList* ignore_strings;
77 
78 } PopBox;
79 
80 
81 static void gnc_combo_cell_gui_realize (BasicCell* bcell, gpointer w);
82 static void gnc_combo_cell_gui_move (BasicCell* bcell);
83 static void gnc_combo_cell_gui_destroy (BasicCell* bcell);
84 static gboolean gnc_combo_cell_enter (BasicCell* bcell,
85  int* cursor_position,
86  int* start_selection,
87  int* end_selection);
88 static void gnc_combo_cell_leave (BasicCell* bcell);
89 static void gnc_combo_cell_destroy (BasicCell* bcell);
90 
91 static GOnce auto_pop_init_once = G_ONCE_INIT;
92 static gboolean auto_pop_combos = FALSE;
93 
94 
95 static void
96 gnc_combo_cell_set_autopop (gpointer prefs, gchar* pref, gpointer user_data)
97 {
98  auto_pop_combos = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
99  GNC_PREF_AUTO_RAISE_LISTS);
100 }
101 
102 static gpointer
103 gnc_combo_cell_autopop_init (gpointer unused)
104 {
105  gulong id;
106  auto_pop_combos = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
107  GNC_PREF_AUTO_RAISE_LISTS);
108 
109  id = gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REGISTER,
110  GNC_PREF_AUTO_RAISE_LISTS,
111  gnc_combo_cell_set_autopop,
112  NULL);
113 
114  gnc_prefs_set_reg_auto_raise_lists_id (id);
115  return NULL;
116 }
117 
118 BasicCell*
119 gnc_combo_cell_new (void)
120 {
121  ComboCell* cell;
122 
123  g_once (&auto_pop_init_once, gnc_combo_cell_autopop_init, NULL);
124 
125  cell = g_new0 (ComboCell, 1);
126 
127  gnc_combo_cell_init (cell);
128 
129  return &cell->cell;
130 }
131 
132 void
133 gnc_combo_cell_init (ComboCell* cell)
134 {
135  PopBox* box;
136 
137  gnc_basic_cell_init (& (cell->cell));
138 
139  cell->cell.is_popup = TRUE;
140 
141  cell->cell.destroy = gnc_combo_cell_destroy;
142 
143  cell->cell.gui_realize = gnc_combo_cell_gui_realize;
144  cell->cell.gui_destroy = gnc_combo_cell_gui_destroy;
145 
146  box = g_new0 (PopBox, 1);
147 
148  box->sheet = NULL;
149  box->item_edit = NULL;
150  box->item_list = NULL;
151  box->tmp_store = gtk_list_store_new (1, G_TYPE_STRING);
152  box->signals_connected = FALSE;
153  box->list_popped = FALSE;
154  box->autosize = FALSE;
155 
156  cell->cell.gui_private = box;
157 
158  box->qf = gnc_quickfill_new();
159  box->use_quickfill_cache = FALSE;
160 
161  box->in_list_select = FALSE;
162 
163  box->strict = TRUE;
164 
165  box->complete_char = '\0';
166 
167  box->ignore_strings = NULL;
168 }
169 
170 static void
171 select_item_cb (GncItemList* item_list, char* item_string, gpointer data)
172 {
173  ComboCell* cell = data;
174  PopBox* box = cell->cell.gui_private;
175 
176  box->in_list_select = TRUE;
177  gnucash_sheet_modify_current_cell (box->sheet, item_string);
178  box->in_list_select = FALSE;
179 
180  gnc_item_edit_hide_popup (box->item_edit);
181  box->list_popped = FALSE;
182 }
183 
184 static void
185 change_item_cb (GncItemList* item_list, char* item_string, gpointer data)
186 {
187  ComboCell* cell = data;
188  PopBox* box = cell->cell.gui_private;
189 
190  box->in_list_select = TRUE;
191  gnucash_sheet_modify_current_cell (box->sheet, item_string);
192  box->in_list_select = FALSE;
193 }
194 
195 static void
196 activate_item_cb (GncItemList* item_list, char* item_string, gpointer data)
197 {
198  ComboCell* cell = data;
199  PopBox* box = cell->cell.gui_private;
200 
201  gnc_item_edit_hide_popup (box->item_edit);
202  box->list_popped = FALSE;
203 }
204 
205 static gboolean
206 key_press_item_cb (GncItemList* item_list, GdkEventKey* event, gpointer data)
207 {
208  ComboCell* cell = data;
209  PopBox* box = cell->cell.gui_private;
210 
211  switch (event->keyval)
212  {
213  case GDK_KEY_Escape:
214  gnc_item_edit_hide_popup (box->item_edit);
215  box->list_popped = FALSE;
216  break;
217 
218  default:
219  gtk_widget_event (GTK_WIDGET (box->sheet),
220  (GdkEvent*) event);
221  break;
222  }
223  return TRUE;
224 }
225 
226 static void
227 combo_disconnect_signals (ComboCell* cell)
228 {
229  PopBox* box = cell->cell.gui_private;
230 
231  if (!box->signals_connected)
232  return;
233 
234  g_signal_handlers_disconnect_matched (G_OBJECT (box->item_list),
235  G_SIGNAL_MATCH_DATA,
236  0, 0, NULL, NULL, cell);
237 
238  box->signals_connected = FALSE;
239 }
240 
241 static void
242 combo_connect_signals (ComboCell* cell)
243 {
244  PopBox* box = cell->cell.gui_private;
245 
246  if (box->signals_connected)
247  return;
248 
249  g_signal_connect (G_OBJECT (box->item_list), "select_item",
250  G_CALLBACK (select_item_cb), cell);
251 
252  g_signal_connect (G_OBJECT (box->item_list), "change_item",
253  G_CALLBACK (change_item_cb), cell);
254 
255  g_signal_connect (G_OBJECT (box->item_list), "activate_item",
256  G_CALLBACK (activate_item_cb), cell);
257 
258  g_signal_connect (G_OBJECT (box->item_list), "key_press_event",
259  G_CALLBACK (key_press_item_cb), cell);
260 
261  box->signals_connected = TRUE;
262 }
263 
264 static void
265 block_list_signals (ComboCell* cell)
266 {
267  PopBox* box = cell->cell.gui_private;
268 
269  if (!box->signals_connected)
270  return;
271 
272  g_signal_handlers_block_matched (G_OBJECT (box->item_list),
273  G_SIGNAL_MATCH_DATA,
274  0, 0, NULL, NULL, cell);
275 }
276 
277 static void
278 unblock_list_signals (ComboCell* cell)
279 {
280  PopBox* box = cell->cell.gui_private;
281 
282  if (!box->signals_connected)
283  return;
284 
285  g_signal_handlers_unblock_matched (G_OBJECT (box->item_list),
286  G_SIGNAL_MATCH_DATA,
287  0, 0, NULL, NULL, cell);
288 }
289 
290 static void
291 gnc_combo_cell_gui_destroy (BasicCell* bcell)
292 {
293  PopBox* box = bcell->gui_private;
294  ComboCell* cell = (ComboCell*) bcell;
295 
296  if (cell->cell.gui_realize == NULL)
297  {
298  if (box != NULL && box->item_list != NULL)
299  {
300  combo_disconnect_signals (cell);
301  g_object_unref (box->item_list);
302  box->item_list = NULL;
303  }
304 
305  if (box && box->tmp_store)
306  {
307  g_object_unref (box->tmp_store);
308  box->tmp_store = NULL;
309  }
310 
311  /* allow the widget to be shown again */
312  cell->cell.gui_realize = gnc_combo_cell_gui_realize;
313  cell->cell.gui_move = NULL;
314  cell->cell.enter_cell = NULL;
315  cell->cell.leave_cell = NULL;
316  cell->cell.gui_destroy = NULL;
317  }
318 }
319 
320 static void
321 gnc_combo_cell_destroy (BasicCell* bcell)
322 {
323  ComboCell* cell = (ComboCell*) bcell;
324  PopBox* box = cell->cell.gui_private;
325 
326  gnc_combo_cell_gui_destroy (& (cell->cell));
327 
328  if (box != NULL)
329  {
330  /* Don't destroy the qf if its not ours to destroy */
331  if (FALSE == box->use_quickfill_cache)
332  {
333  gnc_quickfill_destroy (box->qf);
334  box->qf = NULL;
335  }
336 
337  g_list_free_full (box->ignore_strings, g_free);
338  box->ignore_strings = NULL;
339 
340  g_free (box);
341  cell->cell.gui_private = NULL;
342  }
343 
344  cell->cell.gui_private = NULL;
345  cell->cell.gui_realize = NULL;
346 }
347 
348 void
350 {
351  PopBox* box;
352 
353  if (cell == NULL)
354  return;
355 
356  box = cell->cell.gui_private;
357  if (box->item_list == NULL)
358  return;
359 
360  block_list_signals (cell);
361  gnc_item_list_set_sort_column (box->item_list, 0);
362  unblock_list_signals (cell);
363 }
364 
365 void
366 gnc_combo_cell_clear_menu (ComboCell* cell)
367 {
368  PopBox* box;
369 
370  if (cell == NULL)
371  return;
372 
373  box = cell->cell.gui_private;
374  if (box == NULL)
375  return;
376 
377  /* Don't destroy the qf if its not ours to destroy */
378  if (FALSE == box->use_quickfill_cache)
379  {
380  gnc_quickfill_destroy (box->qf);
381  box->qf = gnc_quickfill_new();
382  }
383 
384  if (box->item_list != NULL)
385  {
386  block_list_signals (cell);
387 
388  gnc_item_list_clear (box->item_list);
389  gnc_item_edit_hide_popup (box->item_edit);
390  box->list_popped = FALSE;
391  unblock_list_signals (cell);
392  }
393  else
394  gtk_list_store_clear (box->tmp_store);
395 }
396 
397 void
398 gnc_combo_cell_use_quickfill_cache (ComboCell* cell, QuickFill* shared_qf)
399 {
400  PopBox* box;
401 
402  if (cell == NULL) return;
403 
404  box = cell->cell.gui_private;
405  if (NULL == box) return;
406 
407  if (FALSE == box->use_quickfill_cache)
408  {
409  box->use_quickfill_cache = TRUE;
410  gnc_quickfill_destroy (box->qf);
411  }
412  box->qf = shared_qf;
413 }
414 
415 void
416 gnc_combo_cell_use_list_store_cache (ComboCell* cell, gpointer data)
417 {
418  if (cell == NULL) return;
419 
420  cell->shared_store = data;
421 }
422 
423 void
424 gnc_combo_cell_add_menu_item (ComboCell* cell, const char* menustr)
425 {
426  PopBox* box;
427 
428  if (cell == NULL)
429  return;
430  if (menustr == NULL)
431  return;
432 
433  box = cell->cell.gui_private;
434 
435  if (box->item_list != NULL)
436  {
437  block_list_signals (cell);
438 
439  gnc_item_list_append (box->item_list, menustr);
440  if (cell->cell.value &&
441  (strcmp (menustr, cell->cell.value) == 0))
442  gnc_item_list_select (box->item_list, menustr);
443 
444  unblock_list_signals (cell);
445  }
446  else
447  {
448  GtkTreeIter iter;
449 
450  gtk_list_store_append (box->tmp_store, &iter);
451  gtk_list_store_set (box->tmp_store, &iter, 0, menustr, -1);
452  }
453 
454  /* If we're going to be using a pre-fab quickfill,
455  * then don't fill it in here */
456  if (FALSE == box->use_quickfill_cache)
457  {
458  gnc_quickfill_insert (box->qf, menustr, QUICKFILL_ALPHA);
459  }
460 }
461 
462 void
464 {
465  PopBox* box;
466 
467  if (cell == NULL)
468  return;
469  if (menustr == NULL)
470  return;
471 
472  box = cell->cell.gui_private;
473 
474  if (box->item_list != NULL)
475  {
476  block_list_signals (cell);
477 
478  gnc_item_list_append (box->item_list, menustr);
479  if (cell->cell.value)
480  {
481  gchar* menu_copy = g_strdup (menustr);
482  gchar* value_copy = g_strdup (cell->cell.value);
483  g_strdelimit (menu_copy, "-:/\\.", ' ');
484  g_strdelimit (value_copy, "-:/\\.", ' ');
485  if (strcmp (menu_copy, value_copy) == 0)
486  {
487  gnc_combo_cell_set_value (cell, menustr);
488  gnc_item_list_select (box->item_list, menustr);
489  }
490  g_free (value_copy);
491  g_free (menu_copy);
492  }
493  unblock_list_signals (cell);
494  }
495 
496  /* If we're going to be using a pre-fab quickfill,
497  * then don't fill it in here */
498  if (FALSE == box->use_quickfill_cache)
499  {
500  gnc_quickfill_insert (box->qf, menustr, QUICKFILL_ALPHA);
501  }
502 }
503 
504 void
505 gnc_combo_cell_set_value (ComboCell* cell, const char* str)
506 {
507  gnc_basic_cell_set_value (&cell->cell, str);
508 }
509 
510 static inline void
511 list_store_append (GtkListStore *store, char* string)
512 {
513  GtkTreeIter iter;
514 
515  g_return_if_fail (store != NULL);
516  g_return_if_fail (string != NULL);
517  gtk_list_store_append (store, &iter);
518  gtk_list_store_set (store, &iter, 0, string, -1);
519 }
520 
521 /* This function looks through full_store for a partial match with newval and
522  * returns the first match (which must be subsequently freed). It fills out
523  * box->item_list with found matches.
524  */
525 static gchar*
526 gnc_combo_cell_type_ahead_search (const gchar* newval,
527  GtkListStore* full_store, ComboCell *cell)
528 {
529  GtkTreeIter iter;
530  PopBox* box = cell->cell.gui_private;
531  int num_found = 0;
532  gchar* match_str = NULL;
533  const char* sep = gnc_get_account_separator_string ();
534  char* escaped_sep = g_regex_escape_string (sep, -1);
535  char* escaped_newval = g_regex_escape_string (newval, -1);
536  gchar* newval_rep = g_strdup_printf (".*%s.*", escaped_sep);
537  GRegex* regex0 = g_regex_new (escaped_sep, 0, 0, NULL);
538  char* rep_str = g_regex_replace_literal (regex0, escaped_newval, -1, 0,
539  newval_rep, 0, NULL);
540  char* normal_rep_str = g_utf8_normalize (rep_str, -1, G_NORMALIZE_NFC);
541  GRegex *regex = g_regex_new (normal_rep_str, G_REGEX_CASELESS, 0, NULL);
542 
543  gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (full_store),
544  &iter);
545 
546  /* Limit the number found to keep the combo box from getting unreasonably
547  * large.
548  */
549  static const gint MAX_NUM_MATCHES = 30;
550 
551  g_free (normal_rep_str);
552  g_free (rep_str);
553  g_free (newval_rep);
554  g_free (escaped_sep);
555  g_free (escaped_newval);
556  g_regex_unref (regex0);
557 
558  block_list_signals (cell); //Prevent recursion from gtk_tree_view signals.
559  gnc_item_edit_hide_popup (box->item_edit);
560  gtk_list_store_clear (box->tmp_store);
561  unblock_list_signals (cell);
562 
563  if (strlen (newval) == 0) {
564  /* Deleting everything in the cell shouldn't provide a search result for
565  * "" because that will just be the first MAX_NUM_MATCHES accounts which
566  * isn't very useful.
567  *
568  * Skip the search show the popup again with all accounts. Clear the
569  * temp store or the cell will be pre-filled with the first account.
570  */
571  gnc_item_list_set_temp_store (box->item_list, NULL);
572  gnc_item_edit_show_popup (box->item_edit);
573  box->list_popped = TRUE;
574  goto cleanup;
575  }
576 
577  while (valid && num_found < MAX_NUM_MATCHES)
578  {
579  gchar* str_data = NULL;
580  gchar* normalized_str_data = NULL;
581  gtk_tree_model_get (GTK_TREE_MODEL (full_store), &iter, 0,
582  &str_data, -1);
583  normalized_str_data = g_utf8_normalize (str_data, -1, G_NORMALIZE_NFC);
584 
585  if (g_regex_match (regex, normalized_str_data, 0, NULL))
586  {
587  if (!num_found)
588  match_str = g_strdup (str_data);
589  ++num_found;
590  list_store_append (box->tmp_store, str_data);
591  }
592  g_free (str_data);
593  g_free (normalized_str_data);
594  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (full_store), &iter);
595  }
596 
597  if (num_found)
598  {
599  gnc_item_list_set_temp_store (box->item_list, box->tmp_store);
600  gnc_item_edit_show_popup (box->item_edit);
601  box->list_popped = TRUE;
602  }
603 
604 cleanup:
605  g_regex_unref (regex);
606  return match_str;
607 }
608 
609 static char*
610 quickfill_match (QuickFill *qf, const char *string)
611 {
612  QuickFill *match = gnc_quickfill_get_string_match (qf, string);
613  return g_strdup (gnc_quickfill_string (match));
614 }
615 
616 
617 static void
618 gnc_combo_cell_modify_verify (BasicCell* _cell,
619  const char* change,
620  int change_len,
621  const char* newval,
622  int newval_len,
623  int* cursor_position,
624  int* start_selection,
625  int* end_selection)
626 {
627  ComboCell* cell = (ComboCell*) _cell;
628  PopBox* box = cell->cell.gui_private;
629  gchar* match_str = NULL;
630  glong newval_chars;
631  glong change_chars;
632  const gchar* box_str = NULL;
633 
634  newval_chars = g_utf8_strlen (newval, newval_len);
635  change_chars = g_utf8_strlen (change, change_len);
636 
637  if (box->in_list_select)
638  {
639  gnc_basic_cell_set_value_internal (_cell, newval);
640  *cursor_position = -1;
641  *start_selection = 0;
642  *end_selection = -1;
643  return;
644  }
645 
646  /* If item_list is using temp then we're already partly matched by
647  * type-ahead and a quickfill_match won't work.
648  */
649  if (!gnc_item_list_using_temp (box->item_list))
650  {
651  // If we were deleting or inserting in the middle, just accept.
652  if (change == NULL || *cursor_position < _cell->value_chars)
653  {
654  gnc_basic_cell_set_value_internal (_cell, newval);
655  *start_selection = *end_selection = *cursor_position;
656  return;
657  }
658 
659  match_str = quickfill_match (box->qf, newval);
660 
661  if (match_str != NULL) // Do we have a quickfill match
662  {
663  *start_selection = newval_chars;
664  *end_selection = -1;
665  *cursor_position += change_chars;
666  box_str = match_str;
667 
668  block_list_signals (cell); // Prevent recursion
669  gnc_item_list_select (box->item_list, match_str);
670  unblock_list_signals (cell);
671  }
672  }
673 
674  // Try using type-ahead
675  if (match_str == NULL && cell->shared_store)
676  {
677  // No start-of-name match, try type-ahead search, we match any substring of the full account name.
678  GtkListStore *store = cell->shared_store;
679  match_str = gnc_combo_cell_type_ahead_search (newval, store, cell);
680  *start_selection = newval_chars;
681  *end_selection = -1;
682  *cursor_position = newval_chars;
683 
684  // Do not change the string in the type-in box.
685  box_str = newval;
686  }
687 
688  // No type-ahead / quickfill entry found
689  if (match_str == NULL)
690  {
691  block_list_signals (cell); // Prevent recursion
692  if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
693  {
694  gnc_item_list_set_temp_store (box->item_list, NULL);
695  gtk_list_store_clear (box->tmp_store);
696  }
697  gnc_item_list_select (box->item_list, NULL);
698  unblock_list_signals (cell);
699  gnc_basic_cell_set_value_internal (_cell, newval);
700  *cursor_position = *start_selection = newval_chars;
701  *end_selection = -1;
702  return;
703  }
704 
705  if (!box->list_popped && auto_pop_combos)
706  {
707  gnc_item_edit_show_popup (box->item_edit);
708  box->list_popped = TRUE;
709  }
710 
711  gnc_basic_cell_set_value_internal (_cell, box_str);
712  g_free (match_str);
713 }
714 
715 static gboolean
716 gnc_combo_cell_direct_update (BasicCell* bcell,
717  int* cursor_position,
718  int* start_selection,
719  int* end_selection,
720  void* gui_data)
721 {
722  ComboCell* cell = (ComboCell*) bcell;
723  PopBox* box = cell->cell.gui_private;
724  GdkEventKey* event = gui_data;
725  gboolean keep_on_going = FALSE;
726  gboolean extra_colon;
727  gunichar unicode_value;
728  QuickFill* match;
729  const char* match_str;
730  int prefix_len;
731  int find_pos;
732  int new_pos;
733 
734  if (event->type != GDK_KEY_PRESS)
735  return FALSE;
736 
737  unicode_value = gdk_keyval_to_unicode (event->keyval);
738  switch (event->keyval)
739  {
740  case GDK_KEY_slash:
741  if (! (event->state & GDK_MOD1_MASK))
742  {
743  if (unicode_value == box->complete_char)
744  break;
745 
746  return FALSE;
747  }
748  keep_on_going = TRUE;
749  /* fall through */
750  case GDK_KEY_Tab:
751  case GDK_KEY_ISO_Left_Tab:
752  if (gnc_item_list_using_temp (box->item_list))
753  {
754  char* string = gnc_item_list_get_selection (box->item_list);
755  g_signal_emit_by_name (G_OBJECT (box->item_list), "change_item",
756  string, (gpointer)bcell);
757  g_free (string);
758  return FALSE;
759  }
760  if (! (event->state & GDK_CONTROL_MASK) &&
761  !keep_on_going)
762  return FALSE;
763 
765  (box->qf, bcell->value, *cursor_position);
766  if (match == NULL)
767  return TRUE;
768 
770  (match, &prefix_len);
771  if (match == NULL)
772  return TRUE;
773 
774  match_str = gnc_quickfill_string (match);
775 
776  if ((match_str != NULL) &&
777  (strncmp (match_str, bcell->value,
778  strlen (bcell->value)) == 0) &&
779  (strcmp (match_str, bcell->value) != 0))
780  {
781  gnc_basic_cell_set_value_internal (bcell,
782  match_str);
783 
784  block_list_signals (cell);
785  gnc_item_list_select (box->item_list,
786  match_str);
787  unblock_list_signals (cell);
788  }
789 
790  *cursor_position += prefix_len;
791  *start_selection = *cursor_position;
792  *end_selection = -1;
793  return TRUE;
794  }
795 
796  if (box->complete_char == 0)
797  return FALSE;
798 
799  if (unicode_value != box->complete_char)
800  return FALSE;
801 
802  if (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))
803  return FALSE;
804 
805  if ((*cursor_position < bcell->value_chars) &&
806  ((*end_selection < bcell->value_chars) ||
807  (*cursor_position < *start_selection)))
808  return FALSE;
809 
810  if ((*cursor_position == bcell->value_chars) &&
811  (*start_selection != *end_selection) &&
812  (*end_selection < bcell->value_chars))
813  return FALSE;
814 
815  find_pos = -1;
816  if (*start_selection < bcell->value_chars)
817  {
818  int i = *start_selection;
819  const char* c;
820  gunichar uc;
821 
822  c = g_utf8_offset_to_pointer (bcell->value, i);
823  while (*c)
824  {
825  uc = g_utf8_get_char (c);
826  if (uc == box->complete_char)
827  {
828  find_pos = (i + 1);
829  break;
830  }
831  c = g_utf8_next_char (c);
832  i++;
833  }
834  }
835 
836  if (find_pos >= 0)
837  {
838  new_pos = find_pos;
839  extra_colon = FALSE;
840  }
841  else
842  {
843  new_pos = bcell->value_chars;
844  extra_colon = TRUE;
845  }
846 
847  match = gnc_quickfill_get_string_len_match (box->qf,
848  bcell->value, new_pos);
849  if (match == NULL)
850  return FALSE;
851 
852  if (extra_colon)
853  {
854  match = gnc_quickfill_get_char_match (match,
855  box->complete_char);
856  if (match == NULL)
857  return FALSE;
858 
859  new_pos++;
860  }
861 
862  match_str = gnc_quickfill_string (match);
863 
864  if ((match_str != NULL) &&
865  (strncmp (match_str, bcell->value, strlen (bcell->value)) == 0) &&
866  (strcmp (match_str, bcell->value) != 0))
867  {
868  gnc_basic_cell_set_value_internal (bcell, match_str);
869 
870  block_list_signals (cell);
871  gnc_item_list_select (box->item_list, match_str);
872  unblock_list_signals (cell);
873  }
874 
875  *cursor_position = new_pos;
876  *start_selection = new_pos;
877  *end_selection = -1;
878 
879  return TRUE;
880 }
881 
882 static void
883 gnc_combo_cell_gui_realize (BasicCell* bcell, gpointer data)
884 {
885  GnucashSheet* sheet = data;
886  GncItemEdit* item_edit = gnucash_sheet_get_item_edit (sheet);
887  ComboCell* cell = (ComboCell*) bcell;
888  PopBox* box = cell->cell.gui_private;
889 
890  /* initialize gui-specific, private data */
891  box->sheet = sheet;
892  box->item_edit = item_edit;
893  if (cell->shared_store)
894  box->item_list = GNC_ITEM_LIST (gnc_item_list_new (cell->shared_store));
895  else
896  box->item_list = GNC_ITEM_LIST (gnc_item_list_new (box->tmp_store));
897  gtk_widget_show_all (GTK_WIDGET (box->item_list));
898  g_object_ref_sink (box->item_list);
899 
900  /* to mark cell as realized, remove the realize method */
901  cell->cell.gui_realize = NULL;
902  cell->cell.gui_move = gnc_combo_cell_gui_move;
903  cell->cell.enter_cell = gnc_combo_cell_enter;
904  cell->cell.leave_cell = gnc_combo_cell_leave;
905  cell->cell.gui_destroy = gnc_combo_cell_gui_destroy;
906  cell->cell.modify_verify = gnc_combo_cell_modify_verify;
907  cell->cell.direct_update = gnc_combo_cell_direct_update;
908 }
909 
910 static void
911 gnc_combo_cell_gui_move (BasicCell* bcell)
912 {
913  PopBox* box = bcell->gui_private;
914 
915  combo_disconnect_signals ((ComboCell*) bcell);
916 
917  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
918  NULL, NULL, NULL, NULL, NULL);
919 
920  gnc_item_list_select (box->item_list, NULL);
921 
922  box->list_popped = FALSE;
923 }
924 
925 static int
926 popup_get_height (GtkWidget* widget,
927  int space_available,
928  G_GNUC_UNUSED int row_height,
929  gpointer user_data)
930 {
931  PopBox* box = user_data;
932  GtkScrolledWindow* scrollwin = GNC_ITEM_LIST(widget)->scrollwin;
933  int height;
934 
935  // if popup_allocation_height set use that
936  if (box->item_edit->popup_allocation_height != -1)
937  height = box->item_edit->popup_allocation_height;
938  else
939  height = gnc_item_list_get_popup_height (GNC_ITEM_LIST(widget));
940 
941  if (height < space_available)
942  {
943  // if the list is empty height would be 0 so return 1 instead to
944  // satisfy the check_popup_height_is_true function
945  gint ret_height = height ? height : 1;
946 
947  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, ret_height);
948  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
949  GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
950  return ret_height;
951  }
952  else
953  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, -1);
954 
955  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
956  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
957  return space_available;
958 }
959 
960 static int
961 popup_autosize (GtkWidget* widget,
962  int max_width,
963  gpointer user_data)
964 {
965  PopBox* box = user_data;
966 
967  if (!box || !box->autosize)
968  return max_width;
969 
970  return gnc_item_list_autosize (GNC_ITEM_LIST (widget)) + 20;
971 }
972 
973 static void
974 popup_set_focus (GtkWidget* widget,
975  G_GNUC_UNUSED gpointer user_data)
976 {
977  /* An empty GtkTreeView grabbing focus causes the key_press events to be
978  * lost because there's no entry cell to handle them.
979  */
980  if (gnc_item_list_num_entries (GNC_ITEM_LIST (widget)))
981  gtk_widget_grab_focus (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view));
982 }
983 
984 static void
985 popup_post_show (GtkWidget* widget,
986  G_GNUC_UNUSED gpointer user_data)
987 {
988  gnc_item_list_autosize (GNC_ITEM_LIST (widget));
989  gnc_item_list_show_selected (GNC_ITEM_LIST (widget));
990 }
991 
992 static int
993 popup_get_width (GtkWidget* widget,
994  G_GNUC_UNUSED gpointer user_data)
995 {
996  GtkAllocation alloc;
997  gtk_widget_get_allocation (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view),
998  &alloc);
999  return alloc.width;
1000 }
1001 
1002 static gboolean
1003 gnc_combo_cell_enter (BasicCell* bcell,
1004  int* cursor_position,
1005  int* start_selection,
1006  int* end_selection)
1007 {
1008  ComboCell* cell = (ComboCell*) bcell;
1009  PopBox* box = bcell->gui_private;
1010  PopupToggle popup_toggle;
1011  GList* find = NULL;
1012 
1013  if (bcell->value)
1014  find = g_list_find_custom (box->ignore_strings,
1015  bcell->value,
1016  (GCompareFunc) strcmp);
1017  if (find)
1018  return FALSE;
1019 
1020  gnc_item_edit_set_popup (box->item_edit,
1021  GTK_WIDGET (box->item_list),
1022  popup_get_height, popup_autosize,
1023  popup_set_focus, popup_post_show,
1024  popup_get_width, box);
1025 
1026  block_list_signals (cell);
1027 
1028  if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
1029  {
1030  // Clear the temp store to ensure we don't start in type-ahead mode.
1031  gnc_item_list_set_temp_store (box->item_list, NULL);
1032  gtk_list_store_clear (box->tmp_store);
1033  }
1034  gnc_item_list_select (box->item_list, bcell->value);
1035  unblock_list_signals (cell);
1036 
1037  popup_toggle = box->item_edit->popup_toggle;
1038 
1039  // if the list is empty disable the toggle button
1040  gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton),
1041  gnc_item_list_num_entries (box->item_list));
1042 
1043  combo_connect_signals (cell);
1044 
1045  *cursor_position = -1;
1046  *start_selection = 0;
1047  *end_selection = -1;
1048 
1049  return TRUE;
1050 }
1051 
1052 static void
1053 gnc_combo_cell_leave (BasicCell* bcell)
1054 {
1055  PopBox* box = bcell->gui_private;
1056 
1057  combo_disconnect_signals ((ComboCell*) bcell);
1058 
1059  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
1060  NULL, NULL, NULL, NULL, NULL);
1061 
1062  box->list_popped = FALSE;
1063 
1064  if (box->strict)
1065  {
1066  if (bcell->value)
1067  {
1068  if (!bcell->changed)
1069  return;
1070 
1071  if (gnc_item_in_list (box->item_list, bcell->value))
1072  return;
1073 
1074  if (g_list_find_custom (box->ignore_strings,
1075  bcell->value,
1076  (GCompareFunc) strcmp))
1077  return;
1078  }
1079  gnc_basic_cell_set_value_internal (bcell, "");
1080  }
1081 }
1082 
1083 void
1084 gnc_combo_cell_set_strict (ComboCell* cell, gboolean strict)
1085 {
1086  PopBox* box;
1087 
1088  if (cell == NULL)
1089  return;
1090 
1091  box = cell->cell.gui_private;
1092 
1093  box->strict = strict;
1094 }
1095 
1096 void
1097 gnc_combo_cell_set_complete_char (ComboCell* cell, gunichar complete_char)
1098 {
1099  PopBox* box;
1100 
1101  if (cell == NULL)
1102  return;
1103 
1104  box = cell->cell.gui_private;
1105 
1106  box->complete_char = complete_char;
1107 }
1108 
1109 void
1111  const char* ignore_string)
1112 {
1113  PopBox* box;
1114 
1115  if (cell == NULL)
1116  return;
1117 
1118  if (!ignore_string)
1119  return;
1120 
1121  box = cell->cell.gui_private;
1122 
1123  box->ignore_strings = g_list_prepend (box->ignore_strings,
1124  g_strdup (ignore_string));
1125 }
1126 
1127 void
1128 gnc_combo_cell_set_autosize (ComboCell* cell, gboolean autosize)
1129 {
1130  PopBox* box;
1131 
1132  if (!cell)
1133  return;
1134 
1135  box = cell->cell.gui_private;
1136  if (!box)
1137  return;
1138 
1139  box->autosize = autosize;
1140 }
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
QuickFill * gnc_quickfill_get_char_match(QuickFill *qf, gunichar uc)
Return the subnode of the tree whose strings all hold &#39;c&#39; as the next letter.
Definition: QuickFill.c:135
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.cpp:127
void gnc_combo_cell_set_sort_enabled(ComboCell *cell, gboolean enabled)
Enable sorting of the menu item&#39;s contents.
void gnc_combo_cell_add_ignore_string(ComboCell *cell, const char *ignore_string)
Add a string to a list of strings which, if the cell has that value, will cause the cell to be unedit...
void gnc_combo_cell_set_complete_char(ComboCell *cell, gunichar complete_char)
Sets a character used for special completion processing.
void gnc_combo_cell_add_account_menu_item(ComboCell *cell, char *menustr)
Add a &#39;account name&#39; menu item to the list.
Account handling public routines.
The ComboCell object implements a cell handler with a "combination-box" pull-down menu in it...
Definition: combocell.h:52
QuickFill * gnc_quickfill_get_string_len_match(QuickFill *qf, const char *str, int len)
Same as gnc_quickfill_get_string_match(), except that the string length is explicitly specified...
Definition: QuickFill.c:150
void gnc_combo_cell_add_menu_item(ComboCell *cell, const char *menustr)
Add a menu item to the list.
Public Declarations for GncItemList class.
void gnc_combo_cell_set_strict(ComboCell *cell, gboolean strict)
Determines whether the cell will accept strings not in the menu.
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
Public declarations of GnucashRegister class.
Private declarations for GnucashSheet class.
GLib helper routines.
Generic api to store and retrieve preferences.
const char * gnc_quickfill_string(QuickFill *qf)
For the given node &#39;qf&#39;, return the best-guess matching string.
Definition: QuickFill.c:123
Public declarations for GncItemEdit class.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
Declarations for the Table object.
QuickFill is used to auto-complete typed user entries.
void gnc_combo_cell_use_quickfill_cache(ComboCell *cell, QuickFill *shared_qf)
Tell the combocell to use a shared QuickFill object.
QuickFill * gnc_quickfill_get_unique_len_match(QuickFill *qf, int *length)
Walk a &#39;unique&#39; part of the QuickFill tree.
Definition: QuickFill.c:199
void gnc_combo_cell_set_autosize(ComboCell *cell, gboolean autosize)
Determines whether the popup list autosizes itself or uses all available space.
char * gnc_item_list_get_selection(GncItemList *item_list)
Retrieve the selected string from the item_list&#39;s active GtkListStore.
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition: Account.cpp:205