7 import util.go_utils as go_utils
8 import util.misc as misc_utils
11 _moduleLogger = logging.getLogger(__name__)
14 class NavigationBox(gobject.GObject):
18 gobject.SIGNAL_RUN_LAST,
20 (gobject.TYPE_STRING, ),
23 gobject.SIGNAL_RUN_LAST,
25 (gobject.TYPE_STRING, ),
34 gobject.GObject.__init__(self)
35 self._eventBox = gtk.EventBox()
36 self._eventBox.connect("button_press_event", self._on_button_press)
37 self._eventBox.connect("button_release_event", self._on_button_release)
38 self._eventBox.connect("motion_notify_event", self._on_motion_notify)
40 self._isPortrait = True
41 self._clickPosition = self._NO_POSITION
47 def set_orientation(self, orientation):
48 if orientation == gtk.ORIENTATION_VERTICAL:
49 self._isPortrait = True
50 elif orientation == gtk.ORIENTATION_HORIZONTAL:
51 self._isPortrait = False
53 raise NotImplementedError(orientation)
56 return self._clickPosition != self._NO_POSITION
58 def get_state(self, newCoord):
59 if self._clickPosition == self._NO_POSITION:
63 newCoord[0] - self._clickPosition[0],
64 - (newCoord[1] - self._clickPosition[1])
66 absDelta = (abs(delta[0]), abs(delta[1]))
67 if max(*absDelta) < self.MINIMUM_MOVEMENT:
70 if absDelta[0] < absDelta[1]:
81 @misc_utils.log_exception(_moduleLogger)
82 def _on_button_press(self, widget, event):
83 if self._clickPosition != self._NO_POSITION:
84 _moduleLogger.debug("Ignoring double click")
85 self._clickPosition = event.get_coords()
87 self.emit("navigating", "clicking")
89 @misc_utils.log_exception(_moduleLogger)
90 def _on_button_release(self, widget, event):
91 assert self._clickPosition != self._NO_POSITION
93 mousePosition = event.get_coords()
94 state = self.get_state(mousePosition)
97 self._clickPosition = self._NO_POSITION
98 self.emit("action", state)
100 @misc_utils.log_exception(_moduleLogger)
101 def _on_motion_notify(self, widget, event):
102 if self._clickPosition == self._NO_POSITION:
105 mousePosition = event.get_coords()
106 newState = self.get_state(mousePosition)
107 self.emit("navigating", newState)
110 gobject.type_register(NavigationBox)
113 class StreamPresenter(object):
115 def __init__(self, store):
118 self._image = gtk.DrawingArea()
119 self._image.connect("expose_event", self._on_expose)
121 self._isPortrait = True
123 self._backgroundImage = None
126 self._buttonImage = None
134 def set_orientation(self, orientation):
135 if orientation == gtk.ORIENTATION_VERTICAL:
136 self._isPortrait = True
137 elif orientation == gtk.ORIENTATION_HORIZONTAL:
138 self._isPortrait = False
140 raise NotImplementedError(orientation)
142 self._image.queue_draw()
144 def set_state(self, stateImage):
145 if stateImage == self._imageName:
147 self._imageName = stateImage
148 self._buttonImage = self._store.get_surface_from_store(stateImage)
150 self._image.queue_draw()
152 def set_context(self, backgroundImage, title, subtitle):
153 self._backgroundImage = self._store.get_surface_from_store(backgroundImage)
155 self._subtitle = subtitle
157 backWidth = self._backgroundImage.get_width()
158 backHeight = self._backgroundImage.get_height()
159 self._image.set_size_request(backWidth, backHeight)
161 self._image.queue_draw()
163 @misc_utils.log_exception(_moduleLogger)
164 def _on_expose(self, widget, event):
165 cairoContext = self._image.window.cairo_create()
166 self._draw_presenter(cairoContext)
168 def _draw_presenter(self, cairoContext):
169 rect = self._image.get_allocation()
170 self._dims = rect.width, rect.height
173 cairoContext.rectangle(
179 cairoContext.set_source_rgb(0, 0, 0)
183 if self._backgroundImage is not None:
184 cairoContext.set_source_surface(
185 self._backgroundImage,
191 pangoContext = self._image.create_pango_context()
193 titleLayout = pango.Layout(pangoContext)
194 titleLayout.set_markup("<i>%s</i>" % self._subtitle)
195 textWidth, textHeight = titleLayout.get_pixel_size()
196 subtitleTextX = self._dims[0] / 2 - textWidth / 2
197 subtitleTextY = self._dims[1] - textHeight - self._buttonImage.get_height() + 10
199 subtitleLayout = pango.Layout(pangoContext)
200 subtitleLayout.set_markup("<b>%s</b>" % self._title)
201 textWidth, textHeight = subtitleLayout.get_pixel_size()
202 textX = self._dims[0] / 2 - textWidth / 2
203 textY = subtitleTextY - textHeight
205 xPadding = min((self._dims[0] - textWidth) / 2 - 5, 5)
207 startContent = xPadding, textY - yPadding
208 endContent = self._dims[0] - xPadding, self._dims[1] - yPadding
211 cairoContext.rectangle(
214 endContent[0] - startContent[0],
215 endContent[1] - startContent[1],
217 cairoContext.set_source_rgba(0.9, 0.9, 0.9, 0.75)
221 if self._title or self._subtitle:
222 cairoContext.move_to(subtitleTextX, subtitleTextY)
223 cairoContext.set_source_rgb(0, 0, 0)
224 cairoContext.show_layout(titleLayout)
226 cairoContext.move_to(textX, textY)
227 cairoContext.set_source_rgb(0, 0, 0)
228 cairoContext.show_layout(subtitleLayout)
230 self._draw_state(cairoContext)
232 def _draw_state(self, cairoContext):
233 if self._backgroundImage is None or self._buttonImage is None:
235 cairoContext.set_source_surface(
237 self._dims[0] / 2 - self._buttonImage.get_width() / 2,
238 self._dims[1] - self._buttonImage.get_height() + 5,
243 class StreamMiniPresenter(object):
245 def __init__(self, store):
248 self._button = gtk.Image()
254 def set_orientation(self, orientation):
257 def set_state(self, stateImage):
258 self._store.set_image_from_store(self._button, stateImage)
260 def set_context(self, backgroundImage, title, subtitle):
264 class NavControl(gobject.GObject, go_utils.AutoSignal):
268 gobject.SIGNAL_RUN_LAST,
273 gobject.SIGNAL_RUN_LAST,
275 (gobject.TYPE_PYOBJECT, ),
279 def __init__(self, player, store):
280 gobject.GObject.__init__(self)
281 self._layout = gtk.HBox()
282 go_utils.AutoSignal.__init__(self, self.toplevel)
286 self._controlButton = store.get_image_from_store(store.STORE_LOOKUP["play"])
288 self._controlBox = NavigationBox()
289 self._controlBox.toplevel.add(self._controlButton)
290 self.connect_auto(self._controlBox, "action", self._on_nav_action)
291 self.connect_auto(self._controlBox, "navigating", self._on_navigating)
293 self._titleButton = gtk.Label()
295 self._displayBox = NavigationBox()
296 self._displayBox.toplevel.add(self._titleButton)
297 self.connect_auto(self._displayBox, "action", self._on_nav_action)
298 self.connect_auto(self._displayBox, "navigating", self._on_navigating)
300 self._layout.pack_start(self._controlBox.toplevel, False, False)
301 self._layout.pack_start(self._displayBox.toplevel, True, True)
302 self._player = player
303 self.connect_auto(self._player, "state-change", self._on_player_state_change)
304 self.connect_auto(self._player, "title-change", self._on_player_title_change)
305 self._titleButton.set_label(self._player.title)
308 self._titleButton.set_label(self._player.title)
309 self._set_context(self._player.state)
311 def _set_context(self, state):
312 if state == self._player.STATE_PLAY:
313 stateImage = self._store.STORE_LOOKUP["pause"]
314 self._store.set_image_from_store(self._controlButton, stateImage)
316 elif state == self._player.STATE_PAUSE:
317 stateImage = self._store.STORE_LOOKUP["play"]
318 self._store.set_image_from_store(self._controlButton, stateImage)
320 elif state == self._player.STATE_STOP:
321 self._titleButton.set_label("")
324 _moduleLogger.info("Unhandled player state %s" % state)
325 stateImage = self._store.STORE_LOOKUP["pause"]
326 self._store.set_image_from_store(self._controlButton, stateImage)
332 def set_orientation(self, orientation):
335 @misc_utils.log_exception(_moduleLogger)
336 def _on_player_state_change(self, player, newState):
337 if self._controlBox.is_active() or self._displayBox.is_active():
340 self._set_context(newState)
342 @misc_utils.log_exception(_moduleLogger)
343 def _on_player_title_change(self, player, node):
344 _moduleLogger.info("Title change: %s" % self._player.title)
345 self._titleButton.set_label(self._player.title)
347 @misc_utils.log_exception(_moduleLogger)
348 def _on_navigating(self, widget, navState):
349 if navState == "down":
351 elif navState == "clicking":
352 if widget is self._controlBox:
353 if self._player.state == self._player.STATE_PLAY:
354 imageName = "pause_pressed"
356 imageName = "play_pressed"
358 if self._player.state == self._player.STATE_PLAY:
362 elif self._player.can_navigate:
365 elif navState == "left":
367 elif navState == "right":
370 if self._player.state == self._player.STATE_PLAY:
375 imagePath = self._store.STORE_LOOKUP[imageName]
376 self._store.set_image_from_store(self._controlButton, imagePath)
378 @misc_utils.log_exception(_moduleLogger)
379 def _on_nav_action(self, widget, navState):
380 self._set_context(self._player.state)
382 if navState == "clicking":
383 if widget is self._controlBox:
384 if self._player.state == self._player.STATE_PLAY:
388 elif widget is self._displayBox:
389 self.emit("jump-to", self._player.node)
391 raise NotImplementedError()
392 elif navState == "down":
394 elif navState == "up":
396 elif navState == "left":
398 elif navState == "right":
402 gobject.type_register(NavControl)