Initial checkin of the presentor
authorEd Page <eopage@byu.net>
Sat, 1 May 2010 16:57:22 +0000 (11:57 -0500)
committerEd Page <eopage@byu.net>
Sat, 1 May 2010 17:01:03 +0000 (12:01 -0500)
data/next.png
data/pause.png
data/play.png
data/prev.png
data/stop.png
hand_tests/fake_player.py
hand_tests/test_presenter.py [new file with mode: 0755]
src/__init__.py
src/imagestore.py
src/presenter.py [new file with mode: 0644]

index 20c771e..cfc53dd 100644 (file)
Binary files a/data/next.png and b/data/next.png differ
index 5b89e18..e9d5aa6 100644 (file)
Binary files a/data/pause.png and b/data/pause.png differ
index 4d2100b..c863850 100644 (file)
Binary files a/data/play.png and b/data/play.png differ
index cd50dec..cacd89e 100644 (file)
Binary files a/data/prev.png and b/data/prev.png differ
index 9ea591f..abffa48 100644 (file)
Binary files a/data/stop.png and b/data/stop.png differ
index 78e6840..d9c84cc 100644 (file)
@@ -68,6 +68,10 @@ class FakePlayer(gobject.GObject):
        def state(self):
                return self._state
 
        def state(self):
                return self._state
 
+       @property
+       def background(self):
+               return "night_temple_background"
+
        def _state_change(self, widget, state):
                self.emit("state_change", state)
                self._state = state
        def _state_change(self, widget, state):
                self.emit("state_change", state)
                self._state = state
diff --git a/hand_tests/test_presenter.py b/hand_tests/test_presenter.py
new file mode 100755 (executable)
index 0000000..70622b2
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+import sys
+import logging
+
+import gtk
+
+sys.path.append('../src')
+
+import imagestore
+import presenter
+import fake_player
+
+
+if __name__ == "__main__":
+       logging.basicConfig(level=logging.DEBUG)
+
+       store = imagestore.ImageStore("../data", ".")
+       player = fake_player.FakePlayer()
+       sp = presenter.StreamPresenter(player, store)
+
+       layout = gtk.VBox()
+       layout.pack_start(player.toplevel)
+       layout.pack_start(sp.toplevel)
+
+       window = gtk.Window()
+       window.set_title("Test")
+       window.add(layout)
+       window.connect("destroy", lambda w: gtk.main_quit())
+       window.show_all()
+
+       gtk.main()
index 4265cc3..7132119 100644 (file)
@@ -1 +1,3 @@
 #!/usr/bin/env python
 #!/usr/bin/env python
+
+import utils
index 7119d65..e5e351c 100644 (file)
@@ -6,6 +6,26 @@ import gtk
 
 class ImageStore(object):
 
 
 class ImageStore(object):
 
+       STORE_LOOKUP = {
+               "next": "next.png",
+               "prev": "prev.png",
+               "pause": "pause.png",
+               "play": "play.png",
+               "stop": "stop.png",
+               "generic_background": "radiobackground_01.png",
+               "night_temple_background": "radiobackground_02.png",
+               "day_temple_background": "radiobackground_03.png",
+               "presidency_background": "radiobackground_04.png",
+               "scriptures_background": "radiobackground_05.png",
+               "conference": "conference.png",
+               "magazines": "magazines.png",
+               "more": "more.png",
+               "mormonmessages": "mormonmessages.png",
+               "radio": "radio.png",
+               "scriptures": "scriptures.png",
+               "icon": "icon.png",
+       }
+
        def __init__(self, storePath, cachePath):
                self._storePath = storePath
                self._cachePath = cachePath
        def __init__(self, storePath, cachePath):
                self._storePath = storePath
                self._cachePath = cachePath
diff --git a/src/presenter.py b/src/presenter.py
new file mode 100644 (file)
index 0000000..20359af
--- /dev/null
@@ -0,0 +1,304 @@
+import logging
+
+import pango
+import cairo
+import gtk
+
+import util.misc as misc_utils
+
+
+_moduleLogger = logging.getLogger(__name__)
+
+
+class StreamPresenter(object):
+
+       MINIMUM_MOVEMENT = 20
+
+       BUTTON_STATE_PLAY = "play"
+       BUTTON_STATE_PAUSE = "pause"
+       BUTTON_STATE_NEXT = "next"
+       BUTTON_STATE_BACK = "back"
+       BUTTON_STATE_UP = "up"
+       BUTTON_STATE_CANCEL = "cancel"
+
+       _NO_POSITION = -1, -1
+
+       _STATE_TO_IMAGE = {
+               BUTTON_STATE_PLAY: "play.png",
+               BUTTON_STATE_PAUSE: "pause.png",
+               BUTTON_STATE_NEXT: "next.png",
+               BUTTON_STATE_BACK: "prev.png",
+       }
+
+       def __init__(self, player, store):
+               self._store = store
+
+               self._player = player
+               self._player.connect("state-change", self._on_player_state_change)
+               self._player.connect("navigate-change", self._on_player_nav_change)
+               self._player.connect("title-change", self._on_player_title_change)
+
+               self._image = gtk.DrawingArea()
+               self._image.connect("expose_event", self._on_expose)
+               self._imageEvents = gtk.EventBox()
+               self._imageEvents.connect("motion_notify_event", self._on_motion_notify)
+               #self._imageEvents.connect("leave_notify_event", self._on_leave_notify)
+               #self._imageEvents.connect("proximity_in_event", self._on_motion_notify)
+               #self._imageEvents.connect("proximity_out_event", self._on_leave_notify)
+               self._imageEvents.connect("button_press_event", self._on_button_press)
+               self._imageEvents.connect("button_release_event", self._on_button_release)
+               self._imageEvents.add(self._image)
+
+               self._isPortrait = True
+
+               self._canNavigate = True
+               self._clickPosition = self._NO_POSITION
+               self._potentialButtonState = self.BUTTON_STATE_PLAY
+               self._currentButtonState = self.BUTTON_STATE_PLAY
+
+               imagePath = self._store.STORE_LOOKUP[self._player.background]
+               self._backgroundImage = self._store.get_surface_from_store(imagePath)
+               imagePath = self._STATE_TO_IMAGE[self._currentButtonState]
+               self._buttonImage = self._store.get_surface_from_store(imagePath)
+
+               if self._isPortrait:
+                       backWidth = self._backgroundImage.get_width()
+                       backHeight = self._backgroundImage.get_height()
+               else:
+                       backHeight = self._backgroundImage.get_width()
+                       backWidth = self._backgroundImage.get_height()
+               self._image.set_size_request(backWidth, backHeight)
+
+       @property
+       def toplevel(self):
+               return self._imageEvents
+
+       def set_orientation(self, orientation):
+               if orientation == gtk.ORIENTATION_VERTICAL:
+                       self._isPortrait = True
+               elif orientation == gtk.ORIENTATION_HORIZONTAL:
+                       self._isPortrait = False
+               else:
+                       raise NotImplementedError(orientation)
+
+               cairoContext = self._image.window.cairo_create()
+               if not self._isPortrait:
+                       cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+               self._draw_presenter(cairoContext, self._currentButtonState)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_player_state_change(self, player, newState):
+               if newState == "play":
+                       newState = self.BUTTON_STATE_PLAY
+               elif newState == "pause":
+                       newState = self.BUTTON_STATE_PAUSE
+               elif newState == "stop":
+                       newState = self.BUTTON_STATE_PAUSE
+               else:
+                       newState = self._currentButtonState
+
+               if newState != self._currentButtonState:
+                       self._currentButtonState = newState
+                       if self._clickPosition == self._NO_POSITION:
+                               cairoContext = self._image.window.cairo_create()
+                               if not self._isPortrait:
+                                       cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+                               self._draw_state(cairoContext, self._currentButtonState)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_player_nav_change(self, player, newState):
+               canNavigate = self._player.can_navigate
+               newPotState = self._potentialButtonState
+               if self._canNavigate != canNavigate:
+                       self._canNavigate = canNavigate
+                       if self._potentialButtonState in (self.BUTTON_STATE_NEXT, self.BUTTON_STATE_BACK):
+                               if self._currentButtonState == self.BUTTON_STATE_PLAY:
+                                       newPotState = self.BUTTON_STATE_PAUSE
+                               else:
+                                       newPotState = self.BUTTON_STATE_PLAY
+
+               if newPotState != self._potentialButtonState:
+                       self._potentialButtonState = newPotState
+                       if self._clickPosition == self._NO_POSITION:
+                               cairoContext = self._image.window.cairo_create()
+                               if not self._isPortrait:
+                                       cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+                               self._draw_state(cairoContext, self._potentialButtonState)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_player_title_change(self, player, newState):
+               if self._isPortrait:
+                       backWidth = self._backgroundImage.get_width()
+                       backHeight = self._backgroundImage.get_height()
+               else:
+                       backHeight = self._backgroundImage.get_width()
+                       backWidth = self._backgroundImage.get_height()
+               self._image.set_size_request(backWidth, backHeight)
+
+               imagePath = self._store.STORE_LOOKUP[self._player.background]
+               self._backgroundImage = self._store.get_surface_from_store(imagePath)
+               if self._clickPosition == self._NO_POSITION:
+                       cairoContext = self._image.window.cairo_create()
+                       if not self._isPortrait:
+                               cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+                       self._draw_presenter(cairoContext, self._currentButtonState)
+               else:
+                       cairoContext = self._image.window.cairo_create()
+                       if not self._isPortrait:
+                               cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+                       self._draw_presenter(cairoContext, self._potentialButtonState)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_button_press(self, widget, event):
+               self._clickPosition = event.get_coords()
+               if self._currentButtonState == self.BUTTON_STATE_PLAY:
+                       newState = self.BUTTON_STATE_PAUSE
+               else:
+                       newState = self.BUTTON_STATE_PLAY
+               self._potentialButtonState = newState
+               cairoContext = self._image.window.cairo_create()
+               if not self._isPortrait:
+                       cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+               self._draw_state(cairoContext, self._potentialButtonState)
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_button_release(self, widget, event):
+               try:
+                       mousePosition = event.get_coords()
+                       newState = self._calculate_state(mousePosition)
+                       if newState == self.BUTTON_STATE_PLAY:
+                               self._player.play()
+                       elif newState == self.BUTTON_STATE_PAUSE:
+                               self._player.pause()
+                       elif newState == self.BUTTON_STATE_NEXT:
+                               self._player.next()
+                       elif newState == self.BUTTON_STATE_BACK:
+                               self._player.back()
+                       elif newState == self.BUTTON_STATE_UP:
+                               raise NotImplementedError("Drag-down not implemented yet")
+                       elif newState == self.BUTTON_STATE_CANCEL:
+                               pass
+               finally:
+                       if self._player.state == "play":
+                               newState = self.BUTTON_STATE_PLAY
+                       else:
+                               newState = self.BUTTON_STATE_PAUSE
+                       self._potentialButtonState = newState
+                       cairoContext = self._image.window.cairo_create()
+                       if not self._isPortrait:
+                               cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+                       self._draw_state(cairoContext, self._potentialButtonState)
+                       self._clickPosition = self._NO_POSITION
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_motion_notify(self, widget, event):
+               if self._clickPosition == self._NO_POSITION:
+                       return
+
+               mousePosition = event.get_coords()
+               newState = self._calculate_state(mousePosition)
+               if newState != self._potentialButtonState:
+                       self._potentialButtonState = newState
+                       cairoContext = self._image.window.cairo_create()
+                       if not self._isPortrait:
+                               cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+                       self._draw_state(cairoContext, self._potentialButtonState)
+
+       def _calculate_state(self, newCoord):
+               assert self._clickPosition != self._NO_POSITION
+
+               if self._isPortrait:
+                       delta = (
+                               newCoord[0] - self._clickPosition[0],
+                               - (newCoord[1] - self._clickPosition[1])
+                       )
+               else:
+                       delta = (
+                               newCoord[1] - self._clickPosition[1],
+                               - (newCoord[0] - self._clickPosition[0])
+                       )
+               absDelta = (abs(delta[0]), abs(delta[1]))
+               if max(*absDelta) < self.MINIMUM_MOVEMENT or not self._canNavigate:
+                       if self._currentButtonState == self.BUTTON_STATE_PLAY:
+                               return self.BUTTON_STATE_PAUSE
+                       else:
+                               return self.BUTTON_STATE_PLAY
+
+               if absDelta[0] < absDelta[1]:
+                       if 0 < delta[1]:
+                               return self.BUTTON_STATE_CANCEL
+                       else:
+                               return self.BUTTON_STATE_UP
+               else:
+                       if 0 < delta[0]:
+                               return self.BUTTON_STATE_BACK
+                       else:
+                               return self.BUTTON_STATE_NEXT
+
+       @misc_utils.log_exception(_moduleLogger)
+       def _on_expose(self, widget, event):
+               self._potentialButtonState = self._player.state
+               cairoContext = self._image.window.cairo_create()
+               if not self._isPortrait:
+                       cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
+               self._draw_presenter(cairoContext, self._player.state)
+
+       def _draw_presenter(self, cairoContext, state):
+               assert state in (self._currentButtonState, self._potentialButtonState)
+
+               # Blank things
+               rect = self._image.get_allocation()
+               cairoContext.rectangle(
+                       0,
+                       0,
+                       rect.width,
+                       rect.height,
+               )
+               cairoContext.set_source_rgb(0, 0, 0)
+               cairoContext.fill()
+               cairoContext.paint()
+
+               # Draw Background
+               cairoContext.set_source_surface(
+                       self._backgroundImage,
+                       0,
+                       0,
+               )
+               cairoContext.paint()
+
+               # title
+               if self._player.title:
+                       _moduleLogger.info("Displaying text")
+                       backWidth = self._backgroundImage.get_width()
+                       backHeight = self._backgroundImage.get_height()
+
+                       pangoContext = self._image.create_pango_context()
+                       textLayout = pango.Layout(pangoContext)
+                       textLayout.set_markup(self._player.title)
+
+                       textWidth, textHeight = textLayout.get_pixel_size()
+                       textX = backWidth / 2 - textWidth / 2
+                       textY = backHeight - textHeight - self._buttonImage.get_height()
+
+                       cairoContext.move_to(textX, textY)
+                       cairoContext.set_source_rgb(0, 0, 0)
+                       cairoContext.show_layout(textLayout)
+
+               self._draw_state(cairoContext, state)
+
+       def _draw_state(self, cairoContext, state):
+               assert state in (self._currentButtonState, self._potentialButtonState)
+               if state == self.BUTTON_STATE_CANCEL:
+                       state = self._currentButtonState
+
+               backWidth = self._backgroundImage.get_width()
+               backHeight = self._backgroundImage.get_height()
+
+               imagePath = self._STATE_TO_IMAGE[state]
+               self._buttonImage = self._store.get_surface_from_store(imagePath)
+               cairoContext.set_source_surface(
+                       self._buttonImage,
+                       backWidth / 2 - self._buttonImage.get_width() / 2,
+                       backHeight - self._buttonImage.get_height() + 5,
+               )
+               cairoContext.paint()