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 # @bug We only want to folow changes in player when its active
191 if newState == "play":
192 newState = self.BUTTON_STATE_PLAY
193 elif newState == "pause":
194 newState = self.BUTTON_STATE_PAUSE
195 elif newState == "stop":
196 newState = self.BUTTON_STATE_PAUSE
198 newState = self._currentButtonState
200 if newState != self._currentButtonState:
201 self._currentButtonState = newState
202 if not self._imageNav.is_active():
203 cairoContext = self._image.window.cairo_create()
204 if not self._isPortrait:
205 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
206 self._draw_state(cairoContext, self._currentButtonState)
208 @misc_utils.log_exception(_moduleLogger)
209 def _on_player_nav_change(self, player, newState):
210 # @bug We only want to folow changes in player when its active
211 canNavigate = self._player.can_navigate
212 newPotState = self._potentialButtonState
213 if self._canNavigate != canNavigate:
214 self._canNavigate = canNavigate
215 if self._potentialButtonState in (self.BUTTON_STATE_NEXT, self.BUTTON_STATE_BACK):
216 if self._currentButtonState == self.BUTTON_STATE_PLAY:
217 newPotState = self.BUTTON_STATE_PAUSE
219 newPotState = self.BUTTON_STATE_PLAY
221 if newPotState != self._potentialButtonState:
222 self._potentialButtonState = newPotState
223 if not self._imageNav.is_active():
224 cairoContext = self._image.window.cairo_create()
225 if not self._isPortrait:
226 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
227 self._draw_state(cairoContext, self._potentialButtonState)
229 @misc_utils.log_exception(_moduleLogger)
230 def _on_player_title_change(self, player, newState):
231 # @bug We only want to folow changes in player when its active
233 backWidth = self._backgroundImage.get_width()
234 backHeight = self._backgroundImage.get_height()
236 backHeight = self._backgroundImage.get_width()
237 backWidth = self._backgroundImage.get_height()
238 self._image.set_size_request(backWidth, backHeight)
240 imagePath = self._store.STORE_LOOKUP[self._player.background]
241 self._backgroundImage = self._store.get_surface_from_store(imagePath)
242 if not self._imageNav.get_state():
243 cairoContext = self._image.window.cairo_create()
244 if not self._isPortrait:
245 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
246 self._draw_presenter(cairoContext, self._currentButtonState)
248 cairoContext = self._image.window.cairo_create()
249 if not self._isPortrait:
250 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
251 self._draw_presenter(cairoContext, self._potentialButtonState)
253 @misc_utils.log_exception(_moduleLogger)
254 def _on_navigating(self, widget, navState):
255 buttonState = self._translate_state(navState)
256 self._potentialButtonState = buttonState
257 cairoContext = self._image.window.cairo_create()
258 if not self._isPortrait:
259 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
260 self._draw_state(cairoContext, self._potentialButtonState)
262 @misc_utils.log_exception(_moduleLogger)
263 def _on_nav_action(self, widget, navState):
264 # @bug We only want to folow changes in player when its active
266 buttonState = self._translate_state(navState)
267 if buttonState == self.BUTTON_STATE_PLAY:
269 elif buttonState == self.BUTTON_STATE_PAUSE:
271 elif buttonState == self.BUTTON_STATE_NEXT:
273 elif buttonState == self.BUTTON_STATE_BACK:
275 elif buttonState == self.BUTTON_STATE_UP:
276 raise NotImplementedError("Drag-down not implemented yet")
277 elif buttonState == self.BUTTON_STATE_CANCEL:
280 if self._player.state == "play":
281 buttonState = self.BUTTON_STATE_PLAY
283 buttonState = self.BUTTON_STATE_PAUSE
284 self._potentialButtonState = buttonState
285 cairoContext = self._image.window.cairo_create()
286 if not self._isPortrait:
287 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
288 self._draw_state(cairoContext, self._potentialButtonState)
290 def _translate_state(self, navState):
291 if navState == "clicking" or not self._canNavigate:
292 if self._currentButtonState == self.BUTTON_STATE_PLAY:
293 return self.BUTTON_STATE_PAUSE
295 return self.BUTTON_STATE_PLAY
296 elif navState == "down":
297 return self.BUTTON_STATE_UP
298 elif navState == "up":
299 return self.BUTTON_STATE_CANCEL
300 elif navState == "left":
301 return self.BUTTON_STATE_NEXT
302 elif navState == "right":
303 return self.BUTTON_STATE_BACK
305 @misc_utils.log_exception(_moduleLogger)
306 def _on_expose(self, widget, event):
307 self._potentialButtonState = self._player.state
308 cairoContext = self._image.window.cairo_create()
309 if not self._isPortrait:
310 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
311 self._draw_presenter(cairoContext, self._player.state)
313 def _draw_presenter(self, cairoContext, state):
314 assert state in (self._currentButtonState, self._potentialButtonState)
317 rect = self._image.get_allocation()
318 cairoContext.rectangle(
324 cairoContext.set_source_rgb(0, 0, 0)
329 cairoContext.set_source_surface(
330 self._backgroundImage,
337 if self._player.title:
338 _moduleLogger.info("Displaying text")
339 backWidth = self._backgroundImage.get_width()
340 backHeight = self._backgroundImage.get_height()
342 pangoContext = self._image.create_pango_context()
343 textLayout = pango.Layout(pangoContext)
344 textLayout.set_markup(self._player.title)
346 textWidth, textHeight = textLayout.get_pixel_size()
347 textX = backWidth / 2 - textWidth / 2
348 textY = backHeight - textHeight - self._buttonImage.get_height()
350 cairoContext.move_to(textX, textY)
351 cairoContext.set_source_rgb(0, 0, 0)
352 cairoContext.show_layout(textLayout)
354 self._draw_state(cairoContext, state)
356 def _draw_state(self, cairoContext, state):
357 assert state in (self._currentButtonState, self._potentialButtonState)
358 if state == self.BUTTON_STATE_CANCEL:
359 state = self._currentButtonState
361 backWidth = self._backgroundImage.get_width()
362 backHeight = self._backgroundImage.get_height()
364 imagePath = self._STATE_TO_IMAGE[state]
365 self._buttonImage = self._store.get_surface_from_store(imagePath)
366 cairoContext.set_source_surface(
368 backWidth / 2 - self._buttonImage.get_width() / 2,
369 backHeight - self._buttonImage.get_height() + 5,
374 class StreamMiniPresenter(object):
376 def __init__(self, player, store):
378 self._player = player
379 self._player.connect("state-change", self._on_player_state_change)
381 self._button = gtk.Image()
382 if self._player.state == "play":
383 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["play"])
385 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["pause"])
387 self._eventBox = gtk.EventBox()
388 self._eventBox.add(self._button)
389 self._eventBox.connect("button_release_event", self._on_button_release)
393 return self._eventBox
395 @misc_utils.log_exception(_moduleLogger)
396 def _on_player_state_change(self, player, newState):
397 if self._player.state == "play":
398 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["play"])
400 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["pause"])
402 @misc_utils.log_exception(_moduleLogger)
403 def _on_button_release(self, widget, event):
404 if self._player.state == "play":