Merge lp:~mvo/software-center/tos-dialog into lp:software-center
- tos-dialog
- Merge into trunk
Proposed by
Michael Vogt
Status: | Merged |
---|---|
Merged at revision: | 2885 |
Proposed branch: | lp:~mvo/software-center/tos-dialog |
Merge into: | lp:software-center |
Diff against target: |
908 lines (+355/-136) (has conflicts) 16 files modified
data/ui/gtk3/SoftwareCenter.ui (+10/-0) data/ui/gtk3/dialogs.ui (+16/-11) po/POTFILES.in (+2/-0) softwarecenter/backend/oneconfhandler/core.py (+18/-3) softwarecenter/enums.py (+3/-0) softwarecenter/ui/gtk3/app.py (+7/-4) softwarecenter/ui/gtk3/dialogs/__init__.py (+20/-2) softwarecenter/ui/gtk3/dialogs/dialog_tos.py (+84/-0) softwarecenter/ui/gtk3/panes/availablepane.py (+5/-5) softwarecenter/ui/gtk3/review_gui_helper.py (+2/-5) softwarecenter/ui/gtk3/utils.py (+14/-0) softwarecenter/ui/gtk3/views/purchaseview.py (+34/-94) softwarecenter/ui/gtk3/views/webkit.py (+114/-0) softwarecenter/utils.py (+0/-10) test/gtk3/test_dialogs.py (+6/-0) test/gtk3/test_purchase.py (+20/-2) Text conflict in softwarecenter/backend/oneconfhandler/core.py |
To merge this branch: | bzr merge lp:~mvo/software-center/tos-dialog |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary Lasker (community) | Approve | ||
Review via email: mp+97810@code.launchpad.net |
Commit message
Description of the change
This adds a terms-of-service dialog before the first purchase.
To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote : | # |
Revision history for this message
Gary Lasker (gary-lasker) wrote : | # |
Thanks Michael, this looks great! Very nice work and nice unit tests! :) Just noting that the content ToS page at https:/
Thanks again!
review:
Approve
Revision history for this message
Michael Vogt (mvo) wrote : | # |
Thanks for the review! Yes, the tos link is currently 404 we wait for the server to deploy it. But we can not hold back the branch as it contains string fixes as well that need to get to the translators.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'data/ui/gtk3/SoftwareCenter.ui' |
2 | --- data/ui/gtk3/SoftwareCenter.ui 2011-11-29 18:52:13 +0000 |
3 | +++ data/ui/gtk3/SoftwareCenter.ui 2012-03-19 14:40:27 +0000 |
4 | @@ -503,6 +503,16 @@ |
5 | </object> |
6 | </child> |
7 | <child> |
8 | + <object class="GtkImageMenuItem" id="menuitem_tos"> |
9 | + <property name="label" translatable="yes">Terms of Service</property> |
10 | + <property name="visible">True</property> |
11 | + <property name="can_focus">False</property> |
12 | + <property name="use_action_appearance">False</property> |
13 | + <property name="use_stock">False</property> |
14 | + <signal name="activate" handler="on_menuitem_tos_activate" swapped="no"/> |
15 | + </object> |
16 | + </child> |
17 | + <child> |
18 | <object class="GtkImageMenuItem" id="menuitem_about"> |
19 | <property name="label">gtk-about</property> |
20 | <property name="visible">True</property> |
21 | |
22 | === modified file 'data/ui/gtk3/dialogs.ui' |
23 | --- data/ui/gtk3/dialogs.ui 2011-10-11 12:02:00 +0000 |
24 | +++ data/ui/gtk3/dialogs.ui 2012-03-19 14:40:27 +0000 |
25 | @@ -1,6 +1,6 @@ |
26 | <?xml version="1.0" encoding="UTF-8"?> |
27 | <interface> |
28 | - <requires lib="gtk+" version="2.16"/> |
29 | + <!-- interface-requires gtk+ 3.0 --> |
30 | <object class="GtkDialog" id="dialog_broken_cache"> |
31 | <property name="can_focus">False</property> |
32 | <property name="border_width">5</property> |
33 | @@ -23,6 +23,7 @@ |
34 | <child> |
35 | <object class="GtkButton" id="button1"> |
36 | <property name="label" translatable="yes">Cancel</property> |
37 | + <property name="use_action_appearance">False</property> |
38 | <property name="visible">True</property> |
39 | <property name="can_focus">True</property> |
40 | <property name="receives_default">False</property> |
41 | @@ -37,6 +38,7 @@ |
42 | <child> |
43 | <object class="GtkButton" id="button2"> |
44 | <property name="label" translatable="yes">Repair</property> |
45 | + <property name="use_action_appearance">False</property> |
46 | <property name="visible">True</property> |
47 | <property name="can_focus">True</property> |
48 | <property name="can_default">True</property> |
49 | @@ -64,8 +66,6 @@ |
50 | <property name="can_focus">False</property> |
51 | <property name="row_spacing">24</property> |
52 | <property name="column_spacing">12</property> |
53 | - <property name="n_rows">2</property> |
54 | - <property name="n_columns">2</property> |
55 | <child> |
56 | <object class="GtkLabel" id="label_broken_cache"> |
57 | <property name="visible">True</property> |
58 | @@ -162,6 +162,7 @@ |
59 | <child> |
60 | <object class="GtkButton" id="button_deauthorize_cancel"> |
61 | <property name="label" translatable="yes">Cancel</property> |
62 | + <property name="use_action_appearance">False</property> |
63 | <property name="visible">True</property> |
64 | <property name="can_focus">True</property> |
65 | <property name="receives_default">False</property> |
66 | @@ -176,6 +177,7 @@ |
67 | <child> |
68 | <object class="GtkButton" id="button_deauthorize_do"> |
69 | <property name="label" translatable="yes">Remove</property> |
70 | + <property name="use_action_appearance">False</property> |
71 | <property name="visible">True</property> |
72 | <property name="can_focus">True</property> |
73 | <property name="can_default">True</property> |
74 | @@ -212,6 +214,9 @@ |
75 | <property name="n_rows">2</property> |
76 | <property name="n_columns">2</property> |
77 | <child> |
78 | + <placeholder/> |
79 | + </child> |
80 | + <child> |
81 | <object class="GtkImage" id="image_icon"> |
82 | <property name="visible">True</property> |
83 | <property name="can_focus">False</property> |
84 | @@ -268,9 +273,6 @@ |
85 | <property name="y_padding">6</property> |
86 | </packing> |
87 | </child> |
88 | - <child> |
89 | - <placeholder/> |
90 | - </child> |
91 | </object> |
92 | </child> |
93 | </object> |
94 | @@ -311,6 +313,7 @@ |
95 | <child> |
96 | <object class="GtkButton" id="button_dependency_cancel"> |
97 | <property name="label" translatable="yes">Cancel</property> |
98 | + <property name="use_action_appearance">False</property> |
99 | <property name="visible">True</property> |
100 | <property name="can_focus">True</property> |
101 | <property name="receives_default">False</property> |
102 | @@ -325,6 +328,7 @@ |
103 | <child> |
104 | <object class="GtkButton" id="button_dependency_do"> |
105 | <property name="label" translatable="yes">Remove</property> |
106 | + <property name="use_action_appearance">False</property> |
107 | <property name="visible">True</property> |
108 | <property name="can_focus">True</property> |
109 | <property name="can_default">True</property> |
110 | @@ -352,8 +356,6 @@ |
111 | <property name="can_focus">False</property> |
112 | <property name="row_spacing">6</property> |
113 | <property name="column_spacing">6</property> |
114 | - <property name="n_rows">2</property> |
115 | - <property name="n_columns">2</property> |
116 | <child> |
117 | <object class="GtkImage" id="image_package_icon"> |
118 | <property name="visible">True</property> |
119 | @@ -446,6 +448,7 @@ |
120 | <child> |
121 | <object class="GtkButton" id="gwib_err_cancel_button"> |
122 | <property name="label">gtk-cancel</property> |
123 | + <property name="use_action_appearance">False</property> |
124 | <property name="visible">True</property> |
125 | <property name="can_focus">True</property> |
126 | <property name="receives_default">True</property> |
127 | @@ -461,6 +464,7 @@ |
128 | <child> |
129 | <object class="GtkButton" id="gwib_err_retry_button"> |
130 | <property name="label" translatable="yes">Retry</property> |
131 | + <property name="use_action_appearance">False</property> |
132 | <property name="visible">True</property> |
133 | <property name="can_focus">True</property> |
134 | <property name="receives_default">True</property> |
135 | @@ -505,6 +509,7 @@ |
136 | <child> |
137 | <object class="GtkButton" id="button_install_error_ok"> |
138 | <property name="label" translatable="yes">OK</property> |
139 | + <property name="use_action_appearance">False</property> |
140 | <property name="visible">True</property> |
141 | <property name="can_focus">True</property> |
142 | <property name="can_default">True</property> |
143 | @@ -541,6 +546,9 @@ |
144 | <property name="n_rows">2</property> |
145 | <property name="n_columns">2</property> |
146 | <child> |
147 | + <placeholder/> |
148 | + </child> |
149 | + <child> |
150 | <object class="GtkImage" id="image_package_icon1"> |
151 | <property name="visible">True</property> |
152 | <property name="can_focus">False</property> |
153 | @@ -604,9 +612,6 @@ |
154 | <property name="bottom_attach">2</property> |
155 | </packing> |
156 | </child> |
157 | - <child> |
158 | - <placeholder/> |
159 | - </child> |
160 | </object> |
161 | </child> |
162 | </object> |
163 | |
164 | === modified file 'po/POTFILES.in' |
165 | --- po/POTFILES.in 2012-02-09 01:40:52 +0000 |
166 | +++ po/POTFILES.in 2012-03-19 14:40:27 +0000 |
167 | @@ -39,6 +39,7 @@ |
168 | softwarecenter/ui/gtk3/app.py |
169 | softwarecenter/ui/gtk3/review_gui_helper.py |
170 | softwarecenter/ui/gtk3/dialogs/__init__.py |
171 | +softwarecenter/ui/gtk3/dialogs/dialog_tos.py |
172 | softwarecenter/ui/gtk3/dialogs/deauthorize_dialog.py |
173 | softwarecenter/ui/gtk3/dialogs/dependency_dialogs.py |
174 | softwarecenter/ui/gtk3/models/appstore2.py |
175 | @@ -55,6 +56,7 @@ |
176 | softwarecenter/ui/gtk3/views/appview.py |
177 | softwarecenter/ui/gtk3/views/catview_gtk.py |
178 | softwarecenter/ui/gtk3/views/purchaseview.py |
179 | +softwarecenter/ui/gtk3/views/webkit.py |
180 | softwarecenter/ui/gtk3/widgets/apptreeview.py |
181 | softwarecenter/ui/gtk3/widgets/backforward.py |
182 | softwarecenter/ui/gtk3/widgets/buttons.py |
183 | |
184 | === modified file 'softwarecenter/backend/oneconfhandler/core.py' |
185 | --- softwarecenter/backend/oneconfhandler/core.py 2012-03-16 19:16:21 +0000 |
186 | +++ softwarecenter/backend/oneconfhandler/core.py 2012-03-19 14:40:27 +0000 |
187 | @@ -17,12 +17,12 @@ |
188 | # this program; if not, write to the Free Software Foundation, Inc., |
189 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
190 | |
191 | - |
192 | from oneconf.dbusconnect import DbusConnect |
193 | from oneconf.enums import MIN_TIME_WITHOUT_ACTIVITY |
194 | |
195 | from softwarecenter.backend.login_sso import get_sso_backend |
196 | from softwarecenter.backend.ubuntusso import get_ubuntu_sso_backend |
197 | +from softwarecenter.enums import SOFTWARE_CENTER_NAME_KEYRING |
198 | |
199 | import datetime |
200 | from gi.repository import GObject |
201 | @@ -31,8 +31,12 @@ |
202 | from gettext import gettext as _ |
203 | |
204 | LOG = logging.getLogger(__name__) |
205 | - |
206 | - |
207 | +<<<<<<< TREE |
208 | + |
209 | + |
210 | +======= |
211 | +1 |
212 | +>>>>>>> MERGE-SOURCE |
213 | class OneConfHandler(GObject.GObject): |
214 | |
215 | __gsignals__ = { |
216 | @@ -51,10 +55,14 @@ |
217 | |
218 | LOG.debug("OneConf Handler init") |
219 | super(OneConfHandler, self).__init__() |
220 | +<<<<<<< TREE |
221 | |
222 | # FIXME: should be an enum common to OneConf and here |
223 | self.appname = "Ubuntu Software Center" |
224 | |
225 | +======= |
226 | + |
227 | +>>>>>>> MERGE-SOURCE |
228 | # OneConf stuff |
229 | self.oneconf = DbusConnect() |
230 | self.oneconf.hosts_dbus_object.connect_to_signal('hostlist_changed', |
231 | @@ -166,12 +174,19 @@ |
232 | def _try_login(self): |
233 | '''Try to get the credential or login on ubuntu sso''' |
234 | logging.debug("OneConf login()") |
235 | +<<<<<<< TREE |
236 | help_text = _("With multiple Ubuntu computers, you can publish " |
237 | "their inventories online to compare the software " |
238 | "installed on each\nNo-one else will be able to see " |
239 | "what you have installed.") |
240 | self.sso = get_sso_backend(0, |
241 | self.appname, help_text) |
242 | +======= |
243 | + help_text = _("With multiple Ubuntu computers, you can publish their inventories online to compare the software installed on each\n" |
244 | + "No-one else will be able to see what you have installed.") |
245 | + self.sso = get_sso_backend( |
246 | + 0, SOFTWARE_CENTER_NAME_KEYRING, help_text) |
247 | +>>>>>>> MERGE-SOURCE |
248 | self.sso.connect("login-successful", self._maybe_login_successful) |
249 | self.sso.connect("login-canceled", self._login_canceled) |
250 | self.sso.login_or_register() |
251 | |
252 | === modified file 'softwarecenter/enums.py' |
253 | --- softwarecenter/enums.py 2012-03-15 20:56:56 +0000 |
254 | +++ softwarecenter/enums.py 2012-03-19 14:40:27 +0000 |
255 | @@ -52,6 +52,9 @@ |
256 | UBUNTU_SSO_SERVICE = os.environ.get( |
257 | "USSOC_SERVICE_URL", "https://login.ubuntu.com/api/1.0") |
258 | |
259 | +# the terms-of-service link |
260 | +SOFTWARE_CENTER_TOS_LINK = "http://apps.ubuntu.com/cat/tos" |
261 | + |
262 | # version of the database, every time something gets added (like |
263 | # terms for mime-type) increase this (but keep as a string!) |
264 | DB_SCHEMA_VERSION = "6" |
265 | |
266 | === modified file 'softwarecenter/ui/gtk3/app.py' |
267 | --- softwarecenter/ui/gtk3/app.py 2012-03-16 17:49:04 +0000 |
268 | +++ softwarecenter/ui/gtk3/app.py 2012-03-19 14:40:27 +0000 |
269 | @@ -59,6 +59,7 @@ |
270 | DB_SCHEMA_VERSION, |
271 | MOUSE_EVENT_FORWARD_BUTTON, |
272 | MOUSE_EVENT_BACK_BUTTON, |
273 | + SOFTWARE_CENTER_TOS_LINK, |
274 | SOFTWARE_CENTER_NAME_KEYRING) |
275 | from softwarecenter.utils import (clear_token_from_ubuntu_sso, |
276 | get_http_proxy_string_from_gsettings, |
277 | @@ -1068,10 +1069,12 @@ |
278 | self.aboutdialog.show() |
279 | |
280 | def on_menuitem_help_activate(self, menuitem): |
281 | - # run yelp |
282 | - p = subprocess.Popen(["yelp", "ghelp:software-center"]) |
283 | - # collect the exit status (otherwise we leave zombies) |
284 | - GObject.timeout_add_seconds(1, lambda p: p.poll() == None, p) |
285 | + # run browser |
286 | + (pid, stdin, stdout, stderr) = GObject.spawn_async( |
287 | + ["yelp", "ghelp:software-center"], flags=GObject.SPAWN_SEARCH_PATH) |
288 | + |
289 | + def on_menuitem_tos_activate(self, menuitem): |
290 | + webbrowser.open_new_tab(SOFTWARE_CENTER_TOS_LINK) |
291 | |
292 | def on_menuitem_developer_activate(self, menuitem): |
293 | webbrowser.open(self.distro.DEVELOPER_URL) |
294 | |
295 | === modified file 'softwarecenter/ui/gtk3/dialogs/__init__.py' |
296 | --- softwarecenter/ui/gtk3/dialogs/__init__.py 2012-03-15 01:39:09 +0000 |
297 | +++ softwarecenter/ui/gtk3/dialogs/__init__.py 2012-03-19 14:40:27 +0000 |
298 | @@ -21,9 +21,10 @@ |
299 | gi.require_version("Gtk", "3.0") |
300 | from gi.repository import Gtk |
301 | |
302 | - |
303 | from gettext import gettext as _ |
304 | |
305 | +import softwarecenter.paths |
306 | + |
307 | |
308 | class SimpleGtkbuilderDialog(object): |
309 | def __init__(self, datadir, domain): |
310 | @@ -37,10 +38,23 @@ |
311 | name = Gtk.Buildable.get_name(o) |
312 | setattr(self, name, o) |
313 | |
314 | + |
315 | # for unitesting only |
316 | _DIALOG = None |
317 | |
318 | |
319 | +def show_accept_tos_dialog(parent): |
320 | + global _DIALOG |
321 | + from dialog_tos import DialogTos |
322 | + dialog = DialogTos(parent) |
323 | + _DIALOG = dialog |
324 | + result = dialog.run() |
325 | + dialog.destroy() |
326 | + if result == Gtk.ResponseType.YES: |
327 | + return True |
328 | + return False |
329 | + |
330 | + |
331 | def confirm_repair_broken_cache(parent, datadir): |
332 | glade_dialog = SimpleGtkbuilderDialog(datadir, domain="software-center") |
333 | dialog = glade_dialog.dialog_broken_cache |
334 | @@ -119,7 +133,11 @@ |
335 | |
336 | |
337 | if __name__ == "__main__": |
338 | - print("Running remove dialog") |
339 | + softwarecenter.paths.datadir = "./data" |
340 | + |
341 | + print("Showing tos dialog") |
342 | + res = show_accept_tos_dialog(None) |
343 | + print "accepted: ", res |
344 | |
345 | print("Running broken apt-cache dialog") |
346 | confirm_repair_broken_cache(None, "./data") |
347 | |
348 | === added file 'softwarecenter/ui/gtk3/dialogs/dialog_tos.py' |
349 | --- softwarecenter/ui/gtk3/dialogs/dialog_tos.py 1970-01-01 00:00:00 +0000 |
350 | +++ softwarecenter/ui/gtk3/dialogs/dialog_tos.py 2012-03-19 14:40:27 +0000 |
351 | @@ -0,0 +1,84 @@ |
352 | +# Copyright (C) 2009 Canonical |
353 | +# |
354 | +# Authors: |
355 | +# Michael Vogt |
356 | +# Andrew Higginson (rugby471) |
357 | +# |
358 | +# This program is free software; you can redistribute it and/or modify it under |
359 | +# the terms of the GNU General Public License as published by the Free Software |
360 | +# Foundation; version 3. |
361 | +# |
362 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
363 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
364 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
365 | +# details. |
366 | +# |
367 | +# You should have received a copy of the GNU General Public License along with |
368 | +# this program; if not, write to the Free Software Foundation, Inc., |
369 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
370 | + |
371 | +import gi |
372 | +gi.require_version("Gtk", "3.0") |
373 | +from gi.repository import Gtk |
374 | +from gi.repository import WebKit |
375 | + |
376 | +from gettext import gettext as _ |
377 | + |
378 | +from softwarecenter.ui.gtk3.views.webkit import ScrolledWebkitWindow |
379 | +from softwarecenter.ui.gtk3.widgets.spinner import SpinnerNotebook |
380 | + |
381 | + |
382 | +class DialogTos(Gtk.Dialog): |
383 | + |
384 | + def __init__(self, parent): |
385 | + Gtk.Dialog.__init__(self) |
386 | + self.set_default_size(420, 400) |
387 | + self.set_transient_for(parent) |
388 | + self.set_title(_("Terms of Use")) |
389 | + # buttons |
390 | + self.add_button(_("Decline"), Gtk.ResponseType.NO) |
391 | + self.add_button(_("Accept"), Gtk.ResponseType.YES) |
392 | + # label |
393 | + self.label = Gtk.Label(_(u"One moment, please\u2026")) |
394 | + self.label.show() |
395 | + # add the label |
396 | + box = self.get_action_area() |
397 | + box.pack_start(self.label, False, False, 0) |
398 | + box.set_child_secondary(self.label, True) |
399 | + # hrm, hrm, there really should be a better way |
400 | + for itm in box.get_children(): |
401 | + if itm.get_label() == _("Accept"): |
402 | + self.button_accept = itm |
403 | + break |
404 | + self.button_accept.set_sensitive(False) |
405 | + # webkit |
406 | + wb = ScrolledWebkitWindow() |
407 | + wb.show_all() |
408 | + self.webkit = wb.webkit |
409 | + self.webkit.connect( |
410 | + "notify::load-status", self._on_load_status_changed) |
411 | + # content |
412 | + content = self.get_content_area() |
413 | + self.spinner = SpinnerNotebook(wb) |
414 | + self.spinner.show_all() |
415 | + content.pack_start(self.spinner, True, True, 0) |
416 | + |
417 | + def run(self): |
418 | + self.spinner.show_spinner() |
419 | + self.webkit.load_uri("http://apps.ubuntu.com/cat/tos") |
420 | + return Gtk.Dialog.run(self) |
421 | + |
422 | + def _on_load_status_changed(self, view, pspec): |
423 | + prop = pspec.name |
424 | + status = view.get_property(prop) |
425 | + if (status == WebKit.LoadStatus.FINISHED or |
426 | + status == WebKit.LoadStatus.FAILED): |
427 | + self.spinner.hide_spinner() |
428 | + if status == WebKit.LoadStatus.FINISHED: |
429 | + self.label.set_text(_("Do you accept these terms?")) |
430 | + self.button_accept.set_sensitive(True) |
431 | + |
432 | +if __name__ == "__main__": |
433 | + d = DialogTos(None) |
434 | + res = d.run() |
435 | + print res |
436 | |
437 | === modified file 'softwarecenter/ui/gtk3/panes/availablepane.py' |
438 | --- softwarecenter/ui/gtk3/panes/availablepane.py 2012-03-16 10:36:55 +0000 |
439 | +++ softwarecenter/ui/gtk3/panes/availablepane.py 2012-03-19 14:40:27 +0000 |
440 | @@ -236,11 +236,11 @@ |
441 | window.set_cursor(None) |
442 | |
443 | def on_purchase_requested(self, appmanager, app, iconname, url): |
444 | - self.purchase_view.initiate_purchase(app, iconname, url) |
445 | - vm = get_viewmanager() |
446 | - vm.display_page( |
447 | - self, AvailablePane.Pages.PURCHASE, self.state, |
448 | - self.display_purchase) |
449 | + if self.purchase_view.initiate_purchase(app, iconname, url): |
450 | + vm = get_viewmanager() |
451 | + vm.display_page( |
452 | + self, AvailablePane.Pages.PURCHASE, self.state, |
453 | + self.display_purchase) |
454 | |
455 | def on_purchase_needs_spinner(self, appmanager, active): |
456 | vm = get_viewmanager() |
457 | |
458 | === modified file 'softwarecenter/ui/gtk3/review_gui_helper.py' |
459 | --- softwarecenter/ui/gtk3/review_gui_helper.py 2012-03-15 03:16:35 +0000 |
460 | +++ softwarecenter/ui/gtk3/review_gui_helper.py 2012-03-19 14:40:27 +0000 |
461 | @@ -48,7 +48,7 @@ |
462 | import piston_mini_client |
463 | |
464 | from softwarecenter.paths import SOFTWARE_CENTER_CONFIG_DIR |
465 | -from softwarecenter.enums import Icons |
466 | +from softwarecenter.enums import Icons, SOFTWARE_CENTER_NAME_KEYRING |
467 | from softwarecenter.config import get_config |
468 | from softwarecenter.distro import get_distro, get_current_arch |
469 | from softwarecenter.backend.login_sso import get_sso_backend |
470 | @@ -379,9 +379,6 @@ |
471 | SimpleGtkbuilderApp.__init__( |
472 | self, os.path.join(datadir, "ui/gtk3", uifile), "software-center") |
473 | # generic data |
474 | - # see bug #773214 for the rational |
475 | - #self.appname = _("Ubuntu Software Center") |
476 | - self.appname = "Ubuntu Software Center" |
477 | self.token = None |
478 | self.display_name = None |
479 | self._login_successful = False |
480 | @@ -453,7 +450,7 @@ |
481 | help_text = _("To review software or to report abuse you need to " |
482 | "sign in to a Ubuntu Single Sign-On account.") |
483 | self.sso = get_sso_backend(login_window_xid, |
484 | - self.appname, help_text) |
485 | + SOFTWARE_CENTER_NAME_KEYRING, help_text) |
486 | self.sso.connect("login-successful", self._maybe_login_successful) |
487 | self.sso.connect("login-canceled", self._login_canceled) |
488 | if show_register: |
489 | |
490 | === modified file 'softwarecenter/ui/gtk3/utils.py' |
491 | --- softwarecenter/ui/gtk3/utils.py 2012-03-15 09:32:18 +0000 |
492 | +++ softwarecenter/ui/gtk3/utils.py 2012-03-19 14:40:27 +0000 |
493 | @@ -28,6 +28,20 @@ |
494 | LOG = logging.getLogger(__name__) |
495 | |
496 | |
497 | +def get_parent(widget): |
498 | + while widget.get_parent(): |
499 | + widget = widget.get_parent() |
500 | + return widget |
501 | + |
502 | + |
503 | +def get_parent_xid(widget): |
504 | + window = get_parent(widget).get_window() |
505 | + #print dir(window) |
506 | + if hasattr(window, 'xid'): |
507 | + return window.xid |
508 | + return 0 # cannot figure out how to get the xid of gdkwindow under pygi |
509 | + |
510 | + |
511 | def point_in(rect, px, py): |
512 | return (rect.x <= px <= rect.x + rect.width and |
513 | rect.y <= py <= rect.y + rect.height) |
514 | |
515 | === modified file 'softwarecenter/ui/gtk3/views/purchaseview.py' |
516 | --- softwarecenter/ui/gtk3/views/purchaseview.py 2012-03-14 16:34:56 +0000 |
517 | +++ softwarecenter/ui/gtk3/views/purchaseview.py 2012-03-19 14:40:27 +0000 |
518 | @@ -20,112 +20,27 @@ |
519 | from gi.repository import GObject |
520 | from gi.repository import Gtk |
521 | from gi.repository import Gdk |
522 | -from gi.repository import Pango |
523 | + |
524 | +import ConfigParser |
525 | import logging |
526 | import os |
527 | import json |
528 | import sys |
529 | import urllib |
530 | -import urlparse |
531 | + |
532 | from gi.repository import WebKit as webkit |
533 | |
534 | from gettext import gettext as _ |
535 | |
536 | from softwarecenter.backend import get_install_backend |
537 | -from softwarecenter.i18n import get_language |
538 | +from softwarecenter.ui.gtk3.dialogs import show_accept_tos_dialog |
539 | +from softwarecenter.config import get_config |
540 | +from softwarecenter.ui.gtk3.utils import get_parent |
541 | +from softwarecenter.ui.gtk3.views.webkit import ScrolledWebkitWindow |
542 | |
543 | LOG = logging.getLogger(__name__) |
544 | |
545 | |
546 | -class LocaleAwareWebView(webkit.WebView): |
547 | - |
548 | - def __init__(self): |
549 | - # actual webkit init |
550 | - webkit.WebView.__init__(self) |
551 | - self.connect("resource-request-starting", |
552 | - self._on_resource_request_starting) |
553 | - |
554 | - def _on_resource_request_starting(self, view, frame, res, req, resp): |
555 | - lang = get_language() |
556 | - if lang: |
557 | - message = req.get_message() |
558 | - if message: |
559 | - headers = message.get_property("request-headers") |
560 | - headers.append("Accept-Language", lang) |
561 | - #def _show_header(name, value, data): |
562 | - # print name, value |
563 | - #headers.foreach(_show_header, None) |
564 | - |
565 | - |
566 | -class ScrolledWebkitWindow(Gtk.VBox): |
567 | - |
568 | - def __init__(self, include_progress_ui=False): |
569 | - super(ScrolledWebkitWindow, self).__init__() |
570 | - # get webkit |
571 | - self.webkit = LocaleAwareWebView() |
572 | - settings = self.webkit.get_settings() |
573 | - settings.set_property("enable-plugins", False) |
574 | - # add progress UI if needed |
575 | - if include_progress_ui: |
576 | - self._add_progress_ui() |
577 | - # create main webkitview |
578 | - self.scroll = Gtk.ScrolledWindow() |
579 | - self.scroll.set_policy(Gtk.PolicyType.AUTOMATIC, |
580 | - Gtk.PolicyType.AUTOMATIC) |
581 | - self.pack_start(self.scroll, True, True, 0) |
582 | - # embed the webkit view in a scrolled window |
583 | - self.scroll.add(self.webkit) |
584 | - self.show_all() |
585 | - |
586 | - def _add_progress_ui(self): |
587 | - # create toolbar box |
588 | - self.header = Gtk.HBox() |
589 | - # add spinner |
590 | - self.spinner = Gtk.Spinner() |
591 | - self.header.pack_start(self.spinner, False, False, 6) |
592 | - # add a url to the toolbar |
593 | - self.url = Gtk.Label() |
594 | - self.url.set_ellipsize(Pango.EllipsizeMode.END) |
595 | - self.url.set_alignment(0.0, 0.5) |
596 | - self.url.set_text("") |
597 | - self.header.pack_start(self.url, True, True, 0) |
598 | - # frame around the box |
599 | - self.frame = Gtk.Frame() |
600 | - self.frame.set_border_width(3) |
601 | - self.frame.add(self.header) |
602 | - self.pack_start(self.frame, False, False, 6) |
603 | - # connect the webkit stuff |
604 | - self.webkit.connect("notify::uri", self._on_uri_changed) |
605 | - self.webkit.connect("notify::load-status", |
606 | - self._on_load_status_changed) |
607 | - |
608 | - def _on_uri_changed(self, view, pspec): |
609 | - prop = pspec.name |
610 | - uri = view.get_property(prop) |
611 | - # the full uri is irellevant for the purchase view, but it is |
612 | - # interessting to know what protocol/netloc is in use so that the |
613 | - # user can verify its https on sites he is expecting |
614 | - scheme, netloc, path, params, query, frag = urlparse.urlparse(uri) |
615 | - if scheme == "file" and netloc == "": |
616 | - self.url.set_text("") |
617 | - else: |
618 | - self.url.set_text("%s://%s" % (scheme, netloc)) |
619 | - # start spinner when the uri changes |
620 | - #self.spinner.start() |
621 | - |
622 | - def _on_load_status_changed(self, view, pspec): |
623 | - prop = pspec.name |
624 | - status = view.get_property(prop) |
625 | - #print status |
626 | - if status == webkit.LoadStatus.PROVISIONAL: |
627 | - self.spinner.start() |
628 | - self.spinner.show() |
629 | - if (status == webkit.LoadStatus.FINISHED or |
630 | - status == webkit.LoadStatus.FAILED): |
631 | - self.spinner.stop() |
632 | - self.spinner.hide() |
633 | - |
634 | - |
635 | class PurchaseView(Gtk.VBox): |
636 | """ |
637 | View that displays the webkit-based UI for purchasing an item. |
638 | @@ -189,6 +104,7 @@ |
639 | self.wk = None |
640 | self._wk_handlers_blocked = False |
641 | self._oauth_token = None |
642 | + self.config = get_config() |
643 | |
644 | def init_view(self): |
645 | if self.wk is None: |
646 | @@ -209,11 +125,28 @@ |
647 | # completed or canceled |
648 | self._unblock_wk_handlers() |
649 | |
650 | + def _ask_for_tos_acceptance_if_needed(self): |
651 | + try: |
652 | + accepted_tos = self.config.getboolean("general", "accepted_tos") |
653 | + except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): |
654 | + accepted_tos = False |
655 | + if not accepted_tos: |
656 | + # show the dialog and ensure the user accepts it |
657 | + res = show_accept_tos_dialog(get_parent(self)) |
658 | + if not res: |
659 | + return False |
660 | + self.config.set("general", "accepted_tos", "yes") |
661 | + return True |
662 | + return True |
663 | + |
664 | def initiate_purchase(self, app, iconname, url=None, html=None): |
665 | """ |
666 | initiates the purchase workflow inside the embedded webkit window |
667 | for the item specified |
668 | """ |
669 | + if not self._ask_for_tos_acceptance_if_needed(): |
670 | + return False |
671 | + |
672 | self.init_view() |
673 | self.app = app |
674 | self.iconname = iconname |
675 | @@ -231,6 +164,7 @@ |
676 | # only for debugging |
677 | if os.environ.get("SOFTWARE_CENTER_DEBUG_BUY"): |
678 | GObject.timeout_add_seconds(1, _generate_events, self) |
679 | + return True |
680 | |
681 | def _on_new_window(self, view, frame, request, action, policy): |
682 | LOG.debug("_on_new_window") |
683 | @@ -477,8 +411,8 @@ |
684 | #GObject.timeout_add_seconds(1, _generate_events, d) |
685 | |
686 | widget = PurchaseView() |
687 | - widget.initiate_purchase(app=None, iconname=None, url=url) |
688 | - #widget.initiate_purchase(app=None, iconname=None, html=DUMMY_HTML) |
689 | + from mock import Mock |
690 | + widget.config = Mock() |
691 | |
692 | win = Gtk.Window() |
693 | win.set_data("view", widget) |
694 | @@ -487,8 +421,14 @@ |
695 | win.set_position(Gtk.WindowPosition.CENTER) |
696 | win.show_all() |
697 | win.connect('destroy', Gtk.main_quit) |
698 | + |
699 | + widget.initiate_purchase(app=None, iconname=None, url=url) |
700 | + #widget.initiate_purchase(app=None, iconname=None, html=DUMMY_HTML) |
701 | + |
702 | return win |
703 | |
704 | if __name__ == "__main__": |
705 | + import softwarecenter.paths |
706 | + softwarecenter.paths.datadir = "./data" |
707 | win = get_test_window_purchaseview() |
708 | Gtk.main() |
709 | |
710 | === added file 'softwarecenter/ui/gtk3/views/webkit.py' |
711 | --- softwarecenter/ui/gtk3/views/webkit.py 1970-01-01 00:00:00 +0000 |
712 | +++ softwarecenter/ui/gtk3/views/webkit.py 2012-03-19 14:40:27 +0000 |
713 | @@ -0,0 +1,114 @@ |
714 | +# Copyright (C) 2010 Canonical |
715 | +# |
716 | +# Authors: |
717 | +# Michael Vogt |
718 | +# Gary Lasker |
719 | +# |
720 | +# This program is free software; you can redistribute it and/or modify it under |
721 | +# the terms of the GNU General Public License as published by the Free Software |
722 | +# Foundation; version 3. |
723 | +# |
724 | +# This program is distributed in the hope that it will be useful, but WITHOUT |
725 | +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
726 | +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
727 | +# details. |
728 | +# |
729 | +# You should have received a copy of the GNU General Public License along with |
730 | +# this program; if not, write to the Free Software Foundation, Inc., |
731 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
732 | + |
733 | +from gi.repository import WebKit as webkit |
734 | +from gi.repository import Gtk |
735 | +from gi.repository import Pango |
736 | +import urlparse |
737 | + |
738 | +from softwarecenter.i18n import get_language |
739 | + |
740 | + |
741 | +class LocaleAwareWebView(webkit.WebView): |
742 | + |
743 | + def __init__(self): |
744 | + # actual webkit init |
745 | + webkit.WebView.__init__(self) |
746 | + self.connect("resource-request-starting", |
747 | + self._on_resource_request_starting) |
748 | + |
749 | + def _on_resource_request_starting(self, view, frame, res, req, resp): |
750 | + lang = get_language() |
751 | + if lang: |
752 | + message = req.get_message() |
753 | + if message: |
754 | + headers = message.get_property("request-headers") |
755 | + headers.append("Accept-Language", lang) |
756 | + #def _show_header(name, value, data): |
757 | + # print name, value |
758 | + #headers.foreach(_show_header, None) |
759 | + |
760 | + |
761 | +class ScrolledWebkitWindow(Gtk.VBox): |
762 | + |
763 | + def __init__(self, include_progress_ui=False): |
764 | + super(ScrolledWebkitWindow, self).__init__() |
765 | + # get webkit |
766 | + self.webkit = LocaleAwareWebView() |
767 | + settings = self.webkit.get_settings() |
768 | + settings.set_property("enable-plugins", False) |
769 | + # add progress UI if needed |
770 | + if include_progress_ui: |
771 | + self._add_progress_ui() |
772 | + # create main webkitview |
773 | + self.scroll = Gtk.ScrolledWindow() |
774 | + self.scroll.set_policy(Gtk.PolicyType.AUTOMATIC, |
775 | + Gtk.PolicyType.AUTOMATIC) |
776 | + self.pack_start(self.scroll, True, True, 0) |
777 | + # embed the webkit view in a scrolled window |
778 | + self.scroll.add(self.webkit) |
779 | + self.show_all() |
780 | + |
781 | + def _add_progress_ui(self): |
782 | + # create toolbar box |
783 | + self.header = Gtk.HBox() |
784 | + # add spinner |
785 | + self.spinner = Gtk.Spinner() |
786 | + self.header.pack_start(self.spinner, False, False, 6) |
787 | + # add a url to the toolbar |
788 | + self.url = Gtk.Label() |
789 | + self.url.set_ellipsize(Pango.EllipsizeMode.END) |
790 | + self.url.set_alignment(0.0, 0.5) |
791 | + self.url.set_text("") |
792 | + self.header.pack_start(self.url, True, True, 0) |
793 | + # frame around the box |
794 | + self.frame = Gtk.Frame() |
795 | + self.frame.set_border_width(3) |
796 | + self.frame.add(self.header) |
797 | + self.pack_start(self.frame, False, False, 6) |
798 | + # connect the webkit stuff |
799 | + self.webkit.connect("notify::uri", self._on_uri_changed) |
800 | + self.webkit.connect("notify::load-status", |
801 | + self._on_load_status_changed) |
802 | + |
803 | + def _on_uri_changed(self, view, pspec): |
804 | + prop = pspec.name |
805 | + uri = view.get_property(prop) |
806 | + # the full uri is irellevant for the purchase view, but it is |
807 | + # interessting to know what protocol/netloc is in use so that the |
808 | + # user can verify its https on sites he is expecting |
809 | + scheme, netloc, path, params, query, frag = urlparse.urlparse(uri) |
810 | + if scheme == "file" and netloc == "": |
811 | + self.url.set_text("") |
812 | + else: |
813 | + self.url.set_text("%s://%s" % (scheme, netloc)) |
814 | + # start spinner when the uri changes |
815 | + #self.spinner.start() |
816 | + |
817 | + def _on_load_status_changed(self, view, pspec): |
818 | + prop = pspec.name |
819 | + status = view.get_property(prop) |
820 | + #print status |
821 | + if status == webkit.LoadStatus.PROVISIONAL: |
822 | + self.spinner.start() |
823 | + self.spinner.show() |
824 | + if (status == webkit.LoadStatus.FINISHED or |
825 | + status == webkit.LoadStatus.FAILED): |
826 | + self.spinner.stop() |
827 | + self.spinner.hide() |
828 | |
829 | === modified file 'softwarecenter/utils.py' |
830 | --- softwarecenter/utils.py 2012-03-16 17:22:59 +0000 |
831 | +++ softwarecenter/utils.py 2012-03-19 14:40:27 +0000 |
832 | @@ -218,16 +218,6 @@ |
833 | return html |
834 | |
835 | |
836 | -def get_parent_xid(widget): |
837 | - while widget.get_parent(): |
838 | - widget = widget.get_parent() |
839 | - window = widget.get_window() |
840 | - #print dir(window) |
841 | - if hasattr(window, 'xid'): |
842 | - return window.xid |
843 | - return 0 # cannot figure out how to get the xid of gdkwindow under pygi |
844 | - |
845 | - |
846 | def get_http_proxy_string_from_libproxy(url): |
847 | """Helper that uses libproxy to get the http proxy for the given url """ |
848 | import libproxy |
849 | |
850 | === modified file 'test/gtk3/test_dialogs.py' |
851 | --- test/gtk3/test_dialogs.py 2012-01-16 14:42:49 +0000 |
852 | +++ test/gtk3/test_dialogs.py 2012-03-19 14:40:27 +0000 |
853 | @@ -33,6 +33,12 @@ |
854 | res = softwarecenter.ui.gtk3.dialogs.error( |
855 | parent=None, primary="primary", secondary="secondary") |
856 | self.assertEqual(res, False) |
857 | + |
858 | + def test_accept_tos_dialog(self): |
859 | + GObject.timeout_add(TIMEOUT, self._close_dialog) |
860 | + res = softwarecenter.ui.gtk3.dialogs.show_accept_tos_dialog( |
861 | + parent=None) |
862 | + self.assertEqual(res, False) |
863 | |
864 | # helper |
865 | def _close_dialog(self): |
866 | |
867 | === modified file 'test/gtk3/test_purchase.py' |
868 | --- test/gtk3/test_purchase.py 2012-01-16 14:42:49 +0000 |
869 | +++ test/gtk3/test_purchase.py 2012-03-19 14:40:27 +0000 |
870 | @@ -3,11 +3,12 @@ |
871 | import time |
872 | import unittest |
873 | |
874 | -from mock import Mock |
875 | +from mock import Mock,patch |
876 | |
877 | -from testutils import setup_test_env, do_events |
878 | +from testutils import setup_test_env |
879 | setup_test_env() |
880 | |
881 | +from softwarecenter.testutils import do_events |
882 | from softwarecenter.ui.gtk3.app import SoftwareCenterAppGtk3 |
883 | from softwarecenter.ui.gtk3.panes.availablepane import AvailablePane |
884 | import softwarecenter.paths |
885 | @@ -38,6 +39,23 @@ |
886 | # run another one |
887 | win.destroy() |
888 | |
889 | + def test_purchase_view_tos(self): |
890 | + from softwarecenter.ui.gtk3.views.purchaseview import get_test_window_purchaseview |
891 | + win = get_test_window_purchaseview() |
892 | + view = win.get_data("view") |
893 | + # install the mock |
894 | + mock_config = Mock() |
895 | + mock_config.has_option.return_value = False |
896 | + mock_config.getboolean.return_value = False |
897 | + view.config = mock_config |
898 | + func = "softwarecenter.ui.gtk3.views.purchaseview.show_accept_tos_dialog" |
899 | + with patch(func) as mock_func: |
900 | + mock_func.return_value = False |
901 | + res = view.initiate_purchase(None, None) |
902 | + self.assertFalse(res) |
903 | + self.assertTrue(mock_func.called) |
904 | + win.destroy() |
905 | + |
906 | def test_spinner_emits_signals(self): |
907 | from softwarecenter.ui.gtk3.views.purchaseview import get_test_window_purchaseview |
908 | win = get_test_window_purchaseview() |
This branch is ready for review now that the spec got updated. It implements the Terms of Use
functionality as speced.