1 /********************************************************************\
2  * quickfillcell.c -- autocompletion based on memorized history     *
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:
25  * quickfillcell.c
26  *
27  * FUNCTION:
28  * Implements a text cell with automatic typed-phrase
29  * completion.
30  *
31  * HISTORY:
32  * Copyright (c) 1998-2000 Linas Vepstas
33  * Copyright (c) 2000 Dave Peticolas
34  */
35 
36 #include <config.h>
37 
38 #include <glib.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 
43 #include "basiccell.h"
44 #include "gnc-ui-util.h"
45 #include "quickfillcell.h"
46 
47 
48 static void gnc_quickfill_cell_set_original (QuickFillCell *cell,
49         const char *original);
50 
51 
52 static void
gnc_quickfill_cell_set_value_internal(BasicCell * _cell,const char * val)53 gnc_quickfill_cell_set_value_internal (BasicCell *_cell,
54                                        const char *val)
55 {
56     QuickFillCell *cell = (QuickFillCell *) _cell;
57     gnc_quickfill_cell_set_value (cell, val);
58 }
59 
60 /* when entering new cell, put cursor at end and select everything */
61 static gboolean
gnc_quickfill_cell_enter(BasicCell * _cell,int * cursor_position,int * start_selection,int * end_selection)62 gnc_quickfill_cell_enter (BasicCell *_cell,
63                           int *cursor_position,
64                           int *start_selection,
65                           int *end_selection)
66 {
67     QuickFillCell *cell = (QuickFillCell *) _cell;
68 
69     *cursor_position = -1;
70     *start_selection = 0;
71     *end_selection = -1;
72 
73     gnc_quickfill_cell_set_original (cell, NULL);
74 
75     return TRUE;
76 }
77 
78 static gboolean
utf8_caseequal(const char * s1,const char * s2)79 utf8_caseequal (const char *s1, const char *s2)
80 {
81     char *s1new;
82     char *s2new;
83     gboolean equal = FALSE;
84 
85     if (s1 == s2)
86         return TRUE;
87 
88     if (!s1 || !s2)
89         return FALSE;
90 
91     s1new = g_utf8_casefold(s1, -1);
92     s2new = g_utf8_casefold(s2, -1);
93 
94     if (g_utf8_collate(s1new, s2new) == 0)
95         equal = TRUE;
96 
97     g_free (s1new);
98     g_free (s2new);
99 
100     return equal;
101 }
102 
103 static gboolean
utf8_caseequal_len(const char * s1,const char * s2,guint len)104 utf8_caseequal_len (const char *s1, const char *s2, guint len)
105 {
106     gchar *s1new;
107     gchar *s2new;
108     const gchar *s1_offset;
109     const gchar *s2_offset;
110     glong s1chars;
111     glong s2chars;
112     glong s1_bytes_len;
113     glong s2_bytes_len;
114     gboolean equal = FALSE;
115 
116     if (len == 0)
117         return TRUE;
118 
119     if (s1 == s2)
120         return TRUE;
121 
122     if (!s1 || !s2)
123         return FALSE;
124 
125     /* Obtain the number of bytes for the given number of characters */
126     s1_offset = g_utf8_offset_to_pointer (s1, len);
127     s2_offset = g_utf8_offset_to_pointer (s2, len);
128     s1_bytes_len = s1_offset - s1;
129     s2_bytes_len = s2_offset - s2;
130 
131     /* Test whether the number of characters might be too small anyway
132        (don't need to examine more than bytes_len bytes to check that) */
133     s1chars = g_utf8_strlen (s1, s1_bytes_len);
134     s2chars = g_utf8_strlen (s2, s2_bytes_len);
135     if ( (s1chars < len) || (s2chars < len) )
136         return FALSE;
137 
138     /* Allocate new strings that are case-independent. */
139     s1new = g_utf8_casefold (s1, s1_bytes_len);
140     s2new = g_utf8_casefold (s2, s2_bytes_len);
141 
142     /* equal = utf8_caseequal (s1new, s2new); */
143     /* ^^ don't call this to save one string allocation; we used
144        g_utf8_casefold here already. */
145 
146     /* Now really compare the two strings */
147     if (g_utf8_collate(s1new, s2new) == 0)
148         equal = TRUE;
149 
150     g_free (s1new);
151     g_free (s2new);
152 
153     return equal;
154 }
155 
156 static void
gnc_quickfill_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)157 gnc_quickfill_cell_modify_verify (BasicCell *_cell,
158                                   const char *change,
159                                   int change_len,
160                                   const char *newval,
161                                   int newval_len,
162                                   int *cursor_position,
163                                   int *start_selection,
164                                   int *end_selection)
165 {
166     QuickFillCell *cell = (QuickFillCell *) _cell;
167     const char *match_str;
168     QuickFill *match;
169 
170     glong newval_chars;
171     glong change_chars;
172 
173     newval_chars = g_utf8_strlen(newval, newval_len);
174     change_chars = g_utf8_strlen(change, change_len);
175 
176     /* If deleting, just accept */
177     if (change == NULL)
178     {
179         /* if the new value is a prefix of the original modulo case,
180          * just truncate the end of the original. Otherwise, set it
181          * to NULL */
182         if ((*cursor_position >= newval_chars) &&
183                 (cell->original != NULL) &&
184                 (g_utf8_strlen (cell->original, -1) >= newval_chars) &&
185                 utf8_caseequal_len (cell->original, newval, newval_chars))
186         {
187             gchar *temp = g_strndup (cell->original, newval_len);
188             gnc_quickfill_cell_set_original (cell, temp);
189             g_free (temp);
190         }
191         else
192             gnc_quickfill_cell_set_original (cell, NULL);
193 
194         gnc_basic_cell_set_value_internal (&cell->cell, newval);
195         // Remove any selection.
196         *end_selection = *start_selection = *cursor_position;
197         return;
198     }
199 
200     /* If we are inserting in the middle, just accept */
201     if (*cursor_position < newval_chars)
202     {
203         gnc_basic_cell_set_value_internal (&cell->cell, newval);
204         gnc_quickfill_cell_set_original (cell, NULL);
205         return;
206     }
207 
208     if (cell->original == NULL)
209         cell->original = g_strdup (newval);
210     else if (utf8_caseequal (cell->original, _cell->value))
211     {
212         GString *original;
213 
214         original = g_string_new (cell->original);
215         g_string_append (original, change);
216 
217         g_free (cell->original);
218         cell->original = g_strdup (original->str);
219         g_string_free (original, TRUE);
220     }
221     else
222     {
223         g_free (cell->original);
224         cell->original = NULL;
225     }
226 
227     match = gnc_quickfill_get_string_match (cell->qf, newval);
228 
229     match_str = gnc_quickfill_string (match);
230 
231     if (match_str == NULL)
232     {
233         if (cell->original != NULL)
234             newval = cell->original;
235 
236         gnc_basic_cell_set_value_internal (&cell->cell, newval);
237         return;
238     }
239 
240     *start_selection = newval_chars;
241     *end_selection = -1;
242     *cursor_position += change_chars;
243 
244     gnc_basic_cell_set_value_internal (&cell->cell, match_str);
245 }
246 
247 /* when leaving cell, make sure that text was put into the qf */
248 
249 static void
gnc_quickfill_cell_leave(BasicCell * _cell)250 gnc_quickfill_cell_leave (BasicCell * _cell)
251 {
252     QuickFillCell *cell = (QuickFillCell *) _cell;
253 
254     gnc_quickfill_insert (cell->qf, _cell->value, cell->sort);
255 }
256 
257 static void
gnc_quickfill_cell_destroy(BasicCell * bcell)258 gnc_quickfill_cell_destroy (BasicCell *bcell)
259 {
260     QuickFillCell *cell = (QuickFillCell *) bcell;
261 
262     if (!cell->use_quickfill_cache)
263     {
264         gnc_quickfill_destroy (cell->qf);
265     }
266     cell->qf = NULL;
267 
268     g_free (cell->original);
269     cell->original = NULL;
270 
271     cell->cell.enter_cell    = NULL;
272     cell->cell.modify_verify = NULL;
273     cell->cell.leave_cell    = NULL;
274     cell->cell.set_value     = NULL;
275 }
276 
277 static void
gnc_quickfill_cell_init(QuickFillCell * cell)278 gnc_quickfill_cell_init (QuickFillCell *cell)
279 {
280     gnc_basic_cell_init (&(cell->cell));
281 
282     cell->qf = gnc_quickfill_new ();
283     cell->use_quickfill_cache = FALSE;
284     cell->sort = QUICKFILL_LIFO;
285     cell->original = NULL;
286 
287     cell->cell.destroy = gnc_quickfill_cell_destroy;
288 
289     cell->cell.enter_cell    = gnc_quickfill_cell_enter;
290     cell->cell.modify_verify = gnc_quickfill_cell_modify_verify;
291     cell->cell.leave_cell    = gnc_quickfill_cell_leave;
292     cell->cell.set_value     = gnc_quickfill_cell_set_value_internal;
293 }
294 
295 BasicCell *
gnc_quickfill_cell_new(void)296 gnc_quickfill_cell_new (void)
297 {
298     QuickFillCell *cell;
299 
300     cell = g_new0 (QuickFillCell, 1);
301 
302     gnc_quickfill_cell_init (cell);
303 
304     return &cell->cell;
305 }
306 
307 void
gnc_quickfill_cell_set_value(QuickFillCell * cell,const char * value)308 gnc_quickfill_cell_set_value (QuickFillCell *cell, const char * value)
309 {
310     if (cell == NULL)
311         return;
312 
313     gnc_basic_cell_set_value_internal (&cell->cell, value);
314     gnc_quickfill_insert (cell->qf, value, cell->sort);
315 }
316 
317 void
gnc_quickfill_cell_set_sort(QuickFillCell * cell,QuickFillSort sort)318 gnc_quickfill_cell_set_sort (QuickFillCell *cell, QuickFillSort sort)
319 {
320     if (cell == NULL)
321         return;
322 
323     cell->sort = sort;
324 }
325 
326 static void
gnc_quickfill_cell_set_original(QuickFillCell * cell,const char * original)327 gnc_quickfill_cell_set_original (QuickFillCell *cell, const char *original)
328 {
329     if (cell == NULL)
330         return;
331 
332     g_free (cell->original);
333 
334     if ((original != NULL) && (*original != 0))
335         cell->original = strdup (original);
336     else
337         cell->original = NULL;
338 }
339 
340 void
gnc_quickfill_cell_add_completion(QuickFillCell * cell,const char * completion)341 gnc_quickfill_cell_add_completion (QuickFillCell *cell, const char *completion)
342 {
343     if (cell == NULL)
344         return;
345 
346     gnc_quickfill_insert (cell->qf, completion, cell->sort);
347 }
348 
349 void
gnc_quickfill_cell_use_quickfill_cache(QuickFillCell * cell,QuickFill * shared_qf)350 gnc_quickfill_cell_use_quickfill_cache (QuickFillCell *cell, QuickFill *shared_qf)
351 {
352     g_assert(cell);
353     g_assert(shared_qf);
354 
355     if (!cell->use_quickfill_cache)
356     {
357         cell->use_quickfill_cache = TRUE;
358         gnc_quickfill_destroy (cell->qf);
359     }
360     cell->qf = shared_qf;
361 }
362