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
=== modified file 'a4lib/app.py'
--- a4lib/app.py 2010-07-08 14:49:06 +0000
+++ a4lib/app.py 2010-07-14 09:29:23 +0000
@@ -59,10 +59,9 @@
59 """Open a file inside this window."""59 """Open a file inside this window."""
60 self.set_status('Loading {0}...'.format(file_name))60 self.set_status('Loading {0}...'.format(file_name))
61 try:61 try:
62 # Try to open a file.62 # Try to open the file.
63 self.player = GtkCairoPlayer(63 self.player = GtkCairoPlayer(
64 self.builder.get_object('drawing_area'))64 file_name, self.builder.get_object('drawing_area'))
65 self.player.load_presentation(file_name)
66 except PresentationError as error:65 except PresentationError as error:
67 # The file doesn't exist, or is not an SVG file. Show an error66 # The file doesn't exist, or is not an SVG file. Show an error
68 # dialog and don't do anything else.67 # dialog and don't do anything else.
@@ -146,12 +145,12 @@
146 def on_zoom_in_clicked(self, widget):145 def on_zoom_in_clicked(self, widget):
147 "The zoom in button has been clicked"146 "The zoom in button has been clicked"
148 if self.player != None:147 if self.player != None:
149 self.player.zoomin()148 self.player.zoom_in()
150149
151 def on_zoom_out_clicked(self, widget):150 def on_zoom_out_clicked(self, widget):
152 "The zoom out button has been cliched"151 "The zoom out button has been cliched"
153 if self.player != None:152 if self.player != None:
154 self.player.zoomout()153 self.player.zoom_out()
155154
156 def on_drawing_area_expose(self, widget, event):155 def on_drawing_area_expose(self, widget, event):
157 """This method is called everytime the drawing area should be redrawn.156 """This method is called everytime the drawing area should be redrawn.
158157
=== modified file 'a4lib/player.py'
--- a4lib/player.py 2010-07-09 13:33:55 +0000
+++ a4lib/player.py 2010-07-14 09:29:23 +0000
@@ -1,35 +1,28 @@
1# Copyright 2010 A4 Developers. This software is licensed under the1# Copyright 2010 A4 Developers. This software is licensed under the
2# GNU General Public License version 3 (see the file COPYING).2# GNU General Public License version 3 (see the file COPYING).
33
4import cairo
5import glib4import glib
6import gtk5import gtk
7from a4lib.presentation import Presentation, PresentationError6from a4lib.presentation import Presentation
8from a4lib.region import Region, Animation, linear_interpolation7from a4lib.region import Region, Animation, linear_interpolation
98
109
11class Player(object):10class Player(object):
12 """ This class handle moving the frame generically from a position to11 """Handle the frame moving generically from a position to another."""
13 another """12
1413 def __init__(self, file_name):
15 def __init__(self):
16 ## each zoom call Region.scle_of with this factor
17 self.zoom_factor = 1.4
18 self.current_region = None
19 self.view_status = None
20 self.current_animation = None
21
22 def load_presentation(self, file_name):
23 """ Set the presentation file, and try to load it """
24 image = Presentation(file_name)14 image = Presentation(file_name)
25 self.presentation = image15 self.presentation = image
26 self.current_region = Region.whole_image(image)16 self.current_region = Region.whole_image(image)
17 self.current_animation = None
18 # Each zoom call Region.scale_of with this factor.
19 self.zoom_factor = 1.4
20 self.view_status = -1
2721
28 def _set_animation_to(self, target):22 def _set_animation_to(self, target):
29 """ Calculate Animation given the id of two point of a presentation """23 """Calculate `Animation` given the id of two point of a presentation.
30 if not self.current_region:24 """
31 return25 if target < 0:
32 if target == None:
33 target_region = Region.whole_image(self.presentation)26 target_region = Region.whole_image(self.presentation)
34 else:27 else:
35 image = self.presentation28 image = self.presentation
@@ -39,68 +32,56 @@
39 self.current_animation = Animation(self.current_region, target_region)32 self.current_animation = Animation(self.current_region, target_region)
4033
41 def next(self):34 def next(self):
42 """ Send the frame to the next presentation region """35 """Send the frame to the next presentation region."""
43 start = self.view_status36 self.view_status = min(
44 if self.view_status == None:37 self.view_status + 1, len(self.presentation.get_path()) - 1)
45 self.view_status = 0
46 else:
47 self.view_status += 1
48 if self.view_status >= len(self.presentation.get_path()):
49 self.view_status = len(self.presentation.get_path()) - 1
50 self._set_animation_to(self.view_status)38 self._set_animation_to(self.view_status)
5139
52 def previous(self):40 def previous(self):
53 """ Send the frame to the previous presentation region """41 """Send the frame to the previous presentation region."""
54 start = self.view_status42 self.view_status -= 1
55 if not self.view_status:43 self._set_animation_to(self.view_status)
56 self.view_status = None44
57 else:45 def go_to(self, position):
58 self.view_status -= 146 self.view_status = min(
59 self._set_animation_to(self.view_status)47 position, len(self.presentation.get_path()) - 1)
6048 self._set_animation_to(self.view_status)
61 def goto(self, position):49
62 start = self.view_status50 def zoom_in(self, translation=(0, 0)):
63 if position >= len(self.presentation.get_path()):51 if self.current_region is None:
64 position = len(self.presentation.get_path()) - 152 return
65 self.view_status = position53 factor = 1.0 / self.zoom_factor
66 self._set_animation_to(position)54 region = self.current_region.scale_of(
6755 factor, factor).move(*translation)
68 def zoomin(self, translation=(0, 0)):56 self.current_animation = Animation(
69 if self.current_region != None:57 self.current_region, region, 0.2, linear_interpolation)
70 factor = 1.0 / self.zoom_factor58
71 region = self.current_region.scale_of(factor,59 def zoom_out(self, translation=(0, 0)):
72 factor).move(*translation)60 if self.current_region is None:
73 self.current_animation = Animation(self.current_region,61 return
74 region, 0.2, linear_interpolation)62 region = self.current_region.scale_of(
7563 self.zoom_factor, self.zoom_factor).move(*translation)
76 def zoomout(self, translation=(0, 0)):64 self.current_animation = Animation(
77 if self.current_region != None:65 self.current_region, region, 0.2, linear_interpolation)
78 region = self.current_region.scale_of(self.zoom_factor,66
79 self.zoom_factor).move(*translation)67 def move(self, step_x, step_y):
80 self.current_animation = Animation(self.current_region,68 if self.current_animation and self.current_animation.status != 'ended':
81 region, 0.2, linear_interpolation)
82
83 def move(self, stepx, stepy):
84 if self.current_animation and self.current_animation.status != "ended":
85 self.current_animation = None69 self.current_animation = None
86 self.current_region = self.current_region.move(stepx, stepy)70 self.current_region = self.current_region.move(step_x, step_y)
8771
8872
89class CairoPlayer(Player):73class CairoPlayer(Player):
90 """ This class extends Player with the cairo rendering functions """74 """This class extends `Player` with the cairo rendering functions."""
9175
92 def __init__(self, drawingarea):76 def __init__(self, file_name, drawing_area):
93 Player.__init__(self)77 Player.__init__(self, file_name)
94 self.area = drawingarea78 self.area = drawing_area
79 drawing_area.queue_draw()
9580
96 def _set_timer(self, interval=100):81 def _set_timer(self, interval=100):
97 self.area.queue_draw()82 self.area.queue_draw()
98 self.timer = glib.timeout_add(interval, self.tick)83 self.timer = glib.timeout_add(interval, self.tick)
9984
100 def load_presentation(self, file_name):
101 Player.load_presentation(self, file_name)
102 self.area.queue_draw()
103
104 def next(self):85 def next(self):
105 Player.next(self)86 Player.next(self)
106 self._set_timer()87 self._set_timer()
@@ -110,23 +91,22 @@
110 self._set_timer()91 self._set_timer()
11192
112 def tick(self):93 def tick(self):
113 if self.current_animation and self.current_animation.status != "ended":94 if self.current_animation and self.current_animation.status != 'ended':
114 self.area.queue_draw()95 self.area.queue_draw()
115 return True96 return True
116 else:97 self.current_animation = None
117 self.current_animation = None98 glib.source_remove(self.timer)
118 glib.source_remove(self.timer)99
119100 def go_to(self, position):
120 def goto(self, position):101 Player.go_to(self, position)
121 Player.goto(self, position)102 self._set_timer()
122 self._set_timer()103
123104 def zoom_in(self, translation=(0, 0)):
124 def zoomin(self, translation=(0, 0)):105 Player.zoom_in(self, translation)
125 Player.zoomin(self, translation)106 self._set_timer()
126 self._set_timer()107
127108 def zoom_out(self, translation=(0, 0)):
128 def zoomout(self, translation=(0, 0)):109 Player.zoom_out(self, translation)
129 Player.zoomout(self, translation)
130 self._set_timer()110 self._set_timer()
131111
132 def move(self, stepx, stepy):112 def move(self, stepx, stepy):
@@ -138,21 +118,21 @@
138 self.area.queue_draw()118 self.area.queue_draw()
139119
140 def render(self, context):120 def render(self, context):
141 """ render a frame, considering the current point of an animation """121 """Render a frame, considering the current point of an animation."""
142 if not self.presentation:122 if not self.presentation:
143 return123 return
144 if self.current_animation and self.current_animation.status != "ended":124 if self.current_animation and self.current_animation.status != 'ended':
145 self.current_region = self.current_animation.get_next_region()125 self.current_region = self.current_animation.get_next_region()
146126
147 self.current_region.render(context, self.presentation,127 self.current_region.render(
148 self.area.window.get_size())128 context, self.presentation, self.area.window.get_size())
149129
150130
151class GtkCairoPlayer(CairoPlayer):131class GtkCairoPlayer(CairoPlayer):
152 """ This class extends CairoPlayer with the handling of gtk events """132 """This class extends `CairoPlayer` with the handling of GTK events."""
153133
154 def __init__(self, area):134 def __init__(self, file_name, area):
155 CairoPlayer.__init__(self, area)135 CairoPlayer.__init__(self, file_name, area)
156 area.connect('button_press_event', self.on_area_button_press_event)136 area.connect('button_press_event', self.on_area_button_press_event)
157 area.connect('button_release_event', self.on_area_button_release_event)137 area.connect('button_release_event', self.on_area_button_release_event)
158 area.connect('scroll_event', self.on_area_scroll_event)138 area.connect('scroll_event', self.on_area_scroll_event)
@@ -163,19 +143,24 @@
163 self.mouse_position = (0, 0)143 self.mouse_position = (0, 0)
164144
165 def _mouse_step_factor(self, factor):145 def _mouse_step_factor(self, factor):
166 #resize factor, between gtk window and the svg images146 # Resize factor, between GTK window and the SVG images.
167 resize_factor = self.current_region.width / (147 current_region = self.current_region
168 self.area.window.get_size()[0])148 resize_factor = current_region.width / self.area.window.get_size()[0]
169 #pointer position adaptation149
170 mouse_step = [self.mouse_position[0] * resize_factor,150 # Pointer position adaptation.
171 self.mouse_position[1] * resize_factor]151 mouse_step = (
172 mouse_step_out = [mouse_step[0] - (mouse_step[0] * factor),152 self.mouse_position[0] * resize_factor,
173 mouse_step[1] - (mouse_step[1] * factor)]153 self.mouse_position[1] * resize_factor)
174 #zoom view and move to the mouse positions. Zoom also compensate the delta if center is false154 mouse_step_out = [
175 mouse_step_out[0] += ((self.current_region.width * factor -155 mouse_step[0] - (mouse_step[0] * factor),
176 self.current_region.width) / 2)156 mouse_step[1] - (mouse_step[1] * factor)]
177 mouse_step_out[1] += ((self.current_region.height * factor -157
178 self.current_region.height) / 2)158 # Zoom view and move to the mouse positions. Zoom also compensate the
159 # delta if center is False.
160 mouse_step_out[0] += (
161 (current_region.width * factor - current_region.width) / 2)
162 mouse_step_out[1] += (
163 (current_region.height * factor - current_region.height) / 2)
179 return mouse_step_out164 return mouse_step_out
180165
181 def on_area_button_press_event(self, widget, event):166 def on_area_button_press_event(self, widget, event):
@@ -189,30 +174,35 @@
189174
190 def on_area_motion_notify_event(self, widget, event):175 def on_area_motion_notify_event(self, widget, event):
191 if self.mouse_pressed:176 if self.mouse_pressed:
192 self.move(self.mouse_position[0] - event.x,177 self.move(
193 self.mouse_position[1] - event.y)178 self.mouse_position[0] - event.x,
179 self.mouse_position[1] - event.y)
194 self.mouse_position = (event.x, event.y)180 self.mouse_position = (event.x, event.y)
195181
196 def on_area_scroll_event(self, widget, event):182 def on_area_scroll_event(self, widget, event):
197 """ callback for the scroll mouse event """183 """Callback for the scroll mouse event."""
198 if event.direction == gtk.gdk.SCROLL_UP:184 if event.direction == gtk.gdk.SCROLL_UP:
199 factor = 1.0 / self.zoom_factor185 factor = 1.0 / self.zoom_factor
200 mouse_step = self._mouse_step_factor(factor)186 mouse_step = self._mouse_step_factor(factor)
201 self.zoomin(mouse_step)187 self.zoom_in(mouse_step)
202 elif event.direction == gtk.gdk.SCROLL_DOWN:188 elif event.direction == gtk.gdk.SCROLL_DOWN:
203 mouse_step = self._mouse_step_factor(self.zoom_factor)189 mouse_step = self._mouse_step_factor(self.zoom_factor)
204 self.zoomout(mouse_step)190 self.zoom_out(mouse_step)
205191
206 def on_area_keypress(self, widget, event):192 def on_area_keypress(self, widget, event):
207 """ callback for the key press event """193 """Callback for the key press event."""
208 if "Left" == gtk.gdk.keyval_name(event.keyval):194 key = gtk.gdk.keyval_name(event.keyval)
195 if key == 'Left':
209 self.previous()196 self.previous()
210 if "Right" == gtk.gdk.keyval_name(event.keyval):197 elif key == 'Right':
211 self.next()198 self.next()
212 numkeys = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']199 elif key in '0123456789':
213 if gtk.gdk.keyval_name(event.keyval) in numkeys:200 # XXX I think most of the presentation will have more than 10
214 self.goto(int(gtk.gdk.keyval_name(event.keyval)))201 # regions. Probably it would be better to give the user the ability
215 if "minus" == gtk.gdk.keyval_name(event.keyval):202 # to decide which keys to associate to a given presentation.
216 self.zoomout()203 # [bug=605310]
217 if "plus" == gtk.gdk.keyval_name(event.keyval):204 self.go_to(int(key))
218 self.zoomin()205 elif key == 'minus':
206 self.zoom_out()
207 elif key == 'plus':
208 self.zoom_in()
219209
=== modified file 'a4lib/region.py'
--- a4lib/region.py 2010-07-09 13:33:55 +0000
+++ a4lib/region.py 2010-07-14 09:29:23 +0000
@@ -30,22 +30,25 @@
30 def from_obj_props(cls, obj_props):30 def from_obj_props(cls, obj_props):
31 """Initialize from a given object's properties."""31 """Initialize from a given object's properties."""
32 o = obj_props32 o = obj_props
33 return cls((o['x'] + o['translate'][0]) * o['scale'][0],33 return cls(
34 (o['y'] + o['translate'][1]) * o['scale'][1],34 (o['x'] + o['translate'][0]) * o['scale'][0],
35 o['width'] * o['scale'][0],35 (o['y'] + o['translate'][1]) * o['scale'][1],
36 o['height'] * o['scale'][1],36 o['width'] * o['scale'][0],
37 o['rotation'])37 o['height'] * o['scale'][1],
38 o['rotation'])
3839
39 def scale_of(self, scalex, scaley=1.0):40 def scale_of(self, scalex, scaley=1.0):
40 """ returns a Region that's a scaled version of self """41 """Returns a `Region` that's a scaled version of self."""
41 deltax = (self.width * scalex - self.width) / 242 deltax = (self.width * scalex - self.width) / 2
42 deltay = (self.height * scaley - self.height) / 243 deltay = (self.height * scaley - self.height) / 2
43 return Region(self.x - deltax, self.y - deltay, self.width * scalex,44 return Region(
44 self.height * scaley, self.rotate)45 self.x - deltax, self.y - deltay, self.width * scalex,
46 self.height * scaley, self.rotate)
4547
46 def move(self, stepx, stepy):48 def move(self, stepx, stepy):
47 return Region(self.x + stepx, self.y + stepy, self.width,49 return Region(
48 self.height, self.rotate)50 self.x + stepx, self.y + stepy, self.width, self.height,
51 self.rotate)
4952
50 def render(self, context, svg_image, window_size):53 def render(self, context, svg_image, window_size):
51 """Render the region of interest to the given Cairo context."""54 """Render the region of interest to the given Cairo context."""
@@ -64,16 +67,18 @@
6467
6568
66def linear_interpolation(fraction):69def linear_interpolation(fraction):
67 """Constant speed interpolation"""70 """Constant speed interpolation."""
68 return fraction71 return fraction
6972
7073
71def quadratic_interpolation(fraction):74def quadratic_interpolation(fraction):
72 """Create an elastic movement animation"""75 """Create an elastic movement animation."""
73 now0 = fraction * 276 now0 = fraction * 2
74 if now0 < 1: #Manage the ease-in acceleration77 if now0 < 1:
78 # Manage the ease-in acceleration.
75 out = now0 * now0 / 279 out = now0 * now0 / 2
76 else: #Manage the ease-out deceleration80 else:
81 # Manage the ease-out deceleration.
77 now0 -= 182 now0 -= 1
78 out = -(now0 * (now0 - 2) - 1) / 283 out = -(now0 * (now0 - 2) - 1) / 2
79 return out84 return out
@@ -81,23 +86,25 @@
8186
82class Animation(object):87class Animation(object):
8388
84 def __init__(self, startregion, endregion, deltatime=1.0,89 def __init__(
85 interpolation_type=quadratic_interpolation):90 self, startregion, endregion, deltatime=1.0,
91 interpolation_type=quadratic_interpolation):
86 self.startregion = startregion92 self.startregion = startregion
87 self.endregion = endregion93 self.endregion = endregion
88 self.time = deltatime94 self.time = deltatime
89 self.interpolator = interpolation_type95 self.interpolator = interpolation_type
90 self.status = "stopped"96 self.status = 'stopped'
9197
92 def _interpolate(self, now):98 def _interpolate(self, now):
93 fraction = (now - self.starttime) / self.time99 fraction = (now - self.starttime) / self.time
94 fraction = self.interpolator(fraction)100 fraction = self.interpolator(fraction)
95 kargs = {}101 kargs = {}
96 for attrname in ['x', 'y', 'width', 'height']:102 for attrname in ('x', 'y', 'width', 'height'):
97 start = getattr(self.startregion, attrname)103 start = getattr(self.startregion, attrname)
98 end = getattr(self.endregion, attrname)104 end = getattr(self.endregion, attrname)
99 out = start + fraction * (end - start)105 out = start + fraction * (end - start)
100 kargs[attrname] = out106 kargs[attrname] = out
107
101 deltarot = self.endregion.rotate - self.startregion.rotate108 deltarot = self.endregion.rotate - self.startregion.rotate
102 deltarot %= 2 * math.pi109 deltarot %= 2 * math.pi
103 if deltarot > math.pi:110 if deltarot > math.pi:
@@ -107,10 +114,10 @@
107114
108 def get_next_region(self):115 def get_next_region(self):
109 now = time.time()116 now = time.time()
110 if self.status == "stopped":117 if self.status == 'stopped':
111 self.starttime = now118 self.starttime = now
112 self.status = "started"119 self.status = 'started'
113 if now > (self.starttime + self.time):120 if now > (self.starttime + self.time):
114 self.status = "ended"121 self.status = 'ended'
115 return self.endregion122 return self.endregion
116 return self._interpolate(now)123 return self._interpolate(now)

Subscribers

People subscribed via source and target branches