← Back to team overview

gtg team mailing list archive

[Merge] lp:~parinporecha/gtg/final_global_shortcut into lp:gtg

 

Parin Porecha has proposed merging lp:~parinporecha/gtg/final_global_shortcut into lp:gtg.

Requested reviews:
  Gtg developers (gtg)

For more details, see:
https://code.launchpad.net/~parinporecha/gtg/final_global_shortcut/+merge/151566

This is a fix for the Bug #967607 - System level global shortcut key for quick adding task.
Plz suggest any improvements or problems with the patch.
-- 
https://code.launchpad.net/~parinporecha/gtg/final_global_shortcut/+merge/151566
Your team Gtg developers is requested to review the proposed merge of lp:~parinporecha/gtg/final_global_shortcut into lp:gtg.
=== modified file 'GTG/gtk/preferences.glade'
--- GTG/gtk/preferences.glade	2012-08-08 14:56:18 +0000
+++ GTG/gtk/preferences.glade	2013-03-04 17:25:24 +0000
@@ -1,6 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <interface>
   <requires lib="gtk+" version="2.16"/>
+  <!-- interface-naming-policy toplevel-contextual -->
   <object class="GtkDialog" id="PreferencesDialog">
     <property name="can_focus">False</property>
     <property name="border_width">3</property>
@@ -8,7 +9,7 @@
     <property name="type_hint">dialog</property>
     <signal name="delete-event" handler="on_PreferencesDialog_delete_event" swapped="no"/>
     <child internal-child="vbox">
-      <object class="GtkBox" id="prefs-vbox1">
+      <object class="GtkVBox" id="prefs-vbox1">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
         <child>
@@ -53,6 +54,40 @@
                                 <property name="position">0</property>
                               </packing>
                             </child>
+                            <child>
+                              <object class="GtkHBox" id="hbox1">
+                                <property name="height_request">21</property>
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="spacing">2</property>
+                                <child>
+                                  <object class="GtkCheckButton" id="shortcut_button">
+                                    <property name="label" translatable="yes">New Task Shortcut :</property>
+                                    <property name="visible">True</property>
+                                    <property name="can_focus">True</property>
+                                    <property name="receives_default">False</property>
+                                    <property name="use_action_appearance">False</property>
+                                    <property name="relief">none</property>
+                                    <property name="draw_indicator">True</property>
+                                    <signal name="toggled" handler="on_shortcut_button_toggled" swapped="no"/>
+                                  </object>
+                                  <packing>
+                                    <property name="expand">False</property>
+                                    <property name="fill">True</property>
+                                    <property name="position">0</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <placeholder/>
+                                </child>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="padding">3</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
                           </object>
                         </child>
                       </object>
@@ -62,7 +97,7 @@
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="ypad">5</property>
-                        <property name="label" translatable="yes">&lt;b&gt;Startup&lt;/b&gt;</property>
+                        <property name="label" translatable="yes">&lt;b&gt;System&lt;/b&gt;</property>
                         <property name="use_markup">True</property>
                       </object>
                     </child>
@@ -255,7 +290,7 @@
           </packing>
         </child>
         <child internal-child="action_area">
-          <object class="GtkButtonBox" id="prefs-action_area">
+          <object class="GtkHButtonBox" id="prefs-action_area">
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <property name="layout_style">end</property>

=== modified file 'GTG/gtk/preferences.py'
--- GTG/gtk/preferences.py	2013-02-25 08:29:31 +0000
+++ GTG/gtk/preferences.py	2013-03-04 17:25:24 +0000
@@ -28,6 +28,8 @@
 from GTG import _
 from GTG import info
 from GTG.gtk import ViewConfig
+from GTG.tools.shortcut import *
+
 
 AUTOSTART_DIRECTORY = os.path.join(xdg_config_home, "autostart")
 AUTOSTART_FILE = "gtg.desktop"
@@ -42,7 +44,7 @@
     desktop_file_path = None
     this_directory = os.path.dirname(os.path.abspath(__file__))
     for path in ["../..", "../../../applications",
-                 "../../../../../share/applications"]:
+                "../../../../../share/applications"]:
         fullpath = os.path.join(this_directory, path, AUTOSTART_FILE)
         fullpath = os.path.normpath(fullpath)
         if os.path.isfile(fullpath):
@@ -55,11 +57,11 @@
 
         # If the path is a symlink and is broken, remove it
         if os.path.islink(AUTOSTART_PATH) and \
-                not os.path.exists(os.path.realpath(AUTOSTART_PATH)):
+            not os.path.exists(os.path.realpath(AUTOSTART_PATH)):
             os.unlink(AUTOSTART_PATH)
 
         if os.path.isdir(AUTOSTART_DIRECTORY) and \
-                not os.path.exists(AUTOSTART_PATH):
+           not os.path.exists(AUTOSTART_PATH):
             if hasattr(os, "symlink"):
                 os.symlink(desktop_file_path, AUTOSTART_PATH)
             else:
@@ -86,6 +88,11 @@
         self.pref_autostart = builder.get_object("pref_autostart")
         self.pref_show_preview = builder.get_object("pref_show_preview")
         self.bg_color_enable = builder.get_object("bg_color_enable")
+        self.hbox1 = builder.get_object("hbox1")
+        self.shortcut_button = builder.get_object("shortcut_button")
+
+        self.shortcut = ShortcutWidget(self.dialog, self.hbox1,
+                                       self.shortcut_button)
 
         self.fontbutton = builder.get_object("fontbutton")
         editor_font = self.config.get("font_name")
@@ -95,27 +102,31 @@
         self.fontbutton.set_font_name(editor_font)
 
         builder.connect_signals({
-                                'on_pref_autostart_toggled':
-                                self.on_autostart_toggled,
-                                'on_pref_show_preview_toggled':
-                                self.toggle_preview,
-                                'on_bg_color_toggled':
-                                self.on_bg_color_toggled,
-                                'on_prefs_help':
-                                self.on_help,
-                                'on_prefs_close':
-                                self.on_close,
-                                'on_PreferencesDialog_delete_event':
-                                self.on_close,
-                                'on_fontbutton_font_set':
-                                self.on_font_change,
-                                })
+          'on_pref_autostart_toggled':
+            self.on_autostart_toggled,
+          'on_pref_show_preview_toggled':
+            self.toggle_preview,
+          'on_bg_color_toggled':
+            self.on_bg_color_toggled,
+          'on_prefs_help':
+            self.on_help,
+          'on_prefs_close':
+            self.on_close,
+          'on_PreferencesDialog_delete_event':
+            self.on_close,
+          'on_fontbutton_font_set':
+            self.on_font_change,
+          'on_shortcut_button_toggled':
+            self.shortcut.on_shortcut_toggled,
+        })
 
-    def _refresh_preferences_store(self):
+    def  _refresh_preferences_store(self):
         """ Sets the correct value in the preferences checkboxes """
         has_autostart = os.path.isfile(AUTOSTART_PATH)
         self.pref_autostart.set_active(has_autostart)
 
+        self.shortcut.refresh_accel()
+
         show_preview = self.config.get("contents_preview_enable")
         self.pref_show_preview.set_active(show_preview)
 
@@ -132,13 +143,13 @@
         self._refresh_preferences_store()
         self.dialog.show_all()
 
-    def on_close(self, widget, data=None):
+    def on_close(self, widget, data=None): # pylint: disable-msg=W0613
         """ Close the preferences dialog."""
         self.dialog.hide()
         return True
 
     @classmethod
-    def on_help(cls, widget):
+    def on_help(cls, widget): # pylint: disable-msg=W0613
         """ In future, this will open help for preferences """
         return True
 
@@ -167,3 +178,91 @@
     def on_font_change(self, widget):
         """ Set a new font for editor """
         self.config.set("font_name", self.fontbutton.get_font_name())
+
+
+class ShortcutWidget:
+    """ Show Shortcut Accelerator Widget """
+
+    def __init__(self, dialog, hbox1, button1):
+        self.dialog = dialog
+        self.hbox1 = hbox1
+        self.button = button1
+        self.new_task_default_binding = "<Primary>F12"
+
+        self.liststore = gtk.ListStore(str, str)
+        self.liststore.append(["", ""])
+        treeview = gtk.TreeView(self.liststore)
+        column_accel = gtk.TreeViewColumn()
+        treeview.append_column(column_accel)
+        treeview.set_headers_visible(False)
+
+        cell = gtk.CellRendererAccel()
+        cell.set_alignment(0.0, 1.0)
+        cell.set_fixed_size(-1, 18)
+        cell.set_property("accel-mode", gtk.CELL_RENDERER_ACCEL_MODE_OTHER)
+        cell.set_property("editable", True)
+        cell.connect("accel-edited", self._cellAccelEdit, self.liststore)
+        cell.connect("accel-cleared", self._accel_cleared, self.liststore)
+        column_accel.pack_start(cell, True)
+        column_accel.add_attribute(cell, "text", 1)
+        self.hbox1.add(treeview)
+
+    def refresh_accel(self):
+        """ Refreshes the accelerator """
+        iter1 = self.liststore.get_iter_first()
+        self.new_task_binding = get_saved_binding()
+        self.binding_backup = self.new_task_binding
+        if self.new_task_binding == "":
+            # User had set a shortcut, but has now disabled it
+            self.button.set_active(False)
+            self.liststore.set_value(iter1, 1, "Disabled")
+            return
+        elif self.new_task_binding == None:
+            # User hasn't set a shortcut ever
+            self.button.set_active(False)
+            self.new_task_binding = self.new_task_default_binding
+            self.binding_backup = self.new_task_binding
+        else:
+            # There exists a shortcut
+            self.button.set_active(True)
+        (accel_key, accel_mods) = gtk.accelerator_parse(self.new_task_binding)
+        self.show_input = gtk.accelerator_get_label(accel_key, accel_mods)
+        self.liststore.set_value(iter1, 1, self.show_input)
+
+    def on_shortcut_toggled(self, widget):
+        """ New task shortcut checkbox is toggled """
+        if widget.get_active() == True:
+            self.new_task_binding = self.binding_backup
+            on_shortcut_change(self.new_task_binding, True)
+        else:
+            self.new_task_binding = ""
+            on_shortcut_change(self.new_task_binding, True)
+
+    def _cellAccelEdit(self, cell, path, accel_key, accel_mods, code, model):
+        """ Accelerator is modified """
+        self.show_input = gtk.accelerator_get_label(accel_key, accel_mods)
+        self.new_task_binding = gtk.accelerator_name(accel_key, accel_mods)
+        if check_invalidity(self.new_task_binding, accel_key, accel_mods):
+            self._show_warning(gtk.Button("Warning"), self.show_input)
+            return
+        self.binding_backup = self.new_task_binding
+        iter = model.get_iter(path)
+        model.set_value(iter, 1, self.show_input)
+        on_shortcut_change(self.new_task_binding, self.button.get_active())
+
+    def _accel_cleared(self, widget, path, model):
+        """ Clear the accelerator """
+        iter = model.get_iter(path)
+        model.set_value(iter, 1, None)
+
+    def _show_warning(self, widget, input_str):
+        """ Show warning when user enters inappropriate accelerator """
+        str1 = "The shortcut \"" + input_str + "\" cannot be used because "
+        str1 = str1 + "it will become impossible to type using this key.\n"
+        str1 = str1 + "Please try with a key such as "
+        show = str1 + "Control, Alt or Shift at the same time."
+        dialog = gtk.MessageDialog(self.dialog, gtk.DIALOG_DESTROY_WITH_PARENT,
+                                   gtk.MESSAGE_WARNING, gtk.BUTTONS_CANCEL,
+                                   show)
+        response = dialog.run()
+        dialog.hide()

=== added file 'GTG/tools/shortcut.py'
--- GTG/tools/shortcut.py	1970-01-01 00:00:00 +0000
+++ GTG/tools/shortcut.py	2013-03-04 17:25:24 +0000
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+# -----------------------------------------------------------------------------
+# Getting Things GNOME! - a personal organizer for the GNOME desktop
+# Copyright (c) 2008-2012 - Lionel Dricot & Bertrand Rousseau
+#
+# 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 3 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 <http://www.gnu.org/licenses/>.
+# -----------------------------------------------------------------------------
+
+import subprocess
+import re
+
+list_prefix = "gsettings list-keys "
+CHECK_VERSION = list_prefix + "org.gnome.settings-daemon.plugins.media-keys"
+NEW_TASK_ACTION = "gtg_new_task"
+NEW_TASK_NAME = "GTG New Task"
+get_temp = "gsettings get "
+path = "org.gnome.settings-daemon.plugins.media-keys "
+GSETTINGS_GET_LIST = get_temp + path + "custom-keybindings"
+set_temp = "gsettings set "
+GSETTINGS_SET_LIST = set_temp + path + "custom-keybindings"
+key_path1 = "org.gnome.settings-daemon.plugins.media-keys.custom-keybinding:"
+path2 = "/org/gnome/settings-daemon/plugins/media-keys/custom-keybindings/"
+GSETTINGS_GET = get_temp + key_path1 + path2 + "custom"
+GSETTINGS_SET = set_temp + key_path1 + path2 + "custom"
+GCONF_GET = "gconftool-2 --get /desktop/gnome/keybindings/custom"
+GCONF_SET = "gconftool-2 --type string --set /desktop/gnome/keybindings/custom"
+
+
+def get_saved_binding():
+    """ Get the current shortcut if the task exists """
+    list_keys = call_subprocess(cmd = CHECK_VERSION)
+    list_keys = list_keys.splitlines()
+    if "custom-keybindings" in list_keys:
+        binding = get_shortcut_from_dconf()
+    else:
+        binding = get_shortcut_from_gconf()
+    return binding
+
+
+def get_shortcut_from_dconf():
+    """ If system uses dconf, then get the shortcut via gsettings """
+    dconf_out = call_subprocess(cmd = GSETTINGS_GET_LIST)
+    custom_shortcuts_list = re.findall(r'custom[0-9]+', dconf_out)
+
+    for entry in custom_shortcuts_list:
+        to_access = entry[-1]
+        get_cmd = call_subprocess(cmd = GSETTINGS_GET, i = str(to_access),
+                                  key = "/ command")
+
+        if NEW_TASK_ACTION in get_cmd:
+            get_bind = call_subprocess(cmd = GSETTINGS_GET, i = str(to_access),
+                                       key = "/ binding")
+            return get_bind.rstrip("\n")
+    return None
+
+
+def get_shortcut_from_gconf():
+    """ If system uses gconf, then get the shortcut via gconftool-2 """
+    item=0
+    while(1):
+        get_action = call_subprocess(cmd = GCONF_GET, i = str(item),
+                                     key = "/action")
+
+        if NEW_TASK_ACTION in get_action:
+            get_bind = call_subprocess(cmd = GCONF_GET, i = str(item),
+                                       key = "/binding")
+            return get_bind.rstrip("\n")
+
+        elif get_action == "":
+            get_name = call_subprocess(cmd = GCONF_GET, i = str(item),
+                                       key = "/name")
+            if get_name == "":
+                return None
+    item += 1
+
+
+def on_shortcut_change(binding, button_state):
+    """ When user has entered a new shortcut """
+    if button_state == True:
+        list_keys = call_subprocess(cmd = CHECK_VERSION)
+        list_keys = list_keys.splitlines()
+        if "custom-keybindings" in list_keys:
+            add_shortcut_to_dconf(binding)
+        else:
+            add_shortcut_to_gconf(binding)
+
+
+def add_shortcut_to_dconf(binding):
+    """ If system uses dconf, then set the new shortcut via gsettings """
+    dconf_out = call_subprocess(cmd = GSETTINGS_GET_LIST)
+    custom_keys = re.findall(r'custom[0-9]+', dconf_out)
+
+    for entry in custom_keys:
+        to_access = entry[-1]
+        get_cmd = call_subprocess(cmd = GSETTINGS_GET, i = str(to_access),
+                                  key = "/ command")
+
+        if NEW_TASK_ACTION in get_cmd:
+            call_subprocess(cmd = GSETTINGS_SET, to_append = binding,
+                            i = str(to_access), key = "/ binding")
+            return
+
+    a=[]
+    for item in custom_keys:
+        a.append(int(re.findall(r'[0-9]+', item)[0]))
+    if a == []:
+        index = 0
+    else:
+        a.sort()
+        index = a[-1] + 1
+    prefix = "['/org/gnome/settings-daemon/plugins/media-keys/"
+    prefix = prefix + "custom-keybindings/custom"
+    append_this = prefix + str(index) + "/']"
+    call_subprocess(cmd = GSETTINGS_SET, to_append = NEW_TASK_ACTION,
+                    i = str(index), key = "/ command")
+    call_subprocess(cmd = GSETTINGS_SET, to_append = binding,
+                    i = str(index), key = "/ binding")
+    call_subprocess(cmd = GSETTINGS_SET, to_append = NEW_TASK_NAME,
+                    i = str(index), key = "/ name")
+
+    if index == 0:
+        call_subprocess(cmd = GSETTINGS_SET_LIST, key = ' ' + append_this)
+
+    else:
+        result_list = dconf_out[:-2] + ", "
+        result_list = result_list + append_this[1:-1] + "]"
+        call_subprocess(cmd = GSETTINGS_SET_LIST,
+                        to_append = result_list)
+
+
+def add_shortcut_to_gconf(binding):
+    """ If system uses gconf, then set the new shortcut via gconftool-2 """
+    item=0
+    while(1):
+        get_action = call_subprocess(cmd = GCONF_GET, i = str(item),
+                                     key = "/action")
+
+        if NEW_TASK_ACTION in get_action:
+            binding_ans = call_subprocess(cmd = GCONF_SET,
+                                          to_append = binding, i = str(item),
+                                          key = "/binding")
+            break
+
+        if get_action == "":
+            call_subprocess(cmd = GCONF_SET, to_append = NEW_TASK_ACTION,
+                            i = str(item), key = "/action")
+            call_subprocess(cmd = GCONF_SET, to_append = binding,
+                            i = str(item), key = "/binding")
+            call_subprocess(cmd = GCONF_SET, to_append = NEW_TASK_NAME,
+                            i = str(item), key = "/name")
+            break
+    item += 1
+
+
+def call_subprocess(cmd, i = "", key = "", to_append = None):
+    """ Sets the values in either dconf or gconf.
+        'cmd' holds the command to access the configuration database,
+        'i' accesses a custom shortcut,
+        'key' accesses values inside the shortcut,
+        'to_append' contains the new value to replace if any """
+    cmd = cmd + i + key
+    cmd = cmd.split(" ")
+    if to_append != None:
+        cmd.append(to_append)
+    out = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+                           stderr=subprocess.PIPE)
+    return out.communicate()[0]
+
+
+def check_invalidity(binding, key, mods):
+    """ Checks if the user has entered inappropriate shortcut """
+    if mods == 0:
+        if (key >= 97 and key <= 122):
+            # key is an alphabet
+            return 1
+        elif (key >= 48 and key <= 57):
+            # key is a number
+            return 1
+        elif (key >= 65361 and key <= 65364):
+            # key is one of the arrow keys
+            return 1
+        elif key == 32:
+            # key is 'space'
+            return 1
+    return 0