Merge lp:~diegosarmentero/ubuntuone-control-panel/menu-status-actions into lp:ubuntuone-control-panel

Proposed by Diego Sarmentero on 2012-08-10
Status: Merged
Approved by: Diego Sarmentero on 2012-08-14
Approved revision: 346
Merged at revision: 342
Proposed branch: lp:~diegosarmentero/ubuntuone-control-panel/menu-status-actions
Merge into: lp:ubuntuone-control-panel
Prerequisite: lp:~diegosarmentero/ubuntuone-control-panel/refactor-sync-status
Diff against target: 327 lines (+216/-31)
2 files modified
ubuntuone/controlpanel/gui/qt/systray.py (+76/-16)
ubuntuone/controlpanel/gui/qt/tests/test_systray.py (+140/-15)
To merge this branch: bzr merge lp:~diegosarmentero/ubuntuone-control-panel/menu-status-actions
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve on 2012-08-13
Manuel de la Peña (community) 2012-08-10 Approve on 2012-08-13
Review via email: mp+119170@code.launchpad.net

Commit Message

- Adding status actions to the system tray menu (LP: #1034542).

Description of the Change

For u1lint you should set the PYTHONPATH including this branch: lp:~diegosarmentero/ubuntuone-client/ipcmenu

To post a comment you must log in.
Manuel de la Peña (mandel) wrote :

Please fix the following:

103 + self._backend_method = getattr(self.backend, data['backend_method'])

You should pass None to the getattr method to make sure you do not get an attr error. like:

103 + self._backend_method = getattr(self.backend, data['backend_method'], None)

review: Needs Fixing
Diego Sarmentero (diegosarmentero) wrote :

> Please fix the following:
>
> 103 + self._backend_method = getattr(self.backend, data['backend_method'])
>
> You should pass None to the getattr method to make sure you do not get an attr
> error. like:
>
> 103 + self._backend_method = getattr(self.backend, data['backend_method'],
> None)

fixed

Manuel de la Peña (mandel) wrote :

I'm getting the following lint error:

== Python Lint Notices ==

ubuntuone/controlpanel/sd_client/__init__.py:
    211: [E1101, SyncDaemonClient.sync_menu] Instance of 'SyncDaemonTool' has no 'sync_menu' member

review: Needs Fixing
Manuel de la Peña (mandel) wrote :

Looks good after the fixes.

review: Approve
Roberto Alsina (ralsina) :
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/systray.py'
2--- ubuntuone/controlpanel/gui/qt/systray.py 2012-05-14 20:16:02 +0000
3+++ ubuntuone/controlpanel/gui/qt/systray.py 2012-08-13 12:10:30 +0000
4@@ -19,13 +19,24 @@
5 from twisted.internet.defer import inlineCallbacks
6 from ubuntuone.platform.tools import SyncDaemonTool
7
8+from ubuntuone.controlpanel import backend, cache
9+from ubuntuone.controlpanel.logger import setup_logging
10 from ubuntuone.controlpanel.gui import (
11- RESTORE_LABEL,
12+ LOADING,
13+ OPEN_UBUNTU_ONE,
14+ PLEASE_WAIT,
15 QUIT_LABEL,
16 )
17-
18-
19-class TrayIcon(QtGui.QSystemTrayIcon):
20+from ubuntuone.controlpanel.gui.qt import (
21+ FILE_SYNC_STATUS,
22+ icon_from_name,
23+)
24+
25+
26+logger = setup_logging('qt.systray')
27+
28+
29+class TrayIcon(QtGui.QSystemTrayIcon, cache.Cache):
30
31 """System notification icon."""
32
33@@ -34,23 +45,72 @@
34 self.setIcon(QtGui.QIcon(":/icon.png"))
35 self.setVisible(True)
36 self.window = window
37- self.activated.connect(self.on_activated)
38+ self._backend_method = None
39+
40+ # Items
41 self.context_menu = QtGui.QMenu()
42- self.restore = QtGui.QAction(RESTORE_LABEL, self,
43- triggered=self.restore_window)
44- self.quit = QtGui.QAction(QUIT_LABEL, self,
45- triggered=self.stop)
46- self.context_menu.addAction(self.restore)
47- self.context_menu.addSeparator()
48- self.context_menu.addAction(self.quit)
49+
50+ self.status = self.context_menu.addAction(LOADING)
51+ self.status.setEnabled(False)
52+ self.status_action = self.context_menu.addAction(PLEASE_WAIT)
53+ self.refresh_status()
54+ self.context_menu.addSeparator()
55+
56+ self.open_u1 = self.context_menu.addAction(OPEN_UBUNTU_ONE)
57+ self.context_menu.addSeparator()
58+
59+ self.quit = self.context_menu.addAction(QUIT_LABEL)
60+
61 self.setContextMenu(self.context_menu)
62
63+ # Connect Signals
64+ self.status_action.triggered.connect(self.change_status)
65+ self.open_u1.triggered.connect(self.restore_window)
66+ self.quit.triggered.connect(self.stop)
67+
68 self.close_callback = close_callback
69
70- def on_activated(self, reason):
71- """The user activated the icon."""
72- if reason == self.Trigger: # Left-click
73- self.restore_window()
74+ @inlineCallbacks
75+ def refresh_status(self):
76+ """Update Ubuntu One status."""
77+ info = yield self.backend.file_sync_status()
78+ self._process_status(info)
79+ self.backend.status_changed_handler = self._process_status
80+
81+ def _process_status(self, status):
82+ """Match status with signals."""
83+ if status is None:
84+ return
85+ try:
86+ status_key = status[backend.STATUS_KEY]
87+ data = FILE_SYNC_STATUS[status_key]
88+ except (KeyError, TypeError):
89+ logger.exception(
90+ '_process_status: received unknown status dict %r', status)
91+ return
92+
93+ self.status_action.setEnabled(True)
94+ icon_name = data.get('icon')
95+ icon = QtGui.QIcon()
96+ if icon_name is not None:
97+ icon = icon_from_name(icon_name)
98+ text = data.get('msg')
99+ self.status.setIcon(icon)
100+ self.status.setText(text)
101+
102+ action = data.get('action')
103+ self._backend_method = getattr(self.backend,
104+ data['backend_method'], None)
105+ self.status_action.setText(action)
106+
107+ def change_status(self):
108+ """Change the Syncing status of syncdaemon."""
109+ if self._backend_method is not None:
110+ self.status_action.setEnabled(False)
111+ self._backend_method()
112+ else:
113+ logger.error('on_sync_status_button_clicked: backend method is '
114+ 'None!')
115
116 def restore_window(self):
117 """Show the main window."""
118
119=== modified file 'ubuntuone/controlpanel/gui/qt/tests/test_systray.py'
120--- ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-06-22 14:42:29 +0000
121+++ ubuntuone/controlpanel/gui/qt/tests/test_systray.py 2012-08-13 12:10:30 +0000
122@@ -21,11 +21,15 @@
123 from PyQt4 import QtGui
124 from twisted.internet.defer import inlineCallbacks
125
126+from ubuntuone.controlpanel.gui import qt
127 from ubuntuone.controlpanel.gui.qt import systray
128-from ubuntuone.controlpanel.tests import TestCase
129+from ubuntuone.controlpanel.gui.qt.tests import BaseTestCase
130 import ubuntuone.controlpanel.gui.qt.gui
131
132
133+backend = systray.backend # pylint: disable=C0103
134+
135+
136 class FakeSDTool(object):
137
138 """Fake SyncDaemonTool."""
139@@ -46,10 +50,138 @@
140 super(FakeMainWindow, self).__init__()
141
142
143-class SystrayTestCase(TestCase):
144+class SystrayTestCase(BaseTestCase):
145
146 """Test the notification area icon."""
147
148+ class_ui = systray.TrayIcon
149+
150+ # pylint: disable=W0212
151+
152+ def assert_status_correct(self, status_bd, status_ui, action,
153+ callback=None):
154+ """The shown status is correct.
155+
156+ * The ui's label shows 'status_ui'.
157+ * If action is not None, the ui's button shows that 'action' as label
158+ and when clicking it, 'callback' gets executed.
159+ * If action is None, the ui's button should be hidden.
160+ * If a tooltip is given, then it exists with correct text.
161+
162+ """
163+ expected_text = status_ui
164+
165+ status = {backend.STATUS_KEY: status_bd}
166+ self.ui._process_status(status)
167+
168+ actual_text = unicode(self.ui.status.text())
169+ self.assertEqual(expected_text, actual_text)
170+
171+ self.assertFalse(self.ui.status.isEnabled())
172+ self.assertEqual(unicode(self.ui.status_action.text()), action)
173+
174+ self.ui.status_action.trigger()
175+ self.assertFalse(self.ui.status_action.isEnabled())
176+ self.assert_backend_called(callback)
177+
178+ def test_process_info_invalid_status(self):
179+ """File sync status invalid, ignore event."""
180+ status = {backend.STATUS_KEY: backend.FILE_SYNC_STARTING,
181+ backend.MSG_KEY: qt.FILE_SYNC_STARTING,
182+ 'icon': backend.FILE_SYNC_STARTING}
183+ self.ui._process_status(status)
184+ self.ui._process_status(3)
185+
186+ actual_text = unicode(self.ui.status.text())
187+ self.assertEqual(qt.FILE_SYNC_STARTING, actual_text)
188+
189+ def test_process_info_changed(self):
190+ """Backend's file sync status changed callback is connected."""
191+ self.ui.refresh_status()
192+ self.assertEqual(self.ui.backend.status_changed_handler,
193+ self.ui._process_status)
194+
195+ # pylint: enable=W0212
196+
197+ def test_process_info_disabled(self):
198+ """File sync status disabled update the label."""
199+ self.ui.refresh_status()
200+ self.assert_status_correct(status_bd=backend.FILE_SYNC_DISABLED,
201+ status_ui=qt.FILE_SYNC_DISABLED,
202+ action=qt.FILE_SYNC_ENABLE,
203+ callback='enable_files')
204+
205+ def test_process_info_disconnected(self):
206+ """File sync status disconnected update the label."""
207+ self.assert_status_correct(status_bd=backend.FILE_SYNC_DISCONNECTED,
208+ status_ui=qt.FILE_SYNC_DISCONNECTED,
209+ action=qt.FILE_SYNC_CONNECT,
210+ callback='connect_files')
211+
212+ def test_process_info_error(self):
213+ """File sync status error update the label."""
214+ msg = qt.WARNING_MARKUP % qt.FILE_SYNC_ERROR
215+ self.assert_status_correct(status_bd=backend.FILE_SYNC_ERROR,
216+ status_ui=msg,
217+ action=qt.FILE_SYNC_RESTART,
218+ callback='restart_files')
219+
220+ def test_process_info_idle(self):
221+ """File sync status idle update the label."""
222+ self.assert_status_correct(status_bd=backend.FILE_SYNC_IDLE,
223+ status_ui=qt.FILE_SYNC_IDLE,
224+ action=qt.FILE_SYNC_DISCONNECT,
225+ callback='disconnect_files')
226+
227+ def test_process_info_starting(self):
228+ """File sync status starting update the label."""
229+ self.assert_status_correct(status_bd=backend.FILE_SYNC_STARTING,
230+ status_ui=qt.FILE_SYNC_STARTING,
231+ action=qt.FILE_SYNC_STOP,
232+ callback='stop_files')
233+
234+ def test_process_info_stopped(self):
235+ """File sync status stopped update the label."""
236+ self.assert_status_correct(status_bd=backend.FILE_SYNC_STOPPED,
237+ status_ui=qt.FILE_SYNC_STOPPED,
238+ action=qt.FILE_SYNC_START,
239+ callback='start_files')
240+
241+ def test_process_info_syncing(self):
242+ """File sync status syncing update the label."""
243+ self.assert_status_correct(status_bd=backend.FILE_SYNC_SYNCING,
244+ status_ui=qt.FILE_SYNC_SYNCING,
245+ action=qt.FILE_SYNC_DISCONNECT,
246+ callback='disconnect_files')
247+
248+ def test_process_info_unknown(self):
249+ """File sync status unknown update the label."""
250+ msg = qt.WARNING_MARKUP % qt.FILE_SYNC_ERROR
251+ self.assert_status_correct(status_bd=backend.FILE_SYNC_UNKNOWN,
252+ status_ui=msg,
253+ action=qt.FILE_SYNC_RESTART,
254+ callback='restart_files')
255+
256+ def test_backend(self):
257+ """Backend is created."""
258+ self.assertIsInstance(self.ui.backend,
259+ backend.ControlBackend)
260+
261+ def test_refresh_status_requested(self):
262+ """test refresh_status was requested on initialized."""
263+ data = {}
264+
265+ def callback(status):
266+ """Fake _process_status callback."""
267+ data['called'] = True
268+ data['status'] = status
269+
270+ self.patch(self.ui, "_process_status", callback)
271+ self.ui.refresh_status()
272+ self.assert_backend_called('file_sync_status')
273+ self.assertTrue(data['called'])
274+ self.assertEqual(data['status'], [])
275+
276 def test_quit(self):
277 """Test the quit option in the menu."""
278 # Not done on setup, because if I patch stop
279@@ -74,29 +206,19 @@
280 "MainWindow", FakeMainWindow)
281 tray = systray.TrayIcon()
282 self.assertEqual(tray.window, None)
283- tray.restore.trigger()
284+ tray.open_u1.trigger()
285 self.assertIsInstance(tray.window, FakeMainWindow)
286 self.assertTrue(tray.window.isVisible())
287 self.assertEqual(tray.window.args, ((),
288 {'close_callback': tray.delete_window}))
289
290- def test_activate(self):
291- """Test the icon activation."""
292- tray = systray.TrayIcon()
293- window = FakeMainWindow()
294- tray.window = window
295- self.assertFalse(tray.window.isVisible())
296- tray.activated.emit(tray.Trigger)
297- self.assertEqual(tray.window, window)
298- self.assertTrue(tray.window.isVisible())
299-
300 def test_restore_window(self):
301 """Test the restore window option in the menu, with a window."""
302 tray = systray.TrayIcon()
303 window = FakeMainWindow()
304 tray.window = window
305 self.assertFalse(tray.window.isVisible())
306- tray.restore.trigger()
307+ tray.open_u1.trigger()
308 self.assertEqual(tray.window, window)
309 self.assertTrue(tray.window.isVisible())
310
311@@ -109,7 +231,7 @@
312 # it has a small delay, and it fails.
313 self.patch(window, "activateWindow", self._set_called)
314 tray.window = window
315- tray.restore.trigger()
316+ tray.open_u1.trigger()
317 self.assertEqual(self._called, ((), {}))
318
319 def test_delete_window(self):
320@@ -133,4 +255,7 @@
321 self.assertTrue(tray.isVisible())
322 self.assertEqual(tray.window, None)
323 self.assertIsInstance(tray.context_menu, QtGui.QMenu)
324+ self.assertIsInstance(tray.status, QtGui.QAction)
325+ self.assertIsInstance(tray.status_action, QtGui.QAction)
326+ self.assertIsInstance(tray.open_u1, QtGui.QAction)
327 self.assertNotEqual(tray.icon(), None)

Subscribers

People subscribed via source and target branches