From fec479f03d60db13da362f50c3c715168fef7ba4 Mon Sep 17 00:00:00 2001 From: Sayamindu Dasgupta Date: Tue, 25 Aug 2009 17:16:50 +0000 Subject: Merge branch 'master' of git://git.sugarlabs.org/sugar/sugar-xkb-support --- (limited to 'extensions') 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 +# +# 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 +# +# 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(' %s ' % 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) + -- cgit v0.9.1