Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bin/sugar-session47
-rw-r--r--configure.ac1
-rw-r--r--data/icons/Makefile.am1
-rw-r--r--data/icons/module-keyboard.svg134
-rw-r--r--data/sugar.schemas.in37
-rw-r--r--extensions/cpsection/Makefile.am2
-rw-r--r--extensions/cpsection/keyboard/Makefile.am6
-rw-r--r--extensions/cpsection/keyboard/__init__.py22
-rw-r--r--extensions/cpsection/keyboard/model.py156
-rw-r--r--extensions/cpsection/keyboard/view.py418
-rw-r--r--po/POTFILES.in1
-rw-r--r--src/jarabe/controlpanel/gui.py5
12 files changed, 829 insertions, 1 deletions
diff --git a/bin/sugar-session b/bin/sugar-session
index 2d9bb98..c891bdb 100644
--- a/bin/sugar-session
+++ b/bin/sugar-session
@@ -131,6 +131,47 @@ def setup_file_transfer_cb():
from jarabe.model import filetransfer
filetransfer.init()
+def setup_keyboard_cb(gconf_client):
+ logging.debug('STARTUP: setup_keyboard_cb')
+ try:
+ display = gtk.gdk.display_get_default()
+ if display is not None:
+ engine = xklavier.Engine(display)
+ else:
+ logging.debug('setup_keyboard_cb: Could not get default display.')
+ return
+
+ configrec = xklavier.ConfigRec()
+ configrec.get_from_server(engine)
+
+ layouts = gconf_client.get_list(\
+ '/desktop/sugar/peripherals/keyboard/layouts', gconf.VALUE_STRING)
+ layouts_list = []
+ variants_list = []
+ for layout in layouts:
+ layouts_list.append(layout.split('(')[0])
+ variants_list.append(layout.split('(')[1][:-1])
+
+ if layouts_list is not None and layouts_list is not [] \
+ and variants_list is not None and variants_list is not []:
+ configrec.set_layouts(layouts_list)
+ configrec.set_variants(variants_list)
+
+ model = gconf_client.get_string(\
+ '/desktop/sugar/peripherals/keyboard/model')
+ if model is not None:
+ configrec.set_model(model)
+
+ options = gconf_client.get_list(\
+ '/desktop/sugar/peripherals/keyboard/options', gconf.VALUE_STRING)
+ if options is not [] and options is not None:
+ configrec.set_options(options)
+
+ configrec.activate(engine)
+ except:
+ logging.exception('Error during keyboard configuration')
+ pass
+
def main():
cleanup_logs()
logger.start('shell')
@@ -170,6 +211,12 @@ def main():
gobject.idle_add(show_software_updates_cb)
try:
+ import xklavier
+ gobject.idle_add(setup_keyboard_cb, client)
+ except ImportError:
+ logging.debug('Could not load xklavier for keyboard configuration')
+
+ try:
gtk.main()
except KeyboardInterrupt:
print 'Ctrl+C pressed, exiting...'
diff --git a/configure.ac b/configure.ac
index e7a6fb1..c781cc6 100644
--- a/configure.ac
+++ b/configure.ac
@@ -58,6 +58,7 @@ extensions/cpsection/aboutme/Makefile
extensions/cpsection/aboutcomputer/Makefile
extensions/cpsection/datetime/Makefile
extensions/cpsection/frame/Makefile
+extensions/cpsection/keyboard/Makefile
extensions/cpsection/language/Makefile
extensions/cpsection/network/Makefile
extensions/cpsection/power/Makefile
diff --git a/data/icons/Makefile.am b/data/icons/Makefile.am
index e1f8fa7..19ad842 100644
--- a/data/icons/Makefile.am
+++ b/data/icons/Makefile.am
@@ -5,6 +5,7 @@ sugar_DATA = \
module-about_my_computer.svg \
module-date_and_time.svg \
module-frame.svg \
+ module-keyboard.svg \
module-language.svg \
module-network.svg \
module-power.svg
diff --git a/data/icons/module-keyboard.svg b/data/icons/module-keyboard.svg
new file mode 100644
index 0000000..43bbc57
--- /dev/null
+++ b/data/icons/module-keyboard.svg
@@ -0,0 +1,134 @@
+<?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#666666">
+ <!ENTITY fill_color "#ffffff">
+]><svg enable-background="new 0 0 55 55" height="55px" id="Layer_1" version="1.1" viewBox="0 0 55 55" width="55px" x="0px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" y="0px"><g display="block" id="module-keyboard">
+ <rect display="inline" fill="&fill_color;" height="23.326" width="54" x="0.5" y="15.837"/>
+ <g display="inline">
+ <path d="M52.61,28.081c0,0.55-0.45,1-1,1h-5.443c-0.55,0-1-0.45-1-1v-5.444c0-0.55,0.45-1,1-1h5.443c0.55,0,1,0.45,1,1V28.081z"/>
+ </g>
+ <g display="inline">
+ <path d="M52.61,19.941c0,0.55-0.45,1-1,1h-5.412c-0.55,0-1-0.45-1-1v-1.368c0-0.55,0.45-1,1-1h5.412c0.55,0,1,0.45,1,1V19.941z"/>
+ </g>
+ <g display="inline">
+ <path d="M52.61,32.267c0,0.55-0.45,1-1,1h-5.412c-0.55,0-1-0.45-1-1v-1.367c0-0.55,0.45-1,1-1h5.412c0.55,0,1,0.45,1,1V32.267z"/>
+ </g>
+ <g display="inline">
+ <path d="M39.11,36.403c0,0.55-0.45,1-1,1H13.176c-0.55,0-1-0.45-1-1v-1.366c0-0.55,0.45-1,1-1H38.11c0.55,0,1,0.45,1,1V36.403z"/>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,19.977c0,0.55-0.45,1-1,1H3.549c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M10.65,19.977c0,0.55-0.45,1-1,1H8.311c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M15.411,19.977c0,0.55-0.45,1-1,1h-1.339c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M20.173,19.978c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V19.978z"/>
+ </g>
+ <g>
+ <path d="M24.934,19.977c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M29.695,19.978c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V19.978z"/>
+ </g>
+ <g>
+ <path d="M34.457,19.977c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M39.218,19.977c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ <g>
+ <path d="M43.979,19.977c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V19.977z"/>
+ </g>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,24.038c0,0.55-0.45,1-1,1H3.549c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M10.65,24.038c0,0.55-0.45,1-1,1H8.311c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M15.411,24.038c0,0.55-0.45,1-1,1h-1.339c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M20.173,24.038c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M24.934,24.038c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M29.695,24.038c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M34.457,24.038c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M39.218,24.038c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ <g>
+ <path d="M43.979,24.038c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.339c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V24.038z"/>
+ </g>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,28.099c0,0.55-0.45,1-1,1.001H3.549c-0.55,0.001-1-0.449-1-0.999V26.76c0-0.55,0.45-1,1-1h1.339 c0.55,0,1,0.45,1,1V28.099z"/>
+ </g>
+ <g>
+ <path d="M10.65,28.099c0,0.55-0.45,1-1,1.001H8.311c-0.55,0.001-1-0.449-1-0.999V26.76c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M15.411,28.099c0,0.55-0.45,1-1,1L13.072,29.1c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M20.173,28.099c0,0.55-0.45,1-1,1l-1.34,0.001c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M24.934,28.099c0,0.55-0.45,1-1,1l-1.34,0.001c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M29.695,28.099c0,0.55-0.45,1-1,1l-1.34,0.001c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M34.457,28.099c0,0.55-0.45,1-1,1L32.116,29.1c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1 V28.099z"/>
+ </g>
+ <g>
+ <path d="M39.218,28.099c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1V26.76c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V28.099z"/>
+ </g>
+ <g>
+ <path d="M43.979,28.099c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1V26.76c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V28.099z"/>
+ </g>
+ </g>
+ <g display="inline">
+ <g>
+ <path d="M5.889,32.161c0,0.55-0.45,1-1,1H3.549c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M10.65,32.161c0,0.55-0.45,1-1,1H8.311c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1H9.65c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M15.411,32.161c0,0.55-0.45,1-1,1h-1.339c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.339c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M20.173,32.161c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M24.934,32.161c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M29.695,32.161c0,0.55-0.45,1-1,1h-1.34c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.34c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M34.457,32.161c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M39.218,32.161c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ <g>
+ <path d="M43.979,32.161c0,0.55-0.45,1-1,1h-1.341c-0.55,0-1-0.45-1-1v-1.34c0-0.55,0.45-1,1-1h1.341c0.55,0,1,0.45,1,1V32.161z"/>
+ </g>
+ </g>
+</g></svg> \ No newline at end of file
diff --git a/data/sugar.schemas.in b/data/sugar.schemas.in
index e0eb7fc..8c43930 100644
--- a/data/sugar.schemas.in
+++ b/data/sugar.schemas.in
@@ -180,5 +180,42 @@
</locale>
</schema>
+ <schema>
+ <key>/schemas/desktop/sugar/peripherals/keyboard/layouts</key>
+ <applyto>/desktop/sugar/peripherals/keyboard/layouts</applyto>
+ <owner>sugar</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <locale name="C">
+ <short>Keyboard layouts</short>
+ <long>List of keyboard layouts. Each entry should be in the form layout(variant)</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/peripherals/keyboard/options</key>
+ <applyto>/desktop/sugar/peripherals/keyboard/options</applyto>
+ <owner>sugar</owner>
+ <type>list</type>
+ <list_type>string</list_type>
+ <locale name="C">
+ <short>Keyboard options</short>
+ <long>List of keyboard options.</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/desktop/sugar/peripherals/keyboard/model</key>
+ <applyto>/desktop/sugar/peripherals/keyboard/model</applyto>
+ <owner>sugar</owner>
+ <type>string</type>
+ <default></default>
+ <locale name="C">
+ <short>Keyboard model</short>
+ <long>The keyboard model to be used</long>
+ </locale>
+ </schema>
+
+
</schemalist>
</gconfschemafile>
diff --git a/extensions/cpsection/Makefile.am b/extensions/cpsection/Makefile.am
index ffca6ca..dd0a6b8 100644
--- a/extensions/cpsection/Makefile.am
+++ b/extensions/cpsection/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = aboutme aboutcomputer datetime frame language network power updater
+SUBDIRS = aboutme aboutcomputer datetime frame keyboard language network power updater
sugardir = $(pkgdatadir)/extensions/cpsection
sugar_PYTHON = __init__.py
diff --git a/extensions/cpsection/keyboard/Makefile.am b/extensions/cpsection/keyboard/Makefile.am
new file mode 100644
index 0000000..7a95da5
--- /dev/null
+++ b/extensions/cpsection/keyboard/Makefile.am
@@ -0,0 +1,6 @@
+sugardir = $(pkgdatadir)/extensions/cpsection/keyboard
+
+sugar_PYTHON = \
+ __init__.py \
+ model.py \
+ view.py
diff --git a/extensions/cpsection/keyboard/__init__.py b/extensions/cpsection/keyboard/__init__.py
new file mode 100644
index 0000000..568e7a5
--- /dev/null
+++ b/extensions/cpsection/keyboard/__init__.py
@@ -0,0 +1,22 @@
+# Copyright (C) 2009, OLPC
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+from gettext import gettext as _
+
+CLASS = 'Keyboard'
+ICON = 'module-keyboard'
+TITLE = _('Keyboard')
+
diff --git a/extensions/cpsection/keyboard/model.py b/extensions/cpsection/keyboard/model.py
new file mode 100644
index 0000000..123e336
--- /dev/null
+++ b/extensions/cpsection/keyboard/model.py
@@ -0,0 +1,156 @@
+# Copyright (C) 2009 OLPC
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+import xklavier
+import gobject
+import gconf
+
+
+_GROUP_NAME = 'grp' # The XKB name for group switch options
+
+_LAYOUTS_KEY = '/desktop/sugar/peripherals/keyboard/layouts'
+_OPTIONS_KEY = '/desktop/sugar/peripherals/keyboard/options'
+_MODEL_KEY = '/desktop/sugar/peripherals/keyboard/model'
+
+class KeyboardManager(object):
+ def __init__(self, display):
+ self._engine = xklavier.Engine(display)
+ self._configregistry = xklavier.ConfigRegistry(self._engine)
+ self._configregistry.load(False)
+ self._configrec = xklavier.ConfigRec()
+ self._configrec.get_from_server(self._engine)
+
+ self._gconf_client = gconf.client_get_default()
+
+ def _populate_one(self, config_registry, item, store):
+ store.append([item.get_description(), item.get_name()])
+
+ def _populate_two(self, config_registry, item, subitem, store):
+ layout = item.get_name()
+ if subitem:
+ description = '%s, %s' % (subitem.get_description(), \
+ item.get_description())
+ variant = subitem.get_name()
+ else:
+ description = 'Default layout, %s' % item.get_description()
+ variant = ''
+
+ store.append([description, ('%s(%s)' % (layout, variant))])
+
+ def get_models(self):
+ """Return list of supported keyboard models"""
+ models = []
+ self._configregistry.foreach_model(self._populate_one, models)
+ models.sort()
+ return models
+
+ def get_languages(self):
+ """Return list of supported keyboard languages"""
+ languages = []
+ self._configregistry.foreach_language(self._populate_one, languages)
+ languages.sort()
+ return languages
+
+ def get_layouts_for_language(self, language):
+ """Return list of supported keyboard layouts for a given language"""
+ layouts = []
+ self._configregistry.foreach_language_variant(language, \
+ self._populate_two, layouts)
+ layouts.sort()
+ return layouts
+
+ def get_options_group(self):
+ """Return list of supported options for switching keyboard group"""
+ options = []
+ self._configregistry.foreach_option(_GROUP_NAME, self._populate_one, \
+ options)
+ options.sort()
+ return options
+
+ def get_current_model(self):
+ """Return the enabled keyboard model"""
+ model = self._gconf_client.get_string(_MODEL_KEY)
+ if model:
+ return model
+ else:
+ return self._configrec.get_model()
+
+ def get_current_layouts(self):
+ """Return the enabled keyboard layouts with variants"""
+ layouts = self._gconf_client.get_list(_LAYOUTS_KEY, gconf.VALUE_STRING)
+ if layouts:
+ return layouts
+
+ layouts = self._configrec.get_layouts()
+ variants = self._configrec.get_variants()
+
+ layout_list = []
+ i = 0
+ for layout in layouts:
+ if len(variants) <= i or variants[i] == '':
+ layout_list.append('%s(%s)' % (layout, ''))
+ else:
+ layout_list.append('%s(%s)' % (layout, variants[i]))
+ i += 1
+
+ return layout_list
+
+ def get_current_option_group(self):
+ """Return the enabled option for switching keyboard group"""
+ options = self._gconf_client.get_list(_OPTIONS_KEY, gconf.VALUE_STRING)
+
+ if not options:
+ options = self._configrec.get_options()
+
+ for option in options:
+ if option.startswith(_GROUP_NAME):
+ return option
+
+ return None
+
+ def get_max_layouts(self):
+ """Return the maximum number of layouts supported simultaneously"""
+ return self._engine.get_max_num_groups()
+
+ def set_model(self, model):
+ """Sets the supplied keyboard model"""
+ self._gconf_client.set_string(_MODEL_KEY, model)
+ self._configrec.set_model(model)
+ self._configrec.activate(self._engine)
+
+ def set_option_group(self, option_group):
+ """Sets the supplied option for switching keyboard group"""
+ #XXX: Merge, not overwrite previous options
+ options = [option_group]
+ self._gconf_client.set_list(_OPTIONS_KEY, gconf.VALUE_STRING, options)
+ self._configrec.set_options(options)
+ self._configrec.activate(self._engine)
+
+ def set_layouts(self, layouts):
+ """Sets the supplied keyboard layouts (with variants)"""
+ self._gconf_client.set_list(_LAYOUTS_KEY, gconf.VALUE_STRING, layouts)
+ layouts_list = []
+ variants_list = []
+ for layout in layouts:
+ layouts_list.append(layout.split('(')[0])
+ variants_list.append(layout.split('(')[1][:-1])
+
+ self._configrec.set_layouts(layouts_list)
+ self._configrec.set_variants(variants_list)
+ self._configrec.activate(self._engine)
+
diff --git a/extensions/cpsection/keyboard/view.py b/extensions/cpsection/keyboard/view.py
new file mode 100644
index 0000000..65bd49a
--- /dev/null
+++ b/extensions/cpsection/keyboard/view.py
@@ -0,0 +1,418 @@
+# Copyright (C) 2009, OLPC
+# Author: Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+import gtk
+import gobject
+import pango
+import logging
+from gettext import gettext as _
+
+from sugar.graphics import style
+from sugar.graphics.icon import Icon
+
+from jarabe.controlpanel.sectionview import SectionView
+
+CLASS = 'Language'
+ICON = 'module-keyboard'
+TITLE = _('Keyboard')
+
+_APPLY_TIMEOUT = 3000
+
+#TODO: This cpsection adds checks for xklavier in bin/sugar-session and
+# src/jarabe/controlpanel/gui.py. We should get rid of these checks
+# once python-xklavier has been packaged for all major distributions
+# For more information, see: http://dev.sugarlabs.org/ticket/407
+
+class LayoutCombo(gtk.HBox):
+
+ """
+ Custom GTK widget with two comboboxes side by side, one for layout, and
+ the other for variants for the selected layout.
+ """
+
+ __gsignals__ = {
+ 'selection-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE,
+ (gobject.TYPE_STRING, gobject.TYPE_INT))
+ }
+
+ def __init__(self, keyboard_manager, n):
+ gtk.HBox.__init__(self)
+ self._keyboard_manager = keyboard_manager
+ self._index = n
+
+ self.set_border_width(style.DEFAULT_SPACING)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ label = gtk.Label(' <b>%s</b> ' % str(n+1))
+ label.set_use_markup(True)
+ label.modify_fg(gtk.STATE_NORMAL,
+ style.COLOR_SELECTION_GREY.get_gdk_color())
+ label.set_alignment(0.5, 0.5)
+ self.pack_start(label, expand=False)
+
+ self._klang_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_languages():
+ self._klang_store.append([name, description])
+
+ self._klang_combo = gtk.ComboBox(model = self._klang_store)
+ self._klang_combo_changed_id = \
+ self._klang_combo.connect('changed', self._klang_combo_changed_cb)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ self._klang_combo.pack_start(cell)
+ self._klang_combo.add_attribute(cell, 'text', 1)
+ self.pack_start(self._klang_combo, expand=True, fill = True)
+
+ self._kvariant_store = None
+ self._kvariant_combo = gtk.ComboBox(model = None)
+ self._kvariant_combo_changed_id = \
+ self._kvariant_combo.connect('changed', \
+ self._kvariant_combo_changed_cb)
+ cell = gtk.CellRendererText()
+ cell.props.ellipsize = pango.ELLIPSIZE_MIDDLE
+ self._kvariant_combo.pack_start(cell)
+ self._kvariant_combo.add_attribute(cell, 'text', 1)
+ self.pack_start(self._kvariant_combo, expand=True, fill = True)
+
+ self._klang_combo.set_active(self._index)
+
+ def select_layout(self, layout):
+ """Select a given keyboard layout and show appropriate variants"""
+ self._kvariant_combo.handler_block(self._kvariant_combo_changed_id)
+ for i in range(0, len(self._klang_store)):
+ self._klang_combo.set_active(i)
+ for j in range(0, len(self._kvariant_store)):
+ if self._kvariant_store[j][0] == layout:
+ self._kvariant_combo.set_active(j)
+ self._kvariant_combo.handler_unblock(\
+ self._kvariant_combo_changed_id)
+ return True
+
+ self._kvariant_combo.handler_unblock(self._kvariant_combo_changed_id)
+ self._klang_combo.set_active(0)
+ return False
+
+ def get_layout(self):
+ """Gets the selected layout (with variant)"""
+ it = self._kvariant_combo.get_active_iter()
+ model = self._kvariant_combo.get_model()
+ return model.get(it, 0)[0]
+
+ def _set_kvariant_store(self, lang):
+ self._kvariant_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_layouts_for_language(lang):
+ self._kvariant_store.append([name, description])
+ self._kvariant_combo.set_model(self._kvariant_store)
+ self._kvariant_combo.set_active(0)
+
+ def _klang_combo_changed_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ lang = model.get(it, 0)[0]
+ self._set_kvariant_store(lang)
+
+ def _kvariant_combo_changed_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ layout = model.get(it, 0)[0]
+ self.emit('selection-changed', layout, self._index)
+
+
+class Keyboard(SectionView):
+ def __init__(self, model, alerts):
+ SectionView.__init__(self)
+
+ self._model = model
+
+ self._kmodel = None
+ self._selected_kmodel = None
+
+ self._klayouts = []
+ self._selected_klayouts = []
+
+ self._group_switch_option = None
+ self._selected_group_switch_option = None
+
+ self.set_border_width(style.DEFAULT_SPACING * 2)
+ self.set_spacing(style.DEFAULT_SPACING)
+
+ self._layout_table = gtk.Table(rows = 4, columns = 2, \
+ homogeneous = False)
+
+ self._keyboard_manager = model.KeyboardManager(self.get_display())
+ self._layout_combo_list = []
+ self._layout_addremovebox_list = []
+
+ scrollwindow = gtk.ScrolledWindow()
+ scrollwindow.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ self.pack_start(scrollwindow, expand=True)
+ scrollwindow.show()
+
+ self._vbox = gtk.VBox()
+ scrollwindow.add_with_viewport(self._vbox)
+
+ self.__kmodel_sid = None
+ self.__layout_sid = None
+ self.__group_switch_sid = None
+
+ self._setup_kmodel()
+ self._setup_layouts()
+ self._setup_group_switch_option()
+
+ self._vbox.show()
+
+ def _setup_kmodel(self):
+ """Adds the controls for changing the keyboard model"""
+ separator_kmodel = gtk.HSeparator()
+ self._vbox.pack_start(separator_kmodel, expand=False)
+ separator_kmodel.show_all()
+
+ label_kmodel = gtk.Label(_('Keyboard Model'))
+ label_kmodel.set_alignment(0, 0)
+ self._vbox.pack_start(label_kmodel, expand=False)
+ label_kmodel.show_all()
+
+ box_kmodel = gtk.VBox()
+ box_kmodel.set_border_width(style.DEFAULT_SPACING * 2)
+ box_kmodel.set_spacing(style.DEFAULT_SPACING)
+
+ kmodel_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_models():
+ kmodel_store.append([name, description])
+
+ kmodel_combo = gtk.ComboBox(model = kmodel_store)
+ cell = gtk.CellRendererText()
+ kmodel_combo.pack_start(cell)
+ kmodel_combo.add_attribute(cell, 'text', 1)
+
+ self._kmodel = self._keyboard_manager.get_current_model()
+ for row in kmodel_store:
+ if self._kmodel in row[0]:
+ kmodel_combo.set_active_iter(row.iter)
+ break
+
+ box_kmodel.pack_start(kmodel_combo, expand = False)
+ self._vbox.pack_start(box_kmodel, expand=False)
+ box_kmodel.show_all()
+
+ kmodel_combo.connect('changed', self.__kmodel_changed_cb)
+
+ def __kmodel_changed_cb(self, combobox):
+ if self.__kmodel_sid is not None:
+ gobject.source_remove(self.__kmodel_sid)
+ self.__kmodel_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__kmodel_timeout_cb, combobox)
+
+ def __kmodel_timeout_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ self._selected_kmodel = model.get(it, 0)[0]
+ if self._selected_kmodel == self._keyboard_manager.get_current_model():
+ return
+ try:
+ self._keyboard_manager.set_model(self._selected_kmodel)
+ except:
+ logging.exception('Could not set new keyboard model')
+
+ return False
+
+ def _setup_group_switch_option(self):
+ """Adds the controls for changing the group switch option of keyboard"""
+ separator_group_option = gtk.HSeparator()
+ self._vbox.pack_start(separator_group_option, expand=False)
+ separator_group_option.show_all()
+
+ label_group_option = gtk.Label(_('Key(s) to change layout'))
+ label_group_option.set_alignment(0, 0)
+ self._vbox.pack_start(label_group_option, expand=False)
+ label_group_option.show_all()
+
+ box_group_option = gtk.VBox()
+ box_group_option.set_border_width(style.DEFAULT_SPACING * 2)
+ box_group_option.set_spacing(style.DEFAULT_SPACING)
+
+ group_option_store = gtk.ListStore(gobject.TYPE_STRING, \
+ gobject.TYPE_STRING)
+ for description, name in self._keyboard_manager.get_options_group():
+ group_option_store.append([name, description])
+
+ group_option_combo = gtk.ComboBox(model = group_option_store)
+ cell = gtk.CellRendererText()
+ group_option_combo.pack_start(cell)
+ group_option_combo.add_attribute(cell, 'text', 1)
+
+ self._group_switch_option = \
+ self._keyboard_manager.get_current_option_group()
+ if not self._group_switch_option:
+ group_option_combo.set_active(0)
+ else:
+ found = False
+ for row in group_option_store:
+ if self._group_switch_option in row[0]:
+ group_option_combo.set_active_iter(row.iter)
+ found = True
+ break
+ if not found:
+ group_option_combo.set_active(0)
+
+ box_group_option.pack_start(group_option_combo, expand = False)
+ self._vbox.pack_start(box_group_option, expand=False)
+ box_group_option.show_all()
+
+ group_option_combo.connect('changed', \
+ self.__group_switch_changed_cb)
+
+ def __group_switch_changed_cb(self, combobox):
+ if self.__group_switch_sid is not None:
+ gobject.source_remove(self.__group_switch_sid)
+ self.__group_switch_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__group_switch_timeout_cb, combobox)
+
+ def __group_switch_timeout_cb(self, combobox):
+ it = combobox.get_active_iter()
+ model = combobox.get_model()
+ self._selected_group_switch_option = model.get(it, 0)[0]
+ if self._selected_group_switch_option == \
+ self._keyboard_manager.get_current_option_group():
+ return
+ try:
+ self._keyboard_manager.set_option_group(\
+ self._selected_group_switch_option)
+ except:
+ logging.exception('Could not set new keyboard group switch option')
+
+
+ return False
+
+ def _setup_layouts(self):
+ """Adds the controls for changing the keyboard layouts"""
+ separator_klayout = gtk.HSeparator()
+ self._vbox.pack_start(separator_klayout, expand=False)
+ separator_klayout.show_all()
+
+ label_klayout = gtk.Label(_('Keyboard Layout(s)'))
+ label_klayout.set_alignment(0, 0)
+ label_klayout.show_all()
+ self._vbox.pack_start(label_klayout, expand=False)
+
+ self._klayouts = self._keyboard_manager.get_current_layouts()
+ for i in range(0, self._keyboard_manager.get_max_layouts()):
+ add_remove_box = self.__create_add_remove_box()
+ self._layout_addremovebox_list.append(add_remove_box)
+ self._layout_table.attach(add_remove_box, 1, 2, i, i+1)
+
+ layout_combo = LayoutCombo(self._keyboard_manager, i)
+ layout_combo.connect('selection-changed', \
+ self.__layout_combo_selection_changed_cb)
+ self._layout_combo_list.append(layout_combo)
+ self._layout_table.attach(layout_combo, 0, 1, i, i+1)
+
+ if i < len(self._klayouts):
+ layout_combo.show_all()
+ layout_combo.select_layout(self._klayouts[i])
+
+ self._vbox.pack_start(self._layout_table, expand=False)
+ self._layout_table.set_size_request(self._vbox.size_request()[0], -1)
+ self._layout_table.show()
+ self._update_klayouts()
+
+ def __determine_add_remove_box_visibility(self):
+ i = 1
+ for box in self._layout_addremovebox_list:
+ if not i == len(self._selected_klayouts):
+ box.props.visible = False
+ else:
+ box.show_all()
+ if i == 1:
+ # First row - no need for showing remove btn
+ add, remove = box.get_children()
+ remove.props.visible = False
+ if i == self._keyboard_manager.get_max_layouts():
+ # Last row - no need for showing add btn
+ add, remove = box.get_children()
+ add.props.visible = False
+ i += 1
+
+ def __create_add_remove_box(self):
+ '''Creates gtk.Hbox with add/remove buttons'''
+ add_icon = Icon(icon_name='list-add')
+
+ add_button = gtk.Button()
+ add_button.set_image(add_icon)
+ add_button.connect('clicked',
+ self.__add_button_clicked_cb)
+
+ remove_icon = Icon(icon_name='list-remove')
+ remove_button = gtk.Button()
+ remove_button.set_image(remove_icon)
+ remove_button.connect('clicked',
+ self.__remove_button_clicked_cb)
+
+ add_remove_box = gtk.HButtonBox()
+ add_remove_box.set_layout(gtk.BUTTONBOX_START)
+ add_remove_box.set_spacing(10)
+ add_remove_box.pack_start(add_button)
+ add_remove_box.pack_start(remove_button)
+
+ return add_remove_box
+
+ def __layout_combo_selection_changed_cb(self, combo, layout, index):
+ self._update_klayouts()
+
+ def __add_button_clicked_cb(self, button):
+ self._layout_combo_list[len(self._selected_klayouts)].show_all()
+ self._update_klayouts()
+
+ def __remove_button_clicked_cb(self, button):
+ self._layout_combo_list[len(self._selected_klayouts) - 1].hide()
+ self._update_klayouts()
+
+ def _update_klayouts(self):
+ """Responds to any changes in the keyboard layout options"""
+ self._selected_klayouts = []
+ for combo in self._layout_combo_list:
+ if combo.props.visible:
+ self._selected_klayouts.append(combo.get_layout())
+
+ self.__determine_add_remove_box_visibility()
+
+ if self.__layout_sid is not None:
+ gobject.source_remove(self.__layout_sid)
+ self.__layout_sid = gobject.timeout_add(_APPLY_TIMEOUT,
+ self.__layout_timeout_cb)
+
+ def __layout_timeout_cb(self):
+ if self._selected_klayouts == \
+ self._keyboard_manager.get_current_layouts():
+ return
+ try:
+ self._keyboard_manager.set_layouts(self._selected_klayouts)
+ except:
+ logging.exception('Could not set new keyboard layouts')
+
+ return False
+
+
+ def undo(self):
+ """Reverts back to the original keyboard configuration"""
+ self._keyboard_manager.set_model(self._kmodel)
+ self._keyboard_manager.set_layouts(self._klayouts)
+ self._keyboard_manager.set_option_group(self._group_switch_option)
+
diff --git a/po/POTFILES.in b/po/POTFILES.in
index c523c31..a93f555 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -10,6 +10,7 @@ extensions/cpsection/datetime/view.py
extensions/cpsection/frame/__init__.py
extensions/cpsection/frame/model.py
extensions/cpsection/frame/view.py
+extensions/cpsection/keyboard/view.py
extensions/cpsection/language/__init__.py
extensions/cpsection/language/model.py
extensions/cpsection/language/view.py
diff --git a/src/jarabe/controlpanel/gui.py b/src/jarabe/controlpanel/gui.py
index 7384a0d..39f9c4a 100644
--- a/src/jarabe/controlpanel/gui.py
+++ b/src/jarabe/controlpanel/gui.py
@@ -134,6 +134,11 @@ class ControlPanel(gtk.Window):
if not os.path.exists('/ofw'):
options.remove('power')
+ try:
+ import xklavier
+ except ImportError:
+ options.remove('keyboard')
+
for option in options:
sectionicon = _SectionIcon(icon_name=self._options[option]['icon'],
title=self._options[option]['title'],