GnuCash c935c2f+
Loading...
Searching...
No Matches
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
49typedef 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
79enum GncCompletionColumn
80{
81 TEXT_COL, //0
82 TEXT_MARKUP_COL, //1
83 WEIGHT_COL, //2
84 FOUND_LOCATION_COL, //3
85};
86
87static void gnc_completion_cell_gui_realize (BasicCell* bcell, gpointer w);
88static void gnc_completion_cell_gui_move (BasicCell* bcell);
89static void gnc_completion_cell_gui_destroy (BasicCell* bcell);
90static gboolean gnc_completion_cell_enter (BasicCell* bcell,
91 int* cursor_position,
92 int* start_selection,
93 int* end_selection);
94static void gnc_completion_cell_leave (BasicCell* bcell);
95static void gnc_completion_cell_destroy (BasicCell* bcell);
96
97BasicCell*
98gnc_completion_cell_new (void)
99{
100 CompletionCell* cell = g_new0 (CompletionCell, 1);
101 gnc_completion_cell_init (cell);
102 return &cell->cell;
103}
104
105void
106gnc_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
140static void
141hide_popup (PopBox* box)
142{
143 gnc_item_edit_hide_popup (box->item_edit);
144 box->list_popped = FALSE;
145}
146
147static void
148select_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
160static gint
161text_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
169static void
170horizontal_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
209static void
210change_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
230static void
231activate_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
238static void
239block_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
251static void
252unblock_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
264static gboolean
265key_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
287static void
288completion_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
302static void
303completion_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
325static void
326gnc_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
356static void
357gnc_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
376static gint
377sort_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
393void
395 gboolean enabled)
396{
397 if (!cell)
398 return;
399
400 PopBox* box = cell->cell.gui_private;
401 box->sort_enabled = enabled;
402}
403
404static void
405set_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
418static void
419item_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
447void
448gnc_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
465void
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
495void
496gnc_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
504static inline void
505list_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
522static gint
523test_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
582static void
583add_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
608static void
609select_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
632static void
633populate_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
692static void
693gnc_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
734static char*
735get_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
752static gboolean
753gnc_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
819void
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
835static void
836gnc_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
867static void
868reset_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
887static void
888gnc_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
900static int
901popup_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
935static int
936popup_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
948static void
949popup_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
959static void
960popup_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
967static int
968popup_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
977static void
978tree_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
997static gboolean
998gnc_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
1035static void
1036gnc_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
1051void
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
1064void
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}
GLib helper routines.
Generic api to store and retrieve preferences.
Public declarations for GncItemEdit class.
Public Declarations for GncItemList class.
Private declarations for GnucashSheet class.
Public declarations of GnucashRegister class.
void gnc_completion_cell_set_strict(CompletionCell *cell, gboolean strict)
Determines whether the cell will accept strings not in the menu.
void gnc_completion_cell_add_menu_item(CompletionCell *cell, const char *menustr)
Add a menu item to the hash table list.
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's contents.
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's active GtkListStore.
void gnc_utf8_strip_invalid_and_controls(gchar *str)
Strip any non-utf8 characters and any control characters (everything < 0x20, , \f,...
The CompletionCell object implements a cell handler with a "combination-box" pull-down menu in it.
Declarations for the Table object.