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  {
565  /* Deleting everything in the cell shouldn't provide a search result for
566  * "" because that will just be the first MAX_NUM_MATCHES accounts which
567  * isn't very useful.
568  *
569  * Skip the search show the popup again with all accounts. Clear the
570  * temp store or the cell will be pre-filled with the first account.
571  */
572  gnc_item_list_set_temp_store (box->item_list, NULL);
573  gnc_item_edit_show_popup (box->item_edit);
574  box->list_popped = TRUE;
575  goto cleanup;
576  }
577 
578  while (valid && num_found < MAX_NUM_MATCHES)
579  {
580  gchar* str_data = NULL;
581  gchar* normalized_str_data = NULL;
582  gtk_tree_model_get (GTK_TREE_MODEL (full_store), &iter, 0,
583  &str_data, -1);
584  normalized_str_data = g_utf8_normalize (str_data, -1, G_NORMALIZE_NFC);
585 
586  if (g_regex_match (regex, normalized_str_data, 0, NULL))
587  {
588  if (!num_found)
589  match_str = g_strdup (str_data);
590  ++num_found;
591  list_store_append (box->tmp_store, str_data);
592  }
593  g_free (str_data);
594  g_free (normalized_str_data);
595  valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (full_store), &iter);
596  }
597 
598  if (num_found)
599  {
600  gnc_item_list_set_temp_store (box->item_list, box->tmp_store);
601  gnc_item_edit_show_popup (box->item_edit);
602  box->list_popped = TRUE;
603  }
604 
605 cleanup:
606  g_regex_unref (regex);
607  return match_str;
608 }
609 
610 static char*
611 quickfill_match (QuickFill *qf, const char *string)
612 {
613  QuickFill *match = gnc_quickfill_get_string_match (qf, string);
614  return g_strdup (gnc_quickfill_string (match));
615 }
616 
617 
618 static void
619 gnc_combo_cell_modify_verify (BasicCell* _cell,
620  const char* change,
621  int change_len,
622  const char* newval,
623  int newval_len,
624  int* cursor_position,
625  int* start_selection,
626  int* end_selection)
627 {
628  ComboCell* cell = (ComboCell*) _cell;
629  PopBox* box = cell->cell.gui_private;
630  gchar* match_str = NULL;
631  glong newval_chars;
632  glong change_chars;
633  const gchar* box_str = NULL;
634 
635  newval_chars = g_utf8_strlen (newval, newval_len);
636  change_chars = g_utf8_strlen (change, change_len);
637 
638  if (box->in_list_select)
639  {
640  gnc_basic_cell_set_value_internal (_cell, newval);
641  *cursor_position = -1;
642  *start_selection = 0;
643  *end_selection = -1;
644  return;
645  }
646 
647  /* If item_list is using temp then we're already partly matched by
648  * type-ahead and a quickfill_match won't work.
649  */
650  if (!gnc_item_list_using_temp (box->item_list))
651  {
652  // If we were deleting or inserting in the middle, just accept.
653  if (change == NULL || *cursor_position < _cell->value_chars)
654  {
655  gnc_basic_cell_set_value_internal (_cell, newval);
656  *start_selection = *end_selection = *cursor_position;
657  return;
658  }
659 
660  match_str = quickfill_match (box->qf, newval);
661 
662  if (match_str != NULL) // Do we have a quickfill match
663  {
664  *start_selection = newval_chars;
665  *end_selection = -1;
666  *cursor_position += change_chars;
667  box_str = match_str;
668 
669  block_list_signals (cell); // Prevent recursion
670  gnc_item_list_select (box->item_list, match_str);
671  unblock_list_signals (cell);
672  }
673  }
674 
675  // Try using type-ahead
676  if (match_str == NULL && cell->shared_store)
677  {
678  // No start-of-name match, try type-ahead search, we match any substring of the full account name.
679  GtkListStore *store = cell->shared_store;
680  match_str = gnc_combo_cell_type_ahead_search (newval, store, cell);
681  *start_selection = newval_chars;
682  *end_selection = -1;
683  *cursor_position = newval_chars;
684 
685  // Do not change the string in the type-in box.
686  box_str = newval;
687  }
688 
689  // No type-ahead / quickfill entry found
690  if (match_str == NULL)
691  {
692  block_list_signals (cell); // Prevent recursion
693  if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
694  {
695  gnc_item_list_set_temp_store (box->item_list, NULL);
696  gtk_list_store_clear (box->tmp_store);
697  }
698  gnc_item_list_select (box->item_list, NULL);
699  unblock_list_signals (cell);
700  gnc_basic_cell_set_value_internal (_cell, newval);
701  *cursor_position = *start_selection = newval_chars;
702  *end_selection = -1;
703  return;
704  }
705 
706  if (!box->list_popped && auto_pop_combos)
707  {
708  gnc_item_edit_show_popup (box->item_edit);
709  box->list_popped = TRUE;
710  }
711 
712  gnc_basic_cell_set_value_internal (_cell, box_str);
713  g_free (match_str);
714 }
715 
716 static gboolean
717 gnc_combo_cell_direct_update (BasicCell* bcell,
718  int* cursor_position,
719  int* start_selection,
720  int* end_selection,
721  void* gui_data)
722 {
723  ComboCell* cell = (ComboCell*) bcell;
724  PopBox* box = cell->cell.gui_private;
725  GdkEventKey* event = gui_data;
726  gboolean keep_on_going = FALSE;
727  gboolean extra_colon;
728  gunichar unicode_value;
729  QuickFill* match;
730  const char* match_str;
731  int prefix_len;
732  int find_pos;
733  int new_pos;
734 
735  if (event->type != GDK_KEY_PRESS)
736  return FALSE;
737 
738  unicode_value = gdk_keyval_to_unicode (event->keyval);
739  switch (event->keyval)
740  {
741  case GDK_KEY_Escape:
742  if (bcell->changed)
743  {
744  const char *value = gnc_table_get_model_entry (box->sheet->table, bcell->cell_name);
745 
746  if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
747  {
748  gnc_item_list_set_temp_store (box->item_list, NULL);
749  gtk_list_store_clear (box->tmp_store);
750  }
751  gnc_basic_cell_set_value_internal (bcell, value);
752  bcell->changed = FALSE;
753  block_list_signals (cell);
754  gnc_item_list_select (box->item_list, value);
755  unblock_list_signals (cell);
756  *cursor_position = -1;
757  *start_selection = 0;
758  *end_selection = -1;
759  return TRUE;
760  }
761  break;
762  case GDK_KEY_slash:
763  if (! (event->state & GDK_MOD1_MASK))
764  {
765  if (unicode_value == box->complete_char)
766  break;
767 
768  return FALSE;
769  }
770  keep_on_going = TRUE;
771  /* fall through */
772  case GDK_KEY_Tab:
773  case GDK_KEY_ISO_Left_Tab:
774  if (gnc_item_list_using_temp (box->item_list))
775  {
776  char* string = gnc_item_list_get_selection (box->item_list);
777  g_signal_emit_by_name (G_OBJECT (box->item_list), "change_item",
778  string, (gpointer)bcell);
779  g_free (string);
780  return FALSE;
781  }
782  if (! (event->state & GDK_CONTROL_MASK) && !keep_on_going)
783  return FALSE;
784 
786  (box->qf, bcell->value, *cursor_position);
787  if (match == NULL)
788  return TRUE;
789 
791  (match, &prefix_len);
792  if (match == NULL)
793  return TRUE;
794 
795  match_str = gnc_quickfill_string (match);
796 
797  if ((match_str != NULL) &&
798  (strncmp (match_str, bcell->value,
799  strlen (bcell->value)) == 0) &&
800  (strcmp (match_str, bcell->value) != 0))
801  {
802  gnc_basic_cell_set_value_internal (bcell,
803  match_str);
804 
805  block_list_signals (cell);
806  gnc_item_list_select (box->item_list,
807  match_str);
808  unblock_list_signals (cell);
809  }
810 
811  *cursor_position += prefix_len;
812  *start_selection = *cursor_position;
813  *end_selection = -1;
814  return TRUE;
815  }
816 
817  if (box->complete_char == 0)
818  return FALSE;
819 
820  if (unicode_value != box->complete_char)
821  return FALSE;
822 
823  if (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))
824  return FALSE;
825 
826  if ((*cursor_position < bcell->value_chars) &&
827  ((*end_selection < bcell->value_chars) ||
828  (*cursor_position < *start_selection)))
829  return FALSE;
830 
831  if ((*cursor_position == bcell->value_chars) &&
832  (*start_selection != *end_selection) &&
833  (*end_selection < bcell->value_chars))
834  return FALSE;
835 
836  find_pos = -1;
837  if (*start_selection < bcell->value_chars)
838  {
839  int i = *start_selection;
840  const char* c;
841  gunichar uc;
842 
843  c = g_utf8_offset_to_pointer (bcell->value, i);
844  while (*c)
845  {
846  uc = g_utf8_get_char (c);
847  if (uc == box->complete_char)
848  {
849  find_pos = (i + 1);
850  break;
851  }
852  c = g_utf8_next_char (c);
853  i++;
854  }
855  }
856 
857  if (find_pos >= 0)
858  {
859  new_pos = find_pos;
860  extra_colon = FALSE;
861  }
862  else
863  {
864  new_pos = bcell->value_chars;
865  extra_colon = TRUE;
866  }
867 
868  match = gnc_quickfill_get_string_len_match (box->qf,
869  bcell->value, new_pos);
870  if (match == NULL)
871  return FALSE;
872 
873  if (extra_colon)
874  {
875  match = gnc_quickfill_get_char_match (match,
876  box->complete_char);
877  if (match == NULL)
878  return FALSE;
879 
880  new_pos++;
881  }
882 
883  match_str = gnc_quickfill_string (match);
884 
885  if ((match_str != NULL) &&
886  (strncmp (match_str, bcell->value, strlen (bcell->value)) == 0) &&
887  (strcmp (match_str, bcell->value) != 0))
888  {
889  gnc_basic_cell_set_value_internal (bcell, match_str);
890 
891  block_list_signals (cell);
892  gnc_item_list_select (box->item_list, match_str);
893  unblock_list_signals (cell);
894  }
895 
896  *cursor_position = new_pos;
897  *start_selection = new_pos;
898  *end_selection = -1;
899 
900  return TRUE;
901 }
902 
903 static void
904 gnc_combo_cell_gui_realize (BasicCell* bcell, gpointer data)
905 {
906  GnucashSheet* sheet = data;
907  GncItemEdit* item_edit = gnucash_sheet_get_item_edit (sheet);
908  ComboCell* cell = (ComboCell*) bcell;
909  PopBox* box = cell->cell.gui_private;
910 
911  /* initialize gui-specific, private data */
912  box->sheet = sheet;
913  box->item_edit = item_edit;
914  if (cell->shared_store)
915  box->item_list = GNC_ITEM_LIST (gnc_item_list_new (cell->shared_store));
916  else
917  box->item_list = GNC_ITEM_LIST (gnc_item_list_new (box->tmp_store));
918  gtk_widget_show_all (GTK_WIDGET (box->item_list));
919  g_object_ref_sink (box->item_list);
920 
921  /* to mark cell as realized, remove the realize method */
922  cell->cell.gui_realize = NULL;
923  cell->cell.gui_move = gnc_combo_cell_gui_move;
924  cell->cell.enter_cell = gnc_combo_cell_enter;
925  cell->cell.leave_cell = gnc_combo_cell_leave;
926  cell->cell.gui_destroy = gnc_combo_cell_gui_destroy;
927  cell->cell.modify_verify = gnc_combo_cell_modify_verify;
928  cell->cell.direct_update = gnc_combo_cell_direct_update;
929 }
930 
931 static void
932 gnc_combo_cell_gui_move (BasicCell* bcell)
933 {
934  PopBox* box = bcell->gui_private;
935 
936  combo_disconnect_signals ((ComboCell*) bcell);
937 
938  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
939  NULL, NULL, NULL, NULL, NULL);
940 
941  gnc_item_list_select (box->item_list, NULL);
942 
943  box->list_popped = FALSE;
944 }
945 
946 static int
947 popup_get_height (GtkWidget* widget,
948  int space_available,
949  G_GNUC_UNUSED int row_height,
950  gpointer user_data)
951 {
952  PopBox* box = user_data;
953  GtkScrolledWindow* scrollwin = GNC_ITEM_LIST(widget)->scrollwin;
954  int height;
955 
956  // if popup_allocation_height set use that
957  if (box->item_edit->popup_allocation_height != -1)
958  height = box->item_edit->popup_allocation_height;
959  else
960  height = gnc_item_list_get_popup_height (GNC_ITEM_LIST(widget));
961 
962  if (height < space_available)
963  {
964  // if the list is empty height would be 0 so return 1 instead to
965  // satisfy the check_popup_height_is_true function
966  gint ret_height = height ? height : 1;
967 
968  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, ret_height);
969  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
970  GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
971  return ret_height;
972  }
973  else
974  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, -1);
975 
976  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
977  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
978  return space_available;
979 }
980 
981 static int
982 popup_autosize (GtkWidget* widget,
983  int max_width,
984  gpointer user_data)
985 {
986  PopBox* box = user_data;
987 
988  if (!box || !box->autosize)
989  return max_width;
990 
991  return gnc_item_list_autosize (GNC_ITEM_LIST (widget)) + 20;
992 }
993 
994 static void
995 popup_set_focus (GtkWidget* widget,
996  G_GNUC_UNUSED gpointer user_data)
997 {
998  /* An empty GtkTreeView grabbing focus causes the key_press events to be
999  * lost because there's no entry cell to handle them.
1000  */
1001  if (gnc_item_list_num_entries (GNC_ITEM_LIST (widget)))
1002  gtk_widget_grab_focus (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view));
1003 }
1004 
1005 static void
1006 popup_post_show (GtkWidget* widget,
1007  G_GNUC_UNUSED gpointer user_data)
1008 {
1009  gnc_item_list_autosize (GNC_ITEM_LIST (widget));
1010  gnc_item_list_show_selected (GNC_ITEM_LIST (widget));
1011 }
1012 
1013 static int
1014 popup_get_width (GtkWidget* widget,
1015  G_GNUC_UNUSED gpointer user_data)
1016 {
1017  GtkAllocation alloc;
1018  gtk_widget_get_allocation (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view),
1019  &alloc);
1020  return alloc.width;
1021 }
1022 
1023 static gboolean
1024 gnc_combo_cell_enter (BasicCell* bcell,
1025  int* cursor_position,
1026  int* start_selection,
1027  int* end_selection)
1028 {
1029  ComboCell* cell = (ComboCell*) bcell;
1030  PopBox* box = bcell->gui_private;
1031  PopupToggle popup_toggle;
1032  GList* find = NULL;
1033 
1034  if (bcell->value)
1035  find = g_list_find_custom (box->ignore_strings,
1036  bcell->value,
1037  (GCompareFunc) strcmp);
1038  if (find)
1039  return FALSE;
1040 
1041  gnc_item_edit_set_popup (box->item_edit,
1042  GTK_WIDGET (box->item_list),
1043  popup_get_height, popup_autosize,
1044  popup_set_focus, popup_post_show,
1045  popup_get_width, box);
1046 
1047  block_list_signals (cell);
1048 
1049  if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
1050  {
1051  // Clear the temp store to ensure we don't start in type-ahead mode.
1052  gnc_item_list_set_temp_store (box->item_list, NULL);
1053  gtk_list_store_clear (box->tmp_store);
1054  }
1055  gnc_item_list_select (box->item_list, bcell->value);
1056  unblock_list_signals (cell);
1057 
1058  popup_toggle = box->item_edit->popup_toggle;
1059 
1060  // if the list is empty disable the toggle button
1061  gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton),
1062  gnc_item_list_num_entries (box->item_list));
1063 
1064  combo_connect_signals (cell);
1065 
1066  *cursor_position = -1;
1067  *start_selection = 0;
1068  *end_selection = -1;
1069 
1070  return TRUE;
1071 }
1072 
1073 static void
1074 gnc_combo_cell_leave (BasicCell* bcell)
1075 {
1076  PopBox* box = bcell->gui_private;
1077 
1078  combo_disconnect_signals ((ComboCell*) bcell);
1079 
1080  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
1081  NULL, NULL, NULL, NULL, NULL);
1082 
1083  box->list_popped = FALSE;
1084 
1085  if (box->strict)
1086  {
1087  if (bcell->value)
1088  {
1089  if (!bcell->changed)
1090  return;
1091 
1092  if (gnc_item_in_list (box->item_list, bcell->value))
1093  return;
1094 
1095  if (g_list_find_custom (box->ignore_strings,
1096  bcell->value,
1097  (GCompareFunc) strcmp))
1098  return;
1099  }
1100  gnc_basic_cell_set_value_internal (bcell, "");
1101  }
1102 }
1103 
1104 void
1105 gnc_combo_cell_set_strict (ComboCell* cell, gboolean strict)
1106 {
1107  PopBox* box;
1108 
1109  if (cell == NULL)
1110  return;
1111 
1112  box = cell->cell.gui_private;
1113 
1114  box->strict = strict;
1115 }
1116 
1117 void
1118 gnc_combo_cell_set_complete_char (ComboCell* cell, gunichar complete_char)
1119 {
1120  PopBox* box;
1121 
1122  if (cell == NULL)
1123  return;
1124 
1125  box = cell->cell.gui_private;
1126 
1127  box->complete_char = complete_char;
1128 }
1129 
1130 void
1132  const char* ignore_string)
1133 {
1134  PopBox* box;
1135 
1136  if (cell == NULL)
1137  return;
1138 
1139  if (!ignore_string)
1140  return;
1141 
1142  box = cell->cell.gui_private;
1143 
1144  box->ignore_strings = g_list_prepend (box->ignore_strings,
1145  g_strdup (ignore_string));
1146 }
1147 
1148 void
1149 gnc_combo_cell_set_autosize (ComboCell* cell, gboolean autosize)
1150 {
1151  PopBox* box;
1152 
1153  if (!cell)
1154  return;
1155 
1156  box = cell->cell.gui_private;
1157  if (!box)
1158  return;
1159 
1160  box->autosize = autosize;
1161 }
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