Integrating and testing of playback
[watersofshiloah] / src / presenter.py
1 import logging
2
3 import gobject
4 import pango
5 import cairo
6 import gtk
7
8 import util.misc as misc_utils
9
10
11 _moduleLogger = logging.getLogger(__name__)
12
13
14 class NavigationBox(gobject.GObject):
15
16         __gsignals__ = {
17                 'action' : (
18                         gobject.SIGNAL_RUN_LAST,
19                         gobject.TYPE_NONE,
20                         (gobject.TYPE_STRING, ),
21                 ),
22                 'navigating' : (
23                         gobject.SIGNAL_RUN_LAST,
24                         gobject.TYPE_NONE,
25                         (gobject.TYPE_STRING, ),
26                 ),
27         }
28
29         MINIMUM_MOVEMENT = 20
30
31         _NO_POSITION = -1, -1
32
33         def __init__(self):
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)
39
40                 self._isPortrait = True
41                 self._clickPosition = self._NO_POSITION
42
43         @property
44         def toplevel(self):
45                 return self._eventBox
46
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
52                 else:
53                         raise NotImplementedError(orientation)
54
55         def is_active(self):
56                 return self._clickPosition != self._NO_POSITION
57
58         def get_state(self, newCoord):
59                 if self._clickPosition == self._NO_POSITION:
60                         return ""
61
62                 if self._isPortrait:
63                         delta = (
64                                 newCoord[0] - self._clickPosition[0],
65                                 - (newCoord[1] - self._clickPosition[1])
66                         )
67                 else:
68                         delta = (
69                                 newCoord[1] - self._clickPosition[1],
70                                 - (newCoord[0] - self._clickPosition[0])
71                         )
72                 absDelta = (abs(delta[0]), abs(delta[1]))
73                 if max(*absDelta) < self.MINIMUM_MOVEMENT:
74                         return "clicking"
75
76                 if absDelta[0] < absDelta[1]:
77                         if 0 < delta[1]:
78                                 return "up"
79                         else:
80                                 return "down"
81                 else:
82                         if 0 < delta[0]:
83                                 return "right"
84                         else:
85                                 return "left"
86
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()
92
93                 self.emit("navigating", "clicking")
94
95         @misc_utils.log_exception(_moduleLogger)
96         def _on_button_release(self, widget, event):
97                 assert self._clickPosition != self._NO_POSITION
98                 try:
99                         mousePosition = event.get_coords()
100                         state = self.get_state(mousePosition)
101                         assert state
102                 finally:
103                         self._clickPosition = self._NO_POSITION
104                 self.emit("action", state)
105
106         @misc_utils.log_exception(_moduleLogger)
107         def _on_motion_notify(self, widget, event):
108                 if self._clickPosition == self._NO_POSITION:
109                         return
110
111                 mousePosition = event.get_coords()
112                 newState = self.get_state(mousePosition)
113                 self.emit("navigating", newState)
114
115
116 gobject.type_register(NavigationBox)
117
118
119 class StreamPresenter(object):
120
121         def __init__(self, store):
122                 self._store = store
123
124                 self._image = gtk.DrawingArea()
125                 self._image.connect("expose_event", self._on_expose)
126
127                 self._isPortrait = True
128
129                 self._backgroundImage = None
130                 self._title = ""
131                 self._subtitle = ""
132                 self._buttonImage = None
133
134         @property
135         def toplevel(self):
136                 return self._image
137
138         def set_orientation(self, orientation):
139                 if orientation == gtk.ORIENTATION_VERTICAL:
140                         self._isPortrait = True
141                 elif orientation == gtk.ORIENTATION_HORIZONTAL:
142                         self._isPortrait = False
143                 else:
144                         raise NotImplementedError(orientation)
145
146                 cairoContext = self._image.window.cairo_create()
147                 if not self._isPortrait:
148                         cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
149                 self._draw_presenter(cairoContext)
150
151         def set_state(self, stateImage):
152                 self._buttonImage = self._store.get_surface_from_store(stateImage)
153
154                 cairoContext = self._image.window.cairo_create()
155                 if not self._isPortrait:
156                         cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
157                 self._draw_state(cairoContext)
158
159         def set_context(self, backgroundImage, title, subtitle):
160                 self._backgroundImage = self._store.get_surface_from_store(backgroundImage)
161
162                 if self._isPortrait:
163                         backWidth = self._backgroundImage.get_width()
164                         backHeight = self._backgroundImage.get_height()
165                 else:
166                         backHeight = self._backgroundImage.get_width()
167                         backWidth = self._backgroundImage.get_height()
168                 self._image.set_size_request(backWidth, backHeight)
169
170                 cairoContext = self._image.window.cairo_create()
171                 if not self._isPortrait:
172                         cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
173                 self._draw_presenter(cairoContext)
174
175         @misc_utils.log_exception(_moduleLogger)
176         def _on_expose(self, widget, event):
177                 cairoContext = self._image.window.cairo_create()
178                 if not self._isPortrait:
179                         cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
180                 self._draw_presenter(cairoContext)
181
182         def _draw_presenter(self, cairoContext):
183                 # Blank things
184                 rect = self._image.get_allocation()
185                 cairoContext.rectangle(
186                         0,
187                         0,
188                         rect.width,
189                         rect.height,
190                 )
191                 cairoContext.set_source_rgb(0, 0, 0)
192                 cairoContext.fill()
193                 cairoContext.paint()
194
195                 # Draw Background
196                 if self._backgroundImage is not None:
197                         cairoContext.set_source_surface(
198                                 self._backgroundImage,
199                                 0,
200                                 0,
201                         )
202                         cairoContext.paint()
203
204                 # title
205                 if self._title or self._subtitle:
206                         backWidth = self._backgroundImage.get_width()
207                         backHeight = self._backgroundImage.get_height()
208
209                         pangoContext = self._image.create_pango_context()
210                         textLayout = pango.Layout(pangoContext)
211
212                         textLayout.set_markup(self._subtitle)
213                         textWidth, textHeight = textLayout.get_pixel_size()
214                         subtitleTextX = backWidth / 2 - textWidth / 2
215                         subtitleTextY = backHeight - textHeight - self._buttonImage.get_height()
216                         cairoContext.move_to(subtitleTextX, subtitleTextY)
217                         cairoContext.set_source_rgb(0, 0, 0)
218                         cairoContext.show_layout(textLayout)
219
220                         textLayout.set_markup(self._title)
221                         textWidth, textHeight = textLayout.get_pixel_size()
222                         textX = backWidth / 2 - textWidth / 2
223                         textY = subtitleTextY - textHeight
224                         cairoContext.move_to(textX, textY)
225                         cairoContext.set_source_rgb(0, 0, 0)
226                         cairoContext.show_layout(textLayout)
227
228                 self._draw_state(cairoContext)
229
230         def _draw_state(self, cairoContext):
231                 if self._backgroundImage is None or self._buttonImage is None:
232                         return
233                 backWidth = self._backgroundImage.get_width()
234                 backHeight = self._backgroundImage.get_height()
235
236                 cairoContext.set_source_surface(
237                         self._buttonImage,
238                         backWidth / 2 - self._buttonImage.get_width() / 2,
239                         backHeight - self._buttonImage.get_height() + 5,
240                 )
241                 cairoContext.paint()
242
243
244 class StreamMiniPresenter(object):
245
246         def __init__(self, store):
247                 self._store = store
248
249                 self._button = gtk.Image()
250
251         @property
252         def toplevel(self):
253                 return self._button
254
255         def set_orientation(self, orientation):
256                 pass
257
258         def set_state(self, stateImage):
259                 self._store.set_image_from_store(self._button, stateImage)
260
261         def set_context(self, backgroundImage, title, subtitle):
262                 pass