Merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.2 into lp:ubuntuone-control-panel/stable-3-0

Proposed by Natalia Bidart on 2012-01-17
Status: Merged
Approved by: Natalia Bidart on 2012-01-17
Approved revision: 246
Merged at revision: 245
Proposed branch: lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.2
Merge into: lp:ubuntuone-control-panel/stable-3-0
Diff against target: 376 lines (+288/-4)
8 files modified
ubuntuone/controlpanel/gui/qt/devices.py (+1/-0)
ubuntuone/controlpanel/gui/qt/main/__init__.py (+4/-4)
ubuntuone/controlpanel/gui/qt/tests/test_devices.py (+22/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py (+28/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py (+29/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py (+17/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py (+135/-0)
ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py (+52/-0)
To merge this branch: bzr merge lp:~nataliabidart/ubuntuone-control-panel/stable-3-0-update-2.99.2
Reviewer Review Type Date Requested Status
dobey (community) Approve on 2012-01-17
Roberto Alsina (community) 2012-01-17 Approve on 2012-01-17
Review via email: mp+88901@code.launchpad.net

Commit Message

[ Diego Sarmentero <email address hidden> ]
  - Fixed: two remove buttons (LP: #904551).
[ Robert Alsina <email address hidden> ]
  - Make control panel a unique instance app on windows (LP: #862997).

To post a comment you must log in.
Roberto Alsina (ralsina) wrote :

+1

review: Approve
dobey (dobey) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'ubuntuone/controlpanel/gui/qt/devices.py'
2--- ubuntuone/controlpanel/gui/qt/devices.py 2011-11-28 15:06:18 +0000
3+++ ubuntuone/controlpanel/gui/qt/devices.py 2012-01-17 16:56:26 +0000
4@@ -81,6 +81,7 @@
5 for i in reversed(range(children)):
6 widget = box.itemAt(i).widget()
7 box.removeWidget(widget)
8+ widget.deleteLater()
9
10 def update_device_info(self, device_info):
11 """Update one device."""
12
13=== modified file 'ubuntuone/controlpanel/gui/qt/main/__init__.py'
14--- ubuntuone/controlpanel/gui/qt/main/__init__.py 2011-11-14 11:46:39 +0000
15+++ ubuntuone/controlpanel/gui/qt/main/__init__.py 2012-01-17 16:56:26 +0000
16@@ -18,13 +18,11 @@
17
18 import sys
19
20-from PyQt4 import QtGui
21-
22 # Module used to include the resources into this file
23 # Unused import images_rc, pylint: disable=W0611
24 from ubuntuone.controlpanel.gui.qt.ui import images_rc
25 # pylint: enable=W0611
26-
27+from ubuntuone.controlpanel.gui.qt.uniqueapp import UniqueApplication
28
29 # Invalid name "source", pylint: disable=C0103
30 if sys.platform == 'win32':
31@@ -42,7 +40,7 @@
32 # because u1trial already provides a reactor.
33
34 # The main loop MUST be initialized before importing the reactor
35- app = QtGui.QApplication(sys.argv)
36+ app = UniqueApplication(sys.argv, "ubuntuone-control-panel")
37 source.main(app)
38
39 # Reimport 'qt4reactor', 'reactor', 'start', pylint: disable=W0404, F0401
40@@ -59,6 +57,8 @@
41 icon, window = start(reactor.stop,
42 minimized=minimized, with_icon=with_icon)
43 # pylint: enable=W0612
44+ if icon:
45+ app.new_instance.connect(icon.restore_window)
46
47 reactor.run()
48 # pylint: enable=E1101
49
50=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_devices.py'
51--- ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2011-11-28 15:06:18 +0000
52+++ ubuntuone/controlpanel/gui/qt/tests/test_devices.py 2012-01-17 16:56:26 +0000
53@@ -35,6 +35,7 @@
54 innerclass_ui = gui.devices_ui
55 innerclass_name = "Ui_Form"
56 class_ui = gui.DevicesPanel
57+ executed = False
58
59 @defer.inlineCallbacks
60 def setUp(self):
61@@ -88,6 +89,27 @@
62 device = self.ui.ui.list_devices.itemWidget(item)
63 self.assertEqual(device.text(), remote_device['name'])
64
65+ def test_remove_device_and_check_layout_state(self):
66+ """Test if the widget is properly removed."""
67+ self.ui.process_info(SAMPLE_DEVICES_INFO)
68+ self.ui.show()
69+
70+ self.assertEqual(self.ui.ui.local_device_box.count(), 1)
71+ local_device = self.ui.ui.local_device_box.itemAt(0).widget()
72+ self.executed = False
73+
74+ def delete_later(reference=None):
75+ """Fake delete later."""
76+ self.executed = True
77+ self.patch(local_device, "deleteLater", delete_later)
78+ self.ui.clear_device_info(self.ui.ui.local_device_box)
79+ self.ui.process_info(SAMPLE_DEVICES_INFO)
80+ self.assertEqual(self.ui.ui.local_device_box.count(), 1)
81+ local_device2 = self.ui.ui.local_device_box.itemAt(0).widget()
82+ self.assertNotEqual(local_device, local_device2)
83+ self.assertTrue(self.executed)
84+ self.assertFalse(local_device.isVisible())
85+
86 def test_process_info_twice(self):
87 """The widget is updated with the info."""
88 self.test_process_info()
89
90=== added directory 'ubuntuone/controlpanel/gui/qt/uniqueapp'
91=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py'
92--- ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py 1970-01-01 00:00:00 +0000
93+++ ubuntuone/controlpanel/gui/qt/uniqueapp/__init__.py 2012-01-17 16:56:26 +0000
94@@ -0,0 +1,28 @@
95+# -*- coding: utf-8 -*-
96+#
97+# Copyright 2011 Canonical Ltd.
98+#
99+# This program is free software: you can redistribute it and/or modify it
100+# under the terms of the GNU General Public License version 3, as published
101+# by the Free Software Foundation.
102+#
103+# This program is distributed in the hope that it will be useful, but
104+# WITHOUT ANY WARRANTY; without even the implied warranties of
105+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
106+# PURPOSE. See the GNU General Public License for more details.
107+#
108+# You should have received a copy of the GNU General Public License along
109+# with this program. If not, see <http://www.gnu.org/licenses/>.
110+
111+"""A QApplication that starts a single instance."""
112+
113+#pylint: disable=W0404
114+import sys
115+
116+if sys.platform == "win32":
117+ import windows as platform
118+else:
119+ import linux as platform
120+
121+#pylint: disable=C0103
122+UniqueApplication = platform.UniqueApplication
123
124=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py'
125--- ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py 1970-01-01 00:00:00 +0000
126+++ ubuntuone/controlpanel/gui/qt/uniqueapp/linux.py 2012-01-17 16:56:26 +0000
127@@ -0,0 +1,29 @@
128+# -*- coding: utf-8 -*-
129+#
130+# Copyright 2011 Canonical Ltd.
131+#
132+# This program is free software: you can redistribute it and/or modify it
133+# under the terms of the GNU General Public License version 3, as published
134+# by the Free Software Foundation.
135+#
136+# This program is distributed in the hope that it will be useful, but
137+# WITHOUT ANY WARRANTY; without even the implied warranties of
138+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
139+# PURPOSE. See the GNU General Public License for more details.
140+#
141+# You should have received a copy of the GNU General Public License along
142+# with this program. If not, see <http://www.gnu.org/licenses/>.
143+
144+"""A QApplication that starts a single instance."""
145+
146+from PyQt4 import QtGui, QtCore
147+
148+
149+class UniqueApplication(QtGui.QApplication):
150+
151+ """A dummy UniqueApplication class."""
152+
153+ new_instance = QtCore.pyqtSignal()
154+
155+ def __init__(self, argv, key):
156+ super(UniqueApplication, self).__init__(argv)
157
158=== added directory 'ubuntuone/controlpanel/gui/qt/uniqueapp/tests'
159=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py'
160--- ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py 1970-01-01 00:00:00 +0000
161+++ ubuntuone/controlpanel/gui/qt/uniqueapp/tests/__init__.py 2012-01-17 16:56:26 +0000
162@@ -0,0 +1,17 @@
163+# -*- coding: utf-8 -*-
164+#
165+# Copyright 2011-2012 Canonical Ltd.
166+#
167+# This program is free software: you can redistribute it and/or modify it
168+# under the terms of the GNU General Public License version 3, as published
169+# by the Free Software Foundation.
170+#
171+# This program is distributed in the hope that it will be useful, but
172+# WITHOUT ANY WARRANTY; without even the implied warranties of
173+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
174+# PURPOSE. See the GNU General Public License for more details.
175+#
176+# You should have received a copy of the GNU General Public License along
177+# with this program. If not, see <http://www.gnu.org/licenses/>.
178+
179+"""Tests for the UniqueApplication class."""
180
181=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py'
182--- ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py 1970-01-01 00:00:00 +0000
183+++ ubuntuone/controlpanel/gui/qt/uniqueapp/tests/test_windows.py 2012-01-17 16:56:26 +0000
184@@ -0,0 +1,135 @@
185+# -*- coding: utf-8 -*-
186+#
187+# Copyright 2011-2012 Canonical Ltd.
188+#
189+# This program is free software: you can redistribute it and/or modify it
190+# under the terms of the GNU General Public License version 3, as published
191+# by the Free Software Foundation.
192+#
193+# This program is distributed in the hope that it will be useful, but
194+# WITHOUT ANY WARRANTY; without even the implied warranties of
195+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
196+# PURPOSE. See the GNU General Public License for more details.
197+#
198+# You should have received a copy of the GNU General Public License along
199+# with this program. If not, see <http://www.gnu.org/licenses/>.
200+
201+"""Tests for the UniqueApplication class."""
202+
203+from PyQt4 import QtCore
204+from twisted.internet.defer import inlineCallbacks
205+
206+from ubuntuone.controlpanel.gui.qt.uniqueapp import windows as uniqueapp
207+from ubuntuone.controlpanel.tests import TestCase
208+
209+
210+#pylint: disable=C0103
211+class FakeLocalSocket(object):
212+ """A fake QLocalSocket."""
213+
214+ def __init__(self):
215+ self.connect_calls = []
216+ self.connect_timeouts = []
217+ self.connect_succeeds = True
218+
219+ def connectToServer(self, *args, **kwargs):
220+ """Fake connectToServer."""
221+ self.connect_calls.append((args, kwargs))
222+
223+ def waitForConnected(self, timeout):
224+ """Fake waitForConnected."""
225+ self.connect_timeouts.append(timeout)
226+ return self.connect_succeeds
227+
228+
229+class FakeSignal(object):
230+
231+ """A fake PyQt signal."""
232+
233+ def __init__(self, *args, **kwargs):
234+ """Initialize."""
235+ self.target = None
236+
237+ def connect(self, target):
238+ """Fake connect."""
239+ self.target = target
240+
241+ def disconnect(self, *args):
242+ """Fake disconnect."""
243+ self.target = None
244+
245+ def emit(self, *args):
246+ """Fake emit."""
247+ if self.target:
248+ self.target(*args)
249+
250+
251+class FakeLocalServer(object):
252+
253+ """A fake QLocalServer."""
254+
255+ def __init__(self):
256+ self.newConnection = FakeSignal()
257+ self.listen_args = []
258+
259+ def listen(self, *args, **kwargs):
260+ """Fake listen."""
261+ self.listen_args.append((args, kwargs))
262+
263+
264+class FakeApplication(object):
265+ """A fake QApplication."""
266+
267+
268+class UniqueAppTestCase(TestCase):
269+ """Test the UniqueAppplication class."""
270+
271+ @inlineCallbacks
272+ def setUp(self):
273+ yield super(UniqueAppTestCase, self).setUp()
274+ self.local_socket = FakeLocalSocket()
275+ self.patch(uniqueapp.QtNetwork, "QLocalSocket",
276+ lambda: self.local_socket)
277+ self.local_server = FakeLocalServer()
278+ self.patch(uniqueapp.QtNetwork, "QLocalServer",
279+ lambda parent: self.local_server)
280+ self.patch(uniqueapp.sys, "exit", self._set_called)
281+ self.fake_quit = FakeSignal()
282+ self.patch(uniqueapp.UniqueApplication, "aboutToQuit", self.fake_quit)
283+ self.patch(uniqueapp.QtGui, "QApplication", FakeApplication)
284+
285+ def test_client_socket(self):
286+ """Check that the client socket is used correctly."""
287+ self.local_socket.connect_succeeds = True
288+ uniqueapp.UniqueApplication([], "key")
289+ self.assertEqual(self.local_socket.connect_calls,
290+ [(("key", QtCore.QIODevice.WriteOnly), {})])
291+ self.assertEqual(self.local_socket.connect_timeouts,
292+ [500])
293+ # The connection succeeds, so it should stop
294+ self.assertEqual(self._called, ((), {}))
295+
296+ def test_server_socket(self):
297+ """Check that the server socket is used correctly."""
298+ self.local_socket.connect_succeeds = False
299+ uniqueapp.UniqueApplication([], "key")
300+ self.assertEqual(self.local_socket.connect_calls,
301+ [(("key", QtCore.QIODevice.WriteOnly), {})])
302+ self.assertEqual(self.local_socket.connect_timeouts,
303+ [500])
304+
305+ # Should not exit
306+ self.assertEqual(self._called, False)
307+
308+ def test_signal_connection(self):
309+ """Check that new_instance is correctly connected."""
310+ app = uniqueapp.UniqueApplication([], "key")
311+ # Yes, this is ugly. I can't find any other meaningful
312+ # way to compare them though.
313+ self.assertEqual(str(app.server.newConnection.target.__self__),
314+ str(app.new_instance))
315+
316+ def test_cleanup(self):
317+ """Check that cleanup is called with the right key."""
318+ app = uniqueapp.UniqueApplication([], "key")
319+ self.assertEqual(self.fake_quit.target, app.cleanup)
320
321=== added file 'ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py'
322--- ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py 1970-01-01 00:00:00 +0000
323+++ ubuntuone/controlpanel/gui/qt/uniqueapp/windows.py 2012-01-17 16:56:26 +0000
324@@ -0,0 +1,52 @@
325+# -*- coding: utf-8 -*-
326+#
327+# Copyright 2011 Canonical Ltd.
328+#
329+# This program is free software: you can redistribute it and/or modify it
330+# under the terms of the GNU General Public License version 3, as published
331+# by the Free Software Foundation.
332+#
333+# This program is distributed in the hope that it will be useful, but
334+# WITHOUT ANY WARRANTY; without even the implied warranties of
335+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
336+# PURPOSE. See the GNU General Public License for more details.
337+#
338+# You should have received a copy of the GNU General Public License along
339+# with this program. If not, see <http://www.gnu.org/licenses/>.
340+
341+"""A QApplication that starts a single instance."""
342+
343+import sys
344+
345+from PyQt4 import QtNetwork, QtGui, QtCore
346+
347+
348+class UniqueApplication(QtGui.QApplication):
349+
350+ """A QApplication that can only be started once.
351+
352+ Also, the new instance notifies the old one to show
353+ its window.
354+ """
355+
356+ new_instance = QtCore.pyqtSignal()
357+
358+ def __init__(self, argv, key):
359+ super(UniqueApplication, self).__init__(argv)
360+ self.key = key
361+ self.server = QtNetwork.QLocalServer(self)
362+ self.server.newConnection.connect(self.new_instance.emit)
363+ self.aboutToQuit.connect(self.cleanup)
364+ # Try to connect to existing app
365+ socket = QtNetwork.QLocalSocket()
366+ socket.connectToServer(key, QtCore.QIODevice.WriteOnly)
367+ if socket.waitForConnected(500):
368+ # Connected, exit
369+ sys.exit()
370+
371+ # Not connected, start server
372+ self.ready = self.server.listen(key)
373+
374+ def cleanup(self):
375+ """Remove the socket when we die."""
376+ self.server.removeServer(self.key)

Subscribers

People subscribed via source and target branches