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