A4

Merge lp:~andrea.corbellini/a4/clean-player into lp:a4

Proposed by Andrea Corbellini
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
Reviewer Review Type Date Requested Status
Andrea Gasparini Approve
Review via email: mp+29842@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Andrea Gasparini (gaspa) wrote :

Completely agree on the cleaning part.

Just a little fix, these lines:
175 + self.area = drawing_area
176 + self.drawing_area.queue_draw()
have to be:
175 + self.area = drawing_area
176 + self.area.queue_draw()
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.

review: Needs Fixing
51. By Andrea Corbellini

Little fix.

Revision history for this message
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_area.queue_draw()
> have to be:
> 175 + self.area = drawing_area
> 176 + self.area.queue_draw()
> 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://www.ubuntu.com/
BeeSeek member | http://www.beeseek.org/

Revision history for this message
Andrea Gasparini (gaspa) wrote :

Yay, approving! thanks ;)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)

Subscribers

People subscribed via source and target branches