Merge lp:~ilidrissi.amine/software-center/addons into lp:software-center

Proposed by Mohamed Amine Ilidrissi
Status: Merged
Merged at revision: 1049
Proposed branch: lp:~ilidrissi.amine/software-center/addons
Merge into: lp:software-center
Diff against target: 1444 lines (+858/-122)
11 files modified
debian/changelog (+16/-0)
softwarecenter/app.py (+13/-4)
softwarecenter/apt/aptcache.py (+227/-9)
softwarecenter/backend/aptd.py (+18/-8)
softwarecenter/db/application.py (+12/-5)
softwarecenter/enums.py (+1/-0)
softwarecenter/view/appdetailsview.py (+37/-6)
softwarecenter/view/appdetailsview_gtk.py (+502/-89)
test/test_appdetails_view.py (+21/-0)
test/test_aptd.py (+8/-0)
test/test_gui.py (+3/-1)
To merge this branch: bzr merge lp:~ilidrissi.amine/software-center/addons
Reviewer Review Type Date Requested Status
Matthew Paul Thomas design Approve
Mohamed Amine Ilidrissi (community) Needs Resubmitting
Review via email: mp+30946@code.launchpad.net

Description of the change

This branch introduces add-on handling as described in https://wiki.ubuntu.com/SoftwareCenter#add-ons.
How to test: pick any package that has potential add-ons (like gimp).
Limitations: Descriptions aren't sorted alphabetically.

Feel free to propose any changes - I know my Python skills are pathetic :P.

To post a comment you must log in.
934. By Mohamed Amine Ilidrissi

merge with trunk

935. By Mohamed Amine Ilidrissi

Modified changelog.

Revision history for this message
Matthew Paul Thomas (mpt) wrote :

Mohamed, thanks so much for working on this. I'm impressed.

From a quick test, I see a few interface glitches:

* By default, every software item screen now says "No changes to be done" by default. This text shouldn't ever be necessary.

* The add-ons section appears before the installed state bar. It should instead appear after the item description. <https://wiki.ubuntu.com/SoftwareCenter#software-item-screen>

* There should be a little more vertical space between add-ons. Currently the icon for the first add-on is closer to the icon for the second add-on than it is to its own title. Also, there should be more vertical space after "Choose add-ons:".

* Thunderbird lists 56 add-ons, of which about 50 of them are language/region packs, which should be installed from "System" > "Administration" > "Language Support" instead. Is there an easy way of recognizing language-pack add-ons to avoid showing them in USC?

review: Needs Fixing
Revision history for this message
Matthew Paul Thomas (mpt) wrote :

One of Banshee's add-ons is "Media Management and Playback application (debug symbols)". Similarly, one of Evolution's add-ons is "debugging symbols for Evolution". We also need to figure out how to hide those, at least by default.

936. By Mohamed Amine IL Idrissi <devildante@devildante-laptop>

Fixed a bunch of things at mpt's request.

Revision history for this message
Mohamed Amine Ilidrissi (ilidrissi.amine) wrote :

Okay, just fixed these issues. Feel free to request any other fixes.

review: Needs Resubmitting
Revision history for this message
Michael Vogt (mvo) wrote :

Sorry for the slow reply. I had a look over the code and its pretty good. There are some small issues I would like to see fixed.
- recommended_addons and suggested_addons are almost the same, would be nice to consolidate the bits that are equal into their own function to avoid duplications
- I moved some of the lowlevel stuff into PackageAddonsManager (in lp:~mvo/software-center/addons
- I would love to avoid the extra addons_install, addons_remove to each of the action methods in aptdaemon (as install()/remove() is really a apply() now for complex changes)
- tests are missing
- there are merge conflicts with trunk/ (because of the debfiles branch merge)

There are some design issues (probably more something for mpt):
- moving the install button to the end of the page is not ideal IMO, with the gimp and the default window size its not visible without scrolling
- just showing the summary without a package name or a way to navigate to the app is not that useful for the user to understand what he gets from the addon

937. By Mohamed Amine Ilidrissi

merge lp:~mvo/software-center/addons, many thanks.

938. By Mohamed Amine Ilidrissi

merge with trunk (argh conflicts).

939. By Mohamed Amine Ilidrissi

bunch of fixes.

940. By Mohamed Amine Ilidrissi

bunch of fixes... again.

941. By Mohamed Amine Ilidrissi

merge mvo's branch, many thanks.

942. By Mohamed Amine Ilidrissi

Thank you mvo for your efforts :)

943. By Mohamed Amine Ilidrissi

Renamed a function.

Revision history for this message
Michael Vogt (mvo) wrote :

Thanks, code is good now, I filed a feature-freee-exception for this now https://edge.launchpad.net/ubuntu/+source/software-center/+bug/617297

944. By Mohamed Amine Ilidrissi

merge with trunk.

945. By Mohamed Amine Ilidrissi

Removed useless code and fixed a "big icon" bug.

946. By Mohamed Amine Ilidrissi

Disable links to pkgnames until we fix it, and made update_totalsize into a gobject.idle_add() so selecting an add-on feels faster.

947. By Mohamed Amine Ilidrissi

software-center will not crash anymore when opening a pkg that does not exist, and it will now check that app_details.icon is not None.

948. By Mohamed Amine Ilidrissi

2 fixes for 2 small bugs.

949. By Mohamed Amine Ilidrissi

Fixed debian/changelog fail.

950. By Mohamed Amine Ilidrissi

merge kiwinote's branch, many thanks :)

951. By Mohamed Amine Ilidrissi

Fixed the bar padding, and the total size text.

952. By Mohamed Amine Ilidrissi

some fixes.

953. By Mohamed Amine Ilidrissi

Polishing.

954. By Mohamed Amine Ilidrissi

Fixed padding and added a :

955. By Mohamed Amine Ilidrissi

Reduced the add-on image size to 22x22 and made loading the appdetailsview snappier.

956. By Mohamed Amine Ilidrissi

Renamed TotalSizeBar to AddonsStateBar.

957. By Mohamed Amine Ilidrissi

Fixed padding issues.

958. By Mohamed Amine Ilidrissi

Fixed an implementation error.

Revision history for this message
Matthew Paul Thomas (mpt) wrote :

Once the vertical spacing above and below the add-ons section is sorted out, I think this is good to merge. Thank you Mohamed.

review: Approve (design)
959. By Mohamed Amine Ilidrissi

Fixed padding issues.

960. By Mohamed Amine Ilidrissi

fixed AddonsStateBar padding issue.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'debian/changelog'
--- debian/changelog 2010-08-13 14:55:27 +0000
+++ debian/changelog 2010-08-16 20:56:42 +0000
@@ -1,3 +1,19 @@
1software-center (2.1.11) UNRELEASED; urgency=low
2
3 [ Mohamed Amine IL Idrissi ]
4 * (all): Implemented add-on handling.
5
6 [ Kiwinote ]
7 * softwarecenter/view/appdetailsview_gtk.py:
8 - use package info lines rather than package info tables
9 (for devildante to use in the addons branch)
10
11 [ Milo Casagrande ]
12 * softwarecenter/db/application.py:
13 - make 'source available' warning more suitable for translation.
14
15 -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 13 Aug 2010 16:46:13 +0200
16
1software-center (2.1.10) maverick; urgency=low17software-center (2.1.10) maverick; urgency=low
218
3 [ Kiwinote ]19 [ Kiwinote ]
420
=== modified file 'softwarecenter/app.py'
--- softwarecenter/app.py 2010-08-13 10:27:47 +0000
+++ softwarecenter/app.py 2010-08-16 20:56:42 +0000
@@ -206,6 +206,9 @@
206 self.on_app_selected)206 self.on_app_selected)
207 self.available_pane.app_details.connect("application-request-action", 207 self.available_pane.app_details.connect("application-request-action",
208 self.on_application_request_action)208 self.on_application_request_action)
209 self.available_pane.app_details.connect("navigation-request",
210 self.on_application_request_navigation,
211 self.available_pane)
209 self.available_pane.app_view.connect("application-request-action", 212 self.available_pane.app_view.connect("application-request-action",
210 self.on_application_request_action)213 self.on_application_request_action)
211 self.available_pane.connect("app-list-changed", 214 self.available_pane.connect("app-list-changed",
@@ -248,6 +251,9 @@
248 self.on_app_selected)251 self.on_app_selected)
249 self.installed_pane.app_details.connect("application-request-action", 252 self.installed_pane.app_details.connect("application-request-action",
250 self.on_application_request_action)253 self.on_application_request_action)
254 self.installed_pane.app_details.connect("navigation-request",
255 self.on_application_request_navigation,
256 self.installed_pane)
251 self.installed_pane.app_view.connect("application-request-action", 257 self.installed_pane.app_view.connect("application-request-action",
252 self.on_application_request_action)258 self.on_application_request_action)
253 self.installed_pane.connect("app-list-changed", 259 self.installed_pane.connect("app-list-changed",
@@ -450,7 +456,10 @@
450 channel_display_name, icon=None, query=query)456 channel_display_name, icon=None, query=query)
451 self.view_switcher.select_channel_node(channel_display_name, False)457 self.view_switcher.select_channel_node(channel_display_name, False)
452 458
453 def on_application_request_action(self, widget, app, action):459 def on_application_request_navigation(self, widget, pkgname, pane):
460 pane.show_app(Application("", pkgname))
461
462 def on_application_request_action(self, widget, app, addons_install, addons_remove, action):
454 """callback when an app action is requested from the appview,463 """callback when an app action is requested from the appview,
455 if action is "remove", must check if other dependencies have to be464 if action is "remove", must check if other dependencies have to be
456 removed as well and show a dialog in that case465 removed as well and show a dialog in that case
@@ -481,7 +490,7 @@
481 self.backend.emit("transaction-stopped")490 self.backend.emit("transaction-stopped")
482 return491 return
483 492
484 # action_func is one of: "install", "remove" or "upgrade"493 # action_func is one of: "install", "remove", "upgrade", or "apply_changes"
485 action_func = getattr(self.backend, action)494 action_func = getattr(self.backend, action)
486 if action == 'install':495 if action == 'install':
487 # the package.deb path name is in the request496 # the package.deb path name is in the request
@@ -489,9 +498,9 @@
489 debfile_name = app.request498 debfile_name = app.request
490 else:499 else:
491 debfile_name = None500 debfile_name = None
492 action_func(app.pkgname, app.appname, appdetails.icon, debfile_name)501 action_func(app.pkgname, app.appname, appdetails.icon, debfile_name, addons_install, addons_remove)
493 elif callable(action_func):502 elif callable(action_func):
494 action_func(app.pkgname, app.appname, appdetails.icon)503 action_func(app.pkgname, app.appname, appdetails.icon, addons_install=addons_install, addons_remove=addons_remove)
495 else:504 else:
496 logging.error("Not a valid action in AptdaemonBackend: '%s'" % action)505 logging.error("Not a valid action in AptdaemonBackend: '%s'" % action)
497 506
498507
=== modified file 'softwarecenter/apt/aptcache.py'
--- softwarecenter/apt/aptcache.py 2010-07-09 03:16:33 +0000
+++ softwarecenter/apt/aptcache.py 2010-08-16 20:56:42 +0000
@@ -32,6 +32,7 @@
32import time32import time
3333
34from gettext import gettext as _34from gettext import gettext as _
35from softwarecenter.enums import *
3536
36class GtkMainIterationProgress(apt.progress.base.OpProgress):37class GtkMainIterationProgress(apt.progress.base.OpProgress):
37 """Progress that just runs the main loop"""38 """Progress that just runs the main loop"""
@@ -48,6 +49,8 @@
48 DEPENDENCY_TYPES = ("PreDepends", "Depends")49 DEPENDENCY_TYPES = ("PreDepends", "Depends")
49 RECOMMENDS_TYPES = ("Recommends",)50 RECOMMENDS_TYPES = ("Recommends",)
50 SUGGESTS_TYPES = ("Suggests",)51 SUGGESTS_TYPES = ("Suggests",)
52 ENHANCES_TYPES = ("Enhances",)
53 PROVIDES_TYPES = ("Provides",)
5154
52 # stamp file to monitor (provided by update-notifier via55 # stamp file to monitor (provided by update-notifier via
53 # APT::Update::Post-Invoke-Success)56 # APT::Update::Post-Invoke-Success)
@@ -104,16 +107,16 @@
104 return self._cache.__iter__()107 return self._cache.__iter__()
105 def __contains__(self, k):108 def __contains__(self, k):
106 return self._cache.__contains__(k)109 return self._cache.__contains__(k)
107 def _get_installed_rdepends_by_type(self, pkg, type):110 def _get_rdepends_by_type(self, pkg, type, onlyInstalled):
108 installed_rdeps = set()111 rdeps = set()
109 for rdep in pkg._pkg.rev_depends_list:112 for rdep in pkg._pkg.rev_depends_list:
110 dep_type = rdep.dep_type_untranslated113 dep_type = rdep.dep_type_untranslated
111 if dep_type in type:114 if dep_type in type:
112 rdep_name = rdep.parent_pkg.name115 rdep_name = rdep.parent_pkg.name
113 if (rdep_name in self._cache and116 if rdep_name in self._cache and (not onlyInstalled or
114 self._cache[rdep_name].is_installed):117 (onlyInstalled and self._cache[rdep_name].is_installed)):
115 installed_rdeps.add(rdep.parent_pkg.name)118 rdeps.add(rdep.parent_pkg.name)
116 return installed_rdeps119 return rdeps
117 def _installed_dependencies(self, pkg_name, all_deps=None):120 def _installed_dependencies(self, pkg_name, all_deps=None):
118 """ recursively return all installed dependencies of a given pkg """121 """ recursively return all installed dependencies of a given pkg """
119 #print "_installed_dependencies", pkg_name, all_deps122 #print "_installed_dependencies", pkg_name, all_deps
@@ -168,11 +171,15 @@
168 origins.add(item.origin)171 origins.add(item.origin)
169 return origins172 return origins
170 def get_installed_rdepends(self, pkg):173 def get_installed_rdepends(self, pkg):
171 return self._get_installed_rdepends_by_type(pkg, self.DEPENDENCY_TYPES)174 return self._get_rdepends_by_type(pkg, self.DEPENDENCY_TYPES, True)
172 def get_installed_rrecommends(self, pkg):175 def get_installed_rrecommends(self, pkg):
173 return self._get_installed_rdepends_by_type(pkg, self.RECOMMENDS_TYPES)176 return self._get_rdepends_by_type(pkg, self.RECOMMENDS_TYPES, True)
174 def get_installed_rsuggests(self, pkg):177 def get_installed_rsuggests(self, pkg):
175 return self._get_installed_rdepends_by_type(pkg, self.SUGGESTS_TYPES)178 return self._get_rdepends_by_type(pkg, self.SUGGESTS_TYPES, True)
179 def get_installed_renhances(self, pkg):
180 return self._get_rdepends_by_type(pkg, self.ENHANCES_TYPES, True)
181 def get_installed_rprovides(self, pkg):
182 return self._get_rdepends_by_type(pkg, self.PROVIDES_TYPES, True)
176 def component_available(self, distro_codename, component):183 def component_available(self, distro_codename, component):
177 """ check if the given component is enabled """184 """ check if the given component is enabled """
178 # FIXME: test for more properties here?185 # FIXME: test for more properties here?
@@ -183,6 +190,202 @@
183 it.archive == distro_codename):190 it.archive == distro_codename):
184 return True191 return True
185 return False192 return False
193
194 def _get_depends_by_type(self, pkg, types):
195 version = pkg.installed
196 if version == None:
197 version = max(pkg.versions)
198 return version.get_dependencies(*types)
199 def _get_depends_by_type_str(self, pkg, *types):
200 def not_in_list(list, item):
201 for i in list:
202 if i == item:
203 return False
204 return True
205 deps = self._get_depends_by_type(pkg, *types)
206 deps_str = []
207 for dep in deps:
208 for dep_ in dep.or_dependencies:
209 if not_in_list(deps_str, dep_.name):
210 deps_str.append(dep_.name)
211 return deps_str
212 def get_depends(self, pkg):
213 return self._get_depends_by_type_str(pkg, self.DEPENDENCY_TYPES)
214 def get_recommends(self, pkg):
215 return self._get_depends_by_type_str(pkg, self.RECOMMENDS_TYPES)
216 def get_suggests(self, pkg):
217 return self._get_depends_by_type_str(pkg, self.SUGGESTS_TYPES)
218 def get_enhances(self, pkg):
219 return self._get_depends_by_type_str(pkg, self.ENHANCES_TYPES)
220 def get_provides(self, pkg):
221 return self._get_depends_by_type_str(pkg, self.PROVIDES_TYPES)
222
223 def get_rdepends(self, pkg):
224 return self._get_rdepends_by_type(pkg, self.DEPENDENCY_TYPES, False)
225 def get_rrecommends(self, pkg):
226 return self._get_rdepends_by_type(pkg, self.RECOMMENDS_TYPES, False)
227 def get_rsuggests(self, pkg):
228 return self._get_rdepends_by_type(pkg, self.SUGGESTS_TYPES, False)
229 def get_renhances(self, pkg):
230 return self._get_rdepends_by_type(pkg, self.ENHANCES_TYPES, False)
231 def get_rprovides(self, pkg):
232 return self._get_rdepends_by_type(pkg, self.PROVIDES_TYPES, False)
233
234 def _get_changes_without_applying(self, pkg):
235 if pkg.installed == None:
236 pkg.mark_install()
237 else:
238 pkg.mark_delete()
239 changes_tmp = self._cache.get_changes()
240 changes = {}
241 for change in changes_tmp:
242 if change.marked_install or change.marked_reinstall:
243 changes[change.name] = PKG_STATE_INSTALLING
244 elif change.marked_delete:
245 changes[change.name] = PKG_STATE_REMOVING
246 elif change.marked_upgrade:
247 changes[change.name] = PKG_STATE_UPGRADING
248 else:
249 changes[change.name] = PKG_STATE_UNKNOWN
250 self._cache.clear()
251 return changes
252 def get_all_deps_installing(self, pkg):
253 """ Return all dependencies of pkg that will be marked for install """
254 changes = self._get_changes_without_applying(pkg)
255 installing_deps = []
256 for change in changes.keys():
257 if change != pkg.name and changes[change] == PKG_STATE_INSTALLING:
258 installing_deps.append(change)
259 return installing_deps
260 def get_all_deps_removing(self, pkg):
261 changes = self._get_changes_without_applying(pkg)
262 removing_deps = []
263 for change in changes.keys():
264 if change != pkg.name and changes[change] == PKG_STATE_REMOVING:
265 removing_deps.append(change)
266 return removing_deps
267 def get_all_deps_upgrading(self, pkg):
268 changes = self._get_changes_without_applying(pkg)
269 upgrading_deps = []
270 for change in changes.keys():
271 if change != pkg.name and changes[change] == PKG_STATE_UPGRADING:
272 upgrading_deps.append(change)
273 return upgrading_deps
274
275class PackageAddonsManager(object):
276 """ class that abstracts the addons handling """
277
278 LANGPACK_PKGDEPENDS = "/usr/share/language-selector/data/pkg_depends"
279 (RECOMMENDED, SUGGESTED) = range(2)
280
281 def __init__(self, cache):
282 self.cache = cache
283 self._language_packages = self._read_language_pkgs()
284
285 def _remove_important_or_langpack(self, addon_list, app_pkg):
286 """ remove packages that are essential or important
287 or langpacks
288 """
289 for addon in addon_list:
290 try:
291 pkg = self.cache[addon]
292 if pkg.essential or pkg._pkg.important or addon == app_pkg.name:
293 addon_list.remove(addon)
294 continue
295
296 deps = self.cache.get_depends(app_pkg)
297 if addon in deps:
298 addon_list.remove(addon)
299 continue
300
301 rdeps = self.cache.get_installed_rdepends(pkg)
302 if (len(rdeps) > 0 or
303 self._is_language_pkg(addon)):
304 addon_list.remove(addon)
305 continue
306 except KeyError:
307 addon_list.remove(addon)
308
309 def _is_language_pkg(self, addon):
310 # a simple "addon in self._language_packages" is not enough
311 for template in self._language_packages:
312 if addon.startswith(template):
313 return True
314 return False
315
316 def _read_language_pkgs(self):
317 language_packages = set()
318 for line in open(self.LANGPACK_PKGDEPENDS):
319 line = line.strip()
320 if line.startswith('#'):
321 continue
322 try:
323 (cat, code, dep_pkg, language_pkg) = line.split(':')
324 except ValueError:
325 continue
326 language_packages.add(language_pkg)
327 return language_packages
328
329 def _addons_for_pkg(self, pkgname, type):
330 try:
331 pkg = self.cache[pkgname]
332 except KeyError:
333 return []
334 deps = self.cache.get_depends(pkg)
335 addons = []
336 if type == self.RECOMMENDED:
337 recommends = self.cache.get_recommends(pkg)
338 if len(recommends) == 1:
339 addons += recommends
340 elif type == self.SUGGESTED:
341 suggests = self.cache.get_suggests(pkg)
342 if len(suggests) == 1:
343 addons += suggests
344 addons += self.cache.get_renhances(pkg)
345
346 for dep in deps:
347 try:
348 pkgdep = self.cache[dep]
349 if len(self.cache.get_rdepends(pkgdep)) == 1:
350 # pkg is the only known package that depends on pkgdep
351 if type == self.RECOMMENDED:
352 addons += self.cache.get_recommends(pkgdep)
353 elif type == self.SUGGESTED:
354 addons += self.cache.get_suggests(pkgdep)
355 addons += self.cache.get_renhances(pkgdep)
356 except KeyError:
357 pass # FIXME: should we handle that differently?
358 self._remove_important_or_langpack(addons, pkg)
359 for addon in addons:
360 try:
361 pkg_ = self.cache[addon]
362 except KeyError:
363 addons.remove(addon)
364 else:
365 can_remove = False
366 for addon_ in addons:
367 try:
368 if addon in self.cache.get_provides(self.cache[addon_]) \
369 or addon in self.cache.get_depends(self.cache[addon_]) \
370 or addon in self.cache.get_recommends(self.cache[addon_]):
371 can_remove = True
372 break
373 except KeyError:
374 addons.remove(addon_)
375 break
376 if can_remove or not pkg_.candidate or addons.count(addon) > 1 \
377 or addon == pkg.name or self._is_language_pkg(addon):
378 addons.remove(addon)
379 # FIXME: figure out why I have to call this function two times to get rid of important packages
380 self._remove_important_or_langpack(addons, pkg)
381 return addons
382
383 def recommended_addons(self, pkgname):
384 return self._addons_for_pkg(pkgname, self.RECOMMENDED)
385
386 def suggested_addons(self, pkgname):
387 return self._addons_for_pkg(pkgname, self.SUGGESTED)
388
186389
187if __name__ == "__main__":390if __name__ == "__main__":
188 c = AptCache()391 c = AptCache()
@@ -200,3 +403,18 @@
200 print c.get_installed_rdepends(pkg)403 print c.get_installed_rdepends(pkg)
201 print c.get_installed_rrecommends(pkg)404 print c.get_installed_rrecommends(pkg)
202 print c.get_installed_rsuggests(pkg)405 print c.get_installed_rsuggests(pkg)
406
407 print "deps of gimp"
408 pkg = c["gimp"]
409 print c.get_depends(pkg)
410 print c.get_recommends(pkg)
411 print c.get_suggests(pkg)
412 print c.get_enhances(pkg)
413 print c.get_provides(pkg)
414
415 print "rdeps of gimp"
416 print c.get_rdepends(pkg)
417 print c.get_rrecommends(pkg)
418 print c.get_rsuggests(pkg)
419 print c.get_renhances(pkg)
420 print c.get_rprovides(pkg)
203421
=== modified file 'softwarecenter/backend/aptd.py'
--- softwarecenter/backend/aptd.py 2010-08-11 14:00:11 +0000
+++ softwarecenter/backend/aptd.py 2010-08-16 20:56:42 +0000
@@ -126,8 +126,9 @@
126 except Exception, error:126 except Exception, error:
127 self._on_trans_error(error)127 self._on_trans_error(error)
128128
129 # FIXME: upgrade add-ons here
129 @inline_callbacks130 @inline_callbacks
130 def upgrade(self, pkgname, appname, iconname, metadata=None):131 def upgrade(self, pkgname, appname, iconname, addons_install=None, addons_remove=None, metadata=None):
131 """ upgrade a single package """132 """ upgrade a single package """
132 self.emit("transaction-started")133 self.emit("transaction-started")
133 try:134 try:
@@ -138,7 +139,7 @@
138 self._on_trans_error(error, pkgname)139 self._on_trans_error(error, pkgname)
139140
140 @inline_callbacks141 @inline_callbacks
141 def remove(self, pkgname, appname, iconname, metadata=None):142 def remove(self, pkgname, appname, iconname, addons_install=None, addons_remove=None, metadata=None):
142 """ remove a single package """143 """ remove a single package """
143 self.emit("transaction-started")144 self.emit("transaction-started")
144 try:145 try:
@@ -149,7 +150,7 @@
149 self._on_trans_error(error, pkgname)150 self._on_trans_error(error, pkgname)
150151
151 @inline_callbacks152 @inline_callbacks
152 def remove_multiple(self, pkgnames, appnames, iconnames, metadatas=None):153 def remove_multiple(self, pkgnames, appnames, iconnames, addons_install=None, addons_remove=None, metadatas=None):
153 """ queue a list of packages for removal """154 """ queue a list of packages for removal """
154 if metadatas == None:155 if metadatas == None:
155 metadatas = []156 metadatas = []
@@ -159,7 +160,7 @@
159 yield self.remove(pkgname, appname, iconname, metadata)160 yield self.remove(pkgname, appname, iconname, metadata)
160161
161 @inline_callbacks162 @inline_callbacks
162 def install(self, pkgname, appname, iconname, filename=None, metadata=None):163 def install(self, pkgname, appname, iconname, filename=None, addons_install=None, addons_remove=None, metadata=None):
163 """Install a single package from the archive164 """Install a single package from the archive
164 If filename is given a local deb package is installed instead.165 If filename is given a local deb package is installed instead.
165 """166 """
@@ -169,21 +170,30 @@
169 trans = yield self.aptd_client.install_file(filename,170 trans = yield self.aptd_client.install_file(filename,
170 defer=True)171 defer=True)
171 else:172 else:
172 trans = yield self.aptd_client.install_packages([pkgname],173 trans = yield self.aptd_client.commit_packages([pkgname] + addons_install, [], addons_remove, [], [])
173 defer=True)
174 yield self._run_transaction(trans, pkgname, appname, iconname, metadata)174 yield self._run_transaction(trans, pkgname, appname, iconname, metadata)
175 except Exception, error:175 except Exception, error:
176 self._on_trans_error(error, pkgname)176 self._on_trans_error(error, pkgname)
177177
178 @inline_callbacks178 @inline_callbacks
179 def install_multiple(self, pkgnames, appnames, iconnames, metadatas=None):179 def install_multiple(self, pkgnames, appnames, iconnames, addons_install=None, addons_remove=None, metadatas=None):
180 """ queue a list of packages for install """180 """ queue a list of packages for install """
181 if metadatas == None:181 if metadatas == None:
182 metadatas = []182 metadatas = []
183 for item in pkgnames:183 for item in pkgnames:
184 metadatas.append(None)184 metadatas.append(None)
185 for pkgname, appname, iconname, metadata in zip(pkgnames, appnames, iconnames, metadatas):185 for pkgname, appname, iconname, metadata in zip(pkgnames, appnames, iconnames, metadatas):
186 yield self.install(pkgname, appname, iconname, metadata)186 yield self.install(pkgname, appname, iconname, metadata=metadata)
187
188 @inline_callbacks
189 def apply_changes(self, pkgname, appname, iconname, addons_install=None, addons_remove=None, metadata=None):
190 """ install and remove add-ons """
191 self.emit("transaction-started")
192 try:
193 trans = yield self.aptd_client.commit_packages(addons_install, [], addons_remove, [], [])
194 yield self._run_transaction(trans, pkgname, appname, iconname)
195 except Exception, error:
196 self._on_trans_error(error)
187197
188 @inline_callbacks198 @inline_callbacks
189 def reload(self, metadata=None):199 def reload(self, metadata=None):
190200
=== modified file 'softwarecenter/db/application.py'
--- softwarecenter/db/application.py 2010-08-13 14:25:11 +0000
+++ softwarecenter/db/application.py 2010-08-16 20:56:42 +0000
@@ -456,11 +456,18 @@
456 source_to_enable = self.component456 source_to_enable = self.component
457 if source_to_enable:457 if source_to_enable:
458 sources = source_to_enable.split('&')458 sources = source_to_enable.split('&')
459 warning = _("Available from the \"%s\"") % sources[0]459 sources_length = len(sources)
460 if len(sources) > 1:460 if sources_length == 1:
461 for source in sources[1:]:461 warning = _("Available from the \"%s\" source.") % sources[0]
462 warning += _(", or from the \"%s\"") % source462 elif sources_length > 1:
463 warning += _(" source.")463 # Translators: the visible string is constructed concatenating
464 # the following 3 strings like this:
465 # Available from the following sources: %s, ... %s, %s.
466 warning = _("Available from the following sources: ")
467 # Cycle through all, but the last
468 for source in sources[:-1]:
469 warning += _("\"%s\", ") % source
470 warning += _("\"%s\".") % sources[sources_length - 1]
464 return warning471 return warning
465472
466 @property473 @property
467474
=== modified file 'softwarecenter/enums.py'
--- softwarecenter/enums.py 2010-08-10 08:31:32 +0000
+++ softwarecenter/enums.py 2010-08-16 20:56:42 +0000
@@ -126,6 +126,7 @@
126APP_ACTION_INSTALL = "install"126APP_ACTION_INSTALL = "install"
127APP_ACTION_REMOVE = "remove"127APP_ACTION_REMOVE = "remove"
128APP_ACTION_UPGRADE = "upgrade"128APP_ACTION_UPGRADE = "upgrade"
129APP_ACTION_APPLY = "apply_changes"
129130
130from version import *131from version import *
131USER_AGENT="Software Center/%s (N;) %s/%s (%s)" % (VERSION, 132USER_AGENT="Software Center/%s (N;) %s/%s (%s)" % (VERSION,
132133
=== modified file 'softwarecenter/view/appdetailsview.py'
--- softwarecenter/view/appdetailsview.py 2010-08-09 14:17:41 +0000
+++ softwarecenter/view/appdetailsview.py 2010-08-16 20:56:42 +0000
@@ -22,7 +22,7 @@
22import urllib22import urllib
23import gobject23import gobject
2424
25from softwarecenter.enums import MISSING_APP_ICON25from softwarecenter.apt.aptcache import PackageAddonsManager
26from softwarecenter.db.application import AppDetails26from softwarecenter.db.application import AppDetails
27from softwarecenter.backend import get_install_backend27from softwarecenter.backend import get_install_backend
28from softwarecenter.enums import *28from softwarecenter.enums import *
@@ -35,8 +35,17 @@
35 __gsignals__ = {35 __gsignals__ = {
36 "application-request-action" : (gobject.SIGNAL_RUN_LAST,36 "application-request-action" : (gobject.SIGNAL_RUN_LAST,
37 gobject.TYPE_NONE,37 gobject.TYPE_NONE,
38 (gobject.TYPE_PYOBJECT, str),38 (gobject.TYPE_PYOBJECT,
39 gobject.TYPE_PYOBJECT,
40 gobject.TYPE_PYOBJECT,
41 str),
39 ),42 ),
43 "navigation-request" : ( gobject.SIGNAL_RUN_LAST,
44 gobject.TYPE_NONE,
45 (str,
46 ),
47 ),
48
40 }49 }
4150
42 def __init__(self, db, distro, icons, cache, history, datadir):51 def __init__(self, db, distro, icons, cache, history, datadir):
@@ -45,10 +54,13 @@
45 self.icons = icons54 self.icons = icons
46 self.cache = cache55 self.cache = cache
47 self.cache.connect("cache-ready", self._on_cache_ready)56 self.cache.connect("cache-ready", self._on_cache_ready)
57 self.addons_manager = PackageAddonsManager(cache)
48 self.history = history58 self.history = history
49 self.datadir = datadir59 self.datadir = datadir
50 self.app = None60 self.app = None
51 self.appdetails = None61 self.appdetails = None
62 self.addons_install = []
63 self.addons_remove = []
52 # aptdaemon64 # aptdaemon
53 self.backend = get_install_backend()65 self.backend = get_install_backend()
54 self._logger = logging.getLogger(__name__)66 self._logger = logging.getLogger(__name__)
@@ -58,6 +70,22 @@
58 you need to overwrite70 you need to overwrite
59 """71 """
60 pass72 pass
73
74 # add-on handling
75 def _set_addon_install(self, addon):
76 pkg = self.cache[addon]
77 if addon not in self.addons_install and pkg.installed == None:
78 self.addons_install.append(addon)
79 if addon in self.addons_remove:
80 self.addons_remove.remove(addon)
81
82 def _set_addon_remove(self, addon):
83 pkg = self.cache[addon]
84 if addon not in self.addons_remove and pkg.installed != None:
85 self.addons_remove.append(addon)
86 if addon in self.addons_install:
87 self.addons_install.remove(addon)
88
61 # public API89 # public API
62 def show_app(self, app):90 def show_app(self, app):
63 """ show the given application """91 """ show the given application """
@@ -75,13 +103,16 @@
75 self.backend.reload()103 self.backend.reload()
76 def install(self):104 def install(self):
77 """ install the current application, fire an action request """105 """ install the current application, fire an action request """
78 self.emit("application-request-action", self.app, APP_ACTION_INSTALL)106 self.emit("application-request-action", self.app, self.addons_install, self.addons_remove, APP_ACTION_INSTALL)
79 def remove(self):107 def remove(self):
80 """ remove the current application, , fire an action request """108 """ remove the current application, , fire an action request """
81 self.emit("application-request-action", self.app, APP_ACTION_REMOVE)109 self.emit("application-request-action", self.app, self.addons_install, self.addons_remove, APP_ACTION_REMOVE)
82 def upgrade(self):110 def upgrade(self):
83 """ upgrade the current application, fire an action request """111 """ upgrade the current application, fire an action request """
84 self.emit("application-request-action", self.app, APP_ACTION_UPGRADE)112 self.emit("application-request-action", self.app, self.addons_install, self.addons_remove, APP_ACTION_UPGRADE)
113 def apply_changes(self):
114 """ apply changes concerning add-ons """
115 self.emit("application-request-action", self.app, self.addons_install, self.addons_remove, APP_ACTION_APPLY)
85116
86 def buy_app(self):117 def buy_app(self):
87 """ initiate the purchase transaction """118 """ initiate the purchase transaction """
@@ -107,10 +138,10 @@
107 get_install_backend().add_repo_add_key_and_install_app(deb_line,138 get_install_backend().add_repo_add_key_and_install_app(deb_line,
108 signing_key_id,139 signing_key_id,
109 self.app)140 self.app)
110
111 # internal callbacks141 # internal callbacks
112 def _on_cache_ready(self, cache):142 def _on_cache_ready(self, cache):
113 # re-show the application if the cache changes, it may affect the143 # re-show the application if the cache changes, it may affect the
114 # current application144 # current application
115 self._logger.debug("on_cache_ready")145 self._logger.debug("on_cache_ready")
116 self.show_app(self.app)146 self.show_app(self.app)
147 self.addons_manager = PackageAddonsManager(cache)
117148
=== modified file 'softwarecenter/view/appdetailsview_gtk.py'
--- softwarecenter/view/appdetailsview_gtk.py 2010-08-13 14:25:11 +0000
+++ softwarecenter/view/appdetailsview_gtk.py 2010-08-16 20:56:42 +0000
@@ -33,8 +33,10 @@
33import cairo33import cairo
3434
35from gettext import gettext as _35from gettext import gettext as _
36import apt_pkg
36from softwarecenter.backend import get_install_backend37from softwarecenter.backend import get_install_backend
37from softwarecenter.db.application import AppDetails38from softwarecenter.db.application import AppDetails, Application
39from softwarecenter.apt.aptcache import AptCache
38from softwarecenter.enums import *40from softwarecenter.enums import *
39from softwarecenter.paths import SOFTWARE_CENTER_ICON_CACHE_DIR41from softwarecenter.paths import SOFTWARE_CENTER_ICON_CACHE_DIR
40from softwarecenter.utils import ImageDownloader42from softwarecenter.utils import ImageDownloader
@@ -150,7 +152,8 @@
150 if state in (PKG_STATE_INSTALLING,152 if state in (PKG_STATE_INSTALLING,
151 PKG_STATE_INSTALLING_PURCHASED,153 PKG_STATE_INSTALLING_PURCHASED,
152 PKG_STATE_REMOVING,154 PKG_STATE_REMOVING,
153 PKG_STATE_UPGRADING):155 PKG_STATE_UPGRADING,
156 APP_ACTION_APPLY):
154 self.button.hide()157 self.button.hide()
155 self.show()158 self.show()
156 elif state == PKG_STATE_NOT_FOUND:159 elif state == PKG_STATE_NOT_FOUND:
@@ -159,8 +162,8 @@
159 self.button.set_sensitive(False)162 self.button.set_sensitive(False)
160 self.button.show()163 self.button.show()
161 self.show()164 self.show()
162 else:
163 state = app_details.pkg_state165 state = app_details.pkg_state
166 else:
164 self.button.set_sensitive(True)167 self.button.set_sensitive(True)
165 self.button.show()168 self.button.show()
166 self.show()169 self.show()
@@ -202,7 +205,7 @@
202 if app_details.price:205 if app_details.price:
203 self.set_label(app_details.price)206 self.set_label(app_details.price)
204 else:207 else:
205 self.set_label("")208 self.set_label("Free")
206 self.set_button_label(_('Install'))209 self.set_button_label(_('Install'))
207 elif state == PKG_STATE_REINSTALLABLE:210 elif state == PKG_STATE_REINSTALLABLE:
208 if app_details.price:211 if app_details.price:
@@ -213,6 +216,11 @@
213 elif state == PKG_STATE_UPGRADABLE:216 elif state == PKG_STATE_UPGRADABLE:
214 self.set_label(_('Upgrade Available'))217 self.set_label(_('Upgrade Available'))
215 self.set_button_label(_('Upgrade'))218 self.set_button_label(_('Upgrade'))
219 elif state == APP_ACTION_APPLY:
220 self.set_label(_('Changing add-ons...'))
221 elif state == PKG_STATE_UNKNOWN:
222 self.set_button_label("")
223 self.set_label(_("Error"))
216 elif state == PKG_STATE_ERROR:224 elif state == PKG_STATE_ERROR:
217 # this is used when the pkg can not be installed225 # this is used when the pkg can not be installed
218 # we display the error in the description field226 # we display the error in the description field
@@ -411,78 +419,51 @@
411 self.show_all()419 self.show_all()
412 return 420 return
413421
414422class PackageInfo(gtk.HBox):
415class PackageInfoTable(gtk.VBox):423
416424 def __init__(self, key):
417 def __init__(self):425 gtk.HBox.__init__(self, spacing=mkit.SPACING_XLARGE)
418 gtk.VBox.__init__(self, spacing=mkit.SPACING_MED)426 self.key = key
419427 self.value_object = gtk.Label()
420 self.version_label = gtk.Label()
421 self.license_label = gtk.Label()
422 self.support_label = gtk.Label()
423
424 self.version_label.set_selectable(True)
425
426 self.connect('realize', self._on_realize)428 self.connect('realize', self._on_realize)
427 return429 return
428430
429 def _on_realize(self, widget):431 def _on_realize(self, widget):
432 # key
433 k = gtk.Label()
430 dark = self.style.dark[self.state].to_string()434 dark = self.style.dark[self.state].to_string()
431 key_markup = '<b><span color="%s">%s</span></b>'435 key_markup = '<b><span color="%s">%s</span></b>'
432 max_lw = 0 # max key label width436 k.set_markup(key_markup % (dark, self.key))
433437 a = gtk.Alignment(1.0, 0.0)
434 for kstr, v in [(_('Version:'), self.version_label),438 # the line below is 'wrong', but in reality it works quite ok
435 (_('License:'), self.license_label),439 a.set_size_request(100, -1)
436 (_('Updates:'), self.support_label)]:440 a.add(k)
437441 self.pack_start(a, False)
438 k = gtk.Label()442
439 k.set_markup(key_markup % (dark, kstr))443 # value
440 v.set_line_wrap(True)444 v = self.value_object
441 max_lw = max(max_lw, k.get_layout().get_pixel_extents()[1][2])445 v.set_line_wrap(True)
442446 v.set_selectable(True)
443 a = gtk.Alignment(1.0, 0.0)447 b = gtk.Alignment(0.0, 0.0)
444 a.add(k)448 b.add(v)
445449 self.pack_start(b, False)
446 # we need this extra box to avoid orca repeating itself450
447 b = gtk.Alignment(0.0, 0.0)451 # a11y stuff
448 b.add(v)452 self.set_property("can-focus", True)
449453 self.a11y = self.get_accessible()
450 row = gtk.HBox(spacing=mkit.SPACING_XLARGE)
451 row.pack_start(a, False)
452 row.pack_start(b, False)
453
454 # a11y stuff
455 row.set_property("can-focus", True)
456 row.a11y = row.get_accessible()
457 row.a11y.set_name(kstr)
458
459 self.pack_start(row, False)
460
461 for row in self.get_children():
462 k, v = row.get_children()
463 k.set_size_request(max_lw+3*mkit.EM, -1)
464454
465 self.show_all()455 self.show_all()
466 return456 return
467457
468 def set_width(self, width):458 def set_width(self, width):
469 for row in self.get_children():459 if self.get_children():
470 k, v = row.get_children()460 k, v = self.get_children()
471 v.set_size_request(width-k.allocation.width-row.get_spacing(), -1)461 v.set_size_request(width-k.allocation.width-self.get_spacing(), -1)
472 return462 return
473463
474 def configure(self, version, license, updates):464 def set_value(self, value):
475465 self.value_object.set_text(value)
476 # set labels466 self.a11y.set_name(self.key + ' ' + value)
477 self.version_label.set_text(version)
478 self.license_label.set_text(license)
479 self.support_label.set_text(updates)
480
481 # set a11y texts
482 for row in self.get_children():
483 key = row.a11y.get_name().split(":")[0]
484 value = row.get_children()[1].get_children()[0].get_text()
485 row.a11y.set_name(key + ': ' + value)
486467
487class ScreenshotView(gtk.Alignment):468class ScreenshotView(gtk.Alignment):
488469
@@ -784,6 +765,215 @@
784 cr.fill()765 cr.fill()
785 return766 return
786767
768class AddonCheckButton(gtk.HBox):
769 """ A widget that represents an add-on:
770 |CheckButton|Icon|Description| """
771
772 __gsignals__ = {'toggled': (gobject.SIGNAL_RUN_FIRST,
773 gobject.TYPE_NONE,
774 ()),
775 }
776
777 def __init__(self, db, icons, pkgname):
778 gtk.HBox.__init__(self, spacing=mkit.SPACING_LARGE)
779 self.app_details = AppDetails(db,
780 application=Application("None", pkgname))
781 # the checkbutton
782 self.checkbutton = gtk.CheckButton()
783 self.checkbutton.connect("toggled", self._on_checkbutton_toggled)
784 self.pack_start(self.checkbutton, False)
785 # the hbox inside the checkbutton that contains the icon and description
786 hbox = gtk.HBox(spacing=mkit.SPACING_MED)
787 image = gtk.Image()
788 icon = self.app_details.icon
789 if not icon or not icons.has_icon(icon):
790 icon = MISSING_APP_ICON
791 try:
792 pixbuf = icons.load_icon(icon, 22, ()).scale_simple(22, 22, gtk.gdk.INTERP_BILINEAR)
793 image.set_from_pixbuf(pixbuf)
794 except TypeError:
795 logging.warning("cant set icon for '%s' " % pkgname)
796 hbox.pack_start(image, False, False)
797 # the display_name
798 display_name = _("%(summary)s (%(pkgname)s)") % {'summary': self.app_details.display_name.capitalize(),
799 'pkgname': pkgname}
800 label = gtk.Label(display_name)
801 hbox.pack_start(label, False)
802 # and put it into the the checkbox
803 self.checkbutton.add(hbox)
804 # this is the addon_pkgname
805 #self.addon_pkgname = gtk.Label(_(" (%(pkgname)s)") % {
806 # 'pkgname' : pkgname } )
807 #hbox.pack_start(self.addon_pkgname, False)
808
809
810 def _on_checkbutton_toggled(self, checkbutton):
811 self.emit("toggled")
812 def get_active(self):
813 return self.checkbutton.get_active()
814 def set_active(self, is_active):
815 self.checkbutton.set_active(is_active)
816 def get_addon(self):
817 return self.app_details.pkgname
818
819
820class AddonView(gtk.VBox):
821 """ A widget that handles the application add-ons """
822 # TODO: sort add-ons in alphabetical order
823
824 __gsignals__ = {'toggled':(gobject.SIGNAL_RUN_FIRST,
825 gobject.TYPE_NONE,
826 (str, gobject.TYPE_PYOBJECT)),
827 'description-clicked':(gobject.SIGNAL_RUN_FIRST,
828 gobject.TYPE_NONE,
829 (str, )),
830 }
831
832 def __init__(self, cache, db, icons):
833 gtk.VBox.__init__(self, False, mkit.SPACING_MED)
834 self.cache = cache
835 self.db = db
836 self.icons = icons
837 self.recommended_addons = None
838 self.suggested_addons = None
839
840 self.label = gtk.Label(_("<b>Choose add-ons:</b>"))
841 self.label.set_use_markup(True)
842 self.label.set_alignment(0, 0.5)
843 self.pack_start(self.label, False, False)
844
845 def set_addons(self, app_details, recommended, suggested):
846 if len(recommended) == 0 and len(suggested) == 0:
847 return
848 self.recommended_addons = recommended
849 self.suggested_addons = suggested
850 self.app_details = app_details
851
852 for widget in self:
853 if widget != self.label:
854 self.remove(widget)
855
856 for addon in recommended:
857 try:
858 pkg = self.cache[addon]
859 except KeyError:
860 continue
861 checkbutton = AddonCheckButton(self.db, self.icons, addon)
862 #checkbutton.addon_pkgname.connect(
863 # "clicked", self._on_description_clicked, addon)
864 checkbutton.set_active(pkg.installed != None)
865 checkbutton.connect("toggled", self._on_checkbutton_toggled)
866 self.pack_start(checkbutton, False)
867 for addon in suggested:
868 try:
869 pkg = self.cache[addon]
870 except KeyError:
871 continue
872 checkbutton = AddonCheckButton(self.db, self.icons, addon)
873 #checkbutton.addon_pkgname.connect(
874 # "clicked", self._on_description_clicked, addon)
875 checkbutton.set_active(pkg.installed != None)
876 checkbutton.connect("toggled", self._on_checkbutton_toggled)
877 self.pack_start(checkbutton, False)
878 self.show_all()
879 return False
880
881 def _on_checkbutton_toggled(self, checkbutton):
882 addon = checkbutton.get_addon()
883 self.emit("toggled", addon, checkbutton.get_active())
884
885 def _on_description_clicked(self, label, addon):
886 self.emit("description-clicked", addon)
887
888class AddonsStateBar(gtk.Alignment):
889 __gsignals__ = {'changes-canceled': (gobject.SIGNAL_RUN_FIRST,
890 gobject.TYPE_NONE,
891 ()),
892 }
893
894 def __init__(self, cache, view):
895 gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0)
896 self.set_redraw_on_allocate(False)
897 self.set_padding(mkit.SPACING_LARGE,
898 mkit.SPACING_LARGE,
899 mkit.SPACING_SMALL+2,
900 mkit.SPACING_SMALL)
901
902 self.hbox = gtk.HBox(spacing=mkit.SPACING_LARGE)
903 self.add(self.hbox)
904
905 self.cache = cache
906 self.view = view
907 self.applying = False
908
909 self.label_price = gtk.Label()
910 self.label_price.set_line_wrap(True)
911 self.hbox.pack_start(self.label_price, False, False)
912
913 self.hbuttonbox = gtk.HButtonBox()
914 self.hbuttonbox.set_layout(gtk.BUTTONBOX_END)
915 self.button_apply = gtk.Button(_("Apply Changes"))
916 self.button_apply.connect("clicked", self._on_button_apply_clicked)
917 self.button_cancel = gtk.Button(_("Cancel"))
918 self.button_cancel.connect("clicked", self._on_button_cancel_clicked)
919 self.hbuttonbox.pack_start(self.button_cancel, False)
920 self.hbuttonbox.pack_start(self.button_apply, False)
921 self.hbox.pack_start(self.hbuttonbox)
922
923 self.fill_color = COLOR_GREEN_FILL
924 self.line_color = COLOR_GREEN_OUTLINE
925
926 def configure(self, app_details, addons_install, addons_remove):
927 if not addons_install and not addons_remove:
928 self.hide()
929 return
930 if app_details.price:
931 self.label_price.set_label(app_details.price)
932 else:
933 self.label_price.set_label(_("Free"))
934 self.show()
935
936 def draw(self, cr, a, expose_area):
937 if mkit.not_overlapping(a, expose_area): return
938
939 cr.save()
940 rr = mkit.ShapeRoundedRectangle()
941 rr.layout(cr,
942 a.x-1, a.y-1,
943 a.x+a.width, a.y+a.height,
944 radius=mkit.CORNER_RADIUS)
945
946 cr.set_source_rgb(*mkit.floats_from_string(self.fill_color))
947# cr.set_source_rgb(*mkit.floats_from_string(self.line_color))
948 cr.fill()
949
950 cr.set_line_width(1)
951 cr.translate(0.5, 0.5)
952
953 rr.layout(cr,
954 a.x-1, a.y-1,
955 a.x+a.width, a.y+a.height,
956 radius=mkit.CORNER_RADIUS)
957
958 cr.set_source_rgb(*mkit.floats_from_string(self.line_color))
959 cr.stroke()
960 cr.restore()
961 return
962
963 def get_applying(self):
964 return self.applying
965 def set_applying(self, applying):
966 self.applying = applying
967
968 def _on_button_apply_clicked(self, button):
969 self.applying = True
970 self.button_apply.set_sensitive(False)
971 self.button_cancel.set_sensitive(False)
972 AppDetailsViewBase.apply_changes(self.view)
973
974 def _on_button_cancel_clicked(self, button):
975 self.emit("changes-canceled")
976
787977
788class AppDetailsViewGtk(gtk.Viewport, AppDetailsViewBase):978class AppDetailsViewGtk(gtk.Viewport, AppDetailsViewBase):
789979
@@ -804,8 +994,14 @@
804 (gobject.TYPE_PYOBJECT,)),994 (gobject.TYPE_PYOBJECT,)),
805 'application-request-action' : (gobject.SIGNAL_RUN_LAST,995 'application-request-action' : (gobject.SIGNAL_RUN_LAST,
806 gobject.TYPE_NONE,996 gobject.TYPE_NONE,
807 (gobject.TYPE_PYOBJECT, str),997 (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str),
808 ),998 ),
999 "navigation-request" : ( gobject.SIGNAL_RUN_LAST,
1000 gobject.TYPE_NONE,
1001 (str,
1002 ),
1003 ),
1004
809 }1005 }
8101006
8111007
@@ -854,7 +1050,9 @@
854 for pt in self.app_desc.points:1050 for pt in self.app_desc.points:
855 pt.set_size_request(w-7*mkit.EM-166, -1)1051 pt.set_size_request(w-7*mkit.EM-166, -1)
8561052
857 self.info_table.set_width(w-6*mkit.EM)1053 self.version_info.set_width(w-6*mkit.EM)
1054 self.license_info.set_width(w-6*mkit.EM)
1055 self.support_info.set_width(w-6*mkit.EM)
8581056
859 self._full_redraw() # ewww1057 self._full_redraw() # ewww
860 return1058 return
@@ -886,6 +1084,11 @@
886 self.action_bar.draw(cr,1084 self.action_bar.draw(cr,
887 self.action_bar.allocation,1085 self.action_bar.allocation,
888 event.area)1086 event.area)
1087
1088 if self.addons_bar.get_property('visible'):
1089 self.addons_bar.draw(cr,
1090 self.addons_bar.hbox.allocation,
1091 event.area)
8891092
890 if self.screenshot.get_property('visible'):1093 if self.screenshot.get_property('visible'):
891 self.screenshot.draw(cr, self.screenshot.allocation, expose_area)1094 self.screenshot.draw(cr, self.screenshot.allocation, expose_area)
@@ -957,17 +1160,17 @@
9571160
958 # we have our own viewport so we know when the viewport grows/shrinks1161 # we have our own viewport so we know when the viewport grows/shrinks
959 self.vbox.set_redraw_on_allocate(False)1162 self.vbox.set_redraw_on_allocate(False)
9601163
961 # framed section that contains all app details1164 # framed section that contains all app details
962 self.app_info = mkit.FramedSection()1165 self.app_info = mkit.FramedSection()
963 self.app_info.image.set_size_request(84, 84)1166 self.app_info.image.set_size_request(84, 84)
964 self.app_info.set_spacing(mkit.SPACING_LARGE)1167 self.app_info.set_spacing(mkit.SPACING_XLARGE)
965 self.app_info.header.set_spacing(mkit.SPACING_XLARGE)1168 self.app_info.header.set_spacing(mkit.SPACING_XLARGE)
966 self.app_info.header_alignment.set_padding(mkit.SPACING_SMALL,1169 self.app_info.header_alignment.set_padding(mkit.SPACING_SMALL,
967 mkit.SPACING_SMALL,1170 mkit.SPACING_SMALL,
968 0, 0)1171 0, 0)
9691172
970 self.app_info.body.set_spacing(mkit.SPACING_LARGE)1173 self.app_info.body.set_spacing(mkit.SPACING_MED)
971 self.vbox.pack_start(self.app_info, False)1174 self.vbox.pack_start(self.app_info, False)
9721175
973 # a11y for name/summary1176 # a11y for name/summary
@@ -977,9 +1180,9 @@
977 # controls which are displayed if the app is installed1180 # controls which are displayed if the app is installed
978 self.action_bar = PackageStatusBar(self)1181 self.action_bar = PackageStatusBar(self)
979 self.app_info.body.pack_start(self.action_bar, False)1182 self.app_info.body.pack_start(self.action_bar, False)
9801183
981 # FramedSection which contains the app description1184 # FramedSection which contains the app description
982 self.desc_section = mkit.FramedSection(xpadding=mkit.SPACING_LARGE)1185 self.desc_section = mkit.FramedSection(xpadding=mkit.SPACING_XLARGE)
983 self.desc_section.header_alignment.set_padding(0,0,0,0)1186 self.desc_section.header_alignment.set_padding(0,0,0,0)
9841187
985 self.app_info.body.pack_start(self.desc_section, False)1188 self.app_info.body.pack_start(self.desc_section, False)
@@ -998,7 +1201,7 @@
998 # screenshot1201 # screenshot
999 self.screenshot = ScreenshotView(self.distro, self.icons)1202 self.screenshot = ScreenshotView(self.distro, self.icons)
1000 app_desc_hb.pack_end(self.screenshot)1203 app_desc_hb.pack_end(self.screenshot)
10011204
1002 # homepage link button1205 # homepage link button
1003 self.homepage_btn = mkit.HLinkButton(_('Website'))1206 self.homepage_btn = mkit.HLinkButton(_('Website'))
1004 self.homepage_btn.connect('clicked', self._on_homepage_clicked)1207 self.homepage_btn.connect('clicked', self._on_homepage_clicked)
@@ -1014,9 +1217,32 @@
1014 self.share_btn.connect('clicked', self._on_share_clicked)1217 self.share_btn.connect('clicked', self._on_share_clicked)
1015 self.app_desc.footer.pack_start(self.share_btn, False)1218 self.app_desc.footer.pack_start(self.share_btn, False)
10161219
1017 # package info table1220 alignment = gtk.Alignment()
1018 self.info_table = PackageInfoTable()1221 alignment.set_padding(mkit.SPACING_LARGE, 0, 0, 0)
1019 self.app_info.body.pack_start(self.info_table, False)1222 self.desc_section.body.pack_start(alignment, False)
1223
1224 # add-on handling
1225 self.addon_view = AddonView(self.cache, self.db, self.icons)
1226 self.addon_view.connect("toggled", self._on_addon_view_toggled)
1227 self.addon_view.connect("description-clicked", self._on_addon_view_description_clicked)
1228 alignment.add(self.addon_view)
1229
1230 self.totalsize_info = PackageInfo(_("Total size:"))
1231 self.app_info.body.pack_start(self.totalsize_info, False)
1232
1233 self.addons_bar = AddonsStateBar(self.cache, self)
1234 self.addons_bar.connect("changes-canceled", self._on_addonsbar_changescanceled)
1235 self.app_info.body.pack_start(self.addons_bar, False)
1236
1237 # package info
1238 self.version_info = PackageInfo(_("Version:"))
1239 self.app_info.body.pack_start(self.version_info, False)
1240
1241 self.license_info = PackageInfo(_("License:"))
1242 self.app_info.body.pack_start(self.license_info, False)
1243
1244 self.support_info = PackageInfo(_("Updates:"))
1245 self.app_info.body.pack_start(self.support_info, False)
10201246
1021 self.show_all()1247 self.show_all()
1022 return1248 return
@@ -1053,12 +1279,16 @@
10531279
1054 # if we have an error or if we need to enable a source, then hide everything else1280 # if we have an error or if we need to enable a source, then hide everything else
1055 if app_details.pkg_state in (PKG_STATE_NOT_FOUND, PKG_STATE_NEEDS_SOURCE):1281 if app_details.pkg_state in (PKG_STATE_NOT_FOUND, PKG_STATE_NEEDS_SOURCE):
1056 self.info_table.hide()
1057 self.screenshot.hide()1282 self.screenshot.hide()
1283 self.version_info.hide()
1284 self.license_info.hide()
1285 self.support_info.hide()
1058 self.desc_section.hide()1286 self.desc_section.hide()
1059 else:1287 else:
1060 self.desc_section.show()1288 self.desc_section.show()
1061 self.info_table.show()1289 self.version_info.show()
1290 self.license_info.show()
1291 self.support_info.show()
1062 self.screenshot.show()1292 self.screenshot.show()
10631293
1064 # depending on pkg install state set action labels1294 # depending on pkg install state set action labels
@@ -1075,14 +1305,14 @@
1075 self.app_desc.body.a11y.set_name("Description: " + description)1305 self.app_desc.body.a11y.set_name("Description: " + description)
10761306
1077 # show or hide the homepage button and set uri if homepage specified1307 # show or hide the homepage button and set uri if homepage specified
1078 if app_details.website and self.info_table.get_property('visible'):1308 if app_details.website:
1079 self.homepage_btn.show()1309 self.homepage_btn.show()
1080 self.homepage_btn.set_tooltip_text(app_details.website)1310 self.homepage_btn.set_tooltip_text(app_details.website)
1081 else:1311 else:
1082 self.homepage_btn.hide()1312 self.homepage_btn.hide()
10831313
1084 # check if gwibber-poster is available, if so display Share... btn1314 # check if gwibber-poster is available, if so display Share... btn
1085 if self._gwibber_is_available and self.info_table.get_property('visible'):1315 if self._gwibber_is_available:
1086 self.share_btn.show()1316 self.share_btn.show()
1087 else:1317 else:
1088 self.share_btn.hide()1318 self.share_btn.hide()
@@ -1107,7 +1337,21 @@
1107 support = app_details.maintenance_status1337 support = app_details.maintenance_status
1108 else:1338 else:
1109 support = _("Unknown")1339 support = _("Unknown")
1110 self.info_table.configure(version, license, support)1340
1341 self.version_info.set_value(version)
1342 self.license_info.set_value(license)
1343 self.support_info.set_value(support)
1344
1345 # Update add-on interface
1346 self.addon_view.hide_all()
1347 gobject.idle_add(self.addon_view.set_addons, self.app_details, self.recommended, self.suggested)
1348
1349 # Update total size label
1350 self.totalsize_info.hide_all()
1351 gobject.idle_add(self.update_totalsize)
1352
1353 # Update addons state bar
1354 self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove)
1111 return1355 return
11121356
1113 # public API1357 # public API
@@ -1129,6 +1373,14 @@
1129 # init data1373 # init data
1130 self.app = app1374 self.app = app
1131 self.app_details = app.get_details(self.db)1375 self.app_details = app.get_details(self.db)
1376 self.recommended = self.addons_manager.recommended_addons(app.pkgname)
1377 self.suggested = self.addons_manager.suggested_addons(app.pkgname)
1378 LOG.debug("AppDetailsView.show_app recommended '%s'" % self.recommended)
1379 LOG.debug("AppDetailsView.show_app suggested '%s'" % self.suggested)
1380
1381 self.addons_install = []
1382 self.addons_remove = []
1383
1132 # for compat with the base class1384 # for compat with the base class
1133 self.appdetails = self.app_details1385 self.appdetails = self.app_details
1134 #print "AppDetailsViewGtk:"1386 #print "AppDetailsViewGtk:"
@@ -1150,7 +1402,31 @@
1150 def _update_interface_on_trans_ended(self, result):1402 def _update_interface_on_trans_ended(self, result):
1151 self.action_bar.button.set_sensitive(True)1403 self.action_bar.button.set_sensitive(True)
1152 self.action_bar.button.show()1404 self.action_bar.button.show()
1405 self.addons_bar.button_apply.set_sensitive(True)
1406 self.addons_bar.button_cancel.set_sensitive(True)
1407 state = self.action_bar.pkg_state
1408 pkg_state = None
1409 if state == PKG_STATE_INSTALLING or state == PKG_STATE_UPGRADING \
1410 or self.addons_bar.applying:
1411 self.addons_bar.show_all()
1412 pkg_state = PKG_STATE_INSTALLED
1413 else:
1414 self.addons_bar.hide_all()
1415 pkg_state = PKG_STATE_UNINSTALLED
11531416
1417 if self.addons_bar.applying:
1418 self.action_bar.configure(self.app_details, pkg_state)
1419 self.addons_install = []
1420 self.addons_remove = []
1421 self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove)
1422 self.addons_bar.applying = False
1423
1424 for widget in self.addon_view:
1425 if widget != self.addon_view.label:
1426 addon = widget.get_addon()
1427 widget.set_active(self.cache[addon].installed != None)
1428 return False
1429
1154 state = self.action_bar.pkg_state1430 state = self.action_bar.pkg_state
1155 # handle purchase: install purchased has multiple steps1431 # handle purchase: install purchased has multiple steps
1156 if (state == PKG_STATE_INSTALLING_PURCHASED and 1432 if (state == PKG_STATE_INSTALLING_PURCHASED and
@@ -1164,14 +1440,24 @@
1164 # normal states1440 # normal states
1165 elif state == PKG_STATE_REMOVING:1441 elif state == PKG_STATE_REMOVING:
1166 self.action_bar.configure(self.app_details, PKG_STATE_UNINSTALLED)1442 self.action_bar.configure(self.app_details, PKG_STATE_UNINSTALLED)
1443 self.addons_install = []
1444 self.addons_remove = []
1167 elif state == PKG_STATE_INSTALLING:1445 elif state == PKG_STATE_INSTALLING:
1168 self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED)1446 self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED)
1447 self.addons_install = []
1448 self.addons_remove = []
1449 self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove)
1169 elif state == PKG_STATE_UPGRADING:1450 elif state == PKG_STATE_UPGRADING:
1170 self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED)1451 self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED)
1171 return False1452 return False
11721453
1173 def _on_transaction_started(self, backend):1454 def _on_transaction_started(self, backend):
1174 self.action_bar.button.hide()1455 self.action_bar.button.hide()
1456
1457 if self.addons_bar.get_applying():
1458 self.action_bar.configure(self.app_details, APP_ACTION_APPLY)
1459 return
1460
1175 state = self.action_bar.pkg_state1461 state = self.action_bar.pkg_state
1176 LOG.debug("_on_transaction_stated %s" % state)1462 LOG.debug("_on_transaction_stated %s" % state)
1177 if state == PKG_STATE_NEEDS_PURCHASE:1463 if state == PKG_STATE_NEEDS_PURCHASE:
@@ -1205,6 +1491,8 @@
1205 gobject.idle_add(self._show_prog_idle_cb)1491 gobject.idle_add(self._show_prog_idle_cb)
1206 if pkgname in backend.pending_transactions:1492 if pkgname in backend.pending_transactions:
1207 self.action_bar.progress.set_fraction(progress/100.0)1493 self.action_bar.progress.set_fraction(progress/100.0)
1494 if progress == 100:
1495 self.action_bar.progress.set_fraction(1)
1208 return1496 return
12091497
1210 def _show_prog_idle_cb(self):1498 def _show_prog_idle_cb(self):
@@ -1293,18 +1581,143 @@
1293 return self.icons.load_icon(MISSING_APP_ICON, 84, 0)1581 return self.icons.load_icon(MISSING_APP_ICON, 84, 0)
1294 elif app_details.icon_needs_download:1582 elif app_details.icon_needs_download:
1295 self._logger.debug("did not find the icon locally, must download it")1583 self._logger.debug("did not find the icon locally, must download it")
12961584 else:
1297 def on_image_download_complete(downloader, image_file_path):1585 return self.icons.load_icon(MISSING_APP_ICON, 84, 0)
1298 # when the download is complete, replace the icon in the view with the downloaded one1586 else:
1299 pb = gtk.gdk.pixbuf_new_from_file(image_file_path)1587 return self.icons.load_icon(MISSING_APP_ICON, 84, 0)
1300 self.app_info.set_icon_from_pixbuf(pb)1588
1301 1589 def _on_addon_view_description_clicked(self, button, pkgname):
1302 icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, app_details.icon_file_name)1590 self.emit("navigation-request", pkgname)
1303 image_downloader = ImageDownloader()1591 return
1304 image_downloader.connect('image-download-complete', on_image_download_complete)1592
1305 image_downloader.download_image(app_details.icon_url, icon_file_path)1593 def _on_addon_view_toggled(self, view, addon, isActive):
1594 if isActive:
1595 self._set_addon_install(addon)
1596 else:
1597 self._set_addon_remove(addon)
1598 if self.app_details.pkg_state == PKG_STATE_INSTALLED:
1599 self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove)
1600 gobject.idle_add(self.update_totalsize)
1601
1602 def _on_addonsbar_changescanceled(self, widget):
1603 self.addons_install = []
1604 self.addons_remove = []
1605 self.addon_view.set_addons(self.app_details,
1606 self.recommended,
1607 self.suggested)
1608 self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove)
1609 gobject.idle_add(self.update_totalsize)
1610
1611 def get_icon_filename(self, iconname, iconsize):
1612 iconinfo = self.icons.lookup_icon(iconname, iconsize, 0)
1613 if not iconinfo:
1614 iconinfo = self.icons.lookup_icon(MISSING_APP_ICON, iconsize, 0)
1615 return iconinfo.get_filename()
1616
1617 def on_image_download_complete(downloader, image_file_path):
1618 # when the download is complete, replace the icon in the view with the downloaded one
1619 pb = gtk.gdk.pixbuf_new_from_file(image_file_path)
1620 self.app_info.set_icon_from_pixbuf(pb)
1621
1622 icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, app_details.icon_file_name)
1623 image_downloader = ImageDownloader()
1624 image_downloader.connect('image-download-complete', on_image_download_complete)
1625 image_downloader.download_image(app_details.icon_url, icon_file_path)
1306 1626
1307 return self.icons.load_icon(MISSING_APP_ICON, 84, 0)1627 return self.icons.load_icon(MISSING_APP_ICON, 84, 0)
1628
1629 def update_totalsize(self):
1630 def pkg_downloaded(pkg_version):
1631 filename = os.path.basename(pkg_version.filename)
1632 # FIXME: use relative path here
1633 return os.path.exists("/var/cache/apt/archives/" + filename)
1634
1635 while gtk.events_pending():
1636 gtk.main_iteration()
1637
1638 pkgs_to_install = []
1639 pkgs_to_remove = []
1640 total_download_size = 0 # in kB
1641 total_install_size = 0 # in kB
1642 label_string = ""
1643
1644 try:
1645 pkg = self.cache[self.app_details.pkgname]
1646 except KeyError:
1647 self.totalsize_info.hide_all()
1648 return False
1649 version = pkg.installed
1650 if version == None:
1651 version = max(pkg.versions)
1652 pkgs_to_install.append(version)
1653 deps_inst = self.cache.get_all_deps_installing(pkg)
1654 for dep in deps_inst:
1655 if self.cache[dep].installed == None:
1656 version = max(self.cache[dep].versions)
1657 pkgs_to_install.append(version)
1658 deps_remove = self.cache.get_all_deps_removing(pkg)
1659 for dep in deps_remove:
1660 if self.cache[dep].installed != None:
1661 version = self.cache[dep].installed
1662 pkgs_to_remove.append(version)
1663
1664 for addon in self.addons_install:
1665 version = max(self.cache[addon].versions)
1666 pkgs_to_install.append(version)
1667 deps_inst = self.cache.get_all_deps_installing(self.cache[addon])
1668 for dep in deps_inst:
1669 if self.cache[dep].installed == None:
1670 version = max(self.cache[dep].versions)
1671 pkgs_to_install.append(version)
1672 deps_remove = self.cache.get_all_deps_removing(self.cache[addon])
1673 for dep in deps_remove:
1674 if self.cache[dep].installed != None:
1675 version = self.cache[dep].installed
1676 pkgs_to_remove.append(version)
1677 for addon in self.addons_remove:
1678 version = self.cache[addon].installed
1679 pkgs_to_remove.append(version)
1680 deps_inst = self.cache.get_all_deps_installing(self.cache[addon])
1681 for dep in deps_inst:
1682 if self.cache[dep].installed == None:
1683 version = max(self.cache[dep].versions)
1684 pkgs_to_install.append(version)
1685 deps_remove = self.cache.get_all_deps_removing(self.cache[addon])
1686 for dep in deps_remove:
1687 if self.cache[dep].installed != None:
1688 version = self.cache[dep].installed
1689 pkgs_to_remove.append(version)
1690
1691 for pkg in pkgs_to_install:
1692 if pkgs_to_install.count(pkg) > 1:
1693 pkgs_to_install.remove(pkg)
1694 for pkg in pkgs_to_remove:
1695 if pkgs_to_remove.count(pkg) > 1:
1696 pkgs_to_remove.remove(pkg)
1697
1698 for pkg in pkgs_to_install:
1699 if not pkg_downloaded(pkg):
1700 total_download_size += pkg.size
1701 total_install_size += pkg.installed_size
1702 for pkg in pkgs_to_remove:
1703 total_install_size -= pkg.installed_size
1704
1705 if total_download_size > 0:
1706 download_size = apt_pkg.size_to_str(total_download_size)
1707 label_string += _("%sB to download, " % (download_size))
1708 if total_install_size > 0:
1709 install_size = apt_pkg.size_to_str(total_install_size)
1710 label_string += _("%sB when installed" % (install_size))
1711 elif total_install_size < 0:
1712 remove_size = apt_pkg.size_to_str(-total_install_size)
1713 label_string += _("%sB to be freed" % (remove_size))
1714
1715 if label_string == "":
1716 self.totalsize_info.hide_all()
1717 else:
1718 self.totalsize_info.set_value(label_string)
1719 self.totalsize_info.show_all()
1720 return False
13081721
13091722
1310if __name__ == "__main__":1723if __name__ == "__main__":
13111724
=== modified file 'test/test_appdetails_view.py'
--- test/test_appdetails_view.py 2010-08-11 19:43:05 +0000
+++ test/test_appdetails_view.py 2010-08-16 20:56:42 +0000
@@ -65,6 +65,27 @@
65 for i in range(PKG_STATE_UNKNOWN):65 for i in range(PKG_STATE_UNKNOWN):
66 mock_app_details.pkg_state = i66 mock_app_details.pkg_state = i
67 self.appdetails.show_app(app)67 self.appdetails.show_app(app)
68
69 def test_show_app_addons(self):
70 app = Application("Web browser", "firefox")
71 mock_app_details = mock.Mock(AppDetails)
72 mock_app_details.pkgname = "firefox"
73 mock_app_details.appname = "Web browser"
74 mock_app_details.display_name = "display_name"
75 mock_app_details.display_summary = "display_summary"
76 mock_app_details.error = None
77 mock_app_details.warning = None
78 mock_app_details.description = "description"
79 mock_app_details.website = "website"
80 mock_app_details.thumbnail = None
81 mock_app_details.license = "license"
82 mock_app_details.maintenance_status = "support_status"
83 mock_app_details.purchase_date = "purchase_date"
84 mock_app_details.installation_date = "installation_date"
85 mock_app_details.price = "price"
86 mock_app_details._error_not_found = ""
87 app.get_details = lambda db: mock_app_details
88 self.appdetails.show_app(app)
68 89
6990
70if __name__ == "__main__":91if __name__ == "__main__":
7192
=== modified file 'test/test_aptd.py'
--- test/test_aptd.py 2010-07-08 15:13:27 +0000
+++ test/test_aptd.py 2010-08-16 20:56:42 +0000
@@ -41,6 +41,14 @@
41 keyserver = "keyserver.ubuntu.com"41 keyserver = "keyserver.ubuntu.com"
42 self.aptd.aptd_client.add_vendor_key_from_keyserver = self._monkey_patched_add_vendor_key_from_keyserver42 self.aptd.aptd_client.add_vendor_key_from_keyserver = self._monkey_patched_add_vendor_key_from_keyserver
43 self.aptd.add_vendor_key_from_keyserver(keyid, keyserver)43 self.aptd.add_vendor_key_from_keyserver(keyid, keyserver)
44
45 def test_apply_changes(self):
46 pkgname = "gimp"
47 appname = "The GIMP app"
48 iconname = "icon-gimp"
49 addons_install = ["gimp-data-extras", "gimp-gutenprint"]
50 addons_remove = ["gimp-plugin-registry"]
51 yield self.aptd.apply_changes(pkgname, appname ,iconname, addons_install, addons_remove)
4452
4553
46if __name__ == "__main__":54if __name__ == "__main__":
4755
=== modified file 'test/test_gui.py'
--- test/test_gui.py 2010-08-10 09:10:35 +0000
+++ test/test_gui.py 2010-08-16 20:56:42 +0000
@@ -166,7 +166,9 @@
166 self.app.show_available_packages(["i-dont-exit"])166 self.app.show_available_packages(["i-dont-exit"])
167 self._p()167 self._p()
168 self.assertFalse(self.app.available_pane.app_details.screenshot.get_property("visible"))168 self.assertFalse(self.app.available_pane.app_details.screenshot.get_property("visible"))
169 self.assertFalse(self.app.available_pane.app_details.info_table.get_property("visible"))169 self.assertFalse(self.app.available_pane.app_details.version_info.get_property("visible"))
170 self.assertFalse(self.app.available_pane.app_details.license_info.get_property("visible"))
171 self.assertFalse(self.app.available_pane.app_details.support_info.get_property("visible"))
170 self.assertFalse(self.app.available_pane.app_details.desc_section.get_property("visible"))172 self.assertFalse(self.app.available_pane.app_details.desc_section.get_property("visible"))
171173
172 # helper stuff174 # helper stuff

Subscribers

People subscribed via source and target branches