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