Merge lp:~nataliabidart/ubuntuone-control-panel/volumes-reborn into lp:ubuntuone-control-panel

Proposed by Natalia Bidart
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
Reviewer Review Type Date Requested Status
Roberto Alsina (community) Approve
Martin Albisetti (community) Approve
Review via email: mp+47064@code.launchpad.net

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-control-panel-backend; DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-backend

DEBUG=True PYTHONPATH=. ./bin/ubuntuone-control-panel-gtk

And play with the 'Cloud Storage' panel. Be careful that folder subcription/unsubscription is "real" :-)

To post a comment you must log in.
Revision history for this message
Martin Albisetti (beuno) wrote :

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.

review: Approve
Revision history for this message
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 :-)

review: Approve
57. By Natalia Bidart

Root folder is on top of others.

Revision history for this message
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://ubuntuone.com/p/ZQL/

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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/ubuntuone-control-panel-gtk'
--- bin/ubuntuone-control-panel-gtk 2011-01-20 21:27:21 +0000
+++ bin/ubuntuone-control-panel-gtk 2011-01-21 19:25:14 +0000
@@ -34,13 +34,13 @@
34def parser_options():34def parser_options():
35 """Parse command line parameters."""35 """Parse command line parameters."""
36 usage = "Usage: %prog [option]"36 usage = "Usage: %prog [option]"
37 parser = OptionParser(usage=usage)37 result = OptionParser(usage=usage)
38 parser.add_option("", "--switch-to", dest="switch_to", type="string",38 result.add_option("", "--switch-to", dest="switch_to", type="string",
39 metavar="PANEL_NAME",39 metavar="PANEL_NAME",
40 help="Start the Ubuntu One Control Panel (GTK) in the "40 help="Start the Ubuntu One Control Panel (GTK) in the "
41 "PANEL_NAME tab. Possible values are: "41 "PANEL_NAME tab. Possible values are: "
42 "dashboard, folders, devices, applications")42 "dashboard, volumes, devices, applications")
43 return parser43 return result
4444
4545
46if __name__ == "__main__":46if __name__ == "__main__":
4747
=== modified file 'data/management.ui'
--- data/management.ui 2010-12-20 22:18:01 +0000
+++ data/management.ui 2011-01-21 19:25:14 +0000
@@ -79,8 +79,8 @@
79 </packing>79 </packing>
80 </child>80 </child>
81 <child>81 <child>
82 <object class="GtkRadioButton" id="folders_button">82 <object class="GtkRadioButton" id="volumes_button">
83 <property name="label" translatable="yes">Folders</property>83 <property name="label" translatable="yes">Cloud Storage</property>
84 <property name="visible">True</property>84 <property name="visible">True</property>
85 <property name="can_focus">True</property>85 <property name="can_focus">True</property>
86 <property name="receives_default">False</property>86 <property name="receives_default">False</property>
8787
=== renamed file 'data/folders.ui' => 'data/volumes.ui'
--- data/folders.ui 2010-12-18 20:16:18 +0000
+++ data/volumes.ui 2011-01-21 19:25:14 +0000
@@ -1,11 +1,10 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<interface>2<interface>
3 <requires lib="gtk+" version="2.16"/>3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkVBox" id="itself">5 <object class="GtkAlignment" id="itself">
6 <property name="visible">True</property>6 <property name="visible">True</property>
7 <property name="border_width">10</property>7 <property name="can_focus">False</property>
8 <property name="spacing">10</property>
9 <child>8 <child>
10 <object class="GtkScrolledWindow" id="scrolledwindow1">9 <object class="GtkScrolledWindow" id="scrolledwindow1">
11 <property name="visible">True</property>10 <property name="visible">True</property>
@@ -13,26 +12,83 @@
13 <property name="hscrollbar_policy">automatic</property>12 <property name="hscrollbar_policy">automatic</property>
14 <property name="vscrollbar_policy">automatic</property>13 <property name="vscrollbar_policy">automatic</property>
15 <child>14 <child>
16 <object class="GtkViewport" id="viewport1">15 <object class="GtkTreeView" id="volumes_view">
17 <property name="visible">True</property>16 <property name="visible">True</property>
18 <property name="resize_mode">queue</property>17 <property name="can_focus">True</property>
19 <property name="shadow_type">none</property>18 <property name="model">volumes_store</property>
20 <child>19 <property name="rules_hint">True</property>
21 <object class="GtkAlignment" id="folders">20 <property name="tooltip_column">0</property>
22 <property name="visible">True</property>21 <child>
23 <property name="xscale">0</property>22 <object class="GtkTreeViewColumn" id="treeviewcolumn2">
24 <property name="yscale">0</property>23 <property name="resizable">True</property>
25 <child>24 <property name="sizing">autosize</property>
26 <placeholder/>25 <property name="expand">True</property>
26 <child>
27 <object class="GtkCellRendererPixbuf" id="cellrendererpixbuf1"/>
28 <attributes>
29 <attribute name="sensitive">1</attribute>
30 <attribute name="icon-name">2</attribute>
31 <attribute name="stock-size">5</attribute>
32 </attributes>
33 </child>
34 <child>
35 <object class="GtkCellRendererText" id="text_renderer">
36 <property name="ellipsize">end</property>
37 <property name="width_chars">80</property>
38 </object>
39 <attributes>
40 <attribute name="markup">0</attribute>
41 <attribute name="text">0</attribute>
42 </attributes>
43 </child>
44 </object>
45 </child>
46 <child>
47 <object class="GtkTreeViewColumn" id="treeviewcolumn3">
48 <property name="sizing">autosize</property>
49 <property name="title">On this device?</property>
50 <child>
51 <object class="GtkCellRendererToggle" id="cellrenderertoggle1">
52 <property name="indicator_size">15</property>
53 <signal name="toggled" handler="on_subscribed_toggled" swapped="no"/>
54 </object>
55 <attributes>
56 <attribute name="sensitive">4</attribute>
57 <attribute name="visible">3</attribute>
58 <attribute name="active">1</attribute>
59 </attributes>
60 </child>
61 <child>
62 <object class="GtkCellRendererText" id="cellrenderertext1">
63 <property name="visible">False</property>
64 </object>
65 <attributes>
66 <attribute name="text">6</attribute>
67 </attributes>
27 </child>68 </child>
28 </object>69 </object>
29 </child>70 </child>
30 </object>71 </object>
31 </child>72 </child>
32 </object>73 </object>
33 <packing>
34 <property name="position">0</property>
35 </packing>
36 </child>74 </child>
37 </object>75 </object>
76 <object class="GtkTreeStore" id="volumes_store">
77 <columns>
78 <!-- column-name description -->
79 <column type="gchararray"/>
80 <!-- column-name subscribed -->
81 <column type="gboolean"/>
82 <!-- column-name icon-name -->
83 <column type="gchararray"/>
84 <!-- column-name subscribed-visible -->
85 <column type="gboolean"/>
86 <!-- column-name subscribed-sensitive -->
87 <column type="gboolean"/>
88 <!-- column-name icon-size -->
89 <column type="gint"/>
90 <!-- column-name identifier -->
91 <column type="gchararray"/>
92 </columns>
93 </object>
38</interface>94</interface>
3995
=== modified file 'ubuntuone/controlpanel/gtk/gui.py'
--- ubuntuone/controlpanel/gtk/gui.py 2011-01-20 21:23:11 +0000
+++ ubuntuone/controlpanel/gtk/gui.py 2011-01-21 19:25:14 +0000
@@ -22,6 +22,7 @@
2222
23import gettext23import gettext
24import operator24import operator
25import os
2526
26from functools import wraps27from functools import wraps
2728
@@ -101,6 +102,11 @@
101 return filter_by_app_name_inner102 return filter_by_app_name_inner
102103
103104
105def on_size_allocate(widget, allocation, label):
106 """Resize labels according to who 'widget' is being resized."""
107 label.set_size_request(allocation.width - 2, -1)
108
109
104@log_call(logger.info)110@log_call(logger.info)
105def uri_hook(button, uri, *args, **kwargs):111def uri_hook(button, uri, *args, **kwargs):
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."""
@@ -159,10 +165,6 @@
159 label.set_markup(WARNING_MARKUP % message)165 label.set_markup(WARNING_MARKUP % message)
160 label.show()166 label.show()
161167
162 def on_size_allocate(self, label, allocation):
163 """The label can re rezised, embrase it!."""
164 label.set_size_request(allocation.width - 2, -1)
165
166168
167class ControlPanelWindow(gtk.Window):169class ControlPanelWindow(gtk.Window):
168 """The main window for the Ubuntu One control panel."""170 """The main window for the Ubuntu One control panel."""
@@ -241,8 +243,8 @@
241 if self.get_current_page() == 0:243 if self.get_current_page() == 0:
242 self.management.load()244 self.management.load()
243 if credentials_are_new:245 if credentials_are_new:
244 # redirect user to Folders page to review folders subscription246 # redirect user to volumes page to review subscription
245 self.management.folders_button.clicked()247 self.management.volumes_button.clicked()
246248
247 self.next_page()249 self.next_page()
248250
@@ -254,17 +256,38 @@
254256
255 def __init__(self, title=None):257 def __init__(self, title=None):
256 gtk.VBox.__init__(self)258 gtk.VBox.__init__(self)
259 self._is_processing = False
260
257 if title is None:261 if title is None:
258 title = self.TITLE262 title = self.TITLE
259263
260 self.title = PanelTitle(markup='<b>' + title + '</b>')264 title = '<span font_size="large">%s</span>' % title
265 self.title = PanelTitle(markup=title)
261 self.pack_start(self.title, expand=False)266 self.pack_start(self.title, expand=False)
262267
263 self.message = LabelLoading(LOADING)268 self.message = LabelLoading(LOADING)
264 self.pack_start(self.message, expand=False)269 self.pack_start(self.message, expand=False)
265270
271 self.connect('size-allocate', on_size_allocate, self.title.label)
266 self.show_all()272 self.show_all()
267273
274 def _get_is_processing(self):
275 """Is this panel processing a request?"""
276 return self._is_processing
277
278 def _set_is_processing(self, new_value):
279 """Set if this panel is processing a request."""
280 if new_value:
281 self.message.start()
282 self.set_sensitive(False)
283 else:
284 self.message.stop()
285 self.set_sensitive(True)
286
287 self._is_processing = new_value
288
289 is_processing = property(fget=_get_is_processing, fset=_set_is_processing)
290
268 @log_call(logger.debug)291 @log_call(logger.debug)
269 def on_success(self, message=''):292 def on_success(self, message=''):
270 """Use this callback to stop the Loading and show 'message'."""293 """Use this callback to stop the Loading and show 'message'."""
@@ -302,7 +325,7 @@
302 self.add(self.itself)325 self.add(self.itself)
303 self.warning_label.set_text('')326 self.warning_label.set_text('')
304 self.warning_label.set_property('xalign', 0.5)327 self.warning_label.set_property('xalign', 0.5)
305 self.warning_label.connect('size-allocate', self.on_size_allocate)328 self.connect('size-allocate', on_size_allocate, self.warning_label)
306329
307 self.connect_button.set_uri(self.CONNECT)330 self.connect_button.set_uri(self.CONNECT)
308331
@@ -348,7 +371,7 @@
348 label.set_markup(self.BULLET + ' ' + msg)371 label.set_markup(self.BULLET + ' ' + msg)
349 label.set_property('xalign', 0)372 label.set_property('xalign', 0)
350 label.set_property('wrap', True)373 label.set_property('wrap', True)
351 label.connect('size-allocate', self.on_size_allocate)374 self.messages.connect('size-allocate', on_size_allocate, label)
352375
353 self.messages.pack_start(label)376 self.messages.pack_start(label)
354 self.messages.show_all()377 self.messages.show_all()
@@ -478,16 +501,25 @@
478 self.on_error()501 self.on_error()
479502
480503
481class FoldersPanel(UbuntuOneBin, ControlPanelMixin):504class VolumesPanel(UbuntuOneBin, ControlPanelMixin):
482 """The folders panel."""505 """The volumes panel."""
483506
484 TITLE = _('Listed below are the folders available on this machine. '507 TITLE = _('Select which folders from your cloud you want synchronized '
485 'Subscribed means the folder will receive and send updates.')508 'on this device.')
509 MY_FOLDERS = _('Mine')
510 ALWAYS_SUBSCRIBED = _('Always in your personal cloud storage!')
511 FREE_SPACE = _('%(free_space)s available storage')
512 CONTACT_ICON_NAME = 'system-users'
513 FOLDER_ICON_NAME = 'folder'
514 SHARE_ICON_NAME = 'folder-remote'
515 ROW_HEADER = '<span font_size="large"><b>%s</b></span> ' \
516 '<span foreground="grey">%s</span>'
517 ROOT = '%s - <span foreground="%s" font_size="small">%s</span>'
486 NO_VOLUMES = _('No folders to show.')518 NO_VOLUMES = _('No folders to show.')
487519
488 def __init__(self):520 def __init__(self):
489 UbuntuOneBin.__init__(self)521 UbuntuOneBin.__init__(self)
490 ControlPanelMixin.__init__(self, filename='folders.ui')522 ControlPanelMixin.__init__(self, filename='volumes.ui')
491 self.add(self.itself)523 self.add(self.itself)
492 self.show_all()524 self.show_all()
493525
@@ -495,66 +527,102 @@
495 self.on_volumes_info_ready)527 self.on_volumes_info_ready)
496 self.backend.connect_to_signal('VolumesInfoError',528 self.backend.connect_to_signal('VolumesInfoError',
497 self.on_volumes_info_error)529 self.on_volumes_info_error)
498 self.volumes = None530 self.backend.connect_to_signal('VolumeSettingsChanged',
499 self._subscribed = []531 self.on_volume_settings_changed)
532 self.backend.connect_to_signal('VolumeSettingsChangeError',
533 self.on_volume_settings_change_error)
534
535 def _process_path(self, path):
536 """Trim 'path' so the '~' is removed."""
537 home = os.path.expanduser('~')
538 return path.replace(os.path.join(home, ''), '')
500539
501 def on_volumes_info_ready(self, info):540 def on_volumes_info_ready(self, info):
502 """Backend notifies of volumes info."""541 """Backend notifies of volumes info."""
503542
504 if self.volumes is not None:543 self.volumes_store.clear()
505 self.folders.remove(self.volumes)
506 self.volumes = None
507
508 if not info:544 if not info:
509 self.on_success(self.NO_VOLUMES)545 self.on_success(self.NO_VOLUMES)
510 return546 return
511 else:547 else:
512 self.on_success()548 self.on_success()
513549
514 self.volumes = gtk.Table(rows=len(info) + 1, columns=2)550 # pylint: disable=W0612
515551 # name, subscribed, icon name, show toggle, sensitive, icon size, id
516 header = (gtk.Label(), gtk.Label())552 empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU, None)
517 header[0].set_markup('<b>' + _('Local path') + '</b>')553
518 header[1].set_markup('<b>' + _('Subscribed') + '</b>')554 for name, free_bytes, volumes in info:
519 self.volumes.attach(header[0], 0, 1, 0, 1)555 if name:
520 self.volumes.attach(header[1], 1, 2, 0, 1, xoptions=0)556 name = name + "'s"
521 self.volumes.show_all()557 icon_name = self.SHARE_ICON_NAME
522558 else:
523 info.sort(key=operator.itemgetter('suggested_path'))559 name = self.MY_FOLDERS
524 for i, volume in enumerate(info):560 icon_name = self.FOLDER_ICON_NAME
525 path = gtk.Label(volume['suggested_path'])561
526 path.set_property('xalign', 0)562 free_bytes_args = {'free_space': self.humanize(int(free_bytes))}
527 path.show()563 row = (self.ROW_HEADER % (name, self.FREE_SPACE % free_bytes_args),
528 self.volumes.attach(path, 0, 1, i + 1, i + 2)564 True, self.CONTACT_ICON_NAME, False, False,
529565 gtk.ICON_SIZE_LARGE_TOOLBAR, None)
530 subscribed = gtk.CheckButton(volume['volume_id'])566 treeiter = self.volumes_store.append(None, row)
531 subscribed.set_active(bool(volume['subscribed']))567
532 subscribed.show()568 volumes.sort(key=operator.itemgetter('path'))
533 subscribed.get_child().hide()569 for volume in volumes:
534 subscribed.connect('clicked', self.on_subscribed_clicked)570 sensitive = True
535 self._subscribed.append(subscribed)571 path = self._process_path(volume['path'])
536 self.volumes.attach(subscribed, 1, 2, i + 1, i + 2, xoptions=0)572 if volume['type'] == u'ROOT':
537573 sensitive = False
538 self.folders.add(self.volumes)574 path = self.ROOT % (path, ORANGE, self.ALWAYS_SUBSCRIBED)
575
576 row = (path, bool(volume['subscribed']), icon_name, True,
577 sensitive, gtk.ICON_SIZE_MENU, volume['volume_id'])
578
579 if volume['type'] == u'ROOT': # root should go first!
580 self.volumes_store.prepend(treeiter, row)
581 else:
582 self.volumes_store.append(treeiter, row)
583
584 # When we display shares info, we'll need to smartly add
585 # an empty row to the tree view to separate volume groups
586 #treeiter = self.volumes_store.append(None, empty_row)
587
588 self.volumes_view.expand_row(0, True)
589 self.volumes_view.show_all()
590 self.is_processing = False
539591
540 @log_call(logger.error)592 @log_call(logger.error)
541 def on_volumes_info_error(self, error_dict=None):593 def on_volumes_info_error(self, error_dict=None):
542 """Backend notifies of an error when fetching volumes info."""594 """Backend notifies of an error when fetching volumes info."""
543 self.on_error()595 self.on_error()
544596
545 def on_subscribed_clicked(self, checkbutton):597 @log_call(logger.info)
546 """The user toggled 'checkbutton'."""598 def on_volume_settings_changed(self, volume_id):
547 volume_id = checkbutton.get_label()599 """The settings for 'volume_id' were changed."""
548 subscribed = bool_str(checkbutton.get_active())600 self.is_processing = False
601
602 @log_call(logger.error)
603 def on_volume_settings_change_error(self, volume_id, error_dict=None):
604 """The settings for 'volume_id' were not changed."""
605 self.load()
606
607 def on_subscribed_toggled(self, widget, path, *args, **kwargs):
608 """The user toggled 'widget'."""
609 treeiter = self.volumes_store.get_iter(path)
610 volume_id = self.volumes_store.get_value(treeiter, 6)
611 subscribed = not self.volumes_store.get_value(treeiter, 1)
612
613 self.volumes_store.set_value(treeiter, 1, subscribed)
614
549 self.backend.change_volume_settings(volume_id,615 self.backend.change_volume_settings(volume_id,
550 {'subscribed': subscribed},616 {'subscribed': bool_str(subscribed)},
551 reply_handler=NO_OP, error_handler=error_handler)617 reply_handler=NO_OP, error_handler=error_handler)
552618
619 self.is_processing = True
620
553 def load(self):621 def load(self):
554 """Load the volume list."""622 """Load the volume list."""
555 self.backend.volumes_info(reply_handler=NO_OP,623 self.backend.volumes_info(reply_handler=NO_OP,
556 error_handler=error_handler)624 error_handler=error_handler)
557 self.message.start()625 self.is_processing = True
558626
559627
560class Device(gtk.VBox, ControlPanelMixin):628class Device(gtk.VBox, ControlPanelMixin):
@@ -1228,7 +1296,7 @@
1228class ManagementPanel(gtk.VBox, ControlPanelMixin):1296class ManagementPanel(gtk.VBox, ControlPanelMixin):
1229 """The management panel.1297 """The management panel.
12301298
1231 The user can manage dashboard, folders, devices and services.1299 The user can manage dashboard, volumes, devices and services.
12321300
1233 """1301 """
12341302
@@ -1255,12 +1323,12 @@
1255 self.status_box.pack_end(self.status_label, expand=False)1323 self.status_box.pack_end(self.status_label, expand=False)
12561324
1257 self.dashboard = DashboardPanel()1325 self.dashboard = DashboardPanel()
1258 self.folders = FoldersPanel()1326 self.volumes = VolumesPanel()
1259 self.devices = DevicesPanel()1327 self.devices = DevicesPanel()
1260 self.services = ServicesPanel()1328 self.services = ServicesPanel()
12611329
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)
1263 self.tabs = (u'dashboard', u'folders', u'devices', u'services')1331 self.tabs = (u'dashboard', u'volumes', u'devices', u'services')
1264 for page_num, tab in enumerate(self.tabs):1332 for page_num, tab in enumerate(self.tabs):
1265 setattr(self, ('%s_page' % tab).upper(), page_num)1333 setattr(self, ('%s_page' % tab).upper(), page_num)
1266 button = getattr(self, '%s_button' % tab)1334 button = getattr(self, '%s_button' % tab)
@@ -1270,7 +1338,7 @@
1270 gtk.gdk.Color(DEFAULT_FG))1338 gtk.gdk.Color(DEFAULT_FG))
1271 self.notebook.insert_page(getattr(self, tab), position=page_num)1339 self.notebook.insert_page(getattr(self, tab), position=page_num)
12721340
1273 self.folders_button.connect('clicked', lambda b: self.folders.load())1341 self.volumes_button.connect('clicked', lambda b: self.volumes.load())
1274 self.devices_button.connect('clicked', lambda b: self.devices.load())1342 self.devices_button.connect('clicked', lambda b: self.devices.load())
1275 self.services_button.connect('clicked', lambda b: self.services.load())1343 self.services_button.connect('clicked', lambda b: self.services.load())
1276 self.devices.connect('local-device-removed',1344 self.devices.connect('local-device-removed',
12771345
=== modified file 'ubuntuone/controlpanel/gtk/tests/__init__.py'
--- ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-10 02:26:22 +0000
+++ ubuntuone/controlpanel/gtk/tests/__init__.py 2011-01-21 19:25:14 +0000
@@ -28,10 +28,35 @@
28FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me',28FAKE_ACCOUNT_INFO = {'type': 'Payed', 'name': 'Test me',
29 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}29 'email': 'test.com', 'quota_total': '12345', 'quota_used': '9999'}
3030
31USER_HOME = '/home/tester'
32
33ROOT = {
34 u'volume_id': '', u'path': '/home/tester/My Ubuntu',
35 u'subscribed': 'True', u'type': u'ROOT',
36}
37
38FAKE_FOLDERS_INFO = [
39 {u'volume_id': u'0', u'path': u'/home/tester/foo',
40 u'suggested_path': u'~/foo', u'subscribed': u'', u'type': u'UDF'},
41 {u'volume_id': u'1', u'path': u'/home/tester/bar',
42 u'suggested_path': u'~/bar', u'subscribed': u'True', u'type': u'UDF'},
43 {u'volume_id': u'2', u'path': u'/home/tester/baz',
44 u'suggested_path': u'~/baz', u'subscribed': u'True', u'type': u'UDF'},
45]
46
47FAKE_SHARES_INFO = [
48 {u'volume_id': u'1234',
49 u'path': u'/home/tester/.local/share/ubuntuone/shares/do from Other User',
50 u'subscribed': u'', u'type': u'SHARE'},
51
52 {u'volume_id': u'5678',
53 u'path': u'/home/tester/.local/share/ubuntuone/shares/re from Other User',
54 u'subscribed': u'True', u'type': u'SHARE'},
55]
56
31FAKE_VOLUMES_INFO = [57FAKE_VOLUMES_INFO = [
32 {'volume_id': '0', 'suggested_path': '~/foo', 'subscribed': ''},58 (u'', u'147852369', [ROOT] + FAKE_FOLDERS_INFO),
33 {'volume_id': '1', 'suggested_path': '~/bar', 'subscribed': 'True'},59 (u'Other User', u'985674', FAKE_SHARES_INFO),
34 {'volume_id': '2', 'suggested_path': '~/baz', 'subscribed': 'True'},
35]60]
3661
37FAKE_DEVICE_INFO = {62FAKE_DEVICE_INFO = {
3863
=== modified file 'ubuntuone/controlpanel/gtk/tests/test_gui.py'
--- ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-20 21:23:11 +0000
+++ ubuntuone/controlpanel/gtk/tests/test_gui.py 2011-01-21 19:25:14 +0000
@@ -27,7 +27,7 @@
27from ubuntuone.controlpanel.gtk import gui27from ubuntuone.controlpanel.gtk import gui
28from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,28from ubuntuone.controlpanel.gtk.tests import (FAKE_ACCOUNT_INFO,
29 FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,29 FAKE_DEVICE_INFO, FAKE_DEVICES_INFO,
30 FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO,30 FAKE_VOLUMES_INFO, FAKE_REPLICATIONS_INFO, USER_HOME,
31 FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,31 FakedNMState, FakedSSOBackend, FakedSessionBus, FakedInterface,
32 FakedPackageManager,32 FakedPackageManager,
33)33)
@@ -52,6 +52,8 @@
5252
53 def setUp(self):53 def setUp(self):
54 super(BaseTestCase, self).setUp()54 super(BaseTestCase, self).setUp()
55 self.patch(gui.os.path, 'expanduser',
56 lambda path: path.replace('~', USER_HOME))
55 self.patch(gui.gtk, 'main', lambda: None)57 self.patch(gui.gtk, 'main', lambda: None)
56 self.patch(gui.dbus, 'SessionBus', FakedSessionBus)58 self.patch(gui.dbus, 'SessionBus', FakedSessionBus)
57 self.patch(gui.dbus, 'Interface', FakedInterface)59 self.patch(gui.dbus, 'Interface', FakedInterface)
@@ -279,10 +281,10 @@
279 self.ui.management.DASHBOARD_PAGE)281 self.ui.management.DASHBOARD_PAGE)
280 self.assertEqual(self._called, ((), {}))282 self.assertEqual(self._called, ((), {}))
281283
282 def test_credentials_found_shows_folders_management_panel(self):284 def test_credentials_found_shows_volumes_management_panel(self):
283 """On 'credentials-found' signal, the management panel is shown.285 """On 'credentials-found' signal, the management panel is shown.
284286
285 If first signal parameter is True, visible tab should be folders.287 If first signal parameter is True, visible tab should be volumes.
286288
287 """289 """
288 a_token = object()290 a_token = object()
@@ -290,7 +292,7 @@
290292
291 self.assert_current_tab_correct(self.ui.management)293 self.assert_current_tab_correct(self.ui.management)
292 self.assertEqual(self.ui.management.notebook.get_current_page(),294 self.assertEqual(self.ui.management.notebook.get_current_page(),
293 self.ui.management.FOLDERS_PAGE)295 self.ui.management.VOLUMES_PAGE)
294296
295 def test_local_device_removed_shows_overview_panel(self):297 def test_local_device_removed_shows_overview_panel(self):
296 """On 'local-device-removed' signal, the overview panel is shown."""298 """On 'local-device-removed' signal, the overview panel is shown."""
@@ -362,6 +364,29 @@
362 self.assert_warning_correct(self.ui.message, msg)364 self.assert_warning_correct(self.ui.message, msg)
363 self.assertFalse(self.ui.message.active)365 self.assertFalse(self.ui.message.active)
364366
367 def test_is_processing(self):
368 """The flag 'is_processing' is False on start."""
369 self.assertFalse(self.ui.is_processing)
370 self.assertTrue(self.ui.is_sensitive())
371
372 def test_set_is_processing(self):
373 """When setting 'is_processing', the spinner is shown."""
374 self.ui.is_processing = False
375 self.ui.is_processing = True
376
377 self.assertTrue(self.ui.message.get_visible())
378 self.assertTrue(self.ui.message.active)
379 self.assertFalse(self.ui.is_sensitive())
380
381 def test_unset_is_processing(self):
382 """When unsetting 'is_processing', the spinner is not shown."""
383 self.ui.is_processing = True
384 self.ui.is_processing = False
385
386 self.assertTrue(self.ui.message.get_visible())
387 self.assertFalse(self.ui.message.active)
388 self.assertTrue(self.ui.is_sensitive())
389
365390
366class OverwiewPanelTestCase(ControlPanelMixinTestCase):391class OverwiewPanelTestCase(ControlPanelMixinTestCase):
367 """The test suite for the overview panel."""392 """The test suite for the overview panel."""
@@ -716,14 +741,14 @@
716 self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)741 self.assert_warning_correct(self.ui.message, gui.VALUE_ERROR)
717742
718743
719class FoldersTestCase(ControlPanelMixinTestCase):744class VolumesTestCase(ControlPanelMixinTestCase):
720 """The test suite for the folders panel."""745 """The test suite for the volumes panel."""
721746
722 klass = gui.FoldersPanel747 klass = gui.VolumesPanel
723 ui_filename = 'folders.ui'748 ui_filename = 'volumes.ui'
724749
725 def setUp(self):750 def setUp(self):
726 super(FoldersTestCase, self).setUp()751 super(VolumesTestCase, self).setUp()
727 self.ui.load()752 self.ui.load()
728753
729 def test_is_an_ubuntuone_bin(self):754 def test_is_an_ubuntuone_bin(self):
@@ -744,6 +769,10 @@
744 [self.ui.on_volumes_info_ready])769 [self.ui.on_volumes_info_ready])
745 self.assertEqual(self.ui.backend._signals['VolumesInfoError'],770 self.assertEqual(self.ui.backend._signals['VolumesInfoError'],
746 [self.ui.on_volumes_info_error])771 [self.ui.on_volumes_info_error])
772 self.assertEqual(self.ui.backend._signals['VolumeSettingsChanged'],
773 [self.ui.on_volume_settings_changed])
774 self.assertEqual(self.ui.backend._signals['VolumeSettingsChangeError'],
775 [self.ui.on_volume_settings_change_error])
747776
748 def test_volumes_info_is_requested_on_load(self):777 def test_volumes_info_is_requested_on_load(self):
749 """The volumes info is requested to the backend."""778 """The volumes info is requested to the backend."""
@@ -753,12 +782,18 @@
753782
754 self.assert_backend_called('volumes_info', ())783 self.assert_backend_called('volumes_info', ())
755784
756 def test_message_after_load(self):785 def test_is_processing_after_load(self):
757 """The volumes label is active when contents are load."""786 """The ui is processing when contents are load."""
758 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)787 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
759 self.ui.load()788 self.ui.load()
760789
761 self.assertTrue(self.ui.message.active)790 self.assertTrue(self.ui.is_processing)
791
792 def test_is_not_processing_after_volumes_info_ready(self):
793 """The ui is processing when contents are load."""
794 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
795
796 self.assertFalse(self.ui.is_processing)
762797
763 def test_message_after_non_empty_volumes_info_ready(self):798 def test_message_after_non_empty_volumes_info_ready(self):
764 """The volumes label is a LabelLoading."""799 """The volumes label is a LabelLoading."""
@@ -777,66 +812,67 @@
777 """The volumes info is processed when ready."""812 """The volumes info is processed when ready."""
778 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)813 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
779814
780 self.assertEqual(self.ui.folders.get_children(), [self.ui.volumes])815 self.assertEqual(len(FAKE_VOLUMES_INFO), len(self.ui.volumes_store))
781816 treeiter = self.ui.volumes_store.get_iter_root()
782 volumes = self.ui.volumes.get_children()817 for name, free_bytes, volumes in FAKE_VOLUMES_INFO:
783 volumes.reverse()818 name = "%s's" % name if name else self.ui.MY_FOLDERS
784819 free_bytes = self.ui.humanize(int(free_bytes))
785 header = volumes[:2] # grab header820 header = (name, self.ui.FREE_SPACE % {'free_space': free_bytes})
786 self.assertEqual(header[0].get_text(), 'Local path')821
787 self.assertEqual(header[1].get_text(), 'Subscribed')822 # check parent row
788823 row = self.ui.volumes_store.get(treeiter, *xrange(7))
789 volumes = volumes[2:] # drop header824
790 labels = filter(lambda w: isinstance(w, gui.gtk.Label), volumes)825 self.assertEqual(row[0], self.ui.ROW_HEADER % header)
791 checks = filter(lambda w: isinstance(w, gui.gtk.CheckButton), volumes)826 self.assertTrue(row[1], 'parent will always be subscribed')
792827 self.assertEqual(row[2], self.ui.CONTACT_ICON_NAME)
793 self.assertEqual(len(checks), len(FAKE_VOLUMES_INFO))828 self.assertFalse(row[3], 'no toggle should be shown on parent!')
794829 self.assertFalse(row[4], 'toggle should be non sensitive.')
795 for label, check, volume in zip(labels, checks, FAKE_VOLUMES_INFO):830 self.assertEqual(row[5], gui.gtk.ICON_SIZE_LARGE_TOOLBAR)
796 self.assertEqual(volume['suggested_path'], label.get_text())831 self.assertEqual(row[6], None)
797 self.assertEqual(bool(volume['subscribed']), check.get_active())832
798 self.assertEqual(volume['volume_id'], check.get_label())833 # check children
799 self.assertFalse(check.get_child().get_visible())834 self.assertEqual(len(volumes),
835 self.ui.volumes_store.iter_n_children(treeiter))
836 childiter = self.ui.volumes_store.iter_children(treeiter)
837
838 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))
839 for volume in sorted_vols:
840 row = self.ui.volumes_store.get(childiter, *xrange(7))
841
842 sensitive = True
843 path = volume['path'].replace(USER_HOME + '/', '')
844 if volume['type'] == 'ROOT':
845 sensitive = False
846 path = self.ui.ROOT % (path, gui.ORANGE,
847 self.ui.ALWAYS_SUBSCRIBED)
848
849 self.assertEqual(row[0], path)
850 self.assertEqual(row[1], bool(volume['subscribed']))
851 if volume['type'] != 'SHARE':
852 self.assertEqual(row[2], self.ui.FOLDER_ICON_NAME)
853 else:
854 self.assertEqual(row[2], self.ui.SHARE_ICON_NAME)
855 self.assertTrue(row[3], 'toggle should be shown on child!')
856 self.assertEqual(row[4], sensitive)
857 self.assertEqual(row[5], gui.gtk.ICON_SIZE_MENU)
858 self.assertEqual(row[6], volume['volume_id'])
859
860 childiter = self.ui.volumes_store.iter_next(childiter)
861
862 treeiter = self.ui.volumes_store.iter_next(treeiter)
800863
801 def test_on_volumes_info_ready_clears_the_list(self):864 def test_on_volumes_info_ready_clears_the_list(self):
802 """The old volumes info is cleared before updated."""865 """The old volumes info is cleared before updated."""
803 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)866 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
804 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)867 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
805868
806 self.assertEqual(len(self.ui.folders.get_children()), 1)869 self.assertEqual(len(self.ui.volumes_store), len(FAKE_VOLUMES_INFO))
807 child = self.ui.folders.get_children()[0]
808 self.assertEqual(child, self.ui.volumes)
809
810 volumes = filter(lambda w: isinstance(w, gui.gtk.CheckButton),
811 self.ui.volumes.get_children())
812 self.assertEqual(len(volumes), len(FAKE_VOLUMES_INFO))
813870
814 def test_on_volumes_info_ready_with_no_volumes(self):871 def test_on_volumes_info_ready_with_no_volumes(self):
815 """When there are no volumes, a notification is shown."""872 """When there are no volumes, a notification is shown."""
816 self.ui.on_volumes_info_ready([])873 self.ui.on_volumes_info_ready([])
817 # no volumes table874
818 self.assertEqual(len(self.ui.folders.get_children()), 0)875 self.assertEqual(len(self.ui.volumes_store), 0)
819 self.assertTrue(self.ui.volumes is None)
820
821 def test_on_subscribed_clicked(self):
822 """Clicking on 'subscribed' updates the folder subscription."""
823 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
824
825 method = 'change_volume_settings'
826 for checkbutton in self.ui._subscribed:
827 checkbutton.clicked()
828 fid = checkbutton.get_label()
829
830 subscribed = gui.bool_str(checkbutton.get_active())
831 self.assert_backend_called(method,
832 (fid, {'subscribed': subscribed}))
833 # clean backend calls
834 self.ui.backend._called.pop(method)
835
836 checkbutton.clicked()
837 subscribed = gui.bool_str(checkbutton.get_active())
838 self.assert_backend_called('change_volume_settings',
839 (fid, {'subscribed': subscribed}))
840876
841 def test_on_volumes_info_error(self):877 def test_on_volumes_info_error(self):
842 """The volumes info couldn't be retrieved."""878 """The volumes info couldn't be retrieved."""
@@ -854,6 +890,57 @@
854 self.test_on_volumes_info_error()890 self.test_on_volumes_info_error()
855 self.test_on_volumes_info_ready_with_no_volumes()891 self.test_on_volumes_info_ready_with_no_volumes()
856892
893 def test_on_subscribed_toggled(self):
894 """Clicking on 'subscribed' updates the folder subscription."""
895 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
896
897 for parent, (_, _, volumes) in enumerate(FAKE_VOLUMES_INFO):
898
899 sorted_vols = sorted(volumes, key=gui.operator.itemgetter('path'))
900 for child, volume in enumerate(sorted_vols):
901 if volume['type'] == 'ROOT':
902 continue # not editable
903
904 path = '%s:%s' % (parent, child)
905 self.ui.on_subscribed_toggled(widget=None, path=path)
906
907 fid = volume['volume_id']
908 subscribed = gui.bool_str(not bool(volume['subscribed']))
909 # backend was called
910 self.assert_backend_called('change_volume_settings',
911 (fid, {'subscribed': subscribed}))
912 # store was updated
913 it = self.ui.volumes_store.get_iter(path)
914 value = self.ui.volumes_store.get_value(it, 1)
915 self.assertEqual(value, bool(subscribed))
916
917 # the ui is processing
918 self.assertTrue(self.ui.is_processing, 'ui must be processing')
919
920 # simulate success for setting change
921 self.ui.on_volume_settings_changed(volume_id=fid)
922
923 def test_on_volume_setting_changed(self):
924 """The setting for a volume was successfully changed."""
925 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
926 self.ui.on_subscribed_toggled(None, "0:0")
927
928 self.ui.on_volume_settings_changed(volume_id=None) # id not used
929
930 # the ui is no longer processing
931 self.assertFalse(self.ui.is_processing, 'ui must not be processing')
932
933 def test_on_volume_setting_change_error(self):
934 """The setting for a volume was not successfully changed."""
935 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
936 self.ui.on_subscribed_toggled(None, "0:0")
937
938 self.patch(self.ui, 'load', self._set_called)
939 self.ui.on_volume_settings_change_error(volume_id=None,
940 error_dict=None) # id not used
941 # reload folders list to sanitize the info in volumes_store
942 self.assertTrue(self._called, ((), {}))
943
857944
858class DeviceTestCase(ControlPanelMixinTestCase):945class DeviceTestCase(ControlPanelMixinTestCase):
859 """The test suite for the device widget."""946 """The test suite for the device widget."""
@@ -2086,11 +2173,11 @@
2086 actual = self.ui.notebook.get_nth_page(self.ui.DASHBOARD_PAGE)2173 actual = self.ui.notebook.get_nth_page(self.ui.DASHBOARD_PAGE)
2087 self.assertTrue(self.ui.dashboard is actual)2174 self.assertTrue(self.ui.dashboard is actual)
20882175
2089 def test_folders_panel_is_packed(self):2176 def test_volumes_panel_is_packed(self):
2090 """The folders panel is packed."""2177 """The volumes panel is packed."""
2091 self.assertIsInstance(self.ui.folders, gui.FoldersPanel)2178 self.assertIsInstance(self.ui.volumes, gui.VolumesPanel)
2092 actual = self.ui.notebook.get_nth_page(self.ui.FOLDERS_PAGE)2179 actual = self.ui.notebook.get_nth_page(self.ui.VOLUMES_PAGE)
2093 self.assertTrue(self.ui.folders is actual)2180 self.assertTrue(self.ui.volumes is actual)
20942181
2095 def test_devices_panel_is_packed(self):2182 def test_devices_panel_is_packed(self):
2096 """The devices panel is packed."""2183 """The devices panel is packed."""
@@ -2104,11 +2191,11 @@
2104 actual = self.ui.notebook.get_nth_page(self.ui.SERVICES_PAGE)2191 actual = self.ui.notebook.get_nth_page(self.ui.SERVICES_PAGE)
2105 self.assertTrue(self.ui.services is actual)2192 self.assertTrue(self.ui.services is actual)
21062193
2107 def test_entering_folders_tab_loads_content(self):2194 def test_entering_volumes_tab_loads_content(self):
2108 """The volumes info is loaded when entering the Folders tab."""2195 """The volumes info is loaded when entering the Volumes tab."""
2109 self.patch(self.ui.folders, 'load', self._set_called)2196 self.patch(self.ui.volumes, 'load', self._set_called)
2110 # clean backend calls2197 # clean backend calls
2111 self.ui.folders_button.clicked()2198 self.ui.volumes_button.clicked()
21122199
2113 self.assertEqual(self._called, ((), {}))2200 self.assertEqual(self._called, ((), {}))
21142201
21152202
=== modified file 'ubuntuone/controlpanel/gtk/tests/test_widgets.py'
--- ubuntuone/controlpanel/gtk/tests/test_widgets.py 2010-12-18 20:16:18 +0000
+++ ubuntuone/controlpanel/gtk/tests/test_widgets.py 2011-01-21 19:25:14 +0000
@@ -156,7 +156,7 @@
156156
157 def setUp(self):157 def setUp(self):
158 super(PanelTitleTestCase, self).setUp()158 super(PanelTitleTestCase, self).setUp()
159 self.widget = widgets.PanelTitle(markup=self.TITLE)159 self.widget = widgets.PanelTitle(markup=self.TITLE, change_bg=True)
160160
161 win = widgets.gtk.Window()161 win = widgets.gtk.Window()
162 win.add(self.widget)162 win.add(self.widget)
@@ -196,13 +196,6 @@
196 self.assertEqual(self.widget.label.get_line_wrap_mode(),196 self.assertEqual(self.widget.label.get_line_wrap_mode(),
197 widgets.pango.WRAP_WORD)197 widgets.pango.WRAP_WORD)
198198
199 def test_label_size_allocated_is_connected(self):
200 """Label have the size-allocate signal connected."""
201 self.widget.label.emit('size-allocate',
202 widgets.gtk.gdk.Rectangle(1, 2, 3, 4))
203 self.assertEqual(self.widget.label.get_size_request(), (3 - 2, -1),
204 'Label must have size-allocate connected.')
205
206 def test_label_padding(self):199 def test_label_padding(self):
207 """The label padding is correct."""200 """The label padding is correct."""
208 self.assertEqual(self.widget.label.get_padding(),201 self.assertEqual(self.widget.label.get_padding(),
209202
=== modified file 'ubuntuone/controlpanel/gtk/widgets.py'
--- ubuntuone/controlpanel/gtk/widgets.py 2010-12-18 20:16:18 +0000
+++ ubuntuone/controlpanel/gtk/widgets.py 2011-01-21 19:25:14 +0000
@@ -108,27 +108,22 @@
108class PanelTitle(gtk.EventBox):108class PanelTitle(gtk.EventBox):
109 """A box with a given color and text."""109 """A box with a given color and text."""
110110
111 def __init__(self, markup='', *args, **kwargs):111 def __init__(self, markup='', change_bg=False, *args, **kwargs):
112 super(PanelTitle, self).__init__(*args, **kwargs)112 super(PanelTitle, self).__init__(*args, **kwargs)
113 self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG))
114
115 self.label = gtk.Label()113 self.label = gtk.Label()
116 self.label.set_markup(markup)114 self.label.set_markup(markup)
117 self.label.set_padding(*DEFAULT_PADDING)115 self.label.set_padding(*DEFAULT_PADDING)
118 self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_FG))
119 self.label.set_property('xalign', 0.0)116 self.label.set_property('xalign', 0.0)
120 self.label.set_line_wrap(True)117 self.label.set_line_wrap(True)
121 self.label.set_line_wrap_mode(pango.WRAP_WORD)118 self.label.set_line_wrap_mode(pango.WRAP_WORD)
122 self.label.connect('size-allocate', self.on_size_allocate)119 if change_bg:
120 self.modify_bg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_BG))
121 self.label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(DEFAULT_FG))
123122
124 self.add(self.label)123 self.add(self.label)
125124
126 self.show_all()125 self.show_all()
127126
128 def on_size_allocate(self, widget, allocation):
129 """The widget can re rezised, embrase it!."""
130 widget.set_size_request(allocation.width - 2, -1)
131
132127
133# Modified from John Stowers' client-side-windows demo.128# Modified from John Stowers' client-side-windows demo.
134class GreyableBin(gtk.Bin):129class GreyableBin(gtk.Bin):

Subscribers

People subscribed via source and target branches