Merge lp:~mvo/software-center/appdetailsview-cleanup into lp:software-center

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
Reviewer Review Type Date Requested Status
Gary Lasker (community) Approve
Michael Vogt Pending
Review via email: mp+83845@code.launchpad.net

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.
Revision history for this message
Gary Lasker (gary-lasker) wrote :

Very nice, mvo! Thanks for this!

review: Approve

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()

Subscribers

People subscribed via source and target branches