37 #include <gdk/gdkkeysyms.h> 39 #include "completioncell.h" 47 #include <gnc-unicode.h> 49 typedef struct _PopBox
55 GHashTable* item_hash;
56 GtkListStore* item_store;
61 gboolean signals_connected;
66 gboolean sort_enabled;
67 gboolean register_is_reversed;
70 gboolean in_list_select;
76 #define DONT_TEXT N_("Don't autocomplete") 79 enum GncCompletionColumn
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,
94 static void gnc_completion_cell_leave (BasicCell* bcell);
95 static void gnc_completion_cell_destroy (BasicCell* bcell);
98 gnc_completion_cell_new (
void)
101 gnc_completion_cell_init (cell);
108 gnc_basic_cell_init (& (cell->cell));
110 cell->cell.is_popup = TRUE;
112 cell->cell.destroy = gnc_completion_cell_destroy;
114 cell->cell.gui_realize = gnc_completion_cell_gui_realize;
115 cell->cell.gui_destroy = gnc_completion_cell_gui_destroy;
120 box->item_edit = NULL;
121 box->item_list = NULL;
122 box->item_store = NULL;
124 box->signals_connected = FALSE;
125 box->list_popped = FALSE;
126 box->autosize = FALSE;
127 box->register_is_reversed = FALSE;
129 box->sort_enabled = FALSE;
131 cell->cell.gui_private = box;
134 box->in_list_select = FALSE;
137 box->item_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
143 gnc_item_edit_hide_popup (box->item_edit);
144 box->list_popped = FALSE;
148 select_item_cb (
GncItemList* item_list,
char* item_string, gpointer user_data)
151 PopBox* box = cell->cell.gui_private;
153 box->in_list_select = TRUE;
154 gnucash_sheet_modify_current_cell (box->sheet, item_string);
155 box->in_list_select = FALSE;
161 text_width (PangoLayout *layout)
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;
170 horizontal_scroll_to_found_text (
PopBox* box,
char* item_string, gint found_location)
172 if (!gtk_widget_get_realized (GTK_WIDGET(box->item_list->tree_view)))
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);
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);
188 gint item_string_width = text_width (layout);
190 pango_layout_set_text (layout, start_string, -1);
192 gint start_string_width = text_width (layout);
194 pango_attr_list_unref (atlist);
195 g_object_unref (layout);
196 g_free (start_string);
198 if (item_string_width <= alloc.width)
201 scroll_point = start_string_width - alloc.width / 2;
203 if (scroll_point < 0)
206 gtk_tree_view_scroll_to_point (box->item_list->tree_view, scroll_point, -1);
210 change_item_cb (
GncItemList* item_list,
char* item_string, gpointer user_data)
213 PopBox* box = cell->cell.gui_private;
215 box->in_list_select = TRUE;
216 gnucash_sheet_modify_current_cell (box->sheet, item_string);
217 box->in_list_select = FALSE;
219 GtkTreeModel *model = gtk_tree_view_get_model (item_list->tree_view);
220 GtkTreeSelection *selection = gtk_tree_view_get_selection (item_list->tree_view);
222 if (gtk_tree_selection_get_selected (selection, &model, &iter))
225 gtk_tree_model_get (model, &iter, FOUND_LOCATION_COL, &found_location, -1);
226 horizontal_scroll_to_found_text (box, item_string, found_location);
231 activate_item_cb (
GncItemList* item_list,
char* item_string, gpointer user_data)
234 PopBox* box = cell->cell.gui_private;
241 PopBox* box = cell->cell.gui_private;
243 if (!box->signals_connected)
246 g_signal_handlers_block_matched (G_OBJECT(box->item_list),
248 0, 0, NULL, NULL, cell);
254 PopBox* box = cell->cell.gui_private;
256 if (!box->signals_connected)
259 g_signal_handlers_unblock_matched (G_OBJECT(box->item_list),
261 0, 0, NULL, NULL, cell);
265 key_press_item_cb (
GncItemList* item_list, GdkEventKey* event, gpointer user_data)
268 PopBox* box = cell->cell.gui_private;
270 switch (event->keyval)
273 block_list_signals (cell);
274 gnc_item_list_select (box->item_list, NULL);
275 unblock_list_signals (cell);
280 gtk_widget_event (GTK_WIDGET (box->sheet),
289 PopBox* box = cell->cell.gui_private;
291 if (!box->signals_connected)
294 g_signal_handlers_disconnect_matched (G_OBJECT(box->item_list),
296 0, 0, NULL, NULL, cell);
298 box->signals_connected = FALSE;
304 PopBox* box = cell->cell.gui_private;
306 if (box->signals_connected)
309 g_signal_connect (G_OBJECT(box->item_list),
"select_item",
310 G_CALLBACK(select_item_cb), cell);
312 g_signal_connect (G_OBJECT(box->item_list),
"change_item",
313 G_CALLBACK(change_item_cb), cell);
315 g_signal_connect (G_OBJECT(box->item_list),
"activate_item",
316 G_CALLBACK(activate_item_cb), cell);
318 g_signal_connect (G_OBJECT(box->item_list),
"key_press_event",
319 G_CALLBACK(key_press_item_cb), cell);
321 box->signals_connected = TRUE;
325 gnc_completion_cell_gui_destroy (BasicCell* bcell)
329 if (!cell->cell.gui_realize)
331 PopBox* box = bcell->gui_private;
336 completion_disconnect_signals (cell);
337 g_object_unref (box->item_list);
338 box->item_list = NULL;
342 g_object_unref (box->item_store);
343 box->item_store = NULL;
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;
356 gnc_completion_cell_destroy (BasicCell* bcell)
359 PopBox* box = cell->cell.gui_private;
361 gnc_completion_cell_gui_destroy (& (cell->cell));
366 g_hash_table_destroy (box->item_hash);
369 cell->cell.gui_private = NULL;
371 cell->cell.gui_private = NULL;
372 cell->cell.gui_realize = NULL;
376 sort_func (GtkTreeModel* model, GtkTreeIter* iter_a, GtkTreeIter* iter_b, gpointer user_data)
378 gint a_weight, b_weight;
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);
384 if (a_weight < b_weight)
386 else if (a_weight > b_weight)
399 PopBox* box = cell->cell.gui_private;
400 box->sort_enabled = enabled;
404 set_sort_column_enabled (
PopBox* box, gboolean enable)
408 gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE(box->item_list->list_store),
409 WEIGHT_COL, sort_func, box->item_list, NULL);
411 gnc_item_list_set_sort_column (box->item_list, WEIGHT_COL);
414 gnc_item_list_set_sort_column (box->item_list, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID);
423 PopBox* box = cell->cell.gui_private;
426 gnc_item_list_disconnect_store (box->item_list);
428 block_list_signals (cell);
430 if (box->sort_enabled)
431 set_sort_column_enabled (box, FALSE);
433 gtk_list_store_clear (box->item_store);
435 if (box->sort_enabled)
436 set_sort_column_enabled (box, TRUE);
438 unblock_list_signals (cell);
441 gnc_item_list_connect_store (box->item_list, box->item_store);
452 PopBox* box = cell->cell.gui_private;
458 g_hash_table_remove_all (box->item_hash);
459 item_store_clear (cell);
468 if (!cell || !menustr)
471 PopBox* box = cell->cell.gui_private;
475 gpointer value = g_hash_table_lookup (box->item_hash, menustr);
476 gboolean update = FALSE;
479 if (!box->register_is_reversed)
487 g_hash_table_insert (box->item_hash, g_strdup (menustr),
488 GINT_TO_POINTER(box->occurrence));
495 gnc_completion_cell_set_value (
CompletionCell* cell,
const char* str)
500 gnc_basic_cell_set_value (&cell->cell, str);
504 list_store_append (GtkListStore *store,
char*
string,
505 char* markup, gint weight, gint found_location)
509 g_return_if_fail (store);
510 g_return_if_fail (
string);
511 g_return_if_fail (markup);
513 gtk_list_store_append (store, &iter);
515 gtk_list_store_set (store, &iter, TEXT_COL,
string,
516 TEXT_MARKUP_COL, markup,
518 FOUND_LOCATION_COL, found_location, -1);
522 test_and_add (
PopBox* box,
const gchar *text, gint start_pos,
523 gpointer key, gint occurrence_difference)
526 gint text_length = g_utf8_strlen (text, -1);
528 if (start_pos >= text_length)
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))
535 gchar *markup = NULL, *prefix = NULL, *match = NULL, *suffix = NULL;
536 gint found_location = start_pos + pos;
537 gboolean have_boundary = FALSE;
541 if (found_location > 0)
542 prefix = g_utf8_substring (text, 0, found_location);
544 prefix = g_strdup (
"");
546 prefix_length = g_utf8_strlen (prefix, -1);
548 match = g_utf8_substring (text, found_location, found_location + len);
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;
556 ret_value = found_location + 1;
559 suffix = g_utf8_substring (text, found_location + len, text_length);
561 markup = g_markup_printf_escaped (
"%s<b>%s</b>%s%s", prefix, match, suffix,
" ");
563 if ((prefix_length == 0 ) || have_boundary)
565 weight = occurrence_difference;
567 if (gnc_unicode_compare_base_chars (sub_text, box->newval) == 0)
570 list_store_append (box->item_store, key, markup, weight, found_location);
582 add_item (gpointer key, gpointer value, gpointer user_data)
585 gchar *hash_entry = g_strdup (key);
587 if (hash_entry && *hash_entry)
590 gint occurrence_difference;
593 if (box->register_is_reversed)
594 occurrence_difference = GPOINTER_TO_INT(value) + 1;
596 occurrence_difference = box->occurrence - GPOINTER_TO_INT(value);
600 start_pos = test_and_add (box, hash_entry, start_pos, key, occurrence_difference);
602 while (start_pos != -1);
608 select_first_entry_in_list (
PopBox* box)
610 GtkTreeModel *model = gtk_tree_view_get_model (box->item_list->tree_view);
614 if (!gtk_tree_model_get_iter_first (model, &iter))
617 if (!gtk_tree_model_iter_next (model, &iter))
620 gtk_tree_model_get (model, &iter, TEXT_COL, &
string, -1);
622 gnc_item_list_select (box->item_list,
string);
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);
634 PopBox* box = cell->cell.gui_private;
636 box->in_list_select = FALSE;
637 box->item_edit->popup_allocation_height = -1;
640 box->newval = g_strdup(str);
644 box->newval_len = g_utf8_strlen (str, -1);
647 gnc_item_list_disconnect_store (box->item_list);
649 block_list_signals (cell);
651 if (box->sort_enabled)
652 set_sort_column_enabled (box, FALSE);
654 gtk_list_store_clear (box->item_store);
657 gchar *markup = g_markup_printf_escaped (
"<i>%s</i>", DONT_TEXT);
658 list_store_append (box->item_store, DONT_TEXT, markup, 0, 0);
662 g_hash_table_foreach (box->item_hash, add_item, box);
664 if (box->sort_enabled)
665 set_sort_column_enabled (box, TRUE);
667 unblock_list_signals (cell);
670 gnc_item_list_connect_store (box->item_list, box->item_store);
673 gtk_tree_view_column_queue_resize (gtk_tree_view_get_column (
674 box->item_list->tree_view, TEXT_COL));
677 if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL(box->item_store), NULL) == 1)
682 gnc_item_edit_show_popup (box->item_edit);
684 block_list_signals (cell);
685 select_first_entry_in_list (box);
686 unblock_list_signals (cell);
688 g_free (box->newval);
692 gnc_completion_cell_modify_verify (BasicCell* bcell,
697 int* cursor_position,
698 int* start_selection,
702 PopBox* box = cell->cell.gui_private;
704 if (box->in_list_select)
706 if (g_strcmp0 (newval, DONT_TEXT) == 0)
708 gnc_basic_cell_set_value_internal (bcell, newval);
709 *cursor_position = -1;
710 *start_selection = 0;
716 if (change == NULL || *cursor_position < bcell->value_chars)
717 *start_selection = *end_selection = *cursor_position;
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);
723 if (g_strcmp0 (newval,
"") == 0)
725 block_list_signals (cell);
726 gnc_item_list_select (box->item_list, NULL);
727 unblock_list_signals (cell);
730 gnc_basic_cell_set_value_internal (bcell, newval);
739 PopBox* box = cell->cell.gui_private;
741 if (box->item_hash && (g_hash_table_size (box->item_hash) == 1))
743 GList *keys = g_hash_table_get_keys (box->item_hash);
744 char *ret = g_strdup (keys->data);
752 gnc_completion_cell_direct_update (BasicCell* bcell,
753 int* cursor_position,
754 int* start_selection,
759 PopBox* box = cell->cell.gui_private;
760 GdkEventKey*
event = gui_data;
762 if (event->type != GDK_KEY_PRESS)
765 switch (event->keyval)
768 case GDK_KEY_ISO_Left_Tab:
770 if (event->state & GDK_CONTROL_MASK)
772 char* hash_string = get_entry_from_hash_if_size_is_one (cell);
776 gnc_basic_cell_set_value_internal (bcell, hash_string);
777 *cursor_position = strlen (hash_string);
779 g_free (hash_string);
788 g_signal_emit_by_name (G_OBJECT(box->item_list),
"change_item",
789 string, (gpointer)bcell);
797 box->in_list_select = gnc_item_in_list (box->item_list, bcell->value);
800 item_store_clear (cell);
811 PopBox* box = cell->cell.gui_private;
813 if (is_reversed != box->register_is_reversed)
815 gnc_completion_cell_clear_menu (cell);
816 box->register_is_reversed = is_reversed;
822 gnc_completion_cell_gui_realize (BasicCell* bcell, gpointer data)
824 GnucashSheet* sheet = data;
825 GncItemEdit* item_edit = gnucash_sheet_get_item_edit (sheet);
827 PopBox* box = cell->cell.gui_private;
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));
836 block_list_signals (cell);
837 set_sort_column_enabled (box, FALSE);
838 unblock_list_signals (cell);
840 gtk_widget_show_all (GTK_WIDGET(box->item_list));
841 g_object_ref_sink (box->item_list);
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;
854 reset_item_list_to_default_setup (BasicCell* bcell)
856 PopBox* box = bcell->gui_private;
857 PopupToggle popup_toggle;
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);
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,
870 box->list_popped = FALSE;
874 gnc_completion_cell_gui_move (BasicCell* bcell)
876 PopBox* box = bcell->gui_private;
880 gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
881 NULL, NULL, NULL, NULL, NULL);
883 reset_item_list_to_default_setup (bcell);
887 popup_get_height (GtkWidget* widget,
889 G_GNUC_UNUSED
int row_height,
893 GtkScrolledWindow* scrollwin = GNC_ITEM_LIST(widget)->scrollwin;
897 if (box->item_edit->popup_allocation_height != -1)
898 height = box->item_edit->popup_allocation_height;
900 height = gnc_item_list_get_popup_height (GNC_ITEM_LIST(widget));
902 if (height < space_available)
906 gint ret_height = height ? height : 1;
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);
914 gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, -1);
916 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW(scrollwin),
917 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
918 return space_available;
922 popup_autosize (GtkWidget* widget,
928 if (!box || !box->autosize)
931 return gnc_item_list_autosize (GNC_ITEM_LIST(widget)) + 20;
935 popup_set_focus (GtkWidget* widget,
936 G_GNUC_UNUSED gpointer user_data)
941 if (gnc_item_list_num_entries (GNC_ITEM_LIST(widget)))
942 gtk_widget_grab_focus (GTK_WIDGET (GNC_ITEM_LIST(widget)->tree_view));
946 popup_post_show (GtkWidget* widget,
947 G_GNUC_UNUSED gpointer user_data)
949 gnc_item_list_autosize (GNC_ITEM_LIST(widget));
950 gnc_item_list_show_selected (GNC_ITEM_LIST(widget));
954 popup_get_width (GtkWidget* widget,
955 G_GNUC_UNUSED gpointer user_data)
958 gtk_widget_get_allocation (GTK_WIDGET (GNC_ITEM_LIST(widget)->tree_view),
964 tree_view_size_allocate_cb (GtkWidget *widget,
965 G_GNUC_UNUSED GtkAllocation *allocation,
968 GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW(widget));
969 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(widget));
971 if (gtk_tree_selection_get_selected (selection, &model, &iter))
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);
984 gnc_completion_cell_enter (BasicCell* bcell,
985 int* cursor_position,
986 int* start_selection,
990 PopBox* box = bcell->gui_private;
991 PopupToggle popup_toggle;
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);
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);
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);
1009 g_signal_connect (G_OBJECT(box->item_list->tree_view),
"size-allocate",
1010 G_CALLBACK(tree_view_size_allocate_cb), box);
1012 completion_connect_signals (cell);
1014 *cursor_position = -1;
1015 *start_selection = 0;
1016 *end_selection = -1;
1022 gnc_completion_cell_leave (BasicCell* bcell)
1024 PopBox* box = bcell->gui_private;
1028 gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
1029 NULL, NULL, NULL, NULL, NULL);
1031 reset_item_list_to_default_setup (bcell);
1033 if (box->strict && !box->in_list_select)
1034 gnc_basic_cell_set_value_internal (bcell,
"");
1043 PopBox* box = cell->cell.gui_private;
1047 box->strict = strict;
1056 PopBox* box = cell->cell.gui_private;
1060 box->autosize = autosize;
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'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.
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's active GtkListStore.