[mousetrap/gnome3-wip: 101/240] New core architecture.



commit fde960968a665ad603e9052bb51f451a4708abd1
Author: Stoney Jackson <dr stoney gmail com>
Date:   Thu Jun 19 15:13:20 2014 -0400

    New core architecture.
    
    main.py had too many reasons to change. It was responsible
    for instantiating all system components and passing messages
    between compoents. So, anytime a new component was added or
    a change in there interaction was desired, main.py had to
    be changed in non-trivial ways.
    
    main.py needs to provide a stable, predictable platform on
    which to build new features.
    
    New classes
    
        mousetrap.main.App
        mousetrap.main.Loop
        mousetrap.main.Observable
        mousetrap.main.parts.interface.Part
    
    App - Provides shared state among Parts eliminating the need
          for Main to pass messages between Parts.
    
    Loop - Drives the application calling run() on each Part each
           pass of the loop. It is the observable in an observer
           pattern with Parts as observers. Use of the observer
           pattern means that Loop does not need to know the
           details of each specific Part.
    
    Observable - Provides implementation of observable in the
                 observer pattern.
    
    Part - The interface each part must implement.
    
    To implement a new Part:
    
    1. Implement the Part (e.g., see mosuetrap.parts.eyes).
    2. Add part description to DEFAULT_PARTS in main.py.
    
    This second step will go away when DEFAULT_PARTS is
    converted to a configuration file in a future commit.

 src/mousetrap/gui.py                               |   70 ++-----------
 src/mousetrap/main.py                              |  110 +++++++++++++++-----
 src/mousetrap/{pointers => parts}/__init__.py      |    0
 src/mousetrap/parts/camera.py                      |    6 +
 src/mousetrap/parts/display.py                     |    7 ++
 src/mousetrap/{pointers => parts}/eyes.py          |   31 ++----
 src/mousetrap/parts/interface.py                   |    4 +
 src/mousetrap/{pointers => parts}/nose.py          |    4 +-
 src/mousetrap/{pointers => parts}/nose_joystick.py |   47 ++------
 src/mousetrap/pointers/interface.py                |   20 ----
 10 files changed, 130 insertions(+), 169 deletions(-)
---
diff --git a/src/mousetrap/gui.py b/src/mousetrap/gui.py
index c36b594..0e22504 100644
--- a/src/mousetrap/gui.py
+++ b/src/mousetrap/gui.py
@@ -5,7 +5,7 @@ All things GUI.
 from gi.repository import Gtk
 from gi.repository import Gdk
 
-from Xlib.display import Display
+from Xlib.display import Display as XlibDisplay
 from Xlib.ext import xtest
 from Xlib import X
 
@@ -56,7 +56,7 @@ class Gui(object):
         return Gtk.Window().get_screen().get_height()
 
 
-class ScreenPointer(object):
+class Pointer(object):
     EVENT_MOVE = 'move'
     EVENT_CLICK = 'click'
     EVENT_DOUBLE_CLICK = 'double click'
@@ -64,9 +64,9 @@ class ScreenPointer(object):
     EVENT_PRESS = 'press'
     EVENT_RELEASE = 'release'
 
-    BUTTON_LEFT = 'left button'
-    BUTTON_RIGHT = 'right button'
-    BUTTON_MIDDLE = 'middle button'
+    BUTTON_LEFT = X.Button1
+    BUTTON_RIGHT = X.Button3
+    BUTTON_MIDDLE = X.Button2
 
     def __init__(self):
         gdk_display = Gdk.Display.get_default()
@@ -74,19 +74,6 @@ class ScreenPointer(object):
         self._pointer = device_manager.get_client_pointer()
         self._screen = gdk_display.get_default_screen()
         self._moved = False
-        self._event_handlers = {}
-        self._initialize_event_handlers()
-
-    def _initialize_event_handlers(self):
-        self._event_handlers[self.EVENT_MOVE] = self.set_position
-        self._event_handlers[self.EVENT_CLICK] = self.click
-        self._event_handlers[self.EVENT_DOUBLE_CLICK] = self.double_click
-        self._event_handlers[self.EVENT_TRIPLE_CLICK] = self.triple_click
-        self._event_handlers[self.EVENT_PRESS] = self.press
-        self._event_handlers[self.EVENT_RELEASE] = self.release
-
-    def trigger_event(self, event):
-        self._event_handlers[event.name](event.button_or_position)
 
     def set_position(self, position=None):
         '''Move pointer to position (x, y). If position is None,
@@ -109,47 +96,8 @@ class ScreenPointer(object):
         return (position[x_index], position[y_index])
 
     def click(self, button=BUTTON_LEFT):
-        if button != BUTTON_LEFT:
-            raise NotImplementedError('Only left click is implemented.')
-        display = Display()
-        for action in LeftClick().get_actions():
-            LOGGER.debug('%s %s', action.event, action.button)
-            xtest.fake_input(display, action.event, action.button)
+        display = XlibDisplay()
+        for event, button in [(X.ButtonPress, button), (X.ButtonRelease, button)]:
+            LOGGER.debug('%s %s', event, button)
+            xtest.fake_input(display, event, button)
             display.sync()
-
-    def double_click(self, button=BUTTON_LEFT):
-        raise NotImplementedError()
-
-    def triple_click(self, button=BUTTON_LEFT):
-        raise NotImplementedError()
-
-    def triple_click(self, button=BUTTON_LEFT):
-        raise NotImplementedError()
-
-    def press(self, button=BUTTON_LEFT):
-        raise NotImplementedError()
-
-    def release(self, button=BUTTON_LEFT):
-        raise NotImplementedError()
-
-    def is_pressed(self, button=BUTTON_LEFT):
-        raise NotImplementedError()
-
-
-class ScreenPointerEvent(object):
-    def __init__(self, name, button_or_position):
-        self.name = name
-        self.button_or_position = button_or_position
-
-
-class LeftClick(object):
-    def get_actions(self):
-        press = Button(event=X.ButtonPress)
-        release = Button(event=X.ButtonRelease)
-        return [press, release]
-
-
-class Button(object):
-    def __init__(self, event, button=1):
-        self.event = event
-        self.button = button
diff --git a/src/mousetrap/main.py b/src/mousetrap/main.py
index 99d2ac4..2f81644 100644
--- a/src/mousetrap/main.py
+++ b/src/mousetrap/main.py
@@ -4,53 +4,107 @@ Where it all begins.
 
 # NOTE: import this first to set up logging properly.
 import mousetrap.initialize_logging
-
 import logging
 from gi.repository import GObject, Gdk, Gtk
+from mousetrap.gui import Gui, Pointer
 from mousetrap.vision import Camera
-from mousetrap.gui import ScreenPointer, Gui
-from mousetrap.pointers.nose_joystick import Pointer
-from mousetrap.pointers.eyes import Pointer as Eyes
 
 
 LOGGER = logging.getLogger('mousetrap.main')
 
 
-class Main(object):
+#TODO: Should be a configuration file.
+DEFAULT_PARTS = [
+        ('camera', 'mousetrap.parts.camera'),
+        ('display', 'mousetrap.parts.display'),
+        ('nose_joystick', 'mousetrap.parts.nose_joystick'),
+        ('eye_click', 'mousetrap.parts.eyes'),
+        ]
+DEFAULT_LOOPS_PER_SECOND = 10
 
-    FPS = 10
-    INTERVAL = int(round(1000.0 / FPS))
 
+class Main(object):
     def __init__(self):
-        self.timeout_id = None
-        self.camera = Camera()
-        self.camera.set_dimensions(320, 240)
-        self.gui = Gui()
-        self.pointer = ScreenPointer()
-        self.nose = Pointer()
-        self.eyes = Eyes()
+        self._app = App()
 
     def run(self):
-        self.timeout_id = GObject.timeout_add(self.INTERVAL, self.on_timeout, None)
+        self._app.run()
+
+
+class App(object):
+    def __init__(self):
+        self.image = None
+        self.loop = Loop(self)
+        self.gui = Gui()
+        self.camera = Camera()
+        self.pointer = Pointer()
+        self.parts = []
+        self._assemble_parts()
+
+    def _assemble_parts(self):
+        self._load_parts(DEFAULT_PARTS)
+        self._register_parts_with_loop()
+
+    def _load_parts(self, part_descriptors):
+        for name, module in part_descriptors:
+            self.parts.append(self._load_part(module))
+
+    @staticmethod
+    def _load_part(module):
+        LOGGER.debug('loading %s', module)
+        module = __import__(module, globals(), locals(), ['Part'])
+        part = module.Part()
+        return part
+
+    def _register_parts_with_loop(self):
+        for part in self.parts:
+            self.loop.subscribe(part)
+
+    def run(self, app=None):
+        self.loop.start()
         self.gui.start()
 
-    def on_timeout(self, user_data):
 
-        image = self.camera.read_image()
-        self.gui.show_image('Raw', image)
-        self.nose.update_image(image)
-        for event in self.nose.get_pointer_events():
-            self.pointer.trigger_event(event)
+class Observable(object):
+    def __init__(self):
+        self.__observers = []
+        self.__arguments = {}
+
+    def subscribe(self, observer):
+        self.__observers.append(observer)
+
+    def _add_argument(self, key, value):
+        self.__arguments[key] = value
+
+    def _fire(self, callback_name):
+        for observer in self.__observers:
+            callback = getattr(observer, callback_name)
+            callback(**self.__arguments)
 
-        if self.pointer.is_moving():
-            return True
 
-        self.eyes.update_image(image)
-        events = self.eyes.get_pointer_events()
-        for event in events:
-            self.pointer.trigger_event(event)
+class Loop(Observable):
+    MILLISECONDS_PER_SECOND = 1000.0
+    CALLBACK_RUN = 'run'
 
-        return True
+    def __init__(self, app):
+        super(Loop, self).__init__()
+        self.set_loops_per_second(DEFAULT_LOOPS_PER_SECOND)
+        self._timeout_id = None
+        self._add_argument('app', app)
+
+    def set_loops_per_second(self, loops_per_second):
+        self._loops_per_second = loops_per_second
+        self._interval = int(round(
+            self.MILLISECONDS_PER_SECOND / self._loops_per_second))
+
+    def start(self):
+        self.timeout_id = GObject.timeout_add(self._interval, self.run)
+
+    def run(self):
+        CONTINUE = True
+        PAUSE = False
+        self._fire(self.CALLBACK_RUN)
+        return CONTINUE
 
 
 if __name__ == '__main__':
diff --git a/src/mousetrap/pointers/__init__.py b/src/mousetrap/parts/__init__.py
similarity index 100%
rename from src/mousetrap/pointers/__init__.py
rename to src/mousetrap/parts/__init__.py
diff --git a/src/mousetrap/parts/camera.py b/src/mousetrap/parts/camera.py
new file mode 100644
index 0000000..11f2346
--- /dev/null
+++ b/src/mousetrap/parts/camera.py
@@ -0,0 +1,6 @@
+import mousetrap.parts.interface as interface
+
+
+class Part(interface.Part):
+    def run(self, app):
+        app.image = app.camera.read_image()
diff --git a/src/mousetrap/parts/display.py b/src/mousetrap/parts/display.py
new file mode 100644
index 0000000..2c39f36
--- /dev/null
+++ b/src/mousetrap/parts/display.py
@@ -0,0 +1,7 @@
+import mousetrap.parts.interface as interface
+import logging
+
+
+class Part(interface.Part):
+    def run(self, app):
+        app.gui.show_image('MouseTrap', app.image)
diff --git a/src/mousetrap/pointers/eyes.py b/src/mousetrap/parts/eyes.py
similarity index 66%
rename from src/mousetrap/pointers/eyes.py
rename to src/mousetrap/parts/eyes.py
index dfc67e8..b5f302f 100644
--- a/src/mousetrap/pointers/eyes.py
+++ b/src/mousetrap/parts/eyes.py
@@ -1,29 +1,28 @@
-import mousetrap.pointers.interface as interface
+import mousetrap.parts.interface as interface
 from mousetrap.vision import FeatureDetector, FeatureNotFoundException
-from mousetrap.gui import ScreenPointer
 import logging
 
 
 LOGGER = logging.getLogger(__name__)
 
 
-class Pointer(interface.Pointer):
+class Part(interface.Part):
     def __init__(self):
-
         self._left_locator = LeftEyeLocator()
-
         self._history = []
-
         self._is_closed = False
 
-    def update_image(self, image):
+    def run(self, app):
         try:
-            point_image = self._left_locator.locate(image)
-
+            point_image = self._left_locator.locate(app.image)
             self._hit(point_image)
         except FeatureNotFoundException:
             self._miss()
 
+        if self._detect_closed():
+            self._history = []
+            app.pointer.click()
+
     def _hit(self, point):
         self._history.append(point)
 
@@ -38,20 +37,6 @@ class Pointer(interface.Pointer):
 
         return misses > 12
 
-    def get_new_position(self):
-        return None
-
-    def get_pointer_events(self):
-        if self._detect_closed():
-            self._is_closed = True
-            if not self._is_closed:
-                return [ScreenPointerEvent(
-                            ScreenPointer.EVENT_CLICK,
-                            ScreenPointer.BUTTON_LEFT)]
-        else:
-            self._is_closed = False
-        return []
-
 
 class LeftEyeLocator(object):
 
diff --git a/src/mousetrap/parts/interface.py b/src/mousetrap/parts/interface.py
new file mode 100644
index 0000000..c55cf48
--- /dev/null
+++ b/src/mousetrap/parts/interface.py
@@ -0,0 +1,4 @@
+class Part(object):
+    def run(self, app):
+        '''Called each pass of the loop.'''
+        raise NotImplementedError('Must implement.')
diff --git a/src/mousetrap/pointers/nose.py b/src/mousetrap/parts/nose.py
similarity index 95%
rename from src/mousetrap/pointers/nose.py
rename to src/mousetrap/parts/nose.py
index b4869d2..55bb5c4 100644
--- a/src/mousetrap/pointers/nose.py
+++ b/src/mousetrap/parts/nose.py
@@ -1,9 +1,9 @@
-import mousetrap.pointers.interface as interface
+import mousetrap.parts.interface as interface
 from mousetrap.vision import FeatureDetector, FeatureNotFoundException
 from mousetrap.gui import Gui
 
 
-class Pointer(interface.Pointer):
+class Part(interface.Part):
     def __init__(self):
         self._nose_locator = NoseLocator()
         self._gui = Gui()
diff --git a/src/mousetrap/pointers/nose_joystick.py b/src/mousetrap/parts/nose_joystick.py
similarity index 52%
rename from src/mousetrap/pointers/nose_joystick.py
rename to src/mousetrap/parts/nose_joystick.py
index 9cee0a7..4162cac 100644
--- a/src/mousetrap/pointers/nose_joystick.py
+++ b/src/mousetrap/parts/nose_joystick.py
@@ -1,38 +1,27 @@
-import mousetrap.pointers.interface as interface
+import mousetrap.parts.interface as interface
 from mousetrap.vision import FeatureDetector, FeatureNotFoundException
-from mousetrap.gui import Gui, ScreenPointer, ScreenPointerEvent
+from mousetrap.parts.nose import NoseLocator
 
-from mousetrap.pointers.nose import NoseLocator
 
-
-class Pointer(interface.Pointer):
+class Part(interface.Part):
     THRESHOLD = 5
 
     def __init__(self):
         self._nose_locator = NoseLocator()
-        self._image = None
-
-        self._pointer = ScreenPointer()
-
         self._initial_image_location = (0, 0)
         self._last_delta = (0, 0)
 
-        self._location = self._pointer.get_position()
-
-        self._tracking = False
-
-    def update_image(self, image):
-        self._image = image
+    def run(self, app):
+        self._app = app
+        location = None
         try:
-            point_image = self._nose_locator.locate(image)
-            self._tracking = True
+            point_image = self._nose_locator.locate(app.image)
             point_screen = self._convert_image_to_screen_point(*point_image)
-            self._location = point_screen
+            location = point_screen
         except FeatureNotFoundException:
-            self._tracking = False
-            location = self._pointer.get_position()
-            self._location = self._apply_delta_to_point(location,
-                                                        self._last_delta)
+            location = app.pointer.get_position()
+            location = self._apply_delta_to_point(location, self._last_delta)
+        app.pointer.set_position(location)
 
     def _apply_delta_to_point(self, point, delta):
         delta_x, delta_y = delta
@@ -67,18 +56,6 @@ class Pointer(interface.Pointer):
 
         self._last_delta = delta
 
-        location = self._pointer.get_position()
+        location = self._app.pointer.get_position()
 
         return self._apply_delta_to_point(location, delta)
-
-#    def get_new_position(self):
-#        return self._location
-
-    def is_tracking(self):
-        return self._tracking
-
-    def get_pointer_events(self):
-        if self._location is None:
-            return []
-        else:
-            return [ScreenPointerEvent(ScreenPointer.EVENT_MOVE, self._location)]


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