Removing use of small icons to be more finger friendly on Maemo
[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                 startContent = 30, self._dims[1] - 125
184                 endContent = self._dims[0] - 30,  self._dims[1] - 5
185
186                 # Blank things
187                 cairoContext.rectangle(
188                         0,
189                         0,
190                         rect.width,
191                         rect.height,
192                 )
193                 cairoContext.set_source_rgb(0, 0, 0)
194                 cairoContext.fill()
195
196                 # Draw Background
197                 if self._backgroundImage is not None:
198                         cairoContext.set_source_surface(
199                                 self._backgroundImage,
200                                 0,
201                                 0,
202                         )
203                         cairoContext.paint()
204
205                 # Control background
206                 cairoContext.rectangle(
207                         startContent[0],
208                         startContent[1],
209                         endContent[0] - startContent[0],
210                         endContent[1] - startContent[1],
211                 )
212                 cairoContext.set_source_rgba(0.9, 0.9, 0.9, 0.75)
213                 cairoContext.fill()
214
215                 # title
216                 if self._title or self._subtitle:
217                         pangoContext = self._image.create_pango_context()
218                         textLayout = pango.Layout(pangoContext)
219
220                         textLayout.set_markup(self._subtitle)
221                         textWidth, textHeight = textLayout.get_pixel_size()
222                         subtitleTextX = self._dims[0] / 2 - textWidth / 2
223                         subtitleTextY = self._dims[1] - textHeight - self._buttonImage.get_height() + 10
224                         cairoContext.move_to(subtitleTextX, subtitleTextY)
225                         cairoContext.set_source_rgb(0, 0, 0)
226                         cairoContext.show_layout(textLayout)
227
228                         textLayout.set_markup(self._title)
229                         textWidth, textHeight = textLayout.get_pixel_size()
230                         textX = self._dims[0] / 2 - textWidth / 2
231                         textY = subtitleTextY - textHeight
232                         cairoContext.move_to(textX, textY)
233                         cairoContext.set_source_rgb(0, 0, 0)
234                         cairoContext.show_layout(textLayout)
235
236                 self._draw_state(cairoContext)
237
238         def _draw_state(self, cairoContext):
239                 if self._backgroundImage is None or self._buttonImage is None:
240                         return
241                 cairoContext.set_source_surface(
242                         self._buttonImage,
243                         self._dims[0] / 2 - self._buttonImage.get_width() / 2,
244                         self._dims[1] - self._buttonImage.get_height() + 5,
245                 )
246                 cairoContext.paint()
247
248
249 class StreamMiniPresenter(object):
250
251         def __init__(self, store):
252                 self._store = store
253
254                 self._button = gtk.Image()
255
256         @property
257         def toplevel(self):
258                 return self._button
259
260         def set_orientation(self, orientation):
261                 pass
262
263         def set_state(self, stateImage):
264                 self._store.set_image_from_store(self._button, stateImage)
265
266         def set_context(self, backgroundImage, title, subtitle):
267                 pass