gnome-control-center/shell/cc-shell-model.c
Florian Müllner d5de9d86c9 model: Convert panel icons to symbolic variants if possible
Panel icons should be consistent between the app and corresponding
search results in GNOME Shell, but currently the former uses the
symbolic variant while the latter uses the colored version.

Address this by converting icons to their symbolic variants when
building the model rather than later when consuming them.

https://gitlab.gnome.org/GNOME/gnome-control-center/merge_requests/151
2018-07-29 21:45:38 +00:00

461 lines
12 KiB
C

/*
* Copyright (c) 2009, 2010 Intel, Inc.
* Copyright (c) 2010 Red Hat, Inc.
*
* The Control Center 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.
*
* The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
* Author: Thomas Wood <thos@gnome.org>
*/
#include "cc-shell-model.h"
#include "cc-util.h"
#include <string.h>
#include <gio/gdesktopappinfo.h>
#define GNOME_SETTINGS_PANEL_ID_KEY "X-GNOME-Settings-Panel"
#define GNOME_SETTINGS_PANEL_CATEGORY GNOME_SETTINGS_PANEL_ID_KEY
#define GNOME_SETTINGS_PANEL_ID_KEYWORDS "Keywords"
struct _CcShellModel
{
GtkListStore parent;
GStrv sort_terms;
};
G_DEFINE_TYPE (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;
if (!self->sort_terms || !self->sort_terms[0])
return sort_by_name (model, a, b);
else
return sort_with_terms (model, a, b, self->sort_terms);
}
static void
cc_shell_model_finalize (GObject *object)
{
CcShellModel *self = CC_SHELL_MODEL (object);
g_clear_pointer (&self->sort_terms, g_strfreev);
G_OBJECT_CLASS (cc_shell_model_parent_class)->finalize (object);
}
static void
cc_shell_model_class_init (CcShellModelClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = cc_shell_model_finalize;
}
static void
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, G_TYPE_UINT };
gtk_list_store_set_column_types (GTK_LIST_STORE (self),
N_COLS, types);
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);
}
CcShellModel *
cc_shell_model_new (void)
{
return g_object_new (CC_TYPE_SHELL_MODEL, NULL);
}
static char **
get_casefolded_keywords (GAppInfo *appinfo)
{
const char * const * keywords;
char **casefolded_keywords;
int i, n;
keywords = g_desktop_app_info_get_keywords (G_DESKTOP_APP_INFO (appinfo));
n = keywords ? g_strv_length ((char**) keywords) : 0;
casefolded_keywords = g_new (char*, n+1);
for (i = 0; i < n; i++)
casefolded_keywords[i] = cc_util_normalize_casefold_and_unaccent (keywords[i]);
casefolded_keywords[n] = NULL;
return casefolded_keywords;
}
static GIcon *
symbolicize_g_icon (GIcon *gicon)
{
const gchar * const *names;
g_autofree gchar *new_name = NULL;
if (!G_IS_THEMED_ICON (gicon))
return g_object_ref (gicon);
names = g_themed_icon_get_names (G_THEMED_ICON (gicon));
if (g_str_has_suffix (names[0], "-symbolic"))
return g_object_ref (gicon);
new_name = g_strdup_printf ("%s-symbolic", names[0]);
return g_themed_icon_new_with_default_fallbacks (new_name);
}
void
cc_shell_model_add_item (CcShellModel *model,
CcPanelCategory category,
GAppInfo *appinfo,
const char *id)
{
g_autoptr(GIcon) icon = NULL;
const gchar *name = g_app_info_get_name (appinfo);
const gchar *comment = g_app_info_get_description (appinfo);
char **keywords;
char *casefolded_name, *casefolded_description;
casefolded_name = cc_util_normalize_casefold_and_unaccent (name);
casefolded_description = cc_util_normalize_casefold_and_unaccent (comment);
keywords = get_casefolded_keywords (appinfo);
icon = symbolicize_g_icon (g_app_info_get_icon (appinfo));
gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, 0,
COL_NAME, name,
COL_CASEFOLDED_NAME, casefolded_name,
COL_APP, appinfo,
COL_ID, id,
COL_CATEGORY, category,
COL_DESCRIPTION, comment,
COL_CASEFOLDED_DESCRIPTION, casefolded_description,
COL_GICON, icon,
COL_KEYWORDS, keywords,
COL_VISIBILITY, CC_PANEL_VISIBLE,
-1);
g_free (casefolded_name);
g_free (casefolded_description);
g_strfreev (keywords);
}
gboolean
cc_shell_model_has_panel (CcShellModel *model,
const char *id)
{
GtkTreeIter iter;
gboolean valid;
g_assert (id);
valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
while (valid)
{
g_autofree gchar *panel_id = NULL;
gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, COL_ID, &panel_id, -1);
if (g_str_equal (id, panel_id))
return TRUE;
valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
}
return FALSE;
}
gboolean
cc_shell_model_iter_matches_search (CcShellModel *model,
GtkTreeIter *iter,
const char *term)
{
gchar *name, *description;
gboolean result;
gchar **keywords;
gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
COL_CASEFOLDED_NAME, &name,
COL_CASEFOLDED_DESCRIPTION, &description,
COL_KEYWORDS, &keywords,
-1);
result = (strstr (name, term) != NULL);
if (!result && description)
result = (strstr (description, term) != NULL);
if (!result && keywords)
{
gint i;
for (i = 0; !result && keywords[i]; i++)
result = (strstr (keywords[i], term) == keywords[i]);
}
g_free (name);
g_free (description);
g_strfreev (keywords);
return result;
}
void
cc_shell_model_set_sort_terms (CcShellModel *self,
gchar **terms)
{
g_return_if_fail (CC_IS_SHELL_MODEL (self));
g_clear_pointer (&self->sort_terms, g_strfreev);
self->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);
}
void
cc_shell_model_set_panel_visibility (CcShellModel *self,
const gchar *id,
CcPanelVisibility visibility)
{
GtkTreeModel *model;
GtkTreeIter iter;
gboolean valid;
g_return_if_fail (CC_IS_SHELL_MODEL (self));
model = GTK_TREE_MODEL (self);
/* Find the iter for the panel with the given id */
valid = gtk_tree_model_get_iter_first (model, &iter);
while (valid)
{
g_autofree gchar *item_id = NULL;
gtk_tree_model_get (model, &iter, COL_ID, &item_id, -1);
/* Found the iter */
if (g_str_equal (id, item_id))
break;
/* If not found, continue */
valid = gtk_tree_model_iter_next (model, &iter);
}
/* If we don't find any panel with the given id, we'll iterate until
* valid == FALSE, so we can use this variable to determine if the
* panel was found or not. It is a programming error to try to set
* the visibility of a non-existant panel.
*/
g_assert (valid);
gtk_list_store_set (GTK_LIST_STORE (self), &iter, COL_VISIBILITY, visibility, -1);
}