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