gnome-control-center/panels/region/cc-input-chooser.c
Joaquim Rocha abb0592866 region: Reuse the input chooser instance
The language input chooser is being created and destroyed every time
it is opened and closed. This is noticeably slow and is hardly
necessary as the values it contains should not change.

To fix this, these changes reuse the same instance of the input chooser
(by hiding and showing it) instead of renewing it.
The input chooser is also now shown using gtk_dialog_run to avoid having
it destroyed from the default delete event.
2015-03-10 19:45:07 +01:00

1236 lines
36 KiB
C

/*
* Copyright (C) 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 <http://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <locale.h>
#include <glib/gi18n.h>
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnome-desktop/gnome-languages.h>
#include "cc-common-language.h"
#include "cc-util.h"
#include "cc-input-chooser.h"
#ifdef HAVE_IBUS
#include <ibus.h>
#include "cc-ibus-utils.h"
#endif /* HAVE_IBUS */
#define INPUT_SOURCE_TYPE_XKB "xkb"
#define INPUT_SOURCE_TYPE_IBUS "ibus"
#define MAIN_WINDOW_WIDTH_RATIO 0.60
#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;
typedef struct {
/* Not owned */
GtkWidget *add_button;
GtkWidget *filter_entry;
GtkWidget *list;
GtkWidget *scrolledwindow;
GtkAdjustment *adjustment;
GnomeXkbInfo *xkb_info;
GHashTable *ibus_engines;
/* Owned */
GtkListBoxRow *more_row;
GtkWidget *no_results;
GHashTable *locales;
GHashTable *locales_by_language;
gboolean showing_extra;
guint filter_timeout_id;
gchar **filter_words;
gboolean is_login;
} CcInputChooserPrivate;
#define GET_PRIVATE(chooser) ((CcInputChooserPrivate *) g_object_get_data (G_OBJECT (chooser), "private"))
#define WID(name) ((GtkWidget *) gtk_builder_get_object (builder, name))
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_box_pack_start (GTK_BOX (widget), arrow, FALSE, TRUE, 0);
}
label = gtk_label_new (text);
gtk_widget_set_halign (label, alignment);
set_row_widget_margins (label);
gtk_box_pack_start (GTK_BOX (widget), label, TRUE, TRUE, 0);
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_box_pack_start (GTK_BOX (widget), arrow, FALSE, TRUE, 0);
}
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_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_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label");
set_row_widget_margins (arrow);
gtk_box_pack_start (GTK_BOX (box), arrow, TRUE, TRUE, 0);
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_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_container_add (GTK_CONTAINER (row), widget);
return GTK_LIST_BOX_ROW (row);
}
static GtkWidget *
locale_header_widget_new (const gchar *text)
{
GtkWidget *widget;
widget = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_box_pack_start (GTK_BOX (widget),
gtk_separator_new (GTK_ORIENTATION_HORIZONTAL),
FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (widget),
padded_label_new (text, ROW_LABEL_POSITION_CENTER, ROW_TRAVEL_DIRECTION_NONE, TRUE),
FALSE, FALSE, 0);
gtk_box_pack_start (GTK_BOX (widget),
gtk_separator_new (GTK_ORIENTATION_HORIZONTAL),
FALSE, FALSE, 0);
gtk_widget_show_all (widget);
return widget;
}
static GtkListBoxRow *
input_source_row_new (GtkWidget *chooser,
const gchar *type,
const gchar *id)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
GtkWidget *row = NULL;
GtkWidget *widget;
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);
row = gtk_list_box_row_new ();
widget = padded_label_new (display_name,
ROW_LABEL_POSITION_START,
ROW_TRAVEL_DIRECTION_NONE,
FALSE);
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 (priv->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_container_add (GTK_CONTAINER (row), widget);
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 (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)
{
GList *list, *l;
list = gtk_container_get_children (container);
for (l = list; l; l = l->next)
gtk_container_remove (container, (GtkWidget *) l->data);
g_list_free (list);
}
static void
set_fixed_size (GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
GtkPolicyType policy;
gint width, height;
gtk_scrolled_window_get_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow), &policy, NULL);
if (policy == GTK_POLICY_AUTOMATIC)
return;
/* Don't let it automatically get wider than the main CC window nor
get taller than the initial height */
gtk_window_get_size (gtk_window_get_transient_for (GTK_WINDOW (chooser)),
&width, NULL);
gtk_window_get_size (GTK_WINDOW (chooser), NULL, &height);
gtk_widget_set_size_request (chooser, width * MAIN_WINDOW_WIDTH_RATIO, height);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolledwindow),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
}
static void
update_header (GtkListBoxRow *row,
GtkListBoxRow *before,
gpointer user_data)
{
GtkWidget *current;
current = gtk_list_box_row_get_header (row);
if (before == NULL)
{
if (current)
gtk_list_box_row_set_header (row, NULL);
return;
}
if (current == NULL || !GTK_IS_SEPARATOR (current))
{
current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
gtk_widget_show (current);
gtk_list_box_row_set_header (row, current);
}
}
static void
add_input_source_rows_for_locale (GtkWidget *chooser,
LocaleInfo *info)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
GtkWidget *row;
GHashTableIter iter;
const gchar *id;
if (info->default_input_source_row)
gtk_container_add (GTK_CONTAINER (priv->list), 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 (priv->list), 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 (priv->list), row);
}
static void
show_input_sources_for_locale (GtkWidget *chooser,
LocaleInfo *info)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
set_fixed_size (chooser);
remove_all_children (GTK_CONTAINER (priv->list));
if (!info->back_row)
{
info->back_row = g_object_ref_sink (back_row_new (info->name));
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 (priv->list), GTK_WIDGET (info->back_row));
add_input_source_rows_for_locale (chooser, info);
gtk_widget_show_all (priv->list);
gtk_adjustment_set_value (priv->adjustment,
gtk_adjustment_get_lower (priv->adjustment));
gtk_list_box_set_header_func (GTK_LIST_BOX (priv->list), update_header, NULL, NULL);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->list));
gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->list), GTK_SELECTION_SINGLE);
gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (priv->list), FALSE);
gtk_list_box_unselect_all (GTK_LIST_BOX (priv->list));
if (gtk_widget_is_visible (priv->filter_entry) &&
!gtk_widget_is_focus (priv->filter_entry))
gtk_widget_grab_focus (priv->filter_entry);
}
static gboolean
is_current_locale (const gchar *locale)
{
return g_strcmp0 (setlocale (LC_CTYPE, NULL), locale) == 0;
}
static void
show_locale_rows (GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
GHashTable *initial = NULL;
LocaleInfo *info;
GHashTableIter iter;
remove_all_children (GTK_CONTAINER (priv->list));
if (!priv->showing_extra)
initial = cc_common_language_get_initial_languages ();
g_hash_table_iter_init (&iter, priv->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));
g_object_set_data (G_OBJECT (info->locale_row), "locale-info", info);
if (!priv->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 (priv->list), GTK_WIDGET (info->locale_row));
}
gtk_container_add (GTK_CONTAINER (priv->list), GTK_WIDGET (priv->more_row));
gtk_widget_show_all (priv->list);
gtk_adjustment_set_value (priv->adjustment,
gtk_adjustment_get_lower (priv->adjustment));
gtk_list_box_set_header_func (GTK_LIST_BOX (priv->list), update_header, NULL, NULL);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->list));
gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->list), GTK_SELECTION_NONE);
gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (priv->list), TRUE);
if (gtk_widget_is_visible (priv->filter_entry) &&
!gtk_widget_is_focus (priv->filter_entry))
gtk_widget_grab_focus (priv->filter_entry);
if (!priv->showing_extra)
g_hash_table_destroy (initial);
}
static gint
list_sort (gconstpointer a,
gconstpointer b,
gpointer data)
{
GtkWidget *chooser = data;
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
LocaleInfo *ia;
LocaleInfo *ib;
const gchar *la;
const gchar *lb;
gint retval;
/* Always goes at the end */
if (a == priv->more_row)
return 1;
if (b == priv->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
list_filter (GtkListBoxRow *row,
gpointer user_data)
{
GtkDialog *chooser = user_data;
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
LocaleInfo *info;
gboolean is_extra;
const gchar *source_name;
if (row == priv->more_row)
return !priv->showing_extra;
is_extra = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "is-extra"));
if (!priv->showing_extra && is_extra)
return FALSE;
if (!priv->filter_words)
return TRUE;
info = g_object_get_data (G_OBJECT (row), "locale-info");
if (match_all (priv->filter_words, info->unaccented_name))
return TRUE;
if (match_all (priv->filter_words, info->untranslated_name))
return TRUE;
source_name = g_object_get_data (G_OBJECT (row), "unaccented-name");
if (source_name && match_all (priv->filter_words, source_name))
return TRUE;
return FALSE;
}
static void
update_header_filter (GtkListBoxRow *row,
GtkListBoxRow *before,
gpointer user_data)
{
LocaleInfo *row_info = NULL;
LocaleInfo *before_info = NULL;
GtkWidget *current;
if (row)
row_info = g_object_get_data (G_OBJECT (row), "locale-info");
if (before)
before_info = g_object_get_data (G_OBJECT (before), "locale-info");
if (!row_info && !before_info)
return;
current = gtk_list_box_row_get_header (row);
if (row_info == before_info)
{
/* Create a regular separator if we don't have one */
if (current && !GTK_IS_SEPARATOR (current))
{
gtk_list_box_row_set_header (row, NULL);
current = NULL;
}
if (current == NULL)
gtk_list_box_row_set_header (row,
gtk_separator_new (GTK_ORIENTATION_HORIZONTAL));
}
else
{
/* Create a locale heading separator if we don't have one */
if (current && GTK_IS_SEPARATOR (current))
{
gtk_list_box_row_set_header (row, NULL);
current = NULL;
}
if (current == NULL)
gtk_list_box_row_set_header (row,
locale_header_widget_new (row_info->name));
}
}
static void
show_filter_widgets (GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
LocaleInfo *info;
GHashTableIter iter;
remove_all_children (GTK_CONTAINER (priv->list));
g_hash_table_iter_init (&iter, priv->locales);
while (g_hash_table_iter_next (&iter, NULL, (gpointer *) &info))
add_input_source_rows_for_locale (chooser, info);
gtk_widget_show_all (priv->list);
gtk_adjustment_set_value (priv->adjustment,
gtk_adjustment_get_lower (priv->adjustment));
gtk_list_box_set_header_func (GTK_LIST_BOX (priv->list),
update_header_filter, NULL, NULL);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->list));
gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->list), GTK_SELECTION_SINGLE);
gtk_list_box_set_activate_on_single_click (GTK_LIST_BOX (priv->list), FALSE);
if (gtk_widget_is_visible (priv->filter_entry) &&
!gtk_widget_is_focus (priv->filter_entry))
gtk_widget_grab_focus (priv->filter_entry);
}
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 (GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
gboolean was_filtering;
gchar **previous_words;
gchar *filter_contents = NULL;
priv->filter_timeout_id = 0;
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_rows (chooser);
gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->list), NULL);
}
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));
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);
if (priv->filter_timeout_id == 0)
priv->filter_timeout_id = g_timeout_add (FILTER_TIMEOUT, (GSourceFunc) do_filter, chooser);
}
static void
show_more (GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
set_fixed_size (chooser);
gtk_widget_show (priv->filter_entry);
gtk_widget_grab_focus (priv->filter_entry);
priv->showing_extra = TRUE;
gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->list));
}
static void
row_activated (GtkListBox *box,
GtkListBoxRow *row,
GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
gpointer data;
if (!row)
return;
if (row == priv->more_row)
{
show_more (chooser);
return;
}
data = g_object_get_data (G_OBJECT (row), "back");
if (data)
{
show_locale_rows (chooser);
return;
}
data = g_object_get_data (G_OBJECT (row), "name");
if (data)
{
gtk_dialog_response (GTK_DIALOG (chooser),
gtk_dialog_get_response_for_widget (GTK_DIALOG (chooser),
priv->add_button));
return;
}
data = g_object_get_data (G_OBJECT (row), "locale-info");
if (data)
{
show_input_sources_for_locale (chooser, (LocaleInfo *) data);
return;
}
}
static void
selected_rows_changed (GtkListBox *box,
GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
GtkListBoxRow *row;
gpointer data;
row = gtk_list_box_get_selected_row (box);
gtk_widget_set_sensitive (priv->add_button, row != NULL);
if (!row)
return;
data = g_object_get_data (G_OBJECT (row), "back");
if (data)
show_locale_rows (chooser);
}
static void
add_default_row (GtkWidget *chooser,
LocaleInfo *info,
const gchar *type,
const gchar *id)
{
info->default_input_source_row = input_source_row_new (chooser, type, id);
if (info->default_input_source_row)
{
g_object_ref_sink (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 (GtkWidget *chooser,
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 (chooser, type, id);
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 (GtkWidget *chooser,
LocaleInfo *info,
const gchar *type,
const gchar *id)
{
GList tmp = { 0 };
tmp.data = (gpointer) id;
add_rows_to_table (chooser, info, &tmp, type, NULL);
}
static void
add_row_other (GtkWidget *chooser,
const gchar *type,
const gchar *id)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
LocaleInfo *info = g_hash_table_lookup (priv->locales, "");
add_row (chooser, info, type, id);
}
#ifdef HAVE_IBUS
static gboolean
maybe_set_as_default (GtkWidget *chooser,
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 (chooser, info, type, id);
return TRUE;
}
return FALSE;
}
static void
get_ibus_locale_infos (GtkWidget *chooser)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
GHashTableIter iter;
LocaleInfo *info;
const gchar *engine_id;
IBusEngineDesc *engine;
if (!priv->ibus_engines || priv->is_login)
return;
g_hash_table_iter_init (&iter, priv->ibus_engines);
while (g_hash_table_iter_next (&iter, (gpointer *) &engine_id, (gpointer *) &engine))
{
gchar *lang_code = NULL;
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)
{
gchar *locale = g_strdup_printf ("%s_%s.UTF-8", lang_code, country_code);
info = g_hash_table_lookup (priv->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 (chooser, info, type, id);
}
else
{
add_row (chooser, info, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
}
else
{
add_row_other (chooser, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
g_free (locale);
}
else if (lang_code != NULL)
{
GHashTableIter iter;
GHashTable *locales_for_language;
gchar *language;
/* 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 (priv->locales_by_language, language);
else
locales_for_language = NULL;
g_free (language);
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 (chooser, info, engine_id))
add_row (chooser, info, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
else
{
add_row_other (chooser, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
}
else
{
add_row_other (chooser, INPUT_SOURCE_TYPE_IBUS, engine_id);
}
g_free (country_code);
g_free (lang_code);
}
}
#endif /* HAVE_IBUS */
static void
add_locale_to_table (GHashTable *table,
const gchar *lang_code,
LocaleInfo *info)
{
GHashTable *set;
gchar *language;
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);
g_free (language);
}
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 (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_("Input Source", "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);
}
static void
cc_input_chooser_private_free (gpointer data)
{
CcInputChooserPrivate *priv = data;
g_object_unref (priv->more_row);
g_object_unref (priv->no_results);
g_hash_table_destroy (priv->locales);
g_hash_table_destroy (priv->locales_by_language);
g_strfreev (priv->filter_words);
if (priv->filter_timeout_id)
g_source_remove (priv->filter_timeout_id);
g_free (priv);
}
GtkWidget *
cc_input_chooser_new (GtkWindow *main_window,
gboolean is_login,
GnomeXkbInfo *xkb_info,
GHashTable *ibus_engines)
{
GtkBuilder *builder;
GtkWidget *chooser;
CcInputChooserPrivate *priv;
gint width;
GError *error = NULL;
builder = gtk_builder_new ();
if (gtk_builder_add_from_resource (builder, "/org/gnome/control-center/region/input-chooser.ui", &error) == 0)
{
g_object_unref (builder);
g_warning ("failed to load input chooser: %s", error->message);
g_error_free (error);
return NULL;
}
chooser = WID ("input-dialog");
priv = g_new0 (CcInputChooserPrivate, 1);
g_object_set_data_full (G_OBJECT (chooser), "private", priv, cc_input_chooser_private_free);
priv->is_login = is_login;
priv->xkb_info = xkb_info;
priv->ibus_engines = ibus_engines;
priv->add_button = WID ("add-button");
priv->filter_entry = WID ("filter-entry");
priv->list = WID ("list");
priv->scrolledwindow = WID ("scrolledwindow");
priv->adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolledwindow));
priv->more_row = g_object_ref_sink (more_row_new ());
priv->no_results = g_object_ref_sink (no_results_widget_new ());
gtk_widget_show_all (priv->no_results);
gtk_list_box_set_adjustment (GTK_LIST_BOX (priv->list), priv->adjustment);
gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->list), list_filter, chooser, NULL);
gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->list), (GtkListBoxSortFunc)list_sort, chooser, NULL);
g_signal_connect (priv->list, "row-activated", G_CALLBACK (row_activated), chooser);
g_signal_connect (priv->list, "selected-rows-changed", G_CALLBACK (selected_rows_changed), chooser);
g_signal_connect_swapped (priv->filter_entry, "search-changed", G_CALLBACK (filter_changed), chooser);
if (priv->is_login)
gtk_widget_show (WID ("login-label"));
get_locale_infos (chooser);
#ifdef HAVE_IBUS
get_ibus_locale_infos (chooser);
#endif /* HAVE_IBUS */
show_locale_rows (chooser);
/* Try to come up with a sensible width */
gtk_window_get_size (main_window, &width, NULL);
gtk_widget_set_size_request (chooser, width * MAIN_WINDOW_WIDTH_RATIO, -1);
gtk_window_set_resizable (GTK_WINDOW (chooser), TRUE);
gtk_window_set_transient_for (GTK_WINDOW (chooser), main_window);
g_object_unref (builder);
return chooser;
}
void
cc_input_chooser_set_ibus_engines (GtkWidget *chooser,
GHashTable *ibus_engines)
{
#ifdef HAVE_IBUS
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
/* 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 (priv->ibus_engines == NULL);
priv->ibus_engines = ibus_engines;
get_ibus_locale_infos (chooser);
show_locale_rows (chooser);
#endif /* HAVE_IBUS */
}
gboolean
cc_input_chooser_get_selected (GtkWidget *chooser,
gchar **type,
gchar **id,
gchar **name)
{
CcInputChooserPrivate *priv = GET_PRIVATE (chooser);
GtkListBoxRow *selected;
const gchar *t, *i, *n;
selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (priv->list));
if (!selected)
return FALSE;
t = g_object_get_data (G_OBJECT (selected), "type");
i = g_object_get_data (G_OBJECT (selected), "id");
n = g_object_get_data (G_OBJECT (selected), "name");
if (!t || !i || !n)
return FALSE;
*type = g_strdup (t);
*id = g_strdup (i);
*name = g_strdup (n);
return TRUE;
}
void
cc_input_chooser_reset (GtkWidget *chooser)
{
show_locale_rows (chooser);
}