Merge lp:~nataliabidart/ubuntuone-control-panel/volumes-reborn into lp:ubuntuone-control-panel
- volumes-reborn
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Natalia Bidart | ||||
Approved revision: | 58 | ||||
Merged at revision: | 51 | ||||
Proposed branch: | lp:~nataliabidart/ubuntuone-control-panel/volumes-reborn | ||||
Merge into: | lp:ubuntuone-control-panel | ||||
Diff against target: |
902 lines (+393/-169) 8 files modified
bin/ubuntuone-control-panel-gtk (+4/-4) data/management.ui (+2/-2) data/volumes.ui (+73/-17) ubuntuone/controlpanel/gtk/gui.py (+123/-55) ubuntuone/controlpanel/gtk/tests/__init__.py (+28/-3) ubuntuone/controlpanel/gtk/tests/test_gui.py (+158/-71) ubuntuone/controlpanel/gtk/tests/test_widgets.py (+1/-8) ubuntuone/controlpanel/gtk/widgets.py (+4/-9) |
||||
To merge this branch: | bzr merge lp:~nataliabidart/ubuntuone-control-panel/volumes-reborn | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Roberto Alsina (community) | Approve | ||
Martin Albisetti (community) | Approve | ||
Review via email:
|
Commit message
Complete redesign of Folders tab, now internally called Volumes since it will cover Shares in a future (LP: #705989).
Description of the change
Folders tab was renamed and improved.
To test, open 2 terminals and run in each:
killall ubuntuone-
DEBUG=True PYTHONPATH=. ./bin/ubuntuone
And play with the 'Cloud Storage' panel. Be careful that folder subcription/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Roberto Alsina (ralsina) wrote : | # |
+1
LOVE it. I agree with Martin about the Mine wording being a bit strange and about moving "Ubuntu one" to the top (and marking it somehow? emblem? shading?)
But great job :-)
- 57. By Natalia Bidart
-
Root folder is on top of others.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Natalia Bidart (nataliabidart) wrote : | # |
> All in all, this is shaping up to be a huge leap forward from what people will
> see from our service. GREAT job.
Thanks!
> A few comments:
>
> - It confused me for a few minutes to have "X of Y used" on top, and "Z
> available" under "Mine". I had even taken a screenshot to report it as a bug,
> until I realised what it was. I wonder if we should unify? Screehshot:
> http://
Agreed. Filed bug #706021 to discuss this further.
> - How about moving the non-deletable Ubuntu One folder fixed to the top?
> It'll make it feel a bit more immutable!
Fixed!
> - The icon next to the section "Mine" is of multiple people, which is odd, as
> I'm only one person :) Maybe the multi-person icon could be used for the
> "From Others" section, and "Mine" gets a single person?
Agreed, filed bug #706034.
- 58. By Natalia Bidart
-
Lint fixes.
Preview Diff
1 | === modified file 'bin/ubuntuone-control-panel-gtk' | |||
2 | --- bin/ubuntuone-control-panel-gtk 2011-01-20 21:27:21 +0000 | |||
3 | +++ bin/ubuntuone-control-panel-gtk 2011-01-21 19:25:14 +0000 | |||
4 | @@ -34,13 +34,13 @@ | |||
5 | 34 | def parser_options(): | 34 | def parser_options(): |
6 | 35 | """Parse command line parameters.""" | 35 | """Parse command line parameters.""" |
7 | 36 | usage = "Usage: %prog [option]" | 36 | usage = "Usage: %prog [option]" |
10 | 37 | parser = OptionParser(usage=usage) | 37 | result = OptionParser(usage=usage) |
11 | 38 | parser.add_option("", "--switch-to", dest="switch_to", type="string", | 38 | result.add_option("", "--switch-to", dest="switch_to", type="string", |
12 | 39 | metavar="PANEL_NAME", | 39 | metavar="PANEL_NAME", |
13 | 40 | help="Start the Ubuntu One Control Panel (GTK) in the " | 40 | help="Start the Ubuntu One Control Panel (GTK) in the " |
14 | 41 | "PANEL_NAME tab. Possible values are: " | 41 | "PANEL_NAME tab. Possible values are: " |
17 | 42 | "dashboard, folders, devices, applications") | 42 | "dashboard, volumes, devices, applications") |
18 | 43 | return parser | 43 | return result |
19 | 44 | 44 | ||
20 | 45 | 45 | ||
21 | 46 | if __name__ == "__main__": | 46 | if __name__ == "__main__": |
22 | 47 | 47 | ||
23 | === modified file 'data/management.ui' | |||
24 | --- data/management.ui 2010-12-20 22:18:01 +0000 | |||
25 | +++ data/management.ui 2011-01-21 19:25:14 +0000 | |||
26 | @@ -79,8 +79,8 @@ | |||
27 | 79 | </packing> | 79 | </packing> |
28 | 80 | </child> | 80 | </child> |
29 | 81 | <child> | 81 | <child> |
32 | 82 | <object class="GtkRadioButton" id="folders_button"> | 82 | <object class="GtkRadioButton" id="volumes_button"> |
33 | 83 | <property name="label" translatable="yes">Folders</property> | 83 | <property name="label" translatable="yes">Cloud Storage</property> |
34 | 84 | <property name="visible">True</property> | 84 | <property name="visible">True</property> |
35 | 85 | <property name="can_focus">True</property> | 85 | <property name="can_focus">True</property> |
36 | 86 | <property name="receives_default">False</property> | 86 | <property name="receives_default">False</property> |
37 | 87 | 87 | ||
38 | === renamed file 'data/folders.ui' => 'data/volumes.ui' | |||
39 | --- data/folders.ui 2010-12-18 20:16:18 +0000 | |||
40 | +++ data/volumes.ui 2011-01-21 19:25:14 +0000 | |||
41 | @@ -1,11 +1,10 @@ | |||
42 | 1 | <?xml version="1.0" encoding="UTF-8"?> | 1 | <?xml version="1.0" encoding="UTF-8"?> |
43 | 2 | <interface> | 2 | <interface> |
45 | 3 | <requires lib="gtk+" version="2.16"/> | 3 | <requires lib="gtk+" version="2.22"/> |
46 | 4 | <!-- interface-naming-policy project-wide --> | 4 | <!-- interface-naming-policy project-wide --> |
48 | 5 | <object class="GtkVBox" id="itself"> | 5 | <object class="GtkAlignment" id="itself"> |
49 | 6 | <property name="visible">True</property> | 6 | <property name="visible">True</property> |
52 | 7 | <property name="border_width">10</property> | 7 | <property name="can_focus">False</property> |
51 | 8 | <property name="spacing">10</property> | ||
53 | 9 | <child> | 8 | <child> |
54 | 10 | <object class="GtkScrolledWindow" id="scrolledwindow1"> | 9 | <object class="GtkScrolledWindow" id="scrolledwindow1"> |
55 | 11 | <property name="visible">True</property> | 10 | <property name="visible">True</property> |
56 | @@ -13,26 +12,83 @@ | |||
57 | 13 | <property name="hscrollbar_policy">automatic</property> | 12 | <property name="hscrollbar_policy">automatic</property> |
58 | 14 | <property name="vscrollbar_policy">automatic</property> | 13 | <property name="vscrollbar_policy">automatic</property> |
59 | 15 | <child> | 14 | <child> |
61 | 16 | <object class="GtkViewport" id="viewport1"> | 15 | <object class="GtkTreeView" id="volumes_view"> |
62 | 17 | <property name="visible">True</property> | 16 | <property name="visible">True</property> |
72 | 18 | <property name="resize_mode">queue</property> | 17 | <property name="can_focus">True</property> |
73 | 19 | <property name="shadow_type">none</property> | 18 | <property name="model">volumes_store</property> |
74 | 20 | <child> | 19 | <property name="rules_hint">True</property> |
75 | 21 | <object class="GtkAlignment" id="folders"> | 20 | <property name="tooltip_column">0</property> |
76 | 22 | <property name="visible">True</property> | 21 | <child> |
77 | 23 | <property name="xscale">0</property> | 22 | <object class="GtkTreeViewColumn" id="treeviewcolumn2"> |
78 | 24 | <property name="yscale">0</property> | 23 | <property name="resizable">True</property> |
79 | 25 | <child> | 24 | <property name="sizing">autosize</property> |
80 | 26 | <placeholder/> | 25 | <property name="expand">True</property> |
81 | 26 | <child> | ||
82 | 27 | <object class="GtkCellRendererPixbuf" id="cellrendererpixbuf1"/> | ||
83 | 28 | <attributes> | ||
84 | 29 | <attribute name="sensitive">1</attribute> | ||
85 | 30 | <attribute name="icon-name">2</attribute> | ||
86 | 31 | <attribute name="stock-size">5</attribute> | ||
87 | 32 | </attributes> | ||
88 | 33 | </child> | ||
89 | 34 | <child> | ||
90 | 35 | <object class="GtkCellRendererText" id="text_renderer"> | ||
91 | 36 | <property name="ellipsize">end</property> | ||
92 | 37 | <property name="width_chars">80</property> | ||
93 | 38 | </object> | ||
94 | 39 | <attributes> | ||
95 | 40 | <attribute name="markup">0</attribute> | ||
96 | 41 | <attribute name="text">0</attribute> | ||
97 | 42 | </attributes> | ||
98 | 43 | </child> | ||
99 | 44 | </object> | ||
100 | 45 | </child> | ||
101 | 46 | <child> | ||
102 | 47 | <object class="GtkTreeViewColumn" id="treeviewcolumn3"> | ||
103 | 48 | <property name="sizing">autosize</property> | ||
104 | 49 | <property name="title">On this device?</property> | ||
105 | 50 | <child> | ||
106 | 51 | <object class="GtkCellRendererToggle" id="cellrenderertoggle1"> | ||
107 | 52 | <property name="indicator_size">15</property> | ||
108 | 53 | <signal name="toggled" handler="on_subscribed_toggled" swapped="no"/> | ||
109 | 54 | </object> | ||
110 | 55 | <attributes> | ||
111 | 56 | <attribute name="sensitive">4</attribute> | ||
112 | 57 | <attribute name="visible">3</attribute> | ||
113 | 58 | <attribute name="active">1</attribute> | ||
114 | 59 | </attributes> | ||
115 | 60 | </child> | ||
116 | 61 | <child> | ||
117 | 62 | <object class="GtkCellRendererText" id="cellrenderertext1"> | ||
118 | 63 | <property name="visible">False</property> | ||
119 | 64 | </object> | ||
120 | 65 | <attributes> | ||
121 | 66 | <attribute name="text">6</attribute> | ||
122 | 67 | </attributes> | ||
123 | 27 | </child> | 68 | </child> |
124 | 28 | </object> | 69 | </object> |
125 | 29 | </child> | 70 | </child> |
126 | 30 | </object> | 71 | </object> |
127 | 31 | </child> | 72 | </child> |
128 | 32 | </object> | 73 | </object> |
129 | 33 | <packing> | ||
130 | 34 | <property name="position">0</property> | ||
131 | 35 | </packing> | ||
132 | 36 | </child> | 74 | </child> |
133 | 37 | </object> | 75 | </object> |
134 | 76 | <object class="GtkTreeStore" id="volumes_store"> | ||
135 | 77 | <columns> | ||
136 | 78 | <!-- column-name description --> | ||
137 | 79 | <column type="gchararray"/> | ||
138 | 80 | <!-- column-name subscribed --> | ||
139 | 81 | <column type="gboolean"/> | ||
140 | 82 | <!-- column-name icon-name --> | ||
141 | 83 | <column type="gchararray"/> | ||
142 | 84 | <!-- column-name subscribed-visible --> | ||
143 | 85 | <column type="gboolean"/> | ||
144 | 86 | <!-- column-name subscribed-sensitive --> | ||
145 | 87 | <column type="gboolean"/> | ||
146 | 88 | <!-- column-name icon-size --> | ||
147 | 89 | <column type="gint"/> | ||
148 | 90 | <!-- column-name identifier --> | ||
149 | 91 | <column type="gchararray"/> | ||
150 | 92 | </columns> | ||
151 | 93 | </object> | ||
152 | 38 | </interface> | 94 | </interface> |
153 | 39 | 95 | ||
154 | === modified file 'ubuntuone/controlpanel/gtk/gui.py' | |||
155 | --- ubuntuone/controlpanel/gtk/gui.py 2011-01-20 21:23:11 +0000 | |||
156 | +++ ubuntuone/controlpanel/gtk/gui.py 2011-01-21 19:25:14 +0000 | |||
157 | @@ -22,6 +22,7 @@ | |||
158 | 22 | 22 | ||
159 | 23 | import gettext | 23 | import gettext |
160 | 24 | import operator | 24 | import operator |
161 | 25 | import os | ||
162 | 25 | 26 | ||
163 | 26 | from functools import wraps | 27 | from functools import wraps |
164 | 27 | 28 | ||
165 | @@ -101,6 +102,11 @@ | |||
166 | 101 | return filter_by_app_name_inner | 102 | return filter_by_app_name_inner |
167 | 102 | 103 | ||
168 | 103 | 104 | ||
169 | 105 | def on_size_allocate(widget, allocation, label): | ||
170 | 106 | """Resize labels according to who 'widget' is being resized.""" | ||
171 | 107 | label.set_size_request(allocation.width - 2, -1) | ||
172 | 108 | |||
173 | 109 | |||
174 | 104 | @log_call(logger.info) | 110 | @log_call(logger.info) |
175 | 105 | def uri_hook(button, uri, *args, **kwargs): | 111 | def uri_hook(button, uri, *args, **kwargs): |
176 | 106 | """Open an URI or do nothing if URI is not an URL.""" | 112 | """Open an URI or do nothing if URI is not an URL.""" |
177 | @@ -159,10 +165,6 @@ | |||
178 | 159 | label.set_markup(WARNING_MARKUP % message) | 165 | label.set_markup(WARNING_MARKUP % message) |
179 | 160 | label.show() | 166 | label.show() |
180 | 161 | 167 | ||
181 | 162 | def on_size_allocate(self, label, allocation): | ||
182 | 163 | """The label can re rezised, embrase it!.""" | ||
183 | 164 | label.set_size_request(allocation.width - 2, -1) | ||
184 | 165 | |||
185 | 166 | 168 | ||
186 | 167 | class ControlPanelWindow(gtk.Window): | 169 | class ControlPanelWindow(gtk.Window): |
187 | 168 | """The main window for the Ubuntu One control panel.""" | 170 | """The main window for the Ubuntu One control panel.""" |
188 | @@ -241,8 +243,8 @@ | |||
189 | 241 | if self.get_current_page() == 0: | 243 | if self.get_current_page() == 0: |
190 | 242 | self.management.load() | 244 | self.management.load() |
191 | 243 | if credentials_are_new: | 245 | if credentials_are_new: |
194 | 244 | # redirect user to Folders page to review folders subscription | 246 | # redirect user to volumes page to review subscription |
195 | 245 | self.management.folders_button.clicked() | 247 | self.management.volumes_button.clicked() |
196 | 246 | 248 | ||
197 | 247 | self.next_page() | 249 | self.next_page() |
198 | 248 | 250 | ||
199 | @@ -254,17 +256,38 @@ | |||
200 | 254 | 256 | ||
201 | 255 | def __init__(self, title=None): | 257 | def __init__(self, title=None): |
202 | 256 | gtk.VBox.__init__(self) | 258 | gtk.VBox.__init__(self) |
203 | 259 | self._is_processing = False | ||
204 | 260 | |||
205 | 257 | if title is None: | 261 | if title is None: |
206 | 258 | title = self.TITLE | 262 | title = self.TITLE |
207 | 259 | 263 | ||
209 | 260 | self.title = PanelTitle(markup='<b>' + title + '</b>') | 264 | title = '<span font_size="large">%s</span>' % title |
210 | 265 | self.title = PanelTitle(markup=title) | ||
211 | 261 | self.pack_start(self.title, expand=False) | 266 | self.pack_start(self.title, expand=False) |
212 | 262 | 267 | ||
213 | 263 | self.message = LabelLoading(LOADING) | 268 | self.message = LabelLoading(LOADING) |
214 | 264 | self.pack_start(self.message, expand=False) | 269 | self.pack_start(self.message, expand=False) |
215 | 265 | 270 | ||
216 | 271 | self.connect('size-allocate', on_size_allocate, self.title.label) | ||
217 | 266 | self.show_all() | 272 | self.show_all() |
218 | 267 | 273 | ||
219 | 274 | def _get_is_processing(self): | ||
220 | 275 | """Is this panel processing a request?""" | ||
221 | 276 | return self._is_processing | ||
222 | 277 | |||
223 | 278 | def _set_is_processing(self, new_value): | ||
224 | 279 | """Set if this panel is processing a request.""" | ||
225 | 280 | if new_value: | ||
226 | 281 | self.message.start() | ||
227 | 282 | self.set_sensitive(False) | ||
228 | 283 | else: | ||
229 | 284 | self.message.stop() | ||
230 | 285 | self.set_sensitive(True) | ||
231 | 286 | |||
232 | 287 | self._is_processing = new_value | ||
233 | 288 | |||
234 | 289 | is_processing = property(fget=_get_is_processing, fset=_set_is_processing) | ||
235 | 290 | |||
236 | 268 | @log_call(logger.debug) | 291 | @log_call(logger.debug) |
237 | 269 | def on_success(self, message=''): | 292 | def on_success(self, message=''): |
238 | 270 | """Use this callback to stop the Loading and show 'message'.""" | 293 | """Use this callback to stop the Loading and show 'message'.""" |
239 | @@ -302,7 +325,7 @@ | |||
240 | 302 | self.add(self.itself) | 325 | self.add(self.itself) |
241 | 303 | self.warning_label.set_text('') | 326 | self.warning_label.set_text('') |
242 | 304 | self.warning_label.set_property('xalign', 0.5) | 327 | self.warning_label.set_property('xalign', 0.5) |
244 | 305 | self.warning_label.connect('size-allocate', self.on_size_allocate) | 328 | self.connect('size-allocate', on_size_allocate, self.warning_label) |
245 | 306 | 329 | ||
246 | 307 | self.connect_button.set_uri(self.CONNECT) | 330 | self.connect_button.set_uri(self.CONNECT) |
247 | 308 | 331 | ||
248 | @@ -348,7 +371,7 @@ | |||
249 | 348 | label.set_markup(self.BULLET + ' ' + msg) | 371 | label.set_markup(self.BULLET + ' ' + msg) |
250 | 349 | label.set_property('xalign', 0) | 372 | label.set_property('xalign', 0) |
251 | 350 | label.set_property('wrap', True) | 373 | label.set_property('wrap', True) |
253 | 351 | label.connect('size-allocate', self.on_size_allocate) | 374 | self.messages.connect('size-allocate', on_size_allocate, label) |
254 | 352 | 375 | ||
255 | 353 | self.messages.pack_start(label) | 376 | self.messages.pack_start(label) |
256 | 354 | self.messages.show_all() | 377 | self.messages.show_all() |
257 | @@ -478,16 +501,25 @@ | |||
258 | 478 | self.on_error() | 501 | self.on_error() |
259 | 479 | 502 | ||
260 | 480 | 503 | ||
263 | 481 | class FoldersPanel(UbuntuOneBin, ControlPanelMixin): | 504 | class VolumesPanel(UbuntuOneBin, ControlPanelMixin): |
264 | 482 | """The folders panel.""" | 505 | """The volumes panel.""" |
265 | 483 | 506 | ||
268 | 484 | TITLE = _('Listed below are the folders available on this machine. ' | 507 | TITLE = _('Select which folders from your cloud you want synchronized ' |
269 | 485 | 'Subscribed means the folder will receive and send updates.') | 508 | 'on this device.') |
270 | 509 | MY_FOLDERS = _('Mine') | ||
271 | 510 | ALWAYS_SUBSCRIBED = _('Always in your personal cloud storage!') | ||
272 | 511 | FREE_SPACE = _('%(free_space)s available storage') | ||
273 | 512 | CONTACT_ICON_NAME = 'system-users' | ||
274 | 513 | FOLDER_ICON_NAME = 'folder' | ||
275 | 514 | SHARE_ICON_NAME = 'folder-remote' | ||
276 | 515 | ROW_HEADER = '<span font_size="large"><b>%s</b></span> ' \ | ||
277 | 516 | '<span foreground="grey">%s</span>' | ||
278 | 517 | ROOT = '%s - <span foreground="%s" font_size="small">%s</span>' | ||
279 | 486 | NO_VOLUMES = _('No folders to show.') | 518 | NO_VOLUMES = _('No folders to show.') |
280 | 487 | 519 | ||
281 | 488 | def __init__(self): | 520 | def __init__(self): |
282 | 489 | UbuntuOneBin.__init__(self) | 521 | UbuntuOneBin.__init__(self) |
284 | 490 | ControlPanelMixin.__init__(self, filename='folders.ui') | 522 | ControlPanelMixin.__init__(self, filename='volumes.ui') |
285 | 491 | self.add(self.itself) | 523 | self.add(self.itself) |
286 | 492 | self.show_all() | 524 | self.show_all() |
287 | 493 | 525 | ||
288 | @@ -495,66 +527,102 @@ | |||
289 | 495 | self.on_volumes_info_ready) | 527 | self.on_volumes_info_ready) |
290 | 496 | self.backend.connect_to_signal('VolumesInfoError', | 528 | self.backend.connect_to_signal('VolumesInfoError', |
291 | 497 | self.on_volumes_info_error) | 529 | self.on_volumes_info_error) |
294 | 498 | self.volumes = None | 530 | self.backend.connect_to_signal('VolumeSettingsChanged', |
295 | 499 | self._subscribed = [] | 531 | self.on_volume_settings_changed) |
296 | 532 | self.backend.connect_to_signal('VolumeSettingsChangeError', | ||
297 | 533 | self.on_volume_settings_change_error) | ||
298 | 534 | |||
299 | 535 | def _process_path(self, path): | ||
300 | 536 | """Trim 'path' so the '~' is removed.""" | ||
301 | 537 | home = os.path.expanduser('~') | ||
302 | 538 | return path.replace(os.path.join(home, ''), '') | ||
303 | 500 | 539 | ||
304 | 501 | def on_volumes_info_ready(self, info): | 540 | def on_volumes_info_ready(self, info): |
305 | 502 | """Backend notifies of volumes info.""" | 541 | """Backend notifies of volumes info.""" |
306 | 503 | 542 | ||
311 | 504 | if self.volumes is not None: | 543 | self.volumes_store.clear() |
308 | 505 | self.folders.remove(self.volumes) | ||
309 | 506 | self.volumes = None | ||
310 | 507 | |||
312 | 508 | if not info: | 544 | if not info: |
313 | 509 | self.on_success(self.NO_VOLUMES) | 545 | self.on_success(self.NO_VOLUMES) |
314 | 510 | return | 546 | return |
315 | 511 | else: | 547 | else: |
316 | 512 | self.on_success() | 548 | self.on_success() |
317 | 513 | 549 | ||
343 | 514 | self.volumes = gtk.Table(rows=len(info) + 1, columns=2) | 550 | # pylint: disable=W0612 |
344 | 515 | 551 | # name, subscribed, icon name, show toggle, sensitive, icon size, id | |
345 | 516 | header = (gtk.Label(), gtk.Label()) | 552 | empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU, None) |
346 | 517 | header[0].set_markup('<b>' + _('Local path') + '</b>') | 553 | |
347 | 518 | header[1].set_markup('<b>' + _('Subscribed') + '</b>') | 554 | for name, free_bytes, volumes in info: |
348 | 519 | self.volumes.attach(header[0], 0, 1, 0, 1) | 555 | if name: |
349 | 520 | self.volumes.attach(header[1], 1, 2, 0, 1, xoptions=0) | 556 | name = name + "'s" |
350 | 521 | self.volumes.show_all() | 557 | icon_name = self.SHARE_ICON_NAME |
351 | 522 | 558 | else: | |
352 | 523 | info.sort(key=operator.itemgetter('suggested_path')) | 559 | name = self.MY_FOLDERS |
353 | 524 | for i, volume in enumerate(info): | 560 | icon_name = self.FOLDER_ICON_NAME |
354 | 525 | path = gtk.Label(volume['suggested_path']) | 561 | |
355 | 526 | path.set_property('xalign', 0) | 562 | free_bytes_args = {'free_space': self.humanize(int(free_bytes))} |
356 | 527 | path.show() | 563 | row = (self.ROW_HEADER % (name, self.FREE_SPACE % free_bytes_args), |
357 | 528 | self.volumes.attach(path, 0, 1, i + 1, i + 2) | 564 | True, self.CONTACT_ICON_NAME, False, False, |
358 | 529 | 565 | gtk.ICON_SIZE_LARGE_TOOLBAR, None) | |
359 | 530 | subscribed = gtk.CheckButton(volume['volume_id']) | 566 | treeiter = self.volumes_store.append(None, row) |
360 | 531 | subscribed.set_active(bool(volume['subscribed'])) | 567 | |
361 | 532 | subscribed.show() | 568 | volumes.sort(key=operator.itemgetter('path')) |
362 | 533 | subscribed.get_child().hide() | 569 | for volume in volumes: |
363 | 534 | subscribed.connect('clicked', self.on_subscribed_clicked) | 570 | sensitive = True |
364 | 535 | self._subscribed.append(subscribed) | 571 | path = self._process_path(volume['path']) |
365 | 536 | self.volumes.attach(subscribed, 1, 2, i + 1, i + 2, xoptions=0) | 572 | if volume['type'] == u'ROOT': |
366 | 537 | 573 | sensitive = False | |
367 | 538 | self.folders.add(self.volumes) | 574 | path = self.ROOT % (path, ORANGE, self.ALWAYS_SUBSCRIBED) |
368 | 575 | |||
369 | 576 | row = (path, bool(volume['subscribed']), icon_name, True, | ||
370 | 577 | sensitive, gtk.ICON_SIZE_MENU, volume['volume_id']) | ||
371 | 578 | |||
372 | 579 | if volume['type'] == u'ROOT': # root should go first! | ||
373 | 580 | self.volumes_store.prepend(treeiter, row) | ||
374 | 581 | else: | ||
375 | 582 | self.volumes_store.append(treeiter, row) | ||
376 | 583 | |||
377 | 584 | # When we display shares info, we'll need to smartly add | ||
378 | 585 | # an empty row to the tree view to separate volume groups | ||
379 | 586 | #treeiter = self.volumes_store.append(None, empty_row) | ||
380 | 587 | |||
381 | 588 | self.volumes_view.expand_row(0, True) | ||
382 | 589 | self.volumes_view.show_all() | ||
383 | 590 | self.is_processing = False | ||
384 | 539 | 591 | ||
385 | 540 | @log_call(logger.error) | 592 | @log_call(logger.error) |
386 | 541 | def on_volumes_info_error(self, error_dict=None): | 593 | def on_volumes_info_error(self, error_dict=None): |
387 | 542 | """Backend notifies of an error when fetching volumes info.""" | 594 | """Backend notifies of an error when fetching volumes info.""" |
388 | 543 | self.on_error() | 595 | self.on_error() |
389 | 544 | 596 | ||
394 | 545 | def on_subscribed_clicked(self, checkbutton): | 597 | @log_call(logger.info) |
395 | 546 | """The user toggled 'checkbutton'.""" | 598 | def on_volume_settings_changed(self, volume_id): |
396 | 547 | volume_id = checkbutton.get_label() | 599 | """The settings for 'volume_id' were changed.""" |
397 | 548 | subscribed = bool_str(checkbutton.get_active()) | 600 | self.is_processing = False |
398 | 601 | |||
399 | 602 | @log_call(logger.error) | ||
400 | 603 | def on_volume_settings_change_error(self, volume_id, error_dict=None): | ||
401 | 604 | """The settings for 'volume_id' were not changed.""" | ||
402 | 605 | self.load() | ||
403 | 606 | |||
404 | 607 | def on_subscribed_toggled(self, widget, path, *args, **kwargs): | ||
405 | 608 | """The user toggled 'widget'.""" | ||
406 | 609 | treeiter = self.volumes_store.get_iter(path) | ||
407 | 610 | volume_id = self.volumes_store.get_value(treeiter, 6) | ||
408 | 611 | subscribed = not self.volumes_store.get_value(treeiter, 1) | ||
409 | 612 | |||
410 | 613 | self.volumes_store.set_value(treeiter, 1, subscribed) | ||
411 | 614 | |||
412 | 549 | self.backend.change_volume_settings(volume_id, | 615 | self.backend.change_volume_settings(volume_id, |
414 | 550 | {'subscribed': subscribed}, | 616 | {'subscribed': bool_str(subscribed)}, |
415 | 551 | reply_handler=NO_OP, error_handler=error_handler) | 617 | reply_handler=NO_OP, error_handler=error_handler) |
416 | 552 | 618 | ||
417 | 619 | self.is_processing = True | ||
418 | 620 | |||
419 | 553 | def load(self): | 621 | def load(self): |
420 | 554 | """Load the volume list.""" | 622 | """Load the volume list.""" |
421 | 555 | self.backend.volumes_info(reply_handler=NO_OP, | 623 | self.backend.volumes_info(reply_handler=NO_OP, |
422 | 556 | error_handler=error_handler) | 624 | error_handler=error_handler) |
424 | 557 | self.message.start() | 625 | self.is_processing = True |
425 | 558 | 626 | ||
426 | 559 | 627 | ||
427 | 560 | class Device(gtk.VBox, ControlPanelMixin): | 628 | class Device(gtk.VBox, ControlPanelMixin): |
428 | @@ -1228,7 +1296,7 @@ | |||
429 | 1228 | class ManagementPanel(gtk.VBox, ControlPanelMixin): | 1296 | class ManagementPanel(gtk.VBox, ControlPanelMixin): |
430 | 1229 | """The management panel. | 1297 | """The management panel. |
431 | 1230 | 1298 | ||
433 | 1231 | The user can manage dashboard, folders, devices and services. | 1299 | The user can manage dashboard, volumes, devices and services. |
434 | 1232 | 1300 | ||
435 | 1233 | """ | 1301 | """ |
436 | 1234 | 1302 | ||
437 | @@ -1255,12 +1323,12 @@ | |||
438 | 1255 | self.status_box.pack_end(self.status_label, expand=False) | 1323 | self.status_box.pack_end(self.status_label, expand=False) |
439 | 1256 | 1324 | ||
440 | 1257 | self.dashboard = DashboardPanel() | 1325 | self.dashboard = DashboardPanel() |
442 | 1258 | self.folders = FoldersPanel() | 1326 | self.volumes = VolumesPanel() |
443 | 1259 | self.devices = DevicesPanel() | 1327 | self.devices = DevicesPanel() |
444 | 1260 | self.services = ServicesPanel() | 1328 | self.services = ServicesPanel() |
445 | 1261 | 1329 | ||
446 | 1262 | cb = lambda button, page_num: self.notebook.set_current_page(page_num) | 1330 | cb = lambda button, page_num: self.notebook.set_current_page(page_num) |
448 | 1263 | self.tabs = (u'dashboard', u'folders', u'devices', u'services') | 1331 | self.tabs = (u'dashboard', u'volumes', u'devices', u'services') |
449 | 1264 | for page_num, tab in enumerate(self.tabs): | 1332 | for page_num, tab in enumerate(self.tabs): |
450 | 1265 | setattr(self, ('%s_page' % tab).upper(), page_num) | 1333 | setattr(self, ('%s_page' % tab).upper(), page_num) |
451 | 1266 | button = getattr(self, '%s_button' % tab) | 1334 | button = getattr(self, '%s_button' % tab) |
452 | @@ -1270,7 +1338,7 @@ | |||
453 | 1270 | gtk.gdk.Color(DEFAULT_FG)) | 1338 | gtk.gdk.Color(DEFAULT_FG)) |
454 | 1271 | self.notebook.insert_page(getattr(self, tab), position=page_num) | 1339 | self.notebook.insert_page(getattr(self, tab), position=page_num) |
455 | 1272 | 1340 | ||
457 | 1273 | self.folders_button.connect('clicked', lambda b: self.folders.load()) | 1341 | self.volumes_button.connect('clicked', lambda b: self.volumes.load()) |
458 | 1274 | self.devices_button.connect('clicked', lambda b: self.devices.load()) | 1342 | self.devices_button.connect('clicked', lambda b: self.devices.load()) |
459 | 1275 | self.services_button.connect('clicked', lambda b: self.services.load()) | 1343 | self.services_button.connect('clicked', lambda b: self.services.load()) |
460 | 1276 | self.devices.connect('local-device-removed', | 1344 | self.devices.connect('local-device-removed', |
461 | 1277 | 1345 | ||
462 | === modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py' | |||
463 | --- ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-10 02:26:22 +0000 | |||
464 | +++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-21 19:25:14 +0000 | |||
465 | @@ -28,10 +28,35 @@ | |||
466 | 28 | FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me', | 28 | FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me', |
467 | 29 | 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'} | 29 | 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'} |
468 | 30 | 30 | ||
469 | 31 | USER_HOME = '/home/tester' | ||
470 | 32 | |||
471 | 33 | ROOT = { | ||
472 | 34 | u'volume_id': '', u'path': '/home/tester/My Ubuntu', | ||
473 | 35 | u'subscribed': 'True', u'type': u'ROOT', | ||
474 | 36 | } | ||
475 | 37 | |||
476 | 38 | FAKE_FOLDERS_INFO = [ | ||
477 | 39 | {u'volume_id': u'0', u'path': u'/home/tester/foo', | ||
478 | 40 | u'suggested_path': u'~/foo', u'subscribed': u'', u'type': u'UDF'}, | ||
479 | 41 | {u'volume_id': u'1', u'path': u'/home/tester/bar', | ||
480 | 42 | u'suggested_path': u'~/bar', u'subscribed': u'True', u'type': u'UDF'}, | ||
481 | 43 | {u'volume_id': u'2', u'path': u'/home/tester/baz', | ||
482 | 44 | u'suggested_path': u'~/baz', u'subscribed': u'True', u'type': u'UDF'}, | ||
483 | 45 | ] | ||
484 | 46 | |||
485 | 47 | FAKE_SHARES_INFO = [ | ||
486 | 48 | {u'volume_id': u'1234', | ||
487 | 49 | u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User', | ||
488 | 50 | u'subscribed': u'', u'type': u'SHARE'}, | ||
489 | 51 | |||
490 | 52 | {u'volume_id': u'5678', | ||
491 | 53 | u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User', | ||
492 | 54 | u'subscribed': u'True', u'type': u'SHARE'}, | ||
493 | 55 | ] | ||
494 | 56 | |||
495 | 31 | FAKE_VOLUMES_INFO = [ | 57 | FAKE_VOLUMES_INFO = [ |
499 | 32 | {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''}, | 58 | (u'', u'147852369', [ROOT] + FAKE_FOLDERS_INFO), |
500 | 33 | {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'}, | 59 | (u'Other User', u'985674', FAKE_SHARES_INFO), |
498 | 34 | {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'}, | ||
501 | 35 | ] | 60 | ] |
502 | 36 | 61 | ||
503 | 37 | FAKE_DEVICE_INFO = { | 62 | FAKE_DEVICE_INFO = { |
504 | 38 | 63 | ||
505 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py' | |||
506 | --- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-20 21:23:11 +0000 | |||
507 | +++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-21 19:25:14 +0000 | |||
508 | @@ -27,7 +27,7 @@ | |||
509 | 27 | from ubuntuone.controlpanel.gtk import gui | 27 | from ubuntuone.controlpanel.gtk import gui |
510 | 28 | from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO, | 28 | from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO, |
511 | 29 | FAKE_DEVICE_INFO, FAKE_DEVICES_INFO, | 29 | FAKE_DEVICE_INFO, FAKE_DEVICES_INFO, |
513 | 30 | FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, | 30 | FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, USER_HOME, |
514 | 31 | FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface, | 31 | FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface, |
515 | 32 | FakedPackageManager, | 32 | FakedPackageManager, |
516 | 33 | ) | 33 | ) |
517 | @@ -52,6 +52,8 @@ | |||
518 | 52 | 52 | ||
519 | 53 | def setUp(self): | 53 | def setUp(self): |
520 | 54 | super(BaseTestCase, self).setUp() | 54 | super(BaseTestCase, self).setUp() |
521 | 55 | self.patch(gui.os.path, 'expanduser', | ||
522 | 56 | lambda path: path.replace('~', USER_HOME)) | ||
523 | 55 | self.patch(gui.gtk, 'main', lambda: None) | 57 | self.patch(gui.gtk, 'main', lambda: None) |
524 | 56 | self.patch(gui.dbus, 'SessionBus', FakedSessionBus) | 58 | self.patch(gui.dbus, 'SessionBus', FakedSessionBus) |
525 | 57 | self.patch(gui.dbus, 'Interface', FakedInterface) | 59 | self.patch(gui.dbus, 'Interface', FakedInterface) |
526 | @@ -279,10 +281,10 @@ | |||
527 | 279 | self.ui.management.DASHBOARD_PAGE) | 281 | self.ui.management.DASHBOARD_PAGE) |
528 | 280 | self.assertEqual(self._called, ((), {})) | 282 | self.assertEqual(self._called, ((), {})) |
529 | 281 | 283 | ||
531 | 282 | def test_credentials_found_shows_folders_management_panel(self): | 284 | def test_credentials_found_shows_volumes_management_panel(self): |
532 | 283 | """On 'credentials-found' signal, the management panel is shown. | 285 | """On 'credentials-found' signal, the management panel is shown. |
533 | 284 | 286 | ||
535 | 285 | If first signal parameter is True, visible tab should be folders. | 287 | If first signal parameter is True, visible tab should be volumes. |
536 | 286 | 288 | ||
537 | 287 | """ | 289 | """ |
538 | 288 | a_token = object() | 290 | a_token = object() |
539 | @@ -290,7 +292,7 @@ | |||
540 | 290 | 292 | ||
541 | 291 | self.assert_current_tab_correct(self.ui.management) | 293 | self.assert_current_tab_correct(self.ui.management) |
542 | 292 | self.assertEqual(self.ui.management.notebook.get_current_page(), | 294 | self.assertEqual(self.ui.management.notebook.get_current_page(), |
544 | 293 | self.ui.management.FOLDERS_PAGE) | 295 | self.ui.management.VOLUMES_PAGE) |
545 | 294 | 296 | ||
546 | 295 | def test_local_device_removed_shows_overview_panel(self): | 297 | def test_local_device_removed_shows_overview_panel(self): |
547 | 296 | """On 'local-device-removed' signal, the overview panel is shown.""" | 298 | """On 'local-device-removed' signal, the overview panel is shown.""" |
548 | @@ -362,6 +364,29 @@ | |||
549 | 362 | self.assert_warning_correct(self.ui.message, msg) | 364 | self.assert_warning_correct(self.ui.message, msg) |
550 | 363 | self.assertFalse(self.ui.message.active) | 365 | self.assertFalse(self.ui.message.active) |
551 | 364 | 366 | ||
552 | 367 | def test_is_processing(self): | ||
553 | 368 | """The flag 'is_processing' is False on start.""" | ||
554 | 369 | self.assertFalse(self.ui.is_processing) | ||
555 | 370 | self.assertTrue(self.ui.is_sensitive()) | ||
556 | 371 | |||
557 | 372 | def test_set_is_processing(self): | ||
558 | 373 | """When setting 'is_processing', the spinner is shown.""" | ||
559 | 374 | self.ui.is_processing = False | ||
560 | 375 | self.ui.is_processing = True | ||
561 | 376 | |||
562 | 377 | self.assertTrue(self.ui.message.get_visible()) | ||
563 | 378 | self.assertTrue(self.ui.message.active) | ||
564 | 379 | self.assertFalse(self.ui.is_sensitive()) | ||
565 | 380 | |||
566 | 381 | def test_unset_is_processing(self): | ||
567 | 382 | """When unsetting 'is_processing', the spinner is not shown.""" | ||
568 | 383 | self.ui.is_processing = True | ||
569 | 384 | self.ui.is_processing = False | ||
570 | 385 | |||
571 | 386 | self.assertTrue(self.ui.message.get_visible()) | ||
572 | 387 | self.assertFalse(self.ui.message.active) | ||
573 | 388 | self.assertTrue(self.ui.is_sensitive()) | ||
574 | 389 | |||
575 | 365 | 390 | ||
576 | 366 | class OverwiewPanelTestCase(ControlPanelMixinTestCase): | 391 | class OverwiewPanelTestCase(ControlPanelMixinTestCase): |
577 | 367 | """The test suite for the overview panel.""" | 392 | """The test suite for the overview panel.""" |
578 | @@ -716,14 +741,14 @@ | |||
579 | 716 | self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR) | 741 | self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR) |
580 | 717 | 742 | ||
581 | 718 | 743 | ||
584 | 719 | class FoldersTestCase(ControlPanelMixinTestCase): | 744 | class VolumesTestCase(ControlPanelMixinTestCase): |
585 | 720 | """The test suite for the folders panel.""" | 745 | """The test suite for the volumes panel.""" |
586 | 721 | 746 | ||
589 | 722 | klass = gui.FoldersPanel | 747 | klass = gui.VolumesPanel |
590 | 723 | ui_filename = 'folders.ui' | 748 | ui_filename = 'volumes.ui' |
591 | 724 | 749 | ||
592 | 725 | def setUp(self): | 750 | def setUp(self): |
594 | 726 | super(FoldersTestCase, self).setUp() | 751 | super(VolumesTestCase, self).setUp() |
595 | 727 | self.ui.load() | 752 | self.ui.load() |
596 | 728 | 753 | ||
597 | 729 | def test_is_an_ubuntuone_bin(self): | 754 | def test_is_an_ubuntuone_bin(self): |
598 | @@ -744,6 +769,10 @@ | |||
599 | 744 | [self.ui.on_volumes_info_ready]) | 769 | [self.ui.on_volumes_info_ready]) |
600 | 745 | self.assertEqual(self.ui.backend._signals['VolumesInfoError'], | 770 | self.assertEqual(self.ui.backend._signals['VolumesInfoError'], |
601 | 746 | [self.ui.on_volumes_info_error]) | 771 | [self.ui.on_volumes_info_error]) |
602 | 772 | self.assertEqual(self.ui.backend._signals['VolumeSettingsChanged'], | ||
603 | 773 | [self.ui.on_volume_settings_changed]) | ||
604 | 774 | self.assertEqual(self.ui.backend._signals['VolumeSettingsChangeError'], | ||
605 | 775 | [self.ui.on_volume_settings_change_error]) | ||
606 | 747 | 776 | ||
607 | 748 | def test_volumes_info_is_requested_on_load(self): | 777 | def test_volumes_info_is_requested_on_load(self): |
608 | 749 | """The volumes info is requested to the backend.""" | 778 | """The volumes info is requested to the backend.""" |
609 | @@ -753,12 +782,18 @@ | |||
610 | 753 | 782 | ||
611 | 754 | self.assert_backend_called('volumes_info', ()) | 783 | self.assert_backend_called('volumes_info', ()) |
612 | 755 | 784 | ||
615 | 756 | def test_message_after_load(self): | 785 | def test_is_processing_after_load(self): |
616 | 757 | """The volumes label is active when contents are load.""" | 786 | """The ui is processing when contents are load.""" |
617 | 758 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | 787 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
618 | 759 | self.ui.load() | 788 | self.ui.load() |
619 | 760 | 789 | ||
621 | 761 | self.assertTrue(self.ui.message.active) | 790 | self.assertTrue(self.ui.is_processing) |
622 | 791 | |||
623 | 792 | def test_is_not_processing_after_volumes_info_ready(self): | ||
624 | 793 | """The ui is processing when contents are load.""" | ||
625 | 794 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
626 | 795 | |||
627 | 796 | self.assertFalse(self.ui.is_processing) | ||
628 | 762 | 797 | ||
629 | 763 | def test_message_after_non_empty_volumes_info_ready(self): | 798 | def test_message_after_non_empty_volumes_info_ready(self): |
630 | 764 | """The volumes label is a LabelLoading.""" | 799 | """The volumes label is a LabelLoading.""" |
631 | @@ -777,66 +812,67 @@ | |||
632 | 777 | """The volumes info is processed when ready.""" | 812 | """The volumes info is processed when ready.""" |
633 | 778 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | 813 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
634 | 779 | 814 | ||
655 | 780 | self.assertEqual(self.ui.folders.get_children(), [self.ui.volumes]) | 815 | self.assertEqual(len(FAKE_VOLUMES_INFO), len(self.ui.volumes_store)) |
656 | 781 | 816 | treeiter = self.ui.volumes_store.get_iter_root() | |
657 | 782 | volumes = self.ui.volumes.get_children() | 817 | for name, free_bytes, volumes in FAKE_VOLUMES_INFO: |
658 | 783 | volumes.reverse() | 818 | name = "%s's" % name if name else self.ui.MY_FOLDERS |
659 | 784 | 819 | free_bytes = self.ui.humanize(int(free_bytes)) | |
660 | 785 | header = volumes[:2] # grab header | 820 | header = (name, self.ui.FREE_SPACE % {'free_space': free_bytes}) |
661 | 786 | self.assertEqual(header[0].get_text(), 'Local path') | 821 | |
662 | 787 | self.assertEqual(header[1].get_text(), 'Subscribed') | 822 | # check parent row |
663 | 788 | 823 | row = self.ui.volumes_store.get(treeiter, *xrange(7)) | |
664 | 789 | volumes = volumes[2:] # drop header | 824 | |
665 | 790 | labels = filter(lambda w: isinstance(w, gui.gtk.Label), volumes) | 825 | self.assertEqual(row[0], self.ui.ROW_HEADER % header) |
666 | 791 | checks = filter(lambda w: isinstance(w, gui.gtk.CheckButton), volumes) | 826 | self.assertTrue(row[1], 'parent will always be subscribed') |
667 | 792 | 827 | self.assertEqual(row[2], self.ui.CONTACT_ICON_NAME) | |
668 | 793 | self.assertEqual(len(checks), len(FAKE_VOLUMES_INFO)) | 828 | self.assertFalse(row[3], 'no toggle should be shown on parent!') |
669 | 794 | 829 | self.assertFalse(row[4], 'toggle should be non sensitive.') | |
670 | 795 | for label, check, volume in zip(labels, checks, FAKE_VOLUMES_INFO): | 830 | self.assertEqual(row[5], gui.gtk.ICON_SIZE_LARGE_TOOLBAR) |
671 | 796 | self.assertEqual(volume['suggested_path'], label.get_text()) | 831 | self.assertEqual(row[6], None) |
672 | 797 | self.assertEqual(bool(volume['subscribed']), check.get_active()) | 832 | |
673 | 798 | self.assertEqual(volume['volume_id'], check.get_label()) | 833 | # check children |
674 | 799 | self.assertFalse(check.get_child().get_visible()) | 834 | self.assertEqual(len(volumes), |
675 | 835 | self.ui.volumes_store.iter_n_children(treeiter)) | ||
676 | 836 | childiter = self.ui.volumes_store.iter_children(treeiter) | ||
677 | 837 | |||
678 | 838 | sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path')) | ||
679 | 839 | for volume in sorted_vols: | ||
680 | 840 | row = self.ui.volumes_store.get(childiter, *xrange(7)) | ||
681 | 841 | |||
682 | 842 | sensitive = True | ||
683 | 843 | path = volume['path'].replace(USER_HOME + '/', '') | ||
684 | 844 | if volume['type'] == 'ROOT': | ||
685 | 845 | sensitive = False | ||
686 | 846 | path = self.ui.ROOT % (path, gui.ORANGE, | ||
687 | 847 | self.ui.ALWAYS_SUBSCRIBED) | ||
688 | 848 | |||
689 | 849 | self.assertEqual(row[0], path) | ||
690 | 850 | self.assertEqual(row[1], bool(volume['subscribed'])) | ||
691 | 851 | if volume['type'] != 'SHARE': | ||
692 | 852 | self.assertEqual(row[2], self.ui.FOLDER_ICON_NAME) | ||
693 | 853 | else: | ||
694 | 854 | self.assertEqual(row[2], self.ui.SHARE_ICON_NAME) | ||
695 | 855 | self.assertTrue(row[3], 'toggle should be shown on child!') | ||
696 | 856 | self.assertEqual(row[4], sensitive) | ||
697 | 857 | self.assertEqual(row[5], gui.gtk.ICON_SIZE_MENU) | ||
698 | 858 | self.assertEqual(row[6], volume['volume_id']) | ||
699 | 859 | |||
700 | 860 | childiter = self.ui.volumes_store.iter_next(childiter) | ||
701 | 861 | |||
702 | 862 | treeiter = self.ui.volumes_store.iter_next(treeiter) | ||
703 | 800 | 863 | ||
704 | 801 | def test_on_volumes_info_ready_clears_the_list(self): | 864 | def test_on_volumes_info_ready_clears_the_list(self): |
705 | 802 | """The old volumes info is cleared before updated.""" | 865 | """The old volumes info is cleared before updated.""" |
706 | 803 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | 866 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
707 | 804 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | 867 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) |
708 | 805 | 868 | ||
716 | 806 | self.assertEqual(len(self.ui.folders.get_children()), 1) | 869 | self.assertEqual(len(self.ui.volumes_store), len(FAKE_VOLUMES_INFO)) |
710 | 807 | child = self.ui.folders.get_children()[0] | ||
711 | 808 | self.assertEqual(child, self.ui.volumes) | ||
712 | 809 | |||
713 | 810 | volumes = filter(lambda w: isinstance(w, gui.gtk.CheckButton), | ||
714 | 811 | self.ui.volumes.get_children()) | ||
715 | 812 | self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO)) | ||
717 | 813 | 870 | ||
718 | 814 | def test_on_volumes_info_ready_with_no_volumes(self): | 871 | def test_on_volumes_info_ready_with_no_volumes(self): |
719 | 815 | """When there are no volumes, a notification is shown.""" | 872 | """When there are no volumes, a notification is shown.""" |
720 | 816 | self.ui.on_volumes_info_ready([]) | 873 | self.ui.on_volumes_info_ready([]) |
744 | 817 | # no volumes table | 874 | |
745 | 818 | self.assertEqual(len(self.ui.folders.get_children()), 0) | 875 | self.assertEqual(len(self.ui.volumes_store), 0) |
723 | 819 | self.assertTrue(self.ui.volumes is None) | ||
724 | 820 | |||
725 | 821 | def test_on_subscribed_clicked(self): | ||
726 | 822 | """Clicking on 'subscribed' updates the folder subscription.""" | ||
727 | 823 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
728 | 824 | |||
729 | 825 | method = 'change_volume_settings' | ||
730 | 826 | for checkbutton in self.ui._subscribed: | ||
731 | 827 | checkbutton.clicked() | ||
732 | 828 | fid = checkbutton.get_label() | ||
733 | 829 | |||
734 | 830 | subscribed = gui.bool_str(checkbutton.get_active()) | ||
735 | 831 | self.assert_backend_called(method, | ||
736 | 832 | (fid, {'subscribed': subscribed})) | ||
737 | 833 | # clean backend calls | ||
738 | 834 | self.ui.backend._called.pop(method) | ||
739 | 835 | |||
740 | 836 | checkbutton.clicked() | ||
741 | 837 | subscribed = gui.bool_str(checkbutton.get_active()) | ||
742 | 838 | self.assert_backend_called('change_volume_settings', | ||
743 | 839 | (fid, {'subscribed': subscribed})) | ||
746 | 840 | 876 | ||
747 | 841 | def test_on_volumes_info_error(self): | 877 | def test_on_volumes_info_error(self): |
748 | 842 | """The volumes info couldn't be retrieved.""" | 878 | """The volumes info couldn't be retrieved.""" |
749 | @@ -854,6 +890,57 @@ | |||
750 | 854 | self.test_on_volumes_info_error() | 890 | self.test_on_volumes_info_error() |
751 | 855 | self.test_on_volumes_info_ready_with_no_volumes() | 891 | self.test_on_volumes_info_ready_with_no_volumes() |
752 | 856 | 892 | ||
753 | 893 | def test_on_subscribed_toggled(self): | ||
754 | 894 | """Clicking on 'subscribed' updates the folder subscription.""" | ||
755 | 895 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
756 | 896 | |||
757 | 897 | for parent, (_, _, volumes) in enumerate(FAKE_VOLUMES_INFO): | ||
758 | 898 | |||
759 | 899 | sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path')) | ||
760 | 900 | for child, volume in enumerate(sorted_vols): | ||
761 | 901 | if volume['type'] == 'ROOT': | ||
762 | 902 | continue # not editable | ||
763 | 903 | |||
764 | 904 | path = '%s:%s' % (parent, child) | ||
765 | 905 | self.ui.on_subscribed_toggled(widget=None, path=path) | ||
766 | 906 | |||
767 | 907 | fid = volume['volume_id'] | ||
768 | 908 | subscribed = gui.bool_str(not bool(volume['subscribed'])) | ||
769 | 909 | # backend was called | ||
770 | 910 | self.assert_backend_called('change_volume_settings', | ||
771 | 911 | (fid, {'subscribed': subscribed})) | ||
772 | 912 | # store was updated | ||
773 | 913 | it = self.ui.volumes_store.get_iter(path) | ||
774 | 914 | value = self.ui.volumes_store.get_value(it, 1) | ||
775 | 915 | self.assertEqual(value, bool(subscribed)) | ||
776 | 916 | |||
777 | 917 | # the ui is processing | ||
778 | 918 | self.assertTrue(self.ui.is_processing, 'ui must be processing') | ||
779 | 919 | |||
780 | 920 | # simulate success for setting change | ||
781 | 921 | self.ui.on_volume_settings_changed(volume_id=fid) | ||
782 | 922 | |||
783 | 923 | def test_on_volume_setting_changed(self): | ||
784 | 924 | """The setting for a volume was successfully changed.""" | ||
785 | 925 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
786 | 926 | self.ui.on_subscribed_toggled(None, "0:0") | ||
787 | 927 | |||
788 | 928 | self.ui.on_volume_settings_changed(volume_id=None) # id not used | ||
789 | 929 | |||
790 | 930 | # the ui is no longer processing | ||
791 | 931 | self.assertFalse(self.ui.is_processing, 'ui must not be processing') | ||
792 | 932 | |||
793 | 933 | def test_on_volume_setting_change_error(self): | ||
794 | 934 | """The setting for a volume was not successfully changed.""" | ||
795 | 935 | self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO) | ||
796 | 936 | self.ui.on_subscribed_toggled(None, "0:0") | ||
797 | 937 | |||
798 | 938 | self.patch(self.ui, 'load', self._set_called) | ||
799 | 939 | self.ui.on_volume_settings_change_error(volume_id=None, | ||
800 | 940 | error_dict=None) # id not used | ||
801 | 941 | # reload folders list to sanitize the info in volumes_store | ||
802 | 942 | self.assertTrue(self._called, ((), {})) | ||
803 | 943 | |||
804 | 857 | 944 | ||
805 | 858 | class DeviceTestCase(ControlPanelMixinTestCase): | 945 | class DeviceTestCase(ControlPanelMixinTestCase): |
806 | 859 | """The test suite for the device widget.""" | 946 | """The test suite for the device widget.""" |
807 | @@ -2086,11 +2173,11 @@ | |||
808 | 2086 | actual = self.ui.notebook.get_nth_page(self.ui.DASHBOARD_PAGE) | 2173 | actual = self.ui.notebook.get_nth_page(self.ui.DASHBOARD_PAGE) |
809 | 2087 | self.assertTrue(self.ui.dashboard is actual) | 2174 | self.assertTrue(self.ui.dashboard is actual) |
810 | 2088 | 2175 | ||
816 | 2089 | def test_folders_panel_is_packed(self): | 2176 | def test_volumes_panel_is_packed(self): |
817 | 2090 | """The folders panel is packed.""" | 2177 | """The volumes panel is packed.""" |
818 | 2091 | self.assertIsInstance(self.ui.folders, gui.FoldersPanel) | 2178 | self.assertIsInstance(self.ui.volumes, gui.VolumesPanel) |
819 | 2092 | actual = self.ui.notebook.get_nth_page(self.ui.FOLDERS_PAGE) | 2179 | actual = self.ui.notebook.get_nth_page(self.ui.VOLUMES_PAGE) |
820 | 2093 | self.assertTrue(self.ui.folders is actual) | 2180 | self.assertTrue(self.ui.volumes is actual) |
821 | 2094 | 2181 | ||
822 | 2095 | def test_devices_panel_is_packed(self): | 2182 | def test_devices_panel_is_packed(self): |
823 | 2096 | """The devices panel is packed.""" | 2183 | """The devices panel is packed.""" |
824 | @@ -2104,11 +2191,11 @@ | |||
825 | 2104 | actual = self.ui.notebook.get_nth_page(self.ui.SERVICES_PAGE) | 2191 | actual = self.ui.notebook.get_nth_page(self.ui.SERVICES_PAGE) |
826 | 2105 | self.assertTrue(self.ui.services is actual) | 2192 | self.assertTrue(self.ui.services is actual) |
827 | 2106 | 2193 | ||
831 | 2107 | def test_entering_folders_tab_loads_content(self): | 2194 | def test_entering_volumes_tab_loads_content(self): |
832 | 2108 | """The volumes info is loaded when entering the Folders tab.""" | 2195 | """The volumes info is loaded when entering the Volumes tab.""" |
833 | 2109 | self.patch(self.ui.folders, 'load', self._set_called) | 2196 | self.patch(self.ui.volumes, 'load', self._set_called) |
834 | 2110 | # clean backend calls | 2197 | # clean backend calls |
836 | 2111 | self.ui.folders_button.clicked() | 2198 | self.ui.volumes_button.clicked() |
837 | 2112 | 2199 | ||
838 | 2113 | self.assertEqual(self._called, ((), {})) | 2200 | self.assertEqual(self._called, ((), {})) |
839 | 2114 | 2201 | ||
840 | 2115 | 2202 | ||
841 | === modified file 'ubuntuone/controlpanel/gtk/tests/test_widgets.py' | |||
842 | --- ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-18 20:16:18 +0000 | |||
843 | +++ ubuntuone/controlpanel/gtk/tests/test_widgets.py 2011-01-21 19:25:14 +0000 | |||
844 | @@ -156,7 +156,7 @@ | |||
845 | 156 | 156 | ||
846 | 157 | def setUp(self): | 157 | def setUp(self): |
847 | 158 | super(PanelTitleTestCase, self).setUp() | 158 | super(PanelTitleTestCase, self).setUp() |
849 | 159 | self.widget = widgets.PanelTitle(markup=self.TITLE) | 159 | self.widget = widgets.PanelTitle(markup=self.TITLE, change_bg=True) |
850 | 160 | 160 | ||
851 | 161 | win = widgets.gtk.Window() | 161 | win = widgets.gtk.Window() |
852 | 162 | win.add(self.widget) | 162 | win.add(self.widget) |
853 | @@ -196,13 +196,6 @@ | |||
854 | 196 | self.assertEqual(self.widget.label.get_line_wrap_mode(), | 196 | self.assertEqual(self.widget.label.get_line_wrap_mode(), |
855 | 197 | widgets.pango.WRAP_WORD) | 197 | widgets.pango.WRAP_WORD) |
856 | 198 | 198 | ||
857 | 199 | def test_label_size_allocated_is_connected(self): | ||
858 | 200 | """Label have the size-allocate signal connected.""" | ||
859 | 201 | self.widget.label.emit('size-allocate', | ||
860 | 202 | widgets.gtk.gdk.Rectangle(1, 2, 3, 4)) | ||
861 | 203 | self.assertEqual(self.widget.label.get_size_request(), (3 - 2, -1), | ||
862 | 204 | 'Label must have size-allocate connected.') | ||
863 | 205 | |||
864 | 206 | def test_label_padding(self): | 199 | def test_label_padding(self): |
865 | 207 | """The label padding is correct.""" | 200 | """The label padding is correct.""" |
866 | 208 | self.assertEqual(self.widget.label.get_padding(), | 201 | self.assertEqual(self.widget.label.get_padding(), |
867 | 209 | 202 | ||
868 | === modified file 'ubuntuone/controlpanel/gtk/widgets.py' | |||
869 | --- ubuntuone/controlpanel/gtk/widgets.py 2010-12-18 20:16:18 +0000 | |||
870 | +++ ubuntuone/controlpanel/gtk/widgets.py 2011-01-21 19:25:14 +0000 | |||
871 | @@ -108,27 +108,22 @@ | |||
872 | 108 | class PanelTitle(gtk.EventBox): | 108 | class PanelTitle(gtk.EventBox): |
873 | 109 | """A box with a given color and text.""" | 109 | """A box with a given color and text.""" |
874 | 110 | 110 | ||
876 | 111 | def __init__(self, markup='', *args, **kwargs): | 111 | def __init__(self, markup='', change_bg=False, *args, **kwargs): |
877 | 112 | super(PanelTitle, self).__init__(*args, **kwargs) | 112 | super(PanelTitle, self).__init__(*args, **kwargs) |
878 | 113 | self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG)) | ||
879 | 114 | |||
880 | 115 | self.label = gtk.Label() | 113 | self.label = gtk.Label() |
881 | 116 | self.label.set_markup(markup) | 114 | self.label.set_markup(markup) |
882 | 117 | self.label.set_padding(*DEFAULT_PADDING) | 115 | self.label.set_padding(*DEFAULT_PADDING) |
883 | 118 | self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_FG)) | ||
884 | 119 | self.label.set_property('xalign', 0.0) | 116 | self.label.set_property('xalign', 0.0) |
885 | 120 | self.label.set_line_wrap(True) | 117 | self.label.set_line_wrap(True) |
886 | 121 | self.label.set_line_wrap_mode(pango.WRAP_WORD) | 118 | self.label.set_line_wrap_mode(pango.WRAP_WORD) |
888 | 122 | self.label.connect('size-allocate', self.on_size_allocate) | 119 | if change_bg: |
889 | 120 | self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG)) | ||
890 | 121 | self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_FG)) | ||
891 | 123 | 122 | ||
892 | 124 | self.add(self.label) | 123 | self.add(self.label) |
893 | 125 | 124 | ||
894 | 126 | self.show_all() | 125 | self.show_all() |
895 | 127 | 126 | ||
896 | 128 | def on_size_allocate(self, widget, allocation): | ||
897 | 129 | """The widget can re rezised, embrase it!.""" | ||
898 | 130 | widget.set_size_request(allocation.width - 2, -1) | ||
899 | 131 | |||
900 | 132 | 127 | ||
901 | 133 | # Modified from John Stowers' client-side-windows demo. | 128 | # Modified from John Stowers' client-side-windows demo. |
902 | 134 | class GreyableBin(gtk.Bin): | 129 | class GreyableBin(gtk.Bin): |
All in all, this is shaping up to be a huge leap forward from what people will see from our service. GREAT job.
A few comments:
- It confused me for a few minutes to have "X of Y used" on top, and "Z available" under "Mine". I had even taken a screenshot to report it as a bug, until I realised what it was. I wonder if we should unify? Screehshot: http:// ubuntuone. com/p/ZQL/
- How about moving the non-deletable Ubuntu One folder fixed to the top? It'll make it feel a bit more immutable!
- The icon next to the section "Mine" is of multiple people, which is odd, as I'm only one person :) Maybe the multi-person icon could be used for the "From Others" section, and "Mine" gets a single person?
I don't feel any of the above blocks this branch, they are open questions and suggestions that could be incorporated in future branches.