Merge lp:~mvo/software-center/tos-dialog into lp:software-center

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

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 :

This branch is ready for review now that the spec got updated. It implements the Terms of Use
functionality as speced.

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://apps.ubuntu.com/cat/tos isn't actually deployed yet, but that will come very soon and need not hold up this merge, which I will do now.

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

Subscribers

People subscribed via source and target branches