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 void
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 }
224 
225 static void
226 combo_disconnect_signals (ComboCell* cell)
227 {
228  PopBox* box = cell->cell.gui_private;
229 
230  if (!box->signals_connected)
231  return;
232 
233  g_signal_handlers_disconnect_matched (G_OBJECT (box->item_list),
234  G_SIGNAL_MATCH_DATA,
235  0, 0, NULL, NULL, cell);
236 
237  box->signals_connected = FALSE;
238 }
239 
240 static void
241 combo_connect_signals (ComboCell* cell)
242 {
243  PopBox* box = cell->cell.gui_private;
244 
245  if (box->signals_connected)
246  return;
247 
248  g_signal_connect (G_OBJECT (box->item_list), "select_item",
249  G_CALLBACK (select_item_cb), cell);
250 
251  g_signal_connect (G_OBJECT (box->item_list), "change_item",
252  G_CALLBACK (change_item_cb), cell);
253 
254  g_signal_connect (G_OBJECT (box->item_list), "activate_item",
255  G_CALLBACK (activate_item_cb), cell);
256 
257  g_signal_connect (G_OBJECT (box->item_list), "key_press_event",
258  G_CALLBACK (key_press_item_cb), cell);
259 
260  box->signals_connected = TRUE;
261 }
262 
263 static void
264 block_list_signals (ComboCell* cell)
265 {
266  PopBox* box = cell->cell.gui_private;
267 
268  if (!box->signals_connected)
269  return;
270 
271  g_signal_handlers_block_matched (G_OBJECT (box->item_list),
272  G_SIGNAL_MATCH_DATA,
273  0, 0, NULL, NULL, cell);
274 }
275 
276 static void
277 unblock_list_signals (ComboCell* cell)
278 {
279  PopBox* box = cell->cell.gui_private;
280 
281  if (!box->signals_connected)
282  return;
283 
284  g_signal_handlers_unblock_matched (G_OBJECT (box->item_list),
285  G_SIGNAL_MATCH_DATA,
286  0, 0, NULL, NULL, cell);
287 }
288 
289 static void
290 gnc_combo_cell_gui_destroy (BasicCell* bcell)
291 {
292  PopBox* box = bcell->gui_private;
293  ComboCell* cell = (ComboCell*) bcell;
294 
295  if (cell->cell.gui_realize == NULL)
296  {
297  if (box != NULL && box->item_list != NULL)
298  {
299  combo_disconnect_signals (cell);
300  g_object_unref (box->item_list);
301  box->item_list = NULL;
302  }
303 
304  if (box && box->tmp_store)
305  {
306  g_object_unref (box->tmp_store);
307  box->tmp_store = NULL;
308  }
309 
310  /* allow the widget to be shown again */
311  cell->cell.gui_realize = gnc_combo_cell_gui_realize;
312  cell->cell.gui_move = NULL;
313  cell->cell.enter_cell = NULL;
314  cell->cell.leave_cell = NULL;
315  cell->cell.gui_destroy = NULL;
316  }
317 }
318 
319 static void
320 gnc_combo_cell_destroy (BasicCell* bcell)
321 {
322  ComboCell* cell = (ComboCell*) bcell;
323  PopBox* box = cell->cell.gui_private;
324 
325  gnc_combo_cell_gui_destroy (& (cell->cell));
326 
327  if (box != NULL)
328  {
329  /* Don't destroy the qf if its not ours to destroy */
330  if (FALSE == box->use_quickfill_cache)
331  {
332  gnc_quickfill_destroy (box->qf);
333  box->qf = NULL;
334  }
335 
336  g_list_free_full (box->ignore_strings, g_free);
337  box->ignore_strings = NULL;
338 
339  g_free (box);
340  cell->cell.gui_private = NULL;
341  }
342 
343  cell->cell.gui_private = NULL;
344  cell->cell.gui_realize = NULL;
345 }
346 
347 void
349 {
350  PopBox* box;
351 
352  if (cell == NULL)
353  return;
354 
355  box = cell->cell.gui_private;
356  if (box->item_list == NULL)
357  return;
358 
359  block_list_signals (cell);
360  gnc_item_list_set_sort_column (box->item_list, 0);
361  unblock_list_signals (cell);
362 }
363 
364 void
365 gnc_combo_cell_clear_menu (ComboCell* cell)
366 {
367  PopBox* box;
368 
369  if (cell == NULL)
370  return;
371 
372  box = cell->cell.gui_private;
373  if (box == NULL)
374  return;
375 
376  /* Don't destroy the qf if its not ours to destroy */
377  if (FALSE == box->use_quickfill_cache)
378  {
379  gnc_quickfill_destroy (box->qf);
380  box->qf = gnc_quickfill_new();
381  }
382 
383  if (box->item_list != NULL)
384  {
385  block_list_signals (cell);
386 
387  gnc_item_list_clear (box->item_list);
388  gnc_item_edit_hide_popup (box->item_edit);
389  box->list_popped = FALSE;
390  unblock_list_signals (cell);
391  }
392  else
393  gtk_list_store_clear (box->tmp_store);
394 }
395 
396 void
397 gnc_combo_cell_use_quickfill_cache (ComboCell* cell, QuickFill* shared_qf)
398 {
399  PopBox* box;
400 
401  if (cell == NULL) return;
402 
403  box = cell->cell.gui_private;
404  if (NULL == box) return;
405 
406  if (FALSE == box->use_quickfill_cache)
407  {
408  box->use_quickfill_cache = TRUE;
409  gnc_quickfill_destroy (box->qf);
410  }
411  box->qf = shared_qf;
412 }
413 
414 void
415 gnc_combo_cell_use_list_store_cache (ComboCell* cell, gpointer data)
416 {
417  if (cell == NULL) return;
418 
419  cell->shared_store = data;
420 }
421 
422 void
423 gnc_combo_cell_add_menu_item (ComboCell* cell, const char* menustr)
424 {
425  PopBox* box;
426 
427  if (cell == NULL)
428  return;
429  if (menustr == NULL)
430  return;
431 
432  box = cell->cell.gui_private;
433 
434  if (box->item_list != NULL)
435  {
436  block_list_signals (cell);
437 
438  gnc_item_list_append (box->item_list, menustr);
439  if (cell->cell.value &&
440  (strcmp (menustr, cell->cell.value) == 0))
441  gnc_item_list_select (box->item_list, menustr);
442 
443  unblock_list_signals (cell);
444  }
445  else
446  {
447  GtkTreeIter iter;
448 
449  gtk_list_store_append (box->tmp_store, &iter);
450  gtk_list_store_set (box->tmp_store, &iter, 0, menustr, -1);
451  }
452 
453  /* If we're going to be using a pre-fab quickfill,
454  * then don't fill it in here */
455  if (FALSE == box->use_quickfill_cache)
456  {
457  gnc_quickfill_insert (box->qf, menustr, QUICKFILL_ALPHA);
458  }
459 }
460 
461 void
463 {
464  PopBox* box;
465 
466  if (cell == NULL)
467  return;
468  if (menustr == NULL)
469  return;
470 
471  box = cell->cell.gui_private;
472 
473  if (box->item_list != NULL)
474  {
475  block_list_signals (cell);
476 
477  gnc_item_list_append (box->item_list, menustr);
478  if (cell->cell.value)
479  {
480  gchar* menu_copy = g_strdup (menustr);
481  gchar* value_copy = g_strdup (cell->cell.value);
482  g_strdelimit (menu_copy, "-:/\\.", ' ');
483  g_strdelimit (value_copy, "-:/\\.", ' ');
484  if (strcmp (menu_copy, value_copy) == 0)
485  {
486  gnc_combo_cell_set_value (cell, menustr);
487  gnc_item_list_select (box->item_list, menustr);
488  }
489  g_free (value_copy);
490  g_free (menu_copy);
491  }
492  unblock_list_signals (cell);
493  }
494 
495  /* If we're going to be using a pre-fab quickfill,
496  * then don't fill it in here */
497  if (FALSE == box->use_quickfill_cache)
498  {
499  gnc_quickfill_insert (box->qf, menustr, QUICKFILL_ALPHA);
500  }
501 }
502 
503 void
504 gnc_combo_cell_set_value (ComboCell* cell, const char* str)
505 {
506  gnc_basic_cell_set_value (&cell->cell, str);
507 }
508 
509 static inline void
510 list_store_append (GtkListStore *store, char* string)
511 {
512  GtkTreeIter iter;
513 
514  g_return_if_fail (store != NULL);
515  g_return_if_fail (string != NULL);
516  gtk_list_store_append (store, &iter);
517  gtk_list_store_set (store, &iter, 0, string, -1);
518 }
519 
520 /* This function looks through full_store for a partial match with newval and
521  * returns the first match (which must be subsequently freed). It fills out
522  * box->item_list with found matches.
523  */
524 static gchar*
525 gnc_combo_cell_type_ahead_search (const gchar* newval,
526  GtkListStore* full_store, ComboCell *cell)
527 {
528  GtkTreeIter iter;
529  PopBox* box = cell->cell.gui_private;
530  int num_found = 0;
531  gchar* match_str = NULL;
532  const char* sep = gnc_get_account_separator_string ();
533  char* escaped_sep = g_regex_escape_string (sep, -1);
534  char* escaped_newval = g_regex_escape_string (newval, -1);
535  gchar* newval_rep = g_strdup_printf (".*%s.*", escaped_sep);
536  GRegex* regex0 = g_regex_new (escaped_sep, 0, 0, NULL);
537  char* rep_str = g_regex_replace_literal (regex0, escaped_newval, -1, 0,
538  newval_rep, 0, NULL);
539  char* normal_rep_str = g_utf8_normalize (rep_str, -1, G_NORMALIZE_NFC);
540  GRegex *regex = g_regex_new (normal_rep_str, G_REGEX_CASELESS, 0, NULL);
541 
542  gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (full_store),
543  &iter);
544 
545  /* Limit the number found to keep the combo box from getting unreasonably
546  * large.
547  */
548  static const gint MAX_NUM_MATCHES = 30;
549 
550  g_free (normal_rep_str);
551  g_free (rep_str);
552  g_free (newval_rep);
553  g_free (escaped_sep);
554  g_free (escaped_newval);
555  g_regex_unref (regex0);
556 
557  block_list_signals (cell); //Prevent recursion from gtk_tree_view signals.
558  gnc_item_edit_hide_popup (box->item_edit);
559  gtk_list_store_clear (box->tmp_store);
560  unblock_list_signals (cell);
561 
562  if (strlen (newval) == 0) {
563  /* Deleting everything in the cell shouldn't provide a search result for
564  * "" because that will just be the first MAX_NUM_MATCHES accounts which
565  * isn't very useful.
566  *
567  * Skip the search show the popup again with all accounts. Clear the
568  * temp store or the cell will be pre-filled with the first account.
569  */
570  gnc_item_list_set_temp_store (box->item_list, NULL);
571  gnc_item_edit_show_popup (box->item_edit);
572  box->list_popped = TRUE;
573  goto cleanup;
574  }
575 
576  while (valid && num_found < MAX_NUM_MATCHES)
577  {
578  gchar* str_data = NULL;
579  gchar* normalized_str_data = NULL;
580  gtk_tree_model_get (GTK_TREE_MODEL (full_store), &iter, 0,
581  &str_data, -1);
582  normalized_str_data = g_utf8_normalize (str_data, -1, G_NORMALIZE_NFC);
583 
584  if (g_regex_match (regex, normalized_str_data, 0, NULL))
585  {
586  if (!num_found)
587  match_str = g_strdup (str_data);
588  ++num_found;
589  list_store_append (box->tmp_store, str_data);
590  }
591  g_free (str_data);
592  g_free (normalized_str_data);
593  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (full_store), &iter);
594  }
595 
596  if (num_found)
597  {
598  gnc_item_list_set_temp_store (box->item_list, box->tmp_store);
599  gnc_item_edit_show_popup (box->item_edit);
600  box->list_popped = TRUE;
601  }
602 
603 cleanup:
604  g_regex_unref (regex);
605  return match_str;
606 }
607 
608 static char*
609 quickfill_match (QuickFill *qf, const char *string)
610 {
611  QuickFill *match = gnc_quickfill_get_string_match (qf, string);
612  return g_strdup (gnc_quickfill_string (match));
613 }
614 
615 
616 static void
617 gnc_combo_cell_modify_verify (BasicCell* _cell,
618  const char* change,
619  int change_len,
620  const char* newval,
621  int newval_len,
622  int* cursor_position,
623  int* start_selection,
624  int* end_selection)
625 {
626  ComboCell* cell = (ComboCell*) _cell;
627  PopBox* box = cell->cell.gui_private;
628  gchar* match_str = NULL;
629  glong newval_chars;
630  glong change_chars;
631  const gchar* box_str = NULL;
632 
633  newval_chars = g_utf8_strlen (newval, newval_len);
634  change_chars = g_utf8_strlen (change, change_len);
635 
636  if (box->in_list_select)
637  {
638  gnc_basic_cell_set_value_internal (_cell, newval);
639  *cursor_position = -1;
640  *start_selection = 0;
641  *end_selection = -1;
642  return;
643  }
644 
645  /* If item_list is using temp then we're already partly matched by
646  * type-ahead and a quickfill_match won't work.
647  */
648  if (!gnc_item_list_using_temp (box->item_list))
649  {
650  // If we were deleting or inserting in the middle, just accept.
651  if (change == NULL || *cursor_position < _cell->value_chars)
652  {
653  gnc_basic_cell_set_value_internal (_cell, newval);
654  *start_selection = *end_selection = *cursor_position;
655  return;
656  }
657 
658  match_str = quickfill_match (box->qf, newval);
659 
660  if (match_str != NULL) // Do we have a quickfill match
661  {
662  *start_selection = newval_chars;
663  *end_selection = -1;
664  *cursor_position += change_chars;
665  box_str = match_str;
666 
667  block_list_signals (cell); // Prevent recursion
668  gnc_item_list_select (box->item_list, match_str);
669  unblock_list_signals (cell);
670  }
671  }
672 
673  // Try using type-ahead
674  if (match_str == NULL && cell->shared_store)
675  {
676  // No start-of-name match, try type-ahead search, we match any substring of the full account name.
677  GtkListStore *store = cell->shared_store;
678  match_str = gnc_combo_cell_type_ahead_search (newval, store, cell);
679  *start_selection = newval_chars;
680  *end_selection = -1;
681  *cursor_position = newval_chars;
682 
683  // Do not change the string in the type-in box.
684  box_str = newval;
685  }
686 
687  // No type-ahead / quickfill entry found
688  if (match_str == NULL)
689  {
690  block_list_signals (cell); // Prevent recursion
691  if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
692  {
693  gnc_item_list_set_temp_store (box->item_list, NULL);
694  gtk_list_store_clear (box->tmp_store);
695  }
696  gnc_item_list_select (box->item_list, NULL);
697  unblock_list_signals (cell);
698  gnc_basic_cell_set_value_internal (_cell, newval);
699  *cursor_position = *start_selection = newval_chars;
700  *end_selection = -1;
701  return;
702  }
703 
704  if (!box->list_popped && auto_pop_combos)
705  {
706  gnc_item_edit_show_popup (box->item_edit);
707  box->list_popped = TRUE;
708  }
709 
710  gnc_basic_cell_set_value_internal (_cell, box_str);
711  g_free (match_str);
712 }
713 
714 static gboolean
715 gnc_combo_cell_direct_update (BasicCell* bcell,
716  int* cursor_position,
717  int* start_selection,
718  int* end_selection,
719  void* gui_data)
720 {
721  ComboCell* cell = (ComboCell*) bcell;
722  PopBox* box = cell->cell.gui_private;
723  GdkEventKey* event = gui_data;
724  gboolean keep_on_going = FALSE;
725  gboolean extra_colon;
726  gunichar unicode_value;
727  QuickFill* match;
728  const char* match_str;
729  int prefix_len;
730  int find_pos;
731  int new_pos;
732 
733  if (event->type != GDK_KEY_PRESS)
734  return FALSE;
735 
736  unicode_value = gdk_keyval_to_unicode (event->keyval);
737  switch (event->keyval)
738  {
739  case GDK_KEY_slash:
740  if (! (event->state & GDK_MOD1_MASK))
741  {
742  if (unicode_value == box->complete_char)
743  break;
744 
745  return FALSE;
746  }
747  keep_on_going = TRUE;
748  /* fall through */
749  case GDK_KEY_Tab:
750  case GDK_KEY_ISO_Left_Tab:
751  if (gnc_item_list_using_temp (box->item_list))
752  {
753  char* string = gnc_item_list_get_selection (box->item_list);
754  g_signal_emit_by_name (G_OBJECT (box->item_list), "change_item",
755  string, (gpointer)bcell);
756  g_free (string);
757  return FALSE;
758  }
759  if (! (event->state & GDK_CONTROL_MASK) &&
760  !keep_on_going)
761  return FALSE;
762 
764  (box->qf, bcell->value, *cursor_position);
765  if (match == NULL)
766  return TRUE;
767 
769  (match, &prefix_len);
770  if (match == NULL)
771  return TRUE;
772 
773  match_str = gnc_quickfill_string (match);
774 
775  if ((match_str != NULL) &&
776  (strncmp (match_str, bcell->value,
777  strlen (bcell->value)) == 0) &&
778  (strcmp (match_str, bcell->value) != 0))
779  {
780  gnc_basic_cell_set_value_internal (bcell,
781  match_str);
782 
783  block_list_signals (cell);
784  gnc_item_list_select (box->item_list,
785  match_str);
786  unblock_list_signals (cell);
787  }
788 
789  *cursor_position += prefix_len;
790  *start_selection = *cursor_position;
791  *end_selection = -1;
792  return TRUE;
793  }
794 
795  if (box->complete_char == 0)
796  return FALSE;
797 
798  if (unicode_value != box->complete_char)
799  return FALSE;
800 
801  if (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))
802  return FALSE;
803 
804  if ((*cursor_position < bcell->value_chars) &&
805  ((*end_selection < bcell->value_chars) ||
806  (*cursor_position < *start_selection)))
807  return FALSE;
808 
809  if ((*cursor_position == bcell->value_chars) &&
810  (*start_selection != *end_selection) &&
811  (*end_selection < bcell->value_chars))
812  return FALSE;
813 
814  find_pos = -1;
815  if (*start_selection < bcell->value_chars)
816  {
817  int i = *start_selection;
818  const char* c;
819  gunichar uc;
820 
821  c = g_utf8_offset_to_pointer (bcell->value, i);
822  while (*c)
823  {
824  uc = g_utf8_get_char (c);
825  if (uc == box->complete_char)
826  {
827  find_pos = (i + 1);
828  break;
829  }
830  c = g_utf8_next_char (c);
831  i++;
832  }
833  }
834 
835  if (find_pos >= 0)
836  {
837  new_pos = find_pos;
838  extra_colon = FALSE;
839  }
840  else
841  {
842  new_pos = bcell->value_chars;
843  extra_colon = TRUE;
844  }
845 
846  match = gnc_quickfill_get_string_len_match (box->qf,
847  bcell->value, new_pos);
848  if (match == NULL)
849  return FALSE;
850 
851  if (extra_colon)
852  {
853  match = gnc_quickfill_get_char_match (match,
854  box->complete_char);
855  if (match == NULL)
856  return FALSE;
857 
858  new_pos++;
859  }
860 
861  match_str = gnc_quickfill_string (match);
862 
863  if ((match_str != NULL) &&
864  (strncmp (match_str, bcell->value, strlen (bcell->value)) == 0) &&
865  (strcmp (match_str, bcell->value) != 0))
866  {
867  gnc_basic_cell_set_value_internal (bcell, match_str);
868 
869  block_list_signals (cell);
870  gnc_item_list_select (box->item_list, match_str);
871  unblock_list_signals (cell);
872  }
873 
874  *cursor_position = new_pos;
875  *start_selection = new_pos;
876  *end_selection = -1;
877 
878  return TRUE;
879 }
880 
881 static void
882 gnc_combo_cell_gui_realize (BasicCell* bcell, gpointer data)
883 {
884  GnucashSheet* sheet = data;
885  GncItemEdit* item_edit = gnucash_sheet_get_item_edit (sheet);
886  ComboCell* cell = (ComboCell*) bcell;
887  PopBox* box = cell->cell.gui_private;
888 
889  /* initialize gui-specific, private data */
890  box->sheet = sheet;
891  box->item_edit = item_edit;
892  if (cell->shared_store)
893  box->item_list = GNC_ITEM_LIST (gnc_item_list_new (cell->shared_store));
894  else
895  box->item_list = GNC_ITEM_LIST (gnc_item_list_new (box->tmp_store));
896  gtk_widget_show_all (GTK_WIDGET (box->item_list));
897  g_object_ref_sink (box->item_list);
898 
899  /* to mark cell as realized, remove the realize method */
900  cell->cell.gui_realize = NULL;
901  cell->cell.gui_move = gnc_combo_cell_gui_move;
902  cell->cell.enter_cell = gnc_combo_cell_enter;
903  cell->cell.leave_cell = gnc_combo_cell_leave;
904  cell->cell.gui_destroy = gnc_combo_cell_gui_destroy;
905  cell->cell.modify_verify = gnc_combo_cell_modify_verify;
906  cell->cell.direct_update = gnc_combo_cell_direct_update;
907 }
908 
909 static void
910 gnc_combo_cell_gui_move (BasicCell* bcell)
911 {
912  PopBox* box = bcell->gui_private;
913 
914  combo_disconnect_signals ((ComboCell*) bcell);
915 
916  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
917  NULL, NULL, NULL, NULL, NULL);
918 
919  box->list_popped = FALSE;
920 }
921 
922 static int
923 popup_get_height (GtkWidget* widget,
924  int space_available,
925  G_GNUC_UNUSED int row_height,
926  gpointer user_data)
927 {
928  PopBox* box = user_data;
929  GtkScrolledWindow* scrollwin = GNC_ITEM_LIST(widget)->scrollwin;
930  int height;
931 
932  // if popup_allocation_height set use that
933  if (box->item_edit->popup_allocation_height != -1)
934  height = box->item_edit->popup_allocation_height;
935  else
936  height = gnc_item_list_get_popup_height (GNC_ITEM_LIST(widget));
937 
938  if (height < space_available)
939  {
940  // if the list is empty height would be 0 so return 1 instead to
941  // satisfy the check_popup_height_is_true function
942  gint ret_height = height ? height : 1;
943 
944  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, ret_height);
945  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
946  GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
947  return ret_height;
948  }
949  else
950  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, -1);
951 
952  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
953  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
954  return space_available;
955 }
956 
957 static int
958 popup_autosize (GtkWidget* widget,
959  int max_width,
960  gpointer user_data)
961 {
962  PopBox* box = user_data;
963 
964  if (!box || !box->autosize)
965  return max_width;
966 
967  return gnc_item_list_autosize (GNC_ITEM_LIST (widget)) + 20;
968 }
969 
970 static void
971 popup_set_focus (GtkWidget* widget,
972  G_GNUC_UNUSED gpointer user_data)
973 {
974  /* An empty GtkTreeView grabbing focus causes the key_press events to be
975  * lost because there's no entry cell to handle them.
976  */
977  if (gnc_item_list_num_entries (GNC_ITEM_LIST (widget)))
978  gtk_widget_grab_focus (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view));
979 }
980 
981 static void
982 popup_post_show (GtkWidget* widget,
983  G_GNUC_UNUSED gpointer user_data)
984 {
985  gnc_item_list_autosize (GNC_ITEM_LIST (widget));
986  gnc_item_list_show_selected (GNC_ITEM_LIST (widget));
987 }
988 
989 static int
990 popup_get_width (GtkWidget* widget,
991  G_GNUC_UNUSED gpointer user_data)
992 {
993  GtkAllocation alloc;
994  gtk_widget_get_allocation (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view),
995  &alloc);
996  return alloc.width;
997 }
998 
999 static gboolean
1000 gnc_combo_cell_enter (BasicCell* bcell,
1001  int* cursor_position,
1002  int* start_selection,
1003  int* end_selection)
1004 {
1005  ComboCell* cell = (ComboCell*) bcell;
1006  PopBox* box = bcell->gui_private;
1007  PopupToggle popup_toggle;
1008  GList* find = NULL;
1009 
1010  if (bcell->value)
1011  find = g_list_find_custom (box->ignore_strings,
1012  bcell->value,
1013  (GCompareFunc) strcmp);
1014  if (find)
1015  return FALSE;
1016 
1017  gnc_item_edit_set_popup (box->item_edit,
1018  GTK_WIDGET (box->item_list),
1019  popup_get_height, popup_autosize,
1020  popup_set_focus, popup_post_show,
1021  popup_get_width, box);
1022 
1023  block_list_signals (cell);
1024 
1025  if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
1026  {
1027  // Clear the temp store to ensure we don't start in type-ahead mode.
1028  gnc_item_list_set_temp_store (box->item_list, NULL);
1029  gtk_list_store_clear (box->tmp_store);
1030  }
1031  gnc_item_list_select (box->item_list, bcell->value);
1032  unblock_list_signals (cell);
1033 
1034  popup_toggle = box->item_edit->popup_toggle;
1035 
1036  // if the list is empty disable the toggle button
1037  gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton),
1038  gnc_item_list_num_entries (box->item_list));
1039 
1040  combo_connect_signals (cell);
1041 
1042  *cursor_position = -1;
1043  *start_selection = 0;
1044  *end_selection = -1;
1045 
1046  return TRUE;
1047 }
1048 
1049 static void
1050 gnc_combo_cell_leave (BasicCell* bcell)
1051 {
1052  PopBox* box = bcell->gui_private;
1053 
1054  combo_disconnect_signals ((ComboCell*) bcell);
1055 
1056  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
1057  NULL, NULL, NULL, NULL, NULL);
1058 
1059  box->list_popped = FALSE;
1060 
1061  if (box->strict)
1062  {
1063  if (bcell->value)
1064  {
1065  if (gnc_item_in_list (box->item_list, bcell->value))
1066  return;
1067 
1068  if (g_list_find_custom (box->ignore_strings,
1069  bcell->value,
1070  (GCompareFunc) strcmp))
1071  return;
1072  }
1073  gnc_basic_cell_set_value_internal (bcell, "");
1074  }
1075 }
1076 
1077 void
1078 gnc_combo_cell_set_strict (ComboCell* cell, gboolean strict)
1079 {
1080  PopBox* box;
1081 
1082  if (cell == NULL)
1083  return;
1084 
1085  box = cell->cell.gui_private;
1086 
1087  box->strict = strict;
1088 }
1089 
1090 void
1091 gnc_combo_cell_set_complete_char (ComboCell* cell, gunichar complete_char)
1092 {
1093  PopBox* box;
1094 
1095  if (cell == NULL)
1096  return;
1097 
1098  box = cell->cell.gui_private;
1099 
1100  box->complete_char = complete_char;
1101 }
1102 
1103 void
1105  const char* ignore_string)
1106 {
1107  PopBox* box;
1108 
1109  if (cell == NULL)
1110  return;
1111 
1112  if (!ignore_string)
1113  return;
1114 
1115  box = cell->cell.gui_private;
1116 
1117  box->ignore_strings = g_list_prepend (box->ignore_strings,
1118  g_strdup (ignore_string));
1119 }
1120 
1121 void
1122 gnc_combo_cell_set_autosize (ComboCell* cell, gboolean autosize)
1123 {
1124  PopBox* box;
1125 
1126  if (!cell)
1127  return;
1128 
1129  box = cell->cell.gui_private;
1130  if (!box)
1131  return;
1132 
1133  box->autosize = autosize;
1134 }
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.c:128
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:203