diff --git a/search-provider/cc-search-provider.c b/search-provider/cc-search-provider.c index 19ac9ee46..ab081d0b3 100644 --- a/search-provider/cc-search-provider.c +++ b/search-provider/cc-search-provider.c @@ -113,6 +113,8 @@ handle_get_initial_result_set (CcShellSearchProvider2 *skeleton, casefolded_terms = get_casefolded_terms (terms); results = g_ptr_array_new (); + cc_shell_model_set_sort_terms (CC_SHELL_MODEL (model), casefolded_terms); + ok = gtk_tree_model_get_iter_first (model, &iter); while (ok) { @@ -154,6 +156,8 @@ handle_get_subsearch_result_set (CcShellSearchProvider2 *skeleton, casefolded_terms = get_casefolded_terms (terms); results = g_ptr_array_new (); + cc_shell_model_set_sort_terms (CC_SHELL_MODEL (model), casefolded_terms); + for (i = 0; previous_results[i]; i++) { if (gtk_tree_model_get_iter_from_string (model, &iter, diff --git a/shell/cc-shell-model.c b/shell/cc-shell-model.c index 2add15edc..55f3e3c82 100644 --- a/shell/cc-shell-model.c +++ b/shell/cc-shell-model.c @@ -30,12 +30,225 @@ #define GNOME_SETTINGS_PANEL_CATEGORY GNOME_SETTINGS_PANEL_ID_KEY #define GNOME_SETTINGS_PANEL_ID_KEYWORDS "Keywords" +struct _CcShellModelPrivate +{ + gchar **sort_terms; +}; -G_DEFINE_TYPE (CcShellModel, cc_shell_model, GTK_TYPE_LIST_STORE) +G_DEFINE_TYPE_WITH_PRIVATE (CcShellModel, cc_shell_model, GTK_TYPE_LIST_STORE) + +static gint +sort_by_name (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b) +{ + gchar *a_name = NULL; + gchar *b_name = NULL; + gint rval = 0; + + gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1); + gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1); + + rval = g_strcmp0 (a_name, b_name); + + g_free (a_name); + g_free (b_name); + + return rval; +} + +static gint +sort_by_name_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gboolean a_match, b_match; + gchar *a_name = NULL; + gchar *b_name = NULL; + gint rval = 0; + gint i; + + gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1); + gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1); + + for (i = 0; terms[i]; ++i) + { + a_match = strstr (a_name, terms[i]) != NULL; + b_match = strstr (b_name, terms[i]) != NULL; + + if (a_match && !b_match) + { + rval = -1; + break; + } + else if (!a_match && b_match) + { + rval = 1; + break; + } + } + + g_free (a_name); + g_free (b_name); + + return rval; +} + +static gint +count_matches (gchar **keywords, + gchar **terms) +{ + gint i, j, c; + + if (!keywords || !terms) + return 0; + + c = 0; + + for (i = 0; terms[i]; ++i) + for (j = 0; keywords[j]; ++j) + if (strstr (keywords[j], terms[i])) + c += 1; + + return c; +} + +static gint +sort_by_keywords_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gint a_matches, b_matches; + gchar **a_keywords = NULL; + gchar **b_keywords = NULL; + gint rval = 0; + + gtk_tree_model_get (model, a, COL_KEYWORDS, &a_keywords, -1); + gtk_tree_model_get (model, b, COL_KEYWORDS, &b_keywords, -1); + + a_matches = count_matches (a_keywords, terms); + b_matches = count_matches (b_keywords, terms); + + if (a_matches > b_matches) + rval = -1; + else if (a_matches < b_matches) + rval = 1; + + g_strfreev (a_keywords); + g_strfreev (b_keywords); + + return rval; +} + +static gint +sort_by_description_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gint a_matches, b_matches; + gchar *a_description = NULL; + gchar *b_description = NULL; + gchar **a_description_split = NULL; + gchar **b_description_split = NULL; + gint rval = 0; + + gtk_tree_model_get (model, a, COL_DESCRIPTION, &a_description, -1); + gtk_tree_model_get (model, b, COL_DESCRIPTION, &b_description, -1); + + if (a_description && !b_description) + { + rval = -1; + goto out; + } + else if (!a_description && b_description) + { + rval = 1; + goto out; + } + else if (!a_description && !b_description) + { + rval = 0; + goto out; + } + + a_description_split = g_strsplit (a_description, " ", -1); + b_description_split = g_strsplit (b_description, " ", -1); + + a_matches = count_matches (a_description_split, terms); + b_matches = count_matches (b_description_split, terms); + + if (a_matches > b_matches) + rval = -1; + else if (a_matches < b_matches) + rval = 1; + + out: + g_free (a_description); + g_free (b_description); + g_strfreev (a_description_split); + g_strfreev (b_description_split); + + return rval; +} + +static gint +sort_with_terms (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gchar **terms) +{ + gint rval; + + rval = sort_by_name_with_terms (model, a, b, terms); + if (rval) + return rval; + + rval = sort_by_keywords_with_terms (model, a, b, terms); + if (rval) + return rval; + + rval = sort_by_description_with_terms (model, a, b, terms); + if (rval) + return rval; + + return sort_by_name (model, a, b); +} + +static gint +cc_shell_model_sort_func (GtkTreeModel *model, + GtkTreeIter *a, + GtkTreeIter *b, + gpointer data) +{ + CcShellModel *self = data; + CcShellModelPrivate *priv = self->priv; + + if (!priv->sort_terms || !priv->sort_terms[0]) + return sort_by_name (model, a, b); + else + return sort_with_terms (model, a, b, priv->sort_terms); +} + +static void +cc_shell_model_finalize (GObject *object) +{ + CcShellModelPrivate *priv = CC_SHELL_MODEL (object)->priv;; + + g_strfreev (priv->sort_terms); + + G_OBJECT_CLASS (cc_shell_model_parent_class)->finalize (object); +} static void cc_shell_model_class_init (CcShellModelClass *klass) { + GObjectClass *gobject_class; + + gobject_class = G_OBJECT_CLASS (klass); + gobject_class->finalize = cc_shell_model_finalize; } static void @@ -44,10 +257,16 @@ cc_shell_model_init (CcShellModel *self) GType types[] = {G_TYPE_STRING, G_TYPE_STRING, G_TYPE_APP_INFO, G_TYPE_STRING, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_ICON, G_TYPE_STRV}; + self->priv = cc_shell_model_get_instance_private (self); + gtk_list_store_set_column_types (GTK_LIST_STORE (self), N_COLS, types); - gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self), COL_NAME, + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self), + cc_shell_model_sort_func, + self, NULL); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self), + GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING); } @@ -142,3 +361,18 @@ cc_shell_model_iter_matches_search (CcShellModel *model, return result; } + +void +cc_shell_model_set_sort_terms (CcShellModel *self, + gchar **terms) +{ + CcShellModelPrivate *priv = self->priv; + + g_strfreev (priv->sort_terms); + priv->sort_terms = g_strdupv (terms); + + /* trigger a re-sort */ + gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self), + cc_shell_model_sort_func, + self, NULL); +} diff --git a/shell/cc-shell-model.h b/shell/cc-shell-model.h index f92c25f2e..bf24173a8 100644 --- a/shell/cc-shell-model.h +++ b/shell/cc-shell-model.h @@ -50,6 +50,7 @@ G_BEGIN_DECLS typedef struct _CcShellModel CcShellModel; typedef struct _CcShellModelClass CcShellModelClass; +typedef struct _CcShellModelPrivate CcShellModelPrivate; typedef enum { CC_CATEGORY_PERSONAL, @@ -76,6 +77,8 @@ enum struct _CcShellModel { GtkListStore parent; + + CcShellModelPrivate *priv; }; struct _CcShellModelClass @@ -96,6 +99,9 @@ gboolean cc_shell_model_iter_matches_search (CcShellModel *model, GtkTreeIter *iter, const char *term); +void cc_shell_model_set_sort_terms (CcShellModel *model, + gchar **terms); + G_END_DECLS #endif /* _CC_SHELL_MODEL_H */ diff --git a/shell/cc-window.c b/shell/cc-window.c index d091e7938..8da464521 100644 --- a/shell/cc-window.c +++ b/shell/cc-window.c @@ -621,6 +621,8 @@ search_entry_changed_cb (GtkEntry *entry, g_strfreev (priv->filter_terms); priv->filter_terms = g_strsplit (priv->filter_string, " ", -1); + cc_shell_model_set_sort_terms (CC_SHELL_MODEL (priv->store), priv->filter_terms); + if (!g_strcmp0 (priv->filter_string, "")) { shell_show_overview_page (center);