[gnome-control-center/wip/input-sources: 3/13] More complete IBus support



commit bf0c16787ed0fff025fabec6a01a967408f5f98f
Author: Matthias Clasen <mclasen redhat com>
Date:   Sat Oct 22 19:42:04 2011 -0400

    More complete IBus support
    
    Things that work now:
    - Adding and removing sources works
    - Input soures can be rearranged
    - Changes are written back to IBus configuration
    - We show the actual shortcuts
    - Keyboard layouts can be previewed

 panels/region/Makefile.am                         |    1 +
 panels/region/gnome-region-panel-input-chooser.ui |  161 ++++++
 panels/region/gnome-region-panel-input.c          |  622 ++++++++++++++++++++-
 panels/region/gnome-region-panel-input.h          |    2 +-
 4 files changed, 755 insertions(+), 31 deletions(-)
---
diff --git a/panels/region/Makefile.am b/panels/region/Makefile.am
index 190e770..f5b3728 100644
--- a/panels/region/Makefile.am
+++ b/panels/region/Makefile.am
@@ -42,6 +42,7 @@ uidir   = $(pkgdatadir)/ui
 ui_DATA = \
 	gnome-region-panel.ui \
 	gnome-region-panel-layout-chooser.ui \
+	gnome-region-panel-input-chooser.ui \
 	gnome-region-panel-options-dialog.ui
 
 desktopdir = $(datadir)/applications
diff --git a/panels/region/gnome-region-panel-input-chooser.ui b/panels/region/gnome-region-panel-input-chooser.ui
new file mode 100644
index 0000000..8fc2b74
--- /dev/null
+++ b/panels/region/gnome-region-panel-input-chooser.ui
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+  <requires lib="gtk+" version="2.16"/>
+  <object class="GtkListStore" id="input_source_model">
+    <columns>
+      <!-- column-name id -->
+      <column type="gchararray"/>
+      <!-- column-name name -->
+      <column type="gchararray"/>
+      <!-- column-name lang -->
+      <column type="gchararray"/>
+      <!-- column-name layout -->
+      <column type="gchararray"/>
+      <!-- column-name xkb -->
+      <column type="gboolean"/>
+    </columns>
+  </object>
+  <object class="GtkTreeModelFilter" id="filtered_input_source_model">
+    <property name="child_model">input_source_model</property>
+  </object>
+  <object class="GtkDialog" id="input_source_chooser">
+    <property name="visible">False</property>
+    <property name="can_focus">False</property>
+    <property name="border_width">5</property>
+    <property name="title" translatable="yes">Choose an input source</property>
+    <property name="modal">True</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="dialog-vbox3">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="hbtnBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel-button">
+                <property name="label">gtk-cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">end</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ok-button">
+                <property name="label">gtk-add</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">False</property>
+                <property name="use_action_appearance">False</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">end</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+          </object>
+        </child>
+        <child>
+          <object class="GtkVBox" id="vbox40">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">5</property>
+            <property name="spacing">6</property>
+            <child>
+              <object class="GtkVBox" id="vbox1">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
+                <child>
+                  <object class="GtkLabel" id="label1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">False</property>
+                    <property name="xalign">0</property>
+                    <property name="label" translatable="yes">Select an input source to add</property>
+                  </object>
+                  <packing>
+                    <property name="expand">False</property>
+                    <property name="fill">False</property>
+                    <property name="position">0</property>
+                  </packing>
+                </child>
+                <child>
+                  <object class="GtkScrolledWindow" id="scrolledwindow1">
+                    <property name="visible">True</property>
+                    <property name="can_focus">True</property>
+                    <property name="hscrollbar_policy">never</property>
+                    <property name="shadow_type">etched-in</property>
+                    <property name="min_content_width">450</property>
+                    <property name="min_content_height">250</property>
+                    <child>
+                      <object class="GtkTreeView" id="filtered_input_source_list">
+                        <property name="visible">True</property>
+                        <property name="can_focus">True</property>
+                        <property name="model">filtered_input_source_model</property>
+                        <property name="headers_visible">False</property>
+                        <property name="search_column">0</property>
+                      </object>
+                    </child>
+                  </object>
+                  <packing>
+                    <property name="expand">True</property>
+                    <property name="fill">True</property>
+                    <property name="position">1</property>
+                  </packing>
+                </child>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="input_source_filter">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="invisible_char">â</property>
+                <property name="secondary-icon-name">edit-find-symbolic</property>
+                <property name="secondary-icon-activatable">False</property>
+                <property name="secondary-icon-sensitive">False</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="pack_type">end</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-5">ok-button</action-widget>
+      <action-widget response="-6">cancel-button</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/panels/region/gnome-region-panel-input.c b/panels/region/gnome-region-panel-input.c
index b261ff7..9951312 100644
--- a/panels/region/gnome-region-panel-input.c
+++ b/panels/region/gnome-region-panel-input.c
@@ -23,57 +23,287 @@
 
 #include <string.h>
 
+#include <glib.h>
+#include <glib/gi18n.h>
+
 #include <ibus.h>
+/* http://code.google.com/p/ibus/issues/detail?id=1338 */
+#define IBUS_COMPILATION
+#include <ibusutil.h>
+#undef IBUS_COMPILATION
+
+#include <gdk/gdkx.h>
+#include <libgnomekbd/gkbd-keyboard-drawing.h>
 
 #include "gnome-region-panel-input.h"
 
 #define WID(s) GTK_WIDGET(gtk_builder_get_object (builder, s))
 
+/* TODO
+ *
+ * - There is a large overlap between xkb and m17n:kbd -
+ *   we need to somehow decide and filter out
+ *   cases to consider:
+ *    Amharic: xkb:layout:et vs m17n:am:sera  - probably keep both
+ *    Arabic: xkb:layout:ara vs m17n:ar:kbd   - probably not
+ *
+ * - Straighten out the naming. It needs to be either
+ *   "Language" or "Language (Method)" when the same
+ *   language occurs more than once. Need to decide what
+ *   to do about xkb and m17n
+ *
+ * - Implement system-wide settings
+ *
+ * - Debug hotkeys, they don't act right
+ *
+ * - Allow changing shortcuts ?
+ */
 
-/* another ibus bus: ibusutil.h is not included in ibus.h */
-#define IBUS_COMPILATION
-#include <ibusutil.h>
-#undef IBUS_COMPILATION
+
+static GtkWidget *input_chooser_new          (GtkBuilder    *builder);
+static gboolean   input_chooser_get_selected (GtkWidget     *chooser,
+                                              GtkTreeModel **model,
+                                              GtkTreeIter   *iter);
+
+enum
+{
+  COL_NAME,
+  COL_DESC,
+  COL_LANG,
+  COL_LAYOUT,
+  COL_XKB
+};
+
+/* IBus interaction {{{1 */
+
+static gchar *
+engine_description (IBusEngineDesc *description, gboolean unique_lang)
+{
+  const gchar *lang;
+  gchar *longname;
+  gchar *desc;
+
+  lang = ibus_get_language_name (ibus_engine_desc_get_language (description));
+
+  if (g_getenv ("DEBUG_IBUS"))
+    return g_strdup_printf ("%s %s/%s/%s/%s", lang,
+                            ibus_engine_desc_get_name (description),
+                            ibus_engine_desc_get_longname (description),
+                            ibus_engine_desc_get_language (description),
+                            ibus_engine_desc_get_layout (description));
+
+  if (unique_lang)
+    return g_strdup (lang);
+
+  if (g_str_has_prefix (ibus_engine_desc_get_name (description), "xkb:layout:"))
+    {
+      if (strcmp (ibus_engine_desc_get_longname (description), lang) == 0)
+        return g_strdup_printf ("%s (xkb)", lang);
+      else
+        return g_strdup (ibus_engine_desc_get_longname (description));
+    }
+
+  longname = g_strdup (ibus_engine_desc_get_longname (description));
+  if (g_str_has_suffix (longname, " (m17n)"))
+    {
+       longname[strlen (longname) - strlen (" (m17n)")] = '\0';
+    }
+
+  desc = g_strdup_printf ("%s (%s)", lang, longname);
+
+  g_free (longname);
+
+  return desc;
+}
 
 static void
-get_active_engines (GtkListStore *store)
+populate_model (GtkListStore *store,
+                gboolean      only_active)
 {
   IBusBus *bus;
   GList *list, *l;
-  IBusEngineDesc *desc;
-  GType type;
+  GList *active_list;
+  IBusEngineDesc *description;
+  GType type G_GNUC_UNUSED;
   const gchar *name;
+  const gchar *lang;
+  gchar *desc;
+  gint count;
   GtkTreeIter iter;
+  GHashTable *lang_hash;
+  GHashTable *active_hash;
 
   bus = ibus_bus_new ();
 
-  /* work around a bug in IBus which forgets to register
-   * its types properly
+  /* IBus forgets to register its serializable types
+   * http://code.google.com/p/ibus/issues/detail?id=1339
    */
   type = ibus_engine_desc_get_type ();
 
-  list = ibus_bus_list_active_engines (bus);
+  list = ibus_bus_list_engines (bus);
+  active_list = ibus_bus_list_active_engines (bus);
 
+  lang_hash = g_hash_table_new (g_str_hash, g_str_equal);
+  active_hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+  /* Figure out which languages occur more than once
+   * for these, showing only the language is not sufficient
+   */
   for (l = list; l; l = l->next)
     {
-      desc = (IBusEngineDesc *)l->data;
+      description = (IBusEngineDesc *)l->data;
 
-      name = ibus_engine_desc_get_name (desc);
+      name = ibus_engine_desc_get_name (description);
       if (g_str_has_prefix (name, "xkb:layout:default:#"))
         continue;
 
+      lang = ibus_engine_desc_get_language (description);
+      lang = ibus_get_language_name (lang);
+      if (lang == NULL)
+        continue;
+
+      count = GPOINTER_TO_INT (g_hash_table_lookup (lang_hash, lang));
+      count++;
+      g_hash_table_insert (lang_hash, (gpointer)lang, GINT_TO_POINTER (count));
+    }
+
+  if (only_active)
+    {
+      g_list_free (list);
+      list = active_list;
+      active_list = NULL;
+    }
+  else
+    {
+      /* We want to skip active engines when
+       * making a list of inactive engines
+       */
+      for (l = active_list; l; l = l->next)
+        {
+          description = (IBusEngineDesc *)l->data;
+          name = ibus_engine_desc_get_name (description);
+          g_hash_table_insert (active_hash, (gpointer)name, (gpointer)name);
+        }
+      g_list_free (active_list);
+      active_list = NULL;
+    }
+
+  for (l = list; l; l = l->next)
+    {
+      description = (IBusEngineDesc *)l->data;
+
+      name = ibus_engine_desc_get_name (description);
+      if (g_str_has_prefix (name, "xkb:layout:default:#"))
+        continue;
+
+      if (g_hash_table_lookup (active_hash, name))
+        continue;
+
+      lang = ibus_engine_desc_get_language (description);
+      lang = ibus_get_language_name (lang);
+      if (lang == NULL)
+        continue;
+
+      count = GPOINTER_TO_INT (g_hash_table_lookup (lang_hash, lang));
+      desc = engine_description (description, count <= 1);
+
       gtk_list_store_append (store, &iter);
       gtk_list_store_set (store, &iter,
-                          0, name,
-                          1, ibus_get_language_name (ibus_engine_desc_get_language (desc)),
-                          2, ibus_engine_desc_get_layout (desc),
-                          3, g_str_has_prefix (name, "xkb:layout:"),
+                          COL_NAME, name,
+                          COL_DESC, desc,
+                          COL_LANG, lang,
+                          COL_LAYOUT, ibus_engine_desc_get_layout (description),
+                          COL_XKB, g_str_has_prefix (name, "xkb:layout:"),
                           -1);
+      g_free (desc);
     }
 
+  g_hash_table_unref (lang_hash);
+  g_hash_table_unref (active_hash);
+
   g_list_free (list);
+
+  g_object_unref (bus);
+}
+
+static void
+update_ibus_configuration (GtkTreeModel *model)
+{
+  GtkTreeIter iter;
+  IBusBus *bus;
+  IBusConfig *config;
+  gchar **active;
+  gsize n_active;
+  gchar *name;
+  gint i;
+
+  n_active = gtk_tree_model_iter_n_children (model, NULL);
+  active = g_new0 (gchar *, n_active + 1);
+  gtk_tree_model_get_iter_first (model, &iter);
+  i = 0;
+  do {
+    gtk_tree_model_get (model, &iter, COL_NAME, &name, -1);
+     active[i++] = name;
+  } while (gtk_tree_model_iter_next (model, &iter));
+  active[n_active] = NULL;
+
+  bus = ibus_bus_new ();
+  config = ibus_bus_get_config (bus);
+
+  if (!ibus_config_set_value (config,
+                              "general", "preload_engines",
+                              g_variant_new_strv ((const gchar * const *)active, -1)))
+    {
+      g_warning ("Failed to update IBus configuration");
+    }
+
+  g_strfreev (active);
+
+  g_object_unref (bus);
 }
 
+static void
+get_shortcuts (gchar **previous, gchar **next)
+{
+  IBusBus *bus;
+  IBusConfig *config;
+  GVariant *value;
+  const gchar **strv;
+  gsize len;
+
+  bus = ibus_bus_new ();
+  config = ibus_bus_get_config (bus);
+
+  *previous = NULL;
+  *next = NULL;
+
+  value = ibus_config_get_value (config, "general/hotkey", "previous_engine");
+  if (value)
+    {
+       strv = g_variant_get_strv (value, &len);
+       if (len > 0)
+         *previous = g_strdup (strv[0]);
+       g_free (strv);
+       g_variant_unref (value);
+    }
+
+  value = ibus_config_get_value (config, "general/hotkey", "next_engine_in_menu");
+  if (value)
+    {
+       strv = g_variant_get_strv (value, &len);
+       if (len > 0)
+         *next = g_strdup (strv[0]);
+       g_free (strv);
+       g_variant_unref (value);
+    }
+
+  g_debug ("Hotkeys: previous: %s next: %s\n", *previous, *next);
+
+  g_object_unref (bus);
+}
+
+/* List handling {{{1 */
+
 static gboolean
 get_selected_iter (GtkBuilder    *builder,
                    GtkTreeModel **model,
@@ -115,7 +345,6 @@ update_button_sensitivity (GtkBuilder *builder)
   GtkWidget *down_button;
   GtkWidget *show_button;
   GtkTreeView *tv;
-  GtkTreeSelection *selection;
   gint n_active;
   gint index;
 
@@ -125,7 +354,6 @@ update_button_sensitivity (GtkBuilder *builder)
   down_button = WID("input_source_move_down");
 
   tv = GTK_TREE_VIEW (WID ("active_input_sources"));
-  selection = gtk_tree_view_get_selection (tv);
 
   n_active = gtk_tree_model_iter_n_children (gtk_tree_view_get_model (tv), NULL);
   index = find_selected_layout_idx (builder);
@@ -148,11 +376,77 @@ set_selected_path (GtkBuilder  *builder,
 }
 
 static void
+chooser_response (GtkWidget *chooser, gint response_id, gpointer data)
+{
+  GtkBuilder *builder = data;
+
+  if (response_id == GTK_RESPONSE_OK)
+    {
+      GtkTreeModel *model;
+      GtkTreeIter iter;
+
+      if (input_chooser_get_selected (chooser, &model, &iter))
+        {
+          GtkTreeView *my_tv;
+          GtkListStore *my_model;
+          GtkTreeIter my_iter;
+          gchar *name;
+          gchar *desc;
+          gchar *lang;
+          gchar *layout;
+          gboolean xkb;
+
+          gtk_tree_model_get (model, &iter,
+                              COL_NAME, &name,
+                              COL_DESC, &desc,
+                              COL_LANG, &lang,
+                              COL_LAYOUT, &layout,
+                              COL_XKB, &xkb,
+                              -1);
+
+          my_tv = GTK_TREE_VIEW (WID ("active_input_sources"));
+          my_model = GTK_LIST_STORE (gtk_tree_view_get_model (my_tv));
+
+          gtk_list_store_append (my_model, &my_iter);
+
+          gtk_list_store_set (my_model, &my_iter,
+                              COL_NAME, name,
+                              COL_DESC, desc,
+                              COL_LANG, lang,
+                              COL_LAYOUT, layout,
+                              COL_XKB, xkb,
+                              -1);
+
+          g_free (name);
+          g_free (desc);
+          g_free (lang);
+          g_free (layout);
+
+          gtk_tree_selection_select_iter (gtk_tree_view_get_selection (my_tv), &my_iter);
+
+          update_button_sensitivity (builder);
+          update_ibus_configuration (GTK_TREE_MODEL (my_model));
+        }
+      else
+        {
+          g_debug ("nothing selected, nothing added");
+        }
+    }
+
+  gtk_widget_destroy (GTK_WIDGET (chooser));
+}
+
+static void
 add_input (GtkButton *button, gpointer data)
 {
   GtkBuilder *builder = data;
+  GtkWidget *chooser;
 
   g_debug ("add an input source");
+
+  chooser = input_chooser_new (builder);
+  g_signal_connect (chooser, "response",
+                    G_CALLBACK (chooser_response), builder);
 }
 
 static void
@@ -161,15 +455,25 @@ remove_selected_input (GtkButton *button, gpointer data)
   GtkBuilder *builder = data;
   GtkTreeModel *model;
   GtkTreeIter iter;
+  GtkTreePath *path;
 
   g_debug ("remove selected input source");
 
   if (get_selected_iter (builder, &model, &iter) == FALSE)
     return;
 
+  path = gtk_tree_model_get_path (model, &iter);
   gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
+
+  if (!gtk_tree_model_get_iter (model, &iter, path))
+    gtk_tree_path_prev (path);
+
+  set_selected_path (builder, path);
+
+  gtk_tree_path_free (path);
+
   update_button_sensitivity (builder);
-  /* update_layouts_list (model, dialog); */
+  update_ibus_configuration (model);
 }
 
 static void
@@ -192,11 +496,11 @@ move_selected_input_up (GtkButton *button, gpointer data)
   path = gtk_tree_model_get_path (model, &prev);
 
   gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &prev);
-  update_button_sensitivity (builder);
-  /* update_layouts_list (model, dialog); */
   set_selected_path (builder, path);
-
   gtk_tree_path_free (path);
+
+  update_button_sensitivity (builder);
+  update_ibus_configuration (model);
 }
 
 static void
@@ -217,23 +521,62 @@ move_selected_input_down (GtkButton *button, gpointer data)
     return;
 
   path = gtk_tree_model_get_path (model, &next);
-
   gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &next);
-  update_button_sensitivity (builder);
-  /* update_layouts_list (model, dialog); */
   set_selected_path (builder, path);
-
   gtk_tree_path_free (path);
+
+  update_button_sensitivity (builder);
+  update_ibus_configuration (model);
 }
 
 static void
 show_selected_layout (GtkButton *button, gpointer data)
 {
   GtkBuilder *builder = data;
+  GtkWidget *parent;
+  GtkWidget *popup;
+  GtkTreeModel *model;
+  GtkTreeIter iter;
+  gchar *layout;
+  gchar *desc;
+  gchar *title;
+  XklEngine *engine;
+  XklConfigRegistry *registry;
 
   g_debug ("show selected layout");
+
+  if (!get_selected_iter (builder, &model, &iter))
+    return;
+
+  gtk_tree_model_get (model, &iter,
+                      COL_LAYOUT, &layout,
+                      COL_DESC, &desc,
+                      -1);
+
+  parent = WID ("region_notebook");
+  popup = gkbd_keyboard_drawing_dialog_new ();
+
+  engine = xkl_engine_get_instance (GDK_DISPLAY_XDISPLAY (gtk_widget_get_display (parent)));
+  registry = xkl_config_registry_get_instance (engine);
+
+  gkbd_keyboard_drawing_dialog_set_layout (popup, registry, layout);
+
+  g_object_unref (registry);
+
+  title = g_strdup_printf (_("Keyboard layout for %s"), desc);
+  gtk_window_set_title (GTK_WINDOW (popup), title);
+  g_free (title);
+
+  gtk_window_set_transient_for (GTK_WINDOW (popup),
+                                GTK_WINDOW (gtk_widget_get_toplevel (parent)));
+  gtk_widget_show_all (popup);
+
+  g_free (layout);
+  g_free (desc);
 }
 
+/* Main setup {{{1 */
+
 void
 setup_input_tabs (GtkBuilder *builder)
 {
@@ -242,19 +585,22 @@ setup_input_tabs (GtkBuilder *builder)
   GtkCellRenderer *cell;
   GtkListStore *store;
   GtkTreeSelection *selection;
+  gchar *previous = NULL;
+  gchar *next = NULL;
+  GtkWidget *label;
 
   /* set up the list of active inputs */
   treeview = WID("active_input_sources");
   column = gtk_tree_view_column_new ();
   cell = gtk_cell_renderer_text_new ();
   gtk_tree_view_column_pack_start (column, cell, TRUE);
-  gtk_tree_view_column_add_attribute (column, cell, "text", 1);
+  gtk_tree_view_column_add_attribute (column, cell, "text", COL_DESC);
   gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
 
-  /* id, name, layout, xkb? */
-  store = gtk_list_store_new (4, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
+  /* id, name, language, layout, xkb? */
+  store = gtk_list_store_new (5, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
 
-  get_active_engines (store);
+  populate_model (store, TRUE);
 
   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
   g_signal_connect_swapped (selection, "changed",
@@ -273,4 +619,220 @@ setup_input_tabs (GtkBuilder *builder)
                     G_CALLBACK (move_selected_input_down), builder);
   g_signal_connect (WID("input_source_show"), "clicked",
                     G_CALLBACK (show_selected_layout), builder);
+
+  /* show the right shortcuts */
+  get_shortcuts (&previous, &next);
+
+  /* use an em dash is no shortcut */
+  if (!previous)
+    previous = g_strdup ("\342\200\224");
+  if (!next)
+    next = g_strdup ("\342\200\224");
+
+  label = WID("prev-source-shortcut-label");
+  gtk_label_set_label (GTK_LABEL (label), previous);
+  label = WID("next-source-shortcut-label");
+  gtk_label_set_label (GTK_LABEL (label), next);
+
+  g_free (previous);
+  g_free (next);
+}
+
+/* Chooser dialog {{{1 */
+
+static void
+filter_clear (GtkEntry             *entry,
+              GtkEntryIconPosition  icon_pos,
+              GdkEvent             *event,
+              gpointer              user_data)
+{
+  gtk_entry_set_text (entry, "");
+}
+
+static gchar **search_pattern_list;
+
+static void
+filter_changed (GtkBuilder *builder)
+{
+  GtkTreeModelFilter *filtered_model;
+  GtkWidget *filter_entry;
+  const gchar *pattern;
+  gchar *upattern;
+
+  filtered_model = GTK_TREE_MODEL_FILTER (gtk_builder_get_object (builder,
+                                                   "filtered_input_source_model"));
+  filter_entry = WID ("input_source_filter");
+  pattern = gtk_entry_get_text (GTK_ENTRY (filter_entry));
+  upattern = g_utf8_strup (pattern, -1);
+  if (!g_strcmp0 (pattern, ""))
+    g_object_set (G_OBJECT (filter_entry),
+                  "secondary-icon-name", "edit-find-symbolic",
+                  "secondary-icon-activatable", FALSE,
+                  "secondary-icon-sensitive", FALSE,
+                  NULL);
+  else
+    g_object_set (G_OBJECT (filter_entry),
+                  "secondary-icon-name", "edit-clear-symbolic",
+                  "secondary-icon-activatable", TRUE,
+                  "secondary-icon-sensitive", TRUE,
+                  NULL);
+
+  if (search_pattern_list != NULL)
+    g_strfreev (search_pattern_list);
+
+  search_pattern_list = g_strsplit (upattern, " ", -1);
+  g_free (upattern);
+
+  gtk_tree_model_filter_refilter (filtered_model);
+}
+
+static void
+selection_changed (GtkTreeSelection *selection,
+                   GtkBuilder       *builder)
+{
+  GList *selected;
+
+  selected = gtk_tree_selection_get_selected_rows (selection, NULL);
+  gtk_widget_set_sensitive (WID ("ok-button"), selected != NULL);
+}
+
+static void
+row_activated (GtkTreeView       *tree_view,
+               GtkTreePath       *path,
+               GtkTreeViewColumn *column,
+               GtkBuilder        *builder)
+{
+  GtkWidget *add_button;
+  GtkWidget *dialog;
+
+  add_button = WID ("ok-button");
+  dialog = WID ("input_source_chooser");
+  if (gtk_widget_is_sensitive (add_button))
+    gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
+}
+
+static gboolean
+filter_func (GtkTreeModel *model,
+             GtkTreeIter  *iter,
+             gpointer      data)
+{
+  gchar *desc = NULL;
+  gchar **pattern;
+  gboolean rv = TRUE;
+
+  if (search_pattern_list == NULL || search_pattern_list[0] == NULL)
+    return TRUE;
+
+  gtk_tree_model_get (model, iter,
+                      COL_DESC, &desc,
+                      -1);
+
+  pattern = search_pattern_list;
+  do {
+    gboolean is_pattern_found = FALSE;
+    gchar *udesc = g_utf8_strup (desc, -1);
+    if (udesc != NULL && g_strstr_len (udesc, -1, *pattern))
+      {
+        is_pattern_found = TRUE;
+      }
+    g_free (udesc);
+
+    if (!is_pattern_found)
+      {
+        rv = FALSE;
+        break;
+      }
+
+  } while (*++pattern != NULL);
+
+  g_free (desc);
+
+  return rv;
+}
+
+static GtkWidget *
+input_chooser_new (GtkBuilder *main_builder)
+{
+  GtkBuilder *builder;
+  GtkWidget *chooser;
+  GtkWidget *filtered_list;
+  GtkWidget *filter_entry;
+  GtkTreeViewColumn *visible_column;
+  GtkTreeSelection *selection;
+  GtkListStore *model;
+  GtkTreeModelFilter *filtered_model;
+
+  builder = gtk_builder_new ();
+  gtk_builder_add_from_file (builder,
+                             GNOMECC_UI_DIR "/gnome-region-panel-input-chooser.ui",
+                             NULL);
+  chooser = WID ("input_source_chooser");
+  filtered_list = WID ("filtered_input_source_list");
+  filter_entry = WID ("input_source_filter");
+
+  g_object_set_data (G_OBJECT (chooser),
+                     "filtered_input_source_list", filtered_list);
+  visible_column =
+    gtk_tree_view_column_new_with_attributes ("Layout",
+                                              gtk_cell_renderer_text_new (),
+                                              "text", COL_DESC,
+                                              NULL);
+
+  gtk_window_set_transient_for (GTK_WINDOW (chooser),
+                                GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (gtk_builder_get_object (main_builder, "region_notebook")))));
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (filtered_list),
+                               visible_column);
+  g_signal_connect_swapped (G_OBJECT (filter_entry), "notify::text",
+                            G_CALLBACK (filter_changed), builder);
+
+  g_signal_connect (G_OBJECT (filter_entry), "icon-release",
+                    G_CALLBACK (filter_clear), NULL);
+
+  selection =
+    gtk_tree_view_get_selection (GTK_TREE_VIEW (filtered_list));
+
+  g_signal_connect (G_OBJECT (selection), "changed",
+                    G_CALLBACK (selection_changed), builder);
+
+  selection_changed (selection, builder);
+
+  g_signal_connect (G_OBJECT (filtered_list), "row-activated",
+                    G_CALLBACK (row_activated), builder);
+
+  filtered_model =
+    GTK_TREE_MODEL_FILTER (gtk_builder_get_object (builder, "filtered_input_source_model"));
+  model =
+    GTK_LIST_STORE (gtk_builder_get_object (builder, "input_source_model"));
+
+  populate_model (model, FALSE);
+
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model),
+                                        COL_DESC, GTK_SORT_ASCENDING);
+
+  gtk_tree_model_filter_set_visible_func (filtered_model,
+                                          filter_func,
+                                          NULL, NULL);
+
+  gtk_widget_grab_focus (filter_entry);
+
+  gtk_widget_show (chooser);
+
+  return chooser;
+}
+
+static gboolean
+input_chooser_get_selected (GtkWidget     *dialog,
+                            GtkTreeModel **model,
+                            GtkTreeIter   *iter)
+{
+  GtkWidget *tv;
+  GtkTreeSelection *selection;
+
+  tv = g_object_get_data (G_OBJECT (dialog), "filtered_input_source_list");
+  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tv));
+
+  return gtk_tree_selection_get_selected (selection, model, iter);
 }
+/* Epilogue {{{1 */
+/* vim: set foldmethod=marker: */
diff --git a/panels/region/gnome-region-panel-input.h b/panels/region/gnome-region-panel-input.h
index 987bce6..a3aa05d 100644
--- a/panels/region/gnome-region-panel-input.h
+++ b/panels/region/gnome-region-panel-input.h
@@ -27,7 +27,7 @@
 
 G_BEGIN_DECLS
 
-extern void setup_input_tabs (GtkBuilder * dialog);
+void setup_input_tabs (GtkBuilder *builder);
 
 G_END_DECLS
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]