Merge lp:~mvo/software-center/appdetailsview-cleanup into lp:software-center
- appdetailsview-cleanup
- Merge into trunk
Proposed by
Michael Vogt
Status: | Merged |
---|---|
Merged at revision: | 2576 |
Proposed branch: | lp:~mvo/software-center/appdetailsview-cleanup |
Merge into: | lp:software-center |
Diff against target: |
1039 lines (+359/-95) 7 files modified
po/POTFILES.in (+0/-1) softwarecenter/ui/gtk3/panes/softwarepane.py (+1/-3) softwarecenter/ui/gtk3/views/appdetailsview.py (+234/-81) test/gen-coverage-for-single-test.py (+4/-0) test/gtk3/test_appdetailsview.py (+118/-8) test/gtk3/test_install_progress.py (+1/-1) test/gtk3/test_views.py (+1/-1) |
To merge this branch: | bzr merge lp:~mvo/software-center/appdetailsview-cleanup |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary Lasker (community) | Approve | ||
Michael Vogt | Pending | ||
Review via email:
|
Commit message
Description of the change
This branch merges appdetailsview and appdetailsview_gtk. It also adds more test coverage for the detailsview.
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 | === modified file 'po/POTFILES.in' |
2 | --- po/POTFILES.in 2011-11-07 09:41:23 +0000 |
3 | +++ po/POTFILES.in 2011-11-29 19:57:26 +0000 |
4 | @@ -47,7 +47,6 @@ |
5 | softwarecenter/ui/gtk3/panes/unused__channelpane.py |
6 | softwarecenter/ui/gtk3/panes/viewswitcher.py |
7 | softwarecenter/ui/gtk3/views/appdetailsview.py |
8 | -softwarecenter/ui/gtk3/views/appdetailsview_gtk.py |
9 | softwarecenter/ui/gtk3/views/appview.py |
10 | softwarecenter/ui/gtk3/views/catview_gtk.py |
11 | softwarecenter/ui/gtk3/views/purchaseview.py |
12 | |
13 | === modified file 'softwarecenter/ui/gtk3/panes/softwarepane.py' |
14 | --- softwarecenter/ui/gtk3/panes/softwarepane.py 2011-10-25 18:38:08 +0000 |
15 | +++ softwarecenter/ui/gtk3/panes/softwarepane.py 2011-11-29 19:57:26 +0000 |
16 | @@ -51,9 +51,7 @@ |
17 | from softwarecenter.ui.gtk3.widgets.searchaid import SearchAid |
18 | |
19 | from softwarecenter.ui.gtk3.views.appview import AppView |
20 | -from softwarecenter.ui.gtk3.views.appdetailsview_gtk import ( |
21 | - AppDetailsViewGtk as |
22 | - AppDetailsView) |
23 | +from softwarecenter.ui.gtk3.views.appdetailsview import AppDetailsView |
24 | |
25 | from softwarecenter.utils import is_no_display_desktop_file |
26 | |
27 | |
28 | === removed file 'softwarecenter/ui/gtk3/views/appdetailsview.py' |
29 | --- softwarecenter/ui/gtk3/views/appdetailsview.py 2011-11-09 10:09:16 +0000 |
30 | +++ softwarecenter/ui/gtk3/views/appdetailsview.py 1970-01-01 00:00:00 +0000 |
31 | @@ -1,139 +0,0 @@ |
32 | -# Copyright (C) 2009 Canonical |
33 | -# |
34 | -# Authors: |
35 | -# Michael Vogt |
36 | -# |
37 | -# This program is free software; you can redistribute it and/or modify it under |
38 | -# the terms of the GNU General Public License as published by the Free Software |
39 | -# Foundation; version 3. |
40 | -# |
41 | -# This program is distributed in the hope that it will be useful, but WITHOUT |
42 | -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
43 | -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
44 | -# details. |
45 | -# |
46 | -# You should have received a copy of the GNU General Public License along with |
47 | -# this program; if not, write to the Free Software Foundation, Inc., |
48 | -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
49 | - |
50 | -import logging |
51 | -import softwarecenter.ui.gtk3.dialogs as dialogs |
52 | - |
53 | -from gettext import gettext as _ |
54 | - |
55 | -from softwarecenter.db.application import AppDetails |
56 | -from softwarecenter.backend.reviews import get_review_loader |
57 | -from softwarecenter.backend import get_install_backend |
58 | - |
59 | - |
60 | -LOG=logging.getLogger(__name__) |
61 | - |
62 | -class AppDetailsViewBase(object): |
63 | - |
64 | - def __init__(self, db, distro, icons, cache, datadir): |
65 | - self.db = db |
66 | - self.distro = distro |
67 | - self.icons = icons |
68 | - self.cache = cache |
69 | - self.backend = get_install_backend() |
70 | - self.cache.connect("cache-ready", self._on_cache_ready) |
71 | - self.datadir = datadir |
72 | - self.app = None |
73 | - self.appdetails = None |
74 | - self.addons_to_install = [] |
75 | - self.addons_to_remove = [] |
76 | - # reviews |
77 | - self.review_loader = get_review_loader(self.cache, self.db) |
78 | - # aptdaemon |
79 | - |
80 | - def _draw(self): |
81 | - """ draw the current app into the window, maybe the function |
82 | - you need to overwrite |
83 | - """ |
84 | - pass |
85 | - |
86 | - # public API |
87 | - def show_app(self, app): |
88 | - """ show the given application """ |
89 | - if app is None: |
90 | - return |
91 | - self.app = app |
92 | - self.appdetails = AppDetails(self.db, application=app) |
93 | - #print "AppDetailsViewWebkit:" |
94 | - #print self.appdetails |
95 | - self._draw() |
96 | - self._check_for_reviews() |
97 | - self.emit("selected", self.app) |
98 | - |
99 | - def refresh_app(self): |
100 | - self.show_app(self.app) |
101 | - |
102 | - # common code |
103 | - def _review_write_new(self): |
104 | - if (not self.app or |
105 | - not self.app.pkgname in self.cache or |
106 | - not self.cache[self.app.pkgname].candidate): |
107 | - dialogs.error(None, |
108 | - _("Version unknown"), |
109 | - _("The version of the application can not " |
110 | - "be detected. Entering a review is not " |
111 | - "possible.")) |
112 | - return |
113 | - # gather data |
114 | - pkg = self.cache[self.app.pkgname] |
115 | - version = pkg.candidate.version |
116 | - origin = self.cache.get_origin(self.app.pkgname) |
117 | - |
118 | - # FIXME: probably want to not display the ui if we can't review it |
119 | - if not origin: |
120 | - dialogs.error(None, |
121 | - _("Origin unknown"), |
122 | - _("The origin of the application can not " |
123 | - "be detected. Entering a review is not " |
124 | - "possible.")) |
125 | - return |
126 | - |
127 | - if pkg.installed: |
128 | - version = pkg.installed.version |
129 | - # call the loader to do call out the right helper and collect the result |
130 | - parent_xid = '' |
131 | - #parent_xid = get_parent_xid(self) |
132 | - self.review_loader.spawn_write_new_review_ui( |
133 | - self.app, version, self.appdetails.icon, origin, |
134 | - parent_xid, self.datadir, |
135 | - self._reviews_ready_callback) |
136 | - |
137 | - def _review_report_abuse(self, review_id): |
138 | - parent_xid = '' |
139 | - #parent_xid = get_parent_xid(self) |
140 | - self.review_loader.spawn_report_abuse_ui( |
141 | - review_id, parent_xid, self.datadir, self._reviews_ready_callback) |
142 | - |
143 | - def _review_submit_usefulness(self, review_id, is_useful): |
144 | - parent_xid = '' |
145 | - #parent_xid = get_parent_xid(self) |
146 | - self.review_loader.spawn_submit_usefulness_ui( |
147 | - review_id, is_useful, parent_xid, self.datadir, |
148 | - self._reviews_ready_callback) |
149 | - |
150 | - def _review_modify(self, review_id): |
151 | - parent_xid = '' |
152 | - #parent_xid = get_parent_xid(self) |
153 | - self.review_loader.spawn_modify_review_ui( |
154 | - parent_xid, self.appdetails.icon, self.datadir, review_id, |
155 | - self._reviews_ready_callback) |
156 | - |
157 | - def _review_delete(self, review_id): |
158 | - parent_xid = '' |
159 | - #parent_xid = get_parent_xid(self) |
160 | - self.review_loader.spawn_delete_review_ui( |
161 | - review_id, parent_xid, self.datadir, self._reviews_ready_callback) |
162 | - |
163 | - # internal callbacks |
164 | - def _on_cache_ready(self, cache): |
165 | - # re-show the application if the cache changes, it may affect the |
166 | - # current application |
167 | - LOG.debug("on_cache_ready") |
168 | - self.show_app(self.app) |
169 | - |
170 | - |
171 | |
172 | === renamed file 'softwarecenter/ui/gtk3/views/appdetailsview_gtk.py' => 'softwarecenter/ui/gtk3/views/appdetailsview.py' |
173 | --- softwarecenter/ui/gtk3/views/appdetailsview_gtk.py 2011-11-11 03:05:45 +0000 |
174 | +++ softwarecenter/ui/gtk3/views/appdetailsview.py 2011-11-29 19:57:26 +0000 |
175 | @@ -1,7 +1,8 @@ |
176 | # -*- coding: utf-8 -*- |
177 | -# Copyright (C) 2009 Canonical |
178 | +# Copyright (C) 2009-2011 Canonical |
179 | # |
180 | # Authors: |
181 | +# Matthew McGowan |
182 | # Michael Vogt |
183 | # |
184 | # This program is free software; you can redistribute it and/or modify it under |
185 | @@ -17,8 +18,6 @@ |
186 | # this program; if not, write to the Free Software Foundation, Inc., |
187 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
188 | |
189 | -import gi |
190 | -gi.require_version("Gtk", "3.0") |
191 | from gi.repository import Atk, Gtk, Gdk, GObject, GdkPixbuf, Pango |
192 | |
193 | import datetime |
194 | @@ -49,7 +48,6 @@ |
195 | from softwarecenter.distro import get_distro |
196 | from softwarecenter.backend.weblive import get_weblive_backend |
197 | from softwarecenter.ui.gtk3.dialogs import error |
198 | -from appdetailsview import AppDetailsViewBase |
199 | |
200 | from softwarecenter.ui.gtk3.em import StockEms, em |
201 | from softwarecenter.ui.gtk3.drawing import color_to_hex |
202 | @@ -65,13 +63,21 @@ |
203 | ShowWebLiveServerChooserDialog) |
204 | from softwarecenter.ui.gtk3.gmenusearch import GMenuSearcher |
205 | |
206 | -LOG = logging.getLogger(__name__) |
207 | - |
208 | +import softwarecenter.ui.gtk3.dialogs as dialogs |
209 | + |
210 | +from softwarecenter.backend.reviews import get_review_loader |
211 | +from softwarecenter.backend import get_install_backend |
212 | + |
213 | + |
214 | +LOG=logging.getLogger(__name__) |
215 | |
216 | class StatusBar(Gtk.Alignment): |
217 | + """ Subclass of Gtk.Alignment that draws a small dash border |
218 | + around the rectangle. |
219 | + """ |
220 | |
221 | def __init__(self, view): |
222 | - GObject.GObject.__init__(self, xscale=1.0, yscale=1.0) |
223 | + Gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) |
224 | self.set_padding(StockEms.SMALL, StockEms.SMALL, 0, 0) |
225 | |
226 | self.hbox = Gtk.HBox() |
227 | @@ -114,11 +120,15 @@ |
228 | cr.stroke() |
229 | |
230 | cr.restore() |
231 | - for child in self: self.propagate_draw(child, cr) |
232 | + for child in self: |
233 | + self.propagate_draw(child, cr) |
234 | |
235 | |
236 | class PackageStatusBar(StatusBar): |
237 | - |
238 | + """ Package specific status bar that contains a state label, |
239 | + a action button and a progress bar. |
240 | + """ |
241 | + |
242 | def __init__(self, view): |
243 | StatusBar.__init__(self, view) |
244 | self.installed_icon = Gtk.Image.new_from_icon_name( |
245 | @@ -139,6 +149,8 @@ |
246 | self.hbox.pack_end(self.progress, False, False, 0) |
247 | self.show_all() |
248 | |
249 | + self.app_manager = get_appmanager() |
250 | + |
251 | self.button.connect('clicked', self._on_button_clicked) |
252 | GObject.timeout_add(500, self._pulse_helper) |
253 | |
254 | @@ -152,9 +164,9 @@ |
255 | button.set_sensitive(False) |
256 | state = self.pkg_state |
257 | app = self.view.app |
258 | - app_manager = get_appmanager() |
259 | addons_to_install = self.view.addons_manager.addons_to_install |
260 | addons_to_remove = self.view.addons_manager.addons_to_remove |
261 | + app_manager = self.app_manager |
262 | if state == PkgStates.INSTALLED: |
263 | app_manager.remove( |
264 | app, addons_to_install, addons_to_remove) |
265 | @@ -204,6 +216,7 @@ |
266 | self.button.show() |
267 | self.show() |
268 | else: |
269 | + # mvo: why do we override state here again? |
270 | state = app_details.pkg_state |
271 | self.pkg_state = state |
272 | self.button.set_sensitive(True) |
273 | @@ -212,9 +225,10 @@ |
274 | self.progress.hide() |
275 | self.installed_icon.hide() |
276 | |
277 | - # FIXME: Use a Gtk.Action for the Install/Remove/Buy/Add Source/Update Now action |
278 | - # so that all UI controls (menu item, applist view button and appdetails |
279 | - # view button) are managed centrally: button text, button sensitivity, |
280 | + # FIXME: Use a Gtk.Action for the Install/Remove/Buy/Add |
281 | + # Source/Update Now action so that all UI controls |
282 | + # (menu item, applist view button and appdetails view button) |
283 | + # are managed centrally: button text, button sensitivity, |
284 | # and the associated callback. |
285 | if state == PkgStates.INSTALLING: |
286 | self.set_label(_('Installing...')) |
287 | @@ -236,18 +250,23 @@ |
288 | self.set_label(_("Installed (you're using it right now)")) |
289 | else: |
290 | if app_details.purchase_date: |
291 | - # purchase_date is a string, must first convert to datetime.datetime |
292 | - pdate = self._convert_purchase_date_str_to_datetime(app_details.purchase_date) |
293 | - # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, please specify a format per your |
294 | - # locale (if you prefer, %x can be used to provide a default locale-specific date |
295 | + # purchase_date is a string, must first convert to |
296 | + # datetime.datetime |
297 | + pdate = self._convert_purchase_date_str_to_datetime( |
298 | + app_details.purchase_date) |
299 | + # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, |
300 | + # please specify a format per your locale (if you prefer, |
301 | + # %x can be used to provide a default locale-specific date |
302 | # representation) |
303 | self.set_label(pdate.strftime(_('Purchased on %Y-%m-%d'))) |
304 | elif app_details.installation_date: |
305 | - # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, please specify a format per your |
306 | - # locale (if you prefer, %x can be used to provide a default locale-specific date |
307 | + # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, |
308 | + # please specify a format per your locale (if you prefer, |
309 | + # %x can be used to provide a default locale-specific date |
310 | # representation) |
311 | template = _('Installed on %Y-%m-%d') |
312 | - self.set_label(app_details.installation_date.strftime(template)) |
313 | + self.set_label(app_details.installation_date.strftime( |
314 | + template)) |
315 | else: |
316 | self.set_label(_('Installed')) |
317 | if state == PkgStates.REINSTALLABLE: # only deb files atm |
318 | @@ -256,22 +275,26 @@ |
319 | self.set_button_label(_('Remove')) |
320 | elif state == PkgStates.NEEDS_PURCHASE: |
321 | # FIXME: need to determine the currency dynamically once we can |
322 | - # get that info from the software-center-agent/payments service. |
323 | - # NOTE: the currency string for this label is purposely not translatable |
324 | - # when hardcoded, since it (currently) won't vary based on locale |
325 | - # and as such we don't want it translated |
326 | + # get that info from the software-center-agent/payments |
327 | + # service. |
328 | + # NOTE: the currency string for this label is purposely not |
329 | + # translatable when hardcoded, since it (currently) |
330 | + # won't vary based on locale and as such we don't want |
331 | + # it translated |
332 | self.set_label("US$ %s" % app_details.price) |
333 | self.set_button_label(_(u'Buy\u2026')) |
334 | elif state == PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED: |
335 | # purchase_date is a string, must first convert to datetime.datetime |
336 | - pdate = self._convert_purchase_date_str_to_datetime(app_details.purchase_date) |
337 | - # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, please specify a format per your |
338 | - # locale (if you prefer, %x can be used to provide a default locale-specific date |
339 | - # representation) |
340 | + pdate = self._convert_purchase_date_str_to_datetime( |
341 | + app_details.purchase_date) |
342 | + # TRANSLATORS : %Y-%m-%d formats the date as 2011-03-31, please |
343 | + # specify a format per your locale (if you prefer, %x can be used |
344 | + # to provide a default locale-specific date representation) |
345 | self.set_label(pdate.strftime(_('Purchased on %Y-%m-%d'))) |
346 | self.set_button_label(_('Install')) |
347 | elif state == PkgStates.UNINSTALLED: |
348 | - #special label only if the app being viewed is software centre itself |
349 | + #special label only if the app being viewed is software centre |
350 | + # itself |
351 | if app_details.pkgname== SOFTWARE_CENTER_PKGNAME: |
352 | self.set_label(_("Removed (close it and it'll be gone)")) |
353 | else: |
354 | @@ -296,8 +319,9 @@ |
355 | self.set_button_label(_("Install")) |
356 | self.set_label("") |
357 | elif state == PkgStates.NOT_FOUND: |
358 | - # this is used when the pkg is not in the cache and there is no request |
359 | - # we display the error in the summary field and hide the rest |
360 | + # this is used when the pkg is not in the cache and there is no |
361 | + # request we display the error in the summary field and hide the |
362 | + # rest |
363 | pass |
364 | elif state == PkgStates.NEEDS_SOURCE: |
365 | channelfile = self.app_details.channelfile |
366 | @@ -324,13 +348,16 @@ |
367 | |
368 | def _convert_purchase_date_str_to_datetime(self, purchase_date): |
369 | if purchase_date is not None: |
370 | - return datetime.datetime.strptime(purchase_date, "%Y-%m-%d %H:%M:%S") |
371 | + return datetime.datetime.strptime( |
372 | + purchase_date, "%Y-%m-%d %H:%M:%S") |
373 | |
374 | |
375 | class PackageInfo(Gtk.HBox): |
376 | + """ Box with labels for package specific information like version info |
377 | + """ |
378 | |
379 | def __init__(self, key, info_keys): |
380 | - GObject.GObject.__init__(self) |
381 | + Gtk.HBox.__init__(self) |
382 | self.set_spacing(StockEms.LARGE) |
383 | |
384 | self.key = key |
385 | @@ -390,7 +417,7 @@ |
386 | """ Widget to select addons: CheckButton - Icon - Title (pkgname) """ |
387 | |
388 | def __init__(self, db, icons, pkgname): |
389 | - GObject.GObject.__init__(self) |
390 | + Gtk.HBox.__init__(self) |
391 | self.set_spacing(StockEms.SMALL) |
392 | self.set_border_width(2) |
393 | |
394 | @@ -434,13 +461,14 @@ |
395 | color = color_to_hex(context.get_color(Gtk.StateFlags.NORMAL)) |
396 | context.restore() |
397 | |
398 | - self.title.set_markup(title + ' <span color="%s">(%s)</span>' % (color, pkgname)) |
399 | + self.title.set_markup( |
400 | + title + ' <span color="%s">(%s)</span>' % (color, pkgname)) |
401 | self.title.set_alignment(0.0, 0.5) |
402 | self.title.set_line_wrap(True) |
403 | self.title.set_ellipsize(Pango.EllipsizeMode.END) |
404 | hbox.pack_start(self.title, False, False, 0) |
405 | |
406 | - loader = self.get_ancestor(AppDetailsViewGtk).review_loader |
407 | + loader = self.get_ancestor(AppDetailsView).review_loader |
408 | stats = loader.get_review_stats(self.app) |
409 | if stats != None: |
410 | rating = Star() |
411 | @@ -471,7 +499,7 @@ |
412 | } |
413 | |
414 | def __init__(self, addons_manager): |
415 | - GObject.GObject.__init__(self) |
416 | + Gtk.VBox.__init__(self) |
417 | self.set_spacing(12) |
418 | |
419 | self.addons_manager = addons_manager |
420 | @@ -532,6 +560,10 @@ |
421 | |
422 | |
423 | class AddonsStatusBar(StatusBar): |
424 | + """ Statusbar for the addons. |
425 | + This will become visible if any addons are scheduled for install |
426 | + or remove. |
427 | + """ |
428 | |
429 | def __init__(self, addons_manager): |
430 | StatusBar.__init__(self, addons_manager.view) |
431 | @@ -576,10 +608,19 @@ |
432 | self.view.addons_to_remove = self.addons_manager.addons_to_remove |
433 | LOG.debug("ApplyButtonClicked: inst=%s rm=%s" % ( |
434 | self.view.addons_to_install, self.view.addons_to_remove)) |
435 | - AppDetailsViewBase.apply_changes(self.view) |
436 | - |
437 | - |
438 | -class AddonsManager(): |
439 | + # apply |
440 | + app_manager = get_appmanager() |
441 | + app_manager.apply_changes(self.view.app, |
442 | + self.view.addons_to_install, |
443 | + self.view.addons_to_remove) |
444 | + |
445 | + |
446 | +class AddonsManager(object): |
447 | + """ Addons manager component. |
448 | + This component deals with keeping track of what is marked |
449 | + for install or removal. |
450 | + """ |
451 | + |
452 | def __init__(self, view): |
453 | self.view = view |
454 | |
455 | @@ -627,8 +668,7 @@ |
456 | |
457 | |
458 | _asset_cache = {} |
459 | -class AppDetailsViewGtk(Viewport, AppDetailsViewBase): |
460 | - |
461 | +class AppDetailsView(Viewport): |
462 | """ The view that shows the application details """ |
463 | |
464 | # the size of the icon on the left side |
465 | @@ -638,7 +678,8 @@ |
466 | "ui/gtk3/art/itemview-background.png") |
467 | |
468 | |
469 | - # need to include application-request-action here also since we are multiple-inheriting |
470 | + # need to include application-request-action here also since we are |
471 | + # multiple-inheriting |
472 | __gsignals__ = {'selected':(GObject.SignalFlags.RUN_FIRST, |
473 | None, |
474 | (GObject.TYPE_PYOBJECT,)), |
475 | @@ -649,10 +690,24 @@ |
476 | |
477 | |
478 | def __init__(self, db, distro, icons, cache, datadir, pane): |
479 | - AppDetailsViewBase.__init__(self, db, distro, icons, cache, datadir) |
480 | Viewport.__init__(self) |
481 | + # basic stuff |
482 | + self.db = db |
483 | + self.distro = distro |
484 | + self.icons = icons |
485 | + self.cache = cache |
486 | + self.backend = get_install_backend() |
487 | + self.cache.connect("cache-ready", self._on_cache_ready) |
488 | + self.datadir = datadir |
489 | + self.app = None |
490 | + self.appdetails = None |
491 | + self.addons_to_install = [] |
492 | + self.addons_to_remove = [] |
493 | + # reviews |
494 | + self.review_loader = get_review_loader(self.cache, self.db) |
495 | + |
496 | + # ui specific stuff |
497 | self.set_shadow_type(Gtk.ShadowType.NONE) |
498 | - |
499 | self.set_name("view") |
500 | |
501 | self._pane = pane |
502 | @@ -671,10 +726,15 @@ |
503 | self.a11y.set_name("app_details pane") |
504 | |
505 | # aptdaemon |
506 | - self.backend.connect("transaction-started", self._on_transaction_started) |
507 | - self.backend.connect("transaction-stopped", self._on_transaction_stopped) |
508 | - self.backend.connect("transaction-finished", self._on_transaction_finished) |
509 | - self.backend.connect("transaction-progress-changed", self._on_transaction_progress_changed) |
510 | + self.backend.connect( |
511 | + "transaction-started", self._on_transaction_started) |
512 | + self.backend.connect( |
513 | + "transaction-stopped", self._on_transaction_stopped) |
514 | + self.backend.connect( |
515 | + "transaction-finished", self._on_transaction_finished) |
516 | + self.backend.connect( |
517 | + "transaction-progress-changed", |
518 | + self._on_transaction_progress_changed) |
519 | |
520 | # network status watcher |
521 | watcher = get_network_watcher() |
522 | @@ -733,7 +793,8 @@ |
523 | return |
524 | |
525 | def _check_for_reviews(self): |
526 | - # self.app may be undefined on network state change events (LP: #742635) |
527 | + # self.app may be undefined on network state change events |
528 | + # (LP: #742635) |
529 | if not self.app: |
530 | return |
531 | # review stats is fast and syncronous |
532 | @@ -801,7 +862,8 @@ |
533 | # stats we update manually |
534 | old_stats = self.review_loader.get_review_stats(self.app) |
535 | if ((old_stats is None and len(reviews_data) > 0) or |
536 | - (old_stats is not None and old_stats.ratings_total < len(reviews_data))): |
537 | + (old_stats is not None and |
538 | + old_stats.ratings_total < len(reviews_data))): |
539 | # generate new stats |
540 | stats = ReviewStats(app) |
541 | stats.ratings_total = len(reviews_data) |
542 | @@ -865,9 +927,13 @@ |
543 | servers = self.weblive.get_servers_for_pkgname(self.app.pkgname) |
544 | |
545 | if len(servers) == 0: |
546 | - error(None,"No available server", "There is currently no available WebLive server for this application.\nPlease try again later.") |
547 | + error(None, |
548 | + "No available server", |
549 | + "There is currently no available WebLive server " |
550 | + "for this application.\nPlease try again later.") |
551 | elif len(servers) == 1: |
552 | - self.weblive.create_automatic_user_and_run_session(session=cmd,serverid=servers[0].name) |
553 | + self.weblive.create_automatic_user_and_run_session( |
554 | + session=cmd,serverid=servers[0].name) |
555 | button.set_sensitive(False) |
556 | else: |
557 | d = ShowWebLiveServerChooserDialog(servers, self.app.pkgname) |
558 | @@ -880,7 +946,8 @@ |
559 | d.destroy() |
560 | |
561 | if serverid: |
562 | - self.weblive.create_automatic_user_and_run_session(session=cmd,serverid=serverid) |
563 | + self.weblive.create_automatic_user_and_run_session( |
564 | + session=cmd, serverid=serverid) |
565 | button.set_sensitive(False) |
566 | |
567 | elif self.weblive.client.state == "connected": |
568 | @@ -937,7 +1004,8 @@ |
569 | |
570 | # star rating widget |
571 | self.review_stats_widget = StarRatingsWidget() |
572 | - vb_inner.pack_start(self.review_stats_widget, False, False, StockEms.SMALL) |
573 | + vb_inner.pack_start( |
574 | + self.review_stats_widget, False, False, StockEms.SMALL) |
575 | |
576 | #~ vb_inner.set_property("can-focus", True) |
577 | self.title.a11y = vb_inner.get_accessible() |
578 | @@ -985,7 +1053,8 @@ |
579 | # attach to all the WebLive events |
580 | self.weblive.client.connect("progress", self.on_weblive_progress) |
581 | self.weblive.client.connect("connected", self.on_weblive_connected) |
582 | - self.weblive.client.connect("disconnected", self.on_weblive_disconnected) |
583 | + self.weblive.client.connect( |
584 | + "disconnected", self.on_weblive_disconnected) |
585 | self.weblive.client.connect("exception", self.on_weblive_exception) |
586 | self.weblive.client.connect("warning", self.on_weblive_warning) |
587 | |
588 | @@ -1053,9 +1122,12 @@ |
589 | self.reviews.connect("submit-usefulness", self._on_review_submit_usefulness) |
590 | self.reviews.connect("modify-review", self._on_review_modify) |
591 | self.reviews.connect("delete-review", self._on_review_delete) |
592 | - self.reviews.connect("more-reviews-clicked", self._on_more_reviews_clicked) |
593 | - self.reviews.connect("different-review-language-clicked", self._on_reviews_in_different_language_clicked) |
594 | - self.reviews.connect("review-sort-changed", self._on_review_sort_method_changed) |
595 | + self.reviews.connect("more-reviews-clicked", |
596 | + self._on_more_reviews_clicked) |
597 | + self.reviews.connect("different-review-language-clicked", |
598 | + self._on_reviews_in_different_language_clicked) |
599 | + self.reviews.connect("review-sort-changed", |
600 | + self._on_review_sort_method_changed) |
601 | if get_distro().REVIEWS_SERVER: |
602 | vb.pack_start(self.reviews, False, False, 0) |
603 | |
604 | @@ -1144,7 +1216,8 @@ |
605 | # show or hide the homepage button and set uri if homepage specified |
606 | if app_details.website: |
607 | self.homepage_btn.show() |
608 | - self.homepage_btn.set_markup("<a href=\"%s\">%s</a>"%(self.app_details.website, _('Developer Web Site'))) |
609 | + self.homepage_btn.set_markup("<a href=\"%s\">%s</a>" % ( |
610 | + self.app_details.website, _('Developer Web Site'))) |
611 | self.homepage_btn.set_tooltip_text(app_details.website) |
612 | else: |
613 | self.homepage_btn.hide() |
614 | @@ -1181,7 +1254,8 @@ |
615 | if app_details.version: |
616 | version = '%s %s' % (app_details.pkgname, app_details.version) |
617 | else: |
618 | - version = utf8(_("%s (unknown version)")) % utf8(app_details.pkgname) |
619 | + version = utf8(_("%s (unknown version)")) % utf8( |
620 | + app_details.pkgname) |
621 | if app_details.license: |
622 | license = app_details.license |
623 | else: |
624 | @@ -1240,7 +1314,7 @@ |
625 | |
626 | self._update_layout_error_status(pkg_ambiguous_error) |
627 | self._update_title_markup(appname, summary) |
628 | - self._update_app_icon(app_details) |
629 | + |
630 | self._update_app_description(app_details, app_details.pkgname) |
631 | self._update_description_footer_links(app_details) |
632 | self._update_app_screenshot(app_details) |
633 | @@ -1306,7 +1380,11 @@ |
634 | vb.pack_start(cmd_label, False, False, 0) |
635 | self.installed_where_hbox.show_all() |
636 | |
637 | - def _add_where_is_it_launcher(self, where): |
638 | + def _add_where_is_it_launcher(self, desktop_file): |
639 | + searcher = GMenuSearcher() |
640 | + where = searcher.get_main_menu_path(desktop_file) |
641 | + if not where: |
642 | + return |
643 | # display launcher location |
644 | label = Gtk.Label(label=_("Find it in the menu: ")) |
645 | self.installed_where_hbox.pack_start(label, False, False, 0) |
646 | @@ -1382,23 +1460,18 @@ |
647 | # first try the desktop file from the DB, then see if |
648 | # there is a local desktop file with the same name as |
649 | # the package |
650 | - searcher = GMenuSearcher() |
651 | # try to show menu location if there is a desktop file, but |
652 | # never show commandline programs for apps with a desktop file |
653 | # to cover cases like "file-roller" that have NoDisplay=true |
654 | desktop_file = get_desktop_file() |
655 | if desktop_file: |
656 | - where = searcher.get_main_menu_path(desktop_file) |
657 | - if where: |
658 | - self._add_where_is_it_launcher(where) |
659 | + self._add_where_is_it_launcher(desktop_file) |
660 | # if there is no desktop file, show commandline |
661 | else: |
662 | self._add_where_is_it_commandline(self.app_details.pkgname) |
663 | return |
664 | |
665 | # public API |
666 | - # FIXME: port to AppDetailsViewBase as |
667 | - # AppDetailsViewBase.show_app(self, app) |
668 | def show_app(self, app, force=False): |
669 | LOG.debug("AppDetailsView.show_app '%s'" % app) |
670 | if app is None: |
671 | @@ -1442,7 +1515,78 @@ |
672 | self.emit("selected", self.app) |
673 | return |
674 | |
675 | - # internal callback |
676 | + def refresh_app(self): |
677 | + self.show_app(self.app) |
678 | + |
679 | + |
680 | + # common code |
681 | + def _review_write_new(self): |
682 | + if (not self.app or |
683 | + not self.app.pkgname in self.cache or |
684 | + not self.cache[self.app.pkgname].candidate): |
685 | + dialogs.error(None, |
686 | + _("Version unknown"), |
687 | + _("The version of the application can not " |
688 | + "be detected. Entering a review is not " |
689 | + "possible.")) |
690 | + return |
691 | + # gather data |
692 | + pkg = self.cache[self.app.pkgname] |
693 | + version = pkg.candidate.version |
694 | + origin = self.cache.get_origin(self.app.pkgname) |
695 | + |
696 | + # FIXME: probably want to not display the ui if we can't review it |
697 | + if not origin: |
698 | + dialogs.error(None, |
699 | + _("Origin unknown"), |
700 | + _("The origin of the application can not " |
701 | + "be detected. Entering a review is not " |
702 | + "possible.")) |
703 | + return |
704 | + |
705 | + if pkg.installed: |
706 | + version = pkg.installed.version |
707 | + # call the loader to do call out the right helper and collect the result |
708 | + parent_xid = '' |
709 | + #parent_xid = get_parent_xid(self) |
710 | + self.review_loader.spawn_write_new_review_ui( |
711 | + self.app, version, self.appdetails.icon, origin, |
712 | + parent_xid, self.datadir, |
713 | + self._reviews_ready_callback) |
714 | + |
715 | + def _review_report_abuse(self, review_id): |
716 | + parent_xid = '' |
717 | + #parent_xid = get_parent_xid(self) |
718 | + self.review_loader.spawn_report_abuse_ui( |
719 | + review_id, parent_xid, self.datadir, self._reviews_ready_callback) |
720 | + |
721 | + def _review_submit_usefulness(self, review_id, is_useful): |
722 | + parent_xid = '' |
723 | + #parent_xid = get_parent_xid(self) |
724 | + self.review_loader.spawn_submit_usefulness_ui( |
725 | + review_id, is_useful, parent_xid, self.datadir, |
726 | + self._reviews_ready_callback) |
727 | + |
728 | + def _review_modify(self, review_id): |
729 | + parent_xid = '' |
730 | + #parent_xid = get_parent_xid(self) |
731 | + self.review_loader.spawn_modify_review_ui( |
732 | + parent_xid, self.appdetails.icon, self.datadir, review_id, |
733 | + self._reviews_ready_callback) |
734 | + |
735 | + def _review_delete(self, review_id): |
736 | + parent_xid = '' |
737 | + #parent_xid = get_parent_xid(self) |
738 | + self.review_loader.spawn_delete_review_ui( |
739 | + review_id, parent_xid, self.datadir, self._reviews_ready_callback) |
740 | + |
741 | + # internal callbacks |
742 | + def _on_cache_ready(self, cache): |
743 | + # re-show the application if the cache changes, it may affect the |
744 | + # current application |
745 | + LOG.debug("on_cache_ready") |
746 | + self.show_app(self.app) |
747 | + |
748 | def _update_interface_on_trans_ended(self, result): |
749 | state = self.pkg_statusbar.pkg_state |
750 | |
751 | @@ -1483,7 +1627,8 @@ |
752 | |
753 | return False |
754 | |
755 | - def _on_transaction_started(self, backend, pkgname, appname, trans_id, trans_type): |
756 | + def _on_transaction_started(self, backend, pkgname, appname, trans_id, |
757 | + trans_type): |
758 | if self.addons_statusbar.applying: |
759 | self.pkg_statusbar.configure(self.app_details, AppActions.APPLY) |
760 | return |
761 | @@ -1491,7 +1636,8 @@ |
762 | state = self.pkg_statusbar.pkg_state |
763 | LOG.debug("_on_transaction_started %s" % state) |
764 | if state == PkgStates.NEEDS_PURCHASE: |
765 | - self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLING_PURCHASED) |
766 | + self.pkg_statusbar.configure(self.app_details, |
767 | + PkgStates.INSTALLING_PURCHASED) |
768 | elif state == PkgStates.UNINSTALLED: |
769 | self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLING) |
770 | elif state == PkgStates.INSTALLED: |
771 | @@ -1501,7 +1647,8 @@ |
772 | elif state == PkgStates.REINSTALLABLE: |
773 | self.pkg_statusbar.configure(self.app_details, PkgStates.INSTALLING) |
774 | # FIXME: is there a way to tell if we are installing/removing? |
775 | - # we will assume that it is being installed, but this means that during removals we get the text "Installing.." |
776 | + # we will assume that it is being installed, but this means that |
777 | + # during removals we get the text "Installing.." |
778 | # self.pkg_statusbar.configure(self.app_details, PkgStates.REMOVING) |
779 | return |
780 | |
781 | @@ -1516,7 +1663,9 @@ |
782 | return |
783 | |
784 | def _on_transaction_progress_changed(self, backend, pkgname, progress): |
785 | - if self.app_details and self.app_details.pkgname and self.app_details.pkgname == pkgname: |
786 | + if (self.app_details and |
787 | + self.app_details.pkgname and |
788 | + self.app_details.pkgname == pkgname): |
789 | if not self.pkg_statusbar.progress.get_property('visible'): |
790 | self.pkg_statusbar.button.hide() |
791 | self.pkg_statusbar.progress.show() |
792 | @@ -1528,8 +1677,8 @@ |
793 | return |
794 | |
795 | def get_app_icon_details(self): |
796 | - """ helper for unity dbus support to provide details about the application |
797 | - icon as it is displayed on-screen |
798 | + """ helper for unity dbus support to provide details about the |
799 | + application icon as it is displayed on-screen |
800 | """ |
801 | icon_size = self._get_app_icon_size_on_screen() |
802 | (icon_x, icon_y) = self._get_app_icon_xy_position_on_screen() |
803 | @@ -1562,7 +1711,8 @@ |
804 | try: |
805 | (x,y) = self.icon.translate_coordinates(parent, 0, 0) |
806 | except Exception as e: |
807 | - LOG.warning("couldn't translate icon coordinates on-screen for unity dbus message: %s" % e) |
808 | + LOG.warning("couldn't translate icon coordinates on-screen " |
809 | + "for unity dbus message: %s" % e) |
810 | return (0,0) |
811 | # get toplevel window position |
812 | (px, py) = parent.get_position() |
813 | @@ -1575,14 +1725,17 @@ |
814 | return self.icons.load_icon(app_details.icon, |
815 | self.APP_ICON_SIZE, 0) |
816 | except GObject.GError as e: |
817 | - logging.warn("failed to load '%s': %s" % (app_details.icon, e)) |
818 | + logging.warn("failed to load '%s': %s" % ( |
819 | + app_details.icon, e)) |
820 | return self.icons.load_icon(Icons.MISSING_APP, |
821 | self.APP_ICON_SIZE, 0) |
822 | elif app_details.icon_url: |
823 | LOG.debug("did not find the icon locally, must download it") |
824 | |
825 | def on_image_download_complete(downloader, image_file_path): |
826 | - # when the download is complete, replace the icon in the view with the downloaded one |
827 | + # when the download is complete, replace the icon in the |
828 | + # view with the downloaded one |
829 | + logging.debug("_get_icon_as_pixbuf:image_downloaded() %s" % image_file_path) |
830 | try: |
831 | pb = GdkPixbuf.Pixbuf.new_from_file(image_file_path) |
832 | self.icon.set_from_pixbuf(pb) |
833 | @@ -1694,7 +1847,7 @@ |
834 | # gui |
835 | win = Gtk.Window() |
836 | scroll = Gtk.ScrolledWindow() |
837 | - view = AppDetailsViewGtk(db, distro, icons, cache, datadir, win) |
838 | + view = AppDetailsView(db, distro, icons, cache, datadir, win) |
839 | |
840 | import sys |
841 | if len(sys.argv) > 1: |
842 | |
843 | === added file 'test/gen-coverage-for-single-test.py' |
844 | --- test/gen-coverage-for-single-test.py 1970-01-01 00:00:00 +0000 |
845 | +++ test/gen-coverage-for-single-test.py 2011-11-29 19:57:26 +0000 |
846 | @@ -0,0 +1,4 @@ |
847 | +#!/bin/sh |
848 | + |
849 | +python-coverage run --parallel $1 |
850 | +./gen-coverage-report.sh |
851 | |
852 | === modified file 'test/gtk3/test_appdetailsview.py' |
853 | --- test/gtk3/test_appdetailsview.py 2011-11-11 03:20:16 +0000 |
854 | +++ test/gtk3/test_appdetailsview.py 2011-11-29 19:57:26 +0000 |
855 | @@ -1,21 +1,26 @@ |
856 | #!/usr/bin/python |
857 | |
858 | from gi.repository import Gtk |
859 | + |
860 | +import os |
861 | import sys |
862 | +import time |
863 | import unittest |
864 | |
865 | sys.path.insert(0,"../..") |
866 | sys.path.insert(0,"..") |
867 | |
868 | -#from mock import Mock |
869 | +from mock import Mock |
870 | |
871 | import softwarecenter.paths |
872 | softwarecenter.paths.datadir = "../data" |
873 | |
874 | from softwarecenter.db.application import Application |
875 | -from softwarecenter.testutils import get_mock_app_from_real_app |
876 | -from softwarecenter.ui.gtk3.views.appdetailsview_gtk import get_test_window_appdetails |
877 | -class TestAppdetailsViews(unittest.TestCase): |
878 | +from softwarecenter.testutils import get_mock_app_from_real_app, do_events |
879 | +from softwarecenter.ui.gtk3.views.appdetailsview import get_test_window_appdetails |
880 | +from softwarecenter.enums import PkgStates |
881 | + |
882 | +class TestAppdetailsView(unittest.TestCase): |
883 | |
884 | def test_videoplayer(self): |
885 | # get the widget |
886 | @@ -25,8 +30,7 @@ |
887 | # show app with no video |
888 | app = Application("", "2vcard") |
889 | view.show_app(app) |
890 | - while Gtk.events_pending(): |
891 | - Gtk.main_iteration() |
892 | + do_events() |
893 | self.assertFalse(view.videoplayer.get_property("visible")) |
894 | |
895 | # create app with video and ensure its visible |
896 | @@ -36,9 +40,115 @@ |
897 | # this is a example html - any html5 video will do |
898 | details.video_url = "http://people.canonical.com/~mvo/totem.html" |
899 | view.show_app(mock) |
900 | - while Gtk.events_pending(): |
901 | - Gtk.main_iteration() |
902 | + do_events() |
903 | self.assertTrue(view.videoplayer.get_property("visible")) |
904 | + |
905 | + def test_page(self): |
906 | + win = get_test_window_appdetails() |
907 | + view = win.get_data("view") |
908 | + # show app |
909 | + app = Application("", "software-center") |
910 | + view.show_app(app) |
911 | + do_events() |
912 | + |
913 | + # create mock app |
914 | + mock_app = get_mock_app_from_real_app(app) |
915 | + view.app = mock_app |
916 | + mock_details = mock_app.get_details(None) |
917 | + mock_details.purchase_date = "2011-11-20 17:45:01" |
918 | + mock_details._error_not_found = "error not found" |
919 | + view.app_details = mock_details |
920 | + |
921 | + # show a app through the various states |
922 | + for var in vars(PkgStates): |
923 | + # FIXME: this just ensures we are not crashing, also |
924 | + # add functional tests to ensure on error we show |
925 | + # the right info etc |
926 | + state = getattr(PkgStates, var) |
927 | + mock_details.pkg_state = state |
928 | + # reset app to ensure its shown again |
929 | + view.app = None |
930 | + # show it |
931 | + view.show_app(mock_app) |
932 | + |
933 | + def test_app_icon_loading(self): |
934 | + win = get_test_window_appdetails() |
935 | + view = win.get_data("view") |
936 | + # get icon |
937 | + app = Application("", "software-center") |
938 | + mock_app = get_mock_app_from_real_app(app) |
939 | + view.app = mock_app |
940 | + mock_details = mock_app.get_details(None) |
941 | + mock_details.cached_icon_file_path = "download-icon-test" |
942 | + mock_details.icon = "favicon.ico" |
943 | + mock_details.icon_url = "http://de.wikipedia.org/favicon.ico" |
944 | + view.app_details = mock_details |
945 | + view.show_app(mock_app) |
946 | + do_events() |
947 | + # ensure the icon is there |
948 | + # FIXME: ensure that the icon is really downloaded |
949 | + self.assertTrue(os.path.exists(mock_details.cached_icon_file_path)) |
950 | + os.unlink(mock_details.cached_icon_file_path) |
951 | + |
952 | + def test_add_where_is_it(self): |
953 | + win = get_test_window_appdetails() |
954 | + view = win.get_data("view") |
955 | + app = Application("", "software-center") |
956 | + view.show_app(app) |
957 | + view._add_where_is_it_commandline("apt") |
958 | + do_events() |
959 | + view._add_where_is_it_launcher("/usr/share/applications/ubuntu-software-center.desktop") |
960 | + do_events() |
961 | + |
962 | + def test_pkgstatus_bar(self): |
963 | + # make sure configure is run with the various states |
964 | + # test |
965 | + win = get_test_window_appdetails() |
966 | + view = win.get_data("view") |
967 | + # show app |
968 | + app = Application("", "software-center") |
969 | + view.show_app(app) |
970 | + do_events() |
971 | + |
972 | + # create mock app |
973 | + mock_app = get_mock_app_from_real_app(app) |
974 | + view.app = mock_app |
975 | + mock_details = mock_app.get_details(None) |
976 | + mock_details.purchase_date = "2011-11-20 17:45:01" |
977 | + view.app_details = mock_details |
978 | + |
979 | + # run the configure on the various states for the pkgstatus bar |
980 | + for var in vars(PkgStates): |
981 | + # FIXME: this just ensures we are not crashing, also |
982 | + # add functional tests to ensure on error we show |
983 | + # the right info etc |
984 | + state = getattr(PkgStates, var) |
985 | + mock_details.pkg_state = state |
986 | + # FIXME2: we should make configure simpler and/or explain |
987 | + # why it gets the state instead of just reading it |
988 | + # from the app_details |
989 | + view.pkg_statusbar.configure(mock_details, state) |
990 | + |
991 | + # make sure the various states are tested for click |
992 | + view.pkg_statusbar.app_manager = mock = Mock() |
993 | + mock_button = Mock() |
994 | + button_to_function_tests = ( |
995 | + (PkgStates.INSTALLED, "remove"), |
996 | + (PkgStates.PURCHASED_BUT_REPO_MUST_BE_ENABLED, "reinstall_purchased"), |
997 | + (PkgStates.NEEDS_PURCHASE, "buy_app"), |
998 | + (PkgStates.UNINSTALLED, "install"), |
999 | + (PkgStates.REINSTALLABLE, "install"), |
1000 | + (PkgStates.UPGRADABLE, "upgrade"), |
1001 | + (PkgStates.NEEDS_SOURCE, "enable_software_source") |
1002 | + ) |
1003 | + for state, func in button_to_function_tests: |
1004 | + view.pkg_statusbar.pkg_state = state |
1005 | + view.pkg_statusbar._on_button_clicked(mock_button) |
1006 | + self.assertTrue( |
1007 | + getattr(mock, func).called, |
1008 | + "for state %s the function %s was not called" % (state, func)) |
1009 | + mock.reset() |
1010 | + |
1011 | |
1012 | if __name__ == "__main__": |
1013 | import logging |
1014 | |
1015 | === modified file 'test/gtk3/test_install_progress.py' |
1016 | --- test/gtk3/test_install_progress.py 2011-08-17 08:21:22 +0000 |
1017 | +++ test/gtk3/test_install_progress.py 2011-11-29 19:57:26 +0000 |
1018 | @@ -27,7 +27,7 @@ |
1019 | stop_dummy_backend() |
1020 | |
1021 | def test_install_appdetails(self): |
1022 | - from softwarecenter.ui.gtk3.views.appdetailsview_gtk import get_test_window_appdetails |
1023 | + from softwarecenter.ui.gtk3.views.appdetailsview import get_test_window_appdetails |
1024 | win = get_test_window_appdetails() |
1025 | view = win.get_data("view") |
1026 | view.show_app(Application("", "2vcard")) |
1027 | |
1028 | === modified file 'test/gtk3/test_views.py' |
1029 | --- test/gtk3/test_views.py 2011-08-24 09:39:59 +0000 |
1030 | +++ test/gtk3/test_views.py 2011-11-29 19:57:26 +0000 |
1031 | @@ -29,7 +29,7 @@ |
1032 | Gtk.main() |
1033 | |
1034 | def test_appdetails(self): |
1035 | - from softwarecenter.ui.gtk3.views.appdetailsview_gtk import get_test_window_appdetails |
1036 | + from softwarecenter.ui.gtk3.views.appdetailsview import get_test_window_appdetails |
1037 | win = get_test_window_appdetails() |
1038 | GObject.timeout_add(TIMEOUT, lambda: win.destroy()) |
1039 | Gtk.main() |
Very nice, mvo! Thanks for this!