void
cc_shell_model_add_item (CcShellModel    *model,
                         CcPanelCategory  category,
                         GAppInfo        *appinfo,
                         const char      *id)
{
  GIcon       *icon = g_app_info_get_icon (appinfo);
  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);

  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,
                                     -1);

  g_free (casefolded_name);
  g_free (casefolded_description);
  g_strfreev (keywords);
}
static GtkWidget *
input_source_widget_new (GtkWidget   *chooser,
                         const gchar *type,
                         const gchar *id)
{
  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
  GtkWidget *widget = NULL;

  if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
    {
      const gchar *display_name;

      gnome_xkb_info_get_layout_info (priv->xkb_info, id, &display_name, NULL, NULL, NULL);

      widget = padded_label_new (display_name,
                                 ROW_LABEL_POSITION_START,
                                 ROW_TRAVEL_DIRECTION_NONE,
                                 FALSE);
      g_object_set_data (G_OBJECT (widget), "name", (gpointer) display_name);
      g_object_set_data_full (G_OBJECT (widget), "unaccented-name",
                              cc_util_normalize_casefold_and_unaccent (display_name), g_free);
    }
  else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
    {
#ifdef HAVE_IBUS
      gchar *display_name;
      GtkWidget *image;

      display_name = engine_get_display_name (g_hash_table_lookup (priv->ibus_engines, id));

      widget = padded_label_new (display_name,
                                 ROW_LABEL_POSITION_START,
                                 ROW_TRAVEL_DIRECTION_NONE,
                                 FALSE);
      image = gtk_image_new_from_icon_name ("system-run-symbolic", GTK_ICON_SIZE_MENU);
      set_row_widget_margins (image);
      gtk_style_context_add_class (gtk_widget_get_style_context (image), "dim-label");
      gtk_box_pack_start (GTK_BOX (widget), image, FALSE, TRUE, 0);

      g_object_set_data_full (G_OBJECT (widget), "name", display_name, g_free);
      g_object_set_data_full (G_OBJECT (widget), "unaccented-name",
                              cc_util_normalize_casefold_and_unaccent (display_name), g_free);
#else
      widget = NULL;
#endif  /* HAVE_IBUS */
    }

  if (widget)
    {
      g_object_set_data (G_OBJECT (widget), "type", (gpointer) type);
      g_object_set_data (G_OBJECT (widget), "id", (gpointer) id);
    }

  return widget;
}
static gboolean
language_visible (GtkListBoxRow *row,
                  gpointer       user_data)
{
        CcLanguageChooser *chooser = user_data;
        CcLanguageChooserPrivate *priv = chooser->priv;
        gchar *locale_name = NULL;
        gchar *locale_current_name = NULL;
        gchar *locale_untranslated_name = NULL;
        LanguageWidget *widget;
        gboolean visible;
        GtkWidget *child;

        child = gtk_bin_get_child (GTK_BIN (row));
        if (child == priv->more_item)
                return !priv->showing_extra;

        widget = get_language_widget (child);

        if (!priv->showing_extra && widget->is_extra)
                return FALSE;

        if (!priv->filter_words)
                return TRUE;

        visible = FALSE;

        locale_name = cc_util_normalize_casefold_and_unaccent (widget->locale_name);
        visible = match_all (priv->filter_words, locale_name);
        if (visible)
                goto out;

        locale_current_name = cc_util_normalize_casefold_and_unaccent (widget->locale_current_name);
        visible = match_all (priv->filter_words, locale_current_name);
        if (visible)
                goto out;

        locale_untranslated_name = cc_util_normalize_casefold_and_unaccent (widget->locale_untranslated_name);
        visible = match_all (priv->filter_words, locale_untranslated_name);
        if (visible)
                goto out;

 out:
        g_free (locale_untranslated_name);
        g_free (locale_current_name);
        g_free (locale_name);
        return visible;
}
static gboolean
language_visible (GtkListBoxRow *row,
                  gpointer   user_data)
{
        GtkDialog *chooser = user_data;
        CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser);
        gchar *locale_name = NULL;
        gchar *locale_current_name = NULL;
        gchar *locale_untranslated_name = NULL;
        gboolean is_extra;
        gboolean visible;

        if (row == priv->more_item)
                return !priv->showing_extra;

        is_extra = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "is-extra"));

        if (!priv->showing_extra && is_extra)
                return FALSE;

        if (!priv->filter_words)
                return TRUE;

        visible = FALSE;

        locale_name =
                cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-name"));
        visible = match_all (priv->filter_words, locale_name);
        if (visible)
                goto out;

        locale_current_name =
                cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-current-name"));
        visible = match_all (priv->filter_words, locale_current_name);
        if (visible)
                goto out;

        locale_untranslated_name =
                cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "locale-untranslated-name"));
        visible = match_all (priv->filter_words, locale_untranslated_name);

out:
        g_free (locale_untranslated_name);
        g_free (locale_current_name);
        g_free (locale_name);
        return visible;
}
static void
filter_changed (GtkEntry        *entry,
                CcLanguageChooser *chooser)
{
        CcLanguageChooserPrivate *priv = chooser->priv;
        gchar *filter_contents = NULL;

        g_clear_pointer (&priv->filter_words, g_strfreev);

        filter_contents =
                cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (priv->filter_entry)));
        if (!filter_contents)
                return;
        priv->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
        g_free (filter_contents);
        gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list));
}
static void
filter_changed (GtkDialog *chooser)
{
        CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser);
        gchar *filter_contents = NULL;

        g_clear_pointer (&priv->filter_words, g_strfreev);

        filter_contents =
                cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (priv->filter_entry)));
        if (!filter_contents) {
                gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list));
                gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->language_list), NULL);
                return;
        }
        priv->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
        g_free (filter_contents);
        gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->language_list), priv->no_results);
        gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list));
}
static gboolean
do_filter (GtkWidget *chooser)
{
  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
  gchar **previous_words;
  gchar *filter_contents = NULL;

  priv->filter_timeout_id = 0;

  previous_words = priv->filter_words;

  filter_contents =
    cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (priv->filter_entry)));

  if (filter_contents)
    {
      priv->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
      g_free (filter_contents);
    }

  if (!priv->filter_words || !priv->filter_words[0])
    {
      g_clear_pointer (&priv->filter_words, g_strfreev);
      gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->list));
      gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->list), NULL);
    }
  else
    {
      if (!previous_words || strvs_differ (priv->filter_words, previous_words))
        {
          gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->list));
          gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->list), priv->no_results);
        }
    }

  g_strfreev (previous_words);

  return G_SOURCE_REMOVE;
}
static void
filter_changed (GtkWidget *chooser)
{
  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
  gboolean was_filtering;
  gchar **previous_words;
  gchar *filter_contents = NULL;

  previous_words = priv->filter_words;
  was_filtering = previous_words != NULL;

  filter_contents =
    cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (priv->filter_entry)));

  if (filter_contents)
    {
      priv->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
      g_free (filter_contents);
    }

  if (!priv->filter_words || !priv->filter_words[0])
    {
      g_clear_pointer (&priv->filter_words, g_strfreev);
      if (was_filtering)
        show_locale_widgets (chooser);
    }
  else
    {
      if (!was_filtering)
        show_filter_widgets (chooser);
      else if (strvs_differ (priv->filter_words, previous_words))
        gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->list));
    }

  g_strfreev (previous_words);
}
static void
get_locale_infos (GtkWidget *chooser)
{
  CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
  GHashTable *layouts_with_locale;
  LocaleInfo *info;
  gchar **locale_ids;
  gchar **locale;
  GList *list, *l;

  priv->locales = g_hash_table_new_full (g_str_hash, g_str_equal,
                                         NULL, locale_info_free);
  priv->locales_by_language = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                     g_free, (GDestroyNotify) g_hash_table_destroy);

  layouts_with_locale = g_hash_table_new (g_str_hash, g_str_equal);

  locale_ids = gnome_get_all_locales ();
  for (locale = locale_ids; *locale; ++locale)
    {
      gchar *lang_code, *country_code;
      gchar *simple_locale;
      gchar *tmp;
      const gchar *type = NULL;
      const gchar *id = NULL;

      if (!gnome_parse_locale (*locale, &lang_code, &country_code, NULL, NULL))
        continue;

      if (country_code != NULL)
	simple_locale = g_strdup_printf ("%s_%s.UTF-8", lang_code, country_code);
      else
	simple_locale = g_strdup_printf ("%s.UTF-8", lang_code);

      if (g_hash_table_contains (priv->locales, simple_locale))
        {
          g_free (simple_locale);
          g_free (country_code);
          g_free (lang_code);
          continue;
        }

      info = g_new0 (LocaleInfo, 1);
      info->id = simple_locale; /* Take ownership */
      info->name = gnome_get_language_from_locale (simple_locale, NULL);
      info->unaccented_name = cc_util_normalize_casefold_and_unaccent (info->name);
      tmp = gnome_get_language_from_locale (simple_locale, "C");
      info->untranslated_name = cc_util_normalize_casefold_and_unaccent (tmp);
      g_free (tmp);

      g_hash_table_replace (priv->locales, simple_locale, info);
      add_locale_to_table (priv->locales_by_language, lang_code, info);

      if (gnome_get_input_source_from_locale (simple_locale, &type, &id) &&
          g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
        {
          add_default_row (chooser, info, type, id);
          g_hash_table_add (layouts_with_locale, (gpointer) id);
        }

      /* We don't own these ids */
      info->layout_rows_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                       NULL, g_object_unref);
      info->engine_rows_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                       NULL, g_object_unref);

      list = gnome_xkb_info_get_layouts_for_language (priv->xkb_info, lang_code);
      add_rows_to_table (chooser, info, list, INPUT_SOURCE_TYPE_XKB, id);
      add_ids_to_set (layouts_with_locale, list);
      g_list_free (list);

      if (country_code != NULL)
        {
          list = gnome_xkb_info_get_layouts_for_country (priv->xkb_info, country_code);
          add_rows_to_table (chooser, info, list, INPUT_SOURCE_TYPE_XKB, id);
          add_ids_to_set (layouts_with_locale, list);
          g_list_free (list);
        }

      g_free (lang_code);
      g_free (country_code);
    }
  g_strfreev (locale_ids);

  /* Add a "Other" locale to hold the remaining input sources */
  info = g_new0 (LocaleInfo, 1);
  info->id = g_strdup ("");
  info->name = g_strdup (C_("Keyboard Layout", "Other"));
  info->unaccented_name = g_strdup ("");
  info->untranslated_name = g_strdup ("");
  g_hash_table_replace (priv->locales, info->id, info);

  info->layout_rows_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                   NULL, g_object_unref);
  info->engine_rows_by_id = g_hash_table_new_full (g_str_hash, g_str_equal,
                                                   NULL, g_object_unref);

  list = gnome_xkb_info_get_all_layouts (priv->xkb_info);
  for (l = list; l; l = l->next)
    if (!g_hash_table_contains (layouts_with_locale, l->data))
      add_row_other (chooser, INPUT_SOURCE_TYPE_XKB, l->data);

  g_list_free (list);

  g_hash_table_destroy (layouts_with_locale);
}