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",
133 BUTTON_STATE_UP: "home.png",
136 def __init__(self, player, store):
139 self._player = player
140 self._player.connect("state-change", self._on_player_state_change)
141 self._player.connect("navigate-change", self._on_player_nav_change)
142 self._player.connect("title-change", self._on_player_title_change)
144 self._image = gtk.DrawingArea()
145 self._image.connect("expose_event", self._on_expose)
146 self._imageNav = NavigationBox()
147 self._imageNav.toplevel.add(self._image)
148 self._imageNav.connect("navigating", self._on_navigating)
149 self._imageNav.connect("action", self._on_nav_action)
151 self._isPortrait = True
153 self._canNavigate = True
154 self._potentialButtonState = self.BUTTON_STATE_PLAY
155 self._currentButtonState = self.BUTTON_STATE_PLAY
157 imagePath = self._store.STORE_LOOKUP[self._player.background]
158 self._backgroundImage = self._store.get_surface_from_store(imagePath)
159 imagePath = self._STATE_TO_IMAGE[self._currentButtonState]
160 self._buttonImage = self._store.get_surface_from_store(imagePath)
163 backWidth = self._backgroundImage.get_width()
164 backHeight = self._backgroundImage.get_height()
166 backHeight = self._backgroundImage.get_width()
167 backWidth = self._backgroundImage.get_height()
168 self._image.set_size_request(backWidth, backHeight)
172 return self._imageNav.toplevel
174 def set_orientation(self, orientation):
175 self._imageNav.set_orientation(orientation)
177 if orientation == gtk.ORIENTATION_VERTICAL:
178 self._isPortrait = True
179 elif orientation == gtk.ORIENTATION_HORIZONTAL:
180 self._isPortrait = False
182 raise NotImplementedError(orientation)
184 cairoContext = self._image.window.cairo_create()
185 if not self._isPortrait:
186 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
187 self._draw_presenter(cairoContext, self._currentButtonState)
189 @misc_utils.log_exception(_moduleLogger)
190 def _on_player_state_change(self, player, newState):
191 # @bug We only want to folow changes in player when its active
192 if newState == "play":
193 newState = self.BUTTON_STATE_PLAY
194 elif newState == "pause":
195 newState = self.BUTTON_STATE_PAUSE
196 elif newState == "stop":
197 newState = self.BUTTON_STATE_PAUSE
199 newState = self._currentButtonState
201 if newState != self._currentButtonState:
202 self._currentButtonState = newState
203 if not self._imageNav.is_active():
204 cairoContext = self._image.window.cairo_create()
205 if not self._isPortrait:
206 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
207 self._draw_state(cairoContext, self._currentButtonState)
209 @misc_utils.log_exception(_moduleLogger)
210 def _on_player_nav_change(self, player, newState):
211 # @bug We only want to folow changes in player when its active
212 canNavigate = self._player.can_navigate
213 newPotState = self._potentialButtonState
214 if self._canNavigate != canNavigate:
215 self._canNavigate = canNavigate
216 if self._potentialButtonState in (self.BUTTON_STATE_NEXT, self.BUTTON_STATE_BACK):
217 if self._currentButtonState == self.BUTTON_STATE_PLAY:
218 newPotState = self.BUTTON_STATE_PAUSE
220 newPotState = self.BUTTON_STATE_PLAY
222 if newPotState != self._potentialButtonState:
223 self._potentialButtonState = newPotState
224 if not self._imageNav.is_active():
225 cairoContext = self._image.window.cairo_create()
226 if not self._isPortrait:
227 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
228 self._draw_state(cairoContext, self._potentialButtonState)
230 @misc_utils.log_exception(_moduleLogger)
231 def _on_player_title_change(self, player, newState):
232 # @bug We only want to folow changes in player when its active
234 backWidth = self._backgroundImage.get_width()
235 backHeight = self._backgroundImage.get_height()
237 backHeight = self._backgroundImage.get_width()
238 backWidth = self._backgroundImage.get_height()
239 self._image.set_size_request(backWidth, backHeight)
241 imagePath = self._store.STORE_LOOKUP[self._player.background]
242 self._backgroundImage = self._store.get_surface_from_store(imagePath)
243 if not self._imageNav.get_state():
244 cairoContext = self._image.window.cairo_create()
245 if not self._isPortrait:
246 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
247 self._draw_presenter(cairoContext, self._currentButtonState)
249 cairoContext = self._image.window.cairo_create()
250 if not self._isPortrait:
251 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
252 self._draw_presenter(cairoContext, self._potentialButtonState)
254 @misc_utils.log_exception(_moduleLogger)
255 def _on_navigating(self, widget, navState):
256 buttonState = self._translate_state(navState)
257 self._potentialButtonState = buttonState
258 cairoContext = self._image.window.cairo_create()
259 if not self._isPortrait:
260 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
261 self._draw_state(cairoContext, self._potentialButtonState)
263 @misc_utils.log_exception(_moduleLogger)
264 def _on_nav_action(self, widget, navState):
265 # @bug We only want to folow changes in player when its active
267 buttonState = self._translate_state(navState)
268 if buttonState == self.BUTTON_STATE_PLAY:
270 elif buttonState == self.BUTTON_STATE_PAUSE:
272 elif buttonState == self.BUTTON_STATE_NEXT:
274 elif buttonState == self.BUTTON_STATE_BACK:
276 elif buttonState == self.BUTTON_STATE_UP:
277 raise NotImplementedError("Drag-down not implemented yet")
278 elif buttonState == self.BUTTON_STATE_CANCEL:
281 if self._player.state == "play":
282 buttonState = self.BUTTON_STATE_PLAY
284 buttonState = self.BUTTON_STATE_PAUSE
285 self._potentialButtonState = buttonState
286 cairoContext = self._image.window.cairo_create()
287 if not self._isPortrait:
288 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
289 self._draw_state(cairoContext, self._potentialButtonState)
291 def _translate_state(self, navState):
292 if navState == "clicking" or not self._canNavigate:
293 if self._currentButtonState == self.BUTTON_STATE_PLAY:
294 return self.BUTTON_STATE_PAUSE
296 return self.BUTTON_STATE_PLAY
297 elif navState == "down":
298 return self.BUTTON_STATE_UP
299 elif navState == "up":
300 return self.BUTTON_STATE_CANCEL
301 elif navState == "left":
302 return self.BUTTON_STATE_NEXT
303 elif navState == "right":
304 return self.BUTTON_STATE_BACK
306 @misc_utils.log_exception(_moduleLogger)
307 def _on_expose(self, widget, event):
308 self._potentialButtonState = self._player.state
309 cairoContext = self._image.window.cairo_create()
310 if not self._isPortrait:
311 cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
312 self._draw_presenter(cairoContext, self._player.state)
314 def _draw_presenter(self, cairoContext, state):
315 assert state in (self._currentButtonState, self._potentialButtonState)
318 rect = self._image.get_allocation()
319 cairoContext.rectangle(
325 cairoContext.set_source_rgb(0, 0, 0)
330 cairoContext.set_source_surface(
331 self._backgroundImage,
338 if self._player.title:
339 _moduleLogger.info("Displaying text")
340 backWidth = self._backgroundImage.get_width()
341 backHeight = self._backgroundImage.get_height()
343 pangoContext = self._image.create_pango_context()
344 textLayout = pango.Layout(pangoContext)
345 textLayout.set_markup(self._player.title)
347 textWidth, textHeight = textLayout.get_pixel_size()
348 textX = backWidth / 2 - textWidth / 2
349 textY = backHeight - textHeight - self._buttonImage.get_height()
351 cairoContext.move_to(textX, textY)
352 cairoContext.set_source_rgb(0, 0, 0)
353 cairoContext.show_layout(textLayout)
355 self._draw_state(cairoContext, state)
357 def _draw_state(self, cairoContext, state):
358 assert state in (self._currentButtonState, self._potentialButtonState)
359 if state == self.BUTTON_STATE_CANCEL:
360 state = self._currentButtonState
362 backWidth = self._backgroundImage.get_width()
363 backHeight = self._backgroundImage.get_height()
365 imagePath = self._STATE_TO_IMAGE[state]
366 self._buttonImage = self._store.get_surface_from_store(imagePath)
367 cairoContext.set_source_surface(
369 backWidth / 2 - self._buttonImage.get_width() / 2,
370 backHeight - self._buttonImage.get_height() + 5,
375 class StreamMiniPresenter(object):
377 def __init__(self, player, store):
379 self._player = player
380 self._player.connect("state-change", self._on_player_state_change)
382 self._button = gtk.Image()
383 if self._player.state == "play":
384 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["play"])
386 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["pause"])
388 self._eventBox = gtk.EventBox()
389 self._eventBox.add(self._button)
390 self._eventBox.connect("button_release_event", self._on_button_release)
394 return self._eventBox
396 @misc_utils.log_exception(_moduleLogger)
397 def _on_player_state_change(self, player, newState):
398 if self._player.state == "play":
399 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["play"])
401 self._store.set_image_from_store(self._button, self._store.STORE_LOOKUP["pause"])
403 @misc_utils.log_exception(_moduleLogger)
404 def _on_button_release(self, widget, event):
405 if self._player.state == "play":