/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2013 Red Hat * * 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 . * * Written by: * Jasper St. Pierre * Matthias Clasen */ #include "config.h" #include "cc-language-chooser.h" #include #include #include #include #define GNOME_DESKTOP_USE_UNSTABLE_API #include #include "cc-common-language.h" #include "cc-util.h" #include struct _CcLanguageChooserPrivate { GtkWidget *filter_entry; GtkWidget *language_list; GtkWidget *no_results; GtkWidget *more_item; gboolean showing_extra; gchar *language; }; typedef struct _CcLanguageChooserPrivate CcLanguageChooserPrivate; G_DEFINE_TYPE_WITH_PRIVATE (CcLanguageChooser, cc_language_chooser, GTK_TYPE_BOX); enum { PROP_0, PROP_LANGUAGE, PROP_SHOWING_EXTRA, PROP_LAST, }; static GParamSpec *obj_props[PROP_LAST]; enum { CONFIRM, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; typedef struct { GtkWidget *box; GtkWidget *checkmark; gchar *locale_id; gchar *locale_name; gchar *locale_current_name; gchar *locale_untranslated_name; gchar *sort_key; gboolean is_extra; } LanguageWidget; static LanguageWidget * get_language_widget (GtkWidget *widget) { return g_object_get_data (G_OBJECT (widget), "language-widget"); } static GtkWidget * padded_label_new (char *text) { GtkWidget *widget; widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); gtk_widget_set_margin_top (widget, 12); gtk_widget_set_margin_bottom (widget, 12); gtk_box_append (GTK_BOX (widget), gtk_label_new (text)); return widget; } static void language_widget_free (gpointer data) { LanguageWidget *widget = data; /* This is called when the box is destroyed, * so don't bother destroying the widget and * children again. */ g_free (widget->locale_id); g_free (widget->locale_name); g_free (widget->locale_current_name); g_free (widget->locale_untranslated_name); g_free (widget->sort_key); g_free (widget); } static GtkWidget * language_widget_new (const char *locale_id, gboolean is_extra) { GtkWidget *label; gchar *locale_name, *locale_current_name, *locale_untranslated_name; gchar *language = NULL; gchar *language_name; gchar *country = NULL; gchar *country_name = NULL; LanguageWidget *widget = g_new0 (LanguageWidget, 1); if (!gnome_parse_locale (locale_id, &language, &country, NULL, NULL)) return NULL; language_name = gnome_get_language_from_code (language, locale_id); if (language_name == NULL) language_name = gnome_get_language_from_code (language, NULL); if (country) { country_name = gnome_get_country_from_code (country, locale_id); if (country_name == NULL) country_name = gnome_get_country_from_code (country, NULL); } locale_name = gnome_get_language_from_locale (locale_id, locale_id); locale_current_name = gnome_get_language_from_locale (locale_id, NULL); locale_untranslated_name = gnome_get_language_from_locale (locale_id, "C"); widget->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_widget_set_margin_top (widget->box, 12); gtk_widget_set_margin_bottom (widget->box, 12); gtk_widget_set_margin_start (widget->box, 12); gtk_widget_set_margin_end (widget->box, 12); gtk_widget_set_halign (widget->box, GTK_ALIGN_FILL); label = gtk_label_new (language_name); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_label_set_max_width_chars (GTK_LABEL (label), 30); gtk_label_set_xalign (GTK_LABEL (label), 0); gtk_box_append (GTK_BOX (widget->box), label); widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic"); gtk_box_append (GTK_BOX (widget->box), widget->checkmark); if (country_name) { label = gtk_label_new (country_name); gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); gtk_label_set_max_width_chars (GTK_LABEL (label), 30); gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label"); gtk_label_set_xalign (GTK_LABEL (label), 0); gtk_widget_set_hexpand (label, TRUE); gtk_widget_set_halign (label, GTK_ALIGN_END); gtk_box_append (GTK_BOX (widget->box), label); } widget->locale_id = g_strdup (locale_id); widget->locale_name = locale_name; widget->locale_current_name = locale_current_name; widget->locale_untranslated_name = locale_untranslated_name; widget->is_extra = is_extra; widget->sort_key = cc_util_normalize_casefold_and_unaccent (locale_name); g_object_set_data_full (G_OBJECT (widget->box), "language-widget", widget, language_widget_free); g_free (language); g_free (language_name); g_free (country); g_free (country_name); return widget->box; } static void sync_all_checkmarks (CcLanguageChooser *chooser) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); GtkWidget *row; row = gtk_widget_get_first_child (priv->language_list); while (row) { LanguageWidget *widget; GtkWidget *child; gboolean should_be_visible; child = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row)); widget = get_language_widget (child); if (widget == NULL) return; should_be_visible = g_str_equal (widget->locale_id, priv->language); gtk_widget_set_opacity (widget->checkmark, should_be_visible ? 1.0 : 0.0); row = gtk_widget_get_next_sibling (row); } } static GtkWidget * more_widget_new (void) { GtkWidget *widget; GtkWidget *arrow; widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); gtk_widget_set_tooltip_text (widget, _("Moreā€¦")); arrow = g_object_new (GTK_TYPE_IMAGE, "icon-name", "view-more-symbolic", "hexpand", TRUE, "halign", GTK_ALIGN_CENTER, NULL); gtk_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label"); gtk_widget_set_margin_top (widget, 12); gtk_widget_set_margin_bottom (widget, 12); gtk_box_append (GTK_BOX (widget), arrow); return widget; } static GtkWidget * no_results_widget_new (void) { GtkWidget *widget; widget = padded_label_new (_("No languages found")); gtk_widget_set_sensitive (widget, FALSE); return widget; } static void add_one_language (CcLanguageChooser *chooser, const char *locale_id, gboolean is_initial) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); GtkWidget *widget; if (!cc_common_language_has_font (locale_id)) { return; } widget = language_widget_new (locale_id, !is_initial); if (widget) gtk_list_box_append (GTK_LIST_BOX (priv->language_list), widget); } static void add_languages (CcLanguageChooser *chooser, char **locale_ids, GHashTable *initial) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); GHashTableIter iter; gchar *key; g_hash_table_iter_init (&iter, initial); while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL)) { add_one_language (chooser, key, TRUE); } while (*locale_ids) { const gchar *locale_id; locale_id = *locale_ids; locale_ids ++; if (!g_hash_table_lookup (initial, locale_id)) add_one_language (chooser, locale_id, FALSE); } gtk_list_box_append (GTK_LIST_BOX (priv->language_list), priv->more_item); gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->language_list), priv->no_results); } static void add_all_languages (CcLanguageChooser *chooser) { g_auto(GStrv) locale_ids = NULL; g_autoptr(GHashTable) initial = NULL; locale_ids = gnome_get_all_locales (); initial = cc_common_language_get_initial_languages (); add_languages (chooser, locale_ids, initial); } static gboolean language_visible (GtkListBoxRow *row, gpointer user_data) { CcLanguageChooser *chooser = user_data; CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); LanguageWidget *widget; gboolean visible; GtkWidget *child; const char *search_term; child = gtk_list_box_row_get_child (row); if (child == priv->more_item) return !priv->showing_extra; widget = get_language_widget (child); if (!priv->showing_extra && widget->is_extra) return FALSE; search_term = gtk_editable_get_text (GTK_EDITABLE (priv->filter_entry)); if (!search_term || !*search_term) return TRUE; visible = FALSE; visible = g_str_match_string (search_term, widget->locale_name, TRUE); if (visible) goto out; visible = g_str_match_string (search_term, widget->locale_current_name, TRUE); if (visible) goto out; visible = g_str_match_string (search_term, widget->locale_untranslated_name, TRUE); if (visible) goto out; out: return visible; } static gint sort_languages (GtkListBoxRow *a, GtkListBoxRow *b, gpointer data) { LanguageWidget *la, *lb; int ret; la = get_language_widget (gtk_list_box_row_get_child (a)); lb = get_language_widget (gtk_list_box_row_get_child (b)); if (la == NULL) return 1; if (lb == NULL) return -1; if (la->is_extra && !lb->is_extra) return 1; if (!la->is_extra && lb->is_extra) return -1; ret = g_strcmp0 (la->sort_key, lb->sort_key); if (ret != 0) return ret; return g_strcmp0 (la->locale_id, lb->locale_id); } static void filter_changed (GtkEntry *entry, CcLanguageChooser *chooser) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list)); } static void show_more (CcLanguageChooser *chooser) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); gtk_widget_grab_focus (priv->filter_entry); gtk_widget_set_valign (GTK_WIDGET (chooser), GTK_ALIGN_FILL); priv->showing_extra = TRUE; gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list)); g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_SHOWING_EXTRA]); } static void set_locale_id (CcLanguageChooser *chooser, const gchar *new_locale_id) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); if (g_strcmp0 (priv->language, new_locale_id) == 0) return; g_free (priv->language); priv->language = g_strdup (new_locale_id); sync_all_checkmarks (chooser); g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_LANGUAGE]); } static gboolean confirm_choice (gpointer data) { GtkWidget *widget = data; g_signal_emit (widget, signals[CONFIRM], 0); return G_SOURCE_REMOVE; } static void row_activated (GtkListBox *box, GtkListBoxRow *row, CcLanguageChooser *chooser) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); GtkWidget *child; LanguageWidget *widget; if (row == NULL) return; child = gtk_list_box_row_get_child (row); if (child == priv->more_item) { show_more (chooser); } else { widget = get_language_widget (child); if (widget == NULL) return; if (g_strcmp0 (priv->language, widget->locale_id) == 0) g_idle_add (confirm_choice, chooser); else set_locale_id (chooser, widget->locale_id); } } static void cc_language_chooser_constructed (GObject *object) { CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); G_OBJECT_CLASS (cc_language_chooser_parent_class)->constructed (object); priv->more_item = more_widget_new (); priv->no_results = no_results_widget_new (); gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->language_list), sort_languages, chooser, NULL); gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->language_list), language_visible, chooser, NULL); gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->language_list), GTK_SELECTION_NONE); add_all_languages (chooser); g_signal_connect (priv->filter_entry, "changed", G_CALLBACK (filter_changed), chooser); g_signal_connect (priv->language_list, "row-activated", G_CALLBACK (row_activated), chooser); if (priv->language == NULL) priv->language = cc_common_language_get_current_language (); sync_all_checkmarks (chooser); } static void cc_language_chooser_finalize (GObject *object) { CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); g_free (priv->language); G_OBJECT_CLASS (cc_language_chooser_parent_class)->finalize (object); } static void cc_language_chooser_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); switch (prop_id) { case PROP_LANGUAGE: g_value_set_string (value, cc_language_chooser_get_language (chooser)); break; case PROP_SHOWING_EXTRA: g_value_set_boolean (value, cc_language_chooser_get_showing_extra (chooser)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cc_language_chooser_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object); switch (prop_id) { case PROP_LANGUAGE: cc_language_chooser_set_language (chooser, g_value_get_string (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void cc_language_chooser_class_init (CcLanguageChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/control-center/language-chooser.ui"); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, filter_entry); gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, language_list); object_class->finalize = cc_language_chooser_finalize; object_class->get_property = cc_language_chooser_get_property; object_class->set_property = cc_language_chooser_set_property; object_class->constructed = cc_language_chooser_constructed; signals[CONFIRM] = g_signal_new ("confirm", G_TYPE_FROM_CLASS (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (CcLanguageChooserClass, confirm), NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); obj_props[PROP_LANGUAGE] = g_param_spec_string ("language", "", "", "", G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); obj_props[PROP_SHOWING_EXTRA] = g_param_spec_string ("showing-extra", "", "", "", G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_properties (object_class, PROP_LAST, obj_props); } static void cc_language_chooser_init (CcLanguageChooser *chooser) { gtk_widget_init_template (GTK_WIDGET (chooser)); } void cc_language_chooser_clear_filter (CcLanguageChooser *chooser) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); gtk_editable_set_text (GTK_EDITABLE (priv->filter_entry), ""); } const gchar * cc_language_chooser_get_language (CcLanguageChooser *chooser) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); return priv->language; } void cc_language_chooser_set_language (CcLanguageChooser *chooser, const gchar *language) { set_locale_id (chooser, language); } gboolean cc_language_chooser_get_showing_extra (CcLanguageChooser *chooser) { CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser); return priv->showing_extra; }