[caribou: 16/22] Major re-work of Python modules:



commit e1ae6ee91507a47cbe2fe537f3107c17390bc0e2
Author: Eitan Isaacson <eitan monotonous org>
Date:   Sun Apr 24 16:57:28 2011 -0700

    Major re-work of Python modules:
    
    * Created Antler to use new keyboard model via PyGI
    * Rearanged settings to accomodate view/model seperation.
    * Created in preferences executable, it will be a standalone
      generic caribou settings UI where we will eventually get the important
      stuff in GNOME's control panel.
    * DBusified the UI.

 .gitignore                                       |    3 +-
 bin/Makefile.am                                  |    2 +-
 bin/caribou-preferences.in                       |   32 ++
 bin/caribou.in                                   |    9 +-
 caribou/Makefile.am                              |   10 +-
 caribou/__init__.py                              |    3 +-
 caribou/antler/Makefile.am                       |   10 +
 caribou/antler/keyboard_view.py                  |  164 +++++++
 caribou/antler/main.py                           |   30 ++
 caribou/{ui => antler}/window.py                 |  147 ++++--
 caribou/common/Makefile.am                       |   11 -
 caribou/common/__init__.py                       |    1 -
 caribou/common/const.py                          |   43 --
 caribou/common/settings.py                       |  161 -------
 caribou/daemon/Makefile.am                       |    8 +
 caribou/daemon/__init__.py                       |    1 +
 caribou/daemon/main.py                           |  150 ++++++
 caribou/{ui => }/i18n.py.in                      |    0
 caribou/settings/Makefile.am                     |   11 +
 caribou/settings/__init__.py                     |    5 +
 caribou/settings/caribou_settings.py             |   64 +++
 caribou/{ui => settings}/preferences_window.py   |   70 ++--
 caribou/{common => settings}/setting_types.py    |   10 +
 caribou/{common => settings}/settings_manager.py |    9 +-
 caribou/ui/Makefile.am                           |   16 -
 caribou/ui/__init__.py                           |    1 -
 caribou/ui/keyboard.py                           |  535 ----------------------
 caribou/ui/main.py                               |  275 -----------
 caribou/ui/opacity.py                            |   83 ----
 caribou/ui/scan.py                               |  216 ---------
 configure.ac                                     |   10 +-
 data/Makefile.am                                 |    4 +-
 data/layouts/Makefile.am                         |    2 +-
 data/layouts/natural/Makefile.am                 |    8 -
 data/layouts/touch/Makefile.am                   |    8 +
 data/layouts/{natural => touch}/ara.json         |    0
 data/layouts/{natural => touch}/il.json          |    0
 data/layouts/{natural => touch}/us.json          |    0
 po/POTFILES.in                                   |    7 +-
 tools/make_schema.py                             |   84 ++++
 40 files changed, 746 insertions(+), 1457 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index a0ba962..de388bc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,10 +9,11 @@ aclocal.m4
 autom4te.cache
 *.make
 bin/caribou
+bin/caribou-preferences
 install-sh
 missing
 py-compile
-caribou/ui/i18n.py
+caribou/i18n.py
 mkinstalldirs
 po/Makefile.in.in
 po/POTFILES
diff --git a/bin/Makefile.am b/bin/Makefile.am
index cae01c9..e7797ec 100644
--- a/bin/Makefile.am
+++ b/bin/Makefile.am
@@ -1,4 +1,4 @@
-bin_SCRIPTS = caribou
+bin_SCRIPTS = caribou caribou-preferences
 
 CLEANFILES = $(bin_SCRIPTS)
 
diff --git a/bin/caribou-preferences.in b/bin/caribou-preferences.in
new file mode 100644
index 0000000..a745bab
--- /dev/null
+++ b/bin/caribou-preferences.in
@@ -0,0 +1,32 @@
+#!/usr/bin/python
+
+import sys, os
+
+libs = os.path.join('@prefix@', 'lib', 'python PYTHON_VERSION@', 'site-packages')
+
+# This might be run from the build dir.
+_dirname = os.path.dirname(__file__)
+if _dirname != "@prefix@/bin":
+    libs = os.path.normpath(os.path.join(_dirname, '..'))
+    
+sys.path.insert(1, libs)
+
+from gi.repository import Gtk
+from caribou.settings.settings_manager import SettingsManager
+from caribou.settings import CaribouSettings
+from caribou.settings.preferences_window import PreferencesWindow
+
+import signal
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+w = PreferencesWindow(CaribouSettings())
+
+w.connect("delete-event", lambda x, y: Gtk.main_quit())
+
+w.show_all()
+
+try:
+    Gtk.main()
+except KeyboardInterrupt:
+    Gtk.main_quit()
+
diff --git a/bin/caribou.in b/bin/caribou.in
index fb31d47..a0aa03f 100644
--- a/bin/caribou.in
+++ b/bin/caribou.in
@@ -53,9 +53,7 @@ else:
     import caribou.ui
     caribou.data_path = os.path.join("@prefix@", "share", "caribou")
 
-import caribou.ui.window as window
-import caribou.ui.keyboard as keyboard
-import caribou.ui.main as main
+from caribou.daemon import CaribouDaemon
 
 _ = gettext.gettext
     
@@ -71,10 +69,9 @@ if __name__ == "__main__":
                       help="print debug messages on stdout")
     (options, args) = parser.parse_args()
 
-    main.debug = options.debug
+    #main.debug = options.debug
 
-    caribou = main.Caribou()
-    caribou.window.hide()
+    caribou = CaribouDaemon()
  
     try:
         pyatspi.Registry.start()
diff --git a/caribou/Makefile.am b/caribou/Makefile.am
index 962a2b4..713d8ad 100644
--- a/caribou/Makefile.am
+++ b/caribou/Makefile.am
@@ -1,11 +1,15 @@
 cariboudir = $(pkgpythondir)/
 
 caribou_PYTHON = \
-	__init__.py 
+	__init__.py \
+	i18n.py 
 
 SUBDIRS = \
-        common/ \
-        ui/
+        antler/ \
+        settings/ \
+	daemon/
+
+DISTCLEANFILES = i18n.py
 
 clean-local:
 	rm -rf *.pyc *.pyo
diff --git a/caribou/__init__.py b/caribou/__init__.py
index 1aadcdc..cf8fdc7 100644
--- a/caribou/__init__.py
+++ b/caribou/__init__.py
@@ -1 +1,2 @@
-data_path = "data/"
+from i18n import _
+APP_NAME=_("Caribou")
diff --git a/caribou/antler/Makefile.am b/caribou/antler/Makefile.am
new file mode 100644
index 0000000..51efe0f
--- /dev/null
+++ b/caribou/antler/Makefile.am
@@ -0,0 +1,10 @@
+caribou_antlerdir = $(pkgpythondir)/antler/
+
+caribou_antler_PYTHON = \
+	__init__.py  \
+	keyboard_view.py  \
+	main.py \
+	window.py
+
+clean-local:
+	rm -rf *.pyc *.pyo
diff --git a/caribou/antler/__init__.py b/caribou/antler/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/caribou/antler/keyboard_view.py b/caribou/antler/keyboard_view.py
new file mode 100644
index 0000000..6cea11b
--- /dev/null
+++ b/caribou/antler/keyboard_view.py
@@ -0,0 +1,164 @@
+from caribou.settings.preferences_window import PreferencesDialog
+from caribou.settings import CaribouSettings
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import Caribou
+import gobject
+
+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 AntlerKey(Gtk.Button):
+    def __init__(self, key):
+        gobject.GObject.__init__(self)
+        self.caribou_key = key
+        self.connect("pressed", self._on_pressed)
+        self.connect("released", self._on_released)
+        self.set_label(self._get_key_label())
+        if key.props.name == "Caribou_Prefs":
+            key.connect("key-clicked", self._on_prefs_clicked)
+        if key.get_extended_keys ():
+            self._sublevel = AntlerSubLevel(self)
+
+    def _on_prefs_clicked(self, key):
+        p = PreferencesDialog(CaribouSettings())
+        p.show_all()
+        p.run()
+        p.destroy()
+
+    def _get_key_label(self):
+        label = self.caribou_key.props.name
+        if PRETTY_LABELS.has_key(self.caribou_key.props.name):
+            label = PRETTY_LABELS[self.caribou_key.props.name]
+        elif self.caribou_key.props.name.startswith('Caribou_'):
+            label = self.caribou_key.name.replace('Caribou_', '')
+        else:
+            unichar = unichr(Gdk.keyval_to_unicode(self.caribou_key.props.keyval))
+            if not unichar.isspace() and unichar != u'\x00':
+                label = unichar
+
+        return label
+
+    def _on_pressed(self, button):
+        self.caribou_key.press()
+
+    def _on_released(self, button):
+        self.caribou_key.release()
+
+    def do_get_preferred_width_for_height(self, w):
+        return (w, w)
+
+    def do_get_request_mode(self):
+        return Gtk.SizeRequestMode.HEIGHT_FOR_WIDTH
+
+class AntlerSubLevel(Gtk.Window):
+    def __init__(self, key):
+        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)
+
+        key.caribou_key.connect("notify::show-subkeys", self._on_show_subkeys)
+        self._key = key
+
+        layout = AntlerLayout()
+        layout.add_row(key.caribou_key.get_extended_keys())
+        self.add(layout)
+
+    def _on_show_subkeys(self, key, prop):
+        parent = self._key.get_toplevel()
+        if key.props.show_subkeys:
+            self.set_transient_for(parent)
+            parent.set_sensitive(False)
+            self.show_all()
+        else:
+            parent.set_sensitive(True)
+            self.hide()
+
+class AntlerLayout(Gtk.Grid):
+    KEY_SPAN = 4
+
+    def __init__(self, level=None):
+        gobject.GObject.__init__(self)
+        self.set_column_homogeneous(True)
+        self.set_row_homogeneous(True)
+        self.set_row_spacing(4)
+        self.set_column_spacing(4)
+        if level:
+            self.load_rows(level.get_rows ())
+
+    def add_row(self, row, row_num=0):
+        col_num = 0
+        for i, key in enumerate(row):
+            antler_key = AntlerKey(key)
+            self.attach(antler_key,
+                        col_num + int(key.props.margin_left * self.KEY_SPAN),
+                        row_num * self.KEY_SPAN,
+                        int(self.KEY_SPAN * key.props.width),
+                        self.KEY_SPAN)
+            col_num += int((key.props.width + key.props.margin_left ) * self.KEY_SPAN)
+
+
+    def load_rows(self, rows):
+        for row_num, row in enumerate(rows):
+            self.add_row(row.get_keys(), row_num)
+
+class AntlerKeyboardView(Gtk.Notebook):
+    def __init__(self):
+        gobject.GObject.__init__(self)
+        self.set_show_tabs(False)
+        self.keyboard_model = Caribou.KeyboardModel()
+        self.keyboard_model.connect("notify::active-group", self._on_group_changed)
+        self.layers = {}
+        for gname in self.keyboard_model.get_groups():
+            group = self.keyboard_model.get_group(gname)
+            self.layers[gname] = {}
+            group.connect("notify::active-level", self._on_level_changed)
+            for lname in group.get_levels():
+                level = group.get_level(lname)
+                layout = AntlerLayout(level)
+                layout.show()
+                self.layers[gname][lname] = self.append_page(layout, None)
+
+        self._set_to_active_layer()
+
+    def _on_level_changed(self, group, prop):
+        self._set_to_active_layer()
+
+    def _on_group_changed(self, kb, prop):
+        self._set_to_active_layer()
+
+    def _set_to_active_layer(self):
+        active_group_name = self.keyboard_model.props.active_group
+        active_group = self.keyboard_model.get_group(active_group_name)
+        active_level_name = active_group.props.active_level
+
+        self.set_current_page(self.layers[active_group_name][active_level_name])
+
+
+if __name__ == "__main__":
+    import signal
+    signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+    w = Gtk.Window()
+    w.set_accept_focus(False)
+
+    kb = AntlerKeyboardView()
+    w.add(kb)
+
+    w.show_all()
+
+    Gtk.main()
diff --git a/caribou/antler/main.py b/caribou/antler/main.py
new file mode 100644
index 0000000..ce0eca3
--- /dev/null
+++ b/caribou/antler/main.py
@@ -0,0 +1,30 @@
+from gi.repository import Caribou
+from window import AntlerWindowEntry
+from keyboard_view import AntlerKeyboardView
+import gobject
+
+class AntlerKeyboardService(Caribou.KeyboardService):
+    def __init__(self):
+        gobject.GObject.__init__(self)
+        self.register_keyboard("Antler")
+        self.window = AntlerWindowEntry(AntlerKeyboardView())
+
+    def run(self):
+        loop = gobject.MainLoop()
+        loop.run()
+
+    def do_show(self):
+        self.window.show_all()
+
+    def do_hide(self):
+        self.window.hide()
+
+    def do_set_cursor_location (self, x, y, w, h):
+        self.window.set_cursor_location(x, y, w, h)
+
+    def do_set_entry_location (self, x, y, w, h):
+        self.window.set_entry_location(x, y, w, h)
+
+if __name__ == "__main__":
+    antler_keyboard_service = AntlerKeyboardService()
+    antler_keyboard_service.run()
diff --git a/caribou/ui/window.py b/caribou/antler/window.py
similarity index 70%
rename from caribou/ui/window.py
rename to caribou/antler/window.py
index 2dc7423..973824a 100644
--- a/caribou/ui/window.py
+++ b/caribou/antler/window.py
@@ -20,9 +20,6 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
-from caribou import data_path
-from opacity import ProximityWindowBase
-
 from gi.repository import Gtk
 from gi.repository import Gdk
 from gi.repository import Clutter
@@ -30,10 +27,71 @@ import os
 import sys
 import gobject
 
-Clutter.init("caribou")
+Clutter.init("antler")
+
+class ProximityWindowBase(object):
+    def __init__(self, min_alpha=1.0, max_alpha=1.0, max_distance=100):
+        if self.__class__ == ProximityWindowBase:
+            raise TypeError, \
+                "ProximityWindowBase is an abstract class, " \
+                "must be subclassed with a Gtk.Window"
+        self.connect('map-event', self.__onmapped)
+        self.max_distance = max_distance
+        if max_alpha < min_alpha:
+            raise ValueError, "min_alpha can't be larger than max_alpha"
+        self.min_alpha = min_alpha
+        self.max_alpha = max_alpha
+
+    def __onmapped(self, obj, event):
+        if self.is_composited():
+            self.set_opacity(self.max_alpha)
+            if self.max_alpha != self.min_alpha:
+                # Don't waste CPU if the max and min are equal.
+                glib.timeout_add(80, self._proximity_check)
+
+    def _proximity_check(self):
+        px, py = self.get_pointer()
+
+        ww = self.get_allocated_width()
+        wh = self.get_allocated_height()
+
+        distance =  self._get_distance_to_bbox(px, py, ww, wh)
 
-class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
-    __gtype_name__ = "CaribouWindow"
+        opacity = (self.max_alpha - self.min_alpha) * \
+            (1 - min(distance, self.max_distance)/self.max_distance)
+        opacity += self.min_alpha
+
+        self.set_opacity(opacity)
+        return self.props.visible
+
+    def _get_distance_to_bbox(self, px, py, bw, bh):
+        if px < 0:
+            x_distance = float(abs(px))
+        elif px > bw:
+            x_distance = float(px - bw)
+        else:
+            x_distance = 0.0
+
+        if py < 0:
+            y_distance = float(abs(px))
+        elif py > bh:
+            y_distance = float(py - bh)
+        else:
+            y_distance = 0.0
+
+        if y_distance == 0 and x_distance == 0:
+            return 0.0
+        elif y_distance != 0 and x_distance == 0:
+            return y_distance
+        elif y_distance == 0 and x_distance != 0:
+            return x_distance
+        else:
+            x2 = 0 if x_distance > 0 else bw
+            y2 = 0 if y_distance > 0 else bh
+            return sqrt((px - x2)**2 + (py - y2)**2)
+
+class AntlerWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
+    __gtype_name__ = "AntlerWindow"
     __gproperties__ = { 
         'animated-window-position' : (gobject.TYPE_PYOBJECT, 'Window position',
                                       'Window position in X, Y coordinates',
@@ -49,7 +107,7 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
                                      max_alpha=max_alpha,
                                      max_distance=max_distance)
 
-        self.set_name("CaribouWindow")
+        self.set_name("AntlerWindow")
 
         self._vbox = Gtk.VBox()
         self.add(self._vbox)
@@ -61,7 +119,7 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
         self._cursor_location = Rectangle()
         self._entry_location = Rectangle()
         self._default_placement = default_placement or \
-            CaribouWindowPlacement()
+            AntlerWindowPlacement()
 
         self.connect('show', self._on_window_show)
 
@@ -111,12 +169,12 @@ 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
+    def set_cursor_location(self, x, y, w, h):
+        self._cursor_location = Rectangle(x, y, w, h)
         self._update_position()
 
-    def set_entry_location(self, entry_location):
-        self._entry_location = entry_location
+    def set_entry_location(self, x, y, w, h):
+        self._entry_location = Rectangle(x, y, w, h)
         self._update_position()
 
     def set_default_placement(self, default_placement):
@@ -174,25 +232,25 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
     def _calculate_axis(self, axis_placement, root_bbox):
         bbox = root_bbox
 
-        if axis_placement.stickto == CaribouWindowPlacement.CURSOR:
+        if axis_placement.stickto == AntlerWindowPlacement.CURSOR:
             bbox = self._cursor_location
-        elif axis_placement.stickto == CaribouWindowPlacement.ENTRY:
+        elif axis_placement.stickto == AntlerWindowPlacement.ENTRY:
             bbox = self._entry_location
 
         offset = axis_placement.get_offset(bbox.x, bbox.y)
 
-        if axis_placement.align == CaribouWindowPlacement.END:
+        if axis_placement.align == AntlerWindowPlacement.END:
             offset += axis_placement.get_length(bbox.width, bbox.height)
-            if axis_placement.gravitate == CaribouWindowPlacement.INSIDE:
+            if axis_placement.gravitate == AntlerWindowPlacement.INSIDE:
                 offset -= axis_placement.get_length(
                     self.get_allocated_width(),
                     self.get_allocated_height())
-        elif axis_placement.align == CaribouWindowPlacement.START:
-            if axis_placement.gravitate == CaribouWindowPlacement.OUTSIDE:
+        elif axis_placement.align == AntlerWindowPlacement.START:
+            if axis_placement.gravitate == AntlerWindowPlacement.OUTSIDE:
                 offset -= axis_placement.get_length(
                     self.get_allocated_width(),
                     self.get_allocated_height())
-        elif axis_placement.align == CaribouWindowPlacement.CENTER:
+        elif axis_placement.align == AntlerWindowPlacement.CENTER:
             offset += axis_placement.get_length(bbox.width, bbox.height)/2
 
         return offset
@@ -211,18 +269,18 @@ class CaribouWindow(Gtk.Window, Clutter.Animatable, ProximityWindowBase):
         req = child.size_request()
         self.resize(req.width + border, req.height + border)
 
-class CaribouWindowDocked(CaribouWindow):
-    __gtype_name__ = "CaribouWindowDocked"
+class AntlerWindowDocked(AntlerWindow):
+    __gtype_name__ = "AntlerWindowDocked"
     
     def __init__(self, text_entry_mech):
-        placement = CaribouWindowPlacement(
-            xalign=CaribouWindowPlacement.END,
-            yalign=CaribouWindowPlacement.START,
-            xstickto=CaribouWindowPlacement.SCREEN,
-            ystickto=CaribouWindowPlacement.SCREEN,
-            xgravitate=CaribouWindowPlacement.INSIDE)
+        placement = AntlerWindowPlacement(
+            xalign=AntlerWindowPlacement.END,
+            yalign=AntlerWindowPlacement.START,
+            xstickto=AntlerWindowPlacement.SCREEN,
+            ystickto=AntlerWindowPlacement.SCREEN,
+            xgravitate=AntlerWindowPlacement.INSIDE)
 
-        CaribouWindow.__init__(self, text_entry_mech, placement)
+        AntlerWindow.__init__(self, text_entry_mech, placement)
 
         self.connect('map-event', self.__onmapped)
 
@@ -240,32 +298,32 @@ class CaribouWindowDocked(CaribouWindow):
 
     def hide(self):
         animation = self._roll_out()
-        animation.connect('completed', lambda x: CaribouWindow.hide(self)) 
+        animation.connect('completed', lambda x: AntlerWindow.hide(self)) 
 
-class CaribouWindowEntry(CaribouWindow):
-    __gtype_name__ = "CaribouWindowEntry"
+class AntlerWindowEntry(AntlerWindow):
+    __gtype_name__ = "AntlerWindowEntry"
 
     def __init__(self, text_entry_mech):
-        placement = CaribouWindowPlacement(
-            xalign=CaribouWindowPlacement.START,
-            xstickto=CaribouWindowPlacement.ENTRY,
-            ystickto=CaribouWindowPlacement.ENTRY,
-            xgravitate=CaribouWindowPlacement.INSIDE,
-            ygravitate=CaribouWindowPlacement.OUTSIDE)
+        placement = AntlerWindowPlacement(
+            xalign=AntlerWindowPlacement.START,
+            xstickto=AntlerWindowPlacement.ENTRY,
+            ystickto=AntlerWindowPlacement.ENTRY,
+            xgravitate=AntlerWindowPlacement.INSIDE,
+            ygravitate=AntlerWindowPlacement.OUTSIDE)
 
-        CaribouWindow.__init__(self, text_entry_mech, placement)
+        AntlerWindow.__init__(self, text_entry_mech, placement)
 
 
     def _calculate_axis(self, axis_placement, root_bbox):
-        offset = CaribouWindow._calculate_axis(self, axis_placement, root_bbox)
+        offset = AntlerWindow._calculate_axis(self, axis_placement, root_bbox)
         if axis_placement.axis == 'y':
             if offset + self.get_allocated_height() > root_bbox.height + root_bbox.y:
-                new_axis_placement = axis_placement.copy(align=CaribouWindowPlacement.START)
-                offset = CaribouWindow._calculate_axis(self, new_axis_placement, root_bbox)
+                new_axis_placement = axis_placement.copy(align=AntlerWindowPlacement.START)
+                offset = AntlerWindow._calculate_axis(self, new_axis_placement, root_bbox)
 
         return offset
 
-class CaribouWindowPlacement(object):
+class AntlerWindowPlacement(object):
     START = 'start'
     END = 'end'
     CENTER = 'center'
@@ -326,7 +384,6 @@ class CaribouWindowPlacement(object):
                                      ystickto or self.CURSOR,
                                      ygravitate or self.OUTSIDE)
 
-
 class Rectangle(object):
     def __init__(self, x=0, y=0, width=0, height=0):
         self.x = x
@@ -335,11 +392,11 @@ class Rectangle(object):
         self.height = height
 
 if __name__ == "__main__":
-    import keyboard
+    import keyboard_view
     import signal
     signal.signal(signal.SIGINT, signal.SIG_DFL)
 
-    w = CaribouWindowDocked(keyboard.CaribouKeyboard())
+    w = AntlerWindowDocked(keyboard_view.AntlerKeyboardView())
     w.show_all()
 
     try:
diff --git a/caribou/daemon/Makefile.am b/caribou/daemon/Makefile.am
new file mode 100644
index 0000000..859c29e
--- /dev/null
+++ b/caribou/daemon/Makefile.am
@@ -0,0 +1,8 @@
+caribou_daemondir = $(pkgpythondir)/daemon/
+
+caribou_daemon_PYTHON = \
+	__init__.py \
+	main.py
+
+clean-local:
+	rm -rf *.pyc *.pyo
diff --git a/caribou/daemon/__init__.py b/caribou/daemon/__init__.py
new file mode 100644
index 0000000..fd9812d
--- /dev/null
+++ b/caribou/daemon/__init__.py
@@ -0,0 +1 @@
+from main import CaribouDaemon
diff --git a/caribou/daemon/main.py b/caribou/daemon/main.py
new file mode 100644
index 0000000..b9df5cf
--- /dev/null
+++ b/caribou/daemon/main.py
@@ -0,0 +1,150 @@
+import pyatspi
+import dbus
+from gi.repository import Gio
+from string import Template
+
+from caribou.i18n import _
+from caribou import APP_NAME
+
+debug = False
+
+class CaribouDaemon:
+    def __init__(self, keyboard_name="Antler"):
+        if not self._get_a11y_enabled():
+            self._show_no_a11y_dialogs()
+        bus = dbus.SessionBus()
+        try:
+            dbus_obj = bus.get_object("org.gnome.Caribou.%s" % keyboard_name,
+                                      "/org/gnome/Caribou/%s" % keyboard_name)
+        except dbus.DBusException:
+            print "%s is not running, and is not provided by any .service file" % \
+                keyboard_name
+            return
+        self.keyboard_proxy = dbus.Interface(dbus_obj, "org.gnome.Caribou.Keyboard")
+        self._current_acc = None
+        self._register_event_listeners()
+
+    def _show_no_a11y_dialogs(self):
+        from gi.repository import Gtk
+        msgdialog = Gtk.MessageDialog(None,
+                                      Gtk.DialogFlags.MODAL,
+                                      Gtk.MessageType.QUESTION,
+                                      Gtk.ButtonsType.YES_NO,
+                                      _("In order to use %s, accessibility needs "
+                                        "to be enabled. Do you want to enable "
+                                        "it now?") % APP_NAME)
+        resp = msgdialog.run()
+        if resp == Gtk.ResponseType.NO:
+            msgdialog.destroy()
+            quit()
+        if resp == Gtk.ResponseType.YES:
+            settings = Gio.Settings('org.gnome.desktop.interface')
+            atspi = settings.set_boolean("toolkit-accessibility", True)
+            msgdialog2 = Gtk.MessageDialog(msgdialog,
+                                           Gtk.DialogFlags.MODAL,
+                                           Gtk.MessageType.INFO,
+                                           Gtk.ButtonsType.OK,
+                                           _("Accessibility has been enabled. "
+                                             "Log out and back in again to use "
+                                             "%s." % APP_NAME))
+            msgdialog2.run()
+            msgdialog2.destroy()
+            msgdialog.destroy()
+            quit()
+
+
+    def _register_event_listeners(self):
+        pyatspi.Registry.registerEventListener(
+            self.on_focus, "object:state-changed:focused")
+        pyatspi.Registry.registerEventListener(self.on_focus, "focus")
+        pyatspi.Registry.registerEventListener(
+            self.on_text_caret_moved, "object:text-caret-moved")
+
+    def _deregister_event_listeners(self):
+        pyatspi.Registry.deregisterEventListener(
+            self.on_focus, "object:state-changed:focused")
+        pyatspi.Registry.deregisterEventListener(self.on_focus, "focus")
+        pyatspi.Registry.deregisterEventListener(
+            self.on_text_caret_moved, "object:text-caret-moved")
+
+    def _get_a11y_enabled(self):
+        try:
+            try:
+                settings = Gio.Settings('org.gnome.desktop.interface')
+                atspi = settings.get_boolean("toolkit-accessibility")
+                return atspi
+            except:
+                raise
+                from gi.repository import GConf
+                gconfc = GConf.Client.get_default()
+                atspi1 = gconfc.get_bool(
+                    "/desktop/gnome/interface/accessibility")
+                atspi2 = gconfc.get_bool(
+                    "/desktop/gnome/interface/accessibility2")
+                return atspi1 or atspi2
+        except:
+            raise
+            return False
+
+    def on_text_caret_moved(self, event):
+        if self._current_acc == event.source:
+            text = self._current_acc.queryText()
+            x, y, w, h = text.getCharacterExtents(text.caretOffset,
+                                                  pyatspi.DESKTOP_COORDS)
+            if (x, y, w, h) == (0, 0, 0, 0):
+                component = self._current_acc.queryComponent()
+                bb = component.getExtents(pyatspi.DESKTOP_COORDS)
+                x, y, w, h = bb.x, bb.y, bb.width, bb.height
+
+            self.keyboard_proxy.SetCursorLocation(x, y, w, h)
+            if debug == True:
+                print "object:text-caret-moved in", event.host_application.name,
+                print event.detail1, event.source.description
+
+    def _set_entry_location(self, acc):
+        text = acc.queryText()
+        bx, by, bw, bh = text.getCharacterExtents(text.caretOffset,
+                                                        pyatspi.DESKTOP_COORDS)
+
+        component = acc.queryComponent()
+        entry_bb = component.getExtents(pyatspi.DESKTOP_COORDS)
+
+        if (bx, by, bw, bh) == (0, 0, 0, 0):
+            bx, by, bw, bh = entry_bb.x, entry_bb.y, entry_bb.width, entry_bb.height
+
+        self.keyboard_proxy.SetCursorLocation(bx, by, bw, bh)
+
+        self.keyboard_proxy.SetEntryLocation(entry_bb.x, entry_bb.y,
+                                             entry_bb.width, entry_bb.height)
+
+        self.keyboard_proxy.Show()
+
+    def on_focus(self, event):
+        acc = event.source
+        source_role = acc.getRole()
+        if acc.getState().contains(pyatspi.STATE_EDITABLE) or \
+                source_role == pyatspi.ROLE_TERMINAL:
+            if source_role in (pyatspi.ROLE_TEXT,
+                               pyatspi.ROLE_PARAGRAPH,
+                               pyatspi.ROLE_PASSWORD_TEXT,
+                               pyatspi.ROLE_TERMINAL,
+                               pyatspi.ROLE_ENTRY):
+                if event.type.startswith("focus") or event.detail1 == 1:
+                    self._set_entry_location(acc)
+                    self._current_acc = event.source
+                    if debug == True:
+                        print "enter text widget in", event.host_application.name
+                elif event.detail1 == 0 and acc == self._current_acc:
+                    self.keyboard_proxy.Hide()
+                    self._current_acc = None
+                    if debug == True:
+                        print "leave text widget in", event.host_application.name
+            else:
+                if debug == True:
+                    print _("WARNING - Caribou: unhandled editable widget:"), \
+                        event.source
+
+    def clean_exit(self):
+        self._deregister_event_listeners()
+
+
diff --git a/caribou/ui/i18n.py.in b/caribou/i18n.py.in
similarity index 100%
rename from caribou/ui/i18n.py.in
rename to caribou/i18n.py.in
diff --git a/caribou/settings/Makefile.am b/caribou/settings/Makefile.am
new file mode 100644
index 0000000..468adab
--- /dev/null
+++ b/caribou/settings/Makefile.am
@@ -0,0 +1,11 @@
+caribou_settingsdir = $(pkgpythondir)/settings/
+
+caribou_settings_PYTHON = \
+	__init__.py \
+	preferences_window.py \
+	settings_manager.py \
+	caribou_settings.py  \
+	setting_types.py
+
+clean-local:
+	rm -rf *.pyc *.pyo
diff --git a/caribou/settings/__init__.py b/caribou/settings/__init__.py
new file mode 100644
index 0000000..0ec8b40
--- /dev/null
+++ b/caribou/settings/__init__.py
@@ -0,0 +1,5 @@
+GSETTINGS_SCHEMA = "org.gnome.caribou"
+
+from caribou_settings import CaribouSettings
+
+AllSettings = [CaribouSettings]
diff --git a/caribou/settings/caribou_settings.py b/caribou/settings/caribou_settings.py
new file mode 100644
index 0000000..638e1c4
--- /dev/null
+++ b/caribou/settings/caribou_settings.py
@@ -0,0 +1,64 @@
+from caribou.settings.setting_types import *
+from caribou.i18n import _
+
+CaribouSettings = SettingsTopGroup(
+    _("Caribou Preferences"), "/org/gnome/caribou/", "org.gnome.caribou",
+    [SettingsGroup("keyboard", _("Keyboard"), [
+                SettingsGroup("general", _("General"), [
+                        StringSetting(
+                            "keyboard_type", _("Keyboard Type"), "touch",
+                            _("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=[(('touch'), _('Touch'))])]),
+                ]),
+        SettingsGroup("scanning", _("Scanning"), [
+                BooleanSetting(
+                    "scan_enabled", _("Enable scanning"), False,
+                    _("Enable switch scanning"),
+                    insensitive_when_false=["scanning_general",
+                                            "scanning_input"]),
+                SettingsGroup("scanning_general", _("General"), [
+                        StringSetting("scanning_type", _("Scanning mode"),
+                                      "block",
+                                      _("Scanning type, block or row"),
+                                      allowed=[("block", _("Block")),
+                                                ("row", _("Row"))]),
+                        FloatSetting("step_time", _("Step time"), 1.0,
+                                     _("Time between key transitions"),
+                                     min=0.1, max=60.0),
+                        BooleanSetting("reverse_scanning",
+                                       _("Reverse scanning"), False,
+                                       _("Scan in reverse order"))
+                        ]),
+                SettingsGroup("scanning_input", _("Input"), [
+                        StringSetting("switch_type", _("Switch device"),
+                                      "keyboard",
+                                      _("Switch device, keyboard or mouse"),
+                                      entry_type=ENTRY_RADIO,
+                                      allowed=[("keyboard", _("Keyboard")),
+                                               ("mouse", _("Mouse"))],
+                                      children=[
+                                StringSetting("keyboard_key", _("Switch key"),
+                                              "Shift_R",
+                                              _(
+                                        "Key to use with scanning mode"),
+                                              allowed=[
+                                        ("Shift_R", _("Right shift")),
+                                        ("Shift_L", _("Left shift")),
+                                        ("ISO_Level3_Shift", _("Alt Gr")),
+                                        ("Num_Lock", _("Num lock"))]),
+                                StringSetting("mouse_button", _("Switch button"),
+                                              "2",
+                                              _(
+                                        "Mouse button to use in the scanning "
+                                        "mode"), 
+                                              allowed=[("1", _("Button 1")),
+                                                       ("2", _("Button 2")),
+                                                       ("3", _("Button 3"))])
+                                ]),
+                        ]),
+                ])
+        ])
diff --git a/caribou/ui/preferences_window.py b/caribou/settings/preferences_window.py
similarity index 91%
rename from caribou/ui/preferences_window.py
rename to caribou/settings/preferences_window.py
index 0158ffb..58d1b4c 100644
--- a/caribou/ui/preferences_window.py
+++ b/caribou/settings/preferences_window.py
@@ -18,47 +18,24 @@
 # along with this program; if not, write to the Free Software Foundation,
 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 
-import caribou.common.const as const
-from caribou.common.setting_types import *
-from caribou.common.settings_manager import SettingsManager
+from caribou.settings.setting_types import *
 
-from gi.repository import GConf
 import gobject
 from gi.repository import Gdk
 from gi.repository import Gtk
-from gi.repository import Pango
-import sys
-import virtkey
-import os
-import traceback
-from i18n import _
-try:
-    import json
-except ImportError:
-    HAS_JSON = False
-else:
-    HAS_JSON = True
-import xml.etree.ElementTree as ET
-from xml.dom import minidom
-import gettext
-import i18n
-
-class PreferencesWindow(Gtk.Dialog):
-    __gtype_name__ = "PreferencesWindow"
-
-    def __init__(self):
-        gobject.GObject.__init__(self)
-        self.set_title(_("Caribou Preferences"))
-        self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
-        self.set_border_width(6)
 
+class AbstractPreferencesUI:
+    def populate_settings(self, groups):
         notebook = Gtk.Notebook()
-        vbox = self.get_content_area()
-        vbox.add(notebook)
-        self._populate_settings(notebook, SettingsManager.groups)
+        self._populate_settings(notebook, groups)
+        if notebook.get_n_pages() == 1:
+            notebook.set_show_tabs(False)
+            
+        return notebook
 
     def _populate_settings(self, parent, setting, level=0):
         if level == 0:
+            self.set_title(setting.label)
             for s in setting:
                 vbox = Gtk.VBox()
                 parent.append_page(vbox, Gtk.Label(label=s.label))
@@ -248,11 +225,38 @@ class PreferencesWindow(Gtk.Dialog):
         self._update_setting(setting, combo.get_active_id(),
                              handler_id)
 
+class PreferencesDialog(Gtk.Dialog, AbstractPreferencesUI):
+    __gtype_name__ = "PreferencesDialog"
+
+    def __init__(self, settings_manager):
+        gobject.GObject.__init__(self)
+        self.add_button(Gtk.STOCK_CLOSE, Gtk.ResponseType.CLOSE)
+        self.set_border_width(6)
+
+        notebook = self.populate_settings(settings_manager.groups)
+        vbox = self.get_content_area()
+        vbox.add(notebook)
+
+class PreferencesWindow(Gtk.Window, AbstractPreferencesUI):
+    __gtype_name__ = "PreferencesWindow"
+
+    def __init__(self, settings_manager):
+        gobject.GObject.__init__(self)
+        self.set_border_width(6)
+
+        notebook = self.populate_settings(settings_manager.groups)
+        self.add(notebook)
+
 if __name__ == "__main__":
+    from caribou.settings.settings_manager import SettingsManager
+    from caribou.settings import CaribouSettings
+
     import signal
     signal.signal(signal.SIGINT, signal.SIG_DFL)
-    w = PreferencesWindow()
+
+    w = PreferencesDialog(CaribouSettings())
     w.show_all()
+
     try:
         w.run()
     except KeyboardInterrupt:
diff --git a/caribou/common/setting_types.py b/caribou/settings/setting_types.py
similarity index 91%
rename from caribou/common/setting_types.py
rename to caribou/settings/setting_types.py
index 109bf1b..b0e374d 100644
--- a/caribou/common/setting_types.py
+++ b/caribou/settings/setting_types.py
@@ -53,6 +53,16 @@ class Setting(gobject.GObject):
 class SettingsGroup(Setting):
     pass
 
+class SettingsTopGroup(SettingsGroup):
+    def __init__(self, label, path, schema_id, children=[]):
+        SettingsGroup.__init__(self, "_top", label, children)
+        self.path = path
+        self.schema_id = schema_id
+
+    def __call__(self):
+        from caribou.settings.settings_manager import SettingsManager
+        return SettingsManager(self)
+
 class ValueSetting(Setting):
     variant_type = ''
     entry_type=ENTRY_DEFAULT
diff --git a/caribou/common/settings_manager.py b/caribou/settings/settings_manager.py
similarity index 93%
rename from caribou/common/settings_manager.py
rename to caribou/settings/settings_manager.py
index 66119f8..1368f3a 100644
--- a/caribou/common/settings_manager.py
+++ b/caribou/settings/settings_manager.py
@@ -1,10 +1,9 @@
 import os
 from gi.repository import Gio
-from setting_types import *
-from settings import settings, GSETTINGS_SCHEMA
-import const
+from caribou.settings.setting_types import *
+from caribou.settings import GSETTINGS_SCHEMA
 
-class _SettingsManager(object):
+class SettingsManager(object):
     def __init__(self, settings):
         self.groups = settings
         self._gsettings = Gio.Settings(GSETTINGS_SCHEMA)
@@ -66,5 +65,3 @@ class _SettingsManager(object):
 
     def __call__(self):
         return self
-
-SettingsManager = _SettingsManager(settings)
diff --git a/configure.ac b/configure.ac
index 8108c43..f1d0895 100644
--- a/configure.ac
+++ b/configure.ac
@@ -72,13 +72,15 @@ AC_OUTPUT([
 Makefile
 po/Makefile.in
 caribou/Makefile
-caribou/common/Makefile
-caribou/ui/i18n.py
-caribou/ui/Makefile
+caribou/i18n.py
+caribou/antler/Makefile
+caribou/settings/Makefile
+caribou/daemon/Makefile
 bin/Makefile
 bin/caribou
+bin/caribou-preferences
 data/Makefile
 data/layouts/Makefile
-data/layouts/natural/Makefile
+data/layouts/touch/Makefile
 libcaribou/Makefile
 ])
diff --git a/data/Makefile.am b/data/Makefile.am
index 98da3b2..745443b 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -17,8 +17,8 @@ autostart_DATA   = $(autostart_in_files:.desktop.in=.desktop)
 
 EXTRA_DIST = $(desktop_in_files) $(autostart_in_files)
 
-org.gnome.caribou.gschema.xml.in: $(top_srcdir)/caribou/common/settings.py
-	PYTHONPATH=${PYTHONPATH}:$(top_srcdir) $(PYTHON) $< > $@
+org.gnome.caribou.gschema.xml.in: $(top_srcdir)/tools/make_schema.py
+	$< org.gnome.caribou
 
 CLEANFILES = $(desktop_DATA) \
 	$(autostart_DATA) \
diff --git a/data/layouts/Makefile.am b/data/layouts/Makefile.am
index 40457f3..9d82efe 100644
--- a/data/layouts/Makefile.am
+++ b/data/layouts/Makefile.am
@@ -1 +1 @@
-SUBDIRS = natural
+SUBDIRS = touch
diff --git a/data/layouts/touch/Makefile.am b/data/layouts/touch/Makefile.am
new file mode 100644
index 0000000..f18d728
--- /dev/null
+++ b/data/layouts/touch/Makefile.am
@@ -0,0 +1,8 @@
+touchlayoutsdir = $(datadir)/caribou/layouts/touch
+
+touchlayouts_DATA = \
+    ara.json \
+    il.json \
+    us.json
+
+EXTRA_DIST = $(touchlayouts_DATA)
diff --git a/data/layouts/natural/ara.json b/data/layouts/touch/ara.json
similarity index 100%
rename from data/layouts/natural/ara.json
rename to data/layouts/touch/ara.json
diff --git a/data/layouts/natural/il.json b/data/layouts/touch/il.json
similarity index 100%
rename from data/layouts/natural/il.json
rename to data/layouts/touch/il.json
diff --git a/data/layouts/natural/us.json b/data/layouts/touch/us.json
similarity index 100%
rename from data/layouts/natural/us.json
rename to data/layouts/touch/us.json
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 0a869dd..5f44bd0 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,5 +1,4 @@
 [encoding: UTF-8]
-caribou/ui/main.py
-caribou/ui/keyboard.py
-caribou/common/settings.py
-caribou/ui/preferences_window.py
+caribou/settings/caribou_settings.py
+caribou/__init__.py
+caribou/daemon/main.py
diff --git a/tools/make_schema.py b/tools/make_schema.py
new file mode 100755
index 0000000..c9ee361
--- /dev/null
+++ b/tools/make_schema.py
@@ -0,0 +1,84 @@
+#!/usr/bin/python
+
+from gi.repository import GLib
+import xml.dom.minidom
+
+import os,sys
+sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from caribou.settings import caribou_settings
+
+class SchemasMaker:
+    def __init__(self, settings):
+        self.settings = settings
+
+    def create_schemas(self):
+        doc = xml.dom.minidom.Document()
+        schemafile =  doc.createElement('schemalist')
+        schema = doc.createElement('schema')
+        schema.setAttribute("id", self.settings.schema_id)
+        schemafile.appendChild(schema)
+        self._create_schema(self.settings, doc, schema)
+
+        fp = open("%s.gschema.xml.in" % self.settings.schema_id, 'w')
+        self._pretty_xml(fp, schemafile)
+        fp.close()
+
+    def _attribs(self, e):
+        if not e.attributes.items():
+            return ""
+        return ' ' + ' '.join(['%s="%s"' % (k,v) \
+                                   for k,v in e.attributes.items()])
+
+    def _pretty_xml(self, fp, e, indent=0):
+        if not e.childNodes or \
+                (len(e.childNodes) == 1 and \
+                     e.firstChild.nodeType == e.TEXT_NODE):
+            fp.write('%s%s\n' % (' '*indent*2, e.toxml().strip()))
+        else:
+            fp.write('%s<%s%s>\n' % (' '*indent*2, e.tagName, self._attribs(e)))
+            for c in e.childNodes:
+                self._pretty_xml(fp, c, indent + 1)
+            fp.write('%s</%s>\n' % (' '*indent*2, e.tagName))
+
+    def _append_children_element_value_pairs(self, doc, element, pairs):
+        for e, t in pairs:
+            el = doc.createElement(e)
+            te = doc.createTextNode(str(t))
+            el.appendChild(te)
+            element.appendChild(el)
+
+    def _create_schema(self, setting, doc, schemalist):
+        if hasattr(setting, 'path'):
+            schemalist.setAttribute("path", setting.path)
+        if hasattr(setting, 'gsettings_key'):
+            key = doc.createElement('key')
+            key.setAttribute('name', setting.gsettings_key)
+            key.setAttribute('type', setting.variant_type)
+            schemalist.appendChild(key)
+            self._append_children_element_value_pairs(
+                doc, key, [('default',
+                            getattr(setting.gvariant, "print")(False)),
+                           ('_summary', setting.short_desc),
+                           ('_description', setting.long_desc)])
+
+        for s in setting:
+            self._create_schema(s, doc, schemalist)
+
+if __name__ == "__main__":
+    from caribou.settings import AllSettings
+
+    if (len(sys.argv) != 2):
+        print "usage: %s <schema id>" % sys.argv[0]
+        sys.exit(1)
+
+    avail_settings = dict([(s.schema_id, s) for s in AllSettings])
+    
+    try:
+        settings = avail_settings[sys.argv[-1]]
+    except KeyError:
+        print "Schema '%s' not available", sys.argv[-1]
+        sys.exit(1)
+    
+    maker = SchemasMaker(settings)
+    maker.create_schemas()



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]