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:
64 newCoord[0] - self._clickPosition[0],
65 - (newCoord[1] - self._clickPosition[1])
69 newCoord[1] - self._clickPosition[1],
70 - (newCoord[0] - self._clickPosition[0])
72 absDelta = (abs(delta[0]), abs(delta[1]))
73 if max(*absDelta) < self.MINIMUM_MOVEMENT:
76 if absDelta[0] < absDelta[1]:
87 @misc_utils.log_exception(_moduleLogger)
88 def _on_button_press(self, widget, event):
89 if self._clickPosition != self._NO_POSITION:
90 _moduleLogger.debug("Ignoring double click")
91 self._clickPosition = event.get_coords()
93 self.emit("navigating", "clicking")
95 @misc_utils.log_exception(_moduleLogger)
96 def _on_button_release(self, widget, event):
97 assert self._clickPosition != self._NO_POSITION
99 mousePosition = event.get_coords()
100 state = self.get_state(mousePosition)
102 self.emit("action", state)
104 self._clickPosition = self._NO_POSITION
106 @misc_utils.log_exception(_moduleLogger)
107 def _on_motion_notify(self, widget, event):
108 if self._clickPosition == self._NO_POSITION:
111 mousePosition = event.get_coords()
112 newState = self.get_state(mousePosition)
113 self.emit("navigating", newState)
116 gobject.type_register(NavigationBox)
119 class StreamPresenter(object):
121 BUTTON_STATE_PLAY = "play"
122 BUTTON_STATE_PAUSE = "pause"
123 BUTTON_STATE_NEXT = "next"
124 BUTTON_STATE_BACK = "back"
125 BUTTON_STATE_UP = "up"
126 BUTTON_STATE_CANCEL = "cancel"
129 BUTTON_STATE_PLAY: "play.png",
130 BUTTON_STATE_PAUSE: "pause.png",
131 BUTTON_STATE_NEXT: "next.png",
132 BUTTON_STATE_BACK: "prev.png",
135 def __init__(self, player, store):
138 self._player = player
139 self._player.connect("state-change", self._on_player_state_change)
140 self._player.connect("navigate-change", self._on_player_nav_change)
141 self._player.connect("title-change", self._on_player_title_change)
143 self._image = gtk.DrawingArea()
144 self._image.connect("expose_event", self._on_expose)
145 self._imageNav = NavigationBox()
146 self._imageNav.toplevel.add(self._image)
147 self._imageNav.connect("navigating", self._on_navigating)
148 self._imageNav.connect("action", self._on_nav_action)
150 self._isPortrait = True
152 self._canNavigate = True
153 self._potentialButtonState = self.BUTTON_STATE_PLAY
154 self._currentButtonState = self.BUTTON_STATE_PLAY
156 imagePath = self._store.STORE_LOOKUP[self._player.background]
157 self._backgroundImage = self._store.get_surface_from_store(imagePath)
158 imagePath = self._STATE_TO_IMAGE[self._currentButtonState]
159 self._buttonImage = self._store.get_surface_from_store(imagePath)
162 backWidth = self._backgroundImage.get_width()
163 backHeight = self._backgroundImage.get_height()
165 backHeight = self._backgroundImage.get_width()
166 backWidth = self._backgroundImage.get_height()
167 self._image.set_size_request(backWidth, backHeight)
171 return self._imageNav.toplevel
173 def set_orientation(self, orientation):
174 self._imageNav.set_orientation(orientation)
176 if orientation == gtk.ORIENTATION_VERTICAL:
177 self._isPortrait = True
178 elif orientation == gtk.ORIENTATION_HORIZONTAL:
179 self._isPortrait = False
181 raise NotImplementedError(orientation)
183 cairoContext = self._image.window.cairo_create()
184 if not self._isPortrait:
185 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
186 self._draw_presenter(cairoContext, self._currentButtonState)
188 @misc_utils.log_exception(_moduleLogger)
189 def _on_player_state_change(self, player, newState):
190 if newState == "play":
191 newState = self.BUTTON_STATE_PLAY
192 elif newState == "pause":
193 newState = self.BUTTON_STATE_PAUSE
194 elif newState == "stop":
195 newState = self.BUTTON_STATE_PAUSE
197 newState = self._currentButtonState
199 if newState != self._currentButtonState:
200 self._currentButtonState = newState
201 if not self._imageNav.is_active():
202 cairoContext = self._image.window.cairo_create()
203 if not self._isPortrait:
204 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
205 self._draw_state(cairoContext, self._currentButtonState)
207 @misc_utils.log_exception(_moduleLogger)
208 def _on_player_nav_change(self, player, newState):
209 canNavigate = self._player.can_navigate
210 newPotState = self._potentialButtonState
211 if self._canNavigate != canNavigate:
212 self._canNavigate = canNavigate
213 if self._potentialButtonState in (self.BUTTON_STATE_NEXT, self.BUTTON_STATE_BACK):
214 if self._currentButtonState == self.BUTTON_STATE_PLAY:
215 newPotState = self.BUTTON_STATE_PAUSE
217 newPotState = self.BUTTON_STATE_PLAY
219 if newPotState != self._potentialButtonState:
220 self._potentialButtonState = newPotState
221 if not self._imageNav.is_active():
222 cairoContext = self._image.window.cairo_create()
223 if not self._isPortrait:
224 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
225 self._draw_state(cairoContext, self._potentialButtonState)
227 @misc_utils.log_exception(_moduleLogger)
228 def _on_player_title_change(self, player, newState):
230 backWidth = self._backgroundImage.get_width()
231 backHeight = self._backgroundImage.get_height()
233 backHeight = self._backgroundImage.get_width()
234 backWidth = self._backgroundImage.get_height()
235 self._image.set_size_request(backWidth, backHeight)
237 imagePath = self._store.STORE_LOOKUP[self._player.background]
238 self._backgroundImage = self._store.get_surface_from_store(imagePath)
239 if not self._imageNav.get_state():
240 cairoContext = self._image.window.cairo_create()
241 if not self._isPortrait:
242 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
243 self._draw_presenter(cairoContext, self._currentButtonState)
245 cairoContext = self._image.window.cairo_create()
246 if not self._isPortrait:
247 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
248 self._draw_presenter(cairoContext, self._potentialButtonState)
250 @misc_utils.log_exception(_moduleLogger)
251 def _on_navigating(self, widget, navState):
252 buttonState = self._translate_state(navState)
253 self._potentialButtonState = buttonState
254 cairoContext = self._image.window.cairo_create()
255 if not self._isPortrait:
256 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
257 self._draw_state(cairoContext, self._potentialButtonState)
259 @misc_utils.log_exception(_moduleLogger)
260 def _on_nav_action(self, widget, navState):
262 buttonState = self._translate_state(navState)
263 if buttonState == self.BUTTON_STATE_PLAY:
265 elif buttonState == self.BUTTON_STATE_PAUSE:
267 elif buttonState == self.BUTTON_STATE_NEXT:
269 elif buttonState == self.BUTTON_STATE_BACK:
271 elif buttonState == self.BUTTON_STATE_UP:
272 raise NotImplementedError("Drag-down not implemented yet")
273 elif buttonState == self.BUTTON_STATE_CANCEL:
276 if self._player.state == "play":
277 buttonState = self.BUTTON_STATE_PLAY
279 buttonState = self.BUTTON_STATE_PAUSE
280 self._potentialButtonState = buttonState
281 cairoContext = self._image.window.cairo_create()
282 if not self._isPortrait:
283 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
284 self._draw_state(cairoContext, self._potentialButtonState)
286 def _translate_state(self, navState):
287 if navState == "clicking" or not self._canNavigate:
288 if self._currentButtonState == self.BUTTON_STATE_PLAY:
289 return self.BUTTON_STATE_PAUSE
291 return self.BUTTON_STATE_PLAY
292 elif navState == "down":
293 return self.BUTTON_STATE_UP
294 elif navState == "up":
295 return self.BUTTON_STATE_CANCEL
296 elif navState == "left":
297 return self.BUTTON_STATE_NEXT
298 elif navState == "right":
299 return self.BUTTON_STATE_BACK
301 @misc_utils.log_exception(_moduleLogger)
302 def _on_expose(self, widget, event):
303 self._potentialButtonState = self._player.state
304 cairoContext = self._image.window.cairo_create()
305 if not self._isPortrait:
306 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
307 self._draw_presenter(cairoContext, self._player.state)
309 def _draw_presenter(self, cairoContext, state):
310 assert state in (self._currentButtonState, self._potentialButtonState)
313 rect = self._image.get_allocation()
314 cairoContext.rectangle(
320 cairoContext.set_source_rgb(0, 0, 0)
325 cairoContext.set_source_surface(
326 self._backgroundImage,
333 if self._player.title:
334 _moduleLogger.info("Displaying text")
335 backWidth = self._backgroundImage.get_width()
336 backHeight = self._backgroundImage.get_height()
338 pangoContext = self._image.create_pango_context()
339 textLayout = pango.Layout(pangoContext)
340 textLayout.set_markup(self._player.title)
342 textWidth, textHeight = textLayout.get_pixel_size()
343 textX = backWidth / 2 - textWidth / 2
344 textY = backHeight - textHeight - self._buttonImage.get_height()
346 cairoContext.move_to(textX, textY)
347 cairoContext.set_source_rgb(0, 0, 0)
348 cairoContext.show_layout(textLayout)
350 self._draw_state(cairoContext, state)
352 def _draw_state(self, cairoContext, state):
353 assert state in (self._currentButtonState, self._potentialButtonState)
354 if state == self.BUTTON_STATE_CANCEL:
355 state = self._currentButtonState
357 backWidth = self._backgroundImage.get_width()
358 backHeight = self._backgroundImage.get_height()
360 imagePath = self._STATE_TO_IMAGE[state]
361 self._buttonImage = self._store.get_surface_from_store(imagePath)
362 cairoContext.set_source_surface(
364 backWidth / 2 - self._buttonImage.get_width() / 2,
365 backHeight - self._buttonImage.get_height() + 5,
370 class StreamMiniPresenter(object):
372 def __init__(self, player, store):
374 self._player = player
375 self._player.connect("state-change", self._on_player_state_change)
377 self._button = gtk.Image()
378 if self._player.state == "play":
379 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["play"])
381 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["pause"])
383 self._eventBox = gtk.EventBox()
384 self._eventBox.add(self._button)
385 self._eventBox.connect("button_release_event", self._on_button_release)
389 return self._eventBox
391 @misc_utils.log_exception(_moduleLogger)
392 def _on_player_state_change(self, player, newState):
393 if self._player.state == "play":
394 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["play"])
396 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["pause"])
398 @misc_utils.log_exception(_moduleLogger)
399 def _on_button_release(self, widget, event):
400 if self._player.state == "play":