1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
2  *
3  * Copyright (C) 2013 Red Hat
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, see <http://www.gnu.org/licenses/>.
17  *
18  * Written by:
19  *     Jasper St. Pierre <jstpierre@mecheye.net>
20  *     Matthias Clasen <mclasen@redhat.com>
21  */
22 
23 #include "config.h"
24 #include "cc-language-chooser.h"
25 
26 #include <locale.h>
27 #include <glib/gi18n.h>
28 #include <gio/gio.h>
29 
30 #include <gtk/gtk.h>
31 
32 #define GNOME_DESKTOP_USE_UNSTABLE_API
33 #include <libgnome-desktop/gnome-languages.h>
34 
35 #include "cc-common-language.h"
36 #include "cc-util.h"
37 
38 #include <glib-object.h>
39 
40 struct _CcLanguageChooserPrivate
41 {
42         GtkWidget *filter_entry;
43         GtkWidget *language_list;
44 
45         GtkWidget *scrolled_window;
46         GtkWidget *no_results;
47         GtkWidget *more_item;
48 
49         gboolean showing_extra;
50         gchar *language;
51 };
52 typedef struct _CcLanguageChooserPrivate CcLanguageChooserPrivate;
53 G_DEFINE_TYPE_WITH_PRIVATE (CcLanguageChooser, cc_language_chooser, GTK_TYPE_BOX);
54 
55 enum {
56         PROP_0,
57         PROP_LANGUAGE,
58         PROP_SHOWING_EXTRA,
59         PROP_LAST,
60 };
61 
62 static GParamSpec *obj_props[PROP_LAST];
63 
64 enum {
65         CONFIRM,
66         LAST_SIGNAL
67 };
68 
69 static guint signals[LAST_SIGNAL] = { 0 };
70 
71 typedef struct {
72         GtkWidget *box;
73         GtkWidget *checkmark;
74 
75         gchar *locale_id;
76         gchar *locale_name;
77         gchar *locale_current_name;
78         gchar *locale_untranslated_name;
79         gchar *sort_key;
80         gboolean is_extra;
81 } LanguageWidget;
82 
83 static LanguageWidget *
get_language_widget(GtkWidget * widget)84 get_language_widget (GtkWidget *widget)
85 {
86         return g_object_get_data (G_OBJECT (widget), "language-widget");
87 }
88 
89 static GtkWidget *
padded_label_new(char * text)90 padded_label_new (char *text)
91 {
92         GtkWidget *widget;
93         widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
94         gtk_widget_set_halign (widget, GTK_ALIGN_CENTER);
95         gtk_widget_set_margin_top (widget, 10);
96         gtk_widget_set_margin_bottom (widget, 10);
97         gtk_box_pack_start (GTK_BOX (widget), gtk_label_new (text), FALSE, FALSE, 0);
98         return widget;
99 }
100 
101 static void
language_widget_free(gpointer data)102 language_widget_free (gpointer data)
103 {
104         LanguageWidget *widget = data;
105 
106         /* This is called when the box is destroyed,
107          * so don't bother destroying the widget and
108          * children again. */
109         g_free (widget->locale_id);
110         g_free (widget->locale_name);
111         g_free (widget->locale_current_name);
112         g_free (widget->locale_untranslated_name);
113         g_free (widget->sort_key);
114         g_free (widget);
115 }
116 
117 static GtkWidget *
language_widget_new(const char * locale_id,gboolean is_extra)118 language_widget_new (const char *locale_id,
119                      gboolean    is_extra)
120 {
121 	GtkWidget *label;
122         gchar *locale_name, *locale_current_name, *locale_untranslated_name;
123         gchar *language = NULL;
124         gchar *language_name;
125         gchar *country = NULL;
126         gchar *country_name = NULL;
127         LanguageWidget *widget = g_new0 (LanguageWidget, 1);
128 
129         if (!gnome_parse_locale (locale_id, &language, &country, NULL, NULL))
130                 return NULL;
131 
132         language_name = gnome_get_language_from_code (language, locale_id);
133         if (language_name == NULL)
134                 language_name = gnome_get_language_from_code (language, NULL);
135 
136         if (country) {
137                 country_name = gnome_get_country_from_code (country, locale_id);
138                 if (country_name == NULL)
139                         country_name = gnome_get_country_from_code (country, NULL);
140         }
141 
142         locale_name = gnome_get_language_from_locale (locale_id, locale_id);
143         locale_current_name = gnome_get_language_from_locale (locale_id, NULL);
144         locale_untranslated_name = gnome_get_language_from_locale (locale_id, "C");
145 
146         widget->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
147         gtk_widget_set_margin_top (widget->box, 10);
148         gtk_widget_set_margin_bottom (widget->box, 10);
149         gtk_widget_set_margin_start (widget->box, 10);
150         gtk_widget_set_margin_end (widget->box, 10);
151         gtk_widget_set_halign (widget->box, GTK_ALIGN_FILL);
152 
153         label = gtk_label_new (language_name);
154         gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
155         gtk_label_set_max_width_chars (GTK_LABEL (label), 30);
156         gtk_label_set_xalign (GTK_LABEL (label), 0);
157         gtk_box_pack_start (GTK_BOX (widget->box), label, FALSE, FALSE, 0);
158 
159         widget->checkmark = gtk_image_new_from_icon_name ("object-select-symbolic", GTK_ICON_SIZE_MENU);
160         gtk_box_pack_start (GTK_BOX (widget->box), widget->checkmark, FALSE, FALSE, 0);
161         gtk_widget_show (widget->checkmark);
162 
163         if (country_name) {
164                 label = gtk_label_new (country_name);
165                 gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END);
166                 gtk_label_set_max_width_chars (GTK_LABEL (label), 30);
167                 gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
168                 gtk_label_set_xalign (GTK_LABEL (label), 0);
169                 gtk_widget_set_halign (label, GTK_ALIGN_END);
170                 gtk_box_pack_end (GTK_BOX (widget->box), label, FALSE, FALSE, 0);
171         }
172 
173         widget->locale_id = g_strdup (locale_id);
174         widget->locale_name = locale_name;
175         widget->locale_current_name = locale_current_name;
176         widget->locale_untranslated_name = locale_untranslated_name;
177         widget->is_extra = is_extra;
178         widget->sort_key = cc_util_normalize_casefold_and_unaccent (locale_name);
179 
180         g_object_set_data_full (G_OBJECT (widget->box), "language-widget", widget,
181                                 language_widget_free);
182 
183         g_free (language);
184         g_free (language_name);
185         g_free (country);
186         g_free (country_name);
187 
188         return widget->box;
189 }
190 
191 static void
sync_checkmark(GtkWidget * row,gpointer user_data)192 sync_checkmark (GtkWidget *row,
193                 gpointer   user_data)
194 {
195         GtkWidget *child;
196         LanguageWidget *widget;
197         gchar *locale_id;
198         gboolean should_be_visible;
199 
200         child = gtk_bin_get_child (GTK_BIN (row));
201         widget = get_language_widget (child);
202 
203         if (widget == NULL)
204                 return;
205 
206         locale_id = user_data;
207         should_be_visible = g_str_equal (widget->locale_id, locale_id);
208         gtk_widget_set_opacity (widget->checkmark, should_be_visible ? 1.0 : 0.0);
209 }
210 
211 static void
sync_all_checkmarks(CcLanguageChooser * chooser)212 sync_all_checkmarks (CcLanguageChooser *chooser)
213 {
214         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
215 
216         gtk_container_foreach (GTK_CONTAINER (priv->language_list),
217                                sync_checkmark, priv->language);
218 }
219 
220 static GtkWidget *
more_widget_new(void)221 more_widget_new (void)
222 {
223         GtkWidget *widget;
224         GtkWidget *arrow;
225 
226         widget = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 10);
227         gtk_widget_set_tooltip_text (widget, _("More…"));
228 
229         arrow = gtk_image_new_from_icon_name ("view-more-symbolic", GTK_ICON_SIZE_MENU);
230         gtk_style_context_add_class (gtk_widget_get_style_context (arrow), "dim-label");
231         gtk_widget_set_margin_top (widget, 10);
232         gtk_widget_set_margin_bottom (widget, 10);
233         gtk_box_pack_start (GTK_BOX (widget), arrow, TRUE, TRUE, 0);
234 
235         return widget;
236 }
237 
238 static GtkWidget *
no_results_widget_new(void)239 no_results_widget_new (void)
240 {
241         GtkWidget *widget;
242 
243         widget = padded_label_new (_("No languages found"));
244         gtk_widget_set_sensitive (widget, FALSE);
245         gtk_widget_show_all (widget);
246         return widget;
247 }
248 
249 static void
add_one_language(CcLanguageChooser * chooser,const char * locale_id,gboolean is_initial)250 add_one_language (CcLanguageChooser *chooser,
251                   const char        *locale_id,
252                   gboolean           is_initial)
253 {
254         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
255 	GtkWidget *widget;
256 
257 	if (!cc_common_language_has_font (locale_id)) {
258 		return;
259 	}
260 
261 	widget = language_widget_new (locale_id, !is_initial);
262         if (widget)
263                 gtk_container_add (GTK_CONTAINER (priv->language_list), widget);
264 }
265 
266 static void
add_languages(CcLanguageChooser * chooser,char ** locale_ids,GHashTable * initial)267 add_languages (CcLanguageChooser  *chooser,
268                char               **locale_ids,
269                GHashTable          *initial)
270 {
271         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
272 	GHashTableIter iter;
273 	gchar *key;
274 
275 	g_hash_table_iter_init (&iter, initial);
276 	while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL)) {
277 		add_one_language (chooser, key, TRUE);
278 	}
279 
280         while (*locale_ids) {
281                 const gchar *locale_id;
282 
283                 locale_id = *locale_ids;
284                 locale_ids ++;
285 
286                 if (!g_hash_table_lookup (initial, locale_id))
287                         add_one_language (chooser, locale_id, FALSE);
288         }
289 
290         gtk_container_add (GTK_CONTAINER (priv->language_list), priv->more_item);
291         gtk_list_box_set_placeholder (GTK_LIST_BOX (priv->language_list), priv->no_results);
292 
293         gtk_widget_show_all (priv->language_list);
294 }
295 
296 static void
add_all_languages(CcLanguageChooser * chooser)297 add_all_languages (CcLanguageChooser *chooser)
298 {
299         g_auto(GStrv) locale_ids = NULL;
300         g_autoptr(GHashTable) initial = NULL;
301 
302         locale_ids = gnome_get_all_locales ();
303         initial = cc_common_language_get_initial_languages ();
304         add_languages (chooser, locale_ids, initial);
305 }
306 
307 static gboolean
language_visible(GtkListBoxRow * row,gpointer user_data)308 language_visible (GtkListBoxRow *row,
309                   gpointer       user_data)
310 {
311         CcLanguageChooser *chooser = user_data;
312         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
313         LanguageWidget *widget;
314         gboolean visible;
315         GtkWidget *child;
316         const char *search_term;
317 
318         child = gtk_bin_get_child (GTK_BIN (row));
319         if (child == priv->more_item)
320                 return !priv->showing_extra;
321 
322         widget = get_language_widget (child);
323 
324         if (!priv->showing_extra && widget->is_extra)
325                 return FALSE;
326 
327         search_term = gtk_entry_get_text (GTK_ENTRY (priv->filter_entry));
328         if (!search_term || !*search_term)
329                 return TRUE;
330 
331         visible = FALSE;
332 
333         visible = g_str_match_string (search_term, widget->locale_name, TRUE);
334         if (visible)
335                 goto out;
336 
337         visible = g_str_match_string (search_term, widget->locale_current_name, TRUE);
338         if (visible)
339                 goto out;
340 
341         visible = g_str_match_string (search_term, widget->locale_untranslated_name, TRUE);
342         if (visible)
343                 goto out;
344 
345  out:
346         return visible;
347 }
348 
349 static gint
sort_languages(GtkListBoxRow * a,GtkListBoxRow * b,gpointer data)350 sort_languages (GtkListBoxRow *a,
351                 GtkListBoxRow *b,
352                 gpointer       data)
353 {
354         LanguageWidget *la, *lb;
355         int ret;
356 
357         la = get_language_widget (gtk_bin_get_child (GTK_BIN (a)));
358         lb = get_language_widget (gtk_bin_get_child (GTK_BIN (b)));
359 
360         if (la == NULL)
361                 return 1;
362 
363         if (lb == NULL)
364                 return -1;
365 
366         if (la->is_extra && !lb->is_extra)
367                 return 1;
368 
369         if (!la->is_extra && lb->is_extra)
370                 return -1;
371 
372         ret = g_strcmp0 (la->sort_key, lb->sort_key);
373         if (ret != 0)
374                 return ret;
375 
376         return g_strcmp0 (la->locale_id, lb->locale_id);
377 }
378 
379 static void
filter_changed(GtkEntry * entry,CcLanguageChooser * chooser)380 filter_changed (GtkEntry        *entry,
381                 CcLanguageChooser *chooser)
382 {
383         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
384         gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list));
385 }
386 
387 static void
show_more(CcLanguageChooser * chooser)388 show_more (CcLanguageChooser *chooser)
389 {
390         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
391 
392         gtk_widget_show (priv->filter_entry);
393         gtk_widget_grab_focus (priv->filter_entry);
394 
395 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window),
396 					GTK_POLICY_NEVER,
397 					GTK_POLICY_AUTOMATIC);
398 	gtk_widget_set_valign (GTK_WIDGET (chooser), GTK_ALIGN_FILL);
399 
400         priv->showing_extra = TRUE;
401         gtk_list_box_invalidate_filter (GTK_LIST_BOX (priv->language_list));
402         g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_SHOWING_EXTRA]);
403 }
404 
405 static void
set_locale_id(CcLanguageChooser * chooser,const gchar * new_locale_id)406 set_locale_id (CcLanguageChooser *chooser,
407                const gchar       *new_locale_id)
408 {
409         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
410 
411         if (g_strcmp0 (priv->language, new_locale_id) == 0)
412                 return;
413 
414         g_free (priv->language);
415         priv->language = g_strdup (new_locale_id);
416 
417         sync_all_checkmarks (chooser);
418 
419         g_object_notify_by_pspec (G_OBJECT (chooser), obj_props[PROP_LANGUAGE]);
420 }
421 
422 static gboolean
confirm_choice(gpointer data)423 confirm_choice (gpointer data)
424 {
425         GtkWidget *widget = data;
426 
427         g_signal_emit (widget, signals[CONFIRM], 0);
428 
429         return G_SOURCE_REMOVE;
430 }
431 
432 static void
row_activated(GtkListBox * box,GtkListBoxRow * row,CcLanguageChooser * chooser)433 row_activated (GtkListBox        *box,
434                GtkListBoxRow     *row,
435                CcLanguageChooser *chooser)
436 {
437         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
438         GtkWidget *child;
439         LanguageWidget *widget;
440 
441         if (row == NULL)
442                 return;
443 
444         child = gtk_bin_get_child (GTK_BIN (row));
445         if (child == priv->more_item) {
446                 show_more (chooser);
447         } else {
448                 widget = get_language_widget (child);
449                 if (widget == NULL)
450                         return;
451                 if (g_strcmp0 (priv->language, widget->locale_id) == 0)
452 			g_idle_add (confirm_choice, chooser);
453                 else
454                         set_locale_id (chooser, widget->locale_id);
455         }
456 }
457 
458 static void
update_header_func(GtkListBoxRow * child,GtkListBoxRow * before,gpointer user_data)459 update_header_func (GtkListBoxRow *child,
460                     GtkListBoxRow *before,
461                     gpointer       user_data)
462 {
463         GtkWidget *header;
464 
465         if (before == NULL) {
466                 gtk_list_box_row_set_header (child, NULL);
467                 return;
468         }
469 
470         header = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
471         gtk_list_box_row_set_header (child, header);
472         gtk_widget_show (header);
473 }
474 
475 static void
cc_language_chooser_constructed(GObject * object)476 cc_language_chooser_constructed (GObject *object)
477 {
478         CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
479         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
480 
481         G_OBJECT_CLASS (cc_language_chooser_parent_class)->constructed (object);
482 
483         priv->more_item = more_widget_new ();
484         priv->no_results = no_results_widget_new ();
485 
486         gtk_list_box_set_sort_func (GTK_LIST_BOX (priv->language_list),
487                                     sort_languages, chooser, NULL);
488         gtk_list_box_set_filter_func (GTK_LIST_BOX (priv->language_list),
489                                       language_visible, chooser, NULL);
490         gtk_list_box_set_header_func (GTK_LIST_BOX (priv->language_list),
491                                       update_header_func, chooser, NULL);
492         gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->language_list),
493                                          GTK_SELECTION_NONE);
494         add_all_languages (chooser);
495 
496         g_signal_connect (priv->filter_entry, "changed",
497                           G_CALLBACK (filter_changed),
498                           chooser);
499 
500         g_signal_connect (priv->language_list, "row-activated",
501                           G_CALLBACK (row_activated), chooser);
502 
503         if (priv->language == NULL)
504                 priv->language = cc_common_language_get_current_language ();
505 
506         sync_all_checkmarks (chooser);
507 }
508 
509 static void
cc_language_chooser_finalize(GObject * object)510 cc_language_chooser_finalize (GObject *object)
511 {
512 	CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
513         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
514 
515         g_free (priv->language);
516 
517 	G_OBJECT_CLASS (cc_language_chooser_parent_class)->finalize (object);
518 }
519 
520 static void
cc_language_chooser_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)521 cc_language_chooser_get_property (GObject      *object,
522                                   guint         prop_id,
523                                   GValue       *value,
524                                   GParamSpec   *pspec)
525 {
526         CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
527         switch (prop_id) {
528         case PROP_LANGUAGE:
529                 g_value_set_string (value, cc_language_chooser_get_language (chooser));
530                 break;
531         case PROP_SHOWING_EXTRA:
532                 g_value_set_boolean (value, cc_language_chooser_get_showing_extra (chooser));
533                 break;
534         default:
535                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
536                 break;
537         }
538 }
539 
540 static void
cc_language_chooser_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)541 cc_language_chooser_set_property (GObject      *object,
542                                   guint         prop_id,
543                                   const GValue *value,
544                                   GParamSpec   *pspec)
545 {
546         CcLanguageChooser *chooser = CC_LANGUAGE_CHOOSER (object);
547         switch (prop_id) {
548         case PROP_LANGUAGE:
549                 cc_language_chooser_set_language (chooser, g_value_get_string (value));
550                 break;
551         default:
552                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
553                 break;
554         }
555 }
556 
557 static void
cc_language_chooser_class_init(CcLanguageChooserClass * klass)558 cc_language_chooser_class_init (CcLanguageChooserClass *klass)
559 {
560         GObjectClass *object_class = G_OBJECT_CLASS (klass);
561 
562         gtk_widget_class_set_template_from_resource (GTK_WIDGET_CLASS (klass), "/org/gnome/control-center/language-chooser.ui");
563 
564         gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, filter_entry);
565         gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, language_list);
566         gtk_widget_class_bind_template_child_private (GTK_WIDGET_CLASS (klass), CcLanguageChooser, scrolled_window);
567 
568 	object_class->finalize = cc_language_chooser_finalize;
569         object_class->get_property = cc_language_chooser_get_property;
570         object_class->set_property = cc_language_chooser_set_property;
571         object_class->constructed = cc_language_chooser_constructed;
572 
573         signals[CONFIRM] = g_signal_new ("confirm",
574                                          G_TYPE_FROM_CLASS (object_class),
575                                          G_SIGNAL_RUN_FIRST,
576                                          G_STRUCT_OFFSET (CcLanguageChooserClass, confirm),
577                                          NULL, NULL,
578                                          g_cclosure_marshal_VOID__VOID,
579                                          G_TYPE_NONE, 0);
580 
581         obj_props[PROP_LANGUAGE] =
582                 g_param_spec_string ("language", "", "", "",
583                                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
584 
585         obj_props[PROP_SHOWING_EXTRA] =
586                 g_param_spec_string ("showing-extra", "", "", "",
587                                      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
588 
589         g_object_class_install_properties (object_class, PROP_LAST, obj_props);
590 }
591 
592 static void
cc_language_chooser_init(CcLanguageChooser * chooser)593 cc_language_chooser_init (CcLanguageChooser *chooser)
594 {
595         gtk_widget_init_template (GTK_WIDGET (chooser));
596 }
597 
598 void
cc_language_chooser_clear_filter(CcLanguageChooser * chooser)599 cc_language_chooser_clear_filter (CcLanguageChooser *chooser)
600 {
601         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
602         gtk_entry_set_text (GTK_ENTRY (priv->filter_entry), "");
603 }
604 
605 const gchar *
cc_language_chooser_get_language(CcLanguageChooser * chooser)606 cc_language_chooser_get_language (CcLanguageChooser *chooser)
607 {
608         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
609         return priv->language;
610 }
611 
612 void
cc_language_chooser_set_language(CcLanguageChooser * chooser,const gchar * language)613 cc_language_chooser_set_language (CcLanguageChooser *chooser,
614                                   const gchar        *language)
615 {
616         set_locale_id (chooser, language);
617 }
618 
619 gboolean
cc_language_chooser_get_showing_extra(CcLanguageChooser * chooser)620 cc_language_chooser_get_showing_extra (CcLanguageChooser *chooser)
621 {
622         CcLanguageChooserPrivate *priv = cc_language_chooser_get_instance_private (chooser);
623         return priv->showing_extra;
624 }
625