Merge lp:~nataliabidart/software-center/cant-stop-the-music into lp:software-center/5.2

Proposed by Natalia Bidart
Status: Merged
Merged at revision: 3050
Proposed branch: lp:~nataliabidart/software-center/cant-stop-the-music
Merge into: lp:software-center/5.2
Diff against target: 491 lines (+165/-59)
8 files modified
softwarecenter/ui/gtk3/session/navhistory.py (+2/-3)
softwarecenter/ui/gtk3/session/viewmanager.py (+5/-0)
softwarecenter/ui/gtk3/views/appdetailsview.py (+9/-8)
softwarecenter/ui/gtk3/widgets/videoplayer.py (+3/-0)
test/gtk3/test_appdetailsview.py (+58/-45)
test/gtk3/test_appmanager.py (+2/-1)
test/gtk3/test_viewmanager.py (+59/-0)
test/gtk3/test_widgets.py (+27/-2)
To merge this branch: bzr merge lp:~nataliabidart/software-center/cant-stop-the-music
Reviewer Review Type Date Requested Status
Michael Vogt Approve
Review via email: mp+110607@code.launchpad.net

Commit message

- Stop the video if user navigates away from an app details page
  (LP: #1003954).

To post a comment you must log in.
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks Natalia, this is great work!

There is one small issue:
- navigate to "plutoids"
- click on "back"
- click on "forward"
- the video frame is now blank because on navigation to the page the url is not updated
  (as the view is "clever" and remembers that it does not need to reload the page)

It would be great if you could have a look at this.

Another thought for trunk would be to decouple the "stop" from the ViewManager.display_page and add
signals or functions like "enter-page", "leave-page" so that the page itself can do the magic that needs
to be done (like stop video or store the vadjustment in the applist). What do you think about
this?

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

(oh, and I love your branch names)

Revision history for this message
Natalia Bidart (nataliabidart) wrote :

> Thanks Natalia, this is great work!
>
> There is one small issue:
> - navigate to "plutoids"
> - click on "back"
> - click on "forward"
> - the video frame is now blank because on navigation to the page the url is
> not updated
> (as the view is "clever" and remembers that it does not need to reload the
> page)
>
> It would be great if you could have a look at this.

Nice catch! Will be fixing this ASAP.

> Another thought for trunk would be to decouple the "stop" from the
> ViewManager.display_page and add
> signals or functions like "enter-page", "leave-page" so that the page itself
> can do the magic that needs
> to be done (like stop video or store the vadjustment in the applist). What do
> you think about
> this?

I would love that considering that the current proposed solution couples the different modules too much.

You want me to propose a branch against trunk? is it ok if trunk and 5.2 start diverging?

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

On Mon, Jun 18, 2012 at 11:51:34AM -0000, Natalia Bidart wrote:
> > Thanks Natalia, this is great work!
> >
> > There is one small issue:
> > - navigate to "plutoids"
> > - click on "back"
> > - click on "forward"
> > - the video frame is now blank because on navigation to the page the url is
> > not updated
> > (as the view is "clever" and remembers that it does not need to reload the
> > page)
> >
> > It would be great if you could have a look at this.
>
> Nice catch! Will be fixing this ASAP.
Thanks!

> > Another thought for trunk would be to decouple the "stop" from the
> > ViewManager.display_page and add
> > signals or functions like "enter-page", "leave-page" so that the page itself
> > can do the magic that needs
> > to be done (like stop video or store the vadjustment in the applist). What do
> > you think about
> > this?
>
> I would love that considering that the current proposed solution couples the different modules too much.
>
> You want me to propose a branch against trunk? is it ok if trunk and 5.2 start diverging?

I think for 5.2 this branch is perfect as it makes the SRU upload
easier (small amount of code changes etc). For the improved fix we
should target trunk and I think its ok if the two branches diverge,
its a unavoidable in the longer term anyway :)

Cheers,
 Michael

3053. By Natalia Bidart

Ensure that video gets updated when navigating back to an already visited app.

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

Thanks for the fix, this looks fine now. Plus I appreciate the cleanup in the test, e.g. the new
 "set_mock_app_and_details()"

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'softwarecenter/ui/gtk3/session/navhistory.py'
2--- softwarecenter/ui/gtk3/session/navhistory.py 2012-03-15 00:16:03 +0000
3+++ softwarecenter/ui/gtk3/session/navhistory.py 2012-06-18 14:42:18 +0000
4@@ -124,9 +124,8 @@
5
6 def __str__(self):
7 facet = self.pane.pane_name.replace(' ', '')[:6]
8- return utf8("%s:%s %s") % (facet,
9- self.page,
10- self.view_state)
11+ result = utf8("%s:%s %s") % (facet, self.page, self.view_state)
12+ return result
13
14 def navigate_to(self):
15 """
16
17=== modified file 'softwarecenter/ui/gtk3/session/viewmanager.py'
18--- softwarecenter/ui/gtk3/session/viewmanager.py 2012-04-16 21:51:02 +0000
19+++ softwarecenter/ui/gtk3/session/viewmanager.py 2012-06-18 14:42:18 +0000
20@@ -177,6 +177,11 @@
21 if ni.pane.is_applist_view_showing():
22 v = ni.pane.app_view.tree_view_scroll.get_vadjustment()
23 ni.view_state.vadjustment = v.get_value()
24+ # if there is a video playing, stop it
25+ try:
26+ ni.pane.app_details_view.videoplayer.stop()
27+ except AttributeError:
28+ pass
29
30 if callback is None:
31 callback = pane.get_callback_for_page(page, view_state)
32
33=== modified file 'softwarecenter/ui/gtk3/views/appdetailsview.py'
34--- softwarecenter/ui/gtk3/views/appdetailsview.py 2012-05-22 07:29:34 +0000
35+++ softwarecenter/ui/gtk3/views/appdetailsview.py 2012-06-18 14:42:18 +0000
36@@ -1565,6 +1565,7 @@
37 self._update_pkg_info_table(app_details)
38 self._update_warning_bar(app_details)
39 # self._update_addons_minimal(app_details)
40+ self._update_app_video(app_details)
41
42 # depending on pkg install state set action labels
43 self.pkg_statusbar.configure(app_details, app_details.pkg_state)
44@@ -2042,7 +2043,7 @@
45 self.section = section
46
47
48-def get_test_window_appdetails():
49+def get_test_window_appdetails(pkgname="totem"):
50
51 from softwarecenter.db.pkginfo import get_pkg_info
52 cache = get_pkg_info()
53@@ -2068,12 +2069,6 @@
54 scroll = Gtk.ScrolledWindow()
55 view = AppDetailsView(db, distro, icons, cache, datadir)
56
57- import sys
58- if len(sys.argv) > 1:
59- pkgname = sys.argv[1]
60- else:
61- pkgname = "totem"
62-
63 view.show_app(Application("", pkgname))
64 #view.show_app(Application("Pay App Example", "pay-app"))
65 #view.show_app(Application("3D Chess", "3dchess"))
66@@ -2105,7 +2100,13 @@
67 view.show_app(Application("Movie Player", "totem"))
68 return True
69
70- win = get_test_window_appdetails()
71+ import sys
72+ if len(sys.argv) > 1:
73+ pkgname = sys.argv[1]
74+ else:
75+ pkgname = "totem"
76+
77+ win = get_test_window_appdetails(pkgname=pkgname)
78
79 # keep it spinning to test for re-draw issues and memleaks
80 #view = win.get_data("view")
81
82=== modified file 'softwarecenter/ui/gtk3/widgets/videoplayer.py'
83--- softwarecenter/ui/gtk3/widgets/videoplayer.py 2012-04-18 21:04:11 +0000
84+++ softwarecenter/ui/gtk3/widgets/videoplayer.py 2012-06-18 14:42:18 +0000
85@@ -91,6 +91,9 @@
86 base_uri = "http://www.ubuntu.com"
87 self.webkit.load_html_string(html, base_uri)
88
89+ def stop(self):
90+ self.webkit.load_uri('')
91+
92
93 # AKA the-segfault-edition-with-no-documentation
94 class VideoPlayerGtk3(Gtk.VBox):
95
96=== modified file 'test/gtk3/test_appdetailsview.py'
97--- test/gtk3/test_appdetailsview.py 2012-04-17 14:27:43 +0000
98+++ test/gtk3/test_appdetailsview.py 2012-06-18 14:42:18 +0000
99@@ -20,10 +20,11 @@
100 from test.test_database import make_purchased_app_details
101
102
103-
104 # window destory timeout
105 TIMEOUT=100
106
107+
108+
109 class TestAppdetailsView(unittest.TestCase):
110
111 def setUp(self):
112@@ -34,6 +35,16 @@
113 GObject.timeout_add(TIMEOUT, lambda: self.win.destroy())
114 Gtk.main()
115
116+ def set_mock_app_and_details(self, app_name="software-center", **kwargs):
117+ app = Application("", app_name)
118+ mock_app = get_mock_app_from_real_app(app)
119+ mock_details = mock_app.get_details(None)
120+ for attr, value in kwargs.iteritems():
121+ setattr(mock_details, attr, value)
122+
123+ self.view.app = mock_app
124+ self.view.app_details = mock_details
125+
126 def test_videoplayer(self):
127 # show app with no video
128 app = Application("", "2vcard")
129@@ -42,34 +53,30 @@
130 self.assertFalse(self.view.videoplayer.get_property("visible"))
131
132 # create app with video and ensure its visible
133- app = Application("", "synaptic")
134- mock = get_mock_app_from_real_app(app)
135- details = mock.get_details(None)
136- # this is a example html - any html5 video will do
137- details.video_url = "http://people.canonical.com/~mvo/totem.html"
138- self.view.show_app(mock)
139+ self.set_mock_app_and_details(
140+ app_name="synaptic",
141+ # this is a example html - any html5 video will do
142+ video_url="http://people.canonical.com/~mvo/totem.html")
143+ self.view.show_app(self.view.app)
144 do_events()
145 self.assertTrue(self.view.videoplayer.get_property("visible"))
146-
147+
148 def test_page_pkgstates(self):
149- # show app
150+ # show app
151 app = Application("", "abiword")
152 self.view.show_app(app)
153 do_events()
154-
155+
156 # check that the action bar is given initial focus in the view
157 self.assertTrue(self.view.pkg_statusbar.button.is_focus())
158
159 # create mock app
160- mock_app = get_mock_app_from_real_app(app)
161- self.view.app = mock_app
162- mock_details = mock_app.get_details(None)
163- mock_details.purchase_date = "2011-11-20 17:45:01"
164- mock_details._error_not_found = "error not found"
165- mock_details.price = "1.00"
166- mock_details.pkgname = "abiword"
167- mock_details.error = "error-text"
168- self.view.app_details = mock_details
169+ self.set_mock_app_and_details(
170+ app_name="abiword", purchase_date="2011-11-20 17:45:01",
171+ _error_not_found="error not found", price="1.00",
172+ pkgname="abiword", error="error-text")
173+ mock_app = self.view.app
174+ mock_details = self.view.app_details
175
176 # the states and what labels we expect in the pkgstatusbar
177 # first string is status text, second is button text
178@@ -101,7 +108,7 @@
179 if state in pkg_states_to_labels:
180 label, button_label = pkg_states_to_labels[state]
181 self.assertEqual(
182- self.view.pkg_statusbar.get_label(),
183+ self.view.pkg_statusbar.get_label(),
184 label.decode("utf-8"))
185 self.assertEqual(
186 self.view.pkg_statusbar.get_button_label().decode("utf-8"),
187@@ -122,15 +129,10 @@
188
189 def test_app_icon_loading(self):
190 # get icon
191- app = Application("", "software-center")
192- mock_app = get_mock_app_from_real_app(app)
193- self.view.app = mock_app
194- mock_details = mock_app.get_details(None)
195- mock_details.cached_icon_file_path = "download-icon-test"
196- mock_details.icon = "favicon.ico"
197- mock_details.icon_url = "http://de.wikipedia.org/favicon.ico"
198- self.view.app_details = mock_details
199- self.view.show_app(mock_app)
200+ self.set_mock_app_and_details(
201+ cached_icon_file_path="download-icon-test", icon="favicon.ico",
202+ icon_url="http://de.wikipedia.org/favicon.ico")
203+ self.view.show_app(self.view.app)
204 do_events()
205 # ensure the icon is there
206 # FIXME: ensure that the icon is really downloaded
207@@ -250,10 +252,21 @@
208 self.view._submit_reviews_done_callback(None, 0)
209
210 self.assertTrue(button.is_sensitive())
211-
212+
213+ def test_show_app_twice_plays_video(self):
214+ video_url = "http://people.canonical.com/~mvo/totem.html"
215+ self.set_mock_app_and_details(video_url=video_url)
216+
217+ self.view.show_app(self.view.app)
218+ self.assertEqual(self.view.videoplayer.uri, video_url)
219+
220+ self.view.videoplayer.uri = None
221+ self.view.show_app(self.view.app)
222+ self.assertEqual(self.view.videoplayer.uri, video_url)
223+
224
225 class MultipleVersionsTestCase(unittest.TestCase):
226-
227+
228 @classmethod
229 def setUpClass(cls):
230 # Set these as class attributes as we don't modify either
231@@ -278,7 +291,7 @@
232 self.view.show_app(self.app_mock)
233 self.assertFalse(self.view.pkg_statusbar.combo_multiple_versions.get_visible())
234 # switch to not-automatic app with different description
235- self.app_mock.details.get_not_automatic_archive_versions = lambda: [
236+ self.app_mock.details.get_not_automatic_archive_versions = lambda: [
237 ("5.0", "precise"),
238 ("12.0", "precise-backports"),
239 ]
240@@ -290,7 +303,7 @@
241 def test_combo_multiple_versions(self):
242 self.app_mock.details.get_not_automatic_archive_versions = lambda: [
243 ("5.0", "precise"),
244- ("12.0", "precise-backports")
245+ ("12.0", "precise-backports")
246 ]
247 # ensure that the right method is called
248 self.app_mock.details.force_not_automatic_archive_suite = Mock()
249@@ -305,7 +318,7 @@
250 def test_installed_multiple_version_default(self):
251 self.app_mock.details.get_not_automatic_archive_versions = lambda: [
252 ("5.0", "precise"),
253- ("12.0", "precise-backports")
254+ ("12.0", "precise-backports")
255 ]
256 self.app_mock.details.pkg_state = PkgStates.INSTALLED
257 self.app_mock.details.version = "12.0"
258@@ -332,10 +345,10 @@
259 #self.assertEqual(call_args, (("precise",), {}))
260 # ensure the button changes
261 self.assertEqual(self.view.pkg_statusbar.button.get_label(), "Change")
262-
263+
264
265 class HardwareRequirementsTestCase(unittest.TestCase):
266-
267+
268 @classmethod
269 def setUpClass(cls):
270 # Set these as class attributes as we don't modify either
271@@ -356,7 +369,7 @@
272 self.app_mock.details.pkg_state = PkgStates.UNINSTALLED
273
274 def test_show_hardware_requirements(self):
275- self.app_mock.details.hardware_requirements = {
276+ self.app_mock.details.hardware_requirements = {
277 'hardware::video:opengl' : 'yes',
278 'hardware::gps' : 'no',
279 }
280@@ -378,7 +391,7 @@
281 self.app_mock.details.pkg_state = PkgStates.NEEDS_PURCHASE
282 self.view.show_app(self.app_mock)
283 self.assertEqual(
284- self.view.pkg_statusbar.button.get_label(),
285+ self.view.pkg_statusbar.button.get_label(),
286 _(u"Buy Anyway\u2026").encode("utf-8"))
287 # check if the warning bar is displayed
288 self.assertTrue(self.view.pkg_warningbar.get_property("visible"))
289@@ -401,13 +414,13 @@
290 self.app_mock.details.pkg_state = PkgStates.NEEDS_PURCHASE
291 self.view.show_app(self.app_mock)
292 self.assertEqual(
293- self.view.pkg_statusbar.button.get_label(),
294+ self.view.pkg_statusbar.button.get_label(),
295 _(u'Buy\u2026').encode("utf-8"))
296 # check if the warning bar is invisible
297 self.assertFalse(self.view.pkg_warningbar.get_property("visible"))
298
299 class RegionRequirementsTestCase(unittest.TestCase):
300-
301+
302 @classmethod
303 def setUpClass(cls):
304 # Set these as class attributes as we don't modify either
305@@ -438,7 +451,7 @@
306 self.app_mock.details.pkg_state = PkgStates.NEEDS_PURCHASE
307 self.view.show_app(self.app_mock)
308 self.assertEqual(
309- self.view.pkg_statusbar.button.get_label(),
310+ self.view.pkg_statusbar.button.get_label(),
311 _(u"Buy Anyway\u2026").encode("utf-8"))
312 # check if the warning bar is displayed
313 self.assertTrue(self.view.pkg_warningbar.get_property("visible"))
314@@ -537,9 +550,9 @@
315 self.assertEqual(
316 [method_name],
317 all_method_calls)
318-
319+
320 class AppRecommendationsTestCase(unittest.TestCase):
321-
322+
323 @classmethod
324 def setUpClass(cls):
325 # Set these as class attributes as we don't modify either
326@@ -558,11 +571,11 @@
327 app = Application("", "pitivi")
328 self.app_mock = get_mock_app_from_real_app(app)
329 self.app_mock.details.pkg_state = PkgStates.UNINSTALLED
330-
331+
332 def on_query_done(self, recagent, data):
333 print "query done, data: '%s'" % data
334 self.loop.quit()
335-
336+
337 def on_query_error(self, recagent, error):
338 print "query error received: ", error
339 self.loop.quit()
340
341=== modified file 'test/gtk3/test_appmanager.py'
342--- test/gtk3/test_appmanager.py 2012-01-16 14:42:49 +0000
343+++ test/gtk3/test_appmanager.py 2012-06-18 14:42:18 +0000
344@@ -14,6 +14,7 @@
345 from softwarecenter.ui.gtk3.session.appmanager import (
346 ApplicationManager, get_appmanager)
347
348+
349 class TestAppManager(unittest.TestCase):
350 """ tests the appmanager """
351
352@@ -38,7 +39,7 @@
353 # test creating it twice raises a error
354 self.assertRaises(
355 ValueError, ApplicationManager, self.db, self.backend, self.icons)
356-
357+
358 def test_appmanager(self):
359 app_manager = get_appmanager()
360 self.assertNotEqual(app_manager, None)
361
362=== added file 'test/gtk3/test_viewmanager.py'
363--- test/gtk3/test_viewmanager.py 1970-01-01 00:00:00 +0000
364+++ test/gtk3/test_viewmanager.py 2012-06-18 14:42:18 +0000
365@@ -0,0 +1,59 @@
366+#!/usr/bin/python
367+
368+import unittest
369+
370+from gi.repository import Gtk
371+from mock import Mock
372+
373+from testutils import setup_test_env
374+setup_test_env()
375+
376+from softwarecenter.ui.gtk3.panes import availablepane, softwarepane
377+from softwarecenter.ui.gtk3.session.viewmanager import (
378+ ViewManager,
379+ get_viewmanager
380+)
381+
382+
383+class TestViewManager(unittest.TestCase):
384+ """Test suite for the viewmanager."""
385+
386+ def setUp(self):
387+ # create it once, it becomes global instance
388+ self.vm = get_viewmanager()
389+ if self.vm is None:
390+ self.vm = ViewManager(Gtk.Notebook())
391+
392+ self.addCleanup(self.vm.destroy)
393+
394+ def test_get_viewmanager(self):
395+ view_manager = get_viewmanager()
396+ self.assertNotEqual(view_manager, None)
397+ # is a singleton singleton
398+ view_manager2 = get_viewmanager()
399+ self.assertIs(view_manager, view_manager2)
400+ # test creating it twice raises a error
401+ self.assertRaises(ValueError, ViewManager, Gtk.Notebook())
402+
403+ def test_display_page_stops_video(self):
404+ called = []
405+ # navigate to an app details view with video
406+ pane = Mock()
407+ pane.pane_name = 'MockPane'
408+ pane.is_applist_view_showing = lambda: False
409+ pane.app_details_view.videoplayer.stop = lambda: called.append('stop')
410+ self.vm.display_page(
411+ pane, page=availablepane.AvailablePane.Pages.DETAILS,
412+ view_state=softwarepane.DisplayState())
413+
414+ other_pane = Mock()
415+ other_pane.pane_name = 'MockPane'
416+ self.vm.display_page(
417+ other_pane, page=availablepane.AvailablePane.Pages.DETAILS,
418+ view_state=softwarepane.DisplayState())
419+
420+ self.assertEqual(called, ['stop'])
421+
422+
423+if __name__ == "__main__":
424+ unittest.main()
425
426=== modified file 'test/gtk3/test_widgets.py'
427--- test/gtk3/test_widgets.py 2012-04-12 07:46:56 +0000
428+++ test/gtk3/test_widgets.py 2012-06-18 14:42:18 +0000
429@@ -10,6 +10,7 @@
430 from testutils import setup_test_env, do_events
431 setup_test_env()
432 from softwarecenter.utils import utf8
433+from softwarecenter.ui.gtk3.widgets import videoplayer
434 from softwarecenter.ui.gtk3.widgets.reviews import get_test_reviews_window
435 from softwarecenter.ui.gtk3.widgets.labels import (
436 HardwareRequirementsLabel, HardwareRequirementsBox)
437@@ -118,13 +119,13 @@
438 GObject.timeout_add(TIMEOUT, lambda: win.destroy())
439 Gtk.main()
440
441-
442 def test_apptreeview(self):
443 from softwarecenter.ui.gtk3.widgets.apptreeview import get_test_window
444 win = get_test_window()
445 GObject.timeout_add(TIMEOUT, lambda: win.destroy())
446 Gtk.main()
447
448+
449 class TestHWRequirements(unittest.TestCase):
450
451 HW_TEST_RESULT = { 'hardware::gps' : 'yes',
452@@ -177,7 +178,7 @@
453 # test seting it again
454 box.set_hardware_requirements(self.HW_TEST_RESULT)
455 self.assertEqual(len(box.get_children()), 2)
456-
457+
458
459 class TestUIReviewsList(unittest.TestCase):
460 def setUp(self):
461@@ -241,6 +242,30 @@
462 _('Be the first to contribute a review for this application'))
463
464
465+class VideoPlayerTestCase(unittest.TestCase):
466+
467+ def setUp(self):
468+ super(VideoPlayerTestCase, self).setUp()
469+ self.webkit_uri = None
470+ self.vp = videoplayer.VideoPlayer()
471+ self.vp.webkit.load_uri = lambda uri: setattr(self, 'webkit_uri', uri)
472+ self.vp.webkit.get_uri = lambda: self.webkit_uri
473+ self.addCleanup(self.vp.destroy)
474+
475+ def test_uri(self):
476+ self.assertEqual(self.vp.uri, '')
477+
478+ expected_uri = 'file://test'
479+ self.vp.uri = expected_uri
480+
481+ self.assertEqual(self.vp.uri, expected_uri)
482+ self.assertEqual(self.vp.webkit.get_uri(), self.vp.uri)
483+
484+ def test_stop(self):
485+ self.vp.uri = 'http://foo.bar.baz'
486+ self.vp.stop()
487+
488+ self.assertEqual(self.vp.webkit.get_uri(), '')
489
490
491 if __name__ == "__main__":

Subscribers

People subscribed via source and target branches