Merge lp:~ilidrissi.amine/software-center/addons into lp:software-center
- addons
- Merge into trunk
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 |
Related bugs: |
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 |
Commit message
Description of the change
This branch introduces add-on handling as described in https:/
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.
- 934. By Mohamed Amine Ilidrissi
-
merge with trunk
- 935. By Mohamed Amine Ilidrissi
-
Modified changelog.
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.
Mohamed Amine Ilidrissi (ilidrissi.amine) wrote : | # |
Okay, just fixed these issues. Feel free to request any other fixes.
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 PackageAddonsMa
- 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.
Michael Vogt (mvo) wrote : | # |
Thanks, code is good now, I filed a feature-
- 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.
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.
- 959. By Mohamed Amine Ilidrissi
-
Fixed padding issues.
- 960. By Mohamed Amine Ilidrissi
-
fixed AddonsStateBar padding issue.
Preview Diff
1 | === modified file 'debian/changelog' | |||
2 | --- debian/changelog 2010-08-13 14:55:27 +0000 | |||
3 | +++ debian/changelog 2010-08-16 20:56:42 +0000 | |||
4 | @@ -1,3 +1,19 @@ | |||
5 | 1 | software-center (2.1.11) UNRELEASED; urgency=low | ||
6 | 2 | |||
7 | 3 | [ Mohamed Amine IL Idrissi ] | ||
8 | 4 | * (all): Implemented add-on handling. | ||
9 | 5 | |||
10 | 6 | [ Kiwinote ] | ||
11 | 7 | * softwarecenter/view/appdetailsview_gtk.py: | ||
12 | 8 | - use package info lines rather than package info tables | ||
13 | 9 | (for devildante to use in the addons branch) | ||
14 | 10 | |||
15 | 11 | [ Milo Casagrande ] | ||
16 | 12 | * softwarecenter/db/application.py: | ||
17 | 13 | - make 'source available' warning more suitable for translation. | ||
18 | 14 | |||
19 | 15 | -- Michael Vogt <michael.vogt@ubuntu.com> Fri, 13 Aug 2010 16:46:13 +0200 | ||
20 | 16 | |||
21 | 1 | software-center (2.1.10) maverick; urgency=low | 17 | software-center (2.1.10) maverick; urgency=low |
22 | 2 | 18 | ||
23 | 3 | [ Kiwinote ] | 19 | [ Kiwinote ] |
24 | 4 | 20 | ||
25 | === modified file 'softwarecenter/app.py' | |||
26 | --- softwarecenter/app.py 2010-08-13 10:27:47 +0000 | |||
27 | +++ softwarecenter/app.py 2010-08-16 20:56:42 +0000 | |||
28 | @@ -206,6 +206,9 @@ | |||
29 | 206 | self.on_app_selected) | 206 | self.on_app_selected) |
30 | 207 | self.available_pane.app_details.connect("application-request-action", | 207 | self.available_pane.app_details.connect("application-request-action", |
31 | 208 | self.on_application_request_action) | 208 | self.on_application_request_action) |
32 | 209 | self.available_pane.app_details.connect("navigation-request", | ||
33 | 210 | self.on_application_request_navigation, | ||
34 | 211 | self.available_pane) | ||
35 | 209 | self.available_pane.app_view.connect("application-request-action", | 212 | self.available_pane.app_view.connect("application-request-action", |
36 | 210 | self.on_application_request_action) | 213 | self.on_application_request_action) |
37 | 211 | self.available_pane.connect("app-list-changed", | 214 | self.available_pane.connect("app-list-changed", |
38 | @@ -248,6 +251,9 @@ | |||
39 | 248 | self.on_app_selected) | 251 | self.on_app_selected) |
40 | 249 | self.installed_pane.app_details.connect("application-request-action", | 252 | self.installed_pane.app_details.connect("application-request-action", |
41 | 250 | self.on_application_request_action) | 253 | self.on_application_request_action) |
42 | 254 | self.installed_pane.app_details.connect("navigation-request", | ||
43 | 255 | self.on_application_request_navigation, | ||
44 | 256 | self.installed_pane) | ||
45 | 251 | self.installed_pane.app_view.connect("application-request-action", | 257 | self.installed_pane.app_view.connect("application-request-action", |
46 | 252 | self.on_application_request_action) | 258 | self.on_application_request_action) |
47 | 253 | self.installed_pane.connect("app-list-changed", | 259 | self.installed_pane.connect("app-list-changed", |
48 | @@ -450,7 +456,10 @@ | |||
49 | 450 | channel_display_name, icon=None, query=query) | 456 | channel_display_name, icon=None, query=query) |
50 | 451 | self.view_switcher.select_channel_node(channel_display_name, False) | 457 | self.view_switcher.select_channel_node(channel_display_name, False) |
51 | 452 | 458 | ||
53 | 453 | def on_application_request_action(self, widget, app, action): | 459 | def on_application_request_navigation(self, widget, pkgname, pane): |
54 | 460 | pane.show_app(Application("", pkgname)) | ||
55 | 461 | |||
56 | 462 | def on_application_request_action(self, widget, app, addons_install, addons_remove, action): | ||
57 | 454 | """callback when an app action is requested from the appview, | 463 | """callback when an app action is requested from the appview, |
58 | 455 | if action is "remove", must check if other dependencies have to be | 464 | if action is "remove", must check if other dependencies have to be |
59 | 456 | removed as well and show a dialog in that case | 465 | removed as well and show a dialog in that case |
60 | @@ -481,7 +490,7 @@ | |||
61 | 481 | self.backend.emit("transaction-stopped") | 490 | self.backend.emit("transaction-stopped") |
62 | 482 | return | 491 | return |
63 | 483 | 492 | ||
65 | 484 | # action_func is one of: "install", "remove" or "upgrade" | 493 | # action_func is one of: "install", "remove", "upgrade", or "apply_changes" |
66 | 485 | action_func = getattr(self.backend, action) | 494 | action_func = getattr(self.backend, action) |
67 | 486 | if action == 'install': | 495 | if action == 'install': |
68 | 487 | # the package.deb path name is in the request | 496 | # the package.deb path name is in the request |
69 | @@ -489,9 +498,9 @@ | |||
70 | 489 | debfile_name = app.request | 498 | debfile_name = app.request |
71 | 490 | else: | 499 | else: |
72 | 491 | debfile_name = None | 500 | debfile_name = None |
74 | 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) |
75 | 493 | elif callable(action_func): | 502 | elif callable(action_func): |
77 | 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) |
78 | 495 | else: | 504 | else: |
79 | 496 | logging.error("Not a valid action in AptdaemonBackend: '%s'" % action) | 505 | logging.error("Not a valid action in AptdaemonBackend: '%s'" % action) |
80 | 497 | 506 | ||
81 | 498 | 507 | ||
82 | === modified file 'softwarecenter/apt/aptcache.py' | |||
83 | --- softwarecenter/apt/aptcache.py 2010-07-09 03:16:33 +0000 | |||
84 | +++ softwarecenter/apt/aptcache.py 2010-08-16 20:56:42 +0000 | |||
85 | @@ -32,6 +32,7 @@ | |||
86 | 32 | import time | 32 | import time |
87 | 33 | 33 | ||
88 | 34 | from gettext import gettext as _ | 34 | from gettext import gettext as _ |
89 | 35 | from softwarecenter.enums import * | ||
90 | 35 | 36 | ||
91 | 36 | class GtkMainIterationProgress(apt.progress.base.OpProgress): | 37 | class GtkMainIterationProgress(apt.progress.base.OpProgress): |
92 | 37 | """Progress that just runs the main loop""" | 38 | """Progress that just runs the main loop""" |
93 | @@ -48,6 +49,8 @@ | |||
94 | 48 | DEPENDENCY_TYPES = ("PreDepends", "Depends") | 49 | DEPENDENCY_TYPES = ("PreDepends", "Depends") |
95 | 49 | RECOMMENDS_TYPES = ("Recommends",) | 50 | RECOMMENDS_TYPES = ("Recommends",) |
96 | 50 | SUGGESTS_TYPES = ("Suggests",) | 51 | SUGGESTS_TYPES = ("Suggests",) |
97 | 52 | ENHANCES_TYPES = ("Enhances",) | ||
98 | 53 | PROVIDES_TYPES = ("Provides",) | ||
99 | 51 | 54 | ||
100 | 52 | # stamp file to monitor (provided by update-notifier via | 55 | # stamp file to monitor (provided by update-notifier via |
101 | 53 | # APT::Update::Post-Invoke-Success) | 56 | # APT::Update::Post-Invoke-Success) |
102 | @@ -104,16 +107,16 @@ | |||
103 | 104 | return self._cache.__iter__() | 107 | return self._cache.__iter__() |
104 | 105 | def __contains__(self, k): | 108 | def __contains__(self, k): |
105 | 106 | return self._cache.__contains__(k) | 109 | return self._cache.__contains__(k) |
108 | 107 | def _get_installed_rdepends_by_type(self, pkg, type): | 110 | def _get_rdepends_by_type(self, pkg, type, onlyInstalled): |
109 | 108 | installed_rdeps = set() | 111 | rdeps = set() |
110 | 109 | for rdep in pkg._pkg.rev_depends_list: | 112 | for rdep in pkg._pkg.rev_depends_list: |
111 | 110 | dep_type = rdep.dep_type_untranslated | 113 | dep_type = rdep.dep_type_untranslated |
112 | 111 | if dep_type in type: | 114 | if dep_type in type: |
113 | 112 | rdep_name = rdep.parent_pkg.name | 115 | rdep_name = rdep.parent_pkg.name |
118 | 113 | if (rdep_name in self._cache and | 116 | if rdep_name in self._cache and (not onlyInstalled or |
119 | 114 | self._cache[rdep_name].is_installed): | 117 | (onlyInstalled and self._cache[rdep_name].is_installed)): |
120 | 115 | installed_rdeps.add(rdep.parent_pkg.name) | 118 | rdeps.add(rdep.parent_pkg.name) |
121 | 116 | return installed_rdeps | 119 | return rdeps |
122 | 117 | def _installed_dependencies(self, pkg_name, all_deps=None): | 120 | def _installed_dependencies(self, pkg_name, all_deps=None): |
123 | 118 | """ recursively return all installed dependencies of a given pkg """ | 121 | """ recursively return all installed dependencies of a given pkg """ |
124 | 119 | #print "_installed_dependencies", pkg_name, all_deps | 122 | #print "_installed_dependencies", pkg_name, all_deps |
125 | @@ -168,11 +171,15 @@ | |||
126 | 168 | origins.add(item.origin) | 171 | origins.add(item.origin) |
127 | 169 | return origins | 172 | return origins |
128 | 170 | def get_installed_rdepends(self, pkg): | 173 | def get_installed_rdepends(self, pkg): |
130 | 171 | return self._get_installed_rdepends_by_type(pkg, self.DEPENDENCY_TYPES) | 174 | return self._get_rdepends_by_type(pkg, self.DEPENDENCY_TYPES, True) |
131 | 172 | def get_installed_rrecommends(self, pkg): | 175 | def get_installed_rrecommends(self, pkg): |
133 | 173 | return self._get_installed_rdepends_by_type(pkg, self.RECOMMENDS_TYPES) | 176 | return self._get_rdepends_by_type(pkg, self.RECOMMENDS_TYPES, True) |
134 | 174 | def get_installed_rsuggests(self, pkg): | 177 | def get_installed_rsuggests(self, pkg): |
136 | 175 | return self._get_installed_rdepends_by_type(pkg, self.SUGGESTS_TYPES) | 178 | return self._get_rdepends_by_type(pkg, self.SUGGESTS_TYPES, True) |
137 | 179 | def get_installed_renhances(self, pkg): | ||
138 | 180 | return self._get_rdepends_by_type(pkg, self.ENHANCES_TYPES, True) | ||
139 | 181 | def get_installed_rprovides(self, pkg): | ||
140 | 182 | return self._get_rdepends_by_type(pkg, self.PROVIDES_TYPES, True) | ||
141 | 176 | def component_available(self, distro_codename, component): | 183 | def component_available(self, distro_codename, component): |
142 | 177 | """ check if the given component is enabled """ | 184 | """ check if the given component is enabled """ |
143 | 178 | # FIXME: test for more properties here? | 185 | # FIXME: test for more properties here? |
144 | @@ -183,6 +190,202 @@ | |||
145 | 183 | it.archive == distro_codename): | 190 | it.archive == distro_codename): |
146 | 184 | return True | 191 | return True |
147 | 185 | return False | 192 | return False |
148 | 193 | |||
149 | 194 | def _get_depends_by_type(self, pkg, types): | ||
150 | 195 | version = pkg.installed | ||
151 | 196 | if version == None: | ||
152 | 197 | version = max(pkg.versions) | ||
153 | 198 | return version.get_dependencies(*types) | ||
154 | 199 | def _get_depends_by_type_str(self, pkg, *types): | ||
155 | 200 | def not_in_list(list, item): | ||
156 | 201 | for i in list: | ||
157 | 202 | if i == item: | ||
158 | 203 | return False | ||
159 | 204 | return True | ||
160 | 205 | deps = self._get_depends_by_type(pkg, *types) | ||
161 | 206 | deps_str = [] | ||
162 | 207 | for dep in deps: | ||
163 | 208 | for dep_ in dep.or_dependencies: | ||
164 | 209 | if not_in_list(deps_str, dep_.name): | ||
165 | 210 | deps_str.append(dep_.name) | ||
166 | 211 | return deps_str | ||
167 | 212 | def get_depends(self, pkg): | ||
168 | 213 | return self._get_depends_by_type_str(pkg, self.DEPENDENCY_TYPES) | ||
169 | 214 | def get_recommends(self, pkg): | ||
170 | 215 | return self._get_depends_by_type_str(pkg, self.RECOMMENDS_TYPES) | ||
171 | 216 | def get_suggests(self, pkg): | ||
172 | 217 | return self._get_depends_by_type_str(pkg, self.SUGGESTS_TYPES) | ||
173 | 218 | def get_enhances(self, pkg): | ||
174 | 219 | return self._get_depends_by_type_str(pkg, self.ENHANCES_TYPES) | ||
175 | 220 | def get_provides(self, pkg): | ||
176 | 221 | return self._get_depends_by_type_str(pkg, self.PROVIDES_TYPES) | ||
177 | 222 | |||
178 | 223 | def get_rdepends(self, pkg): | ||
179 | 224 | return self._get_rdepends_by_type(pkg, self.DEPENDENCY_TYPES, False) | ||
180 | 225 | def get_rrecommends(self, pkg): | ||
181 | 226 | return self._get_rdepends_by_type(pkg, self.RECOMMENDS_TYPES, False) | ||
182 | 227 | def get_rsuggests(self, pkg): | ||
183 | 228 | return self._get_rdepends_by_type(pkg, self.SUGGESTS_TYPES, False) | ||
184 | 229 | def get_renhances(self, pkg): | ||
185 | 230 | return self._get_rdepends_by_type(pkg, self.ENHANCES_TYPES, False) | ||
186 | 231 | def get_rprovides(self, pkg): | ||
187 | 232 | return self._get_rdepends_by_type(pkg, self.PROVIDES_TYPES, False) | ||
188 | 233 | |||
189 | 234 | def _get_changes_without_applying(self, pkg): | ||
190 | 235 | if pkg.installed == None: | ||
191 | 236 | pkg.mark_install() | ||
192 | 237 | else: | ||
193 | 238 | pkg.mark_delete() | ||
194 | 239 | changes_tmp = self._cache.get_changes() | ||
195 | 240 | changes = {} | ||
196 | 241 | for change in changes_tmp: | ||
197 | 242 | if change.marked_install or change.marked_reinstall: | ||
198 | 243 | changes[change.name] = PKG_STATE_INSTALLING | ||
199 | 244 | elif change.marked_delete: | ||
200 | 245 | changes[change.name] = PKG_STATE_REMOVING | ||
201 | 246 | elif change.marked_upgrade: | ||
202 | 247 | changes[change.name] = PKG_STATE_UPGRADING | ||
203 | 248 | else: | ||
204 | 249 | changes[change.name] = PKG_STATE_UNKNOWN | ||
205 | 250 | self._cache.clear() | ||
206 | 251 | return changes | ||
207 | 252 | def get_all_deps_installing(self, pkg): | ||
208 | 253 | """ Return all dependencies of pkg that will be marked for install """ | ||
209 | 254 | changes = self._get_changes_without_applying(pkg) | ||
210 | 255 | installing_deps = [] | ||
211 | 256 | for change in changes.keys(): | ||
212 | 257 | if change != pkg.name and changes[change] == PKG_STATE_INSTALLING: | ||
213 | 258 | installing_deps.append(change) | ||
214 | 259 | return installing_deps | ||
215 | 260 | def get_all_deps_removing(self, pkg): | ||
216 | 261 | changes = self._get_changes_without_applying(pkg) | ||
217 | 262 | removing_deps = [] | ||
218 | 263 | for change in changes.keys(): | ||
219 | 264 | if change != pkg.name and changes[change] == PKG_STATE_REMOVING: | ||
220 | 265 | removing_deps.append(change) | ||
221 | 266 | return removing_deps | ||
222 | 267 | def get_all_deps_upgrading(self, pkg): | ||
223 | 268 | changes = self._get_changes_without_applying(pkg) | ||
224 | 269 | upgrading_deps = [] | ||
225 | 270 | for change in changes.keys(): | ||
226 | 271 | if change != pkg.name and changes[change] == PKG_STATE_UPGRADING: | ||
227 | 272 | upgrading_deps.append(change) | ||
228 | 273 | return upgrading_deps | ||
229 | 274 | |||
230 | 275 | class PackageAddonsManager(object): | ||
231 | 276 | """ class that abstracts the addons handling """ | ||
232 | 277 | |||
233 | 278 | LANGPACK_PKGDEPENDS = "/usr/share/language-selector/data/pkg_depends" | ||
234 | 279 | (RECOMMENDED, SUGGESTED) = range(2) | ||
235 | 280 | |||
236 | 281 | def __init__(self, cache): | ||
237 | 282 | self.cache = cache | ||
238 | 283 | self._language_packages = self._read_language_pkgs() | ||
239 | 284 | |||
240 | 285 | def _remove_important_or_langpack(self, addon_list, app_pkg): | ||
241 | 286 | """ remove packages that are essential or important | ||
242 | 287 | or langpacks | ||
243 | 288 | """ | ||
244 | 289 | for addon in addon_list: | ||
245 | 290 | try: | ||
246 | 291 | pkg = self.cache[addon] | ||
247 | 292 | if pkg.essential or pkg._pkg.important or addon == app_pkg.name: | ||
248 | 293 | addon_list.remove(addon) | ||
249 | 294 | continue | ||
250 | 295 | |||
251 | 296 | deps = self.cache.get_depends(app_pkg) | ||
252 | 297 | if addon in deps: | ||
253 | 298 | addon_list.remove(addon) | ||
254 | 299 | continue | ||
255 | 300 | |||
256 | 301 | rdeps = self.cache.get_installed_rdepends(pkg) | ||
257 | 302 | if (len(rdeps) > 0 or | ||
258 | 303 | self._is_language_pkg(addon)): | ||
259 | 304 | addon_list.remove(addon) | ||
260 | 305 | continue | ||
261 | 306 | except KeyError: | ||
262 | 307 | addon_list.remove(addon) | ||
263 | 308 | |||
264 | 309 | def _is_language_pkg(self, addon): | ||
265 | 310 | # a simple "addon in self._language_packages" is not enough | ||
266 | 311 | for template in self._language_packages: | ||
267 | 312 | if addon.startswith(template): | ||
268 | 313 | return True | ||
269 | 314 | return False | ||
270 | 315 | |||
271 | 316 | def _read_language_pkgs(self): | ||
272 | 317 | language_packages = set() | ||
273 | 318 | for line in open(self.LANGPACK_PKGDEPENDS): | ||
274 | 319 | line = line.strip() | ||
275 | 320 | if line.startswith('#'): | ||
276 | 321 | continue | ||
277 | 322 | try: | ||
278 | 323 | (cat, code, dep_pkg, language_pkg) = line.split(':') | ||
279 | 324 | except ValueError: | ||
280 | 325 | continue | ||
281 | 326 | language_packages.add(language_pkg) | ||
282 | 327 | return language_packages | ||
283 | 328 | |||
284 | 329 | def _addons_for_pkg(self, pkgname, type): | ||
285 | 330 | try: | ||
286 | 331 | pkg = self.cache[pkgname] | ||
287 | 332 | except KeyError: | ||
288 | 333 | return [] | ||
289 | 334 | deps = self.cache.get_depends(pkg) | ||
290 | 335 | addons = [] | ||
291 | 336 | if type == self.RECOMMENDED: | ||
292 | 337 | recommends = self.cache.get_recommends(pkg) | ||
293 | 338 | if len(recommends) == 1: | ||
294 | 339 | addons += recommends | ||
295 | 340 | elif type == self.SUGGESTED: | ||
296 | 341 | suggests = self.cache.get_suggests(pkg) | ||
297 | 342 | if len(suggests) == 1: | ||
298 | 343 | addons += suggests | ||
299 | 344 | addons += self.cache.get_renhances(pkg) | ||
300 | 345 | |||
301 | 346 | for dep in deps: | ||
302 | 347 | try: | ||
303 | 348 | pkgdep = self.cache[dep] | ||
304 | 349 | if len(self.cache.get_rdepends(pkgdep)) == 1: | ||
305 | 350 | # pkg is the only known package that depends on pkgdep | ||
306 | 351 | if type == self.RECOMMENDED: | ||
307 | 352 | addons += self.cache.get_recommends(pkgdep) | ||
308 | 353 | elif type == self.SUGGESTED: | ||
309 | 354 | addons += self.cache.get_suggests(pkgdep) | ||
310 | 355 | addons += self.cache.get_renhances(pkgdep) | ||
311 | 356 | except KeyError: | ||
312 | 357 | pass # FIXME: should we handle that differently? | ||
313 | 358 | self._remove_important_or_langpack(addons, pkg) | ||
314 | 359 | for addon in addons: | ||
315 | 360 | try: | ||
316 | 361 | pkg_ = self.cache[addon] | ||
317 | 362 | except KeyError: | ||
318 | 363 | addons.remove(addon) | ||
319 | 364 | else: | ||
320 | 365 | can_remove = False | ||
321 | 366 | for addon_ in addons: | ||
322 | 367 | try: | ||
323 | 368 | if addon in self.cache.get_provides(self.cache[addon_]) \ | ||
324 | 369 | or addon in self.cache.get_depends(self.cache[addon_]) \ | ||
325 | 370 | or addon in self.cache.get_recommends(self.cache[addon_]): | ||
326 | 371 | can_remove = True | ||
327 | 372 | break | ||
328 | 373 | except KeyError: | ||
329 | 374 | addons.remove(addon_) | ||
330 | 375 | break | ||
331 | 376 | if can_remove or not pkg_.candidate or addons.count(addon) > 1 \ | ||
332 | 377 | or addon == pkg.name or self._is_language_pkg(addon): | ||
333 | 378 | addons.remove(addon) | ||
334 | 379 | # FIXME: figure out why I have to call this function two times to get rid of important packages | ||
335 | 380 | self._remove_important_or_langpack(addons, pkg) | ||
336 | 381 | return addons | ||
337 | 382 | |||
338 | 383 | def recommended_addons(self, pkgname): | ||
339 | 384 | return self._addons_for_pkg(pkgname, self.RECOMMENDED) | ||
340 | 385 | |||
341 | 386 | def suggested_addons(self, pkgname): | ||
342 | 387 | return self._addons_for_pkg(pkgname, self.SUGGESTED) | ||
343 | 388 | |||
344 | 186 | 389 | ||
345 | 187 | if __name__ == "__main__": | 390 | if __name__ == "__main__": |
346 | 188 | c = AptCache() | 391 | c = AptCache() |
347 | @@ -200,3 +403,18 @@ | |||
348 | 200 | print c.get_installed_rdepends(pkg) | 403 | print c.get_installed_rdepends(pkg) |
349 | 201 | print c.get_installed_rrecommends(pkg) | 404 | print c.get_installed_rrecommends(pkg) |
350 | 202 | print c.get_installed_rsuggests(pkg) | 405 | print c.get_installed_rsuggests(pkg) |
351 | 406 | |||
352 | 407 | print "deps of gimp" | ||
353 | 408 | pkg = c["gimp"] | ||
354 | 409 | print c.get_depends(pkg) | ||
355 | 410 | print c.get_recommends(pkg) | ||
356 | 411 | print c.get_suggests(pkg) | ||
357 | 412 | print c.get_enhances(pkg) | ||
358 | 413 | print c.get_provides(pkg) | ||
359 | 414 | |||
360 | 415 | print "rdeps of gimp" | ||
361 | 416 | print c.get_rdepends(pkg) | ||
362 | 417 | print c.get_rrecommends(pkg) | ||
363 | 418 | print c.get_rsuggests(pkg) | ||
364 | 419 | print c.get_renhances(pkg) | ||
365 | 420 | print c.get_rprovides(pkg) | ||
366 | 203 | 421 | ||
367 | === modified file 'softwarecenter/backend/aptd.py' | |||
368 | --- softwarecenter/backend/aptd.py 2010-08-11 14:00:11 +0000 | |||
369 | +++ softwarecenter/backend/aptd.py 2010-08-16 20:56:42 +0000 | |||
370 | @@ -126,8 +126,9 @@ | |||
371 | 126 | except Exception, error: | 126 | except Exception, error: |
372 | 127 | self._on_trans_error(error) | 127 | self._on_trans_error(error) |
373 | 128 | 128 | ||
374 | 129 | # FIXME: upgrade add-ons here | ||
375 | 129 | @inline_callbacks | 130 | @inline_callbacks |
377 | 130 | def upgrade(self, pkgname, appname, iconname, metadata=None): | 131 | def upgrade(self, pkgname, appname, iconname, addons_install=None, addons_remove=None, metadata=None): |
378 | 131 | """ upgrade a single package """ | 132 | """ upgrade a single package """ |
379 | 132 | self.emit("transaction-started") | 133 | self.emit("transaction-started") |
380 | 133 | try: | 134 | try: |
381 | @@ -138,7 +139,7 @@ | |||
382 | 138 | self._on_trans_error(error, pkgname) | 139 | self._on_trans_error(error, pkgname) |
383 | 139 | 140 | ||
384 | 140 | @inline_callbacks | 141 | @inline_callbacks |
386 | 141 | def remove(self, pkgname, appname, iconname, metadata=None): | 142 | def remove(self, pkgname, appname, iconname, addons_install=None, addons_remove=None, metadata=None): |
387 | 142 | """ remove a single package """ | 143 | """ remove a single package """ |
388 | 143 | self.emit("transaction-started") | 144 | self.emit("transaction-started") |
389 | 144 | try: | 145 | try: |
390 | @@ -149,7 +150,7 @@ | |||
391 | 149 | self._on_trans_error(error, pkgname) | 150 | self._on_trans_error(error, pkgname) |
392 | 150 | 151 | ||
393 | 151 | @inline_callbacks | 152 | @inline_callbacks |
395 | 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): |
396 | 153 | """ queue a list of packages for removal """ | 154 | """ queue a list of packages for removal """ |
397 | 154 | if metadatas == None: | 155 | if metadatas == None: |
398 | 155 | metadatas = [] | 156 | metadatas = [] |
399 | @@ -159,7 +160,7 @@ | |||
400 | 159 | yield self.remove(pkgname, appname, iconname, metadata) | 160 | yield self.remove(pkgname, appname, iconname, metadata) |
401 | 160 | 161 | ||
402 | 161 | @inline_callbacks | 162 | @inline_callbacks |
404 | 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): |
405 | 163 | """Install a single package from the archive | 164 | """Install a single package from the archive |
406 | 164 | If filename is given a local deb package is installed instead. | 165 | If filename is given a local deb package is installed instead. |
407 | 165 | """ | 166 | """ |
408 | @@ -169,21 +170,30 @@ | |||
409 | 169 | trans = yield self.aptd_client.install_file(filename, | 170 | trans = yield self.aptd_client.install_file(filename, |
410 | 170 | defer=True) | 171 | defer=True) |
411 | 171 | else: | 172 | else: |
414 | 172 | trans = yield self.aptd_client.install_packages([pkgname], | 173 | trans = yield self.aptd_client.commit_packages([pkgname] + addons_install, [], addons_remove, [], []) |
413 | 173 | defer=True) | ||
415 | 174 | yield self._run_transaction(trans, pkgname, appname, iconname, metadata) | 174 | yield self._run_transaction(trans, pkgname, appname, iconname, metadata) |
416 | 175 | except Exception, error: | 175 | except Exception, error: |
417 | 176 | self._on_trans_error(error, pkgname) | 176 | self._on_trans_error(error, pkgname) |
418 | 177 | 177 | ||
419 | 178 | @inline_callbacks | 178 | @inline_callbacks |
421 | 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): |
422 | 180 | """ queue a list of packages for install """ | 180 | """ queue a list of packages for install """ |
423 | 181 | if metadatas == None: | 181 | if metadatas == None: |
424 | 182 | metadatas = [] | 182 | metadatas = [] |
425 | 183 | for item in pkgnames: | 183 | for item in pkgnames: |
426 | 184 | metadatas.append(None) | 184 | metadatas.append(None) |
427 | 185 | for pkgname, appname, iconname, metadata in zip(pkgnames, appnames, iconnames, metadatas): | 185 | for pkgname, appname, iconname, metadata in zip(pkgnames, appnames, iconnames, metadatas): |
429 | 186 | yield self.install(pkgname, appname, iconname, metadata) | 186 | yield self.install(pkgname, appname, iconname, metadata=metadata) |
430 | 187 | |||
431 | 188 | @inline_callbacks | ||
432 | 189 | def apply_changes(self, pkgname, appname, iconname, addons_install=None, addons_remove=None, metadata=None): | ||
433 | 190 | """ install and remove add-ons """ | ||
434 | 191 | self.emit("transaction-started") | ||
435 | 192 | try: | ||
436 | 193 | trans = yield self.aptd_client.commit_packages(addons_install, [], addons_remove, [], []) | ||
437 | 194 | yield self._run_transaction(trans, pkgname, appname, iconname) | ||
438 | 195 | except Exception, error: | ||
439 | 196 | self._on_trans_error(error) | ||
440 | 187 | 197 | ||
441 | 188 | @inline_callbacks | 198 | @inline_callbacks |
442 | 189 | def reload(self, metadata=None): | 199 | def reload(self, metadata=None): |
443 | 190 | 200 | ||
444 | === modified file 'softwarecenter/db/application.py' | |||
445 | --- softwarecenter/db/application.py 2010-08-13 14:25:11 +0000 | |||
446 | +++ softwarecenter/db/application.py 2010-08-16 20:56:42 +0000 | |||
447 | @@ -456,11 +456,18 @@ | |||
448 | 456 | source_to_enable = self.component | 456 | source_to_enable = self.component |
449 | 457 | if source_to_enable: | 457 | if source_to_enable: |
450 | 458 | sources = source_to_enable.split('&') | 458 | sources = source_to_enable.split('&') |
456 | 459 | warning = _("Available from the \"%s\"") % sources[0] | 459 | sources_length = len(sources) |
457 | 460 | if len(sources) > 1: | 460 | if sources_length == 1: |
458 | 461 | for source in sources[1:]: | 461 | warning = _("Available from the \"%s\" source.") % sources[0] |
459 | 462 | warning += _(", or from the \"%s\"") % source | 462 | elif sources_length > 1: |
460 | 463 | warning += _(" source.") | 463 | # Translators: the visible string is constructed concatenating |
461 | 464 | # the following 3 strings like this: | ||
462 | 465 | # Available from the following sources: %s, ... %s, %s. | ||
463 | 466 | warning = _("Available from the following sources: ") | ||
464 | 467 | # Cycle through all, but the last | ||
465 | 468 | for source in sources[:-1]: | ||
466 | 469 | warning += _("\"%s\", ") % source | ||
467 | 470 | warning += _("\"%s\".") % sources[sources_length - 1] | ||
468 | 464 | return warning | 471 | return warning |
469 | 465 | 472 | ||
470 | 466 | @property | 473 | @property |
471 | 467 | 474 | ||
472 | === modified file 'softwarecenter/enums.py' | |||
473 | --- softwarecenter/enums.py 2010-08-10 08:31:32 +0000 | |||
474 | +++ softwarecenter/enums.py 2010-08-16 20:56:42 +0000 | |||
475 | @@ -126,6 +126,7 @@ | |||
476 | 126 | APP_ACTION_INSTALL = "install" | 126 | APP_ACTION_INSTALL = "install" |
477 | 127 | APP_ACTION_REMOVE = "remove" | 127 | APP_ACTION_REMOVE = "remove" |
478 | 128 | APP_ACTION_UPGRADE = "upgrade" | 128 | APP_ACTION_UPGRADE = "upgrade" |
479 | 129 | APP_ACTION_APPLY = "apply_changes" | ||
480 | 129 | 130 | ||
481 | 130 | from version import * | 131 | from version import * |
482 | 131 | USER_AGENT="Software Center/%s (N;) %s/%s (%s)" % (VERSION, | 132 | USER_AGENT="Software Center/%s (N;) %s/%s (%s)" % (VERSION, |
483 | 132 | 133 | ||
484 | === modified file 'softwarecenter/view/appdetailsview.py' | |||
485 | --- softwarecenter/view/appdetailsview.py 2010-08-09 14:17:41 +0000 | |||
486 | +++ softwarecenter/view/appdetailsview.py 2010-08-16 20:56:42 +0000 | |||
487 | @@ -22,7 +22,7 @@ | |||
488 | 22 | import urllib | 22 | import urllib |
489 | 23 | import gobject | 23 | import gobject |
490 | 24 | 24 | ||
492 | 25 | from softwarecenter.enums import MISSING_APP_ICON | 25 | from softwarecenter.apt.aptcache import PackageAddonsManager |
493 | 26 | from softwarecenter.db.application import AppDetails | 26 | from softwarecenter.db.application import AppDetails |
494 | 27 | from softwarecenter.backend import get_install_backend | 27 | from softwarecenter.backend import get_install_backend |
495 | 28 | from softwarecenter.enums import * | 28 | from softwarecenter.enums import * |
496 | @@ -35,8 +35,17 @@ | |||
497 | 35 | __gsignals__ = { | 35 | __gsignals__ = { |
498 | 36 | "application-request-action" : (gobject.SIGNAL_RUN_LAST, | 36 | "application-request-action" : (gobject.SIGNAL_RUN_LAST, |
499 | 37 | gobject.TYPE_NONE, | 37 | gobject.TYPE_NONE, |
501 | 38 | (gobject.TYPE_PYOBJECT, str), | 38 | (gobject.TYPE_PYOBJECT, |
502 | 39 | gobject.TYPE_PYOBJECT, | ||
503 | 40 | gobject.TYPE_PYOBJECT, | ||
504 | 41 | str), | ||
505 | 39 | ), | 42 | ), |
506 | 43 | "navigation-request" : ( gobject.SIGNAL_RUN_LAST, | ||
507 | 44 | gobject.TYPE_NONE, | ||
508 | 45 | (str, | ||
509 | 46 | ), | ||
510 | 47 | ), | ||
511 | 48 | |||
512 | 40 | } | 49 | } |
513 | 41 | 50 | ||
514 | 42 | def __init__(self, db, distro, icons, cache, history, datadir): | 51 | def __init__(self, db, distro, icons, cache, history, datadir): |
515 | @@ -45,10 +54,13 @@ | |||
516 | 45 | self.icons = icons | 54 | self.icons = icons |
517 | 46 | self.cache = cache | 55 | self.cache = cache |
518 | 47 | self.cache.connect("cache-ready", self._on_cache_ready) | 56 | self.cache.connect("cache-ready", self._on_cache_ready) |
519 | 57 | self.addons_manager = PackageAddonsManager(cache) | ||
520 | 48 | self.history = history | 58 | self.history = history |
521 | 49 | self.datadir = datadir | 59 | self.datadir = datadir |
522 | 50 | self.app = None | 60 | self.app = None |
523 | 51 | self.appdetails = None | 61 | self.appdetails = None |
524 | 62 | self.addons_install = [] | ||
525 | 63 | self.addons_remove = [] | ||
526 | 52 | # aptdaemon | 64 | # aptdaemon |
527 | 53 | self.backend = get_install_backend() | 65 | self.backend = get_install_backend() |
528 | 54 | self._logger = logging.getLogger(__name__) | 66 | self._logger = logging.getLogger(__name__) |
529 | @@ -58,6 +70,22 @@ | |||
530 | 58 | you need to overwrite | 70 | you need to overwrite |
531 | 59 | """ | 71 | """ |
532 | 60 | pass | 72 | pass |
533 | 73 | |||
534 | 74 | # add-on handling | ||
535 | 75 | def _set_addon_install(self, addon): | ||
536 | 76 | pkg = self.cache[addon] | ||
537 | 77 | if addon not in self.addons_install and pkg.installed == None: | ||
538 | 78 | self.addons_install.append(addon) | ||
539 | 79 | if addon in self.addons_remove: | ||
540 | 80 | self.addons_remove.remove(addon) | ||
541 | 81 | |||
542 | 82 | def _set_addon_remove(self, addon): | ||
543 | 83 | pkg = self.cache[addon] | ||
544 | 84 | if addon not in self.addons_remove and pkg.installed != None: | ||
545 | 85 | self.addons_remove.append(addon) | ||
546 | 86 | if addon in self.addons_install: | ||
547 | 87 | self.addons_install.remove(addon) | ||
548 | 88 | |||
549 | 61 | # public API | 89 | # public API |
550 | 62 | def show_app(self, app): | 90 | def show_app(self, app): |
551 | 63 | """ show the given application """ | 91 | """ show the given application """ |
552 | @@ -75,13 +103,16 @@ | |||
553 | 75 | self.backend.reload() | 103 | self.backend.reload() |
554 | 76 | def install(self): | 104 | def install(self): |
555 | 77 | """ install the current application, fire an action request """ | 105 | """ install the current application, fire an action request """ |
557 | 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) |
558 | 79 | def remove(self): | 107 | def remove(self): |
559 | 80 | """ remove the current application, , fire an action request """ | 108 | """ remove the current application, , fire an action request """ |
561 | 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) |
562 | 82 | def upgrade(self): | 110 | def upgrade(self): |
563 | 83 | """ upgrade the current application, fire an action request """ | 111 | """ upgrade the current application, fire an action request """ |
565 | 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) |
566 | 113 | def apply_changes(self): | ||
567 | 114 | """ apply changes concerning add-ons """ | ||
568 | 115 | self.emit("application-request-action", self.app, self.addons_install, self.addons_remove, APP_ACTION_APPLY) | ||
569 | 85 | 116 | ||
570 | 86 | def buy_app(self): | 117 | def buy_app(self): |
571 | 87 | """ initiate the purchase transaction """ | 118 | """ initiate the purchase transaction """ |
572 | @@ -107,10 +138,10 @@ | |||
573 | 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, |
574 | 108 | signing_key_id, | 139 | signing_key_id, |
575 | 109 | self.app) | 140 | self.app) |
576 | 110 | |||
577 | 111 | # internal callbacks | 141 | # internal callbacks |
578 | 112 | def _on_cache_ready(self, cache): | 142 | def _on_cache_ready(self, cache): |
579 | 113 | # re-show the application if the cache changes, it may affect the | 143 | # re-show the application if the cache changes, it may affect the |
580 | 114 | # current application | 144 | # current application |
581 | 115 | self._logger.debug("on_cache_ready") | 145 | self._logger.debug("on_cache_ready") |
582 | 116 | self.show_app(self.app) | 146 | self.show_app(self.app) |
583 | 147 | self.addons_manager = PackageAddonsManager(cache) | ||
584 | 117 | 148 | ||
585 | === modified file 'softwarecenter/view/appdetailsview_gtk.py' | |||
586 | --- softwarecenter/view/appdetailsview_gtk.py 2010-08-13 14:25:11 +0000 | |||
587 | +++ softwarecenter/view/appdetailsview_gtk.py 2010-08-16 20:56:42 +0000 | |||
588 | @@ -33,8 +33,10 @@ | |||
589 | 33 | import cairo | 33 | import cairo |
590 | 34 | 34 | ||
591 | 35 | from gettext import gettext as _ | 35 | from gettext import gettext as _ |
592 | 36 | import apt_pkg | ||
593 | 36 | from softwarecenter.backend import get_install_backend | 37 | from softwarecenter.backend import get_install_backend |
595 | 37 | from softwarecenter.db.application import AppDetails | 38 | from softwarecenter.db.application import AppDetails, Application |
596 | 39 | from softwarecenter.apt.aptcache import AptCache | ||
597 | 38 | from softwarecenter.enums import * | 40 | from softwarecenter.enums import * |
598 | 39 | from softwarecenter.paths import SOFTWARE_CENTER_ICON_CACHE_DIR | 41 | from softwarecenter.paths import SOFTWARE_CENTER_ICON_CACHE_DIR |
599 | 40 | from softwarecenter.utils import ImageDownloader | 42 | from softwarecenter.utils import ImageDownloader |
600 | @@ -150,7 +152,8 @@ | |||
601 | 150 | if state in (PKG_STATE_INSTALLING, | 152 | if state in (PKG_STATE_INSTALLING, |
602 | 151 | PKG_STATE_INSTALLING_PURCHASED, | 153 | PKG_STATE_INSTALLING_PURCHASED, |
603 | 152 | PKG_STATE_REMOVING, | 154 | PKG_STATE_REMOVING, |
605 | 153 | PKG_STATE_UPGRADING): | 155 | PKG_STATE_UPGRADING, |
606 | 156 | APP_ACTION_APPLY): | ||
607 | 154 | self.button.hide() | 157 | self.button.hide() |
608 | 155 | self.show() | 158 | self.show() |
609 | 156 | elif state == PKG_STATE_NOT_FOUND: | 159 | elif state == PKG_STATE_NOT_FOUND: |
610 | @@ -159,8 +162,8 @@ | |||
611 | 159 | self.button.set_sensitive(False) | 162 | self.button.set_sensitive(False) |
612 | 160 | self.button.show() | 163 | self.button.show() |
613 | 161 | self.show() | 164 | self.show() |
614 | 162 | else: | ||
615 | 163 | state = app_details.pkg_state | 165 | state = app_details.pkg_state |
616 | 166 | else: | ||
617 | 164 | self.button.set_sensitive(True) | 167 | self.button.set_sensitive(True) |
618 | 165 | self.button.show() | 168 | self.button.show() |
619 | 166 | self.show() | 169 | self.show() |
620 | @@ -202,7 +205,7 @@ | |||
621 | 202 | if app_details.price: | 205 | if app_details.price: |
622 | 203 | self.set_label(app_details.price) | 206 | self.set_label(app_details.price) |
623 | 204 | else: | 207 | else: |
625 | 205 | self.set_label("") | 208 | self.set_label("Free") |
626 | 206 | self.set_button_label(_('Install')) | 209 | self.set_button_label(_('Install')) |
627 | 207 | elif state == PKG_STATE_REINSTALLABLE: | 210 | elif state == PKG_STATE_REINSTALLABLE: |
628 | 208 | if app_details.price: | 211 | if app_details.price: |
629 | @@ -213,6 +216,11 @@ | |||
630 | 213 | elif state == PKG_STATE_UPGRADABLE: | 216 | elif state == PKG_STATE_UPGRADABLE: |
631 | 214 | self.set_label(_('Upgrade Available')) | 217 | self.set_label(_('Upgrade Available')) |
632 | 215 | self.set_button_label(_('Upgrade')) | 218 | self.set_button_label(_('Upgrade')) |
633 | 219 | elif state == APP_ACTION_APPLY: | ||
634 | 220 | self.set_label(_('Changing add-ons...')) | ||
635 | 221 | elif state == PKG_STATE_UNKNOWN: | ||
636 | 222 | self.set_button_label("") | ||
637 | 223 | self.set_label(_("Error")) | ||
638 | 216 | elif state == PKG_STATE_ERROR: | 224 | elif state == PKG_STATE_ERROR: |
639 | 217 | # this is used when the pkg can not be installed | 225 | # this is used when the pkg can not be installed |
640 | 218 | # we display the error in the description field | 226 | # we display the error in the description field |
641 | @@ -411,78 +419,51 @@ | |||
642 | 411 | self.show_all() | 419 | self.show_all() |
643 | 412 | return | 420 | return |
644 | 413 | 421 | ||
657 | 414 | 422 | class PackageInfo(gtk.HBox): | |
658 | 415 | class PackageInfoTable(gtk.VBox): | 423 | |
659 | 416 | 424 | def __init__(self, key): | |
660 | 417 | def __init__(self): | 425 | gtk.HBox.__init__(self, spacing=mkit.SPACING_XLARGE) |
661 | 418 | gtk.VBox.__init__(self, spacing=mkit.SPACING_MED) | 426 | self.key = key |
662 | 419 | 427 | self.value_object = gtk.Label() | |
651 | 420 | self.version_label = gtk.Label() | ||
652 | 421 | self.license_label = gtk.Label() | ||
653 | 422 | self.support_label = gtk.Label() | ||
654 | 423 | |||
655 | 424 | self.version_label.set_selectable(True) | ||
656 | 425 | |||
663 | 426 | self.connect('realize', self._on_realize) | 428 | self.connect('realize', self._on_realize) |
664 | 427 | return | 429 | return |
665 | 428 | 430 | ||
666 | 429 | def _on_realize(self, widget): | 431 | def _on_realize(self, widget): |
667 | 432 | # key | ||
668 | 433 | k = gtk.Label() | ||
669 | 430 | dark = self.style.dark[self.state].to_string() | 434 | dark = self.style.dark[self.state].to_string() |
670 | 431 | key_markup = '<b><span color="%s">%s</span></b>' | 435 | key_markup = '<b><span color="%s">%s</span></b>' |
703 | 432 | max_lw = 0 # max key label width | 436 | k.set_markup(key_markup % (dark, self.key)) |
704 | 433 | 437 | a = gtk.Alignment(1.0, 0.0) | |
705 | 434 | for kstr, v in [(_('Version:'), self.version_label), | 438 | # the line below is 'wrong', but in reality it works quite ok |
706 | 435 | (_('License:'), self.license_label), | 439 | a.set_size_request(100, -1) |
707 | 436 | (_('Updates:'), self.support_label)]: | 440 | a.add(k) |
708 | 437 | 441 | self.pack_start(a, False) | |
709 | 438 | k = gtk.Label() | 442 | |
710 | 439 | k.set_markup(key_markup % (dark, kstr)) | 443 | # value |
711 | 440 | v.set_line_wrap(True) | 444 | v = self.value_object |
712 | 441 | max_lw = max(max_lw, k.get_layout().get_pixel_extents()[1][2]) | 445 | v.set_line_wrap(True) |
713 | 442 | 446 | v.set_selectable(True) | |
714 | 443 | a = gtk.Alignment(1.0, 0.0) | 447 | b = gtk.Alignment(0.0, 0.0) |
715 | 444 | a.add(k) | 448 | b.add(v) |
716 | 445 | 449 | self.pack_start(b, False) | |
717 | 446 | # we need this extra box to avoid orca repeating itself | 450 | |
718 | 447 | b = gtk.Alignment(0.0, 0.0) | 451 | # a11y stuff |
719 | 448 | b.add(v) | 452 | self.set_property("can-focus", True) |
720 | 449 | 453 | self.a11y = self.get_accessible() | |
689 | 450 | row = gtk.HBox(spacing=mkit.SPACING_XLARGE) | ||
690 | 451 | row.pack_start(a, False) | ||
691 | 452 | row.pack_start(b, False) | ||
692 | 453 | |||
693 | 454 | # a11y stuff | ||
694 | 455 | row.set_property("can-focus", True) | ||
695 | 456 | row.a11y = row.get_accessible() | ||
696 | 457 | row.a11y.set_name(kstr) | ||
697 | 458 | |||
698 | 459 | self.pack_start(row, False) | ||
699 | 460 | |||
700 | 461 | for row in self.get_children(): | ||
701 | 462 | k, v = row.get_children() | ||
702 | 463 | k.set_size_request(max_lw+3*mkit.EM, -1) | ||
721 | 464 | 454 | ||
722 | 465 | self.show_all() | 455 | self.show_all() |
723 | 466 | return | 456 | return |
724 | 467 | 457 | ||
725 | 468 | def set_width(self, width): | 458 | def set_width(self, width): |
729 | 469 | for row in self.get_children(): | 459 | if self.get_children(): |
730 | 470 | k, v = row.get_children() | 460 | k, v = self.get_children() |
731 | 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) |
732 | 472 | return | 462 | return |
733 | 473 | 463 | ||
746 | 474 | def configure(self, version, license, updates): | 464 | def set_value(self, value): |
747 | 475 | 465 | self.value_object.set_text(value) | |
748 | 476 | # set labels | 466 | self.a11y.set_name(self.key + ' ' + value) |
737 | 477 | self.version_label.set_text(version) | ||
738 | 478 | self.license_label.set_text(license) | ||
739 | 479 | self.support_label.set_text(updates) | ||
740 | 480 | |||
741 | 481 | # set a11y texts | ||
742 | 482 | for row in self.get_children(): | ||
743 | 483 | key = row.a11y.get_name().split(":")[0] | ||
744 | 484 | value = row.get_children()[1].get_children()[0].get_text() | ||
745 | 485 | row.a11y.set_name(key + ': ' + value) | ||
749 | 486 | 467 | ||
750 | 487 | class ScreenshotView(gtk.Alignment): | 468 | class ScreenshotView(gtk.Alignment): |
751 | 488 | 469 | ||
752 | @@ -784,6 +765,215 @@ | |||
753 | 784 | cr.fill() | 765 | cr.fill() |
754 | 785 | return | 766 | return |
755 | 786 | 767 | ||
756 | 768 | class AddonCheckButton(gtk.HBox): | ||
757 | 769 | """ A widget that represents an add-on: | ||
758 | 770 | |CheckButton|Icon|Description| """ | ||
759 | 771 | |||
760 | 772 | __gsignals__ = {'toggled': (gobject.SIGNAL_RUN_FIRST, | ||
761 | 773 | gobject.TYPE_NONE, | ||
762 | 774 | ()), | ||
763 | 775 | } | ||
764 | 776 | |||
765 | 777 | def __init__(self, db, icons, pkgname): | ||
766 | 778 | gtk.HBox.__init__(self, spacing=mkit.SPACING_LARGE) | ||
767 | 779 | self.app_details = AppDetails(db, | ||
768 | 780 | application=Application("None", pkgname)) | ||
769 | 781 | # the checkbutton | ||
770 | 782 | self.checkbutton = gtk.CheckButton() | ||
771 | 783 | self.checkbutton.connect("toggled", self._on_checkbutton_toggled) | ||
772 | 784 | self.pack_start(self.checkbutton, False) | ||
773 | 785 | # the hbox inside the checkbutton that contains the icon and description | ||
774 | 786 | hbox = gtk.HBox(spacing=mkit.SPACING_MED) | ||
775 | 787 | image = gtk.Image() | ||
776 | 788 | icon = self.app_details.icon | ||
777 | 789 | if not icon or not icons.has_icon(icon): | ||
778 | 790 | icon = MISSING_APP_ICON | ||
779 | 791 | try: | ||
780 | 792 | pixbuf = icons.load_icon(icon, 22, ()).scale_simple(22, 22, gtk.gdk.INTERP_BILINEAR) | ||
781 | 793 | image.set_from_pixbuf(pixbuf) | ||
782 | 794 | except TypeError: | ||
783 | 795 | logging.warning("cant set icon for '%s' " % pkgname) | ||
784 | 796 | hbox.pack_start(image, False, False) | ||
785 | 797 | # the display_name | ||
786 | 798 | display_name = _("%(summary)s (%(pkgname)s)") % {'summary': self.app_details.display_name.capitalize(), | ||
787 | 799 | 'pkgname': pkgname} | ||
788 | 800 | label = gtk.Label(display_name) | ||
789 | 801 | hbox.pack_start(label, False) | ||
790 | 802 | # and put it into the the checkbox | ||
791 | 803 | self.checkbutton.add(hbox) | ||
792 | 804 | # this is the addon_pkgname | ||
793 | 805 | #self.addon_pkgname = gtk.Label(_(" (%(pkgname)s)") % { | ||
794 | 806 | # 'pkgname' : pkgname } ) | ||
795 | 807 | #hbox.pack_start(self.addon_pkgname, False) | ||
796 | 808 | |||
797 | 809 | |||
798 | 810 | def _on_checkbutton_toggled(self, checkbutton): | ||
799 | 811 | self.emit("toggled") | ||
800 | 812 | def get_active(self): | ||
801 | 813 | return self.checkbutton.get_active() | ||
802 | 814 | def set_active(self, is_active): | ||
803 | 815 | self.checkbutton.set_active(is_active) | ||
804 | 816 | def get_addon(self): | ||
805 | 817 | return self.app_details.pkgname | ||
806 | 818 | |||
807 | 819 | |||
808 | 820 | class AddonView(gtk.VBox): | ||
809 | 821 | """ A widget that handles the application add-ons """ | ||
810 | 822 | # TODO: sort add-ons in alphabetical order | ||
811 | 823 | |||
812 | 824 | __gsignals__ = {'toggled':(gobject.SIGNAL_RUN_FIRST, | ||
813 | 825 | gobject.TYPE_NONE, | ||
814 | 826 | (str, gobject.TYPE_PYOBJECT)), | ||
815 | 827 | 'description-clicked':(gobject.SIGNAL_RUN_FIRST, | ||
816 | 828 | gobject.TYPE_NONE, | ||
817 | 829 | (str, )), | ||
818 | 830 | } | ||
819 | 831 | |||
820 | 832 | def __init__(self, cache, db, icons): | ||
821 | 833 | gtk.VBox.__init__(self, False, mkit.SPACING_MED) | ||
822 | 834 | self.cache = cache | ||
823 | 835 | self.db = db | ||
824 | 836 | self.icons = icons | ||
825 | 837 | self.recommended_addons = None | ||
826 | 838 | self.suggested_addons = None | ||
827 | 839 | |||
828 | 840 | self.label = gtk.Label(_("<b>Choose add-ons:</b>")) | ||
829 | 841 | self.label.set_use_markup(True) | ||
830 | 842 | self.label.set_alignment(0, 0.5) | ||
831 | 843 | self.pack_start(self.label, False, False) | ||
832 | 844 | |||
833 | 845 | def set_addons(self, app_details, recommended, suggested): | ||
834 | 846 | if len(recommended) == 0 and len(suggested) == 0: | ||
835 | 847 | return | ||
836 | 848 | self.recommended_addons = recommended | ||
837 | 849 | self.suggested_addons = suggested | ||
838 | 850 | self.app_details = app_details | ||
839 | 851 | |||
840 | 852 | for widget in self: | ||
841 | 853 | if widget != self.label: | ||
842 | 854 | self.remove(widget) | ||
843 | 855 | |||
844 | 856 | for addon in recommended: | ||
845 | 857 | try: | ||
846 | 858 | pkg = self.cache[addon] | ||
847 | 859 | except KeyError: | ||
848 | 860 | continue | ||
849 | 861 | checkbutton = AddonCheckButton(self.db, self.icons, addon) | ||
850 | 862 | #checkbutton.addon_pkgname.connect( | ||
851 | 863 | # "clicked", self._on_description_clicked, addon) | ||
852 | 864 | checkbutton.set_active(pkg.installed != None) | ||
853 | 865 | checkbutton.connect("toggled", self._on_checkbutton_toggled) | ||
854 | 866 | self.pack_start(checkbutton, False) | ||
855 | 867 | for addon in suggested: | ||
856 | 868 | try: | ||
857 | 869 | pkg = self.cache[addon] | ||
858 | 870 | except KeyError: | ||
859 | 871 | continue | ||
860 | 872 | checkbutton = AddonCheckButton(self.db, self.icons, addon) | ||
861 | 873 | #checkbutton.addon_pkgname.connect( | ||
862 | 874 | # "clicked", self._on_description_clicked, addon) | ||
863 | 875 | checkbutton.set_active(pkg.installed != None) | ||
864 | 876 | checkbutton.connect("toggled", self._on_checkbutton_toggled) | ||
865 | 877 | self.pack_start(checkbutton, False) | ||
866 | 878 | self.show_all() | ||
867 | 879 | return False | ||
868 | 880 | |||
869 | 881 | def _on_checkbutton_toggled(self, checkbutton): | ||
870 | 882 | addon = checkbutton.get_addon() | ||
871 | 883 | self.emit("toggled", addon, checkbutton.get_active()) | ||
872 | 884 | |||
873 | 885 | def _on_description_clicked(self, label, addon): | ||
874 | 886 | self.emit("description-clicked", addon) | ||
875 | 887 | |||
876 | 888 | class AddonsStateBar(gtk.Alignment): | ||
877 | 889 | __gsignals__ = {'changes-canceled': (gobject.SIGNAL_RUN_FIRST, | ||
878 | 890 | gobject.TYPE_NONE, | ||
879 | 891 | ()), | ||
880 | 892 | } | ||
881 | 893 | |||
882 | 894 | def __init__(self, cache, view): | ||
883 | 895 | gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) | ||
884 | 896 | self.set_redraw_on_allocate(False) | ||
885 | 897 | self.set_padding(mkit.SPACING_LARGE, | ||
886 | 898 | mkit.SPACING_LARGE, | ||
887 | 899 | mkit.SPACING_SMALL+2, | ||
888 | 900 | mkit.SPACING_SMALL) | ||
889 | 901 | |||
890 | 902 | self.hbox = gtk.HBox(spacing=mkit.SPACING_LARGE) | ||
891 | 903 | self.add(self.hbox) | ||
892 | 904 | |||
893 | 905 | self.cache = cache | ||
894 | 906 | self.view = view | ||
895 | 907 | self.applying = False | ||
896 | 908 | |||
897 | 909 | self.label_price = gtk.Label() | ||
898 | 910 | self.label_price.set_line_wrap(True) | ||
899 | 911 | self.hbox.pack_start(self.label_price, False, False) | ||
900 | 912 | |||
901 | 913 | self.hbuttonbox = gtk.HButtonBox() | ||
902 | 914 | self.hbuttonbox.set_layout(gtk.BUTTONBOX_END) | ||
903 | 915 | self.button_apply = gtk.Button(_("Apply Changes")) | ||
904 | 916 | self.button_apply.connect("clicked", self._on_button_apply_clicked) | ||
905 | 917 | self.button_cancel = gtk.Button(_("Cancel")) | ||
906 | 918 | self.button_cancel.connect("clicked", self._on_button_cancel_clicked) | ||
907 | 919 | self.hbuttonbox.pack_start(self.button_cancel, False) | ||
908 | 920 | self.hbuttonbox.pack_start(self.button_apply, False) | ||
909 | 921 | self.hbox.pack_start(self.hbuttonbox) | ||
910 | 922 | |||
911 | 923 | self.fill_color = COLOR_GREEN_FILL | ||
912 | 924 | self.line_color = COLOR_GREEN_OUTLINE | ||
913 | 925 | |||
914 | 926 | def configure(self, app_details, addons_install, addons_remove): | ||
915 | 927 | if not addons_install and not addons_remove: | ||
916 | 928 | self.hide() | ||
917 | 929 | return | ||
918 | 930 | if app_details.price: | ||
919 | 931 | self.label_price.set_label(app_details.price) | ||
920 | 932 | else: | ||
921 | 933 | self.label_price.set_label(_("Free")) | ||
922 | 934 | self.show() | ||
923 | 935 | |||
924 | 936 | def draw(self, cr, a, expose_area): | ||
925 | 937 | if mkit.not_overlapping(a, expose_area): return | ||
926 | 938 | |||
927 | 939 | cr.save() | ||
928 | 940 | rr = mkit.ShapeRoundedRectangle() | ||
929 | 941 | rr.layout(cr, | ||
930 | 942 | a.x-1, a.y-1, | ||
931 | 943 | a.x+a.width, a.y+a.height, | ||
932 | 944 | radius=mkit.CORNER_RADIUS) | ||
933 | 945 | |||
934 | 946 | cr.set_source_rgb(*mkit.floats_from_string(self.fill_color)) | ||
935 | 947 | # cr.set_source_rgb(*mkit.floats_from_string(self.line_color)) | ||
936 | 948 | cr.fill() | ||
937 | 949 | |||
938 | 950 | cr.set_line_width(1) | ||
939 | 951 | cr.translate(0.5, 0.5) | ||
940 | 952 | |||
941 | 953 | rr.layout(cr, | ||
942 | 954 | a.x-1, a.y-1, | ||
943 | 955 | a.x+a.width, a.y+a.height, | ||
944 | 956 | radius=mkit.CORNER_RADIUS) | ||
945 | 957 | |||
946 | 958 | cr.set_source_rgb(*mkit.floats_from_string(self.line_color)) | ||
947 | 959 | cr.stroke() | ||
948 | 960 | cr.restore() | ||
949 | 961 | return | ||
950 | 962 | |||
951 | 963 | def get_applying(self): | ||
952 | 964 | return self.applying | ||
953 | 965 | def set_applying(self, applying): | ||
954 | 966 | self.applying = applying | ||
955 | 967 | |||
956 | 968 | def _on_button_apply_clicked(self, button): | ||
957 | 969 | self.applying = True | ||
958 | 970 | self.button_apply.set_sensitive(False) | ||
959 | 971 | self.button_cancel.set_sensitive(False) | ||
960 | 972 | AppDetailsViewBase.apply_changes(self.view) | ||
961 | 973 | |||
962 | 974 | def _on_button_cancel_clicked(self, button): | ||
963 | 975 | self.emit("changes-canceled") | ||
964 | 976 | |||
965 | 787 | 977 | ||
966 | 788 | class AppDetailsViewGtk(gtk.Viewport, AppDetailsViewBase): | 978 | class AppDetailsViewGtk(gtk.Viewport, AppDetailsViewBase): |
967 | 789 | 979 | ||
968 | @@ -804,8 +994,14 @@ | |||
969 | 804 | (gobject.TYPE_PYOBJECT,)), | 994 | (gobject.TYPE_PYOBJECT,)), |
970 | 805 | 'application-request-action' : (gobject.SIGNAL_RUN_LAST, | 995 | 'application-request-action' : (gobject.SIGNAL_RUN_LAST, |
971 | 806 | gobject.TYPE_NONE, | 996 | gobject.TYPE_NONE, |
973 | 807 | (gobject.TYPE_PYOBJECT, str), | 997 | (gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, str), |
974 | 808 | ), | 998 | ), |
975 | 999 | "navigation-request" : ( gobject.SIGNAL_RUN_LAST, | ||
976 | 1000 | gobject.TYPE_NONE, | ||
977 | 1001 | (str, | ||
978 | 1002 | ), | ||
979 | 1003 | ), | ||
980 | 1004 | |||
981 | 809 | } | 1005 | } |
982 | 810 | 1006 | ||
983 | 811 | 1007 | ||
984 | @@ -854,7 +1050,9 @@ | |||
985 | 854 | for pt in self.app_desc.points: | 1050 | for pt in self.app_desc.points: |
986 | 855 | pt.set_size_request(w-7*mkit.EM-166, -1) | 1051 | pt.set_size_request(w-7*mkit.EM-166, -1) |
987 | 856 | 1052 | ||
989 | 857 | self.info_table.set_width(w-6*mkit.EM) | 1053 | self.version_info.set_width(w-6*mkit.EM) |
990 | 1054 | self.license_info.set_width(w-6*mkit.EM) | ||
991 | 1055 | self.support_info.set_width(w-6*mkit.EM) | ||
992 | 858 | 1056 | ||
993 | 859 | self._full_redraw() # ewww | 1057 | self._full_redraw() # ewww |
994 | 860 | return | 1058 | return |
995 | @@ -886,6 +1084,11 @@ | |||
996 | 886 | self.action_bar.draw(cr, | 1084 | self.action_bar.draw(cr, |
997 | 887 | self.action_bar.allocation, | 1085 | self.action_bar.allocation, |
998 | 888 | event.area) | 1086 | event.area) |
999 | 1087 | |||
1000 | 1088 | if self.addons_bar.get_property('visible'): | ||
1001 | 1089 | self.addons_bar.draw(cr, | ||
1002 | 1090 | self.addons_bar.hbox.allocation, | ||
1003 | 1091 | event.area) | ||
1004 | 889 | 1092 | ||
1005 | 890 | if self.screenshot.get_property('visible'): | 1093 | if self.screenshot.get_property('visible'): |
1006 | 891 | self.screenshot.draw(cr, self.screenshot.allocation, expose_area) | 1094 | self.screenshot.draw(cr, self.screenshot.allocation, expose_area) |
1007 | @@ -957,17 +1160,17 @@ | |||
1008 | 957 | 1160 | ||
1009 | 958 | # we have our own viewport so we know when the viewport grows/shrinks | 1161 | # we have our own viewport so we know when the viewport grows/shrinks |
1010 | 959 | self.vbox.set_redraw_on_allocate(False) | 1162 | self.vbox.set_redraw_on_allocate(False) |
1012 | 960 | 1163 | ||
1013 | 961 | # framed section that contains all app details | 1164 | # framed section that contains all app details |
1014 | 962 | self.app_info = mkit.FramedSection() | 1165 | self.app_info = mkit.FramedSection() |
1015 | 963 | self.app_info.image.set_size_request(84, 84) | 1166 | self.app_info.image.set_size_request(84, 84) |
1017 | 964 | self.app_info.set_spacing(mkit.SPACING_LARGE) | 1167 | self.app_info.set_spacing(mkit.SPACING_XLARGE) |
1018 | 965 | self.app_info.header.set_spacing(mkit.SPACING_XLARGE) | 1168 | self.app_info.header.set_spacing(mkit.SPACING_XLARGE) |
1019 | 966 | self.app_info.header_alignment.set_padding(mkit.SPACING_SMALL, | 1169 | self.app_info.header_alignment.set_padding(mkit.SPACING_SMALL, |
1020 | 967 | mkit.SPACING_SMALL, | 1170 | mkit.SPACING_SMALL, |
1021 | 968 | 0, 0) | 1171 | 0, 0) |
1022 | 969 | 1172 | ||
1024 | 970 | self.app_info.body.set_spacing(mkit.SPACING_LARGE) | 1173 | self.app_info.body.set_spacing(mkit.SPACING_MED) |
1025 | 971 | self.vbox.pack_start(self.app_info, False) | 1174 | self.vbox.pack_start(self.app_info, False) |
1026 | 972 | 1175 | ||
1027 | 973 | # a11y for name/summary | 1176 | # a11y for name/summary |
1028 | @@ -977,9 +1180,9 @@ | |||
1029 | 977 | # controls which are displayed if the app is installed | 1180 | # controls which are displayed if the app is installed |
1030 | 978 | self.action_bar = PackageStatusBar(self) | 1181 | self.action_bar = PackageStatusBar(self) |
1031 | 979 | self.app_info.body.pack_start(self.action_bar, False) | 1182 | self.app_info.body.pack_start(self.action_bar, False) |
1033 | 980 | 1183 | ||
1034 | 981 | # FramedSection which contains the app description | 1184 | # FramedSection which contains the app description |
1036 | 982 | self.desc_section = mkit.FramedSection(xpadding=mkit.SPACING_LARGE) | 1185 | self.desc_section = mkit.FramedSection(xpadding=mkit.SPACING_XLARGE) |
1037 | 983 | self.desc_section.header_alignment.set_padding(0,0,0,0) | 1186 | self.desc_section.header_alignment.set_padding(0,0,0,0) |
1038 | 984 | 1187 | ||
1039 | 985 | self.app_info.body.pack_start(self.desc_section, False) | 1188 | self.app_info.body.pack_start(self.desc_section, False) |
1040 | @@ -998,7 +1201,7 @@ | |||
1041 | 998 | # screenshot | 1201 | # screenshot |
1042 | 999 | self.screenshot = ScreenshotView(self.distro, self.icons) | 1202 | self.screenshot = ScreenshotView(self.distro, self.icons) |
1043 | 1000 | app_desc_hb.pack_end(self.screenshot) | 1203 | app_desc_hb.pack_end(self.screenshot) |
1045 | 1001 | 1204 | ||
1046 | 1002 | # homepage link button | 1205 | # homepage link button |
1047 | 1003 | self.homepage_btn = mkit.HLinkButton(_('Website')) | 1206 | self.homepage_btn = mkit.HLinkButton(_('Website')) |
1048 | 1004 | self.homepage_btn.connect('clicked', self._on_homepage_clicked) | 1207 | self.homepage_btn.connect('clicked', self._on_homepage_clicked) |
1049 | @@ -1014,9 +1217,32 @@ | |||
1050 | 1014 | self.share_btn.connect('clicked', self._on_share_clicked) | 1217 | self.share_btn.connect('clicked', self._on_share_clicked) |
1051 | 1015 | self.app_desc.footer.pack_start(self.share_btn, False) | 1218 | self.app_desc.footer.pack_start(self.share_btn, False) |
1052 | 1016 | 1219 | ||
1056 | 1017 | # package info table | 1220 | alignment = gtk.Alignment() |
1057 | 1018 | self.info_table = PackageInfoTable() | 1221 | alignment.set_padding(mkit.SPACING_LARGE, 0, 0, 0) |
1058 | 1019 | self.app_info.body.pack_start(self.info_table, False) | 1222 | self.desc_section.body.pack_start(alignment, False) |
1059 | 1223 | |||
1060 | 1224 | # add-on handling | ||
1061 | 1225 | self.addon_view = AddonView(self.cache, self.db, self.icons) | ||
1062 | 1226 | self.addon_view.connect("toggled", self._on_addon_view_toggled) | ||
1063 | 1227 | self.addon_view.connect("description-clicked", self._on_addon_view_description_clicked) | ||
1064 | 1228 | alignment.add(self.addon_view) | ||
1065 | 1229 | |||
1066 | 1230 | self.totalsize_info = PackageInfo(_("Total size:")) | ||
1067 | 1231 | self.app_info.body.pack_start(self.totalsize_info, False) | ||
1068 | 1232 | |||
1069 | 1233 | self.addons_bar = AddonsStateBar(self.cache, self) | ||
1070 | 1234 | self.addons_bar.connect("changes-canceled", self._on_addonsbar_changescanceled) | ||
1071 | 1235 | self.app_info.body.pack_start(self.addons_bar, False) | ||
1072 | 1236 | |||
1073 | 1237 | # package info | ||
1074 | 1238 | self.version_info = PackageInfo(_("Version:")) | ||
1075 | 1239 | self.app_info.body.pack_start(self.version_info, False) | ||
1076 | 1240 | |||
1077 | 1241 | self.license_info = PackageInfo(_("License:")) | ||
1078 | 1242 | self.app_info.body.pack_start(self.license_info, False) | ||
1079 | 1243 | |||
1080 | 1244 | self.support_info = PackageInfo(_("Updates:")) | ||
1081 | 1245 | self.app_info.body.pack_start(self.support_info, False) | ||
1082 | 1020 | 1246 | ||
1083 | 1021 | self.show_all() | 1247 | self.show_all() |
1084 | 1022 | return | 1248 | return |
1085 | @@ -1053,12 +1279,16 @@ | |||
1086 | 1053 | 1279 | ||
1087 | 1054 | # if we have an error or if we need to enable a source, then hide everything else | 1280 | # if we have an error or if we need to enable a source, then hide everything else |
1088 | 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): |
1089 | 1056 | self.info_table.hide() | ||
1090 | 1057 | self.screenshot.hide() | 1282 | self.screenshot.hide() |
1091 | 1283 | self.version_info.hide() | ||
1092 | 1284 | self.license_info.hide() | ||
1093 | 1285 | self.support_info.hide() | ||
1094 | 1058 | self.desc_section.hide() | 1286 | self.desc_section.hide() |
1095 | 1059 | else: | 1287 | else: |
1096 | 1060 | self.desc_section.show() | 1288 | self.desc_section.show() |
1098 | 1061 | self.info_table.show() | 1289 | self.version_info.show() |
1099 | 1290 | self.license_info.show() | ||
1100 | 1291 | self.support_info.show() | ||
1101 | 1062 | self.screenshot.show() | 1292 | self.screenshot.show() |
1102 | 1063 | 1293 | ||
1103 | 1064 | # depending on pkg install state set action labels | 1294 | # depending on pkg install state set action labels |
1104 | @@ -1075,14 +1305,14 @@ | |||
1105 | 1075 | self.app_desc.body.a11y.set_name("Description: " + description) | 1305 | self.app_desc.body.a11y.set_name("Description: " + description) |
1106 | 1076 | 1306 | ||
1107 | 1077 | # show or hide the homepage button and set uri if homepage specified | 1307 | # show or hide the homepage button and set uri if homepage specified |
1109 | 1078 | if app_details.website and self.info_table.get_property('visible'): | 1308 | if app_details.website: |
1110 | 1079 | self.homepage_btn.show() | 1309 | self.homepage_btn.show() |
1111 | 1080 | self.homepage_btn.set_tooltip_text(app_details.website) | 1310 | self.homepage_btn.set_tooltip_text(app_details.website) |
1112 | 1081 | else: | 1311 | else: |
1113 | 1082 | self.homepage_btn.hide() | 1312 | self.homepage_btn.hide() |
1114 | 1083 | 1313 | ||
1115 | 1084 | # check if gwibber-poster is available, if so display Share... btn | 1314 | # check if gwibber-poster is available, if so display Share... btn |
1117 | 1085 | if self._gwibber_is_available and self.info_table.get_property('visible'): | 1315 | if self._gwibber_is_available: |
1118 | 1086 | self.share_btn.show() | 1316 | self.share_btn.show() |
1119 | 1087 | else: | 1317 | else: |
1120 | 1088 | self.share_btn.hide() | 1318 | self.share_btn.hide() |
1121 | @@ -1107,7 +1337,21 @@ | |||
1122 | 1107 | support = app_details.maintenance_status | 1337 | support = app_details.maintenance_status |
1123 | 1108 | else: | 1338 | else: |
1124 | 1109 | support = _("Unknown") | 1339 | support = _("Unknown") |
1126 | 1110 | self.info_table.configure(version, license, support) | 1340 | |
1127 | 1341 | self.version_info.set_value(version) | ||
1128 | 1342 | self.license_info.set_value(license) | ||
1129 | 1343 | self.support_info.set_value(support) | ||
1130 | 1344 | |||
1131 | 1345 | # Update add-on interface | ||
1132 | 1346 | self.addon_view.hide_all() | ||
1133 | 1347 | gobject.idle_add(self.addon_view.set_addons, self.app_details, self.recommended, self.suggested) | ||
1134 | 1348 | |||
1135 | 1349 | # Update total size label | ||
1136 | 1350 | self.totalsize_info.hide_all() | ||
1137 | 1351 | gobject.idle_add(self.update_totalsize) | ||
1138 | 1352 | |||
1139 | 1353 | # Update addons state bar | ||
1140 | 1354 | self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove) | ||
1141 | 1111 | return | 1355 | return |
1142 | 1112 | 1356 | ||
1143 | 1113 | # public API | 1357 | # public API |
1144 | @@ -1129,6 +1373,14 @@ | |||
1145 | 1129 | # init data | 1373 | # init data |
1146 | 1130 | self.app = app | 1374 | self.app = app |
1147 | 1131 | self.app_details = app.get_details(self.db) | 1375 | self.app_details = app.get_details(self.db) |
1148 | 1376 | self.recommended = self.addons_manager.recommended_addons(app.pkgname) | ||
1149 | 1377 | self.suggested = self.addons_manager.suggested_addons(app.pkgname) | ||
1150 | 1378 | LOG.debug("AppDetailsView.show_app recommended '%s'" % self.recommended) | ||
1151 | 1379 | LOG.debug("AppDetailsView.show_app suggested '%s'" % self.suggested) | ||
1152 | 1380 | |||
1153 | 1381 | self.addons_install = [] | ||
1154 | 1382 | self.addons_remove = [] | ||
1155 | 1383 | |||
1156 | 1132 | # for compat with the base class | 1384 | # for compat with the base class |
1157 | 1133 | self.appdetails = self.app_details | 1385 | self.appdetails = self.app_details |
1158 | 1134 | #print "AppDetailsViewGtk:" | 1386 | #print "AppDetailsViewGtk:" |
1159 | @@ -1150,7 +1402,31 @@ | |||
1160 | 1150 | def _update_interface_on_trans_ended(self, result): | 1402 | def _update_interface_on_trans_ended(self, result): |
1161 | 1151 | self.action_bar.button.set_sensitive(True) | 1403 | self.action_bar.button.set_sensitive(True) |
1162 | 1152 | self.action_bar.button.show() | 1404 | self.action_bar.button.show() |
1163 | 1405 | self.addons_bar.button_apply.set_sensitive(True) | ||
1164 | 1406 | self.addons_bar.button_cancel.set_sensitive(True) | ||
1165 | 1407 | state = self.action_bar.pkg_state | ||
1166 | 1408 | pkg_state = None | ||
1167 | 1409 | if state == PKG_STATE_INSTALLING or state == PKG_STATE_UPGRADING \ | ||
1168 | 1410 | or self.addons_bar.applying: | ||
1169 | 1411 | self.addons_bar.show_all() | ||
1170 | 1412 | pkg_state = PKG_STATE_INSTALLED | ||
1171 | 1413 | else: | ||
1172 | 1414 | self.addons_bar.hide_all() | ||
1173 | 1415 | pkg_state = PKG_STATE_UNINSTALLED | ||
1174 | 1153 | 1416 | ||
1175 | 1417 | if self.addons_bar.applying: | ||
1176 | 1418 | self.action_bar.configure(self.app_details, pkg_state) | ||
1177 | 1419 | self.addons_install = [] | ||
1178 | 1420 | self.addons_remove = [] | ||
1179 | 1421 | self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove) | ||
1180 | 1422 | self.addons_bar.applying = False | ||
1181 | 1423 | |||
1182 | 1424 | for widget in self.addon_view: | ||
1183 | 1425 | if widget != self.addon_view.label: | ||
1184 | 1426 | addon = widget.get_addon() | ||
1185 | 1427 | widget.set_active(self.cache[addon].installed != None) | ||
1186 | 1428 | return False | ||
1187 | 1429 | |||
1188 | 1154 | state = self.action_bar.pkg_state | 1430 | state = self.action_bar.pkg_state |
1189 | 1155 | # handle purchase: install purchased has multiple steps | 1431 | # handle purchase: install purchased has multiple steps |
1190 | 1156 | if (state == PKG_STATE_INSTALLING_PURCHASED and | 1432 | if (state == PKG_STATE_INSTALLING_PURCHASED and |
1191 | @@ -1164,14 +1440,24 @@ | |||
1192 | 1164 | # normal states | 1440 | # normal states |
1193 | 1165 | elif state == PKG_STATE_REMOVING: | 1441 | elif state == PKG_STATE_REMOVING: |
1194 | 1166 | self.action_bar.configure(self.app_details, PKG_STATE_UNINSTALLED) | 1442 | self.action_bar.configure(self.app_details, PKG_STATE_UNINSTALLED) |
1195 | 1443 | self.addons_install = [] | ||
1196 | 1444 | self.addons_remove = [] | ||
1197 | 1167 | elif state == PKG_STATE_INSTALLING: | 1445 | elif state == PKG_STATE_INSTALLING: |
1198 | 1168 | self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED) | 1446 | self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED) |
1199 | 1447 | self.addons_install = [] | ||
1200 | 1448 | self.addons_remove = [] | ||
1201 | 1449 | self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove) | ||
1202 | 1169 | elif state == PKG_STATE_UPGRADING: | 1450 | elif state == PKG_STATE_UPGRADING: |
1203 | 1170 | self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED) | 1451 | self.action_bar.configure(self.app_details, PKG_STATE_INSTALLED) |
1204 | 1171 | return False | 1452 | return False |
1205 | 1172 | 1453 | ||
1206 | 1173 | def _on_transaction_started(self, backend): | 1454 | def _on_transaction_started(self, backend): |
1207 | 1174 | self.action_bar.button.hide() | 1455 | self.action_bar.button.hide() |
1208 | 1456 | |||
1209 | 1457 | if self.addons_bar.get_applying(): | ||
1210 | 1458 | self.action_bar.configure(self.app_details, APP_ACTION_APPLY) | ||
1211 | 1459 | return | ||
1212 | 1460 | |||
1213 | 1175 | state = self.action_bar.pkg_state | 1461 | state = self.action_bar.pkg_state |
1214 | 1176 | LOG.debug("_on_transaction_stated %s" % state) | 1462 | LOG.debug("_on_transaction_stated %s" % state) |
1215 | 1177 | if state == PKG_STATE_NEEDS_PURCHASE: | 1463 | if state == PKG_STATE_NEEDS_PURCHASE: |
1216 | @@ -1205,6 +1491,8 @@ | |||
1217 | 1205 | gobject.idle_add(self._show_prog_idle_cb) | 1491 | gobject.idle_add(self._show_prog_idle_cb) |
1218 | 1206 | if pkgname in backend.pending_transactions: | 1492 | if pkgname in backend.pending_transactions: |
1219 | 1207 | self.action_bar.progress.set_fraction(progress/100.0) | 1493 | self.action_bar.progress.set_fraction(progress/100.0) |
1220 | 1494 | if progress == 100: | ||
1221 | 1495 | self.action_bar.progress.set_fraction(1) | ||
1222 | 1208 | return | 1496 | return |
1223 | 1209 | 1497 | ||
1224 | 1210 | def _show_prog_idle_cb(self): | 1498 | def _show_prog_idle_cb(self): |
1225 | @@ -1293,18 +1581,143 @@ | |||
1226 | 1293 | return self.icons.load_icon(MISSING_APP_ICON, 84, 0) | 1581 | return self.icons.load_icon(MISSING_APP_ICON, 84, 0) |
1227 | 1294 | elif app_details.icon_needs_download: | 1582 | elif app_details.icon_needs_download: |
1228 | 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") |
1239 | 1296 | 1584 | else: | |
1240 | 1297 | def on_image_download_complete(downloader, image_file_path): | 1585 | return self.icons.load_icon(MISSING_APP_ICON, 84, 0) |
1241 | 1298 | # when the download is complete, replace the icon in the view with the downloaded one | 1586 | else: |
1242 | 1299 | pb = gtk.gdk.pixbuf_new_from_file(image_file_path) | 1587 | return self.icons.load_icon(MISSING_APP_ICON, 84, 0) |
1243 | 1300 | self.app_info.set_icon_from_pixbuf(pb) | 1588 | |
1244 | 1301 | 1589 | def _on_addon_view_description_clicked(self, button, pkgname): | |
1245 | 1302 | icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, app_details.icon_file_name) | 1590 | self.emit("navigation-request", pkgname) |
1246 | 1303 | image_downloader = ImageDownloader() | 1591 | return |
1247 | 1304 | image_downloader.connect('image-download-complete', on_image_download_complete) | 1592 | |
1248 | 1305 | image_downloader.download_image(app_details.icon_url, icon_file_path) | 1593 | def _on_addon_view_toggled(self, view, addon, isActive): |
1249 | 1594 | if isActive: | ||
1250 | 1595 | self._set_addon_install(addon) | ||
1251 | 1596 | else: | ||
1252 | 1597 | self._set_addon_remove(addon) | ||
1253 | 1598 | if self.app_details.pkg_state == PKG_STATE_INSTALLED: | ||
1254 | 1599 | self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove) | ||
1255 | 1600 | gobject.idle_add(self.update_totalsize) | ||
1256 | 1601 | |||
1257 | 1602 | def _on_addonsbar_changescanceled(self, widget): | ||
1258 | 1603 | self.addons_install = [] | ||
1259 | 1604 | self.addons_remove = [] | ||
1260 | 1605 | self.addon_view.set_addons(self.app_details, | ||
1261 | 1606 | self.recommended, | ||
1262 | 1607 | self.suggested) | ||
1263 | 1608 | self.addons_bar.configure(self.app_details, self.addons_install, self.addons_remove) | ||
1264 | 1609 | gobject.idle_add(self.update_totalsize) | ||
1265 | 1610 | |||
1266 | 1611 | def get_icon_filename(self, iconname, iconsize): | ||
1267 | 1612 | iconinfo = self.icons.lookup_icon(iconname, iconsize, 0) | ||
1268 | 1613 | if not iconinfo: | ||
1269 | 1614 | iconinfo = self.icons.lookup_icon(MISSING_APP_ICON, iconsize, 0) | ||
1270 | 1615 | return iconinfo.get_filename() | ||
1271 | 1616 | |||
1272 | 1617 | def on_image_download_complete(downloader, image_file_path): | ||
1273 | 1618 | # when the download is complete, replace the icon in the view with the downloaded one | ||
1274 | 1619 | pb = gtk.gdk.pixbuf_new_from_file(image_file_path) | ||
1275 | 1620 | self.app_info.set_icon_from_pixbuf(pb) | ||
1276 | 1621 | |||
1277 | 1622 | icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, app_details.icon_file_name) | ||
1278 | 1623 | image_downloader = ImageDownloader() | ||
1279 | 1624 | image_downloader.connect('image-download-complete', on_image_download_complete) | ||
1280 | 1625 | image_downloader.download_image(app_details.icon_url, icon_file_path) | ||
1281 | 1306 | 1626 | ||
1282 | 1307 | return self.icons.load_icon(MISSING_APP_ICON, 84, 0) | 1627 | return self.icons.load_icon(MISSING_APP_ICON, 84, 0) |
1283 | 1628 | |||
1284 | 1629 | def update_totalsize(self): | ||
1285 | 1630 | def pkg_downloaded(pkg_version): | ||
1286 | 1631 | filename = os.path.basename(pkg_version.filename) | ||
1287 | 1632 | # FIXME: use relative path here | ||
1288 | 1633 | return os.path.exists("/var/cache/apt/archives/" + filename) | ||
1289 | 1634 | |||
1290 | 1635 | while gtk.events_pending(): | ||
1291 | 1636 | gtk.main_iteration() | ||
1292 | 1637 | |||
1293 | 1638 | pkgs_to_install = [] | ||
1294 | 1639 | pkgs_to_remove = [] | ||
1295 | 1640 | total_download_size = 0 # in kB | ||
1296 | 1641 | total_install_size = 0 # in kB | ||
1297 | 1642 | label_string = "" | ||
1298 | 1643 | |||
1299 | 1644 | try: | ||
1300 | 1645 | pkg = self.cache[self.app_details.pkgname] | ||
1301 | 1646 | except KeyError: | ||
1302 | 1647 | self.totalsize_info.hide_all() | ||
1303 | 1648 | return False | ||
1304 | 1649 | version = pkg.installed | ||
1305 | 1650 | if version == None: | ||
1306 | 1651 | version = max(pkg.versions) | ||
1307 | 1652 | pkgs_to_install.append(version) | ||
1308 | 1653 | deps_inst = self.cache.get_all_deps_installing(pkg) | ||
1309 | 1654 | for dep in deps_inst: | ||
1310 | 1655 | if self.cache[dep].installed == None: | ||
1311 | 1656 | version = max(self.cache[dep].versions) | ||
1312 | 1657 | pkgs_to_install.append(version) | ||
1313 | 1658 | deps_remove = self.cache.get_all_deps_removing(pkg) | ||
1314 | 1659 | for dep in deps_remove: | ||
1315 | 1660 | if self.cache[dep].installed != None: | ||
1316 | 1661 | version = self.cache[dep].installed | ||
1317 | 1662 | pkgs_to_remove.append(version) | ||
1318 | 1663 | |||
1319 | 1664 | for addon in self.addons_install: | ||
1320 | 1665 | version = max(self.cache[addon].versions) | ||
1321 | 1666 | pkgs_to_install.append(version) | ||
1322 | 1667 | deps_inst = self.cache.get_all_deps_installing(self.cache[addon]) | ||
1323 | 1668 | for dep in deps_inst: | ||
1324 | 1669 | if self.cache[dep].installed == None: | ||
1325 | 1670 | version = max(self.cache[dep].versions) | ||
1326 | 1671 | pkgs_to_install.append(version) | ||
1327 | 1672 | deps_remove = self.cache.get_all_deps_removing(self.cache[addon]) | ||
1328 | 1673 | for dep in deps_remove: | ||
1329 | 1674 | if self.cache[dep].installed != None: | ||
1330 | 1675 | version = self.cache[dep].installed | ||
1331 | 1676 | pkgs_to_remove.append(version) | ||
1332 | 1677 | for addon in self.addons_remove: | ||
1333 | 1678 | version = self.cache[addon].installed | ||
1334 | 1679 | pkgs_to_remove.append(version) | ||
1335 | 1680 | deps_inst = self.cache.get_all_deps_installing(self.cache[addon]) | ||
1336 | 1681 | for dep in deps_inst: | ||
1337 | 1682 | if self.cache[dep].installed == None: | ||
1338 | 1683 | version = max(self.cache[dep].versions) | ||
1339 | 1684 | pkgs_to_install.append(version) | ||
1340 | 1685 | deps_remove = self.cache.get_all_deps_removing(self.cache[addon]) | ||
1341 | 1686 | for dep in deps_remove: | ||
1342 | 1687 | if self.cache[dep].installed != None: | ||
1343 | 1688 | version = self.cache[dep].installed | ||
1344 | 1689 | pkgs_to_remove.append(version) | ||
1345 | 1690 | |||
1346 | 1691 | for pkg in pkgs_to_install: | ||
1347 | 1692 | if pkgs_to_install.count(pkg) > 1: | ||
1348 | 1693 | pkgs_to_install.remove(pkg) | ||
1349 | 1694 | for pkg in pkgs_to_remove: | ||
1350 | 1695 | if pkgs_to_remove.count(pkg) > 1: | ||
1351 | 1696 | pkgs_to_remove.remove(pkg) | ||
1352 | 1697 | |||
1353 | 1698 | for pkg in pkgs_to_install: | ||
1354 | 1699 | if not pkg_downloaded(pkg): | ||
1355 | 1700 | total_download_size += pkg.size | ||
1356 | 1701 | total_install_size += pkg.installed_size | ||
1357 | 1702 | for pkg in pkgs_to_remove: | ||
1358 | 1703 | total_install_size -= pkg.installed_size | ||
1359 | 1704 | |||
1360 | 1705 | if total_download_size > 0: | ||
1361 | 1706 | download_size = apt_pkg.size_to_str(total_download_size) | ||
1362 | 1707 | label_string += _("%sB to download, " % (download_size)) | ||
1363 | 1708 | if total_install_size > 0: | ||
1364 | 1709 | install_size = apt_pkg.size_to_str(total_install_size) | ||
1365 | 1710 | label_string += _("%sB when installed" % (install_size)) | ||
1366 | 1711 | elif total_install_size < 0: | ||
1367 | 1712 | remove_size = apt_pkg.size_to_str(-total_install_size) | ||
1368 | 1713 | label_string += _("%sB to be freed" % (remove_size)) | ||
1369 | 1714 | |||
1370 | 1715 | if label_string == "": | ||
1371 | 1716 | self.totalsize_info.hide_all() | ||
1372 | 1717 | else: | ||
1373 | 1718 | self.totalsize_info.set_value(label_string) | ||
1374 | 1719 | self.totalsize_info.show_all() | ||
1375 | 1720 | return False | ||
1376 | 1308 | 1721 | ||
1377 | 1309 | 1722 | ||
1378 | 1310 | if __name__ == "__main__": | 1723 | if __name__ == "__main__": |
1379 | 1311 | 1724 | ||
1380 | === modified file 'test/test_appdetails_view.py' | |||
1381 | --- test/test_appdetails_view.py 2010-08-11 19:43:05 +0000 | |||
1382 | +++ test/test_appdetails_view.py 2010-08-16 20:56:42 +0000 | |||
1383 | @@ -65,6 +65,27 @@ | |||
1384 | 65 | for i in range(PKG_STATE_UNKNOWN): | 65 | for i in range(PKG_STATE_UNKNOWN): |
1385 | 66 | mock_app_details.pkg_state = i | 66 | mock_app_details.pkg_state = i |
1386 | 67 | self.appdetails.show_app(app) | 67 | self.appdetails.show_app(app) |
1387 | 68 | |||
1388 | 69 | def test_show_app_addons(self): | ||
1389 | 70 | app = Application("Web browser", "firefox") | ||
1390 | 71 | mock_app_details = mock.Mock(AppDetails) | ||
1391 | 72 | mock_app_details.pkgname = "firefox" | ||
1392 | 73 | mock_app_details.appname = "Web browser" | ||
1393 | 74 | mock_app_details.display_name = "display_name" | ||
1394 | 75 | mock_app_details.display_summary = "display_summary" | ||
1395 | 76 | mock_app_details.error = None | ||
1396 | 77 | mock_app_details.warning = None | ||
1397 | 78 | mock_app_details.description = "description" | ||
1398 | 79 | mock_app_details.website = "website" | ||
1399 | 80 | mock_app_details.thumbnail = None | ||
1400 | 81 | mock_app_details.license = "license" | ||
1401 | 82 | mock_app_details.maintenance_status = "support_status" | ||
1402 | 83 | mock_app_details.purchase_date = "purchase_date" | ||
1403 | 84 | mock_app_details.installation_date = "installation_date" | ||
1404 | 85 | mock_app_details.price = "price" | ||
1405 | 86 | mock_app_details._error_not_found = "" | ||
1406 | 87 | app.get_details = lambda db: mock_app_details | ||
1407 | 88 | self.appdetails.show_app(app) | ||
1408 | 68 | 89 | ||
1409 | 69 | 90 | ||
1410 | 70 | if __name__ == "__main__": | 91 | if __name__ == "__main__": |
1411 | 71 | 92 | ||
1412 | === modified file 'test/test_aptd.py' | |||
1413 | --- test/test_aptd.py 2010-07-08 15:13:27 +0000 | |||
1414 | +++ test/test_aptd.py 2010-08-16 20:56:42 +0000 | |||
1415 | @@ -41,6 +41,14 @@ | |||
1416 | 41 | keyserver = "keyserver.ubuntu.com" | 41 | keyserver = "keyserver.ubuntu.com" |
1417 | 42 | self.aptd.aptd_client.add_vendor_key_from_keyserver = self._monkey_patched_add_vendor_key_from_keyserver | 42 | self.aptd.aptd_client.add_vendor_key_from_keyserver = self._monkey_patched_add_vendor_key_from_keyserver |
1418 | 43 | self.aptd.add_vendor_key_from_keyserver(keyid, keyserver) | 43 | self.aptd.add_vendor_key_from_keyserver(keyid, keyserver) |
1419 | 44 | |||
1420 | 45 | def test_apply_changes(self): | ||
1421 | 46 | pkgname = "gimp" | ||
1422 | 47 | appname = "The GIMP app" | ||
1423 | 48 | iconname = "icon-gimp" | ||
1424 | 49 | addons_install = ["gimp-data-extras", "gimp-gutenprint"] | ||
1425 | 50 | addons_remove = ["gimp-plugin-registry"] | ||
1426 | 51 | yield self.aptd.apply_changes(pkgname, appname ,iconname, addons_install, addons_remove) | ||
1427 | 44 | 52 | ||
1428 | 45 | 53 | ||
1429 | 46 | if __name__ == "__main__": | 54 | if __name__ == "__main__": |
1430 | 47 | 55 | ||
1431 | === modified file 'test/test_gui.py' | |||
1432 | --- test/test_gui.py 2010-08-10 09:10:35 +0000 | |||
1433 | +++ test/test_gui.py 2010-08-16 20:56:42 +0000 | |||
1434 | @@ -166,7 +166,9 @@ | |||
1435 | 166 | self.app.show_available_packages(["i-dont-exit"]) | 166 | self.app.show_available_packages(["i-dont-exit"]) |
1436 | 167 | self._p() | 167 | self._p() |
1437 | 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")) |
1439 | 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")) |
1440 | 170 | self.assertFalse(self.app.available_pane.app_details.license_info.get_property("visible")) | ||
1441 | 171 | self.assertFalse(self.app.available_pane.app_details.support_info.get_property("visible")) | ||
1442 | 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")) |
1443 | 171 | 173 | ||
1444 | 172 | # helper stuff | 174 | # helper stuff |
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/SoftwareCen ter#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?