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 gboolean
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  return TRUE;
285 }
286 
287 static void
288 completion_disconnect_signals (CompletionCell* cell)
289 {
290  PopBox* box = cell->cell.gui_private;
291 
292  if (!box->signals_connected)
293  return;
294 
295  g_signal_handlers_disconnect_matched (G_OBJECT(box->item_list),
296  G_SIGNAL_MATCH_DATA,
297  0, 0, NULL, NULL, cell);
298 
299  box->signals_connected = FALSE;
300 }
301 
302 static void
303 completion_connect_signals (CompletionCell* cell)
304 {
305  PopBox* box = cell->cell.gui_private;
306 
307  if (box->signals_connected)
308  return;
309 
310  g_signal_connect (G_OBJECT(box->item_list), "select_item",
311  G_CALLBACK(select_item_cb), cell);
312 
313  g_signal_connect (G_OBJECT(box->item_list), "change_item",
314  G_CALLBACK(change_item_cb), cell);
315 
316  g_signal_connect (G_OBJECT(box->item_list), "activate_item",
317  G_CALLBACK(activate_item_cb), cell);
318 
319  g_signal_connect (G_OBJECT(box->item_list), "key_press_event",
320  G_CALLBACK(key_press_item_cb), cell);
321 
322  box->signals_connected = TRUE;
323 }
324 
325 static void
326 gnc_completion_cell_gui_destroy (BasicCell* bcell)
327 {
328  CompletionCell* cell = (CompletionCell*) bcell;
329 
330  if (!cell->cell.gui_realize)
331  {
332  PopBox* box = bcell->gui_private;
333  if (box)
334  {
335  if (box->item_list)
336  {
337  completion_disconnect_signals (cell);
338  g_object_unref (box->item_list);
339  box->item_list = NULL;
340  }
341  if (box->item_store)
342  {
343  g_object_unref (box->item_store);
344  box->item_store = NULL;
345  }
346  }
347  /* allow the widget to be shown again */
348  cell->cell.gui_realize = gnc_completion_cell_gui_realize;
349  cell->cell.gui_move = NULL;
350  cell->cell.enter_cell = NULL;
351  cell->cell.leave_cell = NULL;
352  cell->cell.gui_destroy = NULL;
353  }
354 }
355 
356 static void
357 gnc_completion_cell_destroy (BasicCell* bcell)
358 {
359  CompletionCell* cell = (CompletionCell*) bcell;
360  PopBox* box = cell->cell.gui_private;
361 
362  gnc_completion_cell_gui_destroy (& (cell->cell));
363 
364  if (box)
365  {
366  if (box->item_hash)
367  g_hash_table_destroy (box->item_hash);
368 
369  g_free (box);
370  cell->cell.gui_private = NULL;
371  }
372  cell->cell.gui_private = NULL;
373  cell->cell.gui_realize = NULL;
374 }
375 
376 static gint
377 sort_func (GtkTreeModel* model, GtkTreeIter* iter_a, GtkTreeIter* iter_b, gpointer user_data)
378 {
379  gint a_weight, b_weight;
380  gint ret = 0;
381 
382  gtk_tree_model_get (model, iter_a, WEIGHT_COL, &a_weight, -1);
383  gtk_tree_model_get (model, iter_b, WEIGHT_COL, &b_weight, -1);
384 
385  if (a_weight < b_weight)
386  ret = -1;
387  else if (a_weight > b_weight)
388  ret = 1;
389 
390  return ret;
391 }
392 
393 void
395  gboolean enabled)
396 {
397  if (!cell)
398  return;
399 
400  PopBox* box = cell->cell.gui_private;
401  box->sort_enabled = enabled;
402 }
403 
404 static void
405 set_sort_column_enabled (PopBox* box, gboolean enable)
406 {
407  if (enable)
408  {
409  gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(box->item_list->list_store),
410  WEIGHT_COL, sort_func, box->item_list, NULL);
411 
412  gnc_item_list_set_sort_column (box->item_list, WEIGHT_COL);
413  }
414  else
415  gnc_item_list_set_sort_column (box->item_list, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID);
416 }
417 
418 static void
419 item_store_clear (CompletionCell* cell)
420 {
421  if (!cell)
422  return;
423 
424  PopBox* box = cell->cell.gui_private;
425 
426  // disconnect list store from tree view
427  gnc_item_list_disconnect_store (box->item_list);
428 
429  block_list_signals (cell);
430 
431  if (box->sort_enabled) // if sorting, disable it
432  set_sort_column_enabled (box, FALSE);
433 
434  gtk_list_store_clear (box->item_store);
435 
436  if (box->sort_enabled) // if sorting, enable it
437  set_sort_column_enabled (box, TRUE);
438 
439  unblock_list_signals (cell);
440 
441  // reconect list store to tree view
442  gnc_item_list_connect_store (box->item_list, box->item_store);
443 
444  hide_popup (box);
445 }
446 
447 void
448 gnc_completion_cell_clear_menu (CompletionCell* cell)
449 {
450  if (!cell)
451  return;
452 
453  PopBox* box = cell->cell.gui_private;
454  if (!box)
455  return;
456 
457  if (box->item_list)
458  {
459  g_hash_table_remove_all (box->item_hash);
460  item_store_clear (cell);
461  box->occurrence = 0;
462  }
463 }
464 
465 void
467  const char* menustr)
468 {
469  if (!cell || !menustr)
470  return;
471 
472  PopBox* box = cell->cell.gui_private;
473 
474  if (box->item_hash)
475  {
476  gpointer value = g_hash_table_lookup (box->item_hash, menustr);
477  gboolean update = FALSE;
478  if (value)
479  {
480  if (!box->register_is_reversed)
481  update = TRUE;
482  }
483  else
484  update = TRUE;
485 
486  if (update)
487  {
488  g_hash_table_insert (box->item_hash, g_strdup (menustr),
489  GINT_TO_POINTER(box->occurrence));
490  }
491  box->occurrence++;
492  }
493 }
494 
495 void
496 gnc_completion_cell_set_value (CompletionCell* cell, const char* str)
497 {
498  if (!cell || !str)
499  return;
500 
501  gnc_basic_cell_set_value (&cell->cell, str);
502 }
503 
504 static inline void
505 list_store_append (GtkListStore *store, char* string,
506  char* markup, gint weight, gint found_location)
507 {
508  GtkTreeIter iter;
509 
510  g_return_if_fail (store);
511  g_return_if_fail (string);
512  g_return_if_fail (markup);
513 
514  gtk_list_store_append (store, &iter);
515 
516  gtk_list_store_set (store, &iter, TEXT_COL, string,
517  TEXT_MARKUP_COL, markup,
518  WEIGHT_COL, weight,
519  FOUND_LOCATION_COL, found_location, -1);
520 }
521 
522 static gint
523 test_and_add (PopBox* box, const gchar *text, gint start_pos,
524  gpointer key, gint occurrence_difference)
525 {
526  gint ret_value = -1;
527  gint text_length = g_utf8_strlen (text, -1);
528 
529  if (start_pos >= text_length)
530  return ret_value;
531 
532  gchar *sub_text = g_utf8_substring (text, start_pos, text_length);
533  int pos = 0, len = 0;
534  if (gnc_unicode_has_substring_base_chars (box->newval, sub_text, &pos, &len))
535  {
536  gchar *markup = NULL, *prefix = NULL, *match = NULL, *suffix = NULL;
537  gint found_location = start_pos + pos;
538  gboolean have_boundary = FALSE;
539  gint prefix_length;
540  gint weight;
541 
542  if (found_location > 0)
543  prefix = g_utf8_substring (text, 0, found_location);
544  else
545  prefix = g_strdup ("");
546 
547  prefix_length = g_utf8_strlen (prefix, -1);
548 
549  match = g_utf8_substring (text, found_location, found_location + len);
550 
551  if (pos >= 1)
552  {
553  gunichar prev = g_utf8_get_char (g_utf8_offset_to_pointer (sub_text, pos - 1));
554  if (prev && (g_unichar_isspace (prev) || g_unichar_ispunct (prev)))
555  have_boundary = TRUE;
556  else
557  ret_value = found_location + 1;
558  }
559 
560  suffix = g_utf8_substring (text, found_location + len, text_length);
561 
562  markup = g_markup_printf_escaped ("%s<b>%s</b>%s%s", prefix, match, suffix, " ");
563 
564  if ((prefix_length == 0 ) || have_boundary)
565  {
566  weight = occurrence_difference; // sorted by recent first
567 
568  if (gnc_unicode_compare_base_chars (sub_text, box->newval) == 0) // exact match
569  weight = 1;
570 
571  list_store_append (box->item_store, key, markup, weight, found_location);
572  }
573  g_free (markup);
574  g_free (prefix);
575  g_free (match);
576  g_free (suffix);
577  }
578  g_free (sub_text);
579  return ret_value;
580 }
581 
582 static void
583 add_item (gpointer key, gpointer value, gpointer user_data)
584 {
585  PopBox* box = user_data;
586  gchar *hash_entry = g_strdup (key);
587 
588  if (hash_entry && *hash_entry)
589  {
590  gint start_pos = 0;
591  gint occurrence_difference;
593 
594  if (box->register_is_reversed)
595  occurrence_difference = GPOINTER_TO_INT(value) + 1;
596  else
597  occurrence_difference = box->occurrence - GPOINTER_TO_INT(value);
598 
599  do
600  {
601  start_pos = test_and_add (box, hash_entry, start_pos, key, occurrence_difference);
602  }
603  while (start_pos != -1);
604  }
605  g_free (hash_entry);
606 }
607 
608 static void
609 select_first_entry_in_list (PopBox* box)
610 {
611  GtkTreeModel *model = gtk_tree_view_get_model (box->item_list->tree_view);
612  GtkTreeIter iter;
613  gchar* string;
614 
615  if (!gtk_tree_model_get_iter_first (model, &iter))
616  return;
617 
618  if (!gtk_tree_model_iter_next (model, &iter))
619  return;
620 
621  gtk_tree_model_get (model, &iter, TEXT_COL, &string, -1);
622 
623  gnc_item_list_select (box->item_list, string);
624 
625  GtkTreePath* path = gtk_tree_path_new_first ();
626  gtk_tree_view_scroll_to_cell (box->item_list->tree_view,
627  path, NULL, TRUE, 0.5, 0.0);
628  gtk_tree_path_free (path);
629  g_free (string);
630 }
631 
632 static void
633 populate_list_store (CompletionCell* cell, gchar* str)
634 {
635  PopBox* box = cell->cell.gui_private;
636 
637  box->in_list_select = FALSE;
638  box->item_edit->popup_allocation_height = -1;
639 
640  if (str && *str)
641  box->newval = g_strdup(str);
642  else
643  return;
644 
645  box->newval_len = g_utf8_strlen (str, -1);
646 
647  // disconnect list store from tree view
648  gnc_item_list_disconnect_store (box->item_list);
649 
650  block_list_signals (cell);
651 
652  if (box->sort_enabled) // if sorting, disable it
653  set_sort_column_enabled (box, FALSE);
654 
655  gtk_list_store_clear (box->item_store);
656 
657  // add the don't first entry
658  gchar *markup = g_markup_printf_escaped ("<i>%s</i>", DONT_TEXT);
659  list_store_append (box->item_store, DONT_TEXT, markup, 0, 0);
660  g_free (markup);
661 
662  // add to the list store
663  g_hash_table_foreach (box->item_hash, add_item, box);
664 
665  if (box->sort_enabled) // if sorting, enable it
666  set_sort_column_enabled (box, TRUE);
667 
668  unblock_list_signals (cell);
669 
670  // reconnect list store to tree view
671  gnc_item_list_connect_store (box->item_list, box->item_store);
672 
673  // reset horizontal scrolling
674  gtk_tree_view_column_queue_resize (gtk_tree_view_get_column (
675  box->item_list->tree_view, TEXT_COL));
676 
677  // if no entries, do not show popup
678  if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL(box->item_store), NULL) == 1)
679  {
680  hide_popup (box);
681  }
682  else
683  gnc_item_edit_show_popup (box->item_edit);
684 
685  block_list_signals (cell); // Prevent recursion, select first entry
686  select_first_entry_in_list (box);
687  unblock_list_signals (cell);
688 
689  g_free (box->newval);
690 }
691 
692 static void
693 gnc_completion_cell_modify_verify (BasicCell* bcell,
694  const char* change,
695  int change_len,
696  const char* newval,
697  int newval_len,
698  int* cursor_position,
699  int* start_selection,
700  int* end_selection)
701 {
702  CompletionCell* cell = (CompletionCell*) bcell;
703  PopBox* box = cell->cell.gui_private;
704 
705  if (box->in_list_select)
706  {
707  if (g_strcmp0 (newval, DONT_TEXT) == 0)
708  return;
709  gnc_basic_cell_set_value_internal (bcell, newval);
710  *cursor_position = -1;
711  *start_selection = 0;
712  *end_selection = 0;
713  return;
714  }
715 
716  // Are were deleting or inserting in the middle.
717  if (change == NULL || *cursor_position < bcell->value_chars)
718  *start_selection = *end_selection = *cursor_position;
719 
720  gchar *start_of_text = g_utf8_substring (newval, 0, *cursor_position);
721  populate_list_store (cell, start_of_text);
722  g_free (start_of_text);
723 
724  if (g_strcmp0 (newval, "") == 0)
725  {
726  block_list_signals (cell); // Prevent recursion, unselect all
727  gnc_item_list_select (box->item_list, NULL);
728  unblock_list_signals (cell);
729  hide_popup (box);
730  }
731  gnc_basic_cell_set_value_internal (bcell, newval);
732 }
733 
734 static char*
735 get_entry_from_hash_if_size_is_one (CompletionCell* cell)
736 {
737  if (!cell)
738  return NULL;
739 
740  PopBox* box = cell->cell.gui_private;
741 
742  if (box->item_hash && (g_hash_table_size (box->item_hash) == 1))
743  {
744  GList *keys = g_hash_table_get_keys (box->item_hash);
745  char *ret = g_strdup (keys->data);
746  g_list_free (keys);
747  return ret;
748  }
749  return NULL;
750 }
751 
752 static gboolean
753 gnc_completion_cell_direct_update (BasicCell* bcell,
754  int* cursor_position,
755  int* start_selection,
756  int* end_selection,
757  void* gui_data)
758 {
759  CompletionCell* cell = (CompletionCell*) bcell;
760  PopBox* box = cell->cell.gui_private;
761  GdkEventKey* event = gui_data;
762 
763  if (event->type != GDK_KEY_PRESS)
764  return FALSE;
765 
766  switch (event->keyval)
767  {
768  case GDK_KEY_Escape:
769  if (bcell->changed)
770  {
771  const char *value = gnc_table_get_model_entry (box->sheet->table, bcell->cell_name);
772 
773  gnc_basic_cell_set_value_internal (bcell, value);
774  bcell->changed = FALSE;
775  *cursor_position = 0;
776  *start_selection = 0;
777  *end_selection = -1;
778  return TRUE;
779  }
780  break;
781  case GDK_KEY_Tab:
782  case GDK_KEY_ISO_Left_Tab:
783  {
784  if (event->state & GDK_CONTROL_MASK)
785  {
786  char* hash_string = get_entry_from_hash_if_size_is_one (cell);
787 
788  if (hash_string)
789  {
790  gnc_basic_cell_set_value_internal (bcell, hash_string);
791  *cursor_position = strlen (hash_string);
792  }
793  g_free (hash_string);
794  return TRUE;
795  }
796 
797  char* string = gnc_item_list_get_selection (box->item_list);
798 
799  if (!string)
800  break;
801 
802  g_signal_emit_by_name (G_OBJECT(box->item_list), "change_item",
803  string, (gpointer)bcell);
804 
805  g_free (string);
806  break;
807  }
808  }
809 
810  if (box->strict)
811  box->in_list_select = gnc_item_in_list (box->item_list, bcell->value);
812 
813  if (!bcell->value)
814  item_store_clear (cell);
815 
816  return FALSE;
817 }
818 
819 void
821 {
822  if (!cell)
823  return;
824 
825  PopBox* box = cell->cell.gui_private;
826 
827  if (is_reversed != box->register_is_reversed)
828  {
829  gnc_completion_cell_clear_menu (cell);
830  box->register_is_reversed = is_reversed;
831  box->occurrence = 0;
832  }
833 }
834 
835 static void
836 gnc_completion_cell_gui_realize (BasicCell* bcell, gpointer data)
837 {
838  GnucashSheet* sheet = data;
839  GncItemEdit* item_edit = gnucash_sheet_get_item_edit (sheet);
840  CompletionCell* cell = (CompletionCell*) bcell;
841  PopBox* box = cell->cell.gui_private;
842 
843  /* initialize gui-specific, private data */
844  box->sheet = sheet;
845  box->item_edit = item_edit;
846  box->item_store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING,
847  G_TYPE_INT, G_TYPE_INT);
848  box->item_list = GNC_ITEM_LIST(gnc_item_list_new (box->item_store));
849 
850  block_list_signals (cell);
851  set_sort_column_enabled (box, FALSE);
852  unblock_list_signals (cell);
853 
854  gtk_widget_show_all (GTK_WIDGET(box->item_list));
855  g_object_ref_sink (box->item_list);
856 
857  /* to mark cell as realized, remove the realize method */
858  cell->cell.gui_realize = NULL;
859  cell->cell.gui_move = gnc_completion_cell_gui_move;
860  cell->cell.enter_cell = gnc_completion_cell_enter;
861  cell->cell.leave_cell = gnc_completion_cell_leave;
862  cell->cell.gui_destroy = gnc_completion_cell_gui_destroy;
863  cell->cell.modify_verify = gnc_completion_cell_modify_verify;
864  cell->cell.direct_update = gnc_completion_cell_direct_update;
865 }
866 
867 static void
868 reset_item_list_to_default_setup (BasicCell* bcell)
869 {
870  PopBox* box = bcell->gui_private;
871  PopupToggle popup_toggle;
872 
873  item_store_clear ((CompletionCell*) bcell);
874 
875  popup_toggle = box->item_edit->popup_toggle;
876  gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton), TRUE);
877  gtk_widget_set_visible (GTK_WIDGET(popup_toggle.tbutton), TRUE);
878 
879  GtkTreeViewColumn *column = gtk_tree_view_get_column (
880  GTK_TREE_VIEW(box->item_list->tree_view), TEXT_COL);
881  gtk_tree_view_column_clear_attributes (column,box->item_list->renderer);
882  gtk_tree_view_column_add_attribute (column, box->item_list->renderer,
883  "text", TEXT_COL);
884  box->list_popped = FALSE;
885 }
886 
887 static void
888 gnc_completion_cell_gui_move (BasicCell* bcell)
889 {
890  PopBox* box = bcell->gui_private;
891 
892  completion_disconnect_signals ((CompletionCell*) bcell);
893 
894  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
895  NULL, NULL, NULL, NULL, NULL);
896 
897  reset_item_list_to_default_setup (bcell);
898 }
899 
900 static int
901 popup_get_height (GtkWidget* widget,
902  int space_available,
903  G_GNUC_UNUSED int row_height,
904  gpointer user_data)
905 {
906  PopBox* box = user_data;
907  GtkScrolledWindow* scrollwin = GNC_ITEM_LIST(widget)->scrollwin;
908  int height;
909 
910  // if popup_allocation_height set use that
911  if (box->item_edit->popup_allocation_height != -1)
912  height = box->item_edit->popup_allocation_height;
913  else
914  height = gnc_item_list_get_popup_height (GNC_ITEM_LIST(widget));
915 
916  if (height < space_available)
917  {
918  // if the list is empty height would be 0 so return 1 instead to
919  // satisfy the check_popup_height_is_true function
920  gint ret_height = height ? height : 1;
921 
922  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, ret_height);
923  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin),
924  GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
925  return ret_height;
926  }
927  else
928  gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, -1);
929 
930  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin),
931  GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
932  return space_available;
933 }
934 
935 static int
936 popup_autosize (GtkWidget* widget,
937  int max_width,
938  gpointer user_data)
939 {
940  PopBox* box = user_data;
941 
942  if (!box || !box->autosize)
943  return max_width;
944 
945  return gnc_item_list_autosize (GNC_ITEM_LIST(widget)) + 20;
946 }
947 
948 static void
949 popup_set_focus (GtkWidget* widget,
950  G_GNUC_UNUSED gpointer user_data)
951 {
952  /* An empty GtkTreeView grabbing focus causes the key_press events to be
953  * lost because there's no entry cell to handle them.
954  */
955  if (gnc_item_list_num_entries (GNC_ITEM_LIST(widget)))
956  gtk_widget_grab_focus (GTK_WIDGET (GNC_ITEM_LIST(widget)->tree_view));
957 }
958 
959 static void
960 popup_post_show (GtkWidget* widget,
961  G_GNUC_UNUSED gpointer user_data)
962 {
963  gnc_item_list_autosize (GNC_ITEM_LIST(widget));
964  gnc_item_list_show_selected (GNC_ITEM_LIST(widget));
965 }
966 
967 static int
968 popup_get_width (GtkWidget* widget,
969  G_GNUC_UNUSED gpointer user_data)
970 {
971  GtkAllocation alloc;
972  gtk_widget_get_allocation (GTK_WIDGET (GNC_ITEM_LIST(widget)->tree_view),
973  &alloc);
974  return alloc.width;
975 }
976 
977 static void
978 tree_view_size_allocate_cb (GtkWidget *widget,
979  G_GNUC_UNUSED GtkAllocation *allocation,
980  gpointer user_data)
981 {
982  GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW(widget));
983  GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(widget));
984  GtkTreeIter iter;
985  if (gtk_tree_selection_get_selected (selection, &model, &iter))
986  {
987  PopBox* box = user_data;
988  gint found_location;
989  gchar *item_text;
990  gtk_tree_model_get (model, &iter, TEXT_COL, &item_text,
991  FOUND_LOCATION_COL, &found_location, -1);
992  horizontal_scroll_to_found_text (box, item_text, found_location);
993  g_free (item_text);
994  }
995 }
996 
997 static gboolean
998 gnc_completion_cell_enter (BasicCell* bcell,
999  int* cursor_position,
1000  int* start_selection,
1001  int* end_selection)
1002 {
1003  CompletionCell* cell = (CompletionCell*) bcell;
1004  PopBox* box = bcell->gui_private;
1005  PopupToggle popup_toggle;
1006 
1007  gnc_item_edit_set_popup (box->item_edit,
1008  GTK_WIDGET(box->item_list),
1009  popup_get_height, popup_autosize,
1010  popup_set_focus, popup_post_show,
1011  popup_get_width, box);
1012 
1013  popup_toggle = box->item_edit->popup_toggle;
1014  gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton), FALSE);
1015  gtk_widget_set_visible (GTK_WIDGET(popup_toggle.tbutton), FALSE);
1016 
1017  GtkTreeViewColumn *column = gtk_tree_view_get_column (
1018  GTK_TREE_VIEW(box->item_list->tree_view), TEXT_COL);
1019  gtk_tree_view_column_clear_attributes (column, box->item_list->renderer);
1020  gtk_tree_view_column_add_attribute (column, box->item_list->renderer,
1021  "markup", TEXT_MARKUP_COL);
1022 
1023  g_signal_connect (G_OBJECT(box->item_list->tree_view), "size-allocate",
1024  G_CALLBACK(tree_view_size_allocate_cb), box);
1025 
1026  completion_connect_signals (cell);
1027 
1028  *cursor_position = -1;
1029  *start_selection = 0;
1030  *end_selection = -1;
1031 
1032  return TRUE;
1033 }
1034 
1035 static void
1036 gnc_completion_cell_leave (BasicCell* bcell)
1037 {
1038  PopBox* box = bcell->gui_private;
1039 
1040  completion_disconnect_signals ((CompletionCell*) bcell);
1041 
1042  gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
1043  NULL, NULL, NULL, NULL, NULL);
1044 
1045  reset_item_list_to_default_setup (bcell);
1046 
1047  if (box->strict && !box->in_list_select)
1048  gnc_basic_cell_set_value_internal (bcell, "");
1049 }
1050 
1051 void
1053 {
1054  if (!cell)
1055  return;
1056 
1057  PopBox* box = cell->cell.gui_private;
1058  if (!box)
1059  return;
1060 
1061  box->strict = strict;
1062 }
1063 
1064 void
1066 {
1067  if (!cell)
1068  return;
1069 
1070  PopBox* box = cell->cell.gui_private;
1071  if (!box)
1072  return;
1073 
1074  box->autosize = autosize;
1075 }
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.