mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-29 12:14:31 -06:00
1238b9d8cd
This will avoid a ninja-build from picking up a config.h generated by the autotools build (in the root build directory). Picking up the wrong config.h may lead to all kinds of subtle issues if the autotools run was done with different options than the cmake run.
452 lines
12 KiB
C
452 lines
12 KiB
C
/********************************************************************\
|
|
* QuickFill.h -- the quickfill tree data structure *
|
|
* Copyright (C) 1997 Robin D. Clark *
|
|
* Copyright (C) 1998 Linas Vepstas *
|
|
* Copyright (C) 2000 Dave Peticolas *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU General Public License as *
|
|
* published by the Free Software Foundation; either version 2 of *
|
|
* the License, or (at your option) any later version. *
|
|
* *
|
|
* This program is distributed in the hope that it will be useful, *
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
* GNU General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License*
|
|
* along with this program; if not, contact: *
|
|
* *
|
|
* Free Software Foundation Voice: +1-617-542-5942 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
* *
|
|
\********************************************************************/
|
|
|
|
#include <config.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include "QuickFill.h"
|
|
#include "gnc-engine.h"
|
|
#include "gnc-ui-util.h"
|
|
|
|
|
|
struct _QuickFill
|
|
{
|
|
char *text; /* the first matching text string */
|
|
int len; /* number of chars in text string */
|
|
GHashTable *matches; /* array of children in the tree */
|
|
};
|
|
|
|
|
|
/** PROTOTYPES ******************************************************/
|
|
static void quickfill_insert_recursive (QuickFill *qf, const char *text, int len,
|
|
const char* next_char, QuickFillSort sort);
|
|
|
|
static void gnc_quickfill_remove_recursive (QuickFill *qf, const gchar *text,
|
|
gint depth, QuickFillSort sort);
|
|
|
|
/* This static indicates the debugging module that this .o belongs to. */
|
|
static QofLogModule log_module = GNC_MOD_REGISTER;
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
QuickFill *
|
|
gnc_quickfill_new (void)
|
|
{
|
|
QuickFill *qf;
|
|
|
|
if (sizeof (guint) < sizeof (gunichar))
|
|
{
|
|
PWARN ("Can't use quickfill");
|
|
return NULL;
|
|
}
|
|
|
|
qf = g_new (QuickFill, 1);
|
|
|
|
qf->text = NULL;
|
|
qf->len = 0;
|
|
|
|
qf->matches = g_hash_table_new (g_direct_hash, g_direct_equal);
|
|
|
|
return qf;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static gboolean
|
|
destroy_helper (gpointer key, gpointer value, gpointer data)
|
|
{
|
|
gnc_quickfill_destroy (value);
|
|
return TRUE;
|
|
}
|
|
|
|
void
|
|
gnc_quickfill_destroy (QuickFill *qf)
|
|
{
|
|
if (qf == NULL)
|
|
return;
|
|
|
|
g_hash_table_foreach (qf->matches, (GHFunc)destroy_helper, NULL);
|
|
g_hash_table_destroy (qf->matches);
|
|
qf->matches = NULL;
|
|
|
|
if (qf->text)
|
|
g_free(qf->text);
|
|
qf->text = NULL;
|
|
qf->len = 0;
|
|
|
|
g_free (qf);
|
|
}
|
|
|
|
void
|
|
gnc_quickfill_purge (QuickFill *qf)
|
|
{
|
|
if (qf == NULL)
|
|
return;
|
|
|
|
g_hash_table_foreach_remove (qf->matches, destroy_helper, NULL);
|
|
|
|
if (qf->text)
|
|
g_free (qf->text);
|
|
qf->text = NULL;
|
|
qf->len = 0;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
const char *
|
|
gnc_quickfill_string (QuickFill *qf)
|
|
{
|
|
if (qf == NULL)
|
|
return NULL;
|
|
|
|
return qf->text;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
QuickFill *
|
|
gnc_quickfill_get_char_match (QuickFill *qf, gunichar uc)
|
|
{
|
|
guint key = g_unichar_toupper (uc);
|
|
|
|
if (NULL == qf) return NULL;
|
|
|
|
DEBUG ("xaccGetQuickFill(): index = %u\n", key);
|
|
|
|
return g_hash_table_lookup (qf->matches, GUINT_TO_POINTER (key));
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
QuickFill *
|
|
gnc_quickfill_get_string_len_match (QuickFill *qf,
|
|
const char *str, int len)
|
|
{
|
|
const char *c;
|
|
gunichar uc;
|
|
|
|
if (NULL == qf) return NULL;
|
|
if (NULL == str) return NULL;
|
|
|
|
c = str;
|
|
while (*c && (len > 0))
|
|
{
|
|
if (qf == NULL)
|
|
return NULL;
|
|
|
|
uc = g_utf8_get_char (c);
|
|
qf = gnc_quickfill_get_char_match (qf, uc);
|
|
|
|
c = g_utf8_next_char (c);
|
|
len--;
|
|
}
|
|
|
|
return qf;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
QuickFill *
|
|
gnc_quickfill_get_string_match (QuickFill *qf, const char *str)
|
|
{
|
|
if (NULL == qf) return NULL;
|
|
if (NULL == str) return NULL;
|
|
|
|
return gnc_quickfill_get_string_len_match (qf, str, g_utf8_strlen (str, -1));
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static void
|
|
unique_len_helper (gpointer key, gpointer value, gpointer data)
|
|
{
|
|
QuickFill **qf_p = data;
|
|
|
|
*qf_p = value;
|
|
}
|
|
|
|
QuickFill *
|
|
gnc_quickfill_get_unique_len_match (QuickFill *qf, int *length)
|
|
{
|
|
if (length != NULL)
|
|
*length = 0;
|
|
|
|
if (qf == NULL)
|
|
return NULL;
|
|
|
|
while (1)
|
|
{
|
|
guint count;
|
|
|
|
count = g_hash_table_size (qf->matches);
|
|
|
|
if (count != 1)
|
|
break;
|
|
|
|
g_hash_table_foreach (qf->matches, unique_len_helper, &qf);
|
|
|
|
if (length != NULL)
|
|
(*length)++;
|
|
}
|
|
|
|
return qf;
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
gnc_quickfill_insert (QuickFill *qf, const char *text, QuickFillSort sort)
|
|
{
|
|
gchar *normalized_str;
|
|
int len;
|
|
|
|
if (NULL == qf) return;
|
|
if (NULL == text) return;
|
|
|
|
|
|
normalized_str = g_utf8_normalize (text, -1, G_NORMALIZE_NFC);
|
|
len = g_utf8_strlen (text, -1);
|
|
quickfill_insert_recursive (qf, normalized_str, len, normalized_str, sort);
|
|
g_free (normalized_str);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
static void
|
|
quickfill_insert_recursive (QuickFill *qf, const char *text, int len,
|
|
const char *next_char, QuickFillSort sort)
|
|
{
|
|
guint key;
|
|
char *old_text;
|
|
QuickFill *match_qf;
|
|
gunichar key_char_uc;
|
|
|
|
if (qf == NULL)
|
|
return;
|
|
|
|
if ((text == NULL) || (*next_char == '\0'))
|
|
return;
|
|
|
|
|
|
key_char_uc = g_utf8_get_char (next_char);
|
|
key = g_unichar_toupper (key_char_uc);
|
|
|
|
match_qf = g_hash_table_lookup (qf->matches, GUINT_TO_POINTER (key));
|
|
if (match_qf == NULL)
|
|
{
|
|
match_qf = gnc_quickfill_new ();
|
|
g_hash_table_insert (qf->matches, GUINT_TO_POINTER (key), match_qf);
|
|
}
|
|
|
|
old_text = match_qf->text;
|
|
|
|
switch (sort)
|
|
{
|
|
case QUICKFILL_ALPHA:
|
|
if (old_text && (g_utf8_collate (text, old_text) >= 0))
|
|
break;
|
|
/* fall through */
|
|
|
|
case QUICKFILL_LIFO:
|
|
default:
|
|
/* If there's no string there already, just put the new one in. */
|
|
if (old_text == NULL)
|
|
{
|
|
match_qf->text = g_strdup(text);
|
|
match_qf->len = len;
|
|
break;
|
|
}
|
|
|
|
/* Leave prefixes in place */
|
|
if ((len > match_qf->len) &&
|
|
(strncmp(text, old_text, strlen(old_text)) == 0))
|
|
break;
|
|
|
|
g_free(old_text);
|
|
match_qf->text = g_strdup(text);
|
|
match_qf->len = len;
|
|
break;
|
|
}
|
|
|
|
quickfill_insert_recursive (match_qf, text, len, g_utf8_next_char (next_char), sort);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
void
|
|
gnc_quickfill_remove (QuickFill *qf, const gchar *text, QuickFillSort sort)
|
|
{
|
|
gchar *normalized_str;
|
|
|
|
if (qf == NULL) return;
|
|
if (text == NULL) return;
|
|
|
|
normalized_str = g_utf8_normalize (text, -1, G_NORMALIZE_NFC);
|
|
gnc_quickfill_remove_recursive (qf, normalized_str, 0, sort);
|
|
g_free (normalized_str);
|
|
}
|
|
|
|
/********************************************************************\
|
|
\********************************************************************/
|
|
|
|
struct _BestText
|
|
{
|
|
gchar *text;
|
|
QuickFillSort sort;
|
|
};
|
|
|
|
static void
|
|
best_text_helper (gpointer key, gpointer value, gpointer user_data)
|
|
{
|
|
QuickFill *qf = value;
|
|
struct _BestText *best = user_data;
|
|
|
|
if (best->text == NULL)
|
|
{
|
|
/* start with the first text */
|
|
best->text = qf->text;
|
|
|
|
}
|
|
else if (best->text == QUICKFILL_LIFO)
|
|
{
|
|
/* we do not track history, so ignore it */
|
|
return;
|
|
|
|
}
|
|
else if (g_utf8_collate (qf->text, best->text) < 0)
|
|
{
|
|
/* even better text */
|
|
best->text = qf->text;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
gnc_quickfill_remove_recursive (QuickFill *qf, const gchar *text, gint depth,
|
|
QuickFillSort sort)
|
|
{
|
|
QuickFill *match_qf;
|
|
gchar *child_text;
|
|
gint child_len;
|
|
|
|
child_text = NULL;
|
|
child_len = 0;
|
|
|
|
if (depth < g_utf8_strlen (text, -1))
|
|
{
|
|
/* process next letter */
|
|
|
|
gchar *key_char;
|
|
gunichar key_char_uc;
|
|
guint key;
|
|
|
|
key_char = g_utf8_offset_to_pointer (text, depth);
|
|
key_char_uc = g_utf8_get_char (key_char);
|
|
key = g_unichar_toupper (key_char_uc);
|
|
|
|
match_qf = g_hash_table_lookup (qf->matches, GUINT_TO_POINTER (key));
|
|
if (match_qf)
|
|
{
|
|
/* remove text from child qf */
|
|
gnc_quickfill_remove_recursive (match_qf, text, depth + 1, sort);
|
|
|
|
if (match_qf->text == NULL)
|
|
{
|
|
/* text was the only word with a prefix up to match_qf */
|
|
g_hash_table_remove (qf->matches, GUINT_TO_POINTER (key));
|
|
gnc_quickfill_destroy (match_qf);
|
|
|
|
}
|
|
else
|
|
{
|
|
/* remember remaining best child string */
|
|
child_text = match_qf->text;
|
|
child_len = match_qf->len;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (qf->text == NULL)
|
|
return;
|
|
|
|
if (strcmp (text, qf->text) == 0)
|
|
{
|
|
/* the currently best text is about to be removed */
|
|
|
|
gchar *best_text = NULL;
|
|
gint best_len = 0;
|
|
|
|
if (child_text != NULL)
|
|
{
|
|
/* other children are pretty good as well */
|
|
best_text = child_text;
|
|
best_len = child_len;
|
|
|
|
}
|
|
else
|
|
{
|
|
if (g_hash_table_size (qf->matches) != 0)
|
|
{
|
|
/* otherwise search for another good text */
|
|
struct _BestText bts;
|
|
bts.text = NULL;
|
|
bts.sort = sort;
|
|
|
|
g_hash_table_foreach (qf->matches, (GHFunc) best_text_helper, &bts);
|
|
best_text = bts.text;
|
|
best_len = (best_text == NULL) ? 0 : g_utf8_strlen (best_text, -1);
|
|
}
|
|
}
|
|
|
|
/* now replace or clear text */
|
|
g_free(qf->text);
|
|
if (best_text != NULL)
|
|
{
|
|
qf->text = g_strdup(best_text);
|
|
qf->len = best_len;
|
|
}
|
|
else
|
|
{
|
|
qf->text = NULL;
|
|
qf->len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/********************** END OF FILE ********************************* \
|
|
\********************************************************************/
|