/*
* Copyright © 2013 Red Hat, Inc.
*
* This program 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.
*
* This program 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 this program; if not, see .
*/
#include
#include
#include
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include
#include "list-box-helper.h"
#include "cc-common-language.h"
#include "cc-util.h"
#include "cc-input-chooser.h"
#include "cc-input-source-ibus.h"
#include "cc-input-source-xkb.h"
#ifdef HAVE_IBUS
#include
#include "cc-ibus-utils.h"
#endif /* HAVE_IBUS */
#define INPUT_SOURCE_TYPE_XKB "xkb"
#define INPUT_SOURCE_TYPE_IBUS "ibus"
#define FILTER_TIMEOUT 150 /* ms */
typedef enum
{
ROW_TRAVEL_DIRECTION_NONE,
ROW_TRAVEL_DIRECTION_FORWARD,
ROW_TRAVEL_DIRECTION_BACKWARD
} RowTravelDirection;
typedef enum
{
ROW_LABEL_POSITION_START,
ROW_LABEL_POSITION_CENTER,
ROW_LABEL_POSITION_END
} RowLabelPosition;
struct _CcInputChooser
{
GtkDialog parent_instance;
GtkButton *add_button;
GtkSearchEntry *filter_entry;
GtkListBox *input_sources_listbox;
GtkLabel *login_label;
GtkListBoxRow *more_row;
GtkWidget *no_results;
GtkAdjustment *scroll_adjustment;
GnomeXkbInfo *xkb_info;
GHashTable *ibus_engines;
GHashTable *locales;
GHashTable *locales_by_language;
gboolean showing_extra;
guint filter_timeout_id;
gchar **filter_words;
gboolean is_login;
};
G_DEFINE_TYPE (CcInputChooser, cc_input_chooser, GTK_TYPE_DIALOG)
typedef struct
{
gchar *id;
gchar *name;
gchar *unaccented_name;
gchar *untranslated_name;
GtkListBoxRow *default_input_source_row;
GtkListBoxRow *locale_row;
GtkListBoxRow *back_row;
GHashTable *layout_rows_by_id;
GHashTable *engine_rows_by_id;
} LocaleInfo;
static void
locale_info_free (gpointer data)
{
LocaleInfo *info = data;
g_free (info->id);
g_free (info->name);
g_free (info->unaccented_name);
g_free (info->untranslated_name);
g_clear_object (&info->default_input_source_row);
g_clear_object (&info->locale_row);
g_clear_object (&info->back_row);
g_hash_table_destroy (info->layout_rows_by_id);
g_hash_table_destroy (info->engine_rows_by_id);
g_free (info);
}
static void
set_row_widget_margins (GtkWidget *widget)
{
gtk_widget_set_margin_start (widget, 20);
gtk_widget_set_margin_end (widget, 20);
gtk_widget_set_margin_top (widget, 6);
gtk_widget_set_margin_bottom (widget, 6);
}
static GtkWidget *
padded_label_new (const gchar *text,
RowLabelPosition position,
RowTravelDirection direction,
gboolean dim_label)
{
GtkWidget *widget;
GtkWidget *label;
GtkWidget *arrow;
GtkAlign alignment;
if (position == ROW_LABEL_POSITION_START)
alignment = GTK_ALIGN_START;
else if (position == ROW_LABEL_POSITION_CENTER)
alignment = GTK_ALIGN_CENTER;
else
alignment = GTK_ALIGN_END;
widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
if (direction == ROW_TRAVEL_DIRECTION_BACKWARD)
{
arrow = gtk_image_new_from_icon_name ("go-previous-symbolic", GTK_ICON_SIZE_MENU);
gtk_widget_show (arrow);
gtk_container_add (GTK_CONTAINER (widget), arrow);
}
label = gtk_label_new (text);
gtk_widget_show (label);
gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE);
gtk_widget_set_hexpand (label, TRUE);
gtk_widget_set_halign (label, alignment);
set_row_widget_margins (label);
gtk_container_add (GTK_CONTAINER (widget), label);
if (dim_label)
gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
if (direction == ROW_TRAVEL_DIRECTION_FORWARD)
{
arrow = gtk_image_new_from_icon_name ("go-next-symbolic", GTK_ICON_SIZE_MENU);
gtk_widget_show (arrow);
gtk_container_add (GTK_CONTAINER (widget), arrow);
}
return widget;
}
static GtkListBoxRow *
more_row_new (void)
{
GtkWidget *row;
GtkWidget *box;
GtkWidget *arrow;
row = gtk_list_box_row_new ();
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
gtk_widget_show (box);
gtk_container_add (GTK_CONTAINER (row), box);
gtk_widget_set_tooltip_text (row, _("More…"));
arrow = gtk_image_new_from_icon_name ("view-more-symbolic", GTK_ICON_SIZE_MENU);
gtk_widget_show (arrow);
gtk_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label");
gtk_widget_set_hexpand (arrow, TRUE);
set_row_widget_margins (arrow);
gtk_container_add (GTK_CONTAINER (box), arrow);
return GTK_LIST_BOX_ROW (row);
}
static GtkWidget *
no_results_widget_new (void)
{
return padded_label_new (_("No input sources found"), ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_NONE, TRUE);
}
static GtkListBoxRow *
back_row_new (const gchar *text)
{
GtkWidget *row;
GtkWidget *widget;
row = gtk_list_box_row_new ();
widget = padded_label_new (text, ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_BACKWARD, TRUE);
gtk_widget_show (widget);
gtk_container_add (GTK_CONTAINER (row), widget);
return GTK_LIST_BOX_ROW (row);
}
static GtkListBoxRow *
locale_row_new (const gchar *text)
{
GtkWidget *row;
GtkWidget *widget;
row = gtk_list_box_row_new ();
widget = padded_label_new (text, ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_NONE, FALSE);
gtk_widget_show (widget);
gtk_container_add (GTK_CONTAINER (row), widget);
return GTK_LIST_BOX_ROW (row);
}
static GtkListBoxRow *
input_source_row_new (CcInputChooser *self,
const gchar *type,
const gchar *id)
{
GtkWidget *row = NULL;
GtkWidget *widget;
if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
{
const gchar *display_name;
gnome_xkb_info_get_layout_info (self->xkb_info, id, &display_name, NULL, NULL, NULL);
row = gtk_list_box_row_new ();
widget = padded_label_new (display_name,
ROW_LABEL_POSITION_START,
ROW_TRAVEL_DIRECTION_NONE,
FALSE);
gtk_widget_show (widget);
gtk_container_add (GTK_CONTAINER (row), widget);
g_object_set_data (G_OBJECT (row), "name", (gpointer) display_name);
g_object_set_data_full (G_OBJECT (row), "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 (self->ibus_engines, id));
row = gtk_list_box_row_new ();
widget = padded_label_new (display_name,
ROW_LABEL_POSITION_START,
ROW_TRAVEL_DIRECTION_NONE,
FALSE);
gtk_widget_show (widget);
gtk_container_add (GTK_CONTAINER (row), widget);
image = gtk_image_new_from_icon_name ("system-run-symbolic", GTK_ICON_SIZE_MENU);
gtk_widget_show (image);
set_row_widget_margins (image);
gtk_style_context_add_class (gtk_widget_get_style_context (image), "dim-label");
gtk_container_add (GTK_CONTAINER (widget), image);
g_object_set_data_full (G_OBJECT (row), "name", display_name, g_free);
g_object_set_data_full (G_OBJECT (row), "unaccented-name",
cc_util_normalize_casefold_and_unaccent (display_name), g_free);
#else
widget = NULL;
#endif /* HAVE_IBUS */
}
if (row)
{
g_object_set_data (G_OBJECT (row), "type", (gpointer) type);
g_object_set_data (G_OBJECT (row), "id", (gpointer) id);
return GTK_LIST_BOX_ROW (row);
}
return NULL;
}
static void
remove_all_children (GtkContainer *container)
{
g_autoptr(GList) list = NULL;
GList *l;
list = gtk_container_get_children (container);
for (l = list; l; l = l->next)
gtk_container_remove (container, (GtkWidget *) l->data);
}
static void
add_input_source_rows_for_locale (CcInputChooser *self,
LocaleInfo *info)
{
GtkWidget *row;
GHashTableIter iter;
const gchar *id;
if (info->default_input_source_row)
gtk_container_add (GTK_CONTAINER (self->input_sources_listbox), GTK_WIDGET (info->default_input_source_row));
g_hash_table_iter_init (&iter, info->layout_rows_by_id);
while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &row))
gtk_container_add (GTK_CONTAINER (self->input_sources_listbox), row);
g_hash_table_iter_init (&iter, info->engine_rows_by_id);
while (g_hash_table_iter_next (&iter, (gpointer *) &id, (gpointer *) &row))
gtk_container_add (GTK_CONTAINER (self->input_sources_listbox), row);
}
static void
show_input_sources_for_locale (CcInputChooser *self,
LocaleInfo *info)
{
remove_all_children (GTK_CONTAINER (self->input_sources_listbox));
if (!info->back_row)
{
info->back_row = g_object_ref_sink (back_row_new (info->name));
gtk_widget_show (GTK_WIDGET (info->back_row));
g_object_set_data (G_OBJECT (info->back_row), "back", GINT_TO_POINTER (TRUE));
g_object_set_data (G_OBJECT (info->back_row), "locale-info", info);
}
gtk_container_add (GTK_CONTAINER (self->input_sources_listbox), GTK_WIDGET (info->back_row));
add_input_source_rows_for_locale (self, info);
gtk_adjustment_set_value (self->scroll_adjustment,
gtk_adjustment_get_lower (self->scroll_adjustment));
gtk_list_box_set_header_func (self->input_sources_listbox, cc_list_box_update_header_func, NULL, NULL);
gtk_list_box_invalidate_filter (self->input_sources_listbox);
gtk_list_box_set_selection_mode (self->input_sources_listbox, GTK_SELECTION_SINGLE);
gtk_list_box_set_activate_on_single_click (self->input_sources_listbox, FALSE);
gtk_list_box_unselect_all (self->input_sources_listbox);
if (gtk_widget_is_visible (GTK_WIDGET (self->filter_entry)) &&
!gtk_widget_is_focus (GTK_WIDGET (self->filter_entry)))
gtk_widget_grab_focus (GTK_WIDGET (self->filter_entry));
}
static gboolean
is_current_locale (const gchar *locale)
{
return g_strcmp0 (setlocale (LC_CTYPE, NULL), locale) == 0;
}
static void
show_locale_rows (CcInputChooser *self)
{
g_autoptr(GHashTable) initial = NULL;
LocaleInfo *info;
GHashTableIter iter;
remove_all_children (GTK_CONTAINER (self->input_sources_listbox));
if (!self->showing_extra)
initial = cc_common_language_get_initial_languages ();
g_hash_table_iter_init (&iter, self->locales);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
{
if (!info->default_input_source_row &&
!g_hash_table_size (info->layout_rows_by_id) &&
!g_hash_table_size (info->engine_rows_by_id))
continue;
if (!info->locale_row)
{
info->locale_row = g_object_ref_sink (locale_row_new (info->name));
gtk_widget_show (GTK_WIDGET (info->locale_row));
g_object_set_data (G_OBJECT (info->locale_row), "locale-info", info);
if (!self->showing_extra &&
!g_hash_table_contains (initial, info->id) &&
!is_current_locale (info->id))
g_object_set_data (G_OBJECT (info->locale_row), "is-extra", GINT_TO_POINTER (TRUE));
}
gtk_container_add (GTK_CONTAINER (self->input_sources_listbox), GTK_WIDGET (info->locale_row));
}
gtk_container_add (GTK_CONTAINER (self->input_sources_listbox), GTK_WIDGET (self->more_row));
gtk_adjustment_set_value (self->scroll_adjustment,
gtk_adjustment_get_lower (self->scroll_adjustment));
gtk_list_box_set_header_func (self->input_sources_listbox, cc_list_box_update_header_func, NULL, NULL);
gtk_list_box_invalidate_filter (self->input_sources_listbox);
gtk_list_box_set_selection_mode (self->input_sources_listbox, GTK_SELECTION_NONE);
gtk_list_box_set_activate_on_single_click (self->input_sources_listbox, TRUE);
if (gtk_widget_is_visible (GTK_WIDGET (self->filter_entry)) &&
!gtk_widget_is_focus (GTK_WIDGET (self->filter_entry)))
gtk_widget_grab_focus (GTK_WIDGET (self->filter_entry));
}
static gint
list_sort (GtkListBoxRow *a,
GtkListBoxRow *b,
gpointer data)
{
CcInputChooser *self = data;
LocaleInfo *ia;
LocaleInfo *ib;
const gchar *la;
const gchar *lb;
gint retval;
/* Always goes at the end */
if (a == self->more_row)
return 1;
if (b == self->more_row)
return -1;
ia = g_object_get_data (G_OBJECT (a), "locale-info");
ib = g_object_get_data (G_OBJECT (b), "locale-info");
/* The "Other" locale always goes at the end */
if (!ia->id[0] && ib->id[0])
return 1;
else if (ia->id[0] && !ib->id[0])
return -1;
retval = g_strcmp0 (ia->name, ib->name);
if (retval)
return retval;
la = g_object_get_data (G_OBJECT (a), "name");
lb = g_object_get_data (G_OBJECT (b), "name");
/* Only input sources have a "name" property and they should always
go after their respective heading */
if (la && !lb)
return 1;
else if (!la && lb)
return -1;
else if (!la && !lb)
return 0; /* Shouldn't happen */
/* The default input source always goes first in its group */
if (g_object_get_data (G_OBJECT (a), "default"))
return -1;
if (g_object_get_data (G_OBJECT (b), "default"))
return 1;
return g_strcmp0 (la, lb);
}
static gboolean
match_all (gchar **words,
const gchar *str)
{
gchar **w;
for (w = words; *w; ++w)
if (!strstr (str, *w))
return FALSE;
return TRUE;
}
static gboolean
match_source_in_table (gchar **words,
GHashTable *table)
{
GHashTableIter iter;
gpointer row;
const gchar *source_name;
g_hash_table_iter_init (&iter, table);
while (g_hash_table_iter_next (&iter, NULL, &row))
{
source_name = g_object_get_data (G_OBJECT (row), "unaccented-name");
if (source_name && match_all (words, source_name))
return TRUE;
}
return FALSE;
}
static gboolean
list_filter (GtkListBoxRow *row,
gpointer user_data)
{
CcInputChooser *self = user_data;
LocaleInfo *info;
gboolean is_extra;
const gchar *source_name;
if (row == self->more_row)
return !self->showing_extra;
is_extra = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-extra"));
if (!self->showing_extra && is_extra)
return FALSE;
if (!self->filter_words)
return TRUE;
info = g_object_get_data (G_OBJECT (row), "locale-info");
if (row == info->back_row)
return TRUE;
if (match_all (self->filter_words, info->unaccented_name))
return TRUE;
if (match_all (self->filter_words, info->untranslated_name))
return TRUE;
source_name = g_object_get_data (G_OBJECT (row), "unaccented-name");
if (source_name)
{
if (match_all (self->filter_words, source_name))
return TRUE;
}
else
{
if (match_source_in_table (self->filter_words, info->layout_rows_by_id))
return TRUE;
if (match_source_in_table (self->filter_words, info->engine_rows_by_id))
return TRUE;
}
return FALSE;
}
static gboolean
strvs_differ (gchar **av,
gchar **bv)
{
gchar **a, **b;
for (a = av, b = bv; *a && *b; ++a, ++b)
if (!g_str_equal (*a, *b))
return TRUE;
if (*a == NULL && *b == NULL)
return FALSE;
return TRUE;
}
static gboolean
do_filter (CcInputChooser *self)
{
g_auto(GStrv) previous_words = NULL;
g_autofree gchar *filter_contents = NULL;
self->filter_timeout_id = 0;
filter_contents =
cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (self->filter_entry)));
previous_words = self->filter_words;
self->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
if (!self->filter_words[0])
{
gtk_list_box_invalidate_filter (self->input_sources_listbox);
gtk_list_box_set_placeholder (self->input_sources_listbox, NULL);
}
else if (previous_words == NULL || strvs_differ (self->filter_words, previous_words))
{
gtk_list_box_invalidate_filter (self->input_sources_listbox);
gtk_list_box_set_placeholder (self->input_sources_listbox, self->no_results);
}
return G_SOURCE_REMOVE;
}
static void
on_filter_entry_search_changed_cb (CcInputChooser *self)
{
if (self->filter_timeout_id == 0)
self->filter_timeout_id = g_timeout_add (FILTER_TIMEOUT, (GSourceFunc) do_filter, self);
}
static void
show_more (CcInputChooser *self)
{
gtk_widget_show (GTK_WIDGET (self->filter_entry));
gtk_widget_grab_focus (GTK_WIDGET (self->filter_entry));
self->showing_extra = TRUE;
gtk_list_box_invalidate_filter (self->input_sources_listbox);
}
static void
on_input_sources_listbox_row_activated_cb (CcInputChooser *self, GtkListBoxRow *row)
{
gpointer data;
if (!row)
return;
if (row == self->more_row)
{
show_more (self);
return;
}
data = g_object_get_data (G_OBJECT (row), "back");
if (data)
{
show_locale_rows (self);
return;
}
data = g_object_get_data (G_OBJECT (row), "name");
if (data)
{
if (gtk_widget_is_sensitive (GTK_WIDGET (self->add_button)))
gtk_dialog_response (GTK_DIALOG (self),
gtk_dialog_get_response_for_widget (GTK_DIALOG (self),
GTK_WIDGET (self->add_button)));
return;
}
data = g_object_get_data (G_OBJECT (row), "locale-info");
if (data)
{
show_input_sources_for_locale (self, (LocaleInfo *) data);
return;
}
}
static void
on_input_sources_listbox_selected_rows_changed_cb (CcInputChooser *self)
{
gboolean sensitive = TRUE;
GtkListBoxRow *row;
row = gtk_list_box_get_selected_row (self->input_sources_listbox);
if (!row || g_object_get_data (G_OBJECT (row), "back"))
sensitive = FALSE;
gtk_widget_set_sensitive (GTK_WIDGET (self->add_button), sensitive);
}
static gboolean
on_input_sources_listbox_button_release_event_cb (CcInputChooser *self, GdkEvent *event)
{
gdouble x, y;
GtkListBoxRow *row;
gdk_event_get_coords (event, &x, &y);
row = gtk_list_box_get_row_at_y (self->input_sources_listbox, y);
if (row && g_object_get_data (G_OBJECT (row), "back"))
{
g_signal_emit_by_name (row, "activate", NULL);
return TRUE;
}
return FALSE;
}
static void
add_default_row (CcInputChooser *self,
LocaleInfo *info,
const gchar *type,
const gchar *id)
{
info->default_input_source_row = input_source_row_new (self, type, id);
if (info->default_input_source_row)
{
gtk_widget_show (GTK_WIDGET (info->default_input_source_row));
g_object_ref_sink (GTK_WIDGET (info->default_input_source_row));
g_object_set_data (G_OBJECT (info->default_input_source_row), "default", GINT_TO_POINTER (TRUE));
g_object_set_data (G_OBJECT (info->default_input_source_row), "locale-info", info);
}
}
static void
add_rows_to_table (CcInputChooser *self,
LocaleInfo *info,
GList *list,
const gchar *type,
const gchar *default_id)
{
GHashTable *table;
GtkListBoxRow *row;
const gchar *id;
if (g_str_equal (type, INPUT_SOURCE_TYPE_XKB))
table = info->layout_rows_by_id;
else if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS))
table = info->engine_rows_by_id;
else
return;
while (list)
{
id = (const gchar *) list->data;
/* The widget for the default input source lives elsewhere */
if (g_strcmp0 (id, default_id))
{
row = input_source_row_new (self, type, id);
gtk_widget_show (GTK_WIDGET (row));
if (row)
{
g_object_set_data (G_OBJECT (row), "locale-info", info);
g_hash_table_replace (table, (gpointer) id, g_object_ref_sink (row));
}
}
list = list->next;
}
}
static void
add_row (CcInputChooser *self,
LocaleInfo *info,
const gchar *type,
const gchar *id)
{
GList tmp = { 0 };
tmp.data = (gpointer) id;
add_rows_to_table (self, info, &tmp, type, NULL);
}
static void
add_row_other (CcInputChooser *self,
const gchar *type,
const gchar *id)
{
LocaleInfo *info = g_hash_table_lookup (self->locales, "");
add_row (self, info, type, id);
}
#ifdef HAVE_IBUS
static gboolean
maybe_set_as_default (CcInputChooser *self,
LocaleInfo *info,
const gchar *engine_id)
{
const gchar *type, *id;
if (!gnome_get_input_source_from_locale (info->id, &type, &id))
return FALSE;
if (g_str_equal (type, INPUT_SOURCE_TYPE_IBUS) &&
g_str_equal (id, engine_id) &&
info->default_input_source_row == NULL)
{
add_default_row (self, info, type, id);
return TRUE;
}
return FALSE;
}
static void
get_ibus_locale_infos (CcInputChooser *self)
{
GHashTableIter iter;
LocaleInfo *info;
const gchar *engine_id;
IBusEngineDesc *engine;
if (!self->ibus_engines || self->is_login)
return;
g_hash_table_iter_init (&iter, self->ibus_engines);
while (g_hash_table_iter_next (&iter, (gpointer *) &engine_id, (gpointer *) &engine))
{
g_autofree gchar *lang_code = NULL;
g_autofree gchar *country_code = NULL;
const gchar *ibus_locale = ibus_engine_desc_get_language (engine);
if (gnome_parse_locale (ibus_locale, &lang_code, &country_code, NULL, NULL) &&
lang_code != NULL &&
country_code != NULL)
{
g_autofree gchar *locale = g_strdup_printf ("%s_%s.UTF-8", lang_code, country_code);
info = g_hash_table_lookup (self->locales, locale);
if (info)
{
const gchar *type, *id;
if (gnome_get_input_source_from_locale (locale, &type, &id) &&
g_str_equal (type, INPUT_SOURCE_TYPE_IBUS) &&
g_str_equal (id, engine_id))
{
add_default_row (self, info, type, id);
}
else
{
add_row (self, info, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
}
else
{
add_row_other (self, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
}
else if (lang_code != NULL)
{
GHashTableIter iter;
GHashTable *locales_for_language;
g_autofree gchar *language = NULL;
/* Most IBus engines only specify the language so we try to
add them to all locales for that language. */
language = gnome_get_language_from_code (lang_code, NULL);
if (language)
locales_for_language = g_hash_table_lookup (self->locales_by_language, language);
else
locales_for_language = NULL;
if (locales_for_language)
{
g_hash_table_iter_init (&iter, locales_for_language);
while (g_hash_table_iter_next (&iter, (gpointer *) &info, NULL))
if (!maybe_set_as_default (self, info, engine_id))
add_row (self, info, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
else
{
add_row_other (self, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
}
else
{
add_row_other (self, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
}
}
#endif /* HAVE_IBUS */
static void
add_locale_to_table (GHashTable *table,
const gchar *lang_code,
LocaleInfo *info)
{
GHashTable *set;
g_autofree gchar *language = NULL;
language = gnome_get_language_from_code (lang_code, NULL);
set = g_hash_table_lookup (table, language);
if (!set)
{
set = g_hash_table_new (NULL, NULL);
g_hash_table_replace (table, g_strdup (language), set);
}
g_hash_table_add (set, info);
}
static void
add_ids_to_set (GHashTable *set,
GList *list)
{
while (list)
{
g_hash_table_add (set, list->data);
list = list->next;
}
}
static void
get_locale_infos (CcInputChooser *self)
{
g_autoptr(GHashTable) layouts_with_locale = NULL;
LocaleInfo *info;
g_auto(GStrv) locale_ids = NULL;
gchar **locale;
g_autoptr(GList) all_layouts = NULL;
GList *l;
self->locales = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, locale_info_free);
self->locales_by_language = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) g_hash_table_unref);
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)
{
g_autofree gchar *lang_code = NULL;
g_autofree gchar *country_code = NULL;
g_autofree gchar *simple_locale = NULL;
g_autofree gchar *tmp = NULL;
const gchar *type = NULL;
const gchar *id = NULL;
g_autoptr(GList) language_layouts = 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 (self->locales, simple_locale))
continue;
info = g_new0 (LocaleInfo, 1);
info->id = g_strdup (simple_locale);
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_hash_table_replace (self->locales, g_strdup (simple_locale), info);
add_locale_to_table (self->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 (self, 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);
language_layouts = gnome_xkb_info_get_layouts_for_language (self->xkb_info, lang_code);
add_rows_to_table (self, info, language_layouts, INPUT_SOURCE_TYPE_XKB, id);
add_ids_to_set (layouts_with_locale, language_layouts);
if (country_code != NULL)
{
g_autoptr(GList) country_layouts = gnome_xkb_info_get_layouts_for_country (self->xkb_info, country_code);
add_rows_to_table (self, info, country_layouts, INPUT_SOURCE_TYPE_XKB, id);
add_ids_to_set (layouts_with_locale, country_layouts);
}
}
/* Add a "Other" locale to hold the remaining input sources */
info = g_new0 (LocaleInfo, 1);
info->id = g_strdup ("");
info->name = g_strdup (C_("Input Source", "Other"));
info->unaccented_name = g_strdup ("");
info->untranslated_name = g_strdup ("");
g_hash_table_replace (self->locales, g_strdup (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);
all_layouts = gnome_xkb_info_get_all_layouts (self->xkb_info);
for (l = all_layouts; l; l = l->next)
if (!g_hash_table_contains (layouts_with_locale, l->data))
add_row_other (self, INPUT_SOURCE_TYPE_XKB, l->data);
}
static gboolean
on_filter_entry_key_release_event_cb (CcInputChooser *self, GdkEventKey *event)
{
if (event->keyval == GDK_KEY_Escape) {
self->showing_extra = FALSE;
gtk_entry_set_text (GTK_ENTRY (self->filter_entry), "");
gtk_widget_hide (GTK_WIDGET (self->filter_entry));
g_clear_pointer (&self->filter_words, g_strfreev);
show_locale_rows (self);
}
return FALSE;
}
static void
cc_input_chooser_dispose (GObject *object)
{
CcInputChooser *self = CC_INPUT_CHOOSER (object);
g_clear_object (&self->more_row);
g_clear_object (&self->no_results);
g_clear_object (&self->xkb_info);
g_clear_pointer (&self->ibus_engines, g_hash_table_unref);
g_clear_pointer (&self->locales, g_hash_table_unref);
g_clear_pointer (&self->locales_by_language, g_hash_table_unref);
g_clear_pointer (&self->filter_words, g_strfreev);
g_clear_handle_id (&self->filter_timeout_id, g_source_remove);
G_OBJECT_CLASS (cc_input_chooser_parent_class)->dispose (object);
}
void
cc_input_chooser_class_init (CcInputChooserClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = cc_input_chooser_dispose;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/region/cc-input-chooser.ui");
gtk_widget_class_bind_template_child (widget_class, CcInputChooser, add_button);
gtk_widget_class_bind_template_child (widget_class, CcInputChooser, filter_entry);
gtk_widget_class_bind_template_child (widget_class, CcInputChooser, input_sources_listbox);
gtk_widget_class_bind_template_child (widget_class, CcInputChooser, login_label);
gtk_widget_class_bind_template_child (widget_class, CcInputChooser, scroll_adjustment);
gtk_widget_class_bind_template_callback (widget_class, on_input_sources_listbox_row_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, on_input_sources_listbox_selected_rows_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_input_sources_listbox_button_release_event_cb);
gtk_widget_class_bind_template_callback (widget_class, on_filter_entry_search_changed_cb);
gtk_widget_class_bind_template_callback (widget_class, on_filter_entry_key_release_event_cb);
}
void
cc_input_chooser_init (CcInputChooser *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
CcInputChooser *
cc_input_chooser_new (gboolean is_login,
GnomeXkbInfo *xkb_info,
GHashTable *ibus_engines)
{
CcInputChooser *self;
g_autoptr(GError) error = NULL;
self = g_object_new (CC_TYPE_INPUT_CHOOSER,
"use-header-bar", 1,
NULL);
self->is_login = is_login;
self->xkb_info = g_object_ref (xkb_info);
if (ibus_engines)
self->ibus_engines = g_hash_table_ref (ibus_engines);
self->more_row = g_object_ref_sink (more_row_new ());
gtk_widget_show (GTK_WIDGET (self->more_row));
self->no_results = g_object_ref_sink (no_results_widget_new ());
gtk_widget_show (self->no_results);
gtk_list_box_set_filter_func (self->input_sources_listbox, list_filter, self, NULL);
gtk_list_box_set_sort_func (self->input_sources_listbox, list_sort, self, NULL);
if (self->is_login)
gtk_widget_show (GTK_WIDGET (self->login_label));
get_locale_infos (self);
#ifdef HAVE_IBUS
get_ibus_locale_infos (self);
#endif /* HAVE_IBUS */
show_locale_rows (self);
return self;
}
void
cc_input_chooser_set_ibus_engines (CcInputChooser *self,
GHashTable *ibus_engines)
{
g_return_if_fail (CC_IS_INPUT_CHOOSER (self));
#ifdef HAVE_IBUS
/* This should only be called once when IBus shows up in case it
wasn't up yet when the user opened the input chooser dialog. */
g_return_if_fail (self->ibus_engines == NULL);
self->ibus_engines = ibus_engines;
get_ibus_locale_infos (self);
show_locale_rows (self);
#endif /* HAVE_IBUS */
}
CcInputSource *
cc_input_chooser_get_source (CcInputChooser *self)
{
GtkListBoxRow *selected;
const gchar *t, *i;
g_return_val_if_fail (CC_IS_INPUT_CHOOSER (self), FALSE);
selected = gtk_list_box_get_selected_row (self->input_sources_listbox);
if (!selected)
return NULL;
t = g_object_get_data (G_OBJECT (selected), "type");
i = g_object_get_data (G_OBJECT (selected), "id");
if (!t || !i)
return FALSE;
if (g_strcmp0 (t, "xkb") == 0)
return CC_INPUT_SOURCE (cc_input_source_xkb_new_from_id (self->xkb_info, i));
else if (g_strcmp0 (t, "ibus") == 0)
return CC_INPUT_SOURCE (cc_input_source_ibus_new (i));
else
return NULL;
}