GnuCash  5.6-150-g038405b370+
completioncell-gnome.c
1 /********************************************************************\
2  * completioncell-gnome.c -- completion combobox 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: completioncell-gnome.c
25  *
26  * FUNCTION: Implement gnome portion of a entry completion combo widget
27  * embedded in a table cell.
28  *
29  * HISTORY:
30  * @author Copyright (c) 2023 Robert Fewell
31  */
32 
33 #include <config.h>
34 
35 #include <string.h>
36 #include <stdbool.h>
37 #include <gdk/gdkkeysyms.h>
38 
39 #include "completioncell.h"
40 #include "gnc-prefs.h"
41 #include "gnucash-item-edit.h"
42 #include "gnucash-item-list.h"
43 #include "gnucash-sheet.h"
44 #include "gnucash-sheetP.h"
45 #include "table-allgui.h"
46 #include "gnc-glib-utils.h"
47 #include <gnc-unicode.h>
48 
49 typedef struct _PopBox
50 {
51  GnucashSheet* sheet;
52  GncItemEdit* item_edit;
53  GncItemList* item_list;
54 
55  GHashTable* item_hash; // the item hash table
56  GtkListStore* item_store; // the item list store
57 
58  gchar* newval; // string value to find
59  gint newval_len; // length of string value to find
60 
61  gboolean signals_connected; // list signals connected
62  gboolean list_popped; // list is popped up
63 
64  gboolean autosize; // autosize the popup width
65 
66  gboolean sort_enabled; // sort of list store enabled
67  gboolean register_is_reversed; // whether the register is reversed
68 
69  gboolean strict; // text entry must be in the list
70  gboolean in_list_select; // item selected in the list
71 
72  gint occurrence; // the position in the list
73 
74 } PopBox;
75 
76 #define DONT_TEXT N_("Don't autocomplete")
77 
79 enum GncCompletionColumn
80 {
81  TEXT_COL, //0
82  TEXT_MARKUP_COL, //1
83  WEIGHT_COL, //2
84  FOUND_LOCATION_COL, //3
85 };
86 
87 static void gnc_completion_cell_gui_realize (BasicCell* bcell, gpointer w);
88 static void gnc_completion_cell_gui_move (BasicCell* bcell);
89 static void gnc_completion_cell_gui_destroy (BasicCell* bcell);
90 static gboolean gnc_completion_cell_enter (BasicCell* bcell,
91  int* cursor_position,
92  int* start_selection,
93  int* end_selection);
94 static void gnc_completion_cell_leave (BasicCell* bcell);
95 static void gnc_completion_cell_destroy (BasicCell* bcell);
96 
97 BasicCell*
98 gnc_completion_cell_new (void)
99 {
100  CompletionCell* cell = g_new0 (CompletionCell, 1);
101  gnc_completion_cell_init (cell);
102  return &cell->cell;
103 }
104 
105 void
106 gnc_completion_cell_init (CompletionCell* cell)
107 {
108  gnc_basic_cell_init (& (cell->cell));
109 
110  cell->cell.is_popup = TRUE;
111 
112  cell->cell.destroy = gnc_completion_cell_destroy;
113 
114  cell->cell.gui_realize = gnc_completion_cell_gui_realize;
115  cell->cell.gui_destroy = gnc_completion_cell_gui_destroy;
116 
117  PopBox* box = g_new0 (PopBox, 1);
118 
119  box->sheet = NULL;
120  box->item_edit = NULL;
121  box->item_list = NULL;
122  box->item_store = NULL;
123 
124  box->signals_connected = FALSE;
125  box->list_popped = FALSE;
126  box->autosize = FALSE;
127  box->register_is_reversed = FALSE;
128 
129  box->sort_enabled = FALSE;
130 
131  cell->cell.gui_private = box;
132 
133  box->strict = FALSE;
134  box->in_list_select = FALSE;
135  box->occurrence = 0;
136 
137  box->item_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
138 }
139 
140 static void
141 hide_popup (PopBox* box)
142 {
143  gnc_item_edit_hide_popup (box->item_edit);
144  box->list_popped = FALSE;
145 }
146 
147 static void
148 select_item_cb (GncItemList* item_list, char* item_string, gpointer user_data)
149 {
150  CompletionCell* cell = user_data;
151  PopBox* box = cell->cell.gui_private;
152 
153  box->in_list_select = TRUE;
154  gnucash_sheet_modify_current_cell (box->sheet, item_string);
155  box->in_list_select = FALSE;
156 
157  hide_popup (box);
158 }
159 
160 static gint
161 text_width (PangoLayout *layout)
162 {
163  PangoRectangle logical_rect;
164  pango_layout_set_width (layout, -1);
165  pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
166  return logical_rect.width;
167 }
168 
169 static void
170 horizontal_scroll_to_found_text (PopBox* box, char* item_string, gint found_location)
171 {
172  if (!gtk_widget_get_realized (GTK_WIDGET(box->item_list->tree_view)))
173  return;
174 
175  GtkAllocation alloc;
176  gtk_widget_get_allocation (GTK_WIDGET(box->item_list->tree_view), &alloc);
177  gint scroll_point = 0;
178  gchar *start_string = g_utf8_substring (item_string, 0, found_location + box->newval_len);
179 
180  PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(box->item_list->tree_view), item_string);
181  PangoAttrList *atlist = pango_attr_list_new ();
182  PangoAttribute *bold_weight = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
183  bold_weight->start_index = found_location;
184  bold_weight->end_index = found_location + box->newval_len;
185  pango_attr_list_insert (atlist, bold_weight);
186  pango_layout_set_attributes (layout, atlist);
187 
188  gint item_string_width = text_width (layout);
189 
190  pango_layout_set_text (layout, start_string, -1);
191 
192  gint start_string_width = text_width (layout);
193 
194  pango_attr_list_unref (atlist);
195  g_object_unref (layout);
196  g_free (start_string);
197 
198  if (item_string_width <= alloc.width)
199  scroll_point = 0;
200  else
201  scroll_point = start_string_width - alloc.width / 2;
202 
203  if (scroll_point < 0)
204  scroll_point = 0;
205 
206  gtk_tree_view_scroll_to_point (box->item_list->tree_view, scroll_point, -1);
207 }
208 
209 static void
210 change_item_cb (GncItemList* item_list, char* item_string, gpointer user_data)
211 {
212  CompletionCell* cell = user_data;
213  PopBox* box = cell->cell.gui_private;
214 
215  box->in_list_select = TRUE;
216  gnucash_sheet_modify_current_cell (box->sheet, item_string);
217  box->in_list_select = FALSE;
218 
219  GtkTreeModel *model = gtk_tree_view_get_model (item_list->tree_view);
220  GtkTreeSelection *selection = gtk_tree_view_get_selection (item_list->tree_view);
221  GtkTreeIter iter;
222  if (gtk_tree_selection_get_selected (selection, &model, &iter))
223  {
224  gint found_location;
225  gtk_tree_model_get (model, &iter, FOUND_LOCATION_COL, &found_location, -1);
226  horizontal_scroll_to_found_text (box, item_string, found_location);
227  }
228 }
229 
230 static void
231 activate_item_cb (GncItemList* item_list, char* item_string, gpointer user_data)
232 {
233  CompletionCell* cell = user_data;
234  PopBox* box = cell->cell.gui_private;
235  hide_popup (box);
236 }
237 
238 static void
239 block_list_signals (CompletionCell* cell)
240 {
241  PopBox* box = cell->cell.gui_private;
242 
243  if (!box->signals_connected)
244  return;
245 
246  g_signal_handlers_block_matched (G_OBJECT(box->item_list),
247  G_SIGNAL_MATCH_DATA,
248  0, 0, NULL, NULL, cell);
249 }
250 
251 static void
252 unblock_list_signals (CompletionCell* cell)
253 {
254  PopBox* box = cell->cell.gui_private;
255 
256  if (!box->signals_connected)
257  return;
258 
259  g_signal_handlers_unblock_matched (G_OBJECT(box->item_list),
260  G_SIGNAL_MATCH_DATA,
261  0, 0, NULL, NULL, cell);
262 }
263 
264 static void
265 key_press_item_cb (GncItemList* item_list, GdkEventKey* event, gpointer user_data)
266 {
267  CompletionCell* cell = user_data;
268  PopBox* box = cell->cell.gui_private;
269 
270  switch (event->keyval)
271  {
272  case GDK_KEY_Escape:
273  block_list_signals (cell); // Prevent recursion, unselect all
274  gnc_item_list_select (box->item_list, NULL);
275  unblock_list_signals (cell);
276  hide_popup (box);
277  break;
278 
279  default:
280  gtk_widget_event (GTK_WIDGET (box->sheet),
281  (GdkEvent*) event);
282  break;
283  }
284 }
285 
286 static void
287 completion_disconnect_signals (CompletionCell* cell)
288 {
289  PopBox* box = cell->cell.gui_private;
290 
291  if (!box->signals_connected)
292  return;
293 
294  g_signal_handlers_disconnect_matched (G_OBJECT(box->item_list),
295  G_SIGNAL_MATCH_DATA,
296  0, 0, NULL, NULL, cell);
297 
298  box->signals_connected = FALSE;
299 }
300 
301 static void
302 completion_connect_signals (CompletionCell* cell)
303 {
304  PopBox* box = cell->cell.gui_private;
305 
306  if (box->signals_connected)
307  return;
308 
309  g_signal_connect (G_OBJECT(box->item_list), "select_item",
310  G_CALLBACK(select_item_cb), cell);
311 
312  g_signal_connect (G_OBJECT(box->item_list), "change_item",
313  G_CALLBACK(change_item_cb), cell);
314 
315  g_signal_connect (G_OBJECT(box->item_list), "activate_item",
316  G_CALLBACK(activate_item_cb), cell);
317 
318  g_signal_connect (G_OBJECT(box->item_list), "key_press_event",
319  G_CALLBACK(key_press_item_cb), cell);
320 
321  box->signals_connected = TRUE;
322 }
323 
324 static void
325 gnc_completion_cell_gui_destroy (BasicCell* bcell)
326 {
327  CompletionCell* cell = (CompletionCell*) bcell;
328 
329  if (!cell->cell.gui_realize)
330  {
331  PopBox* box = bcell->gui_private;
332  if (box)
333  {
334  if (box->item_list)
335  {
336  completion_disconnect_signals (cell);
337  g_object_unref (box->item_list);
338  box->item_list = NULL;
339  }
340  if (box->item_store)
341  {
342  g_object_unref (box->item_store);
343  box->item_store = NULL;
344  }
345  }
346  /* allow the widget to be shown again */
347  cell->cell.gui_realize = gnc_completion_cell_gui_realize;
348  cell->cell.gui_move = NULL;
349  cell->cell.enter_cell = NULL;
350  cell->cell.leave_cell = NULL;
351  cell->cell.gui_destroy = NULL;
352  }
353 }
354 
355 static void
356 gnc_completion_cell_destroy (BasicCell* bcell)
357 {
358  CompletionCell* cell = (CompletionCell*) bcell;
359  PopBox* box = cell->cell.gui_private;
360 
361  gnc_completion_cell_gui_destroy (& (cell->cell));
362 
363  if (box)
364  {
365  if (box->item_hash)
366  g_hash_table_destroy (box->item_hash);
367 
368  g_free (box);
369  cell->cell.gui_private = NULL;
370  }
371  cell->cell.gui_private = NULL;
372  cell->cell.gui_realize = NULL;
373 }
374 
375 static gint
376 sort_func (GtkTreeModel* model, GtkTreeIter* iter_a, GtkTreeIter* iter_b, gpointer user_data)
377 {
378  gint a_weight, b_weight;
379  gint ret = 0;
380 
381  gtk_tree_model_get (model, iter_a, WEIGHT_COL, &a_weight, -1);
382  gtk_tree_model_get (model, iter_b, WEIGHT_COL, &b_weight, -1);
383 
384  if (a_weight < b_weight)
385  ret = -1;
386  else if (a_weight > b_weight)
387  ret = 1;
388 
389  return ret;
390 }
391 
392 void
394  gboolean enabled)
395 {
396  if (!cell)
397  return;
398 
399  PopBox* box = cell->cell.gui_private;
400  box->sort_enabled = enabled;
401 }
402 
403 static void
404 set_sort_column_enabled (PopBox* box, gboolean enable)
405 {
406  if (enable)
407  {
408  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(box->item_list->list_store),
409  WEIGHT_COL, sort_func, box->item_list, NULL);
410 
411  gnc_item_list_set_sort_column (box->item_list, WEIGHT_COL);
412  }
413  else
414  gnc_item_list_set_sort_column (box->item_list, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID);
415 }
416 
417 static void
418 item_store_clear (CompletionCell* cell)
419 {
420  if (!cell)
421  return;
422 
423  PopBox* box = cell->cell.gui_private;
424 
425  // disconnect list store from tree view
426  gnc_item_list_disconnect_store (box->item_list);
427 
428  block_list_signals (cell);
429 
430  if (box->sort_enabled) // if sorting, disable it
431  set_sort_column_enabled (box, FALSE);
432 
433  gtk_list_store_clear (box->item_store);
434 
435  if (box->sort_enabled) // if sorting, enable it
436  set_sort_column_enabled (box, TRUE);
437 
438  unblock_list_signals (cell);
439 
440  // reconect list store to tree view
441  gnc_item_list_connect_store (box->item_list, box->item_store);
442 
443  hide_popup (box);
444 }
445 
446 void
447 gnc_completion_cell_clear_menu (CompletionCell* cell)
448 {
449  if (!cell)
450  return;
451 
452  PopBox* box = cell->cell.gui_private;
453  if (!box)
454  return;
455 
456  if (box->item_list)
457  {
458  g_hash_table_remove_all (box->item_hash);
459  item_store_clear (cell);
460  box->occurrence = 0;
461  }
462 }
463 
464 void
466  const char* menustr)
467 {
468  if (!cell || !menustr)
469  return;
470 
471  PopBox* box = cell->cell.gui_private;
472 
473  if (box->item_hash)
474  {
475  gpointer value = g_hash_table_lookup (box->item_hash, menustr);
476  gboolean update = FALSE;
477  if (value)
478  {
479  if (!box->register_is_reversed)
480  update = TRUE;
481  }
482  else
483  update = TRUE;
484 
485  if (update)
486  {
487  g_hash_table_insert (box->item_hash, g_strdup (menustr),
488  GINT_TO_POINTER(box->occurrence));
489  }
490  box->occurrence++;
491  }
492 }
493 
494 void
495 gnc_completion_cell_set_value (CompletionCell* cell, const char* str)
496 {
497  if (!cell || !str)
498  return;
499 
500  gnc_basic_cell_set_value (&cell->cell, str);
501 }
502 
503 static inline void
504 list_store_append (GtkListStore *store, char* string,
505  char* markup, gint weight, gint found_location)
506 {
507  GtkTreeIter iter;
508 
509  g_return_if_fail (store);
510  g_return_if_fail (string);
511  g_return_if_fail (markup);
512 
513  gtk_list_store_append (store, &iter);
514 
515  gtk_list_store_set (store, &iter, TEXT_COL, string,
516  TEXT_MARKUP_COL, markup,
517  WEIGHT_COL, weight,
518  FOUND_LOCATION_COL, found_location, -1);
519 }
520 
521 static gint
522 test_and_add (PopBox* box, const gchar *text, gint start_pos,
523  gpointer key, gint occurrence_difference)
524 {
525  gint ret_value = -1;
526  gint text_length = g_utf8_strlen (text, -1);
527 
528  if (start_pos >= text_length)
529  return ret_value;
530 
531  gchar *sub_text = g_utf8_substring (text, start_pos, text_length);
532  int pos = 0, len = 0;
533  if (gnc_unicode_has_substring_base_chars (box->newval, sub_text, &pos, &len))
534  {
535  gchar *markup = NULL, *prefix = NULL, *match = NULL, *suffix = NULL;
536  gint found_location = start_pos + pos;
537  gboolean have_boundary = FALSE;
538  gint prefix_length;
539  gint weight;
540 
541  if (found_location > 0)
542  prefix = g_utf8_substring (text, 0, found_location);
543  else
544  prefix = g_strdup ("");
545 
546  prefix_length = g_utf8_strlen (prefix, -1);
547 
548  match = g_utf8_substring (text, found_location, found_location + len);
549 
550  if (pos >= 1)
551  {
552  gunichar prev = g_utf8_get_char (g_utf8_offset_to_pointer (sub_text, pos - 1));
553  if (prev && (g_unichar_isspace (prev) || g_unichar_ispunct (prev)))
554  have_boundary = TRUE;
555  else
556  ret_value = found_location + 1;
557  }
558 
559  suffix = g_utf8_substring (text, found_location + len, text_length);
560 
561  markup = g_markup_printf_escaped ("%s<b>%s</b>%s%s", prefix, match, suffix, " ");
562 
563  if ((prefix_length == 0 ) || have_boundary)
564  {
565  weight = occurrence_difference; // sorted by recent first
566 
567  if (gnc_unicode_compare_base_chars (sub_text, box->newval) == 0) // exact match
568  weight = 1;
569 
570  list_store_append (box->item_store, key, markup, weight, found_location);
571  }
572  g_free (markup);
573  g_free (prefix);
574  g_free (match);
575  g_free (suffix);
576  }
577  g_free (sub_text);
578  return ret_value;
579 }
580 
581 static void
582 add_item (gpointer key, gpointer value, gpointer user_data)
583 {
584  PopBox* box = user_data;
585  gchar *hash_entry = g_strdup (key);
586 
587  if (hash_entry && *hash_entry)
588  {
589  gint start_pos = 0;
590  gint occurrence_difference;
592 
593  if (box->register_is_reversed)
594  occurrence_difference = GPOINTER_TO_INT(value) + 1;
595  else
596  occurrence_difference = box->occurrence - GPOINTER_TO_INT(value);
597 
598  do
599  {
600  start_pos = test_and_add (box, hash_entry, start_pos, key, occurrence_difference);
601  }
602  while (start_pos != -1);
603  }
604  g_free (hash_entry);
605 }
606 
607 static void
608 select_first_entry_in_list (PopBox* box)
609 {
610  GtkTreeModel *model = gtk_tree_view_get_model (box->item_list->tree_view);
611  GtkTreeIter iter;
612  gchar* string;
613 
614  if (!gtk_tree_model_get_iter_first (model, &iter))
615  return;
616 
617  if (!gtk_tree_model_iter_next (model, &iter))
618  return;
619 
620  gtk_tree_model_get (model, &iter, TEXT_COL, &string, -1);
621 
622  gnc_item_list_select (box->item_list, string);
623 
624  GtkTreePath* path = gtk_tree_path_new_first ();
625  gtk_tree_view_scroll_to_cell (box->item_list->tree_view,
626  path, NULL, TRUE, 0.5, 0.0);
627  gtk_tree_path_free (path);
628  g_free (string);
629 }
630 
631 static void
632 populate_list_store (CompletionCell* cell, gchar* str)
633 {
634  PopBox* box = cell->cell.gui_private;
635 
636  box->in_list_select = FALSE;
637  box->item_edit->popup_allocation_height = -1;
638 
639  if (str && *str)
640  box->newval = g_strdup(str);
641  else
642  return;
643 
644  box->newval_len = g_utf8_strlen (str, -1);
645 
646  // disconnect list store from tree view
647  gnc_item_list_disconnect_store (box->item_list);
648 
649  block_list_signals (cell);
650 
651  if (box->sort_enabled) // if sorting, disable it
652  set_sort_column_enabled (box, FALSE);
653 
654  gtk_list_store_clear (box->item_store);
655 
656  // add the don't first entry
657  gchar *markup = g_markup_printf_escaped ("<i>%s</i>", DONT_TEXT);
658  list_store_append (box->item_store, DONT_TEXT, markup, 0, 0);
659  g_free (markup);
660 
661  // add to the list store
662  g_hash_table_foreach (box->item_hash, add_item, box);
663 
664  if (box->sort_enabled) // if sorting, enable it
665  set_sort_column_enabled (box, TRUE);
666 
667  unblock_list_signals (cell);
668 
669  // reconnect list store to tree view
670  gnc_item_list_connect_store (box->item_list, box->item_store);
671 
672  // reset horizontal scrolling
673  gtk_tree_view_column_queue_resize (gtk_tree_view_get_column (
674  box->item_list->tree_view, TEXT_COL));
675 
676  // if no entries, do not show popup
677  if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL(box->item_store), NULL) == 1)
678  {
679  hide_popup (box);
680  }
681  else
682  gnc_item_edit_show_popup (box->item_edit);
683 
684  block_list_signals (cell); // Prevent recursion, select first entry
685  select_first_entry_in_list (box);
686  unblock_list_signals (cell);
687 
688  g_free (box->newval);
689 }
690 
691 static void
692 gnc_completion_cell_modify_verify (BasicCell* bcell,
693  const char* change,
694  int change_len,
695  const char* newval,
696  int newval_len,
697  int* cursor_position,
698  int* start_selection,
699  int* end_selection)
700 {
701  CompletionCell* cell = (CompletionCell*) bcell;
702  PopBox* box = cell->cell.gui_private;
703 
704  if (box->in_list_select)
705  {
706  if (g_strcmp0 (newval, DONT_TEXT) == 0)
707  return;
708  gnc_basic_cell_set_value_internal (bcell, newval);
709  *cursor_position = -1;
710  *start_selection = 0;
711  *end_selection = 0;
712  return;
713  }
714 
715  // Are were deleting or inserting in the middle.
716  if (change == NULL || *cursor_position < bcell->value_chars)
717  *start_selection = *end_selection = *cursor_position;
718 
719  gchar *start_of_text = g_utf8_substring (newval, 0, *cursor_position);
720  populate_list_store (cell, start_of_text);
721  g_free (start_of_text);
722 
723  if (g_strcmp0 (newval, "") == 0)
724  {
725  block_list_signals (cell); // Prevent recursion, unselect all
726  gnc_item_list_select (box->item_list, NULL);
727  unblock_list_signals (cell);
728  hide_popup (box);
729  }
730  gnc_basic_cell_set_value_internal (bcell, newval);
731 }
732 
733 static char*
734 get_entry_from_hash_if_size_is_one (CompletionCell* cell)
735 {
736  if (!cell)
737  return NULL;
738 
739  PopBox* box = cell->cell.gui_private;
740 
741  if (box->item_hash && (g_hash_table_size (box->item_hash) == 1))
742  {
743  GList *keys = g_hash_table_get_keys (box->item_hash);
744  char *ret = g_strdup (keys->data);
745  g_list_free (keys);
746  return ret;
747  }
748  return NULL;
749 }
750 
751 static gboolean
752 gnc_completion_cell_direct_update (BasicCell* bcell,
753  int* cursor_position,
754  int* start_selection,
755  int* end_selection,
756  void* gui_data)
757 {
758  CompletionCell* cell = (CompletionCell*) bcell;
759  PopBox* box = cell->cell.gui_private;
760  GdkEventKey* event = gui_data;
761 
762  if (event->type != GDK_KEY_PRESS)
763  return FALSE;
764 
765  switch (event->keyval)
766  {
767  case GDK_KEY_Tab:
768  case GDK_KEY_ISO_Left_Tab:
769  {
770  if (event->state & GDK_CONTROL_MASK)
771  {
772  char* hash_string = get_entry_from_hash_if_size_is_one (cell);
773 
774  if (hash_string)
775  {
776  gnc_basic_cell_set_value_internal (bcell, hash_string);
777  *cursor_position = strlen (hash_string);
778  }
779  g_free (hash_string);
780  return TRUE;
781  }
782 
783  char* string = gnc_item_list_get_selection (box->item_list);
784 
785  if (!string)
786  break;
787 
788  g_signal_emit_by_name (G_OBJECT(box->item_list), "change_item",
789  string, (gpointer)bcell);
790 
791  g_free (string);
792  break;
793  }
794  }
795 
796  if (box->strict)
797  box->in_list_select = gnc_item_in_list (box->item_list, bcell->value);
798 
799  if (!bcell->value)
800  item_store_clear (cell);
801 
802  return FALSE;
803 }
804 
805 void
807 {
808  if (!cell)
809  return;
810 
811  PopBox* box = cell->cell.gui_private;
812 
813  if (is_reversed != box->register_is_reversed)
814  {
815  gnc_completion_cell_clear_menu (cell);
816  box->register_is_reversed = is_reversed;
817  box->occurrence = 0;
818  }
819 }
820 
821 static void
822 gnc_completion_cell_gui_realize (BasicCell* bcell, gpointer data)
823 {
824  GnucashSheet* sheet = data;
825  GncItemEdit* item_edit = gnucash_sheet_get_item_edit (sheet);
826  CompletionCell* cell = (CompletionCell*) bcell;
827  PopBox* box = cell->cell.gui_private;
828 
829  /* initialize gui-specific, private data */
830  box->sheet = sheet;
831  box->item_edit = item_edit;
832  box->item_store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING,
833  G_TYPE_INT, G_TYPE_INT);
834  box->item_list = GNC_ITEM_LIST(gnc_item_list_new (box->item_store));
835 
836  block_list_signals (cell);
837  set_sort_column_enabled (box, FALSE);
838  unblock_list_signals (cell);
839 
840  gtk_widget_show_all (GTK_WIDGET(box->item_list));
841  g_object_ref_sink (box->item_list);
842 
843  /* to mark cell as realized, remove the realize method */
844  cell->cell.gui_realize = NULL;
845  cell->cell.gui_move = gnc_completion_cell_gui_move;
846  cell->cell.enter_cell = gnc_completion_cell_enter;
847  cell->cell.leave_cell = gnc_completion_cell_leave;
848  cell->cell.gui_destroy = gnc_completion_cell_gui_destroy;
849  cell->cell.modify_verify = gnc_completion_cell_modify_verify;
850  cell->cell.direct_update = gnc_completion_cell_direct_update;
851 }
852 
853 static void
854 reset_item_list_to_default_setup (BasicCell* bcell)
855 {
856  PopBox* box = bcell->gui_private;
857  PopupToggle popup_toggle;
858 
859  item_store_clear ((CompletionCell*) bcell);
860 
861  popup_toggle = box->item_edit->popup_toggle;
862  gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton), TRUE);
863  gtk_widget_set_visible (GTK_WIDGET(popup_toggle.tbutton), TRUE);
864 
865  GtkTreeViewColumn *column = gtk_tree_view_get_column (
866  GTK_TREE_VIEW(box->item_list->tree_view), TEXT_COL);
867  gtk_tree_view_column_clear_attributes (column,box->item_list->renderer);
868  gtk_tree_view_column_add_attribute (column, box->item_list->renderer,
869  "text", TEXT_COL);
870  box->list_popped = FALSE;
871 }
872 
873 static void
874 gnc_completion_cell_gui_move (BasicCell* bcell)
875 {
876  PopBox* box = bcell->gui_private;
877 
878  completion_disconnect_signals ((CompletionCell*) bcell);
879 
880  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
881  NULL, NULL, NULL, NULL, NULL);
882 
883  reset_item_list_to_default_setup (bcell);
884 }
885 
886 static int
887 popup_get_height (GtkWidget* widget,
888  int space_available,
889  G_GNUC_UNUSED int row_height,
890  gpointer user_data)
891 {
892  PopBox* box = user_data;
893  GtkScrolledWindow* scrollwin = GNC_ITEM_LIST(widget)->scrollwin;
894  int height;
895 
896  // if popup_allocation_height set use that
897  if (box->item_edit->popup_allocation_height != -1)
898  height = box->item_edit->popup_allocation_height;
899  else
900  height = gnc_item_list_get_popup_height (GNC_ITEM_LIST(widget));
901 
902  if (height < space_available)
903  {
904  // if the list is empty height would be 0 so return 1 instead to
905  // satisfy the check_popup_height_is_true function
906  gint ret_height = height ? height : 1;
907 
908  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, ret_height);
909  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin),
910  GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
911  return ret_height;
912  }
913  else
914  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, -1);
915 
916  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin),
917  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
918  return space_available;
919 }
920 
921 static int
922 popup_autosize (GtkWidget* widget,
923  int max_width,
924  gpointer user_data)
925 {
926  PopBox* box = user_data;
927 
928  if (!box || !box->autosize)
929  return max_width;
930 
931  return gnc_item_list_autosize (GNC_ITEM_LIST(widget)) + 20;
932 }
933 
934 static void
935 popup_set_focus (GtkWidget* widget,
936  G_GNUC_UNUSED gpointer user_data)
937 {
938  /* An empty GtkTreeView grabbing focus causes the key_press events to be
939  * lost because there's no entry cell to handle them.
940  */
941  if (gnc_item_list_num_entries (GNC_ITEM_LIST(widget)))
942  gtk_widget_grab_focus (GTK_WIDGET (GNC_ITEM_LIST(widget)->tree_view));
943 }
944 
945 static void
946 popup_post_show (GtkWidget* widget,
947  G_GNUC_UNUSED gpointer user_data)
948 {
949  gnc_item_list_autosize (GNC_ITEM_LIST(widget));
950  gnc_item_list_show_selected (GNC_ITEM_LIST(widget));
951 }
952 
953 static int
954 popup_get_width (GtkWidget* widget,
955  G_GNUC_UNUSED gpointer user_data)
956 {
957  GtkAllocation alloc;
958  gtk_widget_get_allocation (GTK_WIDGET (GNC_ITEM_LIST(widget)->tree_view),
959  &alloc);
960  return alloc.width;
961 }
962 
963 static void
964 tree_view_size_allocate_cb (GtkWidget *widget,
965  G_GNUC_UNUSED GtkAllocation *allocation,
966  gpointer user_data)
967 {
968  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW(widget));
969  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(widget));
970  GtkTreeIter iter;
971  if (gtk_tree_selection_get_selected (selection, &model, &iter))
972  {
973  PopBox* box = user_data;
974  gint found_location;
975  gchar *item_text;
976  gtk_tree_model_get (model, &iter, TEXT_COL, &item_text,
977  FOUND_LOCATION_COL, &found_location, -1);
978  horizontal_scroll_to_found_text (box, item_text, found_location);
979  g_free (item_text);
980  }
981 }
982 
983 static gboolean
984 gnc_completion_cell_enter (BasicCell* bcell,
985  int* cursor_position,
986  int* start_selection,
987  int* end_selection)
988 {
989  CompletionCell* cell = (CompletionCell*) bcell;
990  PopBox* box = bcell->gui_private;
991  PopupToggle popup_toggle;
992 
993  gnc_item_edit_set_popup (box->item_edit,
994  GTK_WIDGET(box->item_list),
995  popup_get_height, popup_autosize,
996  popup_set_focus, popup_post_show,
997  popup_get_width, box);
998 
999  popup_toggle = box->item_edit->popup_toggle;
1000  gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton), FALSE);
1001  gtk_widget_set_visible (GTK_WIDGET(popup_toggle.tbutton), FALSE);
1002 
1003  GtkTreeViewColumn *column = gtk_tree_view_get_column (
1004  GTK_TREE_VIEW(box->item_list->tree_view), TEXT_COL);
1005  gtk_tree_view_column_clear_attributes (column, box->item_list->renderer);
1006  gtk_tree_view_column_add_attribute (column, box->item_list->renderer,
1007  "markup", TEXT_MARKUP_COL);
1008 
1009  g_signal_connect (G_OBJECT(box->item_list->tree_view), "size-allocate",
1010  G_CALLBACK(tree_view_size_allocate_cb), box);
1011 
1012  completion_connect_signals (cell);
1013 
1014  *cursor_position = -1;
1015  *start_selection = 0;
1016  *end_selection = -1;
1017 
1018  return TRUE;
1019 }
1020 
1021 static void
1022 gnc_completion_cell_leave (BasicCell* bcell)
1023 {
1024  PopBox* box = bcell->gui_private;
1025 
1026  completion_disconnect_signals ((CompletionCell*) bcell);
1027 
1028  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
1029  NULL, NULL, NULL, NULL, NULL);
1030 
1031  reset_item_list_to_default_setup (bcell);
1032 
1033  if (box->strict && !box->in_list_select)
1034  gnc_basic_cell_set_value_internal (bcell, "");
1035 }
1036 
1037 void
1039 {
1040  if (!cell)
1041  return;
1042 
1043  PopBox* box = cell->cell.gui_private;
1044  if (!box)
1045  return;
1046 
1047  box->strict = strict;
1048 }
1049 
1050 void
1052 {
1053  if (!cell)
1054  return;
1055 
1056  PopBox* box = cell->cell.gui_private;
1057  if (!box)
1058  return;
1059 
1060  box->autosize = autosize;
1061 }
The CompletionCell object implements a cell handler with a "combination-box" pull-down menu in it...
void gnc_completion_cell_reverse_sort(CompletionCell *cell, gboolean is_reversed)
Register the sort direction.
void gnc_completion_cell_set_sort_enabled(CompletionCell *cell, gboolean enabled)
Enable sorting of the menu item&#39;s contents.
void gnc_utf8_strip_invalid_and_controls(gchar *str)
Strip any non-utf8 characters and any control characters (everything < 0x20, , , ...
Public Declarations for GncItemList class.
Public declarations of GnucashRegister class.
Private declarations for GnucashSheet class.
GLib helper routines.
Generic api to store and retrieve preferences.
void gnc_completion_cell_add_menu_item(CompletionCell *cell, const char *menustr)
Add a menu item to the hash table list.
Public declarations for GncItemEdit class.
void gnc_completion_cell_set_strict(CompletionCell *cell, gboolean strict)
Determines whether the cell will accept strings not in the menu.
Declarations for the Table object.
void gnc_completion_cell_set_autosize(CompletionCell *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.