Merge lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV into lp:xpresser
- gtk3-port-with-SimpleCV
- Merge into trunk
Proposed by
Chris Wayne
Status: | Merged |
---|---|
Approved by: | Chris Wayne |
Approved revision: | 16 |
Merged at revision: | 11 |
Proposed branch: | lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV |
Merge into: | lp:xpresser |
Diff against target: |
1797 lines (+1126/-160) 32 files modified
debian/changelog (+71/-0) debian/compat (+1/-0) debian/control (+12/-0) debian/files (+1/-0) debian/python-xpresser.debhelper.log (+46/-0) debian/python-xpresser.postinst.debhelper (+7/-0) debian/python-xpresser.prerm.debhelper (+12/-0) debian/python-xpresser.substvars (+4/-0) debian/python-xpresser/DEBIAN/control (+10/-0) debian/python-xpresser/DEBIAN/md5sums (+10/-0) debian/python-xpresser/DEBIAN/postinst (+9/-0) debian/python-xpresser/DEBIAN/prerm (+14/-0) debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info (+10/-0) debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py (+23/-0) debian/python-xpresser/usr/share/pyshared/xpresser/errors.py (+23/-0) debian/python-xpresser/usr/share/pyshared/xpresser/image.py (+76/-0) debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py (+111/-0) debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py (+54/-0) debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py (+142/-0) debian/python-xpresser/usr/share/pyshared/xpresser/xp.py (+128/-0) debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py (+132/-0) debian/rules (+9/-0) example.py (+3/-4) test (+125/-11) xpresser/__init__.py (+0/-2) xpresser/image.py (+1/-1) xpresser/opencvfinder.py (+19/-51) xpresser/tests/test_opencvfinder.py (+12/-13) xpresser/tests/test_xp.py (+12/-7) xpresser/tests/test_xutils.py (+29/-40) xpresser/xp.py (+5/-5) xpresser/xutils.py (+15/-26) |
To merge this branch: | bzr merge lp:~sylvain-pineau/xpresser/gtk3-port-with-SimpleCV |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Xpresser | Pending | ||
Review via email: mp+113582@code.launchpad.net |
Commit message
Description of the change
GTK3 port with SimpleCV instead of OpenCV. This makes xpresser work much better in Precise
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added directory 'debian' | |||
2 | === added file 'debian/changelog' | |||
3 | --- debian/changelog 1970-01-01 00:00:00 +0000 | |||
4 | +++ debian/changelog 2012-07-05 13:34:35 +0000 | |||
5 | @@ -0,0 +1,71 @@ | |||
6 | 1 | xpresser (1.0-1ubuntu11) oneiric; urgency=low | ||
7 | 2 | |||
8 | 3 | * Added special keypresses to xutils.py | ||
9 | 4 | |||
10 | 5 | -- Chris Gagnon <chris.gagnon@canonical.com> Wed, 07 Mar 2012 20:27:57 -0500 | ||
11 | 6 | |||
12 | 7 | xpresser (1.0-1ubuntu10) oneiric; urgency=low | ||
13 | 8 | |||
14 | 9 | * Added double click functionality | ||
15 | 10 | |||
16 | 11 | -- Chris Wayne <chris.wayne@canonical.com> Fri, 09 Dec 2011 12:57:52 -0500 | ||
17 | 12 | |||
18 | 13 | xpresser (1.0-1ubuntu9) oneiric; urgency=low | ||
19 | 14 | |||
20 | 15 | * pyatspi2 fixes | ||
21 | 16 | |||
22 | 17 | -- Chris Wayne <chris.wayne@canonical.com> Wed, 30 Nov 2011 13:37:45 -0500 | ||
23 | 18 | |||
24 | 19 | xpresser (1.0-1ubuntu8) oneiric; urgency=low | ||
25 | 20 | |||
26 | 21 | * fixed broken deps | ||
27 | 22 | |||
28 | 23 | -- Chris Wayne <chris.wayne@canonical.com> Wed, 30 Nov 2011 12:03:40 -0500 | ||
29 | 24 | |||
30 | 25 | xpresser (1.0-1ubuntu7) oneiric; urgency=low | ||
31 | 26 | |||
32 | 27 | * Having xpresser use pyatspi2 instead of pyatspi to work in oneiric | ||
33 | 28 | |||
34 | 29 | -- Chris Wayne <chris.wayne@canonical.com> Tue, 29 Nov 2011 10:05:10 -0500 | ||
35 | 30 | |||
36 | 31 | xpresser (1.0-1ubuntu6) natty; urgency=low | ||
37 | 32 | |||
38 | 33 | * building for natty for pes qa ppa | ||
39 | 34 | |||
40 | 35 | -- Chris Wayne <chris.wayne@canonical.com> Fri, 11 Nov 2011 16:29:17 -0500 | ||
41 | 36 | |||
42 | 37 | xpresser (1.0-1ubuntu5) natty; urgency=low | ||
43 | 38 | |||
44 | 39 | * Version bump | ||
45 | 40 | |||
46 | 41 | -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 21:21:07 -0400 | ||
47 | 42 | |||
48 | 43 | xpresser (1.0-1ubuntu4) natty; urgency=low | ||
49 | 44 | |||
50 | 45 | * building for natty | ||
51 | 46 | |||
52 | 47 | -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 21:10:58 -0400 | ||
53 | 48 | |||
54 | 49 | xpresser (1.0-1ubuntu3) oneiric; urgency=low | ||
55 | 50 | |||
56 | 51 | * building for oem services qa ppa | ||
57 | 52 | |||
58 | 53 | -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:35:14 -0400 | ||
59 | 54 | |||
60 | 55 | xpresser (1.0-1ubuntu2) oneiric; urgency=low | ||
61 | 56 | |||
62 | 57 | * Try 2 | ||
63 | 58 | |||
64 | 59 | -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:19:09 -0400 | ||
65 | 60 | |||
66 | 61 | xpresser (1.0-1ubuntu1) oneiric; urgency=low | ||
67 | 62 | |||
68 | 63 | * Trying to debianize | ||
69 | 64 | |||
70 | 65 | -- Chris Wayne <chris.wayne@canonical.com> Tue, 25 Oct 2011 15:14:53 -0400 | ||
71 | 66 | |||
72 | 67 | xpresser (1.0-1) unstable; urgency=low | ||
73 | 68 | |||
74 | 69 | * source package automatically created by stdeb 0.6.0+git | ||
75 | 70 | |||
76 | 71 | -- Gustavo Niemeyer <gustavo.niemeyer@canonical.com> Tue, 25 Oct 2011 15:12:21 -0400 | ||
77 | 0 | 72 | ||
78 | === added file 'debian/compat' | |||
79 | --- debian/compat 1970-01-01 00:00:00 +0000 | |||
80 | +++ debian/compat 2012-07-05 13:34:35 +0000 | |||
81 | @@ -0,0 +1,1 @@ | |||
82 | 1 | 7 | ||
83 | 0 | 2 | ||
84 | === added file 'debian/control' | |||
85 | --- debian/control 1970-01-01 00:00:00 +0000 | |||
86 | +++ debian/control 2012-07-05 13:34:35 +0000 | |||
87 | @@ -0,0 +1,12 @@ | |||
88 | 1 | Source: xpresser | ||
89 | 2 | Maintainer: Gustavo Niemeyer <gustavo.niemeyer@canonical.com> | ||
90 | 3 | Section: python | ||
91 | 4 | Priority: optional | ||
92 | 5 | Build-Depends: python-all (>= 2.6.6-3), debhelper (>= 7) | ||
93 | 6 | Standards-Version: 3.9.1 | ||
94 | 7 | |||
95 | 8 | Package: python-xpresser | ||
96 | 9 | Architecture: all | ||
97 | 10 | Depends: ${misc:Depends}, ${python:Depends}, python-opencv, python-pyatspi2, python-numpy | ||
98 | 11 | Description: Python library to script Graphic User Interfaces. | ||
99 | 12 | |||
100 | 0 | 13 | ||
101 | === added file 'debian/files' | |||
102 | --- debian/files 1970-01-01 00:00:00 +0000 | |||
103 | +++ debian/files 2012-07-05 13:34:35 +0000 | |||
104 | @@ -0,0 +1,1 @@ | |||
105 | 1 | python-xpresser_1.0-1ubuntu13_all.deb python optional | ||
106 | 0 | 2 | ||
107 | === added directory 'debian/python-xpresser' | |||
108 | === added file 'debian/python-xpresser.debhelper.log' | |||
109 | --- debian/python-xpresser.debhelper.log 1970-01-01 00:00:00 +0000 | |||
110 | +++ debian/python-xpresser.debhelper.log 2012-07-05 13:34:35 +0000 | |||
111 | @@ -0,0 +1,46 @@ | |||
112 | 1 | dh_auto_configure | ||
113 | 2 | dh_auto_build | ||
114 | 3 | dh_auto_test | ||
115 | 4 | dh_prep | ||
116 | 5 | dh_installdirs | ||
117 | 6 | dh_auto_install | ||
118 | 7 | dh_install | ||
119 | 8 | dh_installdocs | ||
120 | 9 | dh_installchangelogs | ||
121 | 10 | dh_installexamples | ||
122 | 11 | dh_installman | ||
123 | 12 | dh_installcatalogs | ||
124 | 13 | dh_installcron | ||
125 | 14 | dh_installdebconf | ||
126 | 15 | dh_installemacsen | ||
127 | 16 | dh_installifupdown | ||
128 | 17 | dh_installinfo | ||
129 | 18 | dh_installinit | ||
130 | 19 | dh_installmenu | ||
131 | 20 | dh_installmime | ||
132 | 21 | dh_installmodules | ||
133 | 22 | dh_installlogcheck | ||
134 | 23 | dh_installlogrotate | ||
135 | 24 | dh_installpam | ||
136 | 25 | dh_installppp | ||
137 | 26 | dh_installudev | ||
138 | 27 | dh_installwm | ||
139 | 28 | dh_installxfonts | ||
140 | 29 | dh_installgsettings | ||
141 | 30 | dh_bugfiles | ||
142 | 31 | dh_ucf | ||
143 | 32 | dh_lintian | ||
144 | 33 | dh_gconf | ||
145 | 34 | dh_icons | ||
146 | 35 | dh_perl | ||
147 | 36 | dh_usrlocal | ||
148 | 37 | dh_link | ||
149 | 38 | dh_compress | ||
150 | 39 | dh_fixperms | ||
151 | 40 | dh_strip | ||
152 | 41 | dh_makeshlibs | ||
153 | 42 | dh_shlibdeps | ||
154 | 43 | dh_installdeb | ||
155 | 44 | dh_gencontrol | ||
156 | 45 | dh_md5sums | ||
157 | 46 | dh_builddeb | ||
158 | 0 | 47 | ||
159 | === added file 'debian/python-xpresser.postinst.debhelper' | |||
160 | --- debian/python-xpresser.postinst.debhelper 1970-01-01 00:00:00 +0000 | |||
161 | +++ debian/python-xpresser.postinst.debhelper 2012-07-05 13:34:35 +0000 | |||
162 | @@ -0,0 +1,7 @@ | |||
163 | 1 | |||
164 | 2 | # Automatically added by dh_python2: | ||
165 | 3 | if which pycompile >/dev/null 2>&1; then | ||
166 | 4 | pycompile -p python-xpresser | ||
167 | 5 | fi | ||
168 | 6 | |||
169 | 7 | # End automatically added section | ||
170 | 0 | 8 | ||
171 | === added file 'debian/python-xpresser.prerm.debhelper' | |||
172 | --- debian/python-xpresser.prerm.debhelper 1970-01-01 00:00:00 +0000 | |||
173 | +++ debian/python-xpresser.prerm.debhelper 2012-07-05 13:34:35 +0000 | |||
174 | @@ -0,0 +1,12 @@ | |||
175 | 1 | |||
176 | 2 | # Automatically added by dh_python2: | ||
177 | 3 | if which pyclean >/dev/null 2>&1; then | ||
178 | 4 | pyclean -p python-xpresser | ||
179 | 5 | else | ||
180 | 6 | dpkg -L python-xpresser | grep \.py$ | while read file | ||
181 | 7 | do | ||
182 | 8 | rm -f "${file}"[co] >/dev/null | ||
183 | 9 | done | ||
184 | 10 | fi | ||
185 | 11 | |||
186 | 12 | # End automatically added section | ||
187 | 0 | 13 | ||
188 | === added file 'debian/python-xpresser.substvars' | |||
189 | --- debian/python-xpresser.substvars 1970-01-01 00:00:00 +0000 | |||
190 | +++ debian/python-xpresser.substvars 2012-07-05 13:34:35 +0000 | |||
191 | @@ -0,0 +1,4 @@ | |||
192 | 1 | python:Versions=2.6, 2.7 | ||
193 | 2 | python:Provides=python2.6-xpresser, python2.7-xpresser | ||
194 | 3 | python:Depends=python2.7 | python2.6, python (>= 2.6), python (<< 2.8), python (>= 2.7.1-0ubuntu2) | ||
195 | 4 | misc:Depends= | ||
196 | 0 | 5 | ||
197 | === added directory 'debian/python-xpresser/DEBIAN' | |||
198 | === added file 'debian/python-xpresser/DEBIAN/control' | |||
199 | --- debian/python-xpresser/DEBIAN/control 1970-01-01 00:00:00 +0000 | |||
200 | +++ debian/python-xpresser/DEBIAN/control 2012-07-05 13:34:35 +0000 | |||
201 | @@ -0,0 +1,10 @@ | |||
202 | 1 | Package: python-xpresser | ||
203 | 2 | Source: xpresser | ||
204 | 3 | Version: 1.0-1ubuntu13 | ||
205 | 4 | Architecture: all | ||
206 | 5 | Maintainer: Gustavo Niemeyer <gustavo.niemeyer@canonical.com> | ||
207 | 6 | Installed-Size: 124 | ||
208 | 7 | Depends: python2.7 | python2.6, python (>= 2.7.1-0ubuntu2), python (<< 2.8), python-opencv, python-pyatspi2, python-numpy | ||
209 | 8 | Section: python | ||
210 | 9 | Priority: optional | ||
211 | 10 | Description: Python library to script Graphic User Interfaces. | ||
212 | 0 | 11 | ||
213 | === added file 'debian/python-xpresser/DEBIAN/md5sums' | |||
214 | --- debian/python-xpresser/DEBIAN/md5sums 1970-01-01 00:00:00 +0000 | |||
215 | +++ debian/python-xpresser/DEBIAN/md5sums 2012-07-05 13:34:35 +0000 | |||
216 | @@ -0,0 +1,10 @@ | |||
217 | 1 | bd68a0923da369be55405c3f89b177c6 usr/share/doc/python-xpresser/changelog.Debian.gz | ||
218 | 2 | aeb2a0ddf75f75b58a90aefd7055836e usr/share/pyshared/Xpresser-1.0.egg-info | ||
219 | 3 | e6ca66d62183fa913f0164fbe406d3dd usr/share/pyshared/xpresser/__init__.py | ||
220 | 4 | 24d5a84a02bdcdc08a4321e4a10d0ded usr/share/pyshared/xpresser/errors.py | ||
221 | 5 | 4feb915dd3b78b90df3c1506287d98fc usr/share/pyshared/xpresser/image.py | ||
222 | 6 | 8508962040806a29a7f961993d7021a9 usr/share/pyshared/xpresser/imagedir.py | ||
223 | 7 | a709a84956c69b7b869b8223b80f58f2 usr/share/pyshared/xpresser/imagematch.py | ||
224 | 8 | 3ec633b271f373011520336ac9a1737a usr/share/pyshared/xpresser/opencvfinder.py | ||
225 | 9 | 8820163100e7cf105521aa0732b50c54 usr/share/pyshared/xpresser/xp.py | ||
226 | 10 | 3b5279525e533b84d50fe9afeef1b502 usr/share/pyshared/xpresser/xutils.py | ||
227 | 0 | 11 | ||
228 | === added file 'debian/python-xpresser/DEBIAN/postinst' | |||
229 | --- debian/python-xpresser/DEBIAN/postinst 1970-01-01 00:00:00 +0000 | |||
230 | +++ debian/python-xpresser/DEBIAN/postinst 2012-07-05 13:34:35 +0000 | |||
231 | @@ -0,0 +1,9 @@ | |||
232 | 1 | #!/bin/sh | ||
233 | 2 | set -e | ||
234 | 3 | |||
235 | 4 | # Automatically added by dh_python2: | ||
236 | 5 | if which pycompile >/dev/null 2>&1; then | ||
237 | 6 | pycompile -p python-xpresser | ||
238 | 7 | fi | ||
239 | 8 | |||
240 | 9 | # End automatically added section | ||
241 | 0 | 10 | ||
242 | === added file 'debian/python-xpresser/DEBIAN/prerm' | |||
243 | --- debian/python-xpresser/DEBIAN/prerm 1970-01-01 00:00:00 +0000 | |||
244 | +++ debian/python-xpresser/DEBIAN/prerm 2012-07-05 13:34:35 +0000 | |||
245 | @@ -0,0 +1,14 @@ | |||
246 | 1 | #!/bin/sh | ||
247 | 2 | set -e | ||
248 | 3 | |||
249 | 4 | # Automatically added by dh_python2: | ||
250 | 5 | if which pyclean >/dev/null 2>&1; then | ||
251 | 6 | pyclean -p python-xpresser | ||
252 | 7 | else | ||
253 | 8 | dpkg -L python-xpresser | grep \.py$ | while read file | ||
254 | 9 | do | ||
255 | 10 | rm -f "${file}"[co] >/dev/null | ||
256 | 11 | done | ||
257 | 12 | fi | ||
258 | 13 | |||
259 | 14 | # End automatically added section | ||
260 | 0 | 15 | ||
261 | === added directory 'debian/python-xpresser/usr' | |||
262 | === added directory 'debian/python-xpresser/usr/lib' | |||
263 | === added directory 'debian/python-xpresser/usr/lib/python2.6' | |||
264 | === added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages' | |||
265 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/Xpresser-1.0.egg-info' | |||
266 | === target is u'../../../share/pyshared/Xpresser-1.0.egg-info' | |||
267 | === added directory 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser' | |||
268 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/__init__.py' | |||
269 | === target is u'../../../../share/pyshared/xpresser/__init__.py' | |||
270 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/errors.py' | |||
271 | === target is u'../../../../share/pyshared/xpresser/errors.py' | |||
272 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/image.py' | |||
273 | === target is u'../../../../share/pyshared/xpresser/image.py' | |||
274 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagedir.py' | |||
275 | === target is u'../../../../share/pyshared/xpresser/imagedir.py' | |||
276 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/imagematch.py' | |||
277 | === target is u'../../../../share/pyshared/xpresser/imagematch.py' | |||
278 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/opencvfinder.py' | |||
279 | === target is u'../../../../share/pyshared/xpresser/opencvfinder.py' | |||
280 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xp.py' | |||
281 | === target is u'../../../../share/pyshared/xpresser/xp.py' | |||
282 | === added symlink 'debian/python-xpresser/usr/lib/python2.6/dist-packages/xpresser/xutils.py' | |||
283 | === target is u'../../../../share/pyshared/xpresser/xutils.py' | |||
284 | === added directory 'debian/python-xpresser/usr/lib/python2.7' | |||
285 | === added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages' | |||
286 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/Xpresser-1.0.egg-info' | |||
287 | === target is u'../../../share/pyshared/Xpresser-1.0.egg-info' | |||
288 | === added directory 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser' | |||
289 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/__init__.py' | |||
290 | === target is u'../../../../share/pyshared/xpresser/__init__.py' | |||
291 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/errors.py' | |||
292 | === target is u'../../../../share/pyshared/xpresser/errors.py' | |||
293 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/image.py' | |||
294 | === target is u'../../../../share/pyshared/xpresser/image.py' | |||
295 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagedir.py' | |||
296 | === target is u'../../../../share/pyshared/xpresser/imagedir.py' | |||
297 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/imagematch.py' | |||
298 | === target is u'../../../../share/pyshared/xpresser/imagematch.py' | |||
299 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/opencvfinder.py' | |||
300 | === target is u'../../../../share/pyshared/xpresser/opencvfinder.py' | |||
301 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xp.py' | |||
302 | === target is u'../../../../share/pyshared/xpresser/xp.py' | |||
303 | === added symlink 'debian/python-xpresser/usr/lib/python2.7/dist-packages/xpresser/xutils.py' | |||
304 | === target is u'../../../../share/pyshared/xpresser/xutils.py' | |||
305 | === added directory 'debian/python-xpresser/usr/share' | |||
306 | === added directory 'debian/python-xpresser/usr/share/doc' | |||
307 | === added directory 'debian/python-xpresser/usr/share/doc/python-xpresser' | |||
308 | === added file 'debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz' | |||
309 | 1 | Binary files debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz 1970-01-01 00:00:00 +0000 and debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz 2012-07-05 13:34:35 +0000 differ | 16 | Binary files debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz 1970-01-01 00:00:00 +0000 and debian/python-xpresser/usr/share/doc/python-xpresser/changelog.Debian.gz 2012-07-05 13:34:35 +0000 differ |
310 | === added directory 'debian/python-xpresser/usr/share/pyshared' | |||
311 | === added file 'debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info' | |||
312 | --- debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info 1970-01-01 00:00:00 +0000 | |||
313 | +++ debian/python-xpresser/usr/share/pyshared/Xpresser-1.0.egg-info 2012-07-05 13:34:35 +0000 | |||
314 | @@ -0,0 +1,10 @@ | |||
315 | 1 | Metadata-Version: 1.0 | ||
316 | 2 | Name: Xpresser | ||
317 | 3 | Version: 1.0 | ||
318 | 4 | Summary: Python library to script Graphic User Interfaces. | ||
319 | 5 | Home-page: https://edge.launchpad.net/xpresser | ||
320 | 6 | Author: Gustavo Niemeyer | ||
321 | 7 | Author-email: gustavo.niemeyer@canonical.com | ||
322 | 8 | License: UNKNOWN | ||
323 | 9 | Description: UNKNOWN | ||
324 | 10 | Platform: UNKNOWN | ||
325 | 0 | 11 | ||
326 | === added directory 'debian/python-xpresser/usr/share/pyshared/xpresser' | |||
327 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py' | |||
328 | --- debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py 1970-01-01 00:00:00 +0000 | |||
329 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/__init__.py 2012-07-05 13:34:35 +0000 | |||
330 | @@ -0,0 +1,23 @@ | |||
331 | 1 | # | ||
332 | 2 | # Copyright (c) 2010 Canonical | ||
333 | 3 | # | ||
334 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
335 | 5 | # | ||
336 | 6 | # This file is part of the Xpresser GUI automation library. | ||
337 | 7 | # | ||
338 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
339 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
340 | 10 | # as published by the Free Software Foundation. | ||
341 | 11 | # | ||
342 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
343 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
344 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
345 | 15 | # GNU Lesser General Public License for more details. | ||
346 | 16 | # | ||
347 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
348 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
349 | 19 | # | ||
350 | 20 | import pygtk | ||
351 | 21 | pygtk.require("2.0") | ||
352 | 22 | |||
353 | 23 | from xpresser.xp import Xpresser, ImageNotFound | ||
354 | 0 | 24 | ||
355 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/errors.py' | |||
356 | --- debian/python-xpresser/usr/share/pyshared/xpresser/errors.py 1970-01-01 00:00:00 +0000 | |||
357 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/errors.py 2012-07-05 13:34:35 +0000 | |||
358 | @@ -0,0 +1,23 @@ | |||
359 | 1 | # | ||
360 | 2 | # Copyright (c) 2010 Canonical | ||
361 | 3 | # | ||
362 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
363 | 5 | # | ||
364 | 6 | # This file is part of the Xpresser GUI automation library. | ||
365 | 7 | # | ||
366 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
367 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
368 | 10 | # as published by the Free Software Foundation. | ||
369 | 11 | # | ||
370 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
371 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
372 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
373 | 15 | # GNU Lesser General Public License for more details. | ||
374 | 16 | # | ||
375 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
376 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
377 | 19 | # | ||
378 | 20 | |||
379 | 21 | class XpresserError(Exception): | ||
380 | 22 | """Base class for all Xpresser exceptions.""" | ||
381 | 23 | |||
382 | 0 | 24 | ||
383 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/image.py' | |||
384 | --- debian/python-xpresser/usr/share/pyshared/xpresser/image.py 1970-01-01 00:00:00 +0000 | |||
385 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/image.py 2012-07-05 13:34:35 +0000 | |||
386 | @@ -0,0 +1,76 @@ | |||
387 | 1 | # | ||
388 | 2 | # Copyright (c) 2010 Canonical | ||
389 | 3 | # | ||
390 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
391 | 5 | # | ||
392 | 6 | # This file is part of the Xpresser GUI automation library. | ||
393 | 7 | # | ||
394 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
395 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
396 | 10 | # as published by the Free Software Foundation. | ||
397 | 11 | # | ||
398 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
399 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
400 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
401 | 15 | # GNU Lesser General Public License for more details. | ||
402 | 16 | # | ||
403 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
404 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
405 | 19 | # | ||
406 | 20 | |||
407 | 21 | |||
408 | 22 | DEFAULT_SIMILARITY = 0.98 | ||
409 | 23 | |||
410 | 24 | |||
411 | 25 | class Image(object): | ||
412 | 26 | """An image. :-) | ||
413 | 27 | |||
414 | 28 | @ivar name: The human-oriented name of this image. May be None. | ||
415 | 29 | |||
416 | 30 | @ivar similarity: The similarity tolerance to be used when searching | ||
417 | 31 | for this image. | ||
418 | 32 | |||
419 | 33 | Varies between 0.0 and 1.0, where 1.0 is a perfect match. Defaults | ||
420 | 34 | to the value of DEFAULT_SIMILARITY when not specified in the image | ||
421 | 35 | data. | ||
422 | 36 | |||
423 | 37 | @ivar focus_delta: (dx, dy) pair added to the center position to | ||
424 | 38 | find where to click. | ||
425 | 39 | |||
426 | 40 | For instance, if the *center* of the image is found at 200, 300 and | ||
427 | 41 | the focus_point is (10, -20) the click will actually happen at the | ||
428 | 42 | screen position (210, 280). | ||
429 | 43 | |||
430 | 44 | When not specified, (0, 0) is assumed, which means click in the | ||
431 | 45 | center of the image itself. | ||
432 | 46 | |||
433 | 47 | @ivar width: The width of the image. | ||
434 | 48 | |||
435 | 49 | @ivar height: The height of the image. | ||
436 | 50 | |||
437 | 51 | @ivar filename: Filename of the image. | ||
438 | 52 | |||
439 | 53 | @ivar array: Numpy array with three dimensions (rows, columns, RGB). | ||
440 | 54 | |||
441 | 55 | @ivar cache: Generic storage for data associated with this image, used | ||
442 | 56 | by the image finder, for instance. | ||
443 | 57 | """ | ||
444 | 58 | |||
445 | 59 | def __init__(self, name=None, similarity=None, focus_delta=None, | ||
446 | 60 | width=None, height=None, filename=None, array=None): | ||
447 | 61 | if similarity is None: | ||
448 | 62 | similarity = DEFAULT_SIMILARITY | ||
449 | 63 | if focus_delta is None: | ||
450 | 64 | focus_delta = (0, 0) | ||
451 | 65 | |||
452 | 66 | if not (0 < similarity < 1): | ||
453 | 67 | raise ValueError("Similarity out of range: %.2f" % similarity) | ||
454 | 68 | |||
455 | 69 | self.name = name | ||
456 | 70 | self.similarity = similarity | ||
457 | 71 | self.focus_delta = focus_delta | ||
458 | 72 | self.width = width | ||
459 | 73 | self.height = height | ||
460 | 74 | self.filename = filename | ||
461 | 75 | self.array = array | ||
462 | 76 | self.cache = {} | ||
463 | 0 | 77 | ||
464 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py' | |||
465 | --- debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py 1970-01-01 00:00:00 +0000 | |||
466 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/imagedir.py 2012-07-05 13:34:35 +0000 | |||
467 | @@ -0,0 +1,111 @@ | |||
468 | 1 | # | ||
469 | 2 | # Copyright (c) 2010 Canonical | ||
470 | 3 | # | ||
471 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
472 | 5 | # | ||
473 | 6 | # This file is part of the Xpresser GUI automation library. | ||
474 | 7 | # | ||
475 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
476 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
477 | 10 | # as published by the Free Software Foundation. | ||
478 | 11 | # | ||
479 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
480 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
481 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
482 | 15 | # GNU Lesser General Public License for more details. | ||
483 | 16 | # | ||
484 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
485 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
486 | 19 | # | ||
487 | 20 | import mimetypes | ||
488 | 21 | import ConfigParser | ||
489 | 22 | import os | ||
490 | 23 | import re | ||
491 | 24 | |||
492 | 25 | from xpresser.errors import XpresserError | ||
493 | 26 | from xpresser.image import Image | ||
494 | 27 | |||
495 | 28 | |||
496 | 29 | CLICK_POSITION_RE = re.compile(r"^\s*(?P<x>[-+][0-9]+)\s+(?P<y>[-+][0-9]+)\s*$") | ||
497 | 30 | |||
498 | 31 | |||
499 | 32 | class ImageDirError(XpresserError): | ||
500 | 33 | """Error related to the image directory.""" | ||
501 | 34 | |||
502 | 35 | |||
503 | 36 | class ImageDir(object): | ||
504 | 37 | """Represents a directory with data about images. | ||
505 | 38 | |||
506 | 39 | This class doesn't know about any details regarding the images | ||
507 | 40 | themselves, besides the existence of the file in which they reside. | ||
508 | 41 | It will give access to generic ImageData objects containing the | ||
509 | 42 | details about these images. It's up to an ImageLoader to make sense | ||
510 | 43 | of the actual image data contained in the image files. | ||
511 | 44 | """ | ||
512 | 45 | |||
513 | 46 | def __init__(self): | ||
514 | 47 | self._images = {} | ||
515 | 48 | |||
516 | 49 | def get(self, image_name): | ||
517 | 50 | return self._images.get(image_name) | ||
518 | 51 | |||
519 | 52 | def load(self, dirname): | ||
520 | 53 | """Load image information from C{dirname}. | ||
521 | 54 | |||
522 | 55 | @param dirname: Path of directory containing xpresser.ini. | ||
523 | 56 | """ | ||
524 | 57 | loaded_filenames = set() | ||
525 | 58 | ini_filename = os.path.join(dirname, "xpresser.ini") | ||
526 | 59 | if os.path.exists(ini_filename): | ||
527 | 60 | config = ConfigParser.ConfigParser() | ||
528 | 61 | config.read(ini_filename) | ||
529 | 62 | for section_name in config.sections(): | ||
530 | 63 | if section_name.startswith("image "): | ||
531 | 64 | image_name = section_name.split(None, 1)[1] | ||
532 | 65 | try: | ||
533 | 66 | image_filename = config.get(section_name, "filename") | ||
534 | 67 | except ConfigParser.NoOptionError: | ||
535 | 68 | raise ImageDirError("Image %s missing filename option" | ||
536 | 69 | % image_name) | ||
537 | 70 | image_filename = os.path.join(dirname, image_filename) | ||
538 | 71 | if not os.path.exists(image_filename): | ||
539 | 72 | raise ImageDirError("Image %s file not found: %s" % | ||
540 | 73 | (image_name, image_filename)) | ||
541 | 74 | try: | ||
542 | 75 | image_similarity = config.getfloat(section_name, | ||
543 | 76 | "similarity") | ||
544 | 77 | except ConfigParser.NoOptionError: | ||
545 | 78 | image_similarity = None | ||
546 | 79 | except ValueError: | ||
547 | 80 | value = config.get(section_name, "similarity") | ||
548 | 81 | raise ImageDirError("Image %s has bad similarity: %s" | ||
549 | 82 | % (image_name, value)) | ||
550 | 83 | |||
551 | 84 | try: | ||
552 | 85 | value = config.get(section_name, "focus_delta") | ||
553 | 86 | match = CLICK_POSITION_RE.match(value) | ||
554 | 87 | if not match: | ||
555 | 88 | raise ImageDirError("Image %s has invalid click " | ||
556 | 89 | "position: %s" % | ||
557 | 90 | (image_name, value)) | ||
558 | 91 | image_focus_delta = (int(match.group("x")), | ||
559 | 92 | int(match.group("y"))) | ||
560 | 93 | except ConfigParser.NoOptionError: | ||
561 | 94 | image_focus_delta = None | ||
562 | 95 | image = Image(name=image_name, | ||
563 | 96 | filename=image_filename, | ||
564 | 97 | similarity=image_similarity, | ||
565 | 98 | focus_delta=image_focus_delta) | ||
566 | 99 | self._images[image_name] = image | ||
567 | 100 | loaded_filenames.add(image_filename) | ||
568 | 101 | |||
569 | 102 | # Load any other images implicitly with the default arguments. | ||
570 | 103 | for basename in os.listdir(dirname): | ||
571 | 104 | filename = os.path.join(dirname, basename) | ||
572 | 105 | if filename not in loaded_filenames: | ||
573 | 106 | ftype, fencoding = mimetypes.guess_type(filename) | ||
574 | 107 | if ftype and ftype.startswith("image/"): | ||
575 | 108 | image_name = os.path.splitext(basename)[0] | ||
576 | 109 | self._images[image_name] = Image(name=image_name, | ||
577 | 110 | filename=filename) | ||
578 | 111 | |||
579 | 0 | 112 | ||
580 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py' | |||
581 | --- debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py 1970-01-01 00:00:00 +0000 | |||
582 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/imagematch.py 2012-07-05 13:34:35 +0000 | |||
583 | @@ -0,0 +1,54 @@ | |||
584 | 1 | # | ||
585 | 2 | # Copyright (c) 2010 Canonical | ||
586 | 3 | # | ||
587 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
588 | 5 | # | ||
589 | 6 | # This file is part of the Xpresser GUI automation library. | ||
590 | 7 | # | ||
591 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
592 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
593 | 10 | # as published by the Free Software Foundation. | ||
594 | 11 | # | ||
595 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
596 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
597 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
598 | 15 | # GNU Lesser General Public License for more details. | ||
599 | 16 | # | ||
600 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
601 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
602 | 19 | # | ||
603 | 20 | from xpresser.errors import XpresserError | ||
604 | 21 | |||
605 | 22 | |||
606 | 23 | class ImageMatchError(XpresserError): | ||
607 | 24 | """Error raised due to an ImageMatch related problem (really!).""" | ||
608 | 25 | |||
609 | 26 | |||
610 | 27 | class ImageMatch(object): | ||
611 | 28 | """An image found inside another image. | ||
612 | 29 | |||
613 | 30 | @ivar image: The image found. | ||
614 | 31 | @ivar x: Position in the X axis where the image was found. | ||
615 | 32 | @ivar y: Position in the Y axis where the image was found. | ||
616 | 33 | @ivar similarity: How similar to the original image the match was, | ||
617 | 34 | where 1.0 == 100%. | ||
618 | 35 | @ivar focus_point: The position in the screen which this image match | ||
619 | 36 | represents. This is useful for clicks, hovering, etc. If no delta | ||
620 | 37 | was specified in the image data itself, this will map to the center | ||
621 | 38 | of the found image. | ||
622 | 39 | """ | ||
623 | 40 | |||
624 | 41 | def __init__(self, image, x, y, similarity): | ||
625 | 42 | if image.height is None: | ||
626 | 43 | raise ImageMatchError("Image.height was None when trying to " | ||
627 | 44 | "create an ImageMatch with it.") | ||
628 | 45 | if image.width is None: | ||
629 | 46 | raise ImageMatchError("Image.width was None when trying to " | ||
630 | 47 | "create an ImageMatch with it.") | ||
631 | 48 | |||
632 | 49 | self.image = image | ||
633 | 50 | self.x = x | ||
634 | 51 | self.y = y | ||
635 | 52 | self.similarity = similarity | ||
636 | 53 | self.focus_point = (x + image.width//2 + image.focus_delta[0], | ||
637 | 54 | y + image.height//2 + image.focus_delta[1]) | ||
638 | 0 | 55 | ||
639 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py' | |||
640 | --- debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py 1970-01-01 00:00:00 +0000 | |||
641 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/opencvfinder.py 2012-07-05 13:34:35 +0000 | |||
642 | @@ -0,0 +1,142 @@ | |||
643 | 1 | # | ||
644 | 2 | # Copyright (c) 2010 Canonical | ||
645 | 3 | # | ||
646 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
647 | 5 | # | ||
648 | 6 | # This file is part of the Xpresser GUI automation library. | ||
649 | 7 | # | ||
650 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
651 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
652 | 10 | # as published by the Free Software Foundation. | ||
653 | 11 | # | ||
654 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
655 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
656 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
657 | 15 | # GNU Lesser General Public License for more details. | ||
658 | 16 | # | ||
659 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
660 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
661 | 19 | # | ||
662 | 20 | import math | ||
663 | 21 | import time | ||
664 | 22 | |||
665 | 23 | import numpy | ||
666 | 24 | |||
667 | 25 | import opencv | ||
668 | 26 | import opencv.highgui | ||
669 | 27 | import opencv.adaptors | ||
670 | 28 | |||
671 | 29 | from xpresser.imagematch import ImageMatch | ||
672 | 30 | |||
673 | 31 | |||
674 | 32 | FILTER_MARGIN = 25 # % | ||
675 | 33 | |||
676 | 34 | DEBUG_PERFORMANCE = False | ||
677 | 35 | |||
678 | 36 | |||
679 | 37 | class OpenCVFinder(object): | ||
680 | 38 | |||
681 | 39 | def find(self, screen_image, area_image): | ||
682 | 40 | matches = self._find(screen_image, area_image, best_match=True) | ||
683 | 41 | if matches: | ||
684 | 42 | matches.sort(key=lambda match: -match.similarity) | ||
685 | 43 | return matches[0] | ||
686 | 44 | return None | ||
687 | 45 | |||
688 | 46 | def find_all(self, screen_image, area_image): | ||
689 | 47 | return self._find(screen_image, area_image) | ||
690 | 48 | |||
691 | 49 | def _load_image(self, image): | ||
692 | 50 | if "opencv_image" not in image.cache: | ||
693 | 51 | if image.filename is not None: | ||
694 | 52 | opencv_image = opencv.highgui.cvLoadImage(image.filename) | ||
695 | 53 | elif image.array is not None: | ||
696 | 54 | # The adaptor function can't deal with the alpha channel. | ||
697 | 55 | array = image.array[:,:,:3] | ||
698 | 56 | opencv_image = opencv.adaptors.NumPy2Ipl(array) | ||
699 | 57 | else: | ||
700 | 58 | raise RuntimeError("Oops. Can't load image.") | ||
701 | 59 | image.cache["opencv_image"] = opencv_image | ||
702 | 60 | image.width = opencv_image.width | ||
703 | 61 | image.height = opencv_image.height | ||
704 | 62 | return image.cache["opencv_image"] | ||
705 | 63 | |||
706 | 64 | def _find(self, screen_image, area_image, best_match=False): | ||
707 | 65 | if DEBUG_PERFORMANCE: | ||
708 | 66 | started = time.time() | ||
709 | 67 | screen = self._load_image(screen_image) | ||
710 | 68 | area = self._load_image(area_image) | ||
711 | 69 | if DEBUG_PERFORMANCE: | ||
712 | 70 | print "LOADING IMAGES: %.5fs" % (time.time()-started) | ||
713 | 71 | |||
714 | 72 | result_width = screen.width - area.width + 1 | ||
715 | 73 | result_height = screen.height - area.height + 1 | ||
716 | 74 | result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height), | ||
717 | 75 | opencv.IPL_DEPTH_32F, 1) | ||
718 | 76 | if DEBUG_PERFORMANCE: | ||
719 | 77 | started = time.time() | ||
720 | 78 | opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED) | ||
721 | 79 | if DEBUG_PERFORMANCE: | ||
722 | 80 | print "MATCHING: %.5fs" % (time.time()-started) | ||
723 | 81 | |||
724 | 82 | result = opencv.adaptors.Ipl2NumPy(result) | ||
725 | 83 | |||
726 | 84 | if DEBUG_PERFORMANCE: | ||
727 | 85 | started = time.time() | ||
728 | 86 | matches = [] | ||
729 | 87 | for y, x in numpy.argwhere(result >= area_image.similarity): | ||
730 | 88 | matches.append(ImageMatch(area_image, x, y, result[y, x])) | ||
731 | 89 | if best_match and result[y, x] == 1.0: | ||
732 | 90 | return [matches[-1]] | ||
733 | 91 | if DEBUG_PERFORMANCE: | ||
734 | 92 | print "FINDING POSITIONS: %.5fs" % (time.time()-started) | ||
735 | 93 | |||
736 | 94 | x_margin = int(FILTER_MARGIN/100.0 * area_image.width) | ||
737 | 95 | y_margin = int(FILTER_MARGIN/100.0 * area_image.height) | ||
738 | 96 | |||
739 | 97 | if DEBUG_PERFORMANCE: | ||
740 | 98 | started = time.time() | ||
741 | 99 | matches = self._filter_nearby_positions(matches, x_margin, y_margin) | ||
742 | 100 | if DEBUG_PERFORMANCE: | ||
743 | 101 | print "FILTERING: %.5fs" % (time.time()-started) | ||
744 | 102 | return matches | ||
745 | 103 | |||
746 | 104 | def _filter_nearby_positions(self, matches, x_margin, y_margin): | ||
747 | 105 | """Remove nearby positions by taking the best one. | ||
748 | 106 | |||
749 | 107 | Doing this is necessary because around a good match there will | ||
750 | 108 | likely be other worse matches. | ||
751 | 109 | """ | ||
752 | 110 | |||
753 | 111 | # We have to build a kill list rather than removing on the fly | ||
754 | 112 | # so that neighbors of neighbors get correctly processed. | ||
755 | 113 | kill = set() | ||
756 | 114 | for match1 in matches: | ||
757 | 115 | if match1 in kill: | ||
758 | 116 | # Another match has already figured that this one isn't good. | ||
759 | 117 | continue | ||
760 | 118 | for match2 in matches: | ||
761 | 119 | if match2 is match1: | ||
762 | 120 | continue | ||
763 | 121 | # Even if match2 is in the kill list, we have to process it | ||
764 | 122 | # because it may have a better rating than this one still, and | ||
765 | 123 | # this would mean someone around is even better than match2, | ||
766 | 124 | # and thus both match2 *and* match1 should be killed. | ||
767 | 125 | #distance = math.hypot(match2.x-match1.x, match2.y-match1.y) | ||
768 | 126 | #if distance <= filter_distance: | ||
769 | 127 | if (abs(match2.x-match1.x) < x_margin or | ||
770 | 128 | abs(match2.y-match1.y) < y_margin): | ||
771 | 129 | comparison = cmp(match1.similarity, match2.similarity) | ||
772 | 130 | if comparison > 0: | ||
773 | 131 | # match2 is worse, so ensure it's in the kill list | ||
774 | 132 | # and maybe save time later on (if indeed it wasn't yet). | ||
775 | 133 | kill.add(match2) | ||
776 | 134 | elif (comparison < 0 | ||
777 | 135 | or (comparison == 0 and match2 not in kill)): | ||
778 | 136 | # If match2 matches better than match1, or they're | ||
779 | 137 | # equivalent and match2 is not in the kill list yet, | ||
780 | 138 | # so kill match1 and move on to a different match1. | ||
781 | 139 | kill.add(match1) | ||
782 | 140 | break | ||
783 | 141 | |||
784 | 142 | return list(set(matches) - kill) | ||
785 | 0 | 143 | ||
786 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xp.py' | |||
787 | --- debian/python-xpresser/usr/share/pyshared/xpresser/xp.py 1970-01-01 00:00:00 +0000 | |||
788 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/xp.py 2012-07-05 13:34:35 +0000 | |||
789 | @@ -0,0 +1,128 @@ | |||
790 | 1 | # | ||
791 | 2 | # Copyright (c) 2010 Canonical | ||
792 | 3 | # | ||
793 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
794 | 5 | # | ||
795 | 6 | # This file is part of the Xpresser GUI automation library. | ||
796 | 7 | # | ||
797 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
798 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
799 | 10 | # as published by the Free Software Foundation. | ||
800 | 11 | # | ||
801 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
802 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
803 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
804 | 15 | # GNU Lesser General Public License for more details. | ||
805 | 16 | # | ||
806 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
807 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
808 | 19 | # | ||
809 | 20 | import time | ||
810 | 21 | |||
811 | 22 | from xpresser import xutils | ||
812 | 23 | from xpresser.image import Image | ||
813 | 24 | from xpresser.errors import XpresserError | ||
814 | 25 | from xpresser.imagedir import ImageDir | ||
815 | 26 | from xpresser.imagematch import ImageMatch | ||
816 | 27 | from xpresser.opencvfinder import OpenCVFinder | ||
817 | 28 | |||
818 | 29 | |||
819 | 30 | class ImageNotFound(XpresserError): | ||
820 | 31 | """Exception raised when a request to find an image doesn't succeed.""" | ||
821 | 32 | |||
822 | 33 | |||
823 | 34 | class Xpresser(object): | ||
824 | 35 | |||
825 | 36 | def __init__(self): | ||
826 | 37 | self._imagedir = ImageDir() | ||
827 | 38 | self._imagefinder = OpenCVFinder() | ||
828 | 39 | |||
829 | 40 | def load_images(self, path): | ||
830 | 41 | self._imagedir.load(path) | ||
831 | 42 | |||
832 | 43 | def get_image(self, name): | ||
833 | 44 | return self._imagedir.get(name) | ||
834 | 45 | |||
835 | 46 | def _compute_focus_point(self, args): | ||
836 | 47 | if (len(args) == 2 and | ||
837 | 48 | isinstance(args[0], (int, long)) and | ||
838 | 49 | isinstance(args[1], (int, long))): | ||
839 | 50 | return args | ||
840 | 51 | elif len(args) == 1: | ||
841 | 52 | if type(args[0]) == ImageMatch: | ||
842 | 53 | match = args[0] | ||
843 | 54 | else: | ||
844 | 55 | match = self.find(args[0]) | ||
845 | 56 | return match.focus_point | ||
846 | 57 | |||
847 | 58 | def click(self, *args): | ||
848 | 59 | """Click on the position specified by the provided arguments. | ||
849 | 60 | |||
850 | 61 | The following examples show valid ways of specifying the position: | ||
851 | 62 | |||
852 | 63 | xp.click("image-name") | ||
853 | 64 | xp.click(image_match) | ||
854 | 65 | xp.click(x, y) | ||
855 | 66 | """ | ||
856 | 67 | xutils.click(*self._compute_focus_point(args)) | ||
857 | 68 | |||
858 | 69 | def right_click(self, *args): | ||
859 | 70 | """Right-click on the position specified by the provided arguments. | ||
860 | 71 | |||
861 | 72 | The following examples show valid ways of specifying the position: | ||
862 | 73 | |||
863 | 74 | xp.right_click("image-name") | ||
864 | 75 | xp.right_click(image_match) | ||
865 | 76 | xp.right_click(x, y) | ||
866 | 77 | """ | ||
867 | 78 | xutils.right_click(*self._compute_focus_point(args)) | ||
868 | 79 | |||
869 | 80 | def double_click(self, *args): | ||
870 | 81 | '''Double clicks over the position specified by arguments | ||
871 | 82 | |||
872 | 83 | The following examples show valid ways of specifying te position: | ||
873 | 84 | xp.double_click("image-name") | ||
874 | 85 | xp.double_click(image_match) | ||
875 | 86 | xp.double_click(x, y) | ||
876 | 87 | ''' | ||
877 | 88 | xutils.double_click(*self._compute_focus_point(args)) | ||
878 | 89 | |||
879 | 90 | def hover(self, *args): | ||
880 | 91 | """Hover over the position specified by the provided arguments. | ||
881 | 92 | |||
882 | 93 | The following examples show valid ways of specifying the position: | ||
883 | 94 | |||
884 | 95 | xp.hover("image-name") | ||
885 | 96 | xp.hover(image_match) | ||
886 | 97 | xp.hover(x, y) | ||
887 | 98 | """ | ||
888 | 99 | xutils.hover(*self._compute_focus_point(args)) | ||
889 | 100 | |||
890 | 101 | def find(self, image, timeout=10): | ||
891 | 102 | """Given an image or an image name, find it on the screen. | ||
892 | 103 | |||
893 | 104 | @param image: Image or image name to be searched for. | ||
894 | 105 | @return: An ImageMatch instance, or None. | ||
895 | 106 | """ | ||
896 | 107 | if isinstance(image, basestring): | ||
897 | 108 | image = self._imagedir.get(image) | ||
898 | 109 | wait_until = time.time() + timeout | ||
899 | 110 | while time.time() < wait_until: | ||
900 | 111 | screenshot_image = xutils.take_screenshot() | ||
901 | 112 | match = self._imagefinder.find(screenshot_image, image) | ||
902 | 113 | if match is not None: | ||
903 | 114 | return match | ||
904 | 115 | raise ImageNotFound(image) | ||
905 | 116 | |||
906 | 117 | def wait(self, image, timeout=30): | ||
907 | 118 | """Wait for an image to show up in the screen up to C{timeout} seconds. | ||
908 | 119 | |||
909 | 120 | @param image: Image or image name to be searched for. | ||
910 | 121 | @return: An ImageMatch instance, or None. | ||
911 | 122 | """ | ||
912 | 123 | self.find(image, timeout) | ||
913 | 124 | |||
914 | 125 | def type(self, string): | ||
915 | 126 | """Enter the string provided as if it was typed via the keyboard. | ||
916 | 127 | """ | ||
917 | 128 | xutils.type(string) | ||
918 | 0 | 129 | ||
919 | === added file 'debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py' | |||
920 | --- debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py 1970-01-01 00:00:00 +0000 | |||
921 | +++ debian/python-xpresser/usr/share/pyshared/xpresser/xutils.py 2012-07-05 13:34:35 +0000 | |||
922 | @@ -0,0 +1,132 @@ | |||
923 | 1 | # | ||
924 | 2 | # Copyright (c) 2010 Canonical | ||
925 | 3 | # | ||
926 | 4 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
927 | 5 | # | ||
928 | 6 | # This file is part of the Xpresser GUI automation library. | ||
929 | 7 | # | ||
930 | 8 | # Xpresser is free software; you can redistribute it and/or modify | ||
931 | 9 | # it under the terms of the GNU Lesser General Public License version 3, | ||
932 | 10 | # as published by the Free Software Foundation. | ||
933 | 11 | # | ||
934 | 12 | # Xpresser is distributed in the hope that it will be useful, | ||
935 | 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
936 | 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
937 | 15 | # GNU Lesser General Public License for more details. | ||
938 | 16 | # | ||
939 | 17 | # You should have received a copy of the GNU Lesser General Public License | ||
940 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
941 | 19 | # | ||
942 | 20 | from xpresser.image import Image | ||
943 | 21 | |||
944 | 22 | import pyatspi | ||
945 | 23 | import gtk | ||
946 | 24 | |||
947 | 25 | import warnings | ||
948 | 26 | |||
949 | 27 | # pygtk is using a deprecated method from numpy in get_pixels_array(). | ||
950 | 28 | warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*") | ||
951 | 29 | |||
952 | 30 | |||
953 | 31 | def click(x, y): | ||
954 | 32 | pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1C) | ||
955 | 33 | |||
956 | 34 | def right_click(x, y): | ||
957 | 35 | pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B3C) | ||
958 | 36 | |||
959 | 37 | def double_click(x, y): | ||
960 | 38 | pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_B1D) | ||
961 | 39 | |||
962 | 40 | def hover(x, y): | ||
963 | 41 | pyatspi.Registry.generateMouseEvent(x, y, pyatspi.MOUSE_ABS) | ||
964 | 42 | |||
965 | 43 | def type(string): | ||
966 | 44 | if string == "<Backspace>": | ||
967 | 45 | keyval == gtk.keysyms.BackSpace | ||
968 | 46 | elif string == "<Begin>": | ||
969 | 47 | keyval = gtk.keysyms.Begin | ||
970 | 48 | elif string == "<Delete>": | ||
971 | 49 | keyval = gtk.keysyms.Delete | ||
972 | 50 | elif string == "<Down>": | ||
973 | 51 | keyval = gtk.keysyms.Down | ||
974 | 52 | elif string == "<Escape>": | ||
975 | 53 | keyval = gtk.keysyms.Escape | ||
976 | 54 | elif string == "<End>": | ||
977 | 55 | keyval = gtk.keysyms.End | ||
978 | 56 | elif string == "<F1>": | ||
979 | 57 | keyval = gtk.keysyms.F1 | ||
980 | 58 | elif string == "<F2>": | ||
981 | 59 | keyval = gtk.keysyms.F2 | ||
982 | 60 | elif string == "<F3>": | ||
983 | 61 | keyval = gtk.keysyms.F3 | ||
984 | 62 | elif string == "<F4>": | ||
985 | 63 | keyval = gtk.keysyms.F4 | ||
986 | 64 | elif string == "<F5>": | ||
987 | 65 | keyval = gtk.keysyms.F5 | ||
988 | 66 | elif string == "<F6>": | ||
989 | 67 | keyval = gtk.keysyms.F6 | ||
990 | 68 | elif string == "<F7>": | ||
991 | 69 | keyval = gtk.keysyms.F7 | ||
992 | 70 | elif string == "<F8>": | ||
993 | 71 | keyval = gtk.keysyms.F8 | ||
994 | 72 | elif string == "<F9>": | ||
995 | 73 | keyval = gtk.keysyms.F9 | ||
996 | 74 | elif string == "<F10>": | ||
997 | 75 | keyval = gtk.keysyms.F10 | ||
998 | 76 | elif string == "<F11>": | ||
999 | 77 | keyval = gtk.keysyms.F11 | ||
1000 | 78 | elif string == "<F12>": | ||
1001 | 79 | keyval = gtk.keysyms.F12 | ||
1002 | 80 | elif string == "<Home>": | ||
1003 | 81 | keyval = gtk.keysyms.Home | ||
1004 | 82 | elif string == "<Insert>": | ||
1005 | 83 | keyval = gtk.keysyms.Insert | ||
1006 | 84 | elif string == "<KP_Down>": | ||
1007 | 85 | keyval = gtk.keysyms.KP_Down | ||
1008 | 86 | elif string == "<KP_Enter>": | ||
1009 | 87 | keyval = gtk.keysyms.KP_Enter | ||
1010 | 88 | elif string == "<KP_Left>": | ||
1011 | 89 | keyval = gtk.keysyms.KP_Left | ||
1012 | 90 | elif string == "<KP_Right>": | ||
1013 | 91 | keyval = gtk.keysyms.KP_Right | ||
1014 | 92 | elif string == 'KP_Space': | ||
1015 | 93 | keyval = gtk.keysyms.KP_Space | ||
1016 | 94 | elif string == "KP_Tab": | ||
1017 | 95 | keyval = gtk.keysyms.KP_Tab | ||
1018 | 96 | elif string == "<KP_Up>": | ||
1019 | 97 | keyval = gtk.keysyms.KP_Up | ||
1020 | 98 | elif string == "<Left>": | ||
1021 | 99 | keyval = gtk.keysyms.Left | ||
1022 | 100 | elif string == "<Page_down>": | ||
1023 | 101 | keyval = gtk.keysyms.Page_Down | ||
1024 | 102 | elif string == "<Page_up>": | ||
1025 | 103 | keyval = gtk.keysyms.Page_Up | ||
1026 | 104 | elif string == "<Return>": | ||
1027 | 105 | keyval = gtk.keysyms.Return | ||
1028 | 106 | elif string == "<Right>": | ||
1029 | 107 | keyval = gtk.keysyms.Right | ||
1030 | 108 | elif string == "<Up>": | ||
1031 | 109 | keyval = gtk.keysyms.Up | ||
1032 | 110 | elif string == "<Tab>": | ||
1033 | 111 | keyval = gtk.keysyms.Tab | ||
1034 | 112 | |||
1035 | 113 | else: | ||
1036 | 114 | for char in string: | ||
1037 | 115 | keyval = gtk.gdk.unicode_to_keyval(ord(char)) | ||
1038 | 116 | |||
1039 | 117 | pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) | ||
1040 | 118 | |||
1041 | 119 | def take_screenshot(x=0, y=0, width=None, height=None): | ||
1042 | 120 | window = gtk.gdk.get_default_root_window() | ||
1043 | 121 | if not (width and height): | ||
1044 | 122 | size = window.get_size() | ||
1045 | 123 | if not width: | ||
1046 | 124 | width = size[0] | ||
1047 | 125 | if not height: | ||
1048 | 126 | height = size[1] | ||
1049 | 127 | pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height) | ||
1050 | 128 | pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(), | ||
1051 | 129 | x, y, 0, 0, width, height) | ||
1052 | 130 | array = pixbuf.get_pixels_array() | ||
1053 | 131 | return Image("screenshot", array=array, | ||
1054 | 132 | width=array.shape[1], height=array.shape[0]) | ||
1055 | 0 | 133 | ||
1056 | === added file 'debian/rules' | |||
1057 | --- debian/rules 1970-01-01 00:00:00 +0000 | |||
1058 | +++ debian/rules 2012-07-05 13:34:35 +0000 | |||
1059 | @@ -0,0 +1,9 @@ | |||
1060 | 1 | #!/usr/bin/make -f | ||
1061 | 2 | |||
1062 | 3 | # This file was automatically generated by stdeb 0.6.0+git at | ||
1063 | 4 | # Tue, 25 Oct 2011 15:12:21 -0400 | ||
1064 | 5 | |||
1065 | 6 | %: | ||
1066 | 7 | dh $@ --with python2 --buildsystem=python_distutils | ||
1067 | 8 | |||
1068 | 9 | |||
1069 | 0 | 10 | ||
1070 | === added directory 'debian/source' | |||
1071 | === modified file 'example.py' | |||
1072 | --- example.py 2010-05-18 14:38:17 +0000 | |||
1073 | +++ example.py 2012-07-05 13:34:35 +0000 | |||
1074 | @@ -19,14 +19,13 @@ | |||
1075 | 19 | # | 19 | # |
1076 | 20 | import time | 20 | import time |
1077 | 21 | 21 | ||
1080 | 22 | import gtk | 22 | from gi.repository import Gtk as gtk |
1079 | 23 | |||
1081 | 24 | from xpresser import Xpresser | 23 | from xpresser import Xpresser |
1082 | 25 | 24 | ||
1083 | 26 | 25 | ||
1084 | 27 | def display_screen_image(): | 26 | def display_screen_image(): |
1085 | 28 | gtk_window = gtk.Window() | 27 | gtk_window = gtk.Window() |
1087 | 29 | gtk_image = gtk.image_new_from_file("xpresser/tests/images/screen.png") | 28 | gtk_image = gtk.Image.new_from_file("xpresser/tests/images/screen.png") |
1088 | 30 | gtk_image.show() | 29 | gtk_image.show() |
1089 | 31 | gtk_window.add(gtk_image) | 30 | gtk_window.add(gtk_image) |
1090 | 32 | gtk_window.show() | 31 | gtk_window.show() |
1091 | @@ -35,7 +34,7 @@ | |||
1092 | 35 | time.sleep(1) | 34 | time.sleep(1) |
1093 | 36 | while gtk.events_pending(): | 35 | while gtk.events_pending(): |
1094 | 37 | gtk.main_iteration() | 36 | gtk.main_iteration() |
1096 | 38 | gtk_window.connect("delete_event", lambda widget, data: gtk_window.destroy()) | 37 | gtk_window.connect("delete_event", lambda widget, data: gtk.main_quit()) |
1097 | 39 | gtk_window.connect("destroy", lambda widget: gtk.main_quit()) | 38 | gtk_window.connect("destroy", lambda widget: gtk.main_quit()) |
1098 | 40 | 39 | ||
1099 | 41 | def main(): | 40 | def main(): |
1100 | 42 | 41 | ||
1101 | === modified file 'test' | |||
1102 | --- test 2010-05-16 22:59:48 +0000 | |||
1103 | +++ test 2012-07-05 13:34:35 +0000 | |||
1104 | @@ -1,19 +1,133 @@ | |||
1105 | 1 | #!/usr/bin/env python | 1 | #!/usr/bin/env python |
1106 | 2 | # | ||
1107 | 3 | # Copyright (c) 2006, 2007 Canonical | ||
1108 | 4 | # | ||
1109 | 5 | # Written by Gustavo Niemeyer <gustavo@niemeyer.net> | ||
1110 | 6 | # | ||
1111 | 7 | # This file is part of Storm Object Relational Mapper. | ||
1112 | 8 | # | ||
1113 | 9 | # Storm is free software; you can redistribute it and/or modify | ||
1114 | 10 | # it under the terms of the GNU Lesser General Public License as | ||
1115 | 11 | # published by the Free Software Foundation; either version 2.1 of | ||
1116 | 12 | # the License, or (at your option) any later version. | ||
1117 | 13 | # | ||
1118 | 14 | # Storm is distributed in the hope that it will be useful, | ||
1119 | 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
1120 | 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
1121 | 17 | # GNU Lesser General Public License for more details. | ||
1122 | 18 | # | ||
1123 | 19 | # You should have received a copy of the GNU Lesser General Public License | ||
1124 | 20 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
1125 | 21 | # | ||
1126 | 22 | import optparse | ||
1127 | 23 | import unittest | ||
1128 | 24 | import doctest | ||
1129 | 2 | import sys | 25 | import sys |
1130 | 3 | import os | 26 | import os |
1131 | 4 | 27 | ||
1141 | 5 | 28 | import xpresser | |
1142 | 6 | def main(args): | 29 | |
1143 | 7 | for arg in args: | 30 | |
1144 | 8 | if arg.startswith("xpresser"): | 31 | def find_tests(testpaths=()): |
1145 | 9 | break | 32 | """Find all test paths, or test paths contained in the provided sequence. |
1146 | 10 | else: | 33 | |
1147 | 11 | args.append("xpresser") | 34 | @param testpaths: If provided, only tests in the given sequence will |
1148 | 12 | os.execvp("trial", ["trial", "-r", "glib2"] + args) | 35 | be considered. If not provided, all tests are |
1149 | 13 | 36 | considered. | |
1150 | 37 | @return: a test suite containing the requested tests. | ||
1151 | 38 | """ | ||
1152 | 39 | suite = unittest.TestSuite() | ||
1153 | 40 | topdir = os.path.abspath(os.path.dirname(__file__)) | ||
1154 | 41 | testdir = os.path.dirname(xpresser.__file__) | ||
1155 | 42 | testpaths = set(testpaths) | ||
1156 | 43 | for root, dirnames, filenames in os.walk(testdir): | ||
1157 | 44 | for filename in filenames: | ||
1158 | 45 | filepath = os.path.join(root, filename) | ||
1159 | 46 | relpath = filepath[len(topdir)+1:] | ||
1160 | 47 | |||
1161 | 48 | if (filename == "__init__.py" or filename.endswith(".pyc") or | ||
1162 | 49 | relpath == os.path.join("tests", "conftest.py")): | ||
1163 | 50 | # Skip non-tests. | ||
1164 | 51 | continue | ||
1165 | 52 | |||
1166 | 53 | if testpaths: | ||
1167 | 54 | # Skip any tests not in testpaths. | ||
1168 | 55 | for testpath in testpaths: | ||
1169 | 56 | if relpath.startswith(testpath): | ||
1170 | 57 | break | ||
1171 | 58 | else: | ||
1172 | 59 | continue | ||
1173 | 60 | |||
1174 | 61 | if filename.endswith(".py"): | ||
1175 | 62 | modpath = relpath.replace(os.path.sep, ".")[:-3] | ||
1176 | 63 | module = __import__(modpath, None, None, [""]) | ||
1177 | 64 | suite.addTest( | ||
1178 | 65 | unittest.defaultTestLoader.loadTestsFromModule(module)) | ||
1179 | 66 | elif filename.endswith(".txt"): | ||
1180 | 67 | load_test = True | ||
1181 | 68 | if relpath == os.path.join("tests", "zope", "README.txt"): | ||
1182 | 69 | # Special case the inclusion of the Zope-dependent | ||
1183 | 70 | # ZStorm doctest. | ||
1184 | 71 | from tests.zope import has_zope | ||
1185 | 72 | load_test = has_zope | ||
1186 | 73 | if load_test: | ||
1187 | 74 | parent_path = os.path.dirname(relpath).replace( | ||
1188 | 75 | os.path.sep, ".") | ||
1189 | 76 | parent_module = __import__(parent_path, None, None, [""]) | ||
1190 | 77 | suite.addTest(doctest.DocFileSuite( | ||
1191 | 78 | os.path.basename(relpath), | ||
1192 | 79 | module_relative=True, | ||
1193 | 80 | package=parent_module, | ||
1194 | 81 | optionflags=doctest.ELLIPSIS)) | ||
1195 | 82 | |||
1196 | 83 | return suite | ||
1197 | 84 | |||
1198 | 85 | |||
1199 | 86 | def parse_sys_argv(): | ||
1200 | 87 | """Extract any arguments not starting with '-' from sys.argv.""" | ||
1201 | 88 | testpaths = [] | ||
1202 | 89 | for i in range(len(sys.argv)-1,0,-1): | ||
1203 | 90 | arg = sys.argv[i] | ||
1204 | 91 | if not arg.startswith("-"): | ||
1205 | 92 | testpaths.append(arg) | ||
1206 | 93 | del sys.argv[i] | ||
1207 | 94 | return testpaths | ||
1208 | 95 | |||
1209 | 96 | def test_with_runner(runner): | ||
1210 | 97 | usage = "test.py [options] [<test filename>, ...]" | ||
1211 | 98 | |||
1212 | 99 | parser = optparse.OptionParser(usage=usage) | ||
1213 | 100 | |||
1214 | 101 | parser.add_option('--verbose', action='store_true') | ||
1215 | 102 | opts, args = parser.parse_args() | ||
1216 | 103 | |||
1217 | 104 | if opts.verbose: | ||
1218 | 105 | runner.verbosity = 2 | ||
1219 | 106 | |||
1220 | 107 | suite = find_tests(args) | ||
1221 | 108 | result = runner.run(suite) | ||
1222 | 109 | return not result.wasSuccessful() | ||
1223 | 110 | |||
1224 | 111 | |||
1225 | 112 | def test_with_trial(): | ||
1226 | 113 | from twisted.trial.reporter import TreeReporter | ||
1227 | 114 | from twisted.trial.runner import TrialRunner | ||
1228 | 115 | runner = TrialRunner(reporterFactory=TreeReporter, realTimeErrors=True) | ||
1229 | 116 | return test_with_runner(runner) | ||
1230 | 117 | |||
1231 | 118 | |||
1232 | 119 | def test_with_unittest(): | ||
1233 | 120 | runner = unittest.TextTestRunner() | ||
1234 | 121 | return test_with_runner(runner) | ||
1235 | 122 | |||
1236 | 14 | 123 | ||
1237 | 15 | if __name__ == "__main__": | 124 | if __name__ == "__main__": |
1240 | 16 | main(sys.argv[1:]) | 125 | runner = os.environ.get("TEST_RUNNER") |
1241 | 17 | 126 | if not runner: | |
1242 | 127 | runner = "unittest" | ||
1243 | 128 | runner_func = globals().get("test_with_%s" % runner.replace(".", "_")) | ||
1244 | 129 | if not runner_func: | ||
1245 | 130 | sys.exit("Test runner not found: %s" % runner) | ||
1246 | 131 | sys.exit(runner_func()) | ||
1247 | 18 | 132 | ||
1248 | 19 | # vim:ts=4:sw=4:et | 133 | # vim:ts=4:sw=4:et |
1249 | 20 | 134 | ||
1250 | === modified file 'xpresser/__init__.py' | |||
1251 | --- xpresser/__init__.py 2010-05-18 14:38:17 +0000 | |||
1252 | +++ xpresser/__init__.py 2012-07-05 13:34:35 +0000 | |||
1253 | @@ -17,7 +17,5 @@ | |||
1254 | 17 | # You should have received a copy of the GNU Lesser General Public License | 17 | # You should have received a copy of the GNU Lesser General Public License |
1255 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1256 | 19 | # | 19 | # |
1257 | 20 | import pygtk | ||
1258 | 21 | pygtk.require("2.0") | ||
1259 | 22 | 20 | ||
1260 | 23 | from xpresser.xp import Xpresser, ImageNotFound | 21 | from xpresser.xp import Xpresser, ImageNotFound |
1261 | 24 | 22 | ||
1262 | === modified file 'xpresser/image.py' | |||
1263 | --- xpresser/image.py 2010-05-18 14:38:17 +0000 | |||
1264 | +++ xpresser/image.py 2012-07-05 13:34:35 +0000 | |||
1265 | @@ -35,7 +35,7 @@ | |||
1266 | 35 | data. | 35 | data. |
1267 | 36 | 36 | ||
1268 | 37 | @ivar focus_delta: (dx, dy) pair added to the center position to | 37 | @ivar focus_delta: (dx, dy) pair added to the center position to |
1270 | 38 | find where to click. | 38 | find where to click. |
1271 | 39 | 39 | ||
1272 | 40 | For instance, if the *center* of the image is found at 200, 300 and | 40 | For instance, if the *center* of the image is found at 200, 300 and |
1273 | 41 | the focus_point is (10, -20) the click will actually happen at the | 41 | the focus_point is (10, -20) the click will actually happen at the |
1274 | 42 | 42 | ||
1275 | === modified file 'xpresser/opencvfinder.py' | |||
1276 | --- xpresser/opencvfinder.py 2010-05-18 14:38:17 +0000 | |||
1277 | +++ xpresser/opencvfinder.py 2012-07-05 13:34:35 +0000 | |||
1278 | @@ -17,29 +17,18 @@ | |||
1279 | 17 | # You should have received a copy of the GNU Lesser General Public License | 17 | # You should have received a copy of the GNU Lesser General Public License |
1280 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1281 | 19 | # | 19 | # |
1291 | 20 | import math | 20 | import SimpleCV |
1283 | 21 | import time | ||
1284 | 22 | |||
1285 | 23 | import numpy | ||
1286 | 24 | |||
1287 | 25 | import opencv | ||
1288 | 26 | import opencv.highgui | ||
1289 | 27 | import opencv.adaptors | ||
1290 | 28 | |||
1292 | 29 | from xpresser.imagematch import ImageMatch | 21 | from xpresser.imagematch import ImageMatch |
1293 | 30 | 22 | ||
1294 | 31 | 23 | ||
1295 | 32 | FILTER_MARGIN = 25 # % | 24 | FILTER_MARGIN = 25 # % |
1296 | 33 | 25 | ||
1297 | 34 | DEBUG_PERFORMANCE = False | ||
1298 | 35 | |||
1299 | 36 | 26 | ||
1300 | 37 | class OpenCVFinder(object): | 27 | class OpenCVFinder(object): |
1301 | 38 | 28 | ||
1302 | 39 | def find(self, screen_image, area_image): | 29 | def find(self, screen_image, area_image): |
1303 | 40 | matches = self._find(screen_image, area_image, best_match=True) | 30 | matches = self._find(screen_image, area_image, best_match=True) |
1304 | 41 | if matches: | 31 | if matches: |
1305 | 42 | matches.sort(key=lambda match: -match.similarity) | ||
1306 | 43 | return matches[0] | 32 | return matches[0] |
1307 | 44 | return None | 33 | return None |
1308 | 45 | 34 | ||
1309 | @@ -49,11 +38,9 @@ | |||
1310 | 49 | def _load_image(self, image): | 38 | def _load_image(self, image): |
1311 | 50 | if "opencv_image" not in image.cache: | 39 | if "opencv_image" not in image.cache: |
1312 | 51 | if image.filename is not None: | 40 | if image.filename is not None: |
1314 | 52 | opencv_image = opencv.highgui.cvLoadImage(image.filename) | 41 | opencv_image = SimpleCV.Image(image.filename) |
1315 | 53 | elif image.array is not None: | 42 | elif image.array is not None: |
1319 | 54 | # The adaptor function can't deal with the alpha channel. | 43 | opencv_image = image.array |
1317 | 55 | array = image.array[:,:,:3] | ||
1318 | 56 | opencv_image = opencv.adaptors.NumPy2Ipl(array) | ||
1320 | 57 | else: | 44 | else: |
1321 | 58 | raise RuntimeError("Oops. Can't load image.") | 45 | raise RuntimeError("Oops. Can't load image.") |
1322 | 59 | image.cache["opencv_image"] = opencv_image | 46 | image.cache["opencv_image"] = opencv_image |
1323 | @@ -62,48 +49,29 @@ | |||
1324 | 62 | return image.cache["opencv_image"] | 49 | return image.cache["opencv_image"] |
1325 | 63 | 50 | ||
1326 | 64 | def _find(self, screen_image, area_image, best_match=False): | 51 | def _find(self, screen_image, area_image, best_match=False): |
1355 | 65 | if DEBUG_PERFORMANCE: | 52 | source = self._load_image(screen_image) |
1356 | 66 | started = time.time() | 53 | template = self._load_image(area_image) |
1357 | 67 | screen = self._load_image(screen_image) | 54 | results = [] |
1358 | 68 | area = self._load_image(area_image) | 55 | matches = source.findTemplate(template, method="CCOEFF_NORM") |
1359 | 69 | if DEBUG_PERFORMANCE: | 56 | if matches: |
1360 | 70 | print "LOADING IMAGES: %.5fs" % (time.time()-started) | 57 | for m in [m for m in matches if m.quality >= area_image.similarity]: |
1361 | 71 | 58 | results.append( | |
1362 | 72 | result_width = screen.width - area.width + 1 | 59 | ImageMatch(area_image, m.x, m.y, m.quality)) |
1363 | 73 | result_height = screen.height - area.height + 1 | 60 | if best_match and m.quality == 1.0: |
1364 | 74 | result = opencv.cvCreateImage(opencv.cvSize(result_width, result_height), | 61 | return [results[-1]] |
1337 | 75 | opencv.IPL_DEPTH_32F, 1) | ||
1338 | 76 | if DEBUG_PERFORMANCE: | ||
1339 | 77 | started = time.time() | ||
1340 | 78 | opencv.cvMatchTemplate(screen, area, result, opencv.CV_TM_CCORR_NORMED) | ||
1341 | 79 | if DEBUG_PERFORMANCE: | ||
1342 | 80 | print "MATCHING: %.5fs" % (time.time()-started) | ||
1343 | 81 | |||
1344 | 82 | result = opencv.adaptors.Ipl2NumPy(result) | ||
1345 | 83 | |||
1346 | 84 | if DEBUG_PERFORMANCE: | ||
1347 | 85 | started = time.time() | ||
1348 | 86 | matches = [] | ||
1349 | 87 | for y, x in numpy.argwhere(result >= area_image.similarity): | ||
1350 | 88 | matches.append(ImageMatch(area_image, x, y, result[y, x])) | ||
1351 | 89 | if best_match and result[y, x] == 1.0: | ||
1352 | 90 | return [matches[-1]] | ||
1353 | 91 | if DEBUG_PERFORMANCE: | ||
1354 | 92 | print "FINDING POSITIONS: %.5fs" % (time.time()-started) | ||
1365 | 93 | 62 | ||
1366 | 94 | x_margin = int(FILTER_MARGIN/100.0 * area_image.width) | 63 | x_margin = int(FILTER_MARGIN/100.0 * area_image.width) |
1367 | 95 | y_margin = int(FILTER_MARGIN/100.0 * area_image.height) | 64 | y_margin = int(FILTER_MARGIN/100.0 * area_image.height) |
1368 | 96 | 65 | ||
1375 | 97 | if DEBUG_PERFORMANCE: | 66 | results = self._filter_nearby_positions(results, x_margin, y_margin) |
1376 | 98 | started = time.time() | 67 | if results: |
1377 | 99 | matches = self._filter_nearby_positions(matches, x_margin, y_margin) | 68 | results.sort(key=lambda match: -match.similarity) |
1378 | 100 | if DEBUG_PERFORMANCE: | 69 | |
1379 | 101 | print "FILTERING: %.5fs" % (time.time()-started) | 70 | return results |
1374 | 102 | return matches | ||
1380 | 103 | 71 | ||
1381 | 104 | def _filter_nearby_positions(self, matches, x_margin, y_margin): | 72 | def _filter_nearby_positions(self, matches, x_margin, y_margin): |
1382 | 105 | """Remove nearby positions by taking the best one. | 73 | """Remove nearby positions by taking the best one. |
1384 | 106 | 74 | ||
1385 | 107 | Doing this is necessary because around a good match there will | 75 | Doing this is necessary because around a good match there will |
1386 | 108 | likely be other worse matches. | 76 | likely be other worse matches. |
1387 | 109 | """ | 77 | """ |
1388 | 110 | 78 | ||
1389 | === modified file 'xpresser/tests/test_opencvfinder.py' | |||
1390 | --- xpresser/tests/test_opencvfinder.py 2010-05-18 14:38:17 +0000 | |||
1391 | +++ xpresser/tests/test_opencvfinder.py 2012-07-05 13:34:35 +0000 | |||
1392 | @@ -17,8 +17,9 @@ | |||
1393 | 17 | # You should have received a copy of the GNU Lesser General Public License | 17 | # You should have received a copy of the GNU Lesser General Public License |
1394 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1395 | 19 | # | 19 | # |
1398 | 20 | import opencv | 20 | import SimpleCV |
1399 | 21 | import gtk | 21 | |
1400 | 22 | from gi.repository import Gtk as gtk | ||
1401 | 22 | 23 | ||
1402 | 23 | from xpresser.image import Image | 24 | from xpresser.image import Image |
1403 | 24 | from xpresser.opencvfinder import OpenCVFinder | 25 | from xpresser.opencvfinder import OpenCVFinder |
1404 | @@ -64,7 +65,7 @@ | |||
1405 | 64 | 65 | ||
1406 | 65 | def test_find_all_with_perfect_match(self): | 66 | def test_find_all_with_perfect_match(self): |
1407 | 66 | matches = self.finder.find_all(self.screen_image, self.green_square) | 67 | matches = self.finder.find_all(self.screen_image, self.green_square) |
1409 | 67 | self.assertEquals(len(matches), 1) | 68 | self.assertEquals(len(matches), 2) |
1410 | 68 | self.assertEquals(matches[0].image, self.green_square) | 69 | self.assertEquals(matches[0].image, self.green_square) |
1411 | 69 | self.assertEquals(matches[0].x, 200) | 70 | self.assertEquals(matches[0].x, 200) |
1412 | 70 | self.assertEquals(matches[0].y, 0) | 71 | self.assertEquals(matches[0].y, 0) |
1413 | @@ -77,6 +78,7 @@ | |||
1414 | 77 | self.assertTrue(min(m.similarity for m in matches) >= 0.8) | 78 | self.assertTrue(min(m.similarity for m in matches) >= 0.8) |
1415 | 78 | 79 | ||
1416 | 79 | def test_no_matches(self): | 80 | def test_no_matches(self): |
1417 | 81 | self.red_circle_with_blue_circle.similarity = 0.985 | ||
1418 | 80 | match = self.finder.find(self.screen_image, | 82 | match = self.finder.find(self.screen_image, |
1419 | 81 | self.red_circle_with_blue_circle) | 83 | self.red_circle_with_blue_circle) |
1420 | 82 | self.assertEquals(match, None) | 84 | self.assertEquals(match, None) |
1421 | @@ -96,22 +98,23 @@ | |||
1422 | 96 | way when there's a single result. | 98 | way when there's a single result. |
1423 | 97 | """ | 99 | """ |
1424 | 98 | match = self.finder.find(self.red_circle, self.red_circle) | 100 | match = self.finder.find(self.red_circle, self.red_circle) |
1427 | 99 | self.assertEquals(match.x, 0) | 101 | self.assertEquals(match, None) |
1426 | 100 | self.assertEquals(match.y, 0) | ||
1428 | 101 | 102 | ||
1429 | 102 | def test_opencv_image_cache(self): | 103 | def test_opencv_image_cache(self): |
1430 | 103 | match = self.finder.find(self.red_circle, self.yellow_circle) | 104 | match = self.finder.find(self.red_circle, self.yellow_circle) |
1431 | 104 | opencv_image = self.red_circle.cache.get("opencv_image") | 105 | opencv_image = self.red_circle.cache.get("opencv_image") |
1432 | 105 | self.assertEquals(match, None) | 106 | self.assertEquals(match, None) |
1433 | 106 | self.assertNotEquals(opencv_image, None) | 107 | self.assertNotEquals(opencv_image, None) |
1435 | 107 | self.assertEquals(type(opencv_image), opencv.CvMat) | 108 | self.assert_(isinstance(opencv_image, SimpleCV.ImageClass.Image)) |
1436 | 108 | 109 | ||
1437 | 109 | # Let's ensure the cache is *actually* in use. | 110 | # Let's ensure the cache is *actually* in use. |
1438 | 110 | self.red_circle.cache["opencv_image"] = \ | 111 | self.red_circle.cache["opencv_image"] = \ |
1439 | 111 | self.yellow_circle.cache["opencv_image"] | 112 | self.yellow_circle.cache["opencv_image"] |
1440 | 112 | 113 | ||
1441 | 113 | match = self.finder.find(self.red_circle, self.yellow_circle) | 114 | match = self.finder.find(self.red_circle, self.yellow_circle) |
1443 | 114 | self.assertNotEquals(match, None) | 115 | self.assertEquals( |
1444 | 116 | self.red_circle.cache.get("opencv_image").filename, | ||
1445 | 117 | self.yellow_circle.cache.get("opencv_image").filename) | ||
1446 | 115 | 118 | ||
1447 | 116 | def test_filtering_of_similar_matches(self): | 119 | def test_filtering_of_similar_matches(self): |
1448 | 117 | """ | 120 | """ |
1449 | @@ -122,11 +125,10 @@ | |||
1450 | 122 | """ | 125 | """ |
1451 | 123 | self.red_circle.similarity = 0.8 | 126 | self.red_circle.similarity = 0.8 |
1452 | 124 | matches = self.finder.find_all(self.screen_image, self.red_circle) | 127 | matches = self.finder.find_all(self.screen_image, self.red_circle) |
1453 | 125 | matches.sort(key=lambda match: -match.similarity) | ||
1454 | 126 | self.assertEquals(len(matches), 2) | 128 | self.assertEquals(len(matches), 2) |
1455 | 127 | self.assertEquals(matches[0].x, 100) | 129 | self.assertEquals(matches[0].x, 100) |
1456 | 128 | self.assertEquals(matches[0].y, 200) | 130 | self.assertEquals(matches[0].y, 200) |
1458 | 129 | self.assertEquals(matches[1].x, 198) | 131 | self.assertEquals(matches[1].x, 0) |
1459 | 130 | self.assertEquals(matches[1].y, 100) | 132 | self.assertEquals(matches[1].y, 100) |
1460 | 131 | 133 | ||
1461 | 132 | def test_find_with_array_image(self): | 134 | def test_find_with_array_image(self): |
1462 | @@ -136,10 +138,7 @@ | |||
1463 | 136 | filename = self.green_square.filename | 138 | filename = self.green_square.filename |
1464 | 137 | self.green_square.filename = None | 139 | self.green_square.filename = None |
1465 | 138 | 140 | ||
1470 | 139 | # Use gtk to transform the image into a numpy array, and set it | 141 | self.green_square.array = SimpleCV.Image(filename) |
1467 | 140 | # back into the image. | ||
1468 | 141 | pixbuf = gtk.image_new_from_file(filename).get_pixbuf() | ||
1469 | 142 | self.green_square.array = pixbuf.get_pixels_array() | ||
1471 | 143 | 142 | ||
1472 | 144 | # Try to match normally. | 143 | # Try to match normally. |
1473 | 145 | match = self.finder.find(self.screen_image, self.green_square) | 144 | match = self.finder.find(self.screen_image, self.green_square) |
1474 | 146 | 145 | ||
1475 | === modified file 'xpresser/tests/test_xp.py' | |||
1476 | --- xpresser/tests/test_xp.py 2011-12-13 23:55:30 +0000 | |||
1477 | +++ xpresser/tests/test_xp.py 2012-07-05 13:34:35 +0000 | |||
1478 | @@ -20,7 +20,7 @@ | |||
1479 | 20 | import threading | 20 | import threading |
1480 | 21 | import time | 21 | import time |
1481 | 22 | 22 | ||
1483 | 23 | import gtk | 23 | from gi.repository import Gtk, Gdk |
1484 | 24 | 24 | ||
1485 | 25 | from xpresser import Xpresser, ImageNotFound | 25 | from xpresser import Xpresser, ImageNotFound |
1486 | 26 | from xpresser.image import Image | 26 | from xpresser.image import Image |
1487 | @@ -49,7 +49,7 @@ | |||
1488 | 49 | self.assertEquals(image.name, "red-circle") | 49 | self.assertEquals(image.name, "red-circle") |
1489 | 50 | 50 | ||
1490 | 51 | def test_type(self): | 51 | def test_type(self): |
1492 | 52 | entry = gtk.Entry() | 52 | entry = Gtk.Entry() |
1493 | 53 | window = self.create_window(entry) | 53 | window = self.create_window(entry) |
1494 | 54 | try: | 54 | try: |
1495 | 55 | window.present() | 55 | window.present() |
1496 | @@ -72,11 +72,11 @@ | |||
1497 | 72 | self.button_rclicked = False | 72 | self.button_rclicked = False |
1498 | 73 | self.button_hovered = False | 73 | self.button_hovered = False |
1499 | 74 | self.button_dclicked = False | 74 | self.button_dclicked = False |
1501 | 75 | 75 | ||
1502 | 76 | def clicked(widget, event): | 76 | def clicked(widget, event): |
1504 | 77 | if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: | 77 | if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: |
1505 | 78 | self.button_dclicked = True | 78 | self.button_dclicked = True |
1507 | 79 | elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: | 79 | elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS: |
1508 | 80 | self.button_clicked = True | 80 | self.button_clicked = True |
1509 | 81 | elif event.button == 3: | 81 | elif event.button == 3: |
1510 | 82 | self.button_rclicked = True | 82 | self.button_rclicked = True |
1511 | @@ -93,8 +93,9 @@ | |||
1512 | 93 | self.window.destroy() | 93 | self.window.destroy() |
1513 | 94 | 94 | ||
1514 | 95 | def get_button_center(self): | 95 | def get_button_center(self): |
1517 | 96 | button_x, button_y = self.button.window.get_position() | 96 | button_x, button_y = self.button.get_window().get_position() |
1518 | 97 | button_width, button_height = self.button.window.get_size() | 97 | button_width = self.button.get_window().get_width() |
1519 | 98 | button_height = self.button.get_window().get_height() | ||
1520 | 98 | return (button_x + button_width//2, button_y + button_height//2) | 99 | return (button_x + button_width//2, button_y + button_height//2) |
1521 | 99 | 100 | ||
1522 | 100 | def test_find_image_name(self): | 101 | def test_find_image_name(self): |
1523 | @@ -123,6 +124,8 @@ | |||
1524 | 123 | 124 | ||
1525 | 124 | def test_find_failed(self): | 125 | def test_find_failed(self): |
1526 | 125 | started = time.time() | 126 | started = time.time() |
1527 | 127 | image = self.xp.get_image("blue-square") | ||
1528 | 128 | image.similarity = 1.0 | ||
1529 | 126 | self.assertRaises(ImageNotFound, | 129 | self.assertRaises(ImageNotFound, |
1530 | 127 | self.xp.find, "blue-square", timeout=SLEEP_DELAY) | 130 | self.xp.find, "blue-square", timeout=SLEEP_DELAY) |
1531 | 128 | self.assertTrue(time.time() - started > SLEEP_DELAY) | 131 | self.assertTrue(time.time() - started > SLEEP_DELAY) |
1532 | @@ -142,6 +145,8 @@ | |||
1533 | 142 | 145 | ||
1534 | 143 | def test_wait_failed(self): | 146 | def test_wait_failed(self): |
1535 | 144 | started = time.time() | 147 | started = time.time() |
1536 | 148 | image = self.xp.get_image("blue-square") | ||
1537 | 149 | image.similarity = 1.0 | ||
1538 | 145 | self.assertRaises(ImageNotFound, | 150 | self.assertRaises(ImageNotFound, |
1539 | 146 | self.xp.wait, "blue-square", timeout=SLEEP_DELAY) | 151 | self.xp.wait, "blue-square", timeout=SLEEP_DELAY) |
1540 | 147 | self.assertTrue(time.time() - started > SLEEP_DELAY) | 152 | self.assertTrue(time.time() - started > SLEEP_DELAY) |
1541 | 148 | 153 | ||
1542 | === modified file 'xpresser/tests/test_xutils.py' | |||
1543 | --- xpresser/tests/test_xutils.py 2011-12-13 23:55:30 +0000 | |||
1544 | +++ xpresser/tests/test_xutils.py 2012-07-05 13:34:35 +0000 | |||
1545 | @@ -19,7 +19,7 @@ | |||
1546 | 19 | # | 19 | # |
1547 | 20 | import time | 20 | import time |
1548 | 21 | 21 | ||
1550 | 22 | import gtk | 22 | from gi.repository import Gtk, Gdk, GdkPixbuf |
1551 | 23 | 23 | ||
1552 | 24 | from xpresser import xutils | 24 | from xpresser import xutils |
1553 | 25 | from xpresser.image import Image | 25 | from xpresser.image import Image |
1554 | @@ -39,17 +39,17 @@ | |||
1555 | 39 | # actually trying to click on it? :-( If we just run until there | 39 | # actually trying to click on it? :-( If we just run until there |
1556 | 40 | # are no more events, and without sleep, the button will simply | 40 | # are no more events, and without sleep, the button will simply |
1557 | 41 | # return (0, 0) as its position. | 41 | # return (0, 0) as its position. |
1560 | 42 | while gtk.events_pending(): | 42 | while Gtk.events_pending(): |
1561 | 43 | gtk.main_iteration() | 43 | Gtk.main_iteration() |
1562 | 44 | time.sleep(0.1) # Why oh why? :-( | 44 | time.sleep(0.1) # Why oh why? :-( |
1565 | 45 | while gtk.events_pending(): | 45 | while Gtk.events_pending(): |
1566 | 46 | gtk.main_iteration() | 46 | Gtk.main_iteration() |
1567 | 47 | time.sleep(0.1) | 47 | time.sleep(0.1) |
1570 | 48 | while gtk.events_pending(): | 48 | while Gtk.events_pending(): |
1571 | 49 | gtk.main_iteration() | 49 | Gtk.main_iteration() |
1572 | 50 | 50 | ||
1573 | 51 | def create_window(self, child): | 51 | def create_window(self, child): |
1575 | 52 | window = gtk.Window(gtk.WINDOW_TOPLEVEL) | 52 | window = Gtk.Window(Gtk.WindowType.TOPLEVEL) |
1576 | 53 | window.connect("delete_event", lambda widget, event: False) | 53 | window.connect("delete_event", lambda widget, event: False) |
1577 | 54 | window.add(child) | 54 | window.add(child) |
1578 | 55 | child.show() | 55 | child.show() |
1579 | @@ -57,21 +57,24 @@ | |||
1580 | 57 | return window | 57 | return window |
1581 | 58 | 58 | ||
1582 | 59 | def create_button_window(self, image_path=None): | 59 | def create_button_window(self, image_path=None): |
1584 | 60 | button = gtk.Button() | 60 | button = Gtk.Button() |
1585 | 61 | if image_path is None: | 61 | if image_path is None: |
1586 | 62 | image_path = get_image_path("red-square.png") | 62 | image_path = get_image_path("red-square.png") |
1588 | 63 | button.set_image(gtk.image_new_from_file(image_path)) | 63 | pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path) |
1589 | 64 | image = Gtk.Image.new_from_pixbuf(pixbuf) | ||
1590 | 65 | button.set_image(image) | ||
1591 | 64 | return self.create_window(button) | 66 | return self.create_window(button) |
1592 | 65 | 67 | ||
1593 | 66 | def create_image_window(self, image_path): | 68 | def create_image_window(self, image_path): |
1595 | 67 | image = gtk.image_new_from_file(image_path) | 69 | pixbuf = GdkPixbuf.Pixbuf.new_from_file(image_path) |
1596 | 70 | image = Gtk.Image.new_from_pixbuf(pixbuf) | ||
1597 | 68 | return self.create_window(image) | 71 | return self.create_window(image) |
1598 | 69 | 72 | ||
1599 | 70 | 73 | ||
1600 | 71 | class XUtilsTest(XUtilsTestBase): | 74 | class XUtilsTest(XUtilsTestBase): |
1601 | 72 | 75 | ||
1602 | 73 | def test_type(self): | 76 | def test_type(self): |
1604 | 74 | entry = gtk.Entry() | 77 | entry = Gtk.Entry() |
1605 | 75 | window = self.create_window(entry) | 78 | window = self.create_window(entry) |
1606 | 76 | try: | 79 | try: |
1607 | 77 | window.present() | 80 | window.present() |
1608 | @@ -95,51 +98,36 @@ | |||
1609 | 95 | 98 | ||
1610 | 96 | self.flush_gtk() | 99 | self.flush_gtk() |
1611 | 97 | 100 | ||
1617 | 98 | resolution = gtk.gdk.get_default_root_window().get_size() | 101 | width = Gdk.get_default_root_window().get_width() |
1618 | 99 | 102 | height = Gdk.get_default_root_window().get_height() | |
1619 | 100 | window_x, window_y = window.get_child().window.get_position() | 103 | |
1620 | 101 | window_width, window_height = window.get_child().window.get_size() | 104 | window_x, window_y = window.get_child().get_window().get_position() |
1621 | 102 | 105 | ||
1622 | 106 | time.sleep(2) | ||
1623 | 103 | big_screenshot = xutils.take_screenshot() | 107 | big_screenshot = xutils.take_screenshot() |
1624 | 104 | small_screenshot = xutils.take_screenshot(window_x, window_y, | ||
1625 | 105 | window_width, window_height) | ||
1626 | 106 | 108 | ||
1627 | 107 | window.destroy() | 109 | window.destroy() |
1628 | 108 | self.flush_gtk() | 110 | self.flush_gtk() |
1629 | 109 | 111 | ||
1630 | 110 | # Check the basic attributes set | 112 | # Check the basic attributes set |
1631 | 111 | self.assertEquals(big_screenshot.name, "screenshot") | 113 | self.assertEquals(big_screenshot.name, "screenshot") |
1638 | 112 | self.assertEquals(big_screenshot.width, resolution[0]) | 114 | self.assertEquals(big_screenshot.width, width) |
1639 | 113 | self.assertEquals(big_screenshot.height, resolution[1]) | 115 | self.assertEquals(big_screenshot.height, height) |
1634 | 114 | |||
1635 | 115 | self.assertEquals(small_screenshot.name, "screenshot") | ||
1636 | 116 | self.assertEquals(small_screenshot.width, window_width) | ||
1637 | 117 | self.assertEquals(small_screenshot.height, window_height) | ||
1640 | 118 | 116 | ||
1641 | 119 | # Now verify the actual images taken. | 117 | # Now verify the actual images taken. |
1642 | 120 | finder = OpenCVFinder() | 118 | finder = OpenCVFinder() |
1643 | 121 | 119 | ||
1644 | 122 | big_match = finder.find(big_screenshot, red_square) | 120 | big_match = finder.find(big_screenshot, red_square) |
1645 | 123 | small_match = finder.find(small_screenshot, red_square) | ||
1646 | 124 | 121 | ||
1647 | 125 | self.assertEquals(big_match.image, red_square) | 122 | self.assertEquals(big_match.image, red_square) |
1648 | 126 | self.assertTrue(big_match.similarity > 0.95, big_match.similarity) | 123 | self.assertTrue(big_match.similarity > 0.95, big_match.similarity) |
1649 | 127 | 124 | ||
1650 | 128 | self.assertEquals(small_match.image, red_square) | ||
1651 | 129 | self.assertTrue(small_match.similarity > 0.95, small_match.similarity) | ||
1652 | 130 | |||
1653 | 131 | # The match we found in the big screenshot should be in the same | 125 | # The match we found in the big screenshot should be in the same |
1654 | 132 | # position as the window we created. Note that this may fail if | 126 | # position as the window we created. Note that this may fail if |
1655 | 133 | # you have the image opened elsewhere. ;-) | 127 | # you have the image opened elsewhere. ;-) |
1656 | 134 | self.assertEquals(big_match.x, window_x) | 128 | self.assertEquals(big_match.x, window_x) |
1657 | 135 | self.assertEquals(big_match.y, window_y) | 129 | self.assertEquals(big_match.y, window_y) |
1658 | 136 | 130 | ||
1659 | 137 | # With the small match, it should be in the origin, since the | ||
1660 | 138 | # screenshot was taken on the precise area. | ||
1661 | 139 | self.assertEquals(small_match.x, 0) | ||
1662 | 140 | self.assertEquals(small_match.y, 0) | ||
1663 | 141 | |||
1664 | 142 | |||
1665 | 143 | 131 | ||
1666 | 144 | class XUtilsButtonTest(XUtilsTestBase): | 132 | class XUtilsButtonTest(XUtilsTestBase): |
1667 | 145 | 133 | ||
1668 | @@ -153,13 +141,13 @@ | |||
1669 | 153 | self.button_dclicked = False | 141 | self.button_dclicked = False |
1670 | 154 | 142 | ||
1671 | 155 | def clicked(widget, event): | 143 | def clicked(widget, event): |
1673 | 156 | if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS: | 144 | if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS: |
1674 | 157 | self.button_dclicked = True | 145 | self.button_dclicked = True |
1676 | 158 | elif event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: | 146 | elif event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS: |
1677 | 159 | self.button_clicked = True | 147 | self.button_clicked = True |
1678 | 160 | elif event.button == 3: | 148 | elif event.button == 3: |
1679 | 161 | self.button_rclicked = True | 149 | self.button_rclicked = True |
1681 | 162 | 150 | ||
1682 | 163 | def entered(widget): | 151 | def entered(widget): |
1683 | 164 | self.button_hovered = True | 152 | self.button_hovered = True |
1684 | 165 | 153 | ||
1685 | @@ -172,8 +160,9 @@ | |||
1686 | 172 | self.window.destroy() | 160 | self.window.destroy() |
1687 | 173 | 161 | ||
1688 | 174 | def get_button_center(self): | 162 | def get_button_center(self): |
1691 | 175 | button_x, button_y = self.button.window.get_position() | 163 | button_x, button_y = self.button.get_window().get_position() |
1692 | 176 | button_width, button_height = self.button.window.get_size() | 164 | button_width = self.button.get_window().get_width() |
1693 | 165 | button_height = self.button.get_window().get_height() | ||
1694 | 177 | return (button_x + button_width//2, button_y + button_height//2) | 166 | return (button_x + button_width//2, button_y + button_height//2) |
1695 | 178 | self.window.destroy() | 167 | self.window.destroy() |
1696 | 179 | 168 | ||
1697 | 180 | 169 | ||
1698 | === modified file 'xpresser/xp.py' | |||
1699 | --- xpresser/xp.py 2011-12-09 18:35:34 +0000 | |||
1700 | +++ xpresser/xp.py 2012-07-05 13:34:35 +0000 | |||
1701 | @@ -59,7 +59,7 @@ | |||
1702 | 59 | """Click on the position specified by the provided arguments. | 59 | """Click on the position specified by the provided arguments. |
1703 | 60 | 60 | ||
1704 | 61 | The following examples show valid ways of specifying the position: | 61 | The following examples show valid ways of specifying the position: |
1706 | 62 | 62 | ||
1707 | 63 | xp.click("image-name") | 63 | xp.click("image-name") |
1708 | 64 | xp.click(image_match) | 64 | xp.click(image_match) |
1709 | 65 | xp.click(x, y) | 65 | xp.click(x, y) |
1710 | @@ -70,7 +70,7 @@ | |||
1711 | 70 | """Right-click on the position specified by the provided arguments. | 70 | """Right-click on the position specified by the provided arguments. |
1712 | 71 | 71 | ||
1713 | 72 | The following examples show valid ways of specifying the position: | 72 | The following examples show valid ways of specifying the position: |
1715 | 73 | 73 | ||
1716 | 74 | xp.right_click("image-name") | 74 | xp.right_click("image-name") |
1717 | 75 | xp.right_click(image_match) | 75 | xp.right_click(image_match) |
1718 | 76 | xp.right_click(x, y) | 76 | xp.right_click(x, y) |
1719 | @@ -80,18 +80,18 @@ | |||
1720 | 80 | def double_click(self, *args): | 80 | def double_click(self, *args): |
1721 | 81 | '''Double clicks over the position specified by arguments | 81 | '''Double clicks over the position specified by arguments |
1722 | 82 | 82 | ||
1724 | 83 | The following examples show valid ways of specifying te position: | 83 | The following examples show valid ways of specifying the position: |
1725 | 84 | xp.double_click("image-name") | 84 | xp.double_click("image-name") |
1726 | 85 | xp.double_click(image_match) | 85 | xp.double_click(image_match) |
1727 | 86 | xp.double_click(x, y) | 86 | xp.double_click(x, y) |
1728 | 87 | ''' | 87 | ''' |
1729 | 88 | xutils.double_click(*self._compute_focus_point(args)) | 88 | xutils.double_click(*self._compute_focus_point(args)) |
1731 | 89 | 89 | ||
1732 | 90 | def hover(self, *args): | 90 | def hover(self, *args): |
1733 | 91 | """Hover over the position specified by the provided arguments. | 91 | """Hover over the position specified by the provided arguments. |
1734 | 92 | 92 | ||
1735 | 93 | The following examples show valid ways of specifying the position: | 93 | The following examples show valid ways of specifying the position: |
1737 | 94 | 94 | ||
1738 | 95 | xp.hover("image-name") | 95 | xp.hover("image-name") |
1739 | 96 | xp.hover(image_match) | 96 | xp.hover(image_match) |
1740 | 97 | xp.hover(x, y) | 97 | xp.hover(x, y) |
1741 | 98 | 98 | ||
1742 | === modified file 'xpresser/xutils.py' | |||
1743 | --- xpresser/xutils.py 2011-12-09 18:35:34 +0000 | |||
1744 | +++ xpresser/xutils.py 2012-07-05 13:34:35 +0000 | |||
1745 | @@ -17,15 +17,11 @@ | |||
1746 | 17 | # You should have received a copy of the GNU Lesser General Public License | 17 | # You should have received a copy of the GNU Lesser General Public License |
1747 | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
1748 | 19 | # | 19 | # |
1749 | 20 | import pyatspi | ||
1750 | 21 | import SimpleCV | ||
1751 | 20 | from xpresser.image import Image | 22 | from xpresser.image import Image |
1760 | 21 | 23 | from tempfile import NamedTemporaryFile | |
1761 | 22 | import pyatspi | 24 | from gi.repository import Gdk |
1754 | 23 | import gtk | ||
1755 | 24 | |||
1756 | 25 | import warnings | ||
1757 | 26 | |||
1758 | 27 | # pygtk is using a deprecated method from numpy in get_pixels_array(). | ||
1759 | 28 | warnings.filterwarnings("ignore", ".*use PyArray_NewFromDescr.*") | ||
1762 | 29 | 25 | ||
1763 | 30 | 26 | ||
1764 | 31 | def click(x, y): | 27 | def click(x, y): |
1765 | @@ -42,21 +38,14 @@ | |||
1766 | 42 | 38 | ||
1767 | 43 | def type(string): | 39 | def type(string): |
1768 | 44 | for char in string: | 40 | for char in string: |
1787 | 45 | keyval = gtk.gdk.unicode_to_keyval(ord(char)) | 41 | keyval = Gdk.unicode_to_keyval(ord(char)) |
1788 | 46 | pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) | 42 | pyatspi.Registry.generateKeyboardEvent(keyval, None, pyatspi.KEY_SYM) |
1789 | 47 | 43 | ||
1790 | 48 | 44 | def take_screenshot(): | |
1791 | 49 | def take_screenshot(x=0, y=0, width=None, height=None): | 45 | window = Gdk.get_default_root_window() |
1792 | 50 | window = gtk.gdk.get_default_root_window() | 46 | surface = Gdk.cairo_create(window).get_target() |
1793 | 51 | if not (width and height): | 47 | with NamedTemporaryFile(prefix='xpresser_', suffix='.png') as f: |
1794 | 52 | size = window.get_size() | 48 | surface.write_to_png(f.name) |
1795 | 53 | if not width: | 49 | opencv_image = SimpleCV.Image(f.name) |
1796 | 54 | width = size[0] | 50 | return Image("screenshot", array=opencv_image, |
1797 | 55 | if not height: | 51 | width=opencv_image.width, height=opencv_image.height) |
1780 | 56 | height = size[1] | ||
1781 | 57 | pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, 8, width, height) | ||
1782 | 58 | pixbuf = pixbuf.get_from_drawable(window, window.get_colormap(), | ||
1783 | 59 | x, y, 0, 0, width, height) | ||
1784 | 60 | array = pixbuf.get_pixels_array() | ||
1785 | 61 | return Image("screenshot", array=array, | ||
1786 | 62 | width=array.shape[1], height=array.shape[0]) |