Branch data Line data Source code
1 : : /********************************************************************\
2 : : * QuickFill.h -- the quickfill tree data structure *
3 : : * Copyright (C) 1997 Robin D. Clark *
4 : : * Copyright (C) 1998 Linas Vepstas *
5 : : * Copyright (C) 2000 Dave Peticolas *
6 : : * *
7 : : * This program is free software; you can redistribute it and/or *
8 : : * modify it under the terms of the GNU General Public License as *
9 : : * published by the Free Software Foundation; either version 2 of *
10 : : * the License, or (at your option) any later version. *
11 : : * *
12 : : * This program is distributed in the hope that it will be useful, *
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of *
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15 : : * GNU General Public License for more details. *
16 : : * *
17 : : * You should have received a copy of the GNU General Public License*
18 : : * along with this program; if not, contact: *
19 : : * *
20 : : * Free Software Foundation Voice: +1-617-542-5942 *
21 : : * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
22 : : * Boston, MA 02110-1301, USA gnu@gnu.org *
23 : : * *
24 : : \********************************************************************/
25 : :
26 : : #include <config.h>
27 : :
28 : : #include <string.h>
29 : :
30 : : #include "QuickFill.h"
31 : : #include "gnc-engine.h"
32 : : #include "gnc-ui-util.h"
33 : :
34 : :
35 : : struct _QuickFill
36 : : {
37 : : char *text; /* the first matching text string */
38 : : int len; /* number of chars in text string */
39 : : GHashTable *matches; /* array of children in the tree */
40 : : };
41 : :
42 : :
43 : : /** PROTOTYPES ******************************************************/
44 : : static void quickfill_insert_recursive (QuickFill *qf, const char *text, int len,
45 : : const char* next_char, QuickFillSort sort);
46 : :
47 : : static void gnc_quickfill_remove_recursive (QuickFill *qf, const gchar *text,
48 : : gint depth, QuickFillSort sort);
49 : :
50 : : /* This static indicates the debugging module that this .o belongs to. */
51 : : static QofLogModule log_module = GNC_MOD_REGISTER;
52 : :
53 : : /********************************************************************\
54 : : \********************************************************************/
55 : :
56 : : QuickFill *
57 : 0 : gnc_quickfill_new (void)
58 : : {
59 : : QuickFill *qf;
60 : :
61 : : if (sizeof (guint) < sizeof (gunichar))
62 : : {
63 : : PWARN ("Can't use quickfill");
64 : : return NULL;
65 : : }
66 : :
67 : 0 : qf = g_new (QuickFill, 1);
68 : :
69 : 0 : qf->text = NULL;
70 : 0 : qf->len = 0;
71 : :
72 : 0 : qf->matches = g_hash_table_new (g_direct_hash, g_direct_equal);
73 : :
74 : 0 : return qf;
75 : : }
76 : :
77 : : /********************************************************************\
78 : : \********************************************************************/
79 : :
80 : : static gboolean
81 : 0 : destroy_helper (gpointer key, gpointer value, gpointer data)
82 : : {
83 : 0 : gnc_quickfill_destroy (value);
84 : 0 : return TRUE;
85 : : }
86 : :
87 : : void
88 : 0 : gnc_quickfill_destroy (QuickFill *qf)
89 : : {
90 : 0 : if (qf == NULL)
91 : 0 : return;
92 : :
93 : 0 : g_hash_table_foreach (qf->matches, (GHFunc)destroy_helper, NULL);
94 : 0 : g_hash_table_destroy (qf->matches);
95 : 0 : qf->matches = NULL;
96 : :
97 : 0 : if (qf->text)
98 : 0 : g_free(qf->text);
99 : 0 : qf->text = NULL;
100 : 0 : qf->len = 0;
101 : :
102 : 0 : g_free (qf);
103 : : }
104 : :
105 : : void
106 : 0 : gnc_quickfill_purge (QuickFill *qf)
107 : : {
108 : 0 : if (qf == NULL)
109 : 0 : return;
110 : :
111 : 0 : g_hash_table_foreach_remove (qf->matches, destroy_helper, NULL);
112 : :
113 : 0 : if (qf->text)
114 : 0 : g_free (qf->text);
115 : 0 : qf->text = NULL;
116 : 0 : qf->len = 0;
117 : : }
118 : :
119 : : /********************************************************************\
120 : : \********************************************************************/
121 : :
122 : : const char *
123 : 0 : gnc_quickfill_string (QuickFill *qf)
124 : : {
125 : 0 : if (qf == NULL)
126 : 0 : return NULL;
127 : :
128 : 0 : return qf->text;
129 : : }
130 : :
131 : : /********************************************************************\
132 : : \********************************************************************/
133 : :
134 : : QuickFill *
135 : 0 : gnc_quickfill_get_char_match (QuickFill *qf, gunichar uc)
136 : : {
137 : 0 : guint key = g_unichar_toupper (uc);
138 : :
139 : 0 : if (NULL == qf) return NULL;
140 : :
141 : 0 : DEBUG ("xaccGetQuickFill(): index = %u\n", key);
142 : :
143 : 0 : return g_hash_table_lookup (qf->matches, GUINT_TO_POINTER (key));
144 : : }
145 : :
146 : : /********************************************************************\
147 : : \********************************************************************/
148 : :
149 : : QuickFill *
150 : 0 : gnc_quickfill_get_string_len_match (QuickFill *qf,
151 : : const char *str, int len)
152 : : {
153 : : const char *c;
154 : : gunichar uc;
155 : :
156 : 0 : if (NULL == qf) return NULL;
157 : 0 : if (NULL == str) return NULL;
158 : :
159 : 0 : c = str;
160 : 0 : while (*c && (len > 0))
161 : : {
162 : 0 : if (qf == NULL)
163 : 0 : return NULL;
164 : :
165 : 0 : uc = g_utf8_get_char (c);
166 : 0 : qf = gnc_quickfill_get_char_match (qf, uc);
167 : :
168 : 0 : c = g_utf8_next_char (c);
169 : 0 : len--;
170 : : }
171 : :
172 : 0 : return qf;
173 : : }
174 : :
175 : : /********************************************************************\
176 : : \********************************************************************/
177 : :
178 : : QuickFill *
179 : 0 : gnc_quickfill_get_string_match (QuickFill *qf, const char *str)
180 : : {
181 : 0 : if (NULL == qf) return NULL;
182 : 0 : if (NULL == str) return NULL;
183 : :
184 : 0 : return gnc_quickfill_get_string_len_match (qf, str, g_utf8_strlen (str, -1));
185 : : }
186 : :
187 : : /********************************************************************\
188 : : \********************************************************************/
189 : :
190 : : static void
191 : 0 : unique_len_helper (gpointer key, gpointer value, gpointer data)
192 : : {
193 : 0 : QuickFill **qf_p = data;
194 : :
195 : 0 : *qf_p = value;
196 : 0 : }
197 : :
198 : : QuickFill *
199 : 0 : gnc_quickfill_get_unique_len_match (QuickFill *qf, int *length)
200 : : {
201 : 0 : if (length != NULL)
202 : 0 : *length = 0;
203 : :
204 : 0 : if (qf == NULL)
205 : 0 : return NULL;
206 : :
207 : : while (1)
208 : 0 : {
209 : : guint count;
210 : :
211 : 0 : count = g_hash_table_size (qf->matches);
212 : :
213 : 0 : if (count != 1)
214 : 0 : break;
215 : :
216 : 0 : g_hash_table_foreach (qf->matches, unique_len_helper, &qf);
217 : :
218 : 0 : if (length != NULL)
219 : 0 : (*length)++;
220 : : }
221 : :
222 : 0 : return qf;
223 : : }
224 : :
225 : : /********************************************************************\
226 : : \********************************************************************/
227 : :
228 : : void
229 : 0 : gnc_quickfill_insert (QuickFill *qf, const char *text, QuickFillSort sort)
230 : : {
231 : : gchar *normalized_str;
232 : : int len;
233 : :
234 : 0 : if (NULL == qf) return;
235 : 0 : if (NULL == text) return;
236 : :
237 : :
238 : 0 : normalized_str = g_utf8_normalize (text, -1, G_NORMALIZE_NFC);
239 : 0 : len = g_utf8_strlen (text, -1);
240 : 0 : quickfill_insert_recursive (qf, normalized_str, len, normalized_str, sort);
241 : 0 : g_free (normalized_str);
242 : : }
243 : :
244 : : /********************************************************************\
245 : : \********************************************************************/
246 : :
247 : : static void
248 : 0 : quickfill_insert_recursive (QuickFill *qf, const char *text, int len,
249 : : const char *next_char, QuickFillSort sort)
250 : : {
251 : : guint key;
252 : : char *old_text;
253 : : QuickFill *match_qf;
254 : : gunichar key_char_uc;
255 : :
256 : 0 : if (qf == NULL)
257 : 0 : return;
258 : :
259 : 0 : if ((text == NULL) || (*next_char == '\0'))
260 : 0 : return;
261 : :
262 : :
263 : 0 : key_char_uc = g_utf8_get_char (next_char);
264 : 0 : key = g_unichar_toupper (key_char_uc);
265 : :
266 : 0 : match_qf = g_hash_table_lookup (qf->matches, GUINT_TO_POINTER (key));
267 : 0 : if (match_qf == NULL)
268 : : {
269 : 0 : match_qf = gnc_quickfill_new ();
270 : 0 : g_hash_table_insert (qf->matches, GUINT_TO_POINTER (key), match_qf);
271 : : }
272 : :
273 : 0 : old_text = match_qf->text;
274 : :
275 : 0 : switch (sort)
276 : : {
277 : 0 : case QUICKFILL_ALPHA:
278 : 0 : if (old_text && (g_utf8_collate (text, old_text) >= 0))
279 : 0 : break;
280 : : /* fall through */
281 : :
282 : : case QUICKFILL_LIFO:
283 : : default:
284 : : /* If there's no string there already, just put the new one in. */
285 : 0 : if (old_text == NULL)
286 : : {
287 : 0 : match_qf->text = g_strdup(text);
288 : 0 : match_qf->len = len;
289 : 0 : break;
290 : : }
291 : :
292 : : /* Leave prefixes in place */
293 : 0 : if ((len > match_qf->len) &&
294 : 0 : (strncmp(text, old_text, strlen(old_text)) == 0))
295 : 0 : break;
296 : :
297 : 0 : g_free(old_text);
298 : 0 : match_qf->text = g_strdup(text);
299 : 0 : match_qf->len = len;
300 : 0 : break;
301 : : }
302 : :
303 : 0 : quickfill_insert_recursive (match_qf, text, len, g_utf8_next_char (next_char), sort);
304 : : }
305 : :
306 : : /********************************************************************\
307 : : \********************************************************************/
308 : :
309 : : void
310 : 0 : gnc_quickfill_remove (QuickFill *qf, const gchar *text, QuickFillSort sort)
311 : : {
312 : : gchar *normalized_str;
313 : :
314 : 0 : if (qf == NULL) return;
315 : 0 : if (text == NULL) return;
316 : :
317 : 0 : normalized_str = g_utf8_normalize (text, -1, G_NORMALIZE_NFC);
318 : 0 : gnc_quickfill_remove_recursive (qf, normalized_str, 0, sort);
319 : 0 : g_free (normalized_str);
320 : : }
321 : :
322 : : /********************************************************************\
323 : : \********************************************************************/
324 : :
325 : : struct _BestText
326 : : {
327 : : gchar *text;
328 : : QuickFillSort sort;
329 : : };
330 : :
331 : : static void
332 : 0 : best_text_helper (gpointer key, gpointer value, gpointer user_data)
333 : : {
334 : 0 : QuickFill *qf = value;
335 : 0 : struct _BestText *best = user_data;
336 : :
337 : 0 : if (best->text == NULL)
338 : : {
339 : : /* start with the first text */
340 : 0 : best->text = qf->text;
341 : :
342 : : }
343 : 0 : else if (best->text == QUICKFILL_LIFO)
344 : : {
345 : : /* we do not track history, so ignore it */
346 : 0 : return;
347 : :
348 : : }
349 : 0 : else if (g_utf8_collate (qf->text, best->text) < 0)
350 : : {
351 : : /* even better text */
352 : 0 : best->text = qf->text;
353 : : }
354 : : }
355 : :
356 : :
357 : :
358 : : static void
359 : 0 : gnc_quickfill_remove_recursive (QuickFill *qf, const gchar *text, gint depth,
360 : : QuickFillSort sort)
361 : : {
362 : : QuickFill *match_qf;
363 : : gchar *child_text;
364 : : gint child_len;
365 : :
366 : 0 : child_text = NULL;
367 : 0 : child_len = 0;
368 : :
369 : 0 : if (depth < g_utf8_strlen (text, -1))
370 : : {
371 : : /* process next letter */
372 : :
373 : : gchar *key_char;
374 : : gunichar key_char_uc;
375 : : guint key;
376 : :
377 : 0 : key_char = g_utf8_offset_to_pointer (text, depth);
378 : 0 : key_char_uc = g_utf8_get_char (key_char);
379 : 0 : key = g_unichar_toupper (key_char_uc);
380 : :
381 : 0 : match_qf = g_hash_table_lookup (qf->matches, GUINT_TO_POINTER (key));
382 : 0 : if (match_qf)
383 : : {
384 : : /* remove text from child qf */
385 : 0 : gnc_quickfill_remove_recursive (match_qf, text, depth + 1, sort);
386 : :
387 : 0 : if (match_qf->text == NULL)
388 : : {
389 : : /* text was the only word with a prefix up to match_qf */
390 : 0 : g_hash_table_remove (qf->matches, GUINT_TO_POINTER (key));
391 : 0 : gnc_quickfill_destroy (match_qf);
392 : :
393 : : }
394 : : else
395 : : {
396 : : /* remember remaining best child string */
397 : 0 : child_text = match_qf->text;
398 : 0 : child_len = match_qf->len;
399 : : }
400 : : }
401 : : }
402 : :
403 : 0 : if (qf->text == NULL)
404 : 0 : return;
405 : :
406 : 0 : if (strcmp (text, qf->text) == 0)
407 : : {
408 : : /* the currently best text is about to be removed */
409 : :
410 : 0 : gchar *best_text = NULL;
411 : 0 : gint best_len = 0;
412 : :
413 : 0 : if (child_text != NULL)
414 : : {
415 : : /* other children are pretty good as well */
416 : 0 : best_text = child_text;
417 : 0 : best_len = child_len;
418 : :
419 : : }
420 : : else
421 : : {
422 : 0 : if (g_hash_table_size (qf->matches) != 0)
423 : : {
424 : : /* otherwise search for another good text */
425 : : struct _BestText bts;
426 : 0 : bts.text = NULL;
427 : 0 : bts.sort = sort;
428 : :
429 : 0 : g_hash_table_foreach (qf->matches, (GHFunc) best_text_helper, &bts);
430 : 0 : best_text = bts.text;
431 : 0 : best_len = (best_text == NULL) ? 0 : g_utf8_strlen (best_text, -1);
432 : : }
433 : : }
434 : :
435 : : /* now replace or clear text */
436 : 0 : g_free(qf->text);
437 : 0 : if (best_text != NULL)
438 : : {
439 : 0 : qf->text = g_strdup(best_text);
440 : 0 : qf->len = best_len;
441 : : }
442 : : else
443 : : {
444 : 0 : qf->text = NULL;
445 : 0 : qf->len = 0;
446 : : }
447 : : }
448 : : }
449 : :
450 : : /********************** END OF FILE ********************************* \
451 : : \********************************************************************/
|