/* -*- 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, see .
*
* Written by: Matthias Clasen
*/
#define _GNU_SOURCE
#include
#include "cc-language-chooser.h"
#include "cc-common-resources.h"
#include
#include
#include
#include
#include
#include "list-box-helper.h"
#include "cc-common-language.h"
#include "cc-util.h"
#define GNOME_DESKTOP_USE_UNSTABLE_API
#include
struct _CcLanguageChooser {
GtkDialog parent_instance;
GtkWidget *select_button;
GtkWidget *no_results;
GtkListBoxRow *more_item;
GtkWidget *search_bar;
GtkWidget *language_filter_entry;
GtkWidget *language_listbox;
gboolean showing_extra;
gchar *language;
gchar **filter_words;
};
G_DEFINE_TYPE (CcLanguageChooser, cc_language_chooser, GTK_TYPE_DIALOG)
static GtkWidget *
language_widget_new (const gchar *locale_id,
gboolean is_extra)
{
g_autofree gchar *language_code = NULL;
g_autofree gchar *country_code = NULL;
g_autofree gchar *language = NULL;
g_autofree gchar *country = NULL;
g_autofree gchar *language_local = NULL;
g_autofree gchar *country_local = NULL;
GtkWidget *row;
GtkWidget *box;
GtkWidget *language_label;
GtkWidget *check;
GtkWidget *country_label;
gnome_parse_locale (locale_id, &language_code, &country_code, NULL, NULL);
language = gnome_get_language_from_code (language_code, locale_id);
country = gnome_get_country_from_code (country_code, locale_id);
language_local = gnome_get_language_from_code (language_code, NULL);
country_local = gnome_get_country_from_code (country_code, NULL);
row = gtk_list_box_row_new ();
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
gtk_widget_show (box);
gtk_widget_set_margin_top (box, 12);
gtk_widget_set_margin_bottom (box, 12);
gtk_widget_set_margin_start (box, 18);
gtk_widget_set_margin_end (box, 18);
gtk_container_add (GTK_CONTAINER (row), box);
language_label = gtk_label_new (language);
gtk_widget_show (language_label);
gtk_label_set_xalign (GTK_LABEL (language_label), 0.0);
gtk_label_set_ellipsize (GTK_LABEL (language_label), PANGO_ELLIPSIZE_END);
gtk_box_pack_start (GTK_BOX (box), language_label, FALSE, TRUE, 0);
check = gtk_image_new ();
gtk_widget_hide (check);
gtk_image_set_from_icon_name (GTK_IMAGE (check), "object-select-symbolic", GTK_ICON_SIZE_MENU);
g_object_set (check, "icon-size", GTK_ICON_SIZE_MENU, NULL);
gtk_box_pack_start (GTK_BOX (box), check, FALSE, FALSE, 0);
country_label = gtk_label_new (country);
gtk_widget_show (country_label);
gtk_label_set_xalign (GTK_LABEL (country_label), 1.0);
gtk_label_set_ellipsize (GTK_LABEL (country_label), PANGO_ELLIPSIZE_END);
gtk_style_context_add_class (gtk_widget_get_style_context (country_label), "dim-label");
gtk_box_pack_start (GTK_BOX (box), country_label, TRUE, TRUE, 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), "language", g_steal_pointer (&language), g_free);
g_object_set_data_full (G_OBJECT (row), "country", g_steal_pointer (&country), g_free);
g_object_set_data_full (G_OBJECT (row), "language-local", g_steal_pointer (&language_local), g_free);
g_object_set_data_full (G_OBJECT (row), "country-local", g_steal_pointer (&country_local), 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_widget_show (box);
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_widget_show (arrow);
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_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 = gtk_label_new (_("No languages found"));
gtk_widget_set_sensitive (widget, FALSE);
return widget;
}
static void
add_languages (CcLanguageChooser *chooser,
gchar **locale_ids,
GHashTable *initial)
{
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, !is_initial);
gtk_widget_show (widget);
gtk_container_add (GTK_CONTAINER (chooser->language_listbox), widget);
}
gtk_container_add (GTK_CONTAINER (chooser->language_listbox), GTK_WIDGET (chooser->more_item));
}
static void
add_all_languages (CcLanguageChooser *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;
if (str == NULL)
return FALSE;
for (w = words; *w; ++w)
if (!strstr (str, *w))
return FALSE;
return TRUE;
}
static gboolean
language_visible (GtkListBoxRow *row,
gpointer user_data)
{
CcLanguageChooser *chooser = user_data;
g_autofree gchar *language = NULL;
g_autofree gchar *country = NULL;
g_autofree gchar *language_local = NULL;
g_autofree gchar *country_local = NULL;
gboolean is_extra;
gboolean visible;
if (row == chooser->more_item)
return !chooser->showing_extra;
is_extra = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "is-extra"));
if (!chooser->showing_extra && is_extra)
return FALSE;
if (!chooser->filter_words)
return TRUE;
language =
cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "language"));
visible = match_all (chooser->filter_words, language);
if (visible)
return TRUE;
country =
cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "country"));
visible = match_all (chooser->filter_words, country);
if (visible)
return TRUE;
language_local =
cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "language-local"));
visible = match_all (chooser->filter_words, language_local);
if (visible)
return TRUE;
country_local =
cc_util_normalize_casefold_and_unaccent (g_object_get_data (G_OBJECT (row), "country-local"));
return match_all (chooser->filter_words, country_local);
}
static gint
sort_languages (GtkListBoxRow *a,
GtkListBoxRow *b,
gpointer data)
{
const gchar *la, *lb, *ca, *cb;
int d;
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), "language");
lb = g_object_get_data (G_OBJECT (b), "language");
d = g_strcmp0 (la, lb);
if (d != 0)
return d;
ca = g_object_get_data (G_OBJECT (a), "country");
cb = g_object_get_data (G_OBJECT (b), "country");
return g_strcmp0 (ca, cb);
}
static void
filter_changed (CcLanguageChooser *chooser)
{
g_autofree gchar *filter_contents = NULL;
g_clear_pointer (&chooser->filter_words, g_strfreev);
filter_contents =
cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (GTK_ENTRY (chooser->language_filter_entry)));
if (!filter_contents) {
gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->language_listbox));
gtk_list_box_set_placeholder (GTK_LIST_BOX (chooser->language_listbox), NULL);
return;
}
chooser->filter_words = g_strsplit_set (g_strstrip (filter_contents), " ", 0);
gtk_list_box_set_placeholder (GTK_LIST_BOX (chooser->language_listbox), chooser->no_results);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->language_listbox));
}
static void
show_more (CcLanguageChooser *chooser, gboolean visible)
{
gint width, height;
gtk_window_get_size (GTK_WINDOW (chooser), &width, &height);
gtk_widget_set_size_request (GTK_WIDGET (chooser), width, height);
gtk_search_bar_set_search_mode (GTK_SEARCH_BAR (chooser->search_bar), visible);
gtk_widget_grab_focus (visible ? chooser->language_filter_entry : chooser->language_listbox);
chooser->showing_extra = visible;
gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->language_listbox));
}
static void
set_locale_id (CcLanguageChooser *chooser,
const gchar *locale_id)
{
g_autoptr(GList) children = NULL;
GList *l;
gtk_widget_set_sensitive (chooser->select_button, FALSE);
children = gtk_container_get_children (GTK_CONTAINER (chooser->language_listbox));
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_show (check);
gtk_widget_set_sensitive (chooser->select_button, TRUE);
/* make sure the selected language is shown */
is_extra = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (row), "is-extra"));
if (!chooser->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 (chooser->language_listbox));
}
} else {
gtk_widget_hide (check);
}
}
g_free (chooser->language);
chooser->language = g_strdup (locale_id);
}
static void
row_activated (CcLanguageChooser *chooser,
GtkListBoxRow *row)
{
gchar *new_locale_id;
if (row == NULL)
return;
if (row == chooser->more_item) {
show_more (chooser, TRUE);
return;
}
new_locale_id = g_object_get_data (G_OBJECT (row), "locale-id");
if (g_strcmp0 (new_locale_id, chooser->language) == 0) {
gtk_dialog_response (GTK_DIALOG (chooser),
gtk_dialog_get_response_for_widget (GTK_DIALOG (chooser),
chooser->select_button));
} else {
set_locale_id (chooser, new_locale_id);
}
}
static void
activate_default (CcLanguageChooser *chooser)
{
GtkWidget *focus;
gchar *locale_id;
focus = gtk_window_get_focus (GTK_WINDOW (chooser));
if (!focus)
return;
locale_id = g_object_get_data (G_OBJECT (focus), "locale-id");
if (g_strcmp0 (locale_id, chooser->language) == 0)
return;
g_signal_stop_emission_by_name (GTK_WINDOW (chooser), "activate-default");
gtk_widget_activate (focus);
}
void
cc_language_chooser_init (CcLanguageChooser *chooser)
{
g_resources_register (cc_common_get_resource ());
gtk_widget_init_template (GTK_WIDGET (chooser));
chooser->more_item = more_widget_new ();
gtk_widget_show (GTK_WIDGET (chooser->more_item));
/* We ref-sink here so we can reuse this widget multiple times */
chooser->no_results = g_object_ref_sink (no_results_widget_new ());
gtk_widget_show (chooser->no_results);
gtk_list_box_set_sort_func (GTK_LIST_BOX (chooser->language_listbox),
sort_languages, chooser, NULL);
gtk_list_box_set_filter_func (GTK_LIST_BOX (chooser->language_listbox),
language_visible, chooser, NULL);
gtk_list_box_set_selection_mode (GTK_LIST_BOX (chooser->language_listbox),
GTK_SELECTION_NONE);
gtk_list_box_set_header_func (GTK_LIST_BOX (chooser->language_listbox),
cc_list_box_update_header_func, NULL, NULL);
add_all_languages (chooser);
g_signal_connect_object (chooser->language_filter_entry, "search-changed",
G_CALLBACK (filter_changed), chooser, G_CONNECT_SWAPPED);
g_signal_connect_object (chooser->language_listbox, "row-activated",
G_CALLBACK (row_activated), chooser, G_CONNECT_SWAPPED);
gtk_list_box_invalidate_filter (GTK_LIST_BOX (chooser->language_listbox));
g_signal_connect (chooser, "activate-default",
G_CALLBACK (activate_default), NULL);
}
static void
cc_language_chooser_dispose (GObject *object)
{
CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
g_clear_object (&chooser->no_results);
g_clear_pointer (&chooser->filter_words, g_strfreev);
g_clear_pointer (&chooser->language, g_free);
G_OBJECT_CLASS (cc_language_chooser_parent_class)->dispose (object);
}
void
cc_language_chooser_class_init (CcLanguageChooserClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->dispose = cc_language_chooser_dispose;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/common/cc-language-chooser.ui");
gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, select_button);
gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, search_bar);
gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, language_filter_entry);
gtk_widget_class_bind_template_child (widget_class, CcLanguageChooser, language_listbox);
}
CcLanguageChooser *
cc_language_chooser_new (void)
{
return CC_LANGUAGE_CHOOSER (g_object_new (CC_TYPE_LANGUAGE_CHOOSER,
"use-header-bar", 1,
NULL));
}
void
cc_language_chooser_clear_filter (CcLanguageChooser *chooser)
{
g_return_if_fail (CC_IS_LANGUAGE_CHOOSER (chooser));
gtk_entry_set_text (GTK_ENTRY (chooser->language_filter_entry), "");
show_more (chooser, FALSE);
}
const gchar *
cc_language_chooser_get_language (CcLanguageChooser *chooser)
{
g_return_val_if_fail (CC_IS_LANGUAGE_CHOOSER (chooser), NULL);
return chooser->language;
}
void
cc_language_chooser_set_language (CcLanguageChooser *chooser,
const gchar *language)
{
g_return_if_fail (CC_IS_LANGUAGE_CHOOSER (chooser));
set_locale_id (chooser, language);
}