/* * Copyright © 2012 Canonical Ltd. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License version 3, as * published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranties of * MERCHANTABILITY, SATISFACTORY QUALITY, 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, see . * * Author: Ryan Lortie */ #include "hudresult.h" #include #include "hudsettings.h" #include "hudtoken.h" /** * SECTION:hudresult * @title: HudResult * @short_description: a search result: a #HudItem plus metadata about * why it matched * * A #HudResult is a wrapper around a #HudItem plus information about * why (and how closely) it matched a particular search. **/ /** * HudResult: * * This is an opaque structure type. **/ typedef GObjectClass HudResultClass; struct _HudResult { GObject parent_instance; HudItem *item; guint distance; gchar *description; }; G_DEFINE_TYPE (HudResult, hud_result, G_TYPE_OBJECT) static void hud_result_finalize (GObject *object) { HudResult *result = HUD_RESULT (object); g_object_unref (result->item); g_free (result->description); G_OBJECT_CLASS (hud_result_parent_class) ->finalize (object); } static void hud_result_init (HudResult *result) { } static void hud_result_class_init (HudResultClass *class) { class->finalize = hud_result_finalize; } /** * hud_result_get_if_matched: * @item: a #HudItem * @search_string: the search string used * @penalty: a penalty value * * Creates a #HudResult for @item, only if the resulting unadjusted * distance would be less than or equal to the maximum distance * specified in the HUD settings. * * This is the same as hud_result_new() except that it will return %NULL * if the distance is too great. * * The penalty value is ignored when checking the maximum distance but * will impact the distance of the created result. As a result, the * returned #HudResult may have an effective distance greater than the * maximum distance. * * Returns: a new #HudResult, or %NULL in event of a poor match **/ HudResult * hud_result_get_if_matched (HudItem *item, HudTokenList *search_tokens, guint penalty) { if (!hud_item_get_enabled (item)) return NULL; /* If we're just a blank list, all should be included */ if (search_tokens == NULL) { return hud_result_new (item, search_tokens, penalty); } /* ignore the penalty in the max-distance calculation */ if (hud_token_list_distance (hud_item_get_token_list (item), search_tokens, NULL) <= hud_settings.max_distance) return hud_result_new (item, search_tokens, penalty); else return NULL; } /* We recurse instead of iterating because we want to visit the tokens * in the "user visible order" (which is backwards from the way the * stack was constructed). This allows for two nice properties: * * - first is that it allow us to avoid prepending to the description * string * * - second is that the token-matching algorithm always returns the * results in this order, so we can always just look at the head * of the queue for our match. */ static void hud_result_format_tokens (GString *string, HudStringList *tokens, const HudToken ***matches) { HudStringList *tail; const gchar *head; guint head_length; gchar *escaped; tail = hud_string_list_get_tail (tokens); if (tail) { /* The tail will get a chance to consume some 'matches' first... */ hud_result_format_tokens (string, tail, matches); g_string_append (string, " > "); } head = hud_string_list_get_head (tokens); head_length = strlen (head); while (matches != NULL && *matches != NULL && **matches != NULL) { const gchar *matched_string; guint match_length; matched_string = hud_token_get_original (**matches, &match_length); if (head <= matched_string && matched_string + match_length <= head + head_length) { /* The matched string is a substring of the string that we * were just about to append. * * Append the part before the match. */ escaped = g_markup_escape_text (head, matched_string - head); g_string_append (string, escaped); g_free (escaped); /* Append the matched part, in bold. */ escaped = g_markup_escape_text (matched_string, match_length); g_string_append (string, ""); g_string_append (string, escaped); g_string_append (string, ""); g_free (escaped); /* Fast-forward the head string. There may be multiple * matches here (so we go another time around the 'while'). */ head = matched_string + match_length; /* That's it for this match. */ (*matches)++; } else /* Didn't match? Stop. */ break; } /* Append whatever is left of the string after dealing with the * matches */ escaped = g_markup_escape_text (head, -1); g_string_append (string, escaped); g_free (escaped); } static void hud_result_format_keywords (GString *string, HudStringList *tokens, const HudToken ***matches, gint *count) { HudStringList *tail; const gchar *head; guint head_length; gchar *escaped; tail = hud_string_list_get_tail (tokens); if (tail) { /* The tail will get a chance to consume some 'matches' first... */ hud_result_format_keywords (string, tail, matches, count); } head = hud_string_list_get_head (tokens); head_length = strlen (head); if (*count > 0) { g_string_append (string, "; "); } while (matches != NULL && *matches != NULL && **matches != NULL) { const gchar *matched_string; guint match_length; matched_string = hud_token_get_original (**matches, &match_length); if (matched_string && head <= matched_string && matched_string + match_length <= head + head_length) { (*count)++; /* The matched string is a substring of the string that we * were just about to append. * * Append the part before the match. */ escaped = g_markup_escape_text (head, matched_string - head); g_string_append (string, escaped); g_free (escaped); /* Append the matched part, in bold. */ escaped = g_markup_escape_text (matched_string, match_length); g_string_append (string, ""); g_string_append (string, escaped); g_string_append (string, ""); g_free (escaped); /* Fast-forward the head string. There may be multiple * matches here (so we go another time around the 'while'). */ head = matched_string + match_length; /* That's it for this match. */ (*matches)++; } else /* Didn't match? Stop. */ break; } } static gchar * hud_result_format_description (HudStringList *tokens, HudStringList *keywords, const HudToken **matches) { GString *description; description = g_string_new (NULL); hud_result_format_tokens (description, tokens, &matches); if (matches != NULL && *matches != NULL) { gint count = 0; g_string_append (description, " ("); hud_result_format_keywords (description, keywords, &matches, &count); g_string_append (description, ")"); } return g_string_free (description, FALSE); } /** * hud_result_new: * @item: a #HudItem * @search_string: the search string used * @penalty: a penalty value * * Creates a #HudResult for @item as search for using @search_string. * * If @penalty is non-zero then it is used to increase the distance of * the result. This is used to decrease the ranking of matches from the * indicators. * * Returns: the new #HudResult **/ HudResult * hud_result_new (HudItem *item, HudTokenList *search_tokens, guint penalty) { const HudToken **matched = NULL; HudResult *result; g_return_val_if_fail (HUD_IS_ITEM (item), NULL); result = g_object_new (HUD_TYPE_RESULT, NULL); result->item = g_object_ref (item); result->distance = 10; if (search_tokens != NULL) { result->distance = hud_token_list_distance (hud_item_get_token_list (item), search_tokens, &matched); } result->description = hud_result_format_description ( hud_item_get_tokens (item), hud_item_get_keywords(item), matched); g_free (matched); result->distance += (result->distance * penalty) / 100; if (result->distance == 0 && penalty > 0) result->distance = 1; return result; } /** * hud_result_get_distance: * @result: a #HudResult * @max_usage: the maximum usage count we consider * * Returns the "adjusted" distance of @result. * * If @max_usage is zero then the returned value is equal to the * distance between the #HudItem used to create the result and the * search string. * * If @max_usage is non-zero then it is taken to be the usage count of * the most-used item in the same query as this result. The distance is * adjusted for this fact to penalise less-frequently-used item. * * Returns: the adjusted distance **/ guint hud_result_get_distance (HudResult *result, guint max_usage) { guint distance; g_return_val_if_fail (HUD_IS_RESULT (result), G_MAXINT); distance = result->distance; if (max_usage != 0) { guint usage, inverse_usage; usage = hud_item_get_usage (result->item); inverse_usage = max_usage - usage; distance += (distance * inverse_usage) / max_usage; } return distance; } /** * hud_result_get_item: * @result: a #HudResult * * Gets the #HudItem for @result. * * Returns: (transfer none): a #HudItem **/ HudItem * hud_result_get_item (HudResult *result) { return result->item; } /** * hud_result_get_html_description: * @result: a #HudResult * * Returns a textual description of @result with the parts of the text * that matched the search string strenghtened (ie: in bold). * * Returns: the description **/ const gchar * hud_result_get_html_description (HudResult *result) { return result->description; }