diff options
Diffstat (limited to 'addressbook')
-rw-r--r-- | addressbook/ChangeLog | 18 | ||||
-rw-r--r-- | addressbook/backend/ebook/e-book-util.c | 3 | ||||
-rw-r--r-- | addressbook/backend/ebook/e-card-compare.c | 159 | ||||
-rw-r--r-- | addressbook/backend/ebook/e-card-compare.h | 15 | ||||
-rw-r--r-- | addressbook/gui/component/select-names/e-select-names-completion.c | 132 |
5 files changed, 187 insertions, 140 deletions
diff --git a/addressbook/ChangeLog b/addressbook/ChangeLog index 812ce95967..2e874185ce 100644 --- a/addressbook/ChangeLog +++ b/addressbook/ChangeLog @@ -1,3 +1,21 @@ +2001-10-02 Jon Trowbridge <trow@gnu.org> + + * gui/component/select-names/e-select-names-completion.c + (name_style_query): Strip out commas before forming our query. + (match_name): Use e_card_compare_name_to_string_full, rather + than our crufty old matching code. Not only is this cleaner, + but that crufty old code was very broken when it came to + handling strings with whitespace. (Bug 8988) + (match_nickname): utf8 and bug fixes. + + * backend/ebook/e-card-compare.c + (e_card_compare_name_to_string_full): Added. This is basically + e_card_compare_name_to_string with a bunch of extra options, so + that it can more readily be reused in other contexts. + (e_card_compare_name_to_string): This is now just a call to + e_card_compare_name_to_string_full with the extra args filled in + to defaults that simulate the old behavior. + 2001-10-02 Chris Toshok <toshok@ximian.com> * backend/ebook/e-book.c (e_book_construct): remove most of the diff --git a/addressbook/backend/ebook/e-book-util.c b/addressbook/backend/ebook/e-book-util.c index 1468274230..f9889522cb 100644 --- a/addressbook/backend/ebook/e-book-util.c +++ b/addressbook/backend/ebook/e-book-util.c @@ -534,8 +534,7 @@ static void have_address_query_cb (EBook *book, EBookSimpleQueryStatus status, const GList *cards, gpointer closure) { HaveAddressInfo *info = (HaveAddressInfo *) closure; - - + info->cb (book, info->email, cards && (status == E_BOOK_SIMPLE_QUERY_STATUS_SUCCESS) ? E_CARD (cards->data) : NULL, diff --git a/addressbook/backend/ebook/e-card-compare.c b/addressbook/backend/ebook/e-card-compare.c index 5cdc745dba..d4ffa56dc7 100644 --- a/addressbook/backend/ebook/e-card-compare.c +++ b/addressbook/backend/ebook/e-card-compare.c @@ -46,14 +46,16 @@ combine_comparisons (ECardMatchType prev, /*** Name comparisons ***/ /* This *so* doesn't belong here... at least not implemented in a - sucky way like this. But by getting it in here now, I can fix it - up w/o adding a new feature when we are in feature freeze. :-) */ + sucky way like this. But it can be fixed later. */ /* This is very Anglocentric. */ static gchar *name_synonyms[][2] = { { "jon", "john" }, /* Ah, the hacker's perogative */ { "joseph", "joe" }, { "robert", "bob" }, + { "gene", "jean" }, + { "jesse", "jessie" }, + { "ian", "iain" }, { "richard", "dick" }, { "william", "bill" }, { "anthony", "tony" }, @@ -72,19 +74,44 @@ static gchar *name_synonyms[][2] = { { "rebecca", "becca" }, { "rebecca", "becky" }, { "anderson", "andersen" }, + { "johnson", "johnsen" }, /* We could go on and on... */ { NULL, NULL } }; static gboolean -name_fragment_match (const gchar *a, const gchar *b) +name_fragment_match (const gchar *a, const gchar *b, gboolean strict) { - gint i, len_a, len_b; + gint len; - /* This will cause "Chris" and "Christopher" to match. */ - len_a = g_utf8_strlen (a, -1); - len_b = g_utf8_strlen (b, -1); - if (!g_utf8_strncasecmp (a, b, MIN (len_a, len_b))) + if (!(a && b && *a && *b)) + return FALSE; + + /* If we are in 'strict' mode, b must match the beginning of a. + So "Robert", "Rob" would match, but "Robert", "Robbie" wouldn't. + + If strict is FALSE, it is sufficient for the strings to share + some leading characters. In this case, "Robert" and "Robbie" + would match, as would "Dave" and "Dan". */ + + if (strict) { + len = g_utf8_strlen (b, -1); + } else { + len = MIN (g_utf8_strlen (a, -1), g_utf8_strlen (b, -1)); + } + + return !g_utf8_strncasecmp (a, b, len); +} + +static gboolean +name_fragment_match_with_synonyms (const gchar *a, const gchar *b, gboolean strict) +{ + gint i; + + if (!(a && b && *a && *b)) + return FALSE; + + if (name_fragment_match (a, b, strict)) return TRUE; /* Check for nicknames. Yes, the linear search blows. */ @@ -105,10 +132,21 @@ name_fragment_match (const gchar *a, const gchar *b) ECardMatchType e_card_compare_name_to_string (ECard *card, const gchar *str) { + return e_card_compare_name_to_string_full (card, str, FALSE, NULL, NULL, NULL); +} + +ECardMatchType +e_card_compare_name_to_string_full (ECard *card, const gchar *str, gboolean allow_partial_matches, + gint *matched_parts_out, ECardMatchPart *first_matched_part_out, gint *matched_character_count_out) +{ gchar **namev, **givenv = NULL, **addv = NULL, **familyv = NULL; - gboolean matched_given = FALSE, matched_additional = FALSE, matched_family = FALSE, mismatch = FALSE; + + gint matched_parts = E_CARD_MATCH_PART_NONE; + ECardMatchPart first_matched_part = E_CARD_MATCH_PART_NONE; + ECardMatchPart this_part_match = E_CARD_MATCH_PART_NOT_APPLICABLE; ECardMatchType match_type; - gint match_count = 0; + + gint match_count = 0, matched_character_count = 0, fragment_count; gint i, j; gchar *str_cpy, *s; @@ -116,7 +154,6 @@ e_card_compare_name_to_string (ECard *card, const gchar *str) g_return_val_if_fail (card->name != NULL, E_CARD_MATCH_NOT_APPLICABLE); g_return_val_if_fail (str != NULL, E_CARD_MATCH_NOT_APPLICABLE); - /* FIXME: utf-8 */ str_cpy = s = g_strdup (str); while (*s) { if (*s == ',' || *s == '"') @@ -132,68 +169,106 @@ e_card_compare_name_to_string (ECard *card, const gchar *str) addv = g_strsplit (card->name->additional, " ", 0); if (card->name->family) familyv = g_strsplit (card->name->family, " ", 0); + + fragment_count = 0; + for (i = 0; givenv && givenv[i]; ++i) + ++fragment_count; + for (i = 0; addv && addv[i]; ++i) + ++fragment_count; + for (i = 0; familyv && familyv[i]; ++i) + ++fragment_count; - for (i = 0; namev[i] && !mismatch; ++i) { + for (i = 0; namev[i] && this_part_match != E_CARD_MATCH_PART_NONE; ++i) { if (*namev[i]) { - mismatch = TRUE; + this_part_match = E_CARD_MATCH_PART_NONE; + + /* When we are allowing partials, we are strict about the matches we allow. + Does this make sense? Not really, but it does the right thing for the purposes + of completion. */ - if (mismatch && givenv) { + if (givenv && this_part_match == E_CARD_MATCH_PART_NONE) { for (j = 0; givenv[j]; ++j) { - if (name_fragment_match (givenv[j], namev[i])) { - matched_given = TRUE; - mismatch = FALSE; - ++match_count; + if (name_fragment_match_with_synonyms (givenv[j], namev[i], allow_partial_matches)) { + + this_part_match = E_CARD_MATCH_PART_GIVEN_NAME; + + /* We remove a piece of a name once it has been matched against, so + that "john john" won't match "john doe". */ + g_free (givenv[j]); + givenv[j] = g_strdup (""); break; } } } - if (mismatch && addv) { + if (addv && this_part_match == E_CARD_MATCH_PART_NONE) { for (j = 0; addv[j]; ++j) { - if (name_fragment_match (addv[j], namev[i])) { - matched_additional = TRUE; - mismatch = FALSE; - ++match_count; + if (name_fragment_match_with_synonyms (addv[j], namev[i], allow_partial_matches)) { + + this_part_match = E_CARD_MATCH_PART_ADDITIONAL_NAME; + + g_free (addv[j]); + addv[j] = g_strdup (""); break; } } } - if (mismatch && familyv) { + if (familyv && this_part_match == E_CARD_MATCH_PART_NONE) { for (j = 0; familyv[j]; ++j) { - if (!g_utf8_strcasecmp (familyv[j], namev[i])) { - matched_family = TRUE; - mismatch = FALSE; - ++match_count; + if (allow_partial_matches ? name_fragment_match_with_synonyms (familyv[j], namev[i], allow_partial_matches) + : !g_utf8_strcasecmp (familyv[j], namev[i])) { + + this_part_match = E_CARD_MATCH_PART_FAMILY_NAME; + + g_free (familyv[j]); + familyv[j] = g_strdup (""); break; } } } + if (this_part_match != E_CARD_MATCH_PART_NONE) { + ++match_count; + matched_character_count += g_utf8_strlen (namev[i], -1); + matched_parts |= this_part_match; + if (first_matched_part == E_CARD_MATCH_PART_NONE) + first_matched_part = this_part_match; + } } } - match_type = E_CARD_MATCH_NONE; - if (! mismatch) { - - switch ( (matched_family ? 1 : 0) + (matched_additional ? 1 : 0) + (matched_given ? 1 : 0)) { - case 0: - match_type = E_CARD_MATCH_NONE; - break; - case 1: + if (this_part_match != E_CARD_MATCH_PART_NONE) { + + if (match_count > 0) match_type = E_CARD_MATCH_VAGUE; - break; - case 2: - case 3: + + if (fragment_count == match_count) { + + match_type = E_CARD_MATCH_EXACT; + + } else if (fragment_count == match_count + 1) { + match_type = E_CARD_MATCH_PARTIAL; - break; + } } + if (match_type != E_CARD_MATCH_NONE) { + g_message ("Matched %s on %s", e_card_name_to_string (card->name), str); + } + + if (matched_parts_out) + *matched_parts_out = matched_parts; + if (first_matched_part_out) + *first_matched_part_out = first_matched_part; + if (matched_character_count_out) + *matched_character_count_out = matched_character_count; + g_strfreev (namev); g_strfreev (givenv); g_strfreev (addv); @@ -220,7 +295,7 @@ e_card_compare_name (ECard *card1, ECard *card2) if (a->given && b->given) { ++possible; - if (name_fragment_match (a->given, b->given)) { + if (name_fragment_match_with_synonyms (a->given, b->given, FALSE /* both inputs are complete */)) { ++matches; given_match = TRUE; } @@ -228,7 +303,7 @@ e_card_compare_name (ECard *card1, ECard *card2) if (a->additional && b->additional) { ++possible; - if (name_fragment_match (a->additional, b->additional)) { + if (name_fragment_match_with_synonyms (a->additional, b->additional, FALSE /* both inputs are complete */)) { ++matches; additional_match = TRUE; } diff --git a/addressbook/backend/ebook/e-card-compare.h b/addressbook/backend/ebook/e-card-compare.h index 56d7b6e1f5..355dc814a9 100644 --- a/addressbook/backend/ebook/e-card-compare.h +++ b/addressbook/backend/ebook/e-card-compare.h @@ -39,9 +39,22 @@ typedef enum { E_CARD_MATCH_EXACT = 4 } ECardMatchType; +typedef enum { + E_CARD_MATCH_PART_NOT_APPLICABLE = -1, + E_CARD_MATCH_PART_NONE = 0, + E_CARD_MATCH_PART_GIVEN_NAME = 1<<0, + E_CARD_MATCH_PART_ADDITIONAL_NAME = 1<<2, + E_CARD_MATCH_PART_FAMILY_NAME = 1<<3 +} ECardMatchPart; + typedef void (*ECardMatchQueryCallback) (ECard *card, ECard *match, ECardMatchType type, gpointer closure); -ECardMatchType e_card_compare_name_to_string (ECard *card, const gchar *str); +ECardMatchType e_card_compare_name_to_string (ECard *card, const gchar *str); + +ECardMatchType e_card_compare_name_to_string_full (ECard *card, const gchar *str, + gboolean allow_partial_matches, + gint *matched_parts, ECardMatchPart *first_matched_part, + gint *matched_character_count); ECardMatchType e_card_compare_name (ECard *card1, ECard *card2); ECardMatchType e_card_compare_nickname (ECard *card1, ECard *card2); diff --git a/addressbook/gui/component/select-names/e-select-names-completion.c b/addressbook/gui/component/select-names/e-select-names-completion.c index 1e9027e8fc..7f4a89af54 100644 --- a/addressbook/gui/component/select-names/e-select-names-completion.c +++ b/addressbook/gui/component/select-names/e-select-names-completion.c @@ -42,6 +42,7 @@ #include <addressbook/backend/ebook/e-book-util.h> #include <addressbook/backend/ebook/e-destination.h> #include <addressbook/backend/ebook/e-card-simple.h> +#include <addressbook/backend/ebook/e-card-compare.h> struct _ESelectNamesCompletionPrivate { @@ -142,15 +143,14 @@ match_nickname (ESelectNamesCompletion *comp, EDestination *dest) if (card->nickname == NULL) return NULL; - len = MIN (strlen (card->nickname), strlen (comp->priv->query_text)); - + len = g_utf8_strlen (comp->priv->query_text, -1); if (card->nickname && !g_utf8_strncasecmp (comp->priv->query_text, card->nickname, len)) { const gchar *name; gchar *str; score = len * 2; /* nickname gives 2 points per matching character */ - if (len == strlen (card->nickname)) /* boost score on an exact match */ + if (len == g_utf8_strlen (card->nickname, -1)) /* boost score on an exact match */ score *= 10; name = e_destination_get_name (dest); @@ -216,11 +216,16 @@ static gchar * name_style_query (ESelectNamesCompletion *comp, const gchar *field) { if (comp && comp->priv->query_text && *comp->priv->query_text) { - gchar *cpy = g_strdup (comp->priv->query_text); + gchar *cpy = g_strdup (comp->priv->query_text), *c; gchar **strv; gchar *query; gint i, count=0; + for (c = cpy; *c; ++c) { + if (*c == ',') + *c = ' '; + } + strv = g_strsplit (cpy, " ", 0); for (i=0; strv[i]; ++i) { ++count; @@ -252,35 +257,6 @@ sexp_name (ESelectNamesCompletion *comp) return name_style_query (comp, "full_name"); } -enum { - MATCHED_NOTHING = 0, - MATCHED_GIVEN_NAME = 1<<0, - MATCHED_ADDITIONAL_NAME = 1<<1, - MATCHED_FAMILY_NAME = 1<<2 -}; - -/* - Match text against every substring in fragment that follows whitespace. - This allows the fragment "de Icaza" to match against txt "ica". -*/ -static gboolean -match_name_fragment (const gchar *fragment, const gchar *txt) -{ - gint len = strlen (txt); - - while (*fragment) { - if (!g_utf8_strncasecmp (fragment, txt, len)) - return TRUE; - - while (*fragment && !isspace ((gint) *fragment)) - ++fragment; - while (*fragment && isspace ((gint) *fragment)) - ++fragment; - } - - return FALSE; -} - static ECompletionMatch * match_name (ESelectNamesCompletion *comp, EDestination *dest) { @@ -288,9 +264,9 @@ match_name (ESelectNamesCompletion *comp, EDestination *dest) gchar *menu_text = NULL; ECard *card; const gchar *email; - gchar *cpy, **strv; - gint len, i, match_len = 0; - gint match = MATCHED_NOTHING, first_match = MATCHED_NOTHING; + gint match_len = 0; + ECardMatchType match; + ECardMatchPart first_match; double score = 0; gboolean have_given, have_additional, have_family; @@ -301,56 +277,15 @@ match_name (ESelectNamesCompletion *comp, EDestination *dest) email = e_destination_get_email (dest); - cpy = g_strdup (comp->priv->query_text); - strv = g_strsplit (cpy, " ", 0); - - for (i=0; strv[i] != NULL; ++i) { - gint this_match = MATCHED_NOTHING; - - g_strstrip (strv[i]); - len = strlen (strv[i]); - - if (card->name->given - && *card->name->given - && !(match & MATCHED_GIVEN_NAME) - && match_name_fragment (card->name->given, strv[i])) { - - this_match = MATCHED_GIVEN_NAME; - - } - else if (card->name->additional - && *card->name->additional - && !(match & MATCHED_ADDITIONAL_NAME) - && match_name_fragment (card->name->additional, strv[i])) { - - this_match = MATCHED_ADDITIONAL_NAME; - - } else if (card->name->family - && *card->name->family - && !(match & MATCHED_FAMILY_NAME) - && match_name_fragment (card->name->family, strv[i])) { - - this_match = MATCHED_FAMILY_NAME; - } - - - if (this_match != MATCHED_NOTHING) { - match_len += len; - match |= this_match; - if (i == 0) - first_match = this_match; - } else { - match = first_match = MATCHED_NOTHING; - break; - } - - } + match = e_card_compare_name_to_string_full (card, comp->priv->query_text, TRUE /* yes, allow partial matches */, + NULL, &first_match, &match_len); - g_free (cpy); - g_strfreev (strv); + if (match <= E_CARD_MATCH_NONE) + return NULL; score = match_len * 3; /* three points per match character */ +#if 0 if (card->nickname) { /* We massively boost the score if the nickname exists and is the same as one of the "real" names. This keeps the nickname from matching ahead of the real name for this card. */ @@ -360,23 +295,24 @@ match_name (ESelectNamesCompletion *comp, EDestination *dest) || (card->name->additional && !g_utf8_strncasecmp (card->name->additional, card->nickname, MIN (strlen (card->name->additional), len)))) score *= 100; } +#endif have_given = card->name->given && *card->name->given; have_additional = card->name->additional && *card->name->additional; have_family = card->name->family && *card->name->family; - if (first_match != MATCHED_NOTHING && e_card_evolution_list (card)) { + if (e_card_evolution_list (card)) { menu_text = e_card_name_to_string (card->name); - } else if (first_match == MATCHED_GIVEN_NAME) { + } else if (first_match == E_CARD_MATCH_PART_GIVEN_NAME) { if (have_family) menu_text = g_strdup_printf ("%s %s <%s>", card->name->given, card->name->family, email); else menu_text = g_strdup_printf ("%s <%s>", card->name->given, email); - } else if (first_match == MATCHED_ADDITIONAL_NAME) { + } else if (first_match == E_CARD_MATCH_PART_ADDITIONAL_NAME) { if (have_family) { @@ -397,7 +333,7 @@ match_name (ESelectNamesCompletion *comp, EDestination *dest) } - } else if (first_match == MATCHED_FAMILY_NAME) { + } else if (first_match == E_CARD_MATCH_PART_FAMILY_NAME) { if (have_given) menu_text = g_strdup_printf ("%s, %s%s%s <%s>", @@ -408,6 +344,11 @@ match_name (ESelectNamesCompletion *comp, EDestination *dest) email); else menu_text = g_strdup_printf ("%s <%s>", card->name->family, email); + + } else { /* something funny happened */ + + menu_text = g_strdup_printf ("<%s> ???", email); + } if (menu_text) { @@ -435,9 +376,8 @@ match_file_as (ESelectNamesCompletion *comp, EDestination *dest) const gchar *name; const gchar *email; gchar *cpy, **strv, *menu_text; - gint i; - gboolean matched; - double score = 0; + gint i, len; + double score = 0.00001; ECompletionMatch *match; name = e_destination_get_name (dest); @@ -449,17 +389,18 @@ match_file_as (ESelectNamesCompletion *comp, EDestination *dest) cpy = g_strdup (comp->priv->query_text); strv = g_strsplit (cpy, " ", 0); - matched = FALSE; - for (i=0; strv[i] && !matched; ++i) { - matched = match_name_fragment (name, strv[i]); - if (matched) - score = strlen (strv[i]); /* one point per character of the match */ + for (i=0; strv[i] && score > 0; ++i) { + len = g_utf8_strlen (strv[i], -1); + if (!g_utf8_strncasecmp (name, strv[i], len)) + score += len; /* one point per character of the match */ + else + score = 0; } g_free (cpy); g_strfreev (strv); - if (!matched) + if (score <= 0) return NULL; menu_text = g_strdup_printf ("%s <%s>", name, email); @@ -1075,6 +1016,7 @@ static SearchOverride override[] = { { "why?", { "\"I must create a system, or be enslaved by another man's.\"", " -- Wiliam Blake, \"Jerusalem\"", NULL } }, + { "easter-egg?", { "What were you expecting, a flight simulator?", NULL } }, { NULL, { NULL } } }; static gboolean |