summaryrefslogtreecommitdiff
path: root/caribou
diff options
context:
space:
mode:
authorEitan Isaacson <eitan@monotonous.org>2011-04-22 15:10:38 -0700
committerEitan Isaacson <eitan@monotonous.org>2011-05-02 10:18:49 -0700
commit048f045f85982579932d147390e6503710a455bc (patch)
tree5fe0068af995bb083c9190c917670d799f5cb332 /caribou
parentfde58831fdef2a0d89a884822e0b065e54a62996 (diff)
downloadcaribou-048f045f85982579932d147390e6503710a455bc.tar.gz
Use new layout format. Added sub-keys. Added group change notification.
Diffstat (limited to 'caribou')
-rw-r--r--caribou/common/const.py2
-rw-r--r--caribou/common/settings.py34
-rw-r--r--caribou/ui/keyboard.py339
-rw-r--r--caribou/ui/main.py6
-rw-r--r--caribou/ui/window.py28
5 files changed, 214 insertions, 195 deletions
diff --git a/caribou/common/const.py b/caribou/common/const.py
index b3a13b7..0591e5d 100644
--- a/caribou/common/const.py
+++ b/caribou/common/const.py
@@ -30,7 +30,7 @@ APP_SLUG_NAME = 'caribou'
# Paths
DATA_DIR = data_path
-KEYBOARDS_DIR = join(DATA_DIR, 'keyboards')
+LAYOUTS_DIR = join(DATA_DIR, 'layouts')
# Preferences
CARIBOU_GCONF = '/org/gnome/caribou/osk'
diff --git a/caribou/common/settings.py b/caribou/common/settings.py
index a3d900d..7837508 100644
--- a/caribou/common/settings.py
+++ b/caribou/common/settings.py
@@ -4,39 +4,21 @@ from gettext import gettext as _
import caribou.common.const as const
import caribou.ui.i18n
import xml.dom.minidom
+import json
GSETTINGS_SCHEMA = "org.gnome.caribou"
-try:
- import json
-except ImportError:
- HAS_JSON = False
-else:
- HAS_JSON = True
-
-def fetch_keyboards():
- try:
- files = os.listdir(const.KEYBOARDS_DIR)
- except:
- files = []
- kbds = []
- for f in files:
- if (HAS_JSON and f.endswith('.json')) or f.endswith('.xml'):
- module = f.rsplit('.', 1)[0]
- # TODO: verify keyboard before adding it to the list
- kbds.append(module)
- return kbds
-
settings = SettingsGroup("_top", "", [
SettingsGroup("keyboard", _("Keyboard"), [
SettingsGroup("general", _("General"), [
StringSetting(
- "layout", _("Keyboard layout"), "qwerty",
- _("The layout Caribou should use."),
- _("The layout should be in the data directory of "
- "Caribou (usually /usr/share/caribou/keyboards) "
- "and should be a .xml or .json file."),
- allowed=[(a,a) for a in fetch_keyboards()])]),
+ "geometry", _("Keyboard geometry"), "natural",
+ _("The keyboard geometery Caribou should use"),
+ _("The keyboard geometery determines the shape "
+ "and complexity of the keyboard, it could range from "
+ "a 'natural' look and feel good for composing simple "
+ "text, to a fullscale keyboard."),
+ allowed=[(('natural'), _('Natural'))])]),
SettingsGroup("color", _("Color"), [
BooleanSetting(
"default_colors", _("Use system theme"), True,
diff --git a/caribou/ui/keyboard.py b/caribou/ui/keyboard.py
index c390d49..9546935 100644
--- a/caribou/ui/keyboard.py
+++ b/caribou/ui/keyboard.py
@@ -25,6 +25,7 @@
import caribou.common.const as const
from caribou.common.settings_manager import SettingsManager
from preferences_window import PreferencesWindow
+from caribou import data_path
from gi.repository import GConf
import gobject
from gi.repository import Gdk
@@ -48,33 +49,40 @@ from xml.dom import minidom
import gettext
import i18n
+PRETTY_LABELS = {
+ "BackSpace" : u'\u232b',
+ "space" : u' ',
+ "Return" : u'\u23ce',
+ 'Caribou_Prefs' : u'\u2328',
+ 'Caribou_ShiftUp' : u'\u2b06',
+ 'Caribou_ShiftDown' : u'\u2b07',
+ 'Caribou_Emoticons' : u'\u263a',
+ 'Caribou_Symbols' : u'123',
+ 'Caribou_Symbols_More' : u'{#*',
+ 'Caribou_Alpha' : u'Abc'
+}
+
class BaseKey(object):
'''An abstract class the represents a key on the keyboard.
Inheriting classes also need to inherit from Gtk.Button or any
of it's subclasses.'''
- def __init__(self, label = '', value = '', key_type = 'normal',
- width = 1, fill = False):
- self.key_type = key_type
- self.value = value
- self.width = float(width)
- self.fill = False
- self.label = label or value
- if self.key_type == const.DUMMY_KEY_TYPE:
- self.set_relief(Gtk.ReliefStyle.NONE)
- self.set_sensitive(False)
- elif self.key_type == const.PREFERENCES_KEY_TYPE:
- image = Gtk.Image()
- image.set_from_stock(Gtk.STOCK_PREFERENCES,
- Gtk.IconSize.BUTTON)
- self.set_image(image)
+ def __init__(self, **kwargs):
+ if not kwargs.has_key("name"):
+ raise TypeError, "%r requires a 'name' parameter" % self.__class__
+ self.margin_left = 0
+ self.width = 1
+
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+ if hasattr(self, "extended_names"):
+ self.extended_keys = \
+ [self.__class__(name=n) for n in self.extended_names]
else:
- if label:
- label_markup = Gtk.Label()
- label_markup.set_markup(self.label)
- self.add(label_markup)
- else:
- self.set_label(self.label)
+ self.extended_keys = []
+
+ self.keyval, self.key_label = self._get_keyval_and_label(self.name)
+ self.set_label(self.key_label)
ctx = self.get_style_context()
ctx.add_class("caribou-keyboard-button")
@@ -86,6 +94,21 @@ class BaseKey(object):
if not SettingsManager.default_font.value:
self._key_font_changed(None, None)
+ def _get_keyval_and_label(self, name):
+ keyval = Gdk.keyval_from_name(name)
+ if PRETTY_LABELS.has_key(name):
+ label = PRETTY_LABELS[name]
+ elif name.startswith('Caribou_'):
+ label = name.replace('Caribou_', '')
+ else:
+ unichar = unichr(Gdk.keyval_to_unicode(keyval))
+ if unichar.isspace() or unichar == u'\x00':
+ label = name
+ else:
+ label = unichar
+
+ return keyval, label
+
def _key_font_changed(self, setting, value):
if SettingsManager.default_font.value:
self.reset_font()
@@ -107,54 +130,55 @@ class BaseKey(object):
def scan_highlight_clear(self):
raise NotImplemented
- def _on_image_key_mapped(self, key):
- key_width = key.get_allocated_height()
- icon_size = Gtk.IconSize.MENU
- image = Gtk.Image()
- for size in [Gtk.IconSize.MENU,
- Gtk.IconSize.SMALL_TOOLBAR,
- Gtk.IconSize.LARGE_TOOLBAR,
- Gtk.IconSize.BUTTON,
- Gtk.IconSize.DND,
- Gtk.IconSize.DIALOG]:
- pixbuf = image.render_icon_pixbuf(Gtk.STOCK_PREFERENCES, size)
- pixel_size = pixbuf.get_width()
- if pixel_size > key_width:
- break
- icon_size = size
- image.set_from_stock(Gtk.STOCK_PREFERENCES, icon_size)
- self.set_image(image)
-
def set_font(self, font):
raise NotImplemented
def reset_font(self):
raise NotImplemented
- def _get_value(self):
- return self._value
-
- def _set_value(self, value):
- if self.key_type in (const.NORMAL_KEY_TYPE, const.MASK_KEY_TYPE):
- if type(value) == str:
- value = value.decode('utf-8')
- if type(value) == unicode:
- if len(value) == 1:
- self._value = Gdk.unicode_to_keyval(ord(value))
- else:
- key_value = Gdk.keyval_from_name(value)
- if key_value:
- self._value = key_value
- else:
- self._value = value
+class CaribouSubKeys(Gtk.Window):
+ def __init__(self, keys):
+ gobject.GObject.__init__(self, type=Gtk.WindowType.POPUP)
+ self.set_decorated(False)
+ self.set_resizable(False)
+ self.set_accept_focus(False)
+ self.set_position(Gtk.WindowPosition.MOUSE)
+ self.set_type_hint(Gdk.WindowTypeHint.DIALOG)
- value = property(_get_value, _set_value)
+ for key in keys:
+ key.connect("clicked", self._on_key_clicked)
+
+ layout = KeyboardLayout("")
+ layout.add_row(keys)
+ self.add(layout)
+
+ def show_subkeys(self, parent):
+ self.set_transient_for(parent)
+ self._parent = parent
+ self._parent.set_sensitive(False)
+ self.show_all()
+
+ def _on_key_clicked(self, key):
+ self._parent.set_sensitive(True)
+ self.hide()
class Key(Gtk.Button, BaseKey):
- def __init__(self, label = '', value = '', key_type = 'normal',
- width = 1, fill = False):
+ def __init__(self, **kwargs):
gobject.GObject.__init__(self)
- BaseKey.__init__(self, label, value, key_type, width, fill)
+ BaseKey.__init__(self, **kwargs)
+ if self.extended_keys:
+ self.sub_keys = CaribouSubKeys(self.extended_keys)
+ else:
+ self.sub_keys = None
+
+ child = self.get_child()
+ child.set_padding(4, 4)
+
+ def do_get_preferred_width_for_height(self, w):
+ return (w, w)
+
+ def do_get_request_mode(self):
+ return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH
def set_font(self, font):
child = self.get_child()
@@ -196,27 +220,30 @@ class Key(Gtk.Button, BaseKey):
class ModifierKey(Gtk.ToggleButton, Key):
pass
-class KeyboardLayout(Gtk.Table):
+class KeyboardLayout(Gtk.Grid):
KEY_SPAN = 4
+
def __init__(self, name):
gobject.GObject.__init__(self)
self.layout_name = name
self.rows = []
- self.set_homogeneous(True)
+ self.set_column_homogeneous(True)
+ self.set_row_homogeneous(True)
+ self.set_row_spacing(4)
+ self.set_column_spacing(4)
def add_row(self, row):
row_num = len(self.rows)
self.rows.append(row)
- last_col = 0
+ col_num = 0
for i, key in enumerate(row):
- next_col = (last_col + (key.width * self.KEY_SPAN))
- self.attach(key, last_col, next_col,
- row_num * self.KEY_SPAN, (row_num + 1) * self.KEY_SPAN,
- Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
- Gtk.AttachOptions.EXPAND | Gtk.AttachOptions.FILL,
- 0, 0)
- last_col = next_col
-
+ self.attach(key,
+ col_num + int(key.margin_left * self.KEY_SPAN),
+ row_num * self.KEY_SPAN,
+ int(self.KEY_SPAN * key.width),
+ self.KEY_SPAN)
+ col_num += int((key.width + key.margin_left ) * self.KEY_SPAN)
+
def get_scan_rows(self):
return [filter(lambda x: x.is_sensitive(), row) for row in self.rows]
@@ -247,16 +274,21 @@ class KeyboardLayout(Gtk.Table):
group.append(block)
return reduce(lambda a, b: a + b, blocks)
-
-
class KbLayoutDeserializer(object):
- def __init__(self):
- pass
-
- def deserialize(self, kb_layout_file):
- kb_file = os.path.abspath(kb_layout_file)
- if not os.path.isfile(kb_file):
+ def _get_layout_file(self, group, variant):
+ layout_path = os.path.join(data_path, "layouts",
+ SettingsManager.geometry.value)
+ for fn in ('%s_%s.json' % (group, variant),
+ '%s.json' % group):
+ layout_file = os.path.join(layout_path, fn)
+ if os.path.exists(layout_file):
+ return layout_file
+ return None
+
+ def deserialize(self, group, variant):
+ kb_file = self._get_layout_file(group, variant)
+ if not kb_file:
return []
kb_file_obj = open(kb_file)
contents = kb_file_obj.read()
@@ -304,37 +336,24 @@ class KbLayoutDeserializer(object):
def _create_kb_layout_from_dict(self, dictionary):
if not isinstance(dictionary, dict):
return None
- layouts = self._get_dict_value_as_list(dictionary, 'layout')
layouts_encoded = []
- for layout in layouts:
- name = layout.get('name')
- if not name:
- continue
+ for name, level in dictionary.items():
kb_layout = KeyboardLayout(name)
- rows_list = self._get_dict_value_as_list(layout, 'rows')
- for rows in rows_list:
- for row_encoded in self._get_rows_from_dict(rows):
- kb_layout.add_row(row_encoded)
- layouts_encoded.append(kb_layout)
+ rows_list = self._get_dict_value_as_list(level, 'rows')
+ for row in rows_list:
+ keys = self._get_keys_from_list(row)
+ kb_layout.add_row(keys)
+ layouts_encoded.append(kb_layout)
return layouts_encoded
- def _get_rows_from_dict(self, rows):
- rows_encoded = []
- row_list = self._get_dict_value_as_list(rows, 'row')
- for row in row_list:
- keys = self._get_dict_value_as_list(row, 'key')
- if keys:
- rows_encoded.append(self._get_keys_from_list(keys))
- return rows_encoded
-
def _get_keys_from_list(self, keys_list):
keys = []
for key_vars in keys_list:
vars = {}
for key, value in key_vars.items():
vars[str(key)] = value
- if vars.get('key_type', '') == const.MASK_KEY_TYPE:
- key = ModifierKey(**vars)
+ if vars.get('modifier', False) == const.MASK_KEY_TYPE:
+ key = ModifierKey(self.vk, **vars)
else:
key = Key(**vars)
keys.append(key)
@@ -360,45 +379,82 @@ class CaribouKeyboard(Gtk.Notebook):
self.key_size = 30
self.current_page = 0
self.depressed_mods = []
-
- self.row_height = -1
-
- def load_kb(self, kb_location):
+ self.layouts = {}
+ self._load_kb()
+ self.vk.connect('group-changed', self._on_group_changed)
+ grpid, group, variant = self.vk.get_current_group()
+ self._on_group_changed(self.vk, grpid, group, variant)
+ self._key_hold_tid = 0
+
+ def _load_kb(self):
kb_deserializer = KbLayoutDeserializer()
- layouts = kb_deserializer.deserialize(kb_location)
- self._set_layouts(layouts)
-
- def _set_layouts(self, layout_list):
- self._clear()
- for layout in layout_list:
- self.append_page(layout, None)
- for row in layout.rows:
+ groups, variants = self.vk.get_groups()
+ for group, variant in zip(groups, variants):
+ levels = kb_deserializer.deserialize(group, variant)
+ self._add_levels('%s_%s' % (group, variant), levels)
+
+ def _connect_key_signals(self, key):
+ if hasattr(key, "toggle"):
+ key.connect('clicked',
+ self._pressed_layout_switcher_key)
+ elif key.name == "Caribou_Prefs":
+ key.connect('clicked', self._pressed_preferences_key)
+ elif key.keyval != 0:
+ if False: # We should enable this for hardware emulation
+ key.connect('pressed',
+ self._pressed_normal_key)
+ key.connect('released',
+ self._released_normal_key)
+ else:
+ key.connect('clicked',
+ self._clicked_normal_key)
+ key.connect('pressed',
+ self._key_hold_start)
+ key.connect('released',
+ self._key_hold_end)
+
+
+ def _add_levels(self, group, level_list):
+ self.layouts[group] = {}
+ for level in level_list:
+ level.show()
+ self.layouts[group][level.layout_name] = self.append_page(level, None)
+ for row in level.rows:
for key in row:
- if key.key_type == const.LAYOUT_SWITCHER_KEY_TYPE:
- key.connect('clicked',
- self._pressed_layout_switcher_key)
- elif key.key_type == const.MASK_KEY_TYPE:
- key.connect('toggled',
- self._toggled_mask_key)
- elif key.key_type == const.PREFERENCES_KEY_TYPE:
- key.connect('clicked',
- self._pressed_preferences_key)
- else:
- key.connect('pressed',
- self._pressed_normal_key)
- key.connect('released',
- self._released_normal_key)
+ self._connect_key_signals(key)
+ for k in key.extended_keys:
+ self._connect_key_signals(k)
def _clear(self):
n_pages = self.get_n_pages()
for i in range(n_pages):
self.remove_page(i)
+ def _key_hold_start(self, key):
+ self._key_hold_tid = gobject.timeout_add(1000, self._on_key_held, key)
+
+ def _key_hold_end(self, key):
+ if self._key_hold_tid != 0:
+ gobject.source_remove(self._key_hold_tid)
+ self._key_hold_tid = 0
+
+ def _on_key_held(self, key):
+ self._key_hold_tid = 0
+ if key.sub_keys:
+ key.sub_keys.show_subkeys(self.get_toplevel())
+ return False
+
+ def _clicked_normal_key(self, key):
+ if self._key_hold_tid == 0:
+ return
+ self._pressed_normal_key(key)
+ self._released_normal_key(key)
+
def _pressed_normal_key(self, key):
- self.vk.keyval_press(key.value)
+ self.vk.keyval_press(key.keyval)
def _released_normal_key(self, key):
- self.vk.keyval_release(key.value)
+ self.vk.keyval_release(key.keyval)
while True:
try:
mod = self.depressed_mods.pop()
@@ -407,7 +463,11 @@ class CaribouKeyboard(Gtk.Notebook):
mod.set_active (False)
def _pressed_layout_switcher_key(self, key):
- self._switch_to_layout(key.value)
+ _, group, variant = self.vk.get_current_group()
+ self._switch_to_layout('%s_%s' % (group, variant), key.toggle)
+
+ def _on_group_changed(self, vk, groupid, group, variant):
+ self._switch_to_default('%s_%s' % (group, variant))
def _toggled_mask_key(self, key):
if key.get_active():
@@ -430,13 +490,19 @@ class CaribouKeyboard(Gtk.Notebook):
p.run()
p.destroy()
- def _switch_to_layout(self, name):
- n_pages = self.get_n_pages()
- for i in range(n_pages):
- if self.get_nth_page(i).layout_name == name:
- self.set_current_page(i)
- self.current_page = i
- break
+ def _switch_to_default(self, group):
+ try:
+ i = min(self.layouts[group].values())
+ except KeyError:
+ i = 0
+ self.set_current_page(i)
+
+ def _switch_to_layout(self, group, level):
+ if self.layouts.has_key(group):
+ if self.layouts[group].has_key(level):
+ self.set_current_page(self.layouts[group][level])
+ return
+ self._switch_to_default(group)
def get_current_layout(self):
i = self.get_current_page()
@@ -450,10 +516,9 @@ if __name__ == "__main__":
signal.signal(signal.SIGINT, signal.SIG_DFL)
w = Gtk.Window()
+ w.set_accept_focus(False)
kb = CaribouKeyboard()
- kb.load_kb('data/keyboards/qwerty.xml')
-
w.add(kb)
w.show_all()
diff --git a/caribou/ui/main.py b/caribou/ui/main.py
index 7e861b8..de2302c 100644
--- a/caribou/ui/main.py
+++ b/caribou/ui/main.py
@@ -83,8 +83,8 @@ class Caribou:
kb = kb_factory()
self.window = window_factory(kb)
self._register_event_listeners()
- SettingsManager.layout.connect("value-changed",
- self._on_layout_changed)
+ SettingsManager.geometry.connect("value-changed",
+ self._on_geometry_changed)
# Scanning
self.scan_master = ScanMaster(self.window, kb)
@@ -157,7 +157,7 @@ class Caribou:
else:
self.scan_master.stop()
- def _on_layout_changed(self, setting, val):
+ def _on_geometry_changed(self, setting, val):
self._deregister_event_listeners()
self.window.destroy()
self._update_window()
diff --git a/caribou/ui/window.py b/caribou/ui/window.py
index 471ee1c..2dc7423 100644
--- a/caribou/ui/window.py
+++ b/caribou/ui/window.py
@@ -22,7 +22,6 @@
from caribou import data_path
from opacity import ProximityWindowBase
-from caribou.common.settings_manager import SettingsManager
from gi.repository import Gtk
from gi.repository import Gdk
@@ -31,8 +30,6 @@ import os
import sys
import gobject
-CARIBOU_LAYOUT_DIR = 'keyboards'
-
Clutter.init("caribou")
class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
@@ -66,10 +63,6 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
self._default_placement = default_placement or \
CaribouWindowPlacement()
- conf_file_path = self._get_keyboard_conf()
- if conf_file_path:
- text_entry_mech.load_kb(conf_file_path)
-
self.connect('show', self._on_window_show)
# animation
@@ -118,7 +111,6 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
self.keyboard.destroy()
super(Gtk.Window, self).destroy()
-
def set_cursor_location(self, cursor_location):
self._cursor_location = cursor_location
self._update_position()
@@ -205,26 +197,6 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
return offset
- def _get_keyboard_conf(self):
- layout = SettingsManager.layout.value
- conf_file_path = os.path.join(data_path, CARIBOU_LAYOUT_DIR, layout)
-
- if os.path.exists(conf_file_path):
- return conf_file_path
- else:
- json_path = '%s.json' % conf_file_path
-
- if os.path.exists(json_path):
- return json_path
-
- xml_path = '%s.xml' % conf_file_path
-
- if os.path.exists(xml_path):
- return xml_path
-
- raise Exception("Could not load keyboard %s" % conf_file_path)
-
-
def show_all(self):
Gtk.Window.show_all(self)
self.keyboard.show_all()