Add Ctrl+space customization.
authorfujiwarat <takao.fujiwara1@gmail.com>
Wed, 5 Dec 2012 02:20:37 +0000 (11:20 +0900)
committerfujiwarat <takao.fujiwara1@gmail.com>
Wed, 5 Dec 2012 02:20:37 +0000 (11:20 +0900)
Review URL: https://codereview.appspot.com/6822102

data/ibus.schemas.in
setup/keyboardshortcut.py
setup/main.py
setup/setup.ui
ui/gtk3/panel.vala

index a8c7d7f..dbb6da8 100644 (file)
       <default>[Control+space,Zenkaku_Hankaku,Alt+Kanji,Alt+grave,Hangul,Alt+Release+Alt_R]</default>
       <locale name="C">
         <short>Trigger shortcut keys</short>
-           <long>The shortcut keys for turning input method on or off</long>
+          <long>The shortcut keys for turning input method on or off</long>
+      </locale>
+    </schema>
+    <schema>
+      <key>/schemas/desktop/ibus/general/hotkey/triggers</key>
+      <applyto>/desktop/ibus/general/hotkey/triggers</applyto>
+      <owner>ibus</owner>
+      <type>list</type>
+      <list_type>string</list_type>
+      <default>[&lt;Control&gt;space]</default>
+      <locale name="C">
+        <short>Trigger shortcut keys for gtk_accelerator_parse</short>
+          <long>The shortcut keys for turning input method on or off</long>
       </locale>
     </schema>
     <schema>
index 7cc8649..2e8ca54 100644 (file)
@@ -102,9 +102,8 @@ class KeyboardShortcutSelection(Gtk.VBox):
         self.__modifier_buttons.append(("Hyper",
                                         Gtk.CheckButton.new_with_mnemonic("_Hyper"),
                                         Gdk.ModifierType.HYPER_MASK))
-        self.__modifier_buttons.append(("Capslock",
-                                        Gtk.CheckButton.new_with_mnemonic("Capsloc_k"),
-                                        Gdk.ModifierType.LOCK_MASK))
+        # <CapsLock> is not parsed by gtk_accelerator_parse()
+        # FIXME: Need to check if ibus gtk panel can enable <Release>.
         self.__modifier_buttons.append(("Release",
                                         Gtk.CheckButton.new_with_mnemonic("_Release"),
                                         Gdk.ModifierType.RELEASE_MASK))
@@ -118,7 +117,6 @@ class KeyboardShortcutSelection(Gtk.VBox):
         table.attach(self.__modifier_buttons[4][1], 0, 1, 1, 2)
         table.attach(self.__modifier_buttons[5][1], 1, 2, 1, 2)
         table.attach(self.__modifier_buttons[6][1], 2, 3, 1, 2)
-        table.attach(self.__modifier_buttons[7][1], 3, 4, 1, 2)
         hbox.pack_start(table, True, True, 4)
         self.pack_start(hbox, False, True, 4)
 
@@ -184,19 +182,20 @@ class KeyboardShortcutSelection(Gtk.VBox):
                 modifiers.append(name)
         if keycode.startswith("_"):
             keycode = keycode[1:]
-        keys = modifiers + [keycode]
-        shortcut = "+".join(keys)
+        shortcut = "".join(map(lambda m: '<' + m + '>', modifiers))
+        shortcut += keycode
         return shortcut
 
     def __set_shortcut_to_buttons(self, shortcut):
-        keys = shortcut.split("+")
-        mods = keys[:-1]
+        (keyval, state) = Gtk.accelerator_parse(shortcut)
+        if keyval == 0 and state == 0:
+            return
         for name, button, mask in self.__modifier_buttons:
-            if name in mods:
+            if state & mask:
                 button.set_active(True)
             else:
                 button.set_active(False)
-        self.__keycode_entry.set_text(keys[-1])
+        self.__keycode_entry.set_text(shortcut.rsplit('>', 1)[-1])
 
     def __get_selected_shortcut(self):
         model = self.__shortcut_view.get_model()
@@ -251,49 +250,52 @@ class KeyboardShortcutSelection(Gtk.VBox):
         message = _("Please press a key (or a key combination).\nThe dialog will be closed when the key is released.")
         dlg.set_markup(message)
         dlg.set_title(_("Please press a key (or a key combination)"))
-
-        def __key_press_event(d, k, out):
-            out.append(k.copy())
-
-        def __key_release_event(d, k, out):
-            d.response(Gtk.ResponseType.OK)
-
-        dlg.connect("key-press-event", __key_press_event, out)
-        dlg.connect("key-release-event", __key_release_event, None)
+        sw = Gtk.ScrolledWindow()
+
+        def __accel_edited_cb(c, path, keyval, state, keycode):
+            out.append(keyval)
+            out.append(state)
+            out.append(keycode)
+            dlg.response(Gtk.ResponseType.OK)
+
+        model = Gtk.ListStore(GObject.TYPE_INT,
+                              GObject.TYPE_UINT,
+                              GObject.TYPE_UINT)
+        accel_view = Gtk.TreeView(model)
+        sw.add(accel_view)
+        column = Gtk.TreeViewColumn()
+        renderer = Gtk.CellRendererAccel(accel_mode=Gtk.CellRendererAccelMode.OTHER,
+                                         editable=True)
+        renderer.connect('accel-edited', __accel_edited_cb)
+        column.pack_start(renderer, True)
+        column.add_attribute(renderer, 'accel-mods', 0)
+        column.add_attribute(renderer, 'accel-key', 1)
+        column.add_attribute(renderer, 'keycode', 2)
+        accel_view.append_column(column)
+        it = model.append(None)
+        area = dlg.get_message_area()
+        area.pack_end(sw, True, True, 0)
+        sw.show_all()
         id = dlg.run()
         dlg.destroy()
-        if id != Gtk.ResponseType.OK or not out:
+        if id != Gtk.ResponseType.OK or len(out) < 3:
             return
-        keyevent = out[len(out) - 1]
-        state = keyevent.state & (Gdk.ModifierType.CONTROL_MASK | \
-                                  Gdk.ModifierType.SHIFT_MASK   | \
-                                  Gdk.ModifierType.MOD1_MASK    | \
-                                  Gdk.ModifierType.META_MASK    | \
-                                  Gdk.ModifierType.SUPER_MASK   | \
-                                  Gdk.ModifierType.HYPER_MASK)
-
-
-        if state == 0:
-            state = state | Gdk.ModifierType.RELEASE_MASK
-        elif keyevent.keyval in (Gdk.KEY_Control_L, Gdk.KEY_Control_R) and state == Gdk.ModifierType.CONTROL_MASK:
-            state = state | Gdk.ModifierType.RELEASE_MASK
-        elif keyevent.keyval in (Gdk.KEY_Shift_L, Gdk.KEY_Shift_R) and state == Gdk.ModifierType.SHIFT_MASK:
-            state = state | Gdk.ModifierType.RELEASE_MASK
-        elif keyevent.keyval in (Gdk.KEY_Alt_L, Gdk.KEY_Alt_R) and state == Gdk.ModifierType.MOD1_MASK:
-            state = state | Gdk.ModifierType.RELEASE_MASK
-        elif keyevent.keyval in (Gdk.KEY_Meta_L, Gdk.KEY_Meta_R) and state == Gdk.ModifierType.META_MASK:
-            state = state | Gdk.ModifierType.RELEASE_MASK
-        elif keyevent.keyval in (Gdk.KEY_Super_L, Gdk.KEY_Super_R) and state == Gdk.ModifierType.SUPER_MASK:
-            state = state | Gdk.ModifierType.RELEASE_MASK
-        elif keyevent.keyval in (Gdk.KEY_Hyper_L, Gdk.KEY_Hyper_R) and state == Gdk.ModifierType.HYPER_MASK:
-            state = state | Gdk.ModifierType.RELEASE_MASK
+        keyval = out[0]
+        state = out[1]
+        keycode = out[2]
 
         for name, button, mask in self.__modifier_buttons:
             if state & mask:
                 button.set_active(True)
             else:
                 button.set_active(False)
-        self.__keycode_entry.set_text(Gdk.keyval_name(keyevent.keyval))
+
+        shortcut = Gtk.accelerator_name_with_keycode(None,
+                                                     keyval,
+                                                     keycode,
+                                                     state)
+        shortcut = shortcut.replace('<Primary>', '<Control>')
+        self.__keycode_entry.set_text(shortcut.rsplit('>', 1)[-1])
 
     def __add_button_clicked_cb(self, button):
         shortcut = self.__get_shortcut_from_buttons()
index f527da1..a8acc7a 100644 (file)
@@ -83,22 +83,23 @@ class Setup(object):
         self.__init_ui()
 
     def __init_hotkey(self):
-        default_values = {
-            "trigger" : (N_("trigger"), ["Control+space"]),
-            "enable_unconditional" : (N_("enable"), []),
-            "disable_unconditional" : (N_("disable"), [])
-        }
-
-        values = dict(self.__config.get_values("general/hotkey"))
-
-        for name, (label, shortcuts) in default_values.items():
-            shortcuts = values.get(name, shortcuts)
-            button = self.__builder.get_object("button_%s" % name)
-            entry = self.__builder.get_object("entry_%s" % name)
-            entry.set_text("; ".join(shortcuts))
-            entry.set_tooltip_text("\n".join(shortcuts))
-            button.connect("clicked", self.__shortcut_button_clicked_cb,
-                    label, "general/hotkey", name, entry)
+        name = 'triggers'
+        label = 'switch_engine'
+        variant = self.__config.get_value('general/hotkey', name)
+        if variant != None:
+            shortcuts = variant.dup_strv()
+        else:
+            shortcuts =  ['<Control>space']
+
+        button = self.__builder.get_object("button_%s" % label)
+        entry = self.__builder.get_object("entry_%s" % label)
+        entry.set_text("; ".join(shortcuts))
+        tooltip = "\n".join(shortcuts)
+        tooltip += "\n" + \
+            _("Use shortcut with shift to switch to the previous input method") 
+        entry.set_tooltip_text(tooltip)
+        button.connect("clicked", self.__shortcut_button_clicked_cb,
+                name, "general/hotkey", label, entry)
 
     def __init_panel(self):
         values = dict(self.__config.get_values("panel"))
@@ -375,7 +376,8 @@ class Setup(object):
     def __shortcut_button_clicked_cb(self, button, name, section, _name, entry):
         buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
                 Gtk.STOCK_OK, Gtk.ResponseType.OK)
-        title = _("Select keyboard shortcut for %s") %  _(name)
+        title = _("Select keyboard shortcut for %s") % \
+                _("switching input methods")
         dialog = keyboardshortcut.KeyboardShortcutSelectionDialog(buttons = buttons, title = title)
         text = entry.get_text()
         if text:
@@ -388,11 +390,13 @@ class Setup(object):
         dialog.destroy()
         if id != Gtk.ResponseType.OK:
             return
-        self.__config.set_value(section, _name, GLib.Variant.new_strv(shortcuts))
+        self.__config.set_value(section, name, GLib.Variant.new_strv(shortcuts))
         text = "; ".join(shortcuts)
         entry.set_text(text)
-        entry.set_tooltip_text(text)
-
+        tooltip = "\n".join(shortcuts)
+        tooltip += "\n" + \
+            _("Use shortcut with shift to switch to the previous input method") 
+        entry.set_tooltip_text(tooltip)
 
     def __item_started_column_toggled_cb(self, cell, path_str, model):
 
index 8121d62..04bb493 100644 (file)
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="label9">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="sensitive">False</property>
                                     <property name="can_focus">False</property>
                                     <property name="tooltip_text" translatable="yes">The shortcut keys for switching to previous input method in the list</property>
                                 </child>
                                 <child>
                                   <object class="GtkHBox" id="hbox4">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="spacing">6</property>
                                     <child>
                                     <property name="can_focus">False</property>
                                     <property name="spacing">6</property>
                                     <child>
-                                      <object class="GtkEntry" id="entry_next_engine">
+                                      <object class="GtkEntry" id="entry_switch_engine">
                                         <property name="visible">True</property>
                                         <property name="can_focus">True</property>
                                         <property name="editable">False</property>
                                       </packing>
                                     </child>
                                     <child>
-                                      <object class="GtkButton" id="button_next_engine">
+                                      <object class="GtkButton" id="button_switch_engine">
                                         <property name="label" translatable="yes">...</property>
                                         <property name="use_action_appearance">False</property>
                                         <property name="visible">True</property>
                                     <property name="right_attach">2</property>
                                     <property name="top_attach">3</property>
                                     <property name="bottom_attach">4</property>
+                                    <property name="y_options">GTK_FILL</property>
                                   </packing>
                                 </child>
                                 <child>
                                   <object class="GtkHBox" id="hbox6">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="spacing">6</property>
                                     <child>
                                       <object class="GtkEntry" id="entry_prev_engine">
-                                        <property name="visible">True</property>
+  >                                     <property name="no_show_all">True</property>
                                         <property name="sensitive">False</property>
                                         <property name="can_focus">True</property>
                                         <property name="editable">False</property>
                                       <object class="GtkButton" id="button_prev_engine">
                                         <property name="label" translatable="yes">...</property>
                                         <property name="use_action_appearance">False</property>
-                                        <property name="visible">True</property>
+                                        <property name="no_show_all">True</property>
                                         <property name="sensitive">False</property>
                                         <property name="can_focus">True</property>
                                         <property name="receives_default">False</property>
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="label7">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="tooltip_text" translatable="yes">The shortcut keys for turning input method on or off</property>
                                     <property name="xalign">0</property>
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="label18">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="xalign">0</property>
                                     <property name="label" translatable="yes">Enable:</property>
                                 </child>
                                 <child>
                                   <object class="GtkHBox" id="hbox2">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="spacing">6</property>
                                     <child>
                                 </child>
                                 <child>
                                   <object class="GtkLabel" id="label19">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="xalign">0</property>
                                     <property name="label" translatable="yes">Disable:</property>
                                 </child>
                                 <child>
                                   <object class="GtkHBox" id="hbox3">
-                                    <property name="visible">True</property>
+                                    <property name="no_show_all">True</property>
                                     <property name="can_focus">False</property>
                                     <property name="spacing">6</property>
                                     <child>
index 3df0be7..24e6b2e 100644 (file)
  */
 
 class Panel : IBus.PanelService {
+    private class Keybinding {
+        public Keybinding(uint keysym,
+                          Gdk.ModifierType modifiers,
+                          bool reverse) {
+            this.keysym = keysym;
+            this.modifiers = modifiers;
+            this.reverse = reverse;
+        }
+
+        public uint keysym { get; set; }
+        public Gdk.ModifierType modifiers { get; set; }
+        public bool reverse { get; set; }
+    }
+
     private IBus.Bus m_bus;
     private IBus.Config m_config;
     private Gtk.StatusIcon m_status_icon;
@@ -36,8 +50,7 @@ class Panel : IBus.PanelService {
     private int m_switcher_delay_time = 400;
     private const string ACCELERATOR_SWITCH_IME_FOREWARD = "<Control>space";
 
-    private uint m_switch_keysym = 0;
-    private Gdk.ModifierType m_switch_modifiers = 0;
+    private GLib.List<Keybinding> m_keybindings = new GLib.List<Keybinding>();
 
     public Panel(IBus.Bus bus) {
         GLib.assert(bus.is_connected());
@@ -60,7 +73,8 @@ class Panel : IBus.PanelService {
         m_candidate_panel.page_down.connect((w) => this.page_down());
 
         m_switcher = new Switcher();
-        bind_switch_shortcut();
+        // The initial shortcut is "<Control>space"
+        bind_switch_shortcut(null);
 
         if (m_switcher_delay_time >= 0) {
             m_switcher.set_popup_delay_time((uint) m_switcher_delay_time);
@@ -78,62 +92,101 @@ class Panel : IBus.PanelService {
         unbind_switch_shortcut();
     }
 
-    private void bind_switch_shortcut() {
-        var keybinding_manager = KeybindingManager.get_instance();
+    private void keybinding_manager_bind(KeybindingManager keybinding_manager,
+                                         string?           accelerator) {
+        uint switch_keysym = 0;
+        Gdk.ModifierType switch_modifiers = 0;
+        Gdk.ModifierType reverse_modifier = Gdk.ModifierType.SHIFT_MASK;
+        Keybinding keybinding;
 
-        var accelerator = ACCELERATOR_SWITCH_IME_FOREWARD;
         Gtk.accelerator_parse(accelerator,
-                out m_switch_keysym, out m_switch_modifiers);
+                out switch_keysym, out switch_modifiers);
 
-        // Map virtual modifiers to (i.e.Mod2, Mod3, ...)
+        // Map virtual modifiers to (i.e. Mod2, Mod3, ...)
         const Gdk.ModifierType VIRTUAL_MODIFIERS = (
                 Gdk.ModifierType.SUPER_MASK |
                 Gdk.ModifierType.HYPER_MASK |
                 Gdk.ModifierType.META_MASK);
-        if ((m_switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
+        if ((switch_modifiers & VIRTUAL_MODIFIERS) != 0) {
         // workaround a bug in gdk vapi vala > 0.18
         // https://bugzilla.gnome.org/show_bug.cgi?id=677559
 #if VALA_0_18
             Gdk.Keymap.get_default().map_virtual_modifiers(
-                    ref m_switch_modifiers);
+                    ref switch_modifiers);
 #else
-            if ((m_switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
-                m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
-            if ((m_switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
-                m_switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
+            if ((switch_modifiers & Gdk.ModifierType.SUPER_MASK) != 0)
+                switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
+            if ((switch_modifiers & Gdk.ModifierType.HYPER_MASK) != 0)
+                switch_modifiers |= Gdk.ModifierType.MOD4_MASK;
 #endif
-            m_switch_modifiers &= ~VIRTUAL_MODIFIERS;
+            switch_modifiers &= ~VIRTUAL_MODIFIERS;
         }
 
-        if (m_switch_keysym == 0 && m_switch_modifiers == 0) {
+        if (switch_keysym == 0 && switch_modifiers == 0) {
             warning("Parse accelerator '%s' failed!", accelerator);
             return;
         }
 
-        keybinding_manager.bind(m_switch_keysym, m_switch_modifiers,
+        keybinding = new Keybinding(switch_keysym,
+                                    switch_modifiers,
+                                    false);
+        m_keybindings.append(keybinding);
+
+        keybinding_manager.bind(switch_keysym, switch_modifiers,
                 (e) => handle_engine_switch(e, false));
 
         // accelerator already has Shift mask
-        if ((m_switch_modifiers & Gdk.ModifierType.SHIFT_MASK) != 0)
+        if ((switch_modifiers & reverse_modifier) != 0) {
             return;
+        }
+
+        switch_modifiers |= reverse_modifier;
 
-        keybinding_manager.bind(m_switch_keysym,
-                m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK,
+        keybinding = new Keybinding(switch_keysym,
+                                    switch_modifiers,
+                                    true);
+        m_keybindings.append(keybinding);
+
+        keybinding_manager.bind(switch_keysym, switch_modifiers,
                 (e) => handle_engine_switch(e, true));
     }
 
+    private void bind_switch_shortcut(Variant? variant) {
+        string[] accelerators = {};
+        Variant var_trigger = variant;
+
+        if (var_trigger == null && m_config != null) {
+            var_trigger = m_config.get_value("general/hotkey",
+                                             "triggers");
+        }
+
+        if (var_trigger != null) {
+            accelerators = var_trigger.dup_strv();
+        } else {
+            accelerators += ACCELERATOR_SWITCH_IME_FOREWARD;
+        }
+
+        var keybinding_manager = KeybindingManager.get_instance();
+
+        foreach (var accelerator in accelerators) {
+            keybinding_manager_bind(keybinding_manager, accelerator);
+        }
+    }
+
     private void unbind_switch_shortcut() {
         var keybinding_manager = KeybindingManager.get_instance();
 
-        if (m_switch_keysym == 0 && m_switch_modifiers == 0)
-            return;
+        unowned GLib.List<Keybinding> keybindings = m_keybindings;
 
-        keybinding_manager.unbind(m_switch_keysym, m_switch_modifiers);
-        keybinding_manager.unbind(m_switch_keysym,
-                m_switch_modifiers | Gdk.ModifierType.SHIFT_MASK);
+        while (keybindings != null) {
+            Keybinding keybinding = keybindings.data;
 
-        m_switch_keysym = 0;
-        m_switch_modifiers = 0;
+            keybinding_manager.unbind(keybinding.keysym,
+                                      keybinding.modifiers);
+            keybindings = keybindings.next;
+        }
+
+        m_keybindings = null;
     }
 
     private void set_custom_font() {
@@ -225,10 +278,13 @@ class Panel : IBus.PanelService {
             m_config.watch("general", "preload_engines");
             m_config.watch("general", "engines_order");
             m_config.watch("general", "switcher_delay_time");
+            m_config.watch("general/hotkey", "triggers");
             m_config.watch("panel", "custom_font");
             m_config.watch("panel", "use_custom_font");
             update_engines(m_config.get_value("general", "preload_engines"),
                            m_config.get_value("general", "engines_order"));
+            unbind_switch_shortcut();
+            bind_switch_shortcut(null);
             set_switcher_delay_time(null);
         } else {
             update_engines(null, null);
@@ -308,6 +364,12 @@ class Panel : IBus.PanelService {
             return;
         }
 
+        if (section == "general/hotkey" && name == "triggers") {
+            unbind_switch_shortcut();
+            bind_switch_shortcut(variant);
+            return;
+        }
+
         if (section == "panel" && (name == "custom_font" ||
                                    name == "use_custom_font")) {
             set_custom_font();
@@ -316,6 +378,7 @@ class Panel : IBus.PanelService {
 
         if (section == "general" && name == "switcher_delay_time") {
             set_switcher_delay_time(variant);
+            return;
         }
     }
 
@@ -324,15 +387,22 @@ class Panel : IBus.PanelService {
         if (m_engines.length <= 1)
             return;
 
+        uint keyval = event.key.keyval;
+        uint modifiers = KeybindingManager.MODIFIER_FILTER & event.key.state;
+
         uint primary_modifiers =
             KeybindingManager.get_primary_modifier(event.key.state);
 
         bool pressed = KeybindingManager.primary_modifier_still_pressed(
                 event, primary_modifiers);
+
+        if (revert) {
+            modifiers &= ~Gdk.ModifierType.SHIFT_MASK;
+        }
+
         if (pressed && m_switcher_delay_time >= 0) {
             int i = revert ? m_engines.length - 1 : 1;
-            i = m_switcher.run(m_switch_keysym, m_switch_modifiers, event,
-                    m_engines, i);
+            i = m_switcher.run(keyval, modifiers, event, m_engines, i);
             if (i < 0) {
                 debug("switch cancelled");
             } else {