Merge lp:~andrea.corbellini/a4/clean-player into lp:a4
- clean-player
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 49 |
Proposed branch: | lp:~andrea.corbellini/a4/clean-player |
Merge into: | lp:a4 |
Diff against target: |
451 lines (+139/-143) 3 files modified
a4lib/app.py (+4/-5) a4lib/player.py (+107/-117) a4lib/region.py (+28/-21) |
To merge this branch: | bzr merge lp:~andrea.corbellini/a4/clean-player |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Andrea Gasparini | Approve | ||
Review via email: mp+29842@code.launchpad.net |
Commit message
Description of the change
Here I've focused my attention of cleaning up the a4lib.player module. Here's what I've done:
* PEP 8 and 257.
* Some optimizations:
1. removed all comparisons with None;
2. replaced lists with tuples when possible;
3. use of local variables when possible;
4. some other minus optimizations.
There's a thing that I've not cleaned up because I would like to have some feedback on the topic. I see there are three classes for just one player: Player, CairoPlayer, GtkCairoPlayer. I'd merge everything into Player if there are no objections.
- 51. By Andrea Corbellini
-
Little fix.
Andrea Corbellini (andrea.corbellini) wrote : | # |
On Wed, 2010-07-14 at 09:15 +0000, Andrea Gasparini wrote:
> Review: Needs Fixing
> Completely agree on the cleaning part.
>
> Just a little fix, these lines:
> 175 + self.area = drawing_area
> 176 + self.drawing_
> have to be:
> 175 + self.area = drawing_area
> 176 + self.area.
> Otherwise it wont work.
Oops, sorry! I thought I had tested everything.
> About *Player class, perhaps it'd be fine to merge CairoPlayer and
> GtkCairoPlayer (let's still think about it a little); although the
> base Player class could be useful, as it could be tested without a
> GUI, and cause it could be subclassed by a future Wx/Qt/web/whatever
> based GUI.
That's fine, I agree.
--
Ubuntu member | http://
BeeSeek member | http://
Andrea Gasparini (gaspa) wrote : | # |
Yay, approving! thanks ;)
Preview Diff
1 | === modified file 'a4lib/app.py' |
2 | --- a4lib/app.py 2010-07-08 14:49:06 +0000 |
3 | +++ a4lib/app.py 2010-07-14 09:29:23 +0000 |
4 | @@ -59,10 +59,9 @@ |
5 | """Open a file inside this window.""" |
6 | self.set_status('Loading {0}...'.format(file_name)) |
7 | try: |
8 | - # Try to open a file. |
9 | + # Try to open the file. |
10 | self.player = GtkCairoPlayer( |
11 | - self.builder.get_object('drawing_area')) |
12 | - self.player.load_presentation(file_name) |
13 | + file_name, self.builder.get_object('drawing_area')) |
14 | except PresentationError as error: |
15 | # The file doesn't exist, or is not an SVG file. Show an error |
16 | # dialog and don't do anything else. |
17 | @@ -146,12 +145,12 @@ |
18 | def on_zoom_in_clicked(self, widget): |
19 | "The zoom in button has been clicked" |
20 | if self.player != None: |
21 | - self.player.zoomin() |
22 | + self.player.zoom_in() |
23 | |
24 | def on_zoom_out_clicked(self, widget): |
25 | "The zoom out button has been cliched" |
26 | if self.player != None: |
27 | - self.player.zoomout() |
28 | + self.player.zoom_out() |
29 | |
30 | def on_drawing_area_expose(self, widget, event): |
31 | """This method is called everytime the drawing area should be redrawn. |
32 | |
33 | === modified file 'a4lib/player.py' |
34 | --- a4lib/player.py 2010-07-09 13:33:55 +0000 |
35 | +++ a4lib/player.py 2010-07-14 09:29:23 +0000 |
36 | @@ -1,35 +1,28 @@ |
37 | # Copyright 2010 A4 Developers. This software is licensed under the |
38 | # GNU General Public License version 3 (see the file COPYING). |
39 | |
40 | -import cairo |
41 | import glib |
42 | import gtk |
43 | -from a4lib.presentation import Presentation, PresentationError |
44 | +from a4lib.presentation import Presentation |
45 | from a4lib.region import Region, Animation, linear_interpolation |
46 | |
47 | |
48 | class Player(object): |
49 | - """ This class handle moving the frame generically from a position to |
50 | - another """ |
51 | - |
52 | - def __init__(self): |
53 | - ## each zoom call Region.scle_of with this factor |
54 | - self.zoom_factor = 1.4 |
55 | - self.current_region = None |
56 | - self.view_status = None |
57 | - self.current_animation = None |
58 | - |
59 | - def load_presentation(self, file_name): |
60 | - """ Set the presentation file, and try to load it """ |
61 | + """Handle the frame moving generically from a position to another.""" |
62 | + |
63 | + def __init__(self, file_name): |
64 | image = Presentation(file_name) |
65 | self.presentation = image |
66 | self.current_region = Region.whole_image(image) |
67 | + self.current_animation = None |
68 | + # Each zoom call Region.scale_of with this factor. |
69 | + self.zoom_factor = 1.4 |
70 | + self.view_status = -1 |
71 | |
72 | def _set_animation_to(self, target): |
73 | - """ Calculate Animation given the id of two point of a presentation """ |
74 | - if not self.current_region: |
75 | - return |
76 | - if target == None: |
77 | + """Calculate `Animation` given the id of two point of a presentation. |
78 | + """ |
79 | + if target < 0: |
80 | target_region = Region.whole_image(self.presentation) |
81 | else: |
82 | image = self.presentation |
83 | @@ -39,68 +32,56 @@ |
84 | self.current_animation = Animation(self.current_region, target_region) |
85 | |
86 | def next(self): |
87 | - """ Send the frame to the next presentation region """ |
88 | - start = self.view_status |
89 | - if self.view_status == None: |
90 | - self.view_status = 0 |
91 | - else: |
92 | - self.view_status += 1 |
93 | - if self.view_status >= len(self.presentation.get_path()): |
94 | - self.view_status = len(self.presentation.get_path()) - 1 |
95 | + """Send the frame to the next presentation region.""" |
96 | + self.view_status = min( |
97 | + self.view_status + 1, len(self.presentation.get_path()) - 1) |
98 | self._set_animation_to(self.view_status) |
99 | |
100 | def previous(self): |
101 | - """ Send the frame to the previous presentation region """ |
102 | - start = self.view_status |
103 | - if not self.view_status: |
104 | - self.view_status = None |
105 | - else: |
106 | - self.view_status -= 1 |
107 | - self._set_animation_to(self.view_status) |
108 | - |
109 | - def goto(self, position): |
110 | - start = self.view_status |
111 | - if position >= len(self.presentation.get_path()): |
112 | - position = len(self.presentation.get_path()) - 1 |
113 | - self.view_status = position |
114 | - self._set_animation_to(position) |
115 | - |
116 | - def zoomin(self, translation=(0, 0)): |
117 | - if self.current_region != None: |
118 | - factor = 1.0 / self.zoom_factor |
119 | - region = self.current_region.scale_of(factor, |
120 | - factor).move(*translation) |
121 | - self.current_animation = Animation(self.current_region, |
122 | - region, 0.2, linear_interpolation) |
123 | - |
124 | - def zoomout(self, translation=(0, 0)): |
125 | - if self.current_region != None: |
126 | - region = self.current_region.scale_of(self.zoom_factor, |
127 | - self.zoom_factor).move(*translation) |
128 | - self.current_animation = Animation(self.current_region, |
129 | - region, 0.2, linear_interpolation) |
130 | - |
131 | - def move(self, stepx, stepy): |
132 | - if self.current_animation and self.current_animation.status != "ended": |
133 | + """Send the frame to the previous presentation region.""" |
134 | + self.view_status -= 1 |
135 | + self._set_animation_to(self.view_status) |
136 | + |
137 | + def go_to(self, position): |
138 | + self.view_status = min( |
139 | + position, len(self.presentation.get_path()) - 1) |
140 | + self._set_animation_to(self.view_status) |
141 | + |
142 | + def zoom_in(self, translation=(0, 0)): |
143 | + if self.current_region is None: |
144 | + return |
145 | + factor = 1.0 / self.zoom_factor |
146 | + region = self.current_region.scale_of( |
147 | + factor, factor).move(*translation) |
148 | + self.current_animation = Animation( |
149 | + self.current_region, region, 0.2, linear_interpolation) |
150 | + |
151 | + def zoom_out(self, translation=(0, 0)): |
152 | + if self.current_region is None: |
153 | + return |
154 | + region = self.current_region.scale_of( |
155 | + self.zoom_factor, self.zoom_factor).move(*translation) |
156 | + self.current_animation = Animation( |
157 | + self.current_region, region, 0.2, linear_interpolation) |
158 | + |
159 | + def move(self, step_x, step_y): |
160 | + if self.current_animation and self.current_animation.status != 'ended': |
161 | self.current_animation = None |
162 | - self.current_region = self.current_region.move(stepx, stepy) |
163 | + self.current_region = self.current_region.move(step_x, step_y) |
164 | |
165 | |
166 | class CairoPlayer(Player): |
167 | - """ This class extends Player with the cairo rendering functions """ |
168 | + """This class extends `Player` with the cairo rendering functions.""" |
169 | |
170 | - def __init__(self, drawingarea): |
171 | - Player.__init__(self) |
172 | - self.area = drawingarea |
173 | + def __init__(self, file_name, drawing_area): |
174 | + Player.__init__(self, file_name) |
175 | + self.area = drawing_area |
176 | + drawing_area.queue_draw() |
177 | |
178 | def _set_timer(self, interval=100): |
179 | self.area.queue_draw() |
180 | self.timer = glib.timeout_add(interval, self.tick) |
181 | |
182 | - def load_presentation(self, file_name): |
183 | - Player.load_presentation(self, file_name) |
184 | - self.area.queue_draw() |
185 | - |
186 | def next(self): |
187 | Player.next(self) |
188 | self._set_timer() |
189 | @@ -110,23 +91,22 @@ |
190 | self._set_timer() |
191 | |
192 | def tick(self): |
193 | - if self.current_animation and self.current_animation.status != "ended": |
194 | + if self.current_animation and self.current_animation.status != 'ended': |
195 | self.area.queue_draw() |
196 | return True |
197 | - else: |
198 | - self.current_animation = None |
199 | - glib.source_remove(self.timer) |
200 | - |
201 | - def goto(self, position): |
202 | - Player.goto(self, position) |
203 | - self._set_timer() |
204 | - |
205 | - def zoomin(self, translation=(0, 0)): |
206 | - Player.zoomin(self, translation) |
207 | - self._set_timer() |
208 | - |
209 | - def zoomout(self, translation=(0, 0)): |
210 | - Player.zoomout(self, translation) |
211 | + self.current_animation = None |
212 | + glib.source_remove(self.timer) |
213 | + |
214 | + def go_to(self, position): |
215 | + Player.go_to(self, position) |
216 | + self._set_timer() |
217 | + |
218 | + def zoom_in(self, translation=(0, 0)): |
219 | + Player.zoom_in(self, translation) |
220 | + self._set_timer() |
221 | + |
222 | + def zoom_out(self, translation=(0, 0)): |
223 | + Player.zoom_out(self, translation) |
224 | self._set_timer() |
225 | |
226 | def move(self, stepx, stepy): |
227 | @@ -138,21 +118,21 @@ |
228 | self.area.queue_draw() |
229 | |
230 | def render(self, context): |
231 | - """ render a frame, considering the current point of an animation """ |
232 | + """Render a frame, considering the current point of an animation.""" |
233 | if not self.presentation: |
234 | return |
235 | - if self.current_animation and self.current_animation.status != "ended": |
236 | + if self.current_animation and self.current_animation.status != 'ended': |
237 | self.current_region = self.current_animation.get_next_region() |
238 | |
239 | - self.current_region.render(context, self.presentation, |
240 | - self.area.window.get_size()) |
241 | + self.current_region.render( |
242 | + context, self.presentation, self.area.window.get_size()) |
243 | |
244 | |
245 | class GtkCairoPlayer(CairoPlayer): |
246 | - """ This class extends CairoPlayer with the handling of gtk events """ |
247 | + """This class extends `CairoPlayer` with the handling of GTK events.""" |
248 | |
249 | - def __init__(self, area): |
250 | - CairoPlayer.__init__(self, area) |
251 | + def __init__(self, file_name, area): |
252 | + CairoPlayer.__init__(self, file_name, area) |
253 | area.connect('button_press_event', self.on_area_button_press_event) |
254 | area.connect('button_release_event', self.on_area_button_release_event) |
255 | area.connect('scroll_event', self.on_area_scroll_event) |
256 | @@ -163,19 +143,24 @@ |
257 | self.mouse_position = (0, 0) |
258 | |
259 | def _mouse_step_factor(self, factor): |
260 | - #resize factor, between gtk window and the svg images |
261 | - resize_factor = self.current_region.width / ( |
262 | - self.area.window.get_size()[0]) |
263 | - #pointer position adaptation |
264 | - mouse_step = [self.mouse_position[0] * resize_factor, |
265 | - self.mouse_position[1] * resize_factor] |
266 | - mouse_step_out = [mouse_step[0] - (mouse_step[0] * factor), |
267 | - mouse_step[1] - (mouse_step[1] * factor)] |
268 | - #zoom view and move to the mouse positions. Zoom also compensate the delta if center is false |
269 | - mouse_step_out[0] += ((self.current_region.width * factor - |
270 | - self.current_region.width) / 2) |
271 | - mouse_step_out[1] += ((self.current_region.height * factor - |
272 | - self.current_region.height) / 2) |
273 | + # Resize factor, between GTK window and the SVG images. |
274 | + current_region = self.current_region |
275 | + resize_factor = current_region.width / self.area.window.get_size()[0] |
276 | + |
277 | + # Pointer position adaptation. |
278 | + mouse_step = ( |
279 | + self.mouse_position[0] * resize_factor, |
280 | + self.mouse_position[1] * resize_factor) |
281 | + mouse_step_out = [ |
282 | + mouse_step[0] - (mouse_step[0] * factor), |
283 | + mouse_step[1] - (mouse_step[1] * factor)] |
284 | + |
285 | + # Zoom view and move to the mouse positions. Zoom also compensate the |
286 | + # delta if center is False. |
287 | + mouse_step_out[0] += ( |
288 | + (current_region.width * factor - current_region.width) / 2) |
289 | + mouse_step_out[1] += ( |
290 | + (current_region.height * factor - current_region.height) / 2) |
291 | return mouse_step_out |
292 | |
293 | def on_area_button_press_event(self, widget, event): |
294 | @@ -189,30 +174,35 @@ |
295 | |
296 | def on_area_motion_notify_event(self, widget, event): |
297 | if self.mouse_pressed: |
298 | - self.move(self.mouse_position[0] - event.x, |
299 | - self.mouse_position[1] - event.y) |
300 | + self.move( |
301 | + self.mouse_position[0] - event.x, |
302 | + self.mouse_position[1] - event.y) |
303 | self.mouse_position = (event.x, event.y) |
304 | |
305 | def on_area_scroll_event(self, widget, event): |
306 | - """ callback for the scroll mouse event """ |
307 | + """Callback for the scroll mouse event.""" |
308 | if event.direction == gtk.gdk.SCROLL_UP: |
309 | factor = 1.0 / self.zoom_factor |
310 | mouse_step = self._mouse_step_factor(factor) |
311 | - self.zoomin(mouse_step) |
312 | + self.zoom_in(mouse_step) |
313 | elif event.direction == gtk.gdk.SCROLL_DOWN: |
314 | mouse_step = self._mouse_step_factor(self.zoom_factor) |
315 | - self.zoomout(mouse_step) |
316 | + self.zoom_out(mouse_step) |
317 | |
318 | def on_area_keypress(self, widget, event): |
319 | - """ callback for the key press event """ |
320 | - if "Left" == gtk.gdk.keyval_name(event.keyval): |
321 | + """Callback for the key press event.""" |
322 | + key = gtk.gdk.keyval_name(event.keyval) |
323 | + if key == 'Left': |
324 | self.previous() |
325 | - if "Right" == gtk.gdk.keyval_name(event.keyval): |
326 | + elif key == 'Right': |
327 | self.next() |
328 | - numkeys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] |
329 | - if gtk.gdk.keyval_name(event.keyval) in numkeys: |
330 | - self.goto(int(gtk.gdk.keyval_name(event.keyval))) |
331 | - if "minus" == gtk.gdk.keyval_name(event.keyval): |
332 | - self.zoomout() |
333 | - if "plus" == gtk.gdk.keyval_name(event.keyval): |
334 | - self.zoomin() |
335 | + elif key in '0123456789': |
336 | + # XXX I think most of the presentation will have more than 10 |
337 | + # regions. Probably it would be better to give the user the ability |
338 | + # to decide which keys to associate to a given presentation. |
339 | + # [bug=605310] |
340 | + self.go_to(int(key)) |
341 | + elif key == 'minus': |
342 | + self.zoom_out() |
343 | + elif key == 'plus': |
344 | + self.zoom_in() |
345 | |
346 | === modified file 'a4lib/region.py' |
347 | --- a4lib/region.py 2010-07-09 13:33:55 +0000 |
348 | +++ a4lib/region.py 2010-07-14 09:29:23 +0000 |
349 | @@ -30,22 +30,25 @@ |
350 | def from_obj_props(cls, obj_props): |
351 | """Initialize from a given object's properties.""" |
352 | o = obj_props |
353 | - return cls((o['x'] + o['translate'][0]) * o['scale'][0], |
354 | - (o['y'] + o['translate'][1]) * o['scale'][1], |
355 | - o['width'] * o['scale'][0], |
356 | - o['height'] * o['scale'][1], |
357 | - o['rotation']) |
358 | + return cls( |
359 | + (o['x'] + o['translate'][0]) * o['scale'][0], |
360 | + (o['y'] + o['translate'][1]) * o['scale'][1], |
361 | + o['width'] * o['scale'][0], |
362 | + o['height'] * o['scale'][1], |
363 | + o['rotation']) |
364 | |
365 | def scale_of(self, scalex, scaley=1.0): |
366 | - """ returns a Region that's a scaled version of self """ |
367 | + """Returns a `Region` that's a scaled version of self.""" |
368 | deltax = (self.width * scalex - self.width) / 2 |
369 | deltay = (self.height * scaley - self.height) / 2 |
370 | - return Region(self.x - deltax, self.y - deltay, self.width * scalex, |
371 | - self.height * scaley, self.rotate) |
372 | + return Region( |
373 | + self.x - deltax, self.y - deltay, self.width * scalex, |
374 | + self.height * scaley, self.rotate) |
375 | |
376 | def move(self, stepx, stepy): |
377 | - return Region(self.x + stepx, self.y + stepy, self.width, |
378 | - self.height, self.rotate) |
379 | + return Region( |
380 | + self.x + stepx, self.y + stepy, self.width, self.height, |
381 | + self.rotate) |
382 | |
383 | def render(self, context, svg_image, window_size): |
384 | """Render the region of interest to the given Cairo context.""" |
385 | @@ -64,16 +67,18 @@ |
386 | |
387 | |
388 | def linear_interpolation(fraction): |
389 | - """Constant speed interpolation""" |
390 | + """Constant speed interpolation.""" |
391 | return fraction |
392 | |
393 | |
394 | def quadratic_interpolation(fraction): |
395 | - """Create an elastic movement animation""" |
396 | + """Create an elastic movement animation.""" |
397 | now0 = fraction * 2 |
398 | - if now0 < 1: #Manage the ease-in acceleration |
399 | + if now0 < 1: |
400 | + # Manage the ease-in acceleration. |
401 | out = now0 * now0 / 2 |
402 | - else: #Manage the ease-out deceleration |
403 | + else: |
404 | + # Manage the ease-out deceleration. |
405 | now0 -= 1 |
406 | out = -(now0 * (now0 - 2) - 1) / 2 |
407 | return out |
408 | @@ -81,23 +86,25 @@ |
409 | |
410 | class Animation(object): |
411 | |
412 | - def __init__(self, startregion, endregion, deltatime=1.0, |
413 | - interpolation_type=quadratic_interpolation): |
414 | + def __init__( |
415 | + self, startregion, endregion, deltatime=1.0, |
416 | + interpolation_type=quadratic_interpolation): |
417 | self.startregion = startregion |
418 | self.endregion = endregion |
419 | self.time = deltatime |
420 | self.interpolator = interpolation_type |
421 | - self.status = "stopped" |
422 | + self.status = 'stopped' |
423 | |
424 | def _interpolate(self, now): |
425 | fraction = (now - self.starttime) / self.time |
426 | fraction = self.interpolator(fraction) |
427 | kargs = {} |
428 | - for attrname in ['x', 'y', 'width', 'height']: |
429 | + for attrname in ('x', 'y', 'width', 'height'): |
430 | start = getattr(self.startregion, attrname) |
431 | end = getattr(self.endregion, attrname) |
432 | out = start + fraction * (end - start) |
433 | kargs[attrname] = out |
434 | + |
435 | deltarot = self.endregion.rotate - self.startregion.rotate |
436 | deltarot %= 2 * math.pi |
437 | if deltarot > math.pi: |
438 | @@ -107,10 +114,10 @@ |
439 | |
440 | def get_next_region(self): |
441 | now = time.time() |
442 | - if self.status == "stopped": |
443 | + if self.status == 'stopped': |
444 | self.starttime = now |
445 | - self.status = "started" |
446 | + self.status = 'started' |
447 | if now > (self.starttime + self.time): |
448 | - self.status = "ended" |
449 | + self.status = 'ended' |
450 | return self.endregion |
451 | return self._interpolate(now) |
Completely agree on the cleaning part.
Just a little fix, these lines: area.queue_ draw() queue_draw( )
175 + self.area = drawing_area
176 + self.drawing_
have to be:
175 + self.area = drawing_area
176 + self.area.
Otherwise it wont work.
About *Player class, perhaps it'd be fine to merge CairoPlayer and GtkCairoPlayer (let's still think about it a little); although the base Player class could be useful, as it could be tested without a GUI, and cause it could be subclassed by a future Wx/Qt/web/whatever based GUI.