/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Written by: Matthias Clasen */ #define _GNU_SOURCE #include #include "cc-language-chooser.h" #include "cc-common-resources.h" #include #include #include #include #include #include "cc-common-language.h" #include "cc-util.h" #define GNOME_DESKTOP_USE_UNSTABLE_API #include typedef struct { GtkWidget *no_results; GtkListBoxRow *more_item; GtkWidget *filter_entry; GtkWidget *language_list; GtkWidget *scrolledwindow; gboolean showing_extra; gchar *language; gchar **filter_words; } CcLanguageChooserPrivate; #define GET_PRIVATE(chooser) ((CcLanguageChooserPrivate *) g_object_get_data (G_OBJECT (chooser), "private")) static GtkWidget * padded_label_new (char *text, gboolean narrow) { GtkWidget *widget; widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); gtk_widget_set_halign (widget, GTK_ALIGN_CENTER); gtk_widget_set_margin_top (widget, 10); gtk_widget_set_margin_bottom (widget, 10); gtk_widget_set_margin_left (widget, narrow ? 10 : 80); gtk_widget_set_margin_right (widget, narrow ? 10 : 80); gtk_box_pack_start (GTK_BOX (widget), gtk_label_new (text), FALSE, FALSE, 0); return widget; } static GtkWidget * language_widget_new (const gchar *locale_id, const gchar *current_locale_id, gboolean is_extra) { gchar *locale_name; gchar *locale_current_name; gchar *locale_untranslated_name; GtkWidget *row; GtkWidget *check; GtkWidget *box; 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"); row = gtk_list_box_row_new (); box = padded_label_new (locale_name, is_extra); gtk_container_add (GTK_CONTAINER (row), box); /* We add a check on each side of the label to keep it centered. */ check = gtk_image_new (); gtk_image_set_from_icon_name (GTK_IMAGE (check), "object-select-symbolic", GTK_ICON_SIZE_MENU); gtk_widget_set_opacity (check, 0.0); g_object_set (check, "icon-size", GTK_ICON_SIZE_MENU, NULL); gtk_box_pack_start (GTK_BOX (box), check, FALSE, FALSE, 0); gtk_box_reorder_child (GTK_BOX (box), check, 0); check = gtk_image_new (); gtk_image_set_from_icon_name (GTK_IMAGE (check), "object-select-symbolic", GTK_ICON_SIZE_MENU); gtk_widget_set_opacity (check, 0.0); g_object_set (check, "icon-size", GTK_ICON_SIZE_MENU, NULL); gtk_box_pack_start (GTK_BOX (box), check, FALSE, FALSE, 0); if (g_strcmp0 (locale_id, current_locale_id) == 0) gtk_widget_set_opacity (check, 1.0); g_object_set_data (G_OBJECT (row), "check", check); g_object_set_data_full (G_OBJECT (row), "locale-id", g_strdup (locale_id), g_free); g_object_set_data_full (G_OBJECT (row), "locale-name", locale_name, g_free); g_object_set_data_full (G_OBJECT (row), "locale-current-name", locale_current_name, g_free); g_object_set_data_full (G_OBJECT (row), "locale-untranslated-name", locale_untranslated_name, g_free); g_object_set_data (G_OBJECT (row), "is-extra", GUINT_TO_POINTER (is_extra)); return row; } static GtkListBoxRow * more_widget_new (void) { GtkWidget *box, *row; GtkWidget *arrow; row = gtk_list_box_row_new (); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10); gtk_container_add (GTK_CONTAINER (row), box); gtk_widget_set_tooltip_text (box, _("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"); gtk_widget_set_margin_top (box, 10); gtk_widget_set_margin_bottom (box, 10); gtk_misc_set_alignment (GTK_MISC (arrow), 0.5, 0.5); gtk_box_pack_start (GTK_BOX (box), arrow, TRUE, TRUE, 0); return GTK_LIST_BOX_ROW (row); } static GtkWidget * no_results_widget_new (void) { GtkWidget *widget; widget = padded_label_new (_("No languages found"), TRUE); gtk_widget_set_sensitive (widget, FALSE); return widget; } static void add_languages (GtkDialog *chooser, gchar **locale_ids, GHashTable *initial) { CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser); while (*locale_ids) { gchar *locale_id; gboolean is_initial; GtkWidget *widget; locale_id = *locale_ids; locale_ids ++; if (!cc_common_language_has_font (locale_id)) continue; is_initial = (g_hash_table_lookup (initial, locale_id) != NULL); widget = language_widget_new (locale_id, priv->language, !is_initial); gtk_container_add (GTK_CONTAINER (priv->language_list), widget); } gtk_container_add (GTK_CONTAINER (priv->language_list), GTK_WIDGET (priv->more_item)); gtk_widget_show_all (priv->language_list); } static void add_all_languages (GtkDialog *chooser) { gchar **locale_ids; GHashTable *initial; locale_ids = gnome_get_all_locales (); initial = cc_common_language_get_initial_languages (); add_languages (chooser, locale_ids, initial); g_hash_table_destroy (initial); g_strfreev (locale_ids); } 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 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 gint sort_languages (GtkListBoxRow *a, GtkListBoxRow *b, gpointer data) { const gchar *la; const gchar *lb; if (g_object_get_data (G_OBJECT (a), "locale-id") == NULL) return 1; if (g_object_get_data (G_OBJECT (b), "locale-id") == NULL) return -1; la = g_object_get_data (G_OBJECT (a), "locale-name"); lb = g_object_get_data (G_OBJECT (b), "locale-name"); return g_strcmp0 (la, lb); } 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 void show_more (GtkDialog *chooser) { CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser); GtkWidget *widget; gint width, height; gtk_window_get_size (GTK_WINDOW (chooser), &width, &height); gtk_widget_set_size_request (GTK_WIDGET (chooser), width, height); gtk_window_set_resizable (GTK_WINDOW (chooser), TRUE); widget = priv->scrolledwindow; gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (widget), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); 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->language_list)); } static void set_locale_id (GtkDialog *chooser, const gchar *locale_id) { CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser); GList *children, *l; children = gtk_container_get_children (GTK_CONTAINER (priv->language_list)); for (l = children; l; l = l->next) { GtkWidget *row = l->data; GtkWidget *check = g_object_get_data (G_OBJECT (row), "check"); const gchar *language = g_object_get_data (G_OBJECT (row), "locale-id"); if (check == NULL || language == NULL) continue; if (g_strcmp0 (locale_id, language) == 0) { gboolean is_extra; gtk_widget_set_opacity (check, 1.0); /* make sure the selected language is shown */ is_extra = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "is-extra")); if (!priv->showing_extra && is_extra) { g_object_set_data (G_OBJECT (row), "is-extra", GINT_TO_POINTER (FALSE)); gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list)); } } else { gtk_widget_set_opacity (check, 0.0); } } g_list_free (children); g_free (priv->language); priv->language = g_strdup (locale_id); } static void row_activated (GtkListBox *box, GtkListBoxRow *row, GtkDialog *chooser) { CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser); gchar *new_locale_id; if (row == NULL) return; if (row == priv->more_item) { show_more (chooser); return; } new_locale_id = g_object_get_data (G_OBJECT (row), "locale-id"); set_locale_id (chooser, new_locale_id); } static void update_header_func (GtkListBoxRow *row, GtkListBoxRow *before, gpointer user_data) { GtkWidget *current; if (before == NULL) return; current = gtk_list_box_row_get_header (row); if (current == NULL) { current = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL); gtk_widget_show (current); gtk_list_box_row_set_header (row, current); } } static void cc_language_chooser_private_free (gpointer data) { CcLanguageChooserPrivate *priv = data; g_clear_object (&priv->no_results); g_strfreev (priv->filter_words); g_free (priv->language); g_free (priv); } #define WID(name) ((GtkWidget *) gtk_builder_get_object (builder, name)) GtkWidget * cc_language_chooser_new (GtkWidget *parent) { GtkBuilder *builder; GtkWidget *chooser; CcLanguageChooserPrivate *priv; GError *error = NULL; g_resources_register (cc_common_get_resource ()); builder = gtk_builder_new (); if (gtk_builder_add_from_resource (builder, "/org/gnome/control-center/common/language-chooser.ui", &error) == 0) { g_object_unref (builder); g_warning ("failed to load language chooser: %s", error->message); g_error_free (error); return NULL; } chooser = WID ("language-dialog"); priv = g_new0 (CcLanguageChooserPrivate, 1); g_object_set_data_full (G_OBJECT (chooser), "private", priv, cc_language_chooser_private_free); g_object_set_data_full (G_OBJECT (chooser), "builder", builder, g_object_unref); priv->filter_entry = WID ("language-filter-entry"); priv->language_list = WID ("language-list"); priv->scrolledwindow = WID ("language-scrolledwindow"); priv->more_item = more_widget_new (); /* We ref-sink here so we can reuse this widget multiple times */ priv->no_results = g_object_ref_sink (no_results_widget_new ()); gtk_widget_show_all (priv->no_results); 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); gtk_list_box_set_header_func (GTK_LIST_BOX (priv->language_list), update_header_func, NULL, NULL); add_all_languages (GTK_DIALOG (chooser)); g_signal_connect_swapped (priv->filter_entry, "search-changed", G_CALLBACK (filter_changed), chooser); g_signal_connect (priv->language_list, "row-activated", G_CALLBACK (row_activated), chooser); gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list)); gtk_window_set_transient_for (GTK_WINDOW (chooser), GTK_WINDOW (parent)); return chooser; } void cc_language_chooser_clear_filter (GtkWidget *chooser) { CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser); gtk_entry_set_text (GTK_ENTRY (priv->filter_entry), ""); } const gchar * cc_language_chooser_get_language (GtkWidget *chooser) { CcLanguageChooserPrivate *priv = GET_PRIVATE (chooser); return priv->language; } void cc_language_chooser_set_language (GtkWidget *chooser, const gchar *language) { set_locale_id (GTK_DIALOG (chooser), language); }