GnuCash c935c2f+
Loading...
Searching...
No Matches
combocell-gnome.c
1/********************************************************************\
2 * combocell-gnome.c -- implement combobox pull down cell for gnome *
3 * *
4 * This program is free software; you can redistribute it and/or *
5 * modify it under the terms of the GNU General Public License as *
6 * published by the Free Software Foundation; either version 2 of *
7 * the License, or (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License*
15 * along with this program; if not, contact: *
16 * *
17 * Free Software Foundation Voice: +1-617-542-5942 *
18 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
19 * Boston, MA 02110-1301, USA gnu@gnu.org *
20 * *
21\********************************************************************/
22
23/*
24 * FILE: combocell-gnome.c
25 *
26 * FUNCTION: Implement gnome portion of a pull-down combo widget
27 * embedded in a table cell.
28 *
29 * HISTORY:
30 * Copyright (c) 1998 Linas Vepstas <linas@linas.org>
31 * Copyright (c) 1998-1999 Rob Browning <rlb@cs.utexas.edu>
32 * Copyright (c) 2000 Linas Vepstas <linas@linas.org>
33 * Copyright (c) 2006 David Hampton <hampton@employees.org>
34 */
35
36#include <config.h>
37
38#include <string.h>
39#include <gdk/gdkkeysyms.h>
40
41#include "QuickFill.h"
42#include "combocell.h"
43#include "gnc-prefs.h"
44#include "gnucash-item-edit.h"
45#include "gnucash-item-list.h"
46#include "gnucash-sheet.h"
47#include "gnucash-sheetP.h"
48#include "table-allgui.h"
49#include "Account.h"
50#include "gnc-glib-utils.h"
51
52#define GNC_PREF_AUTO_RAISE_LISTS "auto-raise-lists"
53
54typedef struct _PopBox
55{
56 GnucashSheet* sheet;
57 GncItemEdit* item_edit;
58 GncItemList* item_list;
59 GtkListStore* tmp_store;
60
61 gboolean signals_connected; /* list signals connected? */
62
63 gboolean list_popped; /* list is popped up? */
64
65 gboolean autosize;
66
67 QuickFill* qf;
68 gboolean use_quickfill_cache; /* If TRUE, we don't own the qf */
69
70 gboolean in_list_select;
71
72 gboolean strict;
73
74 gunichar complete_char; /* char to be used for auto-completion */
75
76 GList* ignore_strings;
77
78} PopBox;
79
80
81static void gnc_combo_cell_gui_realize (BasicCell* bcell, gpointer w);
82static void gnc_combo_cell_gui_move (BasicCell* bcell);
83static void gnc_combo_cell_gui_destroy (BasicCell* bcell);
84static gboolean gnc_combo_cell_enter (BasicCell* bcell,
85 int* cursor_position,
86 int* start_selection,
87 int* end_selection);
88static void gnc_combo_cell_leave (BasicCell* bcell);
89static void gnc_combo_cell_destroy (BasicCell* bcell);
90
91static GOnce auto_pop_init_once = G_ONCE_INIT;
92static gboolean auto_pop_combos = FALSE;
93
94
95static void
96gnc_combo_cell_set_autopop (gpointer prefs, gchar* pref, gpointer user_data)
97{
98 auto_pop_combos = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
99 GNC_PREF_AUTO_RAISE_LISTS);
100}
101
102static gpointer
103gnc_combo_cell_autopop_init (gpointer unused)
104{
105 gulong id;
106 auto_pop_combos = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
107 GNC_PREF_AUTO_RAISE_LISTS);
108
109 id = gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REGISTER,
110 GNC_PREF_AUTO_RAISE_LISTS,
111 gnc_combo_cell_set_autopop,
112 NULL);
113
114 gnc_prefs_set_reg_auto_raise_lists_id (id);
115 return NULL;
116}
117
118BasicCell*
119gnc_combo_cell_new (void)
120{
121 ComboCell* cell;
122
123 g_once (&auto_pop_init_once, gnc_combo_cell_autopop_init, NULL);
124
125 cell = g_new0 (ComboCell, 1);
126
127 gnc_combo_cell_init (cell);
128
129 return &cell->cell;
130}
131
132void
133gnc_combo_cell_init (ComboCell* cell)
134{
135 PopBox* box;
136
137 gnc_basic_cell_init (& (cell->cell));
138
139 cell->cell.is_popup = TRUE;
140
141 cell->cell.destroy = gnc_combo_cell_destroy;
142
143 cell->cell.gui_realize = gnc_combo_cell_gui_realize;
144 cell->cell.gui_destroy = gnc_combo_cell_gui_destroy;
145
146 box = g_new0 (PopBox, 1);
147
148 box->sheet = NULL;
149 box->item_edit = NULL;
150 box->item_list = NULL;
151 box->tmp_store = gtk_list_store_new (1, G_TYPE_STRING);
152 box->signals_connected = FALSE;
153 box->list_popped = FALSE;
154 box->autosize = FALSE;
155
156 cell->cell.gui_private = box;
157
158 box->qf = gnc_quickfill_new();
159 box->use_quickfill_cache = FALSE;
160
161 box->in_list_select = FALSE;
162
163 box->strict = TRUE;
164
165 box->complete_char = '\0';
166
167 box->ignore_strings = NULL;
168}
169
170static void
171select_item_cb (GncItemList* item_list, char* item_string, gpointer data)
172{
173 ComboCell* cell = data;
174 PopBox* box = cell->cell.gui_private;
175
176 box->in_list_select = TRUE;
177 gnucash_sheet_modify_current_cell (box->sheet, item_string);
178 box->in_list_select = FALSE;
179
180 gnc_item_edit_hide_popup (box->item_edit);
181 box->list_popped = FALSE;
182}
183
184static void
185change_item_cb (GncItemList* item_list, char* item_string, gpointer data)
186{
187 ComboCell* cell = data;
188 PopBox* box = cell->cell.gui_private;
189
190 box->in_list_select = TRUE;
191 gnucash_sheet_modify_current_cell (box->sheet, item_string);
192 box->in_list_select = FALSE;
193}
194
195static void
196activate_item_cb (GncItemList* item_list, char* item_string, gpointer data)
197{
198 ComboCell* cell = data;
199 PopBox* box = cell->cell.gui_private;
200
201 gnc_item_edit_hide_popup (box->item_edit);
202 box->list_popped = FALSE;
203}
204
205static gboolean
206key_press_item_cb (GncItemList* item_list, GdkEventKey* event, gpointer data)
207{
208 ComboCell* cell = data;
209 PopBox* box = cell->cell.gui_private;
210
211 switch (event->keyval)
212 {
213 case GDK_KEY_Escape:
214 gnc_item_edit_hide_popup (box->item_edit);
215 box->list_popped = FALSE;
216 break;
217
218 default:
219 gtk_widget_event (GTK_WIDGET (box->sheet),
220 (GdkEvent*) event);
221 break;
222 }
223 return TRUE;
224}
225
226static void
227combo_disconnect_signals (ComboCell* cell)
228{
229 PopBox* box = cell->cell.gui_private;
230
231 if (!box->signals_connected)
232 return;
233
234 g_signal_handlers_disconnect_matched (G_OBJECT (box->item_list),
235 G_SIGNAL_MATCH_DATA,
236 0, 0, NULL, NULL, cell);
237
238 box->signals_connected = FALSE;
239}
240
241static void
242combo_connect_signals (ComboCell* cell)
243{
244 PopBox* box = cell->cell.gui_private;
245
246 if (box->signals_connected)
247 return;
248
249 g_signal_connect (G_OBJECT (box->item_list), "select_item",
250 G_CALLBACK (select_item_cb), cell);
251
252 g_signal_connect (G_OBJECT (box->item_list), "change_item",
253 G_CALLBACK (change_item_cb), cell);
254
255 g_signal_connect (G_OBJECT (box->item_list), "activate_item",
256 G_CALLBACK (activate_item_cb), cell);
257
258 g_signal_connect (G_OBJECT (box->item_list), "key_press_event",
259 G_CALLBACK (key_press_item_cb), cell);
260
261 box->signals_connected = TRUE;
262}
263
264static void
265block_list_signals (ComboCell* cell)
266{
267 PopBox* box = cell->cell.gui_private;
268
269 if (!box->signals_connected)
270 return;
271
272 g_signal_handlers_block_matched (G_OBJECT (box->item_list),
273 G_SIGNAL_MATCH_DATA,
274 0, 0, NULL, NULL, cell);
275}
276
277static void
278unblock_list_signals (ComboCell* cell)
279{
280 PopBox* box = cell->cell.gui_private;
281
282 if (!box->signals_connected)
283 return;
284
285 g_signal_handlers_unblock_matched (G_OBJECT (box->item_list),
286 G_SIGNAL_MATCH_DATA,
287 0, 0, NULL, NULL, cell);
288}
289
290static void
291gnc_combo_cell_gui_destroy (BasicCell* bcell)
292{
293 PopBox* box = bcell->gui_private;
294 ComboCell* cell = (ComboCell*) bcell;
295
296 if (cell->cell.gui_realize == NULL)
297 {
298 if (box != NULL && box->item_list != NULL)
299 {
300 combo_disconnect_signals (cell);
301 g_object_unref (box->item_list);
302 box->item_list = NULL;
303 }
304
305 if (box && box->tmp_store)
306 {
307 g_object_unref (box->tmp_store);
308 box->tmp_store = NULL;
309 }
310
311 /* allow the widget to be shown again */
312 cell->cell.gui_realize = gnc_combo_cell_gui_realize;
313 cell->cell.gui_move = NULL;
314 cell->cell.enter_cell = NULL;
315 cell->cell.leave_cell = NULL;
316 cell->cell.gui_destroy = NULL;
317 }
318}
319
320static void
321gnc_combo_cell_destroy (BasicCell* bcell)
322{
323 ComboCell* cell = (ComboCell*) bcell;
324 PopBox* box = cell->cell.gui_private;
325
326 gnc_combo_cell_gui_destroy (& (cell->cell));
327
328 if (box != NULL)
329 {
330 /* Don't destroy the qf if its not ours to destroy */
331 if (FALSE == box->use_quickfill_cache)
332 {
333 gnc_quickfill_destroy (box->qf);
334 box->qf = NULL;
335 }
336
337 g_list_free_full (box->ignore_strings, g_free);
338 box->ignore_strings = NULL;
339
340 g_free (box);
341 cell->cell.gui_private = NULL;
342 }
343
344 cell->cell.gui_private = NULL;
345 cell->cell.gui_realize = NULL;
346}
347
348void
350{
351 PopBox* box;
352
353 if (cell == NULL)
354 return;
355
356 box = cell->cell.gui_private;
357 if (box->item_list == NULL)
358 return;
359
360 block_list_signals (cell);
361 gnc_item_list_set_sort_column (box->item_list, 0);
362 unblock_list_signals (cell);
363}
364
365void
366gnc_combo_cell_clear_menu (ComboCell* cell)
367{
368 PopBox* box;
369
370 if (cell == NULL)
371 return;
372
373 box = cell->cell.gui_private;
374 if (box == NULL)
375 return;
376
377 /* Don't destroy the qf if its not ours to destroy */
378 if (FALSE == box->use_quickfill_cache)
379 {
380 gnc_quickfill_destroy (box->qf);
381 box->qf = gnc_quickfill_new();
382 }
383
384 if (box->item_list != NULL)
385 {
386 block_list_signals (cell);
387
388 gnc_item_list_clear (box->item_list);
389 gnc_item_edit_hide_popup (box->item_edit);
390 box->list_popped = FALSE;
391 unblock_list_signals (cell);
392 }
393 else
394 gtk_list_store_clear (box->tmp_store);
395}
396
397void
399{
400 PopBox* box;
401
402 if (cell == NULL) return;
403
404 box = cell->cell.gui_private;
405 if (NULL == box) return;
406
407 if (FALSE == box->use_quickfill_cache)
408 {
409 box->use_quickfill_cache = TRUE;
410 gnc_quickfill_destroy (box->qf);
411 }
412 box->qf = shared_qf;
413}
414
415void
416gnc_combo_cell_use_list_store_cache (ComboCell* cell, gpointer data)
417{
418 if (cell == NULL) return;
419
420 cell->shared_store = data;
421}
422
423void
424gnc_combo_cell_add_menu_item (ComboCell* cell, const char* menustr)
425{
426 PopBox* box;
427
428 if (cell == NULL)
429 return;
430 if (menustr == NULL)
431 return;
432
433 box = cell->cell.gui_private;
434
435 if (box->item_list != NULL)
436 {
437 block_list_signals (cell);
438
439 gnc_item_list_append (box->item_list, menustr);
440 if (cell->cell.value &&
441 (strcmp (menustr, cell->cell.value) == 0))
442 gnc_item_list_select (box->item_list, menustr);
443
444 unblock_list_signals (cell);
445 }
446 else
447 {
448 GtkTreeIter iter;
449
450 gtk_list_store_append (box->tmp_store, &iter);
451 gtk_list_store_set (box->tmp_store, &iter, 0, menustr, -1);
452 }
453
454 /* If we're going to be using a pre-fab quickfill,
455 * then don't fill it in here */
456 if (FALSE == box->use_quickfill_cache)
457 {
458 gnc_quickfill_insert (box->qf, menustr, QUICKFILL_ALPHA);
459 }
460}
461
462void
464{
465 PopBox* box;
466
467 if (cell == NULL)
468 return;
469 if (menustr == NULL)
470 return;
471
472 box = cell->cell.gui_private;
473
474 if (box->item_list != NULL)
475 {
476 block_list_signals (cell);
477
478 gnc_item_list_append (box->item_list, menustr);
479 if (cell->cell.value)
480 {
481 gchar* menu_copy = g_strdup (menustr);
482 gchar* value_copy = g_strdup (cell->cell.value);
483 g_strdelimit (menu_copy, "-:/\\.", ' ');
484 g_strdelimit (value_copy, "-:/\\.", ' ');
485 if (strcmp (menu_copy, value_copy) == 0)
486 {
487 gnc_combo_cell_set_value (cell, menustr);
488 gnc_item_list_select (box->item_list, menustr);
489 }
490 g_free (value_copy);
491 g_free (menu_copy);
492 }
493 unblock_list_signals (cell);
494 }
495
496 /* If we're going to be using a pre-fab quickfill,
497 * then don't fill it in here */
498 if (FALSE == box->use_quickfill_cache)
499 {
500 gnc_quickfill_insert (box->qf, menustr, QUICKFILL_ALPHA);
501 }
502}
503
504void
505gnc_combo_cell_set_value (ComboCell* cell, const char* str)
506{
507 gnc_basic_cell_set_value (&cell->cell, str);
508}
509
510static inline void
511list_store_append (GtkListStore *store, char* string)
512{
513 GtkTreeIter iter;
514
515 g_return_if_fail (store != NULL);
516 g_return_if_fail (string != NULL);
517 gtk_list_store_append (store, &iter);
518 gtk_list_store_set (store, &iter, 0, string, -1);
519}
520
521/* This function looks through full_store for a partial match with newval and
522 * returns the first match (which must be subsequently freed). It fills out
523 * box->item_list with found matches.
524 */
525static gchar*
526gnc_combo_cell_type_ahead_search (const gchar* newval,
527 GtkListStore* full_store, ComboCell *cell)
528{
529 GtkTreeIter iter;
530 PopBox* box = cell->cell.gui_private;
531 int num_found = 0;
532 gchar* match_str = NULL;
533 const char* sep = gnc_get_account_separator_string ();
534 char* escaped_sep = g_regex_escape_string (sep, -1);
535 char* escaped_newval = g_regex_escape_string (newval, -1);
536 gchar* newval_rep = g_strdup_printf (".*%s.*", escaped_sep);
537 GRegex* regex0 = g_regex_new (escaped_sep, 0, 0, NULL);
538 char* rep_str = g_regex_replace_literal (regex0, escaped_newval, -1, 0,
539 newval_rep, 0, NULL);
540 char* normal_rep_str = g_utf8_normalize (rep_str, -1, G_NORMALIZE_NFC);
541 GRegex *regex = g_regex_new (normal_rep_str, G_REGEX_CASELESS, 0, NULL);
542
543 gboolean valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (full_store),
544 &iter);
545
546 /* Limit the number found to keep the combo box from getting unreasonably
547 * large.
548 */
549 static const gint MAX_NUM_MATCHES = 30;
550
551 g_free (normal_rep_str);
552 g_free (rep_str);
553 g_free (newval_rep);
554 g_free (escaped_sep);
555 g_free (escaped_newval);
556 g_regex_unref (regex0);
557
558 block_list_signals (cell); //Prevent recursion from gtk_tree_view signals.
559 gnc_item_edit_hide_popup (box->item_edit);
560 gtk_list_store_clear (box->tmp_store);
561 unblock_list_signals (cell);
562
563 if (strlen (newval) == 0)
564 {
565 /* Deleting everything in the cell shouldn't provide a search result for
566 * "" because that will just be the first MAX_NUM_MATCHES accounts which
567 * isn't very useful.
568 *
569 * Skip the search show the popup again with all accounts. Clear the
570 * temp store or the cell will be pre-filled with the first account.
571 */
572 gnc_item_list_set_temp_store (box->item_list, NULL);
573 gnc_item_edit_show_popup (box->item_edit);
574 box->list_popped = TRUE;
575 goto cleanup;
576 }
577
578 while (valid && num_found < MAX_NUM_MATCHES)
579 {
580 gchar* str_data = NULL;
581 gchar* normalized_str_data = NULL;
582 gtk_tree_model_get (GTK_TREE_MODEL (full_store), &iter, 0,
583 &str_data, -1);
584 normalized_str_data = g_utf8_normalize (str_data, -1, G_NORMALIZE_NFC);
585
586 if (g_regex_match (regex, normalized_str_data, 0, NULL))
587 {
588 if (!num_found)
589 match_str = g_strdup (str_data);
590 ++num_found;
591 list_store_append (box->tmp_store, str_data);
592 }
593 g_free (str_data);
594 g_free (normalized_str_data);
595 valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (full_store), &iter);
596 }
597
598 if (num_found)
599 {
600 gnc_item_list_set_temp_store (box->item_list, box->tmp_store);
601 gnc_item_edit_show_popup (box->item_edit);
602 box->list_popped = TRUE;
603 }
604
605cleanup:
606 g_regex_unref (regex);
607 return match_str;
608}
609
610static char*
611quickfill_match (QuickFill *qf, const char *string)
612{
613 QuickFill *match = gnc_quickfill_get_string_match (qf, string);
614 return g_strdup (gnc_quickfill_string (match));
615}
616
617
618static void
619gnc_combo_cell_modify_verify (BasicCell* _cell,
620 const char* change,
621 int change_len,
622 const char* newval,
623 int newval_len,
624 int* cursor_position,
625 int* start_selection,
626 int* end_selection)
627{
628 ComboCell* cell = (ComboCell*) _cell;
629 PopBox* box = cell->cell.gui_private;
630 gchar* match_str = NULL;
631 glong newval_chars;
632 glong change_chars;
633 const gchar* box_str = NULL;
634
635 newval_chars = g_utf8_strlen (newval, newval_len);
636 change_chars = g_utf8_strlen (change, change_len);
637
638 if (box->in_list_select)
639 {
640 gnc_basic_cell_set_value_internal (_cell, newval);
641 *cursor_position = -1;
642 *start_selection = 0;
643 *end_selection = -1;
644 return;
645 }
646
647 /* If item_list is using temp then we're already partly matched by
648 * type-ahead and a quickfill_match won't work.
649 */
650 if (!gnc_item_list_using_temp (box->item_list))
651 {
652 // If we were deleting or inserting in the middle, just accept.
653 if (change == NULL || *cursor_position < _cell->value_chars)
654 {
655 gnc_basic_cell_set_value_internal (_cell, newval);
656 *start_selection = *end_selection = *cursor_position;
657 return;
658 }
659
660 match_str = quickfill_match (box->qf, newval);
661
662 if (match_str != NULL) // Do we have a quickfill match
663 {
664 *start_selection = newval_chars;
665 *end_selection = -1;
666 *cursor_position += change_chars;
667 box_str = match_str;
668
669 block_list_signals (cell); // Prevent recursion
670 gnc_item_list_select (box->item_list, match_str);
671 unblock_list_signals (cell);
672 }
673 }
674
675 // Try using type-ahead
676 if (match_str == NULL && cell->shared_store)
677 {
678 // No start-of-name match, try type-ahead search, we match any substring of the full account name.
679 GtkListStore *store = cell->shared_store;
680 match_str = gnc_combo_cell_type_ahead_search (newval, store, cell);
681 *start_selection = newval_chars;
682 *end_selection = -1;
683 *cursor_position = newval_chars;
684
685 // Do not change the string in the type-in box.
686 box_str = newval;
687 }
688
689 // No type-ahead / quickfill entry found
690 if (match_str == NULL)
691 {
692 block_list_signals (cell); // Prevent recursion
693 if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
694 {
695 gnc_item_list_set_temp_store (box->item_list, NULL);
696 gtk_list_store_clear (box->tmp_store);
697 }
698 gnc_item_list_select (box->item_list, NULL);
699 unblock_list_signals (cell);
700 gnc_basic_cell_set_value_internal (_cell, newval);
701 *cursor_position = *start_selection = newval_chars;
702 *end_selection = -1;
703 return;
704 }
705
706 if (!box->list_popped && auto_pop_combos)
707 {
708 gnc_item_edit_show_popup (box->item_edit);
709 box->list_popped = TRUE;
710 }
711
712 gnc_basic_cell_set_value_internal (_cell, box_str);
713 g_free (match_str);
714}
715
716static gboolean
717gnc_combo_cell_direct_update (BasicCell* bcell,
718 int* cursor_position,
719 int* start_selection,
720 int* end_selection,
721 void* gui_data)
722{
723 ComboCell* cell = (ComboCell*) bcell;
724 PopBox* box = cell->cell.gui_private;
725 GdkEventKey* event = gui_data;
726 gboolean keep_on_going = FALSE;
727 gboolean extra_colon;
728 gunichar unicode_value;
729 QuickFill* match;
730 const char* match_str;
731 int prefix_len;
732 int find_pos;
733 int new_pos;
734
735 if (event->type != GDK_KEY_PRESS)
736 return FALSE;
737
738 unicode_value = gdk_keyval_to_unicode (event->keyval);
739 switch (event->keyval)
740 {
741 case GDK_KEY_Escape:
742 if (bcell->changed)
743 {
744 const char *value = gnc_table_get_model_entry (box->sheet->table, bcell->cell_name);
745
746 if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
747 {
748 gnc_item_list_set_temp_store (box->item_list, NULL);
749 gtk_list_store_clear (box->tmp_store);
750 }
751 gnc_basic_cell_set_value_internal (bcell, value);
752 bcell->changed = FALSE;
753 block_list_signals (cell);
754 gnc_item_list_select (box->item_list, value);
755 unblock_list_signals (cell);
756 *cursor_position = -1;
757 *start_selection = 0;
758 *end_selection = -1;
759 return TRUE;
760 }
761 break;
762 case GDK_KEY_slash:
763 if (! (event->state & GDK_MOD1_MASK))
764 {
765 if (unicode_value == box->complete_char)
766 break;
767
768 return FALSE;
769 }
770 keep_on_going = TRUE;
771 /* fall through */
772 case GDK_KEY_Tab:
773 case GDK_KEY_ISO_Left_Tab:
774 if (gnc_item_list_using_temp (box->item_list))
775 {
776 char* string = gnc_item_list_get_selection (box->item_list);
777 g_signal_emit_by_name (G_OBJECT (box->item_list), "change_item",
778 string, (gpointer)bcell);
779 g_free (string);
780 return FALSE;
781 }
782 if (! (event->state & GDK_CONTROL_MASK) && !keep_on_going)
783 return FALSE;
784
786 (box->qf, bcell->value, *cursor_position);
787 if (match == NULL)
788 return TRUE;
789
791 (match, &prefix_len);
792 if (match == NULL)
793 return TRUE;
794
795 match_str = gnc_quickfill_string (match);
796
797 if ((match_str != NULL) &&
798 (strncmp (match_str, bcell->value,
799 strlen (bcell->value)) == 0) &&
800 (strcmp (match_str, bcell->value) != 0))
801 {
802 gnc_basic_cell_set_value_internal (bcell,
803 match_str);
804
805 block_list_signals (cell);
806 gnc_item_list_select (box->item_list,
807 match_str);
808 unblock_list_signals (cell);
809 }
810
811 *cursor_position += prefix_len;
812 *start_selection = *cursor_position;
813 *end_selection = -1;
814 return TRUE;
815 }
816
817 if (box->complete_char == 0)
818 return FALSE;
819
820 if (unicode_value != box->complete_char)
821 return FALSE;
822
823 if (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))
824 return FALSE;
825
826 if ((*cursor_position < bcell->value_chars) &&
827 ((*end_selection < bcell->value_chars) ||
828 (*cursor_position < *start_selection)))
829 return FALSE;
830
831 if ((*cursor_position == bcell->value_chars) &&
832 (*start_selection != *end_selection) &&
833 (*end_selection < bcell->value_chars))
834 return FALSE;
835
836 find_pos = -1;
837 if (*start_selection < bcell->value_chars)
838 {
839 int i = *start_selection;
840 const char* c;
841 gunichar uc;
842
843 c = g_utf8_offset_to_pointer (bcell->value, i);
844 while (*c)
845 {
846 uc = g_utf8_get_char (c);
847 if (uc == box->complete_char)
848 {
849 find_pos = (i + 1);
850 break;
851 }
852 c = g_utf8_next_char (c);
853 i++;
854 }
855 }
856
857 if (find_pos >= 0)
858 {
859 new_pos = find_pos;
860 extra_colon = FALSE;
861 }
862 else
863 {
864 new_pos = bcell->value_chars;
865 extra_colon = TRUE;
866 }
867
868 match = gnc_quickfill_get_string_len_match (box->qf,
869 bcell->value, new_pos);
870 if (match == NULL)
871 return FALSE;
872
873 if (extra_colon)
874 {
875 match = gnc_quickfill_get_char_match (match,
876 box->complete_char);
877 if (match == NULL)
878 return FALSE;
879
880 new_pos++;
881 }
882
883 match_str = gnc_quickfill_string (match);
884
885 if ((match_str != NULL) &&
886 (strncmp (match_str, bcell->value, strlen (bcell->value)) == 0) &&
887 (strcmp (match_str, bcell->value) != 0))
888 {
889 gnc_basic_cell_set_value_internal (bcell, match_str);
890
891 block_list_signals (cell);
892 gnc_item_list_select (box->item_list, match_str);
893 unblock_list_signals (cell);
894 }
895
896 *cursor_position = new_pos;
897 *start_selection = new_pos;
898 *end_selection = -1;
899
900 return TRUE;
901}
902
903static void
904gnc_combo_cell_gui_realize (BasicCell* bcell, gpointer data)
905{
906 GnucashSheet* sheet = data;
907 GncItemEdit* item_edit = gnucash_sheet_get_item_edit (sheet);
908 ComboCell* cell = (ComboCell*) bcell;
909 PopBox* box = cell->cell.gui_private;
910
911 /* initialize gui-specific, private data */
912 box->sheet = sheet;
913 box->item_edit = item_edit;
914 if (cell->shared_store)
915 box->item_list = GNC_ITEM_LIST (gnc_item_list_new (cell->shared_store));
916 else
917 box->item_list = GNC_ITEM_LIST (gnc_item_list_new (box->tmp_store));
918 gtk_widget_show_all (GTK_WIDGET (box->item_list));
919 g_object_ref_sink (box->item_list);
920
921 /* to mark cell as realized, remove the realize method */
922 cell->cell.gui_realize = NULL;
923 cell->cell.gui_move = gnc_combo_cell_gui_move;
924 cell->cell.enter_cell = gnc_combo_cell_enter;
925 cell->cell.leave_cell = gnc_combo_cell_leave;
926 cell->cell.gui_destroy = gnc_combo_cell_gui_destroy;
927 cell->cell.modify_verify = gnc_combo_cell_modify_verify;
928 cell->cell.direct_update = gnc_combo_cell_direct_update;
929}
930
931static void
932gnc_combo_cell_gui_move (BasicCell* bcell)
933{
934 PopBox* box = bcell->gui_private;
935
936 combo_disconnect_signals ((ComboCell*) bcell);
937
938 gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
939 NULL, NULL, NULL, NULL, NULL);
940
941 gnc_item_list_select (box->item_list, NULL);
942
943 box->list_popped = FALSE;
944}
945
946static int
947popup_get_height (GtkWidget* widget,
948 int space_available,
949 G_GNUC_UNUSED int row_height,
950 gpointer user_data)
951{
952 PopBox* box = user_data;
953 GtkScrolledWindow* scrollwin = GNC_ITEM_LIST(widget)->scrollwin;
954 int height;
955
956 // if popup_allocation_height set use that
957 if (box->item_edit->popup_allocation_height != -1)
958 height = box->item_edit->popup_allocation_height;
959 else
960 height = gnc_item_list_get_popup_height (GNC_ITEM_LIST(widget));
961
962 if (height < space_available)
963 {
964 // if the list is empty height would be 0 so return 1 instead to
965 // satisfy the check_popup_height_is_true function
966 gint ret_height = height ? height : 1;
967
968 gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, ret_height);
969 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
970 GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);
971 return ret_height;
972 }
973 else
974 gtk_widget_set_size_request (GTK_WIDGET(scrollwin), -1, -1);
975
976 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrollwin),
977 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
978 return space_available;
979}
980
981static int
982popup_autosize (GtkWidget* widget,
983 int max_width,
984 gpointer user_data)
985{
986 PopBox* box = user_data;
987
988 if (!box || !box->autosize)
989 return max_width;
990
991 return gnc_item_list_autosize (GNC_ITEM_LIST (widget)) + 20;
992}
993
994static void
995popup_set_focus (GtkWidget* widget,
996 G_GNUC_UNUSED gpointer user_data)
997{
998 /* An empty GtkTreeView grabbing focus causes the key_press events to be
999 * lost because there's no entry cell to handle them.
1000 */
1001 if (gnc_item_list_num_entries (GNC_ITEM_LIST (widget)))
1002 gtk_widget_grab_focus (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view));
1003}
1004
1005static void
1006popup_post_show (GtkWidget* widget,
1007 G_GNUC_UNUSED gpointer user_data)
1008{
1009 gnc_item_list_autosize (GNC_ITEM_LIST (widget));
1010 gnc_item_list_show_selected (GNC_ITEM_LIST (widget));
1011}
1012
1013static int
1014popup_get_width (GtkWidget* widget,
1015 G_GNUC_UNUSED gpointer user_data)
1016{
1017 GtkAllocation alloc;
1018 gtk_widget_get_allocation (GTK_WIDGET (GNC_ITEM_LIST (widget)->tree_view),
1019 &alloc);
1020 return alloc.width;
1021}
1022
1023static gboolean
1024gnc_combo_cell_enter (BasicCell* bcell,
1025 int* cursor_position,
1026 int* start_selection,
1027 int* end_selection)
1028{
1029 ComboCell* cell = (ComboCell*) bcell;
1030 PopBox* box = bcell->gui_private;
1031 PopupToggle popup_toggle;
1032 GList* find = NULL;
1033
1034 if (bcell->value)
1035 find = g_list_find_custom (box->ignore_strings,
1036 bcell->value,
1037 (GCompareFunc) strcmp);
1038 if (find)
1039 return FALSE;
1040
1041 gnc_item_edit_set_popup (box->item_edit,
1042 GTK_WIDGET (box->item_list),
1043 popup_get_height, popup_autosize,
1044 popup_set_focus, popup_post_show,
1045 popup_get_width, box);
1046
1047 block_list_signals (cell);
1048
1049 if (cell->shared_store && gnc_item_list_using_temp (box->item_list))
1050 {
1051 // Clear the temp store to ensure we don't start in type-ahead mode.
1052 gnc_item_list_set_temp_store (box->item_list, NULL);
1053 gtk_list_store_clear (box->tmp_store);
1054 }
1055 gnc_item_list_select (box->item_list, bcell->value);
1056 unblock_list_signals (cell);
1057
1058 popup_toggle = box->item_edit->popup_toggle;
1059
1060 // if the list is empty disable the toggle button
1061 gtk_widget_set_sensitive (GTK_WIDGET(popup_toggle.tbutton),
1062 gnc_item_list_num_entries (box->item_list));
1063
1064 combo_connect_signals (cell);
1065
1066 *cursor_position = -1;
1067 *start_selection = 0;
1068 *end_selection = -1;
1069
1070 return TRUE;
1071}
1072
1073static void
1074gnc_combo_cell_leave (BasicCell* bcell)
1075{
1076 PopBox* box = bcell->gui_private;
1077
1078 combo_disconnect_signals ((ComboCell*) bcell);
1079
1080 gnc_item_edit_set_popup (box->item_edit, NULL, NULL,
1081 NULL, NULL, NULL, NULL, NULL);
1082
1083 box->list_popped = FALSE;
1084
1085 if (box->strict)
1086 {
1087 if (bcell->value)
1088 {
1089 if (!bcell->changed)
1090 return;
1091
1092 if (gnc_item_in_list (box->item_list, bcell->value))
1093 return;
1094
1095 if (g_list_find_custom (box->ignore_strings,
1096 bcell->value,
1097 (GCompareFunc) strcmp))
1098 return;
1099 }
1100 gnc_basic_cell_set_value_internal (bcell, "");
1101 }
1102}
1103
1104void
1105gnc_combo_cell_set_strict (ComboCell* cell, gboolean strict)
1106{
1107 PopBox* box;
1108
1109 if (cell == NULL)
1110 return;
1111
1112 box = cell->cell.gui_private;
1113
1114 box->strict = strict;
1115}
1116
1117void
1118gnc_combo_cell_set_complete_char (ComboCell* cell, gunichar complete_char)
1119{
1120 PopBox* box;
1121
1122 if (cell == NULL)
1123 return;
1124
1125 box = cell->cell.gui_private;
1126
1127 box->complete_char = complete_char;
1128}
1129
1130void
1132 const char* ignore_string)
1133{
1134 PopBox* box;
1135
1136 if (cell == NULL)
1137 return;
1138
1139 if (!ignore_string)
1140 return;
1141
1142 box = cell->cell.gui_private;
1143
1144 box->ignore_strings = g_list_prepend (box->ignore_strings,
1145 g_strdup (ignore_string));
1146}
1147
1148void
1149gnc_combo_cell_set_autosize (ComboCell* cell, gboolean autosize)
1150{
1151 PopBox* box;
1152
1153 if (!cell)
1154 return;
1155
1156 box = cell->cell.gui_private;
1157 if (!box)
1158 return;
1159
1160 box->autosize = autosize;
1161}
Account handling public routines.
QuickFill is used to auto-complete typed user entries.
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.
const gchar * gnc_get_account_separator_string(void)
Returns the account separation character chosen by the user.
Definition Account.cpp:205
void gnc_combo_cell_set_complete_char(ComboCell *cell, gunichar complete_char)
Sets a character used for special completion processing.
void gnc_combo_cell_set_autosize(ComboCell *cell, gboolean autosize)
Determines whether the popup list autosizes itself or uses all available space.
void gnc_combo_cell_set_strict(ComboCell *cell, gboolean strict)
Determines whether the cell will accept strings not in the menu.
void gnc_combo_cell_use_quickfill_cache(ComboCell *cell, QuickFill *shared_qf)
Tell the combocell to use a shared QuickFill object.
void gnc_combo_cell_set_sort_enabled(ComboCell *cell, gboolean enabled)
Enable sorting of the menu item's contents.
void gnc_combo_cell_add_ignore_string(ComboCell *cell, const char *ignore_string)
Add a string to a list of strings which, if the cell has that value, will cause the cell to be unedit...
void gnc_combo_cell_add_account_menu_item(ComboCell *cell, char *menustr)
Add a 'account name' menu item to the list.
void gnc_combo_cell_add_menu_item(ComboCell *cell, const char *menustr)
Add a menu item to the list.
char * gnc_item_list_get_selection(GncItemList *item_list)
Retrieve the selected string from the item_list's active GtkListStore.
gulong gnc_prefs_register_cb(const char *group, const gchar *pref_name, gpointer func, gpointer user_data)
Register a callback that gets triggered when the given preference changes.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
QuickFill * gnc_quickfill_get_string_match(QuickFill *qf, const char *str)
Return a subnode in the tree whose strings all match the string 'str' as the next substring.
Definition QuickFill.c:179
const char * gnc_quickfill_string(QuickFill *qf)
For the given node 'qf', return the best-guess matching string.
Definition QuickFill.c:123
QuickFill * gnc_quickfill_get_string_len_match(QuickFill *qf, const char *str, int len)
Same as gnc_quickfill_get_string_match(), except that the string length is explicitly specified.
Definition QuickFill.c:150
void gnc_quickfill_insert(QuickFill *qf, const char *text, QuickFillSort sort)
Add the string "text" to the collection of searchable strings.
Definition QuickFill.c:229
QuickFill * gnc_quickfill_get_unique_len_match(QuickFill *qf, int *length)
Walk a 'unique' part of the QuickFill tree.
Definition QuickFill.c:199
QuickFill * gnc_quickfill_get_char_match(QuickFill *qf, gunichar uc)
Return the subnode of the tree whose strings all hold 'c' as the next letter.
Definition QuickFill.c:135
The ComboCell object implements a cell handler with a "combination-box" pull-down menu in it.
Definition combocell.h:53
Declarations for the Table object.