mirror of
https://github.com/Gnucash/gnucash.git
synced 2024-11-29 20:24:25 -06:00
83d14e1c1c
It is split into - /libgnucash (for the non-gui bits) - /gnucash (for the gui) - /common (misc source files used by both) - /bindings (currently only holds python bindings) This is the first step in restructuring the code. It will need much more fine tuning later on.
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 ********************************* \
|
|
\********************************************************************/
|