73afaf519ae4da878154fd314dd8a4ac35b84d0c
[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 = 32
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                 self._imageName = ""
134                 self._dims = 0, 0
135
136         @property
137         def toplevel(self):
138                 return self._image
139
140         def set_orientation(self, orientation):
141                 if orientation == gtk.ORIENTATION_VERTICAL:
142                         self._isPortrait = True
143                 elif orientation == gtk.ORIENTATION_HORIZONTAL:
144                         self._isPortrait = False
145                 else:
146                         raise NotImplementedError(orientation)
147
148                 self._image.queue_draw()
149
150         def set_state(self, stateImage):
151                 if stateImage == self._imageName:
152                         return
153                 self._imageName = stateImage
154                 self._buttonImage = self._store.get_surface_from_store(stateImage)
155
156                 self._image.queue_draw()
157
158         def set_context(self, backgroundImage, title, subtitle):
159                 self._backgroundImage = self._store.get_surface_from_store(backgroundImage)
160                 self._title = title
161                 self._subtitle = subtitle
162
163                 if self._isPortrait:
164                         backWidth = self._backgroundImage.get_width()
165                         backHeight = self._backgroundImage.get_height()
166                 else:
167                         backHeight = self._backgroundImage.get_width()
168                         backWidth = self._backgroundImage.get_height()
169                 self._image.set_size_request(backWidth, backHeight)
170
171                 self._image.queue_draw()
172
173         @misc_utils.log_exception(_moduleLogger)
174         def _on_expose(self, widget, event):
175                 cairoContext = self._image.window.cairo_create()
176                 if not self._isPortrait:
177                         cairoContext.transform(cairo.Matrix(0, 1, 1, 0, 0, 0))
178                 self._draw_presenter(cairoContext)
179
180         def _draw_presenter(self, cairoContext):
181                 rect = self._image.get_allocation()
182                 self._dims = rect.width, rect.height
183
184                 # Blank things
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
194                 # Draw Background
195                 if self._backgroundImage is not None:
196                         cairoContext.set_source_surface(
197                                 self._backgroundImage,
198                                 0,
199                                 0,
200                         )
201                         cairoContext.paint()
202
203                 pangoContext = self._image.create_pango_context()
204
205                 titleLayout = pango.Layout(pangoContext)
206                 titleLayout.set_markup(self._subtitle)
207                 textWidth, textHeight = titleLayout.get_pixel_size()
208                 subtitleTextX = self._dims[0] / 2 - textWidth / 2
209                 subtitleTextY = self._dims[1] - textHeight - self._buttonImage.get_height() + 10
210
211                 subtitleLayout = pango.Layout(pangoContext)
212                 subtitleLayout.set_markup(self._title)
213                 textWidth, textHeight = subtitleLayout.get_pixel_size()
214                 textX = self._dims[0] / 2 - textWidth / 2
215                 textY = subtitleTextY - textHeight
216
217                 startContent = 30, textY - 5
218                 endContent = self._dims[0] - 30,  self._dims[1] - 5
219
220                 # Control background
221                 cairoContext.rectangle(
222                         startContent[0],
223                         startContent[1],
224                         endContent[0] - startContent[0],
225                         endContent[1] - startContent[1],
226                 )
227                 cairoContext.set_source_rgba(0.9, 0.9, 0.9, 0.75)
228                 cairoContext.fill()
229
230                 # title
231                 if self._title or self._subtitle:
232                         cairoContext.move_to(subtitleTextX, subtitleTextY)
233                         cairoContext.set_source_rgb(0, 0, 0)
234                         cairoContext.show_layout(titleLayout)
235
236                         cairoContext.move_to(textX, textY)
237                         cairoContext.set_source_rgb(0, 0, 0)
238                         cairoContext.show_layout(subtitleLayout)
239
240                 self._draw_state(cairoContext)
241
242         def _draw_state(self, cairoContext):
243                 if self._backgroundImage is None or self._buttonImage is None:
244                         return
245                 cairoContext.set_source_surface(
246                         self._buttonImage,
247                         self._dims[0] / 2 - self._buttonImage.get_width() / 2,
248                         self._dims[1] - self._buttonImage.get_height() + 5,
249                 )
250                 cairoContext.paint()
251
252
253 class StreamMiniPresenter(object):
254
255         def __init__(self, store):
256                 self._store = store
257
258                 self._button = gtk.Image()
259
260         @property
261         def toplevel(self):
262                 return self._button
263
264         def set_orientation(self, orientation):
265                 pass
266
267         def set_state(self, stateImage):
268                 self._store.set_image_from_store(self._button, stateImage)
269
270         def set_context(self, backgroundImage, title, subtitle):
271                 pass