Merge lp:~dobey/ubuntuone-control-panel/remove-gtk into lp:ubuntuone-control-panel

Proposed by dobey
Status: Merged
Approved by: Natalia Bidart
Approved revision: 271
Merged at revision: 273
Proposed branch: lp:~dobey/ubuntuone-control-panel/remove-gtk
Merge into: lp:ubuntuone-control-panel
Diff against target: 7732 lines (+7/-7524)
26 files modified
bin/ubuntuone-control-panel-gtk (+0/-49)
com.ubuntuone.controlpanel.gui.service.in (+0/-3)
data/gtk/dashboard.ui (+0/-198)
data/gtk/device.ui (+0/-279)
data/gtk/devices.ui (+0/-44)
data/gtk/install.ui (+0/-57)
data/gtk/management.ui (+0/-334)
data/gtk/overview.ui (+0/-308)
data/gtk/services.ui (+0/-317)
data/gtk/volumes.ui (+0/-98)
docs/ubuntuone-control-panel-gtk.1 (+0/-15)
po/POTFILES.in (+0/-8)
run-tests (+2/-7)
run-tests.bat (+1/-1)
setup.py (+3/-5)
ubuntuone/controlpanel/dbustests/test_gui_service.py (+0/-104)
ubuntuone/controlpanel/gui/gtk/__init__.py (+0/-28)
ubuntuone/controlpanel/gui/gtk/gui.py (+0/-1667)
ubuntuone/controlpanel/gui/gtk/package_manager.py (+0/-62)
ubuntuone/controlpanel/gui/gtk/tests/__init__.py (+0/-232)
ubuntuone/controlpanel/gui/gtk/tests/test_gui.py (+0/-2179)
ubuntuone/controlpanel/gui/gtk/tests/test_gui_basic.py (+0/-780)
ubuntuone/controlpanel/gui/gtk/tests/test_package_manager.py (+0/-181)
ubuntuone/controlpanel/gui/gtk/tests/test_widgets.py (+0/-205)
ubuntuone/controlpanel/gui/gtk/widgets.py (+0/-362)
ubuntuone/controlpanel/gui/tests/__init__.py (+1/-1)
To merge this branch: bzr merge lp:~dobey/ubuntuone-control-panel/remove-gtk
Reviewer Review Type Date Requested Status
Natalia Bidart (community) Approve
Roberto Alsina (community) Approve
Review via email: mp+95252@code.launchpad.net

Commit message

Remove the GTK+ 2.x control panel

To post a comment you must log in.
Revision history for this message
Natalia Bidart (nataliabidart) wrote :

Thanks for working on this!

I think you should also remove the contropanel backend dbus service, since that one was used only from the GTK+ UI.

review: Needs Fixing
Revision history for this message
dobey (dobey) wrote :

It looks like we can't remove the dbus backend service yet. Deja Dup is using it to query for amount of space the user has left on the account.

Revision history for this message
Roberto Alsina (ralsina) wrote :

+1 doesn't seem to remove anything it shouldn't

review: Approve
271. By dobey

Remove the man page for the gtk panel as well.

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

Looks good.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file 'bin/ubuntuone-control-panel-gtk'
--- bin/ubuntuone-control-panel-gtk 2012-02-22 12:09:28 +0000
+++ bin/ubuntuone-control-panel-gtk 1970-01-01 00:00:00 +0000
@@ -1,49 +0,0 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
5# Eric Casteleijn <eric.casteleijn@canonical.com>
6#
7# Copyright 2010 Canonical Ltd.
8#
9# This program is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License version 3, as published
11# by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE. See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program. If not, see <http://www.gnu.org/licenses/>.
20"""Execute the graphical interface for the Ubuntu One control panel."""
21
22# Invalid name "ubuntuone-control-panel-gtk", pylint: disable=C0103
23
24import sys
25
26from optparse import OptionParser
27
28from ubuntuone.controlpanel.gui.gtk import main
29
30
31def parser_options():
32 """Parse command line parameters."""
33 usage = "Usage: %prog [option]"
34 result = OptionParser(usage=usage)
35 result.add_option("", "--switch-to", dest="switch_to", type="string",
36 metavar="PANEL_NAME", default="",
37 help="Start Ubuntu One in the "
38 "PANEL_NAME tab. Possible values are: "
39 "dashboard, volumes, devices, applications")
40 result.add_option("-a", "--alert", dest="alert", action="store_true",
41 default=False, help="Start Ubuntu One "
42 "alerting the user to its presence.")
43 return result
44
45
46if __name__ == "__main__":
47 parser = parser_options()
48 (options, args) = parser.parse_args(sys.argv)
49 main(switch_to=options.switch_to, alert=options.alert)
500
=== removed file 'com.ubuntuone.controlpanel.gui.service.in'
--- com.ubuntuone.controlpanel.gui.service.in 2011-03-22 13:04:38 +0000
+++ com.ubuntuone.controlpanel.gui.service.in 1970-01-01 00:00:00 +0000
@@ -1,3 +0,0 @@
1[D-BUS Service]
2Name=com.ubuntuone.controlpanel.gui
3Exec=@prefix@/bin/ubuntuone-control-panel-gtk
40
=== removed directory 'data/gtk'
=== removed file 'data/gtk/dashboard.ui'
--- data/gtk/dashboard.ui 2011-07-11 11:19:09 +0000
+++ data/gtk/dashboard.ui 1970-01-01 00:00:00 +0000
@@ -1,198 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkAlignment" id="itself">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <property name="yalign">0</property>
9 <property name="yscale">0</property>
10 <child>
11 <object class="GtkHBox" id="account">
12 <property name="visible">True</property>
13 <property name="can_focus">False</property>
14 <property name="border_width">12</property>
15 <property name="spacing">12</property>
16 <child>
17 <object class="GtkFrame" id="frame2">
18 <property name="visible">True</property>
19 <property name="can_focus">False</property>
20 <property name="label_xalign">0</property>
21 <property name="shadow_type">out</property>
22 <child>
23 <object class="GtkAlignment" id="alignment2">
24 <property name="visible">True</property>
25 <property name="can_focus">False</property>
26 <property name="top_padding">12</property>
27 <property name="bottom_padding">12</property>
28 <property name="left_padding">12</property>
29 <property name="right_padding">12</property>
30 <child>
31 <object class="GtkVBox" id="vbox5">
32 <property name="visible">True</property>
33 <property name="can_focus">False</property>
34 <property name="spacing">12</property>
35 <child>
36 <object class="GtkLabel" id="name_label">
37 <property name="visible">True</property>
38 <property name="can_focus">False</property>
39 <property name="xalign">0</property>
40 <property name="label">tester name</property>
41 <property name="use_markup">True</property>
42 </object>
43 <packing>
44 <property name="expand">False</property>
45 <property name="fill">True</property>
46 <property name="position">0</property>
47 </packing>
48 </child>
49 <child>
50 <object class="GtkLabel" id="email_label">
51 <property name="visible">True</property>
52 <property name="can_focus">False</property>
53 <property name="xalign">0</property>
54 <property name="label">a@example.com</property>
55 </object>
56 <packing>
57 <property name="expand">False</property>
58 <property name="fill">True</property>
59 <property name="position">1</property>
60 </packing>
61 </child>
62 <child>
63 <object class="GtkHButtonBox" id="hbuttonbox1">
64 <property name="visible">True</property>
65 <property name="can_focus">False</property>
66 <child>
67 <object class="GtkLinkButton" id="linkbutton3">
68 <property name="label" translatable="yes">Edit account details</property>
69 <property name="visible">True</property>
70 <property name="can_focus">True</property>
71 <property name="receives_default">True</property>
72 <property name="use_action_appearance">False</property>
73 <property name="relief">half</property>
74 <property name="uri">http://login.ubuntu.com</property>
75 </object>
76 <packing>
77 <property name="expand">False</property>
78 <property name="fill">False</property>
79 <property name="pack_type">end</property>
80 <property name="position">0</property>
81 </packing>
82 </child>
83 </object>
84 <packing>
85 <property name="expand">False</property>
86 <property name="fill">True</property>
87 <property name="pack_type">end</property>
88 <property name="position">2</property>
89 </packing>
90 </child>
91 </object>
92 </child>
93 </object>
94 </child>
95 <child type="label">
96 <object class="GtkLabel" id="label1">
97 <property name="visible">True</property>
98 <property name="can_focus">False</property>
99 <property name="label" translatable="yes">&lt;b&gt;Personal details&lt;/b&gt;</property>
100 <property name="use_markup">True</property>
101 </object>
102 </child>
103 </object>
104 <packing>
105 <property name="expand">True</property>
106 <property name="fill">True</property>
107 <property name="position">0</property>
108 </packing>
109 </child>
110 <child>
111 <object class="GtkFrame" id="frame1">
112 <property name="visible">True</property>
113 <property name="can_focus">False</property>
114 <property name="label_xalign">0</property>
115 <property name="shadow_type">out</property>
116 <child>
117 <object class="GtkAlignment" id="alignment3">
118 <property name="visible">True</property>
119 <property name="can_focus">False</property>
120 <property name="top_padding">12</property>
121 <property name="bottom_padding">12</property>
122 <property name="left_padding">12</property>
123 <property name="right_padding">12</property>
124 <child>
125 <object class="GtkVBox" id="vbox4">
126 <property name="visible">True</property>
127 <property name="can_focus">False</property>
128 <property name="spacing">12</property>
129 <child>
130 <object class="GtkLabel" id="type_label">
131 <property name="visible">True</property>
132 <property name="can_focus">False</property>
133 <property name="xalign">0</property>
134 <property name="label">Ubuntu One Basic (2 GB)
13522GB of awesomeness
136Total storage 22GB
137You also have a mobile plan!
138This is great!</property>
139 </object>
140 <packing>
141 <property name="expand">False</property>
142 <property name="fill">True</property>
143 <property name="position">0</property>
144 </packing>
145 </child>
146 <child>
147 <object class="GtkHButtonBox" id="hbuttonbox2">
148 <property name="visible">True</property>
149 <property name="can_focus">False</property>
150 <child>
151 <object class="GtkLinkButton" id="linkbutton1">
152 <property name="label" translatable="yes">Buy storage and plans</property>
153 <property name="visible">True</property>
154 <property name="can_focus">True</property>
155 <property name="receives_default">True</property>
156 <property name="use_action_appearance">False</property>
157 <property name="relief">half</property>
158 <property name="uri">https://one.ubuntu.com/plans/</property>
159 </object>
160 <packing>
161 <property name="expand">False</property>
162 <property name="fill">False</property>
163 <property name="pack_type">end</property>
164 <property name="position">0</property>
165 </packing>
166 </child>
167 </object>
168 <packing>
169 <property name="expand">False</property>
170 <property name="fill">True</property>
171 <property name="pack_type">end</property>
172 <property name="position">1</property>
173 </packing>
174 </child>
175 </object>
176 </child>
177 </object>
178 </child>
179 <child type="label">
180 <object class="GtkLabel" id="label3">
181 <property name="visible">True</property>
182 <property name="can_focus">False</property>
183 <property name="label" translatable="yes">&lt;b&gt;Your services&lt;/b&gt;</property>
184 <property name="use_markup">True</property>
185 </object>
186 </child>
187 </object>
188 <packing>
189 <property name="expand">True</property>
190 <property name="fill">True</property>
191 <property name="pack_type">end</property>
192 <property name="position">1</property>
193 </packing>
194 </child>
195 </object>
196 </child>
197 </object>
198</interface>
1990
=== removed file 'data/gtk/device.ui'
--- data/gtk/device.ui 2011-07-11 11:19:09 +0000
+++ data/gtk/device.ui 1970-01-01 00:00:00 +0000
@@ -1,279 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkAdjustment" id="adjustment1">
6 <property name="upper">10000</property>
7 <property name="step_increment">1</property>
8 </object>
9 <object class="GtkAdjustment" id="adjustment2">
10 <property name="upper">10000</property>
11 <property name="step_increment">1</property>
12 </object>
13 <object class="GtkVBox" id="itself">
14 <property name="visible">True</property>
15 <property name="can_focus">False</property>
16 <property name="border_width">10</property>
17 <property name="spacing">5</property>
18 <child>
19 <object class="GtkHBox" id="hbox1">
20 <property name="visible">True</property>
21 <property name="can_focus">False</property>
22 <property name="spacing">10</property>
23 <child>
24 <object class="GtkVBox" id="vbox1">
25 <property name="visible">True</property>
26 <property name="can_focus">False</property>
27 <property name="spacing">5</property>
28 <child>
29 <object class="GtkHBox" id="hbox2">
30 <property name="visible">True</property>
31 <property name="can_focus">False</property>
32 <child>
33 <object class="GtkImage" id="device_type">
34 <property name="visible">True</property>
35 <property name="can_focus">False</property>
36 <property name="icon_name">computer</property>
37 </object>
38 <packing>
39 <property name="expand">False</property>
40 <property name="fill">True</property>
41 <property name="position">0</property>
42 </packing>
43 </child>
44 <child>
45 <object class="GtkLabel" id="device_name">
46 <property name="visible">True</property>
47 <property name="can_focus">False</property>
48 <property name="xalign">0</property>
49 <property name="xpad">5</property>
50 <property name="label">My Laptop</property>
51 <property name="wrap">True</property>
52 </object>
53 <packing>
54 <property name="expand">True</property>
55 <property name="fill">True</property>
56 <property name="position">1</property>
57 </packing>
58 </child>
59 </object>
60 <packing>
61 <property name="expand">True</property>
62 <property name="fill">True</property>
63 <property name="position">0</property>
64 </packing>
65 </child>
66 <child>
67 <object class="GtkAlignment" id="alignment1">
68 <property name="visible">True</property>
69 <property name="can_focus">False</property>
70 <property name="left_padding">25</property>
71 <child>
72 <object class="GtkVBox" id="config_settings">
73 <property name="visible">True</property>
74 <property name="can_focus">False</property>
75 <child>
76 <object class="GtkCheckButton" id="show_all_notifications">
77 <property name="label" translatable="yes">Show activity notifications</property>
78 <property name="visible">True</property>
79 <property name="can_focus">True</property>
80 <property name="receives_default">False</property>
81 <property name="use_action_appearance">False</property>
82 <property name="draw_indicator">True</property>
83 <signal name="toggled" handler="on_show_all_notifications_toggled" swapped="no"/>
84 </object>
85 <packing>
86 <property name="expand">True</property>
87 <property name="fill">True</property>
88 <property name="position">0</property>
89 </packing>
90 </child>
91 <child>
92 <object class="GtkVBox" id="throttling">
93 <property name="visible">True</property>
94 <property name="can_focus">False</property>
95 <child>
96 <object class="GtkCheckButton" id="limit_bandwidth">
97 <property name="label" translatable="yes">Limit file sync bandwidth usage</property>
98 <property name="visible">True</property>
99 <property name="can_focus">True</property>
100 <property name="receives_default">False</property>
101 <property name="use_action_appearance">False</property>
102 <property name="draw_indicator">True</property>
103 <signal name="toggled" handler="on_limit_bandwidth_toggled" swapped="no"/>
104 </object>
105 <packing>
106 <property name="expand">True</property>
107 <property name="fill">True</property>
108 <property name="position">0</property>
109 </packing>
110 </child>
111 <child>
112 <object class="GtkTable" id="throttling_limits">
113 <property name="visible">True</property>
114 <property name="can_focus">False</property>
115 <property name="n_rows">2</property>
116 <property name="n_columns">3</property>
117 <property name="column_spacing">3</property>
118 <property name="row_spacing">3</property>
119 <child>
120 <object class="GtkLabel" id="max_upload_speed_label">
121 <property name="visible">True</property>
122 <property name="can_focus">False</property>
123 <property name="xalign">0</property>
124 <property name="xpad">22</property>
125 <property name="label" translatable="yes">Max upload speed:</property>
126 <property name="track_visited_links">False</property>
127 </object>
128 </child>
129 <child>
130 <object class="GtkLabel" id="max_download_speed_label">
131 <property name="visible">True</property>
132 <property name="can_focus">False</property>
133 <property name="xalign">0</property>
134 <property name="xpad">22</property>
135 <property name="label" translatable="yes">Max download speed:</property>
136 </object>
137 <packing>
138 <property name="top_attach">1</property>
139 <property name="bottom_attach">2</property>
140 </packing>
141 </child>
142 <child>
143 <object class="GtkSpinButton" id="max_upload_speed">
144 <property name="visible">True</property>
145 <property name="can_focus">True</property>
146 <property name="invisible_char">•</property>
147 <property name="activates_default">True</property>
148 <property name="invisible_char_set">True</property>
149 <property name="adjustment">adjustment1</property>
150 <signal name="value-changed" handler="on_max_upload_speed_value_changed" swapped="no"/>
151 </object>
152 <packing>
153 <property name="left_attach">1</property>
154 <property name="right_attach">2</property>
155 <property name="x_options">GTK_FILL</property>
156 <property name="y_options">GTK_FILL</property>
157 </packing>
158 </child>
159 <child>
160 <object class="GtkSpinButton" id="max_download_speed">
161 <property name="visible">True</property>
162 <property name="can_focus">True</property>
163 <property name="invisible_char">•</property>
164 <property name="activates_default">True</property>
165 <property name="invisible_char_set">True</property>
166 <property name="adjustment">adjustment2</property>
167 <signal name="value-changed" handler="on_max_download_speed_value_changed" swapped="no"/>
168 </object>
169 <packing>
170 <property name="left_attach">1</property>
171 <property name="right_attach">2</property>
172 <property name="top_attach">1</property>
173 <property name="bottom_attach">2</property>
174 </packing>
175 </child>
176 <child>
177 <object class="GtkLabel" id="label1">
178 <property name="visible">True</property>
179 <property name="can_focus">False</property>
180 <property name="label" translatable="yes">KiB/s</property>
181 </object>
182 <packing>
183 <property name="left_attach">2</property>
184 <property name="right_attach">3</property>
185 </packing>
186 </child>
187 <child>
188 <object class="GtkLabel" id="label2">
189 <property name="visible">True</property>
190 <property name="can_focus">False</property>
191 <property name="label" translatable="yes">KiB/s</property>
192 </object>
193 <packing>
194 <property name="left_attach">2</property>
195 <property name="right_attach">3</property>
196 <property name="top_attach">1</property>
197 <property name="bottom_attach">2</property>
198 </packing>
199 </child>
200 </object>
201 <packing>
202 <property name="expand">True</property>
203 <property name="fill">True</property>
204 <property name="position">1</property>
205 </packing>
206 </child>
207 </object>
208 <packing>
209 <property name="expand">True</property>
210 <property name="fill">True</property>
211 <property name="position">1</property>
212 </packing>
213 </child>
214 </object>
215 </child>
216 </object>
217 <packing>
218 <property name="expand">False</property>
219 <property name="fill">True</property>
220 <property name="position">1</property>
221 </packing>
222 </child>
223 </object>
224 <packing>
225 <property name="expand">False</property>
226 <property name="fill">True</property>
227 <property name="position">0</property>
228 </packing>
229 </child>
230 <child>
231 <object class="GtkVButtonBox" id="vbuttonbox1">
232 <property name="visible">True</property>
233 <property name="can_focus">False</property>
234 <property name="layout_style">start</property>
235 <child>
236 <object class="GtkButton" id="remove">
237 <property name="label">gtk-remove</property>
238 <property name="visible">True</property>
239 <property name="can_focus">True</property>
240 <property name="receives_default">True</property>
241 <property name="use_action_appearance">False</property>
242 <property name="use_stock">True</property>
243 <signal name="activate" handler="on_remove_clicked" swapped="no"/>
244 <signal name="clicked" handler="on_remove_clicked" swapped="no"/>
245 </object>
246 <packing>
247 <property name="expand">False</property>
248 <property name="fill">False</property>
249 <property name="position">0</property>
250 </packing>
251 </child>
252 </object>
253 <packing>
254 <property name="expand">False</property>
255 <property name="fill">True</property>
256 <property name="pack_type">end</property>
257 <property name="position">1</property>
258 </packing>
259 </child>
260 </object>
261 <packing>
262 <property name="expand">False</property>
263 <property name="fill">True</property>
264 <property name="position">0</property>
265 </packing>
266 </child>
267 <child>
268 <object class="GtkLabel" id="warning_label">
269 <property name="visible">True</property>
270 <property name="can_focus">False</property>
271 </object>
272 <packing>
273 <property name="expand">True</property>
274 <property name="fill">True</property>
275 <property name="position">1</property>
276 </packing>
277 </child>
278 </object>
279</interface>
2800
=== removed file 'data/gtk/devices.ui'
--- data/gtk/devices.ui 2011-07-11 11:19:09 +0000
+++ data/gtk/devices.ui 1970-01-01 00:00:00 +0000
@@ -1,44 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkVBox" id="itself">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <child>
9 <object class="GtkScrolledWindow" id="scrolledwindow1">
10 <property name="visible">True</property>
11 <property name="can_focus">True</property>
12 <property name="hscrollbar_policy">automatic</property>
13 <property name="vscrollbar_policy">automatic</property>
14 <child>
15 <object class="GtkViewport" id="viewport1">
16 <property name="visible">True</property>
17 <property name="can_focus">False</property>
18 <property name="resize_mode">queue</property>
19 <child>
20 <object class="GtkAlignment" id="alignment1">
21 <property name="visible">True</property>
22 <property name="can_focus">False</property>
23 <child>
24 <object class="GtkVBox" id="devices">
25 <property name="visible">True</property>
26 <property name="can_focus">False</property>
27 <child>
28 <placeholder/>
29 </child>
30 </object>
31 </child>
32 </object>
33 </child>
34 </object>
35 </child>
36 </object>
37 <packing>
38 <property name="expand">True</property>
39 <property name="fill">True</property>
40 <property name="position">0</property>
41 </packing>
42 </child>
43 </object>
44</interface>
450
=== removed file 'data/gtk/install.ui'
--- data/gtk/install.ui 2011-07-11 11:19:09 +0000
+++ data/gtk/install.ui 1970-01-01 00:00:00 +0000
@@ -1,57 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkImage" id="image1">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <property name="stock">gtk-ok</property>
9 </object>
10 <object class="GtkVBox" id="itself">
11 <property name="visible">True</property>
12 <property name="can_focus">False</property>
13 <property name="border_width">10</property>
14 <property name="spacing">10</property>
15 <child>
16 <object class="GtkLabel" id="install_label">
17 <property name="visible">True</property>
18 <property name="can_focus">False</property>
19 <property name="label">label</property>
20 <property name="wrap">True</property>
21 </object>
22 <packing>
23 <property name="expand">True</property>
24 <property name="fill">True</property>
25 <property name="position">0</property>
26 </packing>
27 </child>
28 <child>
29 <object class="GtkHButtonBox" id="install_button_box">
30 <property name="visible">True</property>
31 <property name="can_focus">False</property>
32 <child>
33 <object class="GtkButton" id="install_button">
34 <property name="label" translatable="yes">_Install now</property>
35 <property name="visible">True</property>
36 <property name="can_focus">True</property>
37 <property name="receives_default">True</property>
38 <property name="use_action_appearance">False</property>
39 <property name="image">image1</property>
40 <property name="use_underline">True</property>
41 <signal name="clicked" handler="on_install_button_clicked" swapped="no"/>
42 </object>
43 <packing>
44 <property name="expand">False</property>
45 <property name="fill">False</property>
46 <property name="position">0</property>
47 </packing>
48 </child>
49 </object>
50 <packing>
51 <property name="expand">False</property>
52 <property name="fill">True</property>
53 <property name="position">1</property>
54 </packing>
55 </child>
56 </object>
57</interface>
580
=== removed file 'data/gtk/management.ui'
--- data/gtk/management.ui 2011-07-11 11:19:09 +0000
+++ data/gtk/management.ui 1970-01-01 00:00:00 +0000
@@ -1,334 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkVBox" id="itself">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <child>
9 <object class="GtkEventBox" id="header">
10 <property name="visible">True</property>
11 <property name="can_focus">False</property>
12 <child>
13 <object class="GtkVBox" id="vbox2">
14 <property name="visible">True</property>
15 <property name="can_focus">False</property>
16 <child>
17 <object class="GtkHBox" id="status_box">
18 <property name="visible">True</property>
19 <property name="can_focus">False</property>
20 <property name="border_width">10</property>
21 <property name="spacing">10</property>
22 <child>
23 <object class="GtkVBox" id="quota_box">
24 <property name="visible">True</property>
25 <property name="can_focus">False</property>
26 <property name="spacing">5</property>
27 <child>
28 <object class="GtkProgressBar" id="quota_progressbar">
29 <property name="visible">True</property>
30 <property name="can_focus">False</property>
31 </object>
32 <packing>
33 <property name="expand">True</property>
34 <property name="fill">True</property>
35 <property name="position">0</property>
36 </packing>
37 </child>
38 </object>
39 <packing>
40 <property name="expand">False</property>
41 <property name="fill">True</property>
42 <property name="position">0</property>
43 </packing>
44 </child>
45 </object>
46 <packing>
47 <property name="expand">False</property>
48 <property name="fill">True</property>
49 <property name="position">0</property>
50 </packing>
51 </child>
52 <child>
53 <object class="GtkHBox" id="hbox2">
54 <property name="visible">True</property>
55 <property name="can_focus">False</property>
56 <child>
57 <object class="GtkHSeparator" id="hseparator1">
58 <property name="visible">True</property>
59 <property name="can_focus">False</property>
60 </object>
61 <packing>
62 <property name="expand">True</property>
63 <property name="fill">True</property>
64 <property name="position">0</property>
65 </packing>
66 </child>
67 <child>
68 <object class="GtkHButtonBox" id="hbuttonbox1">
69 <property name="visible">True</property>
70 <property name="can_focus">False</property>
71 <property name="layout_style">center</property>
72 <child>
73 <object class="GtkRadioButton" id="dashboard_button">
74 <property name="label" translatable="yes">Account</property>
75 <property name="visible">True</property>
76 <property name="can_focus">True</property>
77 <property name="receives_default">False</property>
78 <property name="use_action_appearance">False</property>
79 <property name="active">True</property>
80 <property name="draw_indicator">False</property>
81 </object>
82 <packing>
83 <property name="expand">False</property>
84 <property name="fill">False</property>
85 <property name="position">0</property>
86 </packing>
87 </child>
88 <child>
89 <object class="GtkRadioButton" id="volumes_button">
90 <property name="label" translatable="yes">Cloud Folders</property>
91 <property name="visible">True</property>
92 <property name="can_focus">True</property>
93 <property name="receives_default">False</property>
94 <property name="use_action_appearance">False</property>
95 <property name="draw_indicator">False</property>
96 <property name="group">dashboard_button</property>
97 </object>
98 <packing>
99 <property name="expand">False</property>
100 <property name="fill">False</property>
101 <property name="position">1</property>
102 </packing>
103 </child>
104 <child>
105 <object class="GtkRadioButton" id="shares_button">
106 <property name="label" translatable="yes">Shares</property>
107 <property name="can_focus">True</property>
108 <property name="receives_default">False</property>
109 <property name="use_action_appearance">False</property>
110 <property name="draw_indicator">False</property>
111 <property name="group">dashboard_button</property>
112 </object>
113 <packing>
114 <property name="expand">False</property>
115 <property name="fill">False</property>
116 <property name="position">2</property>
117 </packing>
118 </child>
119 <child>
120 <object class="GtkRadioButton" id="devices_button">
121 <property name="label" translatable="yes">Devices</property>
122 <property name="visible">True</property>
123 <property name="can_focus">True</property>
124 <property name="receives_default">False</property>
125 <property name="use_action_appearance">False</property>
126 <property name="draw_indicator">False</property>
127 <property name="group">dashboard_button</property>
128 </object>
129 <packing>
130 <property name="expand">False</property>
131 <property name="fill">False</property>
132 <property name="position">3</property>
133 </packing>
134 </child>
135 <child>
136 <object class="GtkRadioButton" id="services_button">
137 <property name="label" translatable="yes">Services</property>
138 <property name="visible">True</property>
139 <property name="can_focus">True</property>
140 <property name="receives_default">False</property>
141 <property name="use_action_appearance">False</property>
142 <property name="draw_indicator">False</property>
143 <property name="group">dashboard_button</property>
144 </object>
145 <packing>
146 <property name="expand">False</property>
147 <property name="fill">False</property>
148 <property name="position">4</property>
149 </packing>
150 </child>
151 </object>
152 <packing>
153 <property name="expand">False</property>
154 <property name="fill">True</property>
155 <property name="position">1</property>
156 </packing>
157 </child>
158 <child>
159 <object class="GtkHSeparator" id="hseparator2">
160 <property name="visible">True</property>
161 <property name="can_focus">False</property>
162 </object>
163 <packing>
164 <property name="expand">True</property>
165 <property name="fill">True</property>
166 <property name="position">2</property>
167 </packing>
168 </child>
169 </object>
170 <packing>
171 <property name="expand">False</property>
172 <property name="fill">True</property>
173 <property name="position">1</property>
174 </packing>
175 </child>
176 </object>
177 </child>
178 </object>
179 <packing>
180 <property name="expand">False</property>
181 <property name="fill">True</property>
182 <property name="position">0</property>
183 </packing>
184 </child>
185 <child>
186 <object class="GtkNotebook" id="notebook">
187 <property name="visible">True</property>
188 <property name="can_focus">False</property>
189 <property name="show_tabs">False</property>
190 <property name="show_border">False</property>
191 <property name="homogeneous">True</property>
192 </object>
193 <packing>
194 <property name="expand">True</property>
195 <property name="fill">True</property>
196 <property name="position">1</property>
197 </packing>
198 </child>
199 <child>
200 <object class="GtkHBox" id="hbox1">
201 <property name="visible">True</property>
202 <property name="can_focus">False</property>
203 <property name="border_width">12</property>
204 <child>
205 <object class="GtkAlignment" id="alignment3">
206 <property name="visible">True</property>
207 <property name="can_focus">False</property>
208 <property name="yalign">1</property>
209 <property name="yscale">0</property>
210 <child>
211 <object class="GtkHButtonBox" id="hbuttonbox3">
212 <property name="visible">True</property>
213 <property name="can_focus">False</property>
214 <property name="spacing">5</property>
215 <property name="layout_style">start</property>
216 <child>
217 <object class="GtkLinkButton" id="linkbutton2">
218 <property name="label" translatable="yes">Official Support</property>
219 <property name="visible">True</property>
220 <property name="can_focus">True</property>
221 <property name="receives_default">True</property>
222 <property name="use_action_appearance">False</property>
223 <property name="relief">half</property>
224 <property name="uri">https://one.ubuntu.com/support/</property>
225 </object>
226 <packing>
227 <property name="expand">False</property>
228 <property name="fill">False</property>
229 <property name="position">0</property>
230 </packing>
231 </child>
232 <child>
233 <object class="GtkLinkButton" id="linkbutton4">
234 <property name="label" translatable="yes">Community Support</property>
235 <property name="visible">True</property>
236 <property name="can_focus">True</property>
237 <property name="receives_default">True</property>
238 <property name="use_action_appearance">False</property>
239 <property name="relief">half</property>
240 <property name="uri">http://askubuntu.com/questions/tagged/ubuntu-one</property>
241 </object>
242 <packing>
243 <property name="expand">False</property>
244 <property name="fill">False</property>
245 <property name="position">1</property>
246 </packing>
247 </child>
248 </object>
249 </child>
250 </object>
251 <packing>
252 <property name="expand">False</property>
253 <property name="fill">True</property>
254 <property name="position">0</property>
255 </packing>
256 </child>
257 <child>
258 <object class="GtkHBox" id="hbox3">
259 <property name="visible">True</property>
260 <property name="can_focus">False</property>
261 <child>
262 <object class="GtkLabel" id="label4">
263 <property name="visible">True</property>
264 <property name="can_focus">False</property>
265 <property name="label" translatable="yes">Talk to us on:</property>
266 </object>
267 <packing>
268 <property name="expand">True</property>
269 <property name="fill">True</property>
270 <property name="position">0</property>
271 </packing>
272 </child>
273 <child>
274 <object class="GtkLinkButton" id="linkbutton5">
275 <property name="visible">True</property>
276 <property name="can_focus">True</property>
277 <property name="receives_default">True</property>
278 <property name="use_action_appearance">False</property>
279 <property name="relief">none</property>
280 <property name="uri">http://twitter.com/ubuntuone</property>
281 <child>
282 <object class="GtkImage" id="twitter_logo">
283 <property name="visible">True</property>
284 <property name="can_focus">False</property>
285 <property name="tooltip_text" translatable="yes">http://twitter.com/ubuntuone</property>
286 </object>
287 </child>
288 </object>
289 <packing>
290 <property name="expand">False</property>
291 <property name="fill">False</property>
292 <property name="position">1</property>
293 </packing>
294 </child>
295 <child>
296 <object class="GtkLinkButton" id="linkbutton6">
297 <property name="visible">True</property>
298 <property name="can_focus">True</property>
299 <property name="receives_default">True</property>
300 <property name="use_action_appearance">False</property>
301 <property name="relief">none</property>
302 <property name="uri">http://www.facebook.com/ubuntuone</property>
303 <child>
304 <object class="GtkImage" id="facebook_logo">
305 <property name="visible">True</property>
306 <property name="can_focus">False</property>
307 <property name="tooltip_text" translatable="yes">http://www.facebook.com/ubuntuone</property>
308 </object>
309 </child>
310 </object>
311 <packing>
312 <property name="expand">True</property>
313 <property name="fill">True</property>
314 <property name="position">2</property>
315 </packing>
316 </child>
317 </object>
318 <packing>
319 <property name="expand">False</property>
320 <property name="fill">True</property>
321 <property name="pack_type">end</property>
322 <property name="position">1</property>
323 </packing>
324 </child>
325 </object>
326 <packing>
327 <property name="expand">False</property>
328 <property name="fill">True</property>
329 <property name="pack_type">end</property>
330 <property name="position">1</property>
331 </packing>
332 </child>
333 </object>
334</interface>
3350
=== removed file 'data/gtk/overview.ui'
--- data/gtk/overview.ui 2011-09-14 19:08:52 +0000
+++ data/gtk/overview.ui 1970-01-01 00:00:00 +0000
@@ -1,308 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkVBox" id="itself">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <child>
9 <object class="GtkEventBox" id="eventbox1">
10 <property name="visible">True</property>
11 <property name="can_focus">False</property>
12 <child>
13 <object class="GtkImage" id="banner">
14 <property name="visible">True</property>
15 <property name="can_focus">False</property>
16 <property name="xpad">12</property>
17 <property name="ypad">12</property>
18 </object>
19 </child>
20 </object>
21 <packing>
22 <property name="expand">True</property>
23 <property name="fill">True</property>
24 <property name="position">0</property>
25 </packing>
26 </child>
27 <child>
28 <object class="GtkLabel" id="label7">
29 <property name="visible">True</property>
30 <property name="can_focus">False</property>
31 <property name="label" translatable="yes">&lt;span font="24" foreground="#4d4d4d"&gt;The Power of Your Personal Cloud&lt;/span&gt;</property>
32 <property name="use_markup">True</property>
33 </object>
34 <packing>
35 <property name="expand">True</property>
36 <property name="fill">True</property>
37 <property name="position">1</property>
38 </packing>
39 </child>
40 <child>
41 <object class="GtkHBox" id="hbox1">
42 <property name="visible">True</property>
43 <property name="can_focus">False</property>
44 <property name="border_width">12</property>
45 <child>
46 <object class="GtkTable" id="table1">
47 <property name="visible">True</property>
48 <property name="can_focus">False</property>
49 <property name="n_rows">4</property>
50 <property name="n_columns">2</property>
51 <property name="column_spacing">10</property>
52 <child>
53 <object class="GtkImage" id="files_icon">
54 <property name="visible">True</property>
55 <property name="can_focus">False</property>
56 </object>
57 <packing>
58 <property name="x_options">GTK_FILL</property>
59 <property name="y_options">GTK_FILL</property>
60 </packing>
61 </child>
62 <child>
63 <object class="GtkImage" id="music_stream_icon">
64 <property name="visible">True</property>
65 <property name="can_focus">False</property>
66 </object>
67 <packing>
68 <property name="top_attach">1</property>
69 <property name="bottom_attach">2</property>
70 <property name="x_options">GTK_FILL</property>
71 <property name="y_options">GTK_FILL</property>
72 </packing>
73 </child>
74 <child>
75 <object class="GtkImage" id="contacts_icon">
76 <property name="visible">True</property>
77 <property name="can_focus">False</property>
78 </object>
79 <packing>
80 <property name="top_attach">2</property>
81 <property name="bottom_attach">3</property>
82 <property name="x_options">GTK_FILL</property>
83 <property name="y_options">GTK_FILL</property>
84 </packing>
85 </child>
86 <child>
87 <object class="GtkImage" id="notes_icon">
88 <property name="visible">True</property>
89 <property name="can_focus">False</property>
90 </object>
91 <packing>
92 <property name="top_attach">3</property>
93 <property name="bottom_attach">4</property>
94 <property name="x_options">GTK_FILL</property>
95 <property name="y_options">GTK_FILL</property>
96 </packing>
97 </child>
98 <child>
99 <object class="GtkLabel" id="label3">
100 <property name="visible">True</property>
101 <property name="can_focus">False</property>
102 <property name="xalign">0</property>
103 <property name="label" translatable="yes">Files Anywhere
104&lt;span foreground="#909090"&gt;Back up and access your files from Ubuntu, Windows, Web or Mobile&lt;/span&gt;</property>
105 <property name="use_markup">True</property>
106 <property name="wrap">True</property>
107 </object>
108 <packing>
109 <property name="left_attach">1</property>
110 <property name="right_attach">2</property>
111 </packing>
112 </child>
113 <child>
114 <object class="GtkLabel" id="label4">
115 <property name="visible">True</property>
116 <property name="can_focus">False</property>
117 <property name="xalign">0</property>
118 <property name="label" translatable="yes">Keep Connected
119&lt;span foreground="#909090"&gt;Unify your contacts across Desktop, Mobile and Web&lt;/span&gt;</property>
120 <property name="use_markup">True</property>
121 <property name="wrap">True</property>
122 </object>
123 <packing>
124 <property name="left_attach">1</property>
125 <property name="right_attach">2</property>
126 <property name="top_attach">2</property>
127 <property name="bottom_attach">3</property>
128 </packing>
129 </child>
130 <child>
131 <object class="GtkLabel" id="label5">
132 <property name="visible">True</property>
133 <property name="can_focus">False</property>
134 <property name="xalign">0</property>
135 <property name="label" translatable="yes">Rock Out
136&lt;span foreground="#909090"&gt;Your entire collection follows you around with music streaming to Android and iPhone&lt;/span&gt;</property>
137 <property name="use_markup">True</property>
138 <property name="wrap">True</property>
139 </object>
140 <packing>
141 <property name="left_attach">1</property>
142 <property name="right_attach">2</property>
143 <property name="top_attach">1</property>
144 <property name="bottom_attach">2</property>
145 </packing>
146 </child>
147 <child>
148 <object class="GtkLabel" id="label6">
149 <property name="visible">True</property>
150 <property name="can_focus">False</property>
151 <property name="xalign">0</property>
152 <property name="label" translatable="yes">Stay Productive
153&lt;span foreground="#909090"&gt;Keep your Tomboy notes synced&lt;/span&gt;</property>
154 <property name="use_markup">True</property>
155 <property name="wrap">True</property>
156 </object>
157 <packing>
158 <property name="left_attach">1</property>
159 <property name="right_attach">2</property>
160 <property name="top_attach">3</property>
161 <property name="bottom_attach">4</property>
162 </packing>
163 </child>
164 </object>
165 <packing>
166 <property name="expand">True</property>
167 <property name="fill">True</property>
168 <property name="position">0</property>
169 </packing>
170 </child>
171 <child>
172 <object class="GtkVBox" id="vbox2">
173 <property name="visible">True</property>
174 <property name="can_focus">False</property>
175 <child>
176 <object class="GtkLabel" id="warning_label">
177 <property name="visible">True</property>
178 <property name="can_focus">False</property>
179 <property name="label">A warning label that can be long. Possible really long, let's see how it behaves.</property>
180 <property name="wrap">True</property>
181 </object>
182 <packing>
183 <property name="expand">False</property>
184 <property name="fill">True</property>
185 <property name="position">0</property>
186 </packing>
187 </child>
188 <child>
189 <object class="GtkAlignment" id="alignment2">
190 <property name="visible">True</property>
191 <property name="can_focus">False</property>
192 <property name="xscale">0</property>
193 <property name="yscale">0</property>
194 <child>
195 <object class="GtkVBox" id="vbox3">
196 <property name="visible">True</property>
197 <property name="can_focus">False</property>
198 <property name="spacing">10</property>
199 <child>
200 <object class="GtkButton" id="learn_more_button">
201 <property name="visible">True</property>
202 <property name="can_focus">True</property>
203 <property name="receives_default">True</property>
204 <property name="use_action_appearance">False</property>
205 <signal name="clicked" handler="on_learn_more_button_clicked" swapped="no"/>
206 <child>
207 <object class="GtkVBox" id="vbox5">
208 <property name="visible">True</property>
209 <property name="can_focus">False</property>
210 <child>
211 <object class="GtkImage" id="image5">
212 <property name="visible">True</property>
213 <property name="can_focus">False</property>
214 <property name="pixel_size">55</property>
215 <property name="icon_name">ubuntuone</property>
216 </object>
217 <packing>
218 <property name="expand">True</property>
219 <property name="fill">True</property>
220 <property name="position">0</property>
221 </packing>
222 </child>
223 <child>
224 <object class="GtkLabel" id="label8">
225 <property name="visible">True</property>
226 <property name="can_focus">False</property>
227 <property name="label" translatable="yes">&lt;span foreground="#909090"&gt;Learn More&lt;/span&gt;</property>
228 <property name="use_markup">True</property>
229 </object>
230 <packing>
231 <property name="expand">True</property>
232 <property name="fill">True</property>
233 <property name="position">1</property>
234 </packing>
235 </child>
236 </object>
237 </child>
238 </object>
239 <packing>
240 <property name="expand">True</property>
241 <property name="fill">True</property>
242 <property name="position">0</property>
243 </packing>
244 </child>
245 <child>
246 <object class="GtkButton" id="join_now_button">
247 <property name="visible">True</property>
248 <property name="can_focus">True</property>
249 <property name="can_default">True</property>
250 <property name="receives_default">True</property>
251 <property name="use_action_appearance">False</property>
252 <signal name="clicked" handler="on_join_now_button_clicked" swapped="no"/>
253 <child>
254 <object class="GtkLabel" id="label1">
255 <property name="visible">True</property>
256 <property name="can_focus">False</property>
257 <property name="label" translatable="yes">&lt;span font_size="xx-large" foreground="#4d4d4d"&gt;Join now&lt;/span&gt;</property>
258 <property name="use_markup">True</property>
259 </object>
260 </child>
261 </object>
262 <packing>
263 <property name="expand">False</property>
264 <property name="fill">True</property>
265 <property name="position">1</property>
266 </packing>
267 </child>
268 <child>
269 <object class="GtkLinkButton" id="connect_button">
270 <property name="label" translatable="yes">I already have an account!</property>
271 <property name="visible">True</property>
272 <property name="can_focus">True</property>
273 <property name="receives_default">True</property>
274 <property name="use_action_appearance">False</property>
275 <property name="relief">none</property>
276 <signal name="clicked" handler="on_connect_button_clicked" swapped="no"/>
277 </object>
278 <packing>
279 <property name="expand">False</property>
280 <property name="fill">False</property>
281 <property name="position">2</property>
282 </packing>
283 </child>
284 </object>
285 </child>
286 </object>
287 <packing>
288 <property name="expand">True</property>
289 <property name="fill">True</property>
290 <property name="position">1</property>
291 </packing>
292 </child>
293 </object>
294 <packing>
295 <property name="expand">True</property>
296 <property name="fill">True</property>
297 <property name="position">1</property>
298 </packing>
299 </child>
300 </object>
301 <packing>
302 <property name="expand">True</property>
303 <property name="fill">True</property>
304 <property name="position">2</property>
305 </packing>
306 </child>
307 </object>
308</interface>
3090
=== removed file 'data/gtk/services.ui'
--- data/gtk/services.ui 2011-09-14 19:08:52 +0000
+++ data/gtk/services.ui 1970-01-01 00:00:00 +0000
@@ -1,317 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkVBox" id="itself">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <child>
9 <object class="GtkScrolledWindow" id="scrolledwindow1">
10 <property name="visible">True</property>
11 <property name="can_focus">True</property>
12 <property name="hscrollbar_policy">automatic</property>
13 <property name="vscrollbar_policy">automatic</property>
14 <child>
15 <object class="GtkViewport" id="viewport1">
16 <property name="visible">True</property>
17 <property name="can_focus">False</property>
18 <property name="resize_mode">queue</property>
19 <property name="shadow_type">none</property>
20 <child>
21 <object class="GtkAlignment" id="alignment1">
22 <property name="visible">True</property>
23 <property name="can_focus">False</property>
24 <property name="left_padding">5</property>
25 <property name="right_padding">5</property>
26 <child>
27 <object class="GtkVBox" id="vbox1">
28 <property name="visible">True</property>
29 <property name="can_focus">False</property>
30 <property name="spacing">5</property>
31 <child>
32 <object class="GtkFrame" id="files">
33 <property name="visible">True</property>
34 <property name="can_focus">False</property>
35 <property name="label_xalign">0</property>
36 <property name="shadow_type">out</property>
37 <child>
38 <object class="GtkAlignment" id="alignment2">
39 <property name="visible">True</property>
40 <property name="can_focus">False</property>
41 <child>
42 <object class="GtkHBox" id="hbox2">
43 <property name="visible">True</property>
44 <property name="can_focus">False</property>
45 <property name="border_width">5</property>
46 <child>
47 <object class="GtkTable" id="table1">
48 <property name="visible">True</property>
49 <property name="can_focus">False</property>
50 <property name="n_rows">3</property>
51 <property name="n_columns">3</property>
52 <property name="column_spacing">5</property>
53 <property name="row_spacing">5</property>
54 <child>
55 <object class="GtkCheckButton" id="file_sync_check">
56 <property name="visible">True</property>
57 <property name="can_focus">True</property>
58 <property name="receives_default">False</property>
59 <property name="use_action_appearance">False</property>
60 <property name="draw_indicator">True</property>
61 </object>
62 <packing>
63 <property name="x_options"></property>
64 <property name="y_options"></property>
65 </packing>
66 </child>
67 <child>
68 <object class="GtkImage" id="files_icon">
69 <property name="visible">True</property>
70 <property name="can_focus">False</property>
71 </object>
72 <packing>
73 <property name="left_attach">1</property>
74 <property name="right_attach">2</property>
75 <property name="x_options"></property>
76 <property name="y_options"></property>
77 </packing>
78 </child>
79 <child>
80 <object class="GtkLabel" id="label1">
81 <property name="visible">True</property>
82 <property name="can_focus">False</property>
83 <property name="xalign">0</property>
84 <property name="label" translatable="yes">Enable File Sync</property>
85 </object>
86 <packing>
87 <property name="left_attach">2</property>
88 <property name="right_attach">3</property>
89 </packing>
90 </child>
91 <child>
92 <object class="GtkLabel" id="label2">
93 <property name="visible">True</property>
94 <property name="can_focus">False</property>
95 <property name="xalign">0</property>
96 <property name="yalign">0</property>
97 <property name="label" translatable="yes">&lt;span font_size="small"&gt;Enable and then choose which folders you want to access from the Web or any device you connected to Ubuntu One
98
99Simply drag and drop any file or folder to your Ubuntu One folder on this computer&lt;/span&gt;</property>
100 <property name="use_markup">True</property>
101 <property name="wrap">True</property>
102 <property name="width_chars">35</property>
103 </object>
104 <packing>
105 <property name="left_attach">2</property>
106 <property name="right_attach">3</property>
107 <property name="top_attach">1</property>
108 <property name="bottom_attach">2</property>
109 </packing>
110 </child>
111 <child>
112 <object class="GtkHButtonBox" id="hbuttonbox1">
113 <property name="visible">True</property>
114 <property name="can_focus">False</property>
115 <child>
116 <object class="GtkButton" id="file_sync_button">
117 <property name="label" translatable="yes">_Show me my Ubuntu One folder</property>
118 <property name="visible">True</property>
119 <property name="can_focus">True</property>
120 <property name="receives_default">True</property>
121 <property name="use_action_appearance">False</property>
122 <property name="use_underline">True</property>
123 <signal name="clicked" handler="on_file_sync_button_clicked" swapped="no"/>
124 </object>
125 <packing>
126 <property name="expand">False</property>
127 <property name="fill">False</property>
128 <property name="position">0</property>
129 </packing>
130 </child>
131 </object>
132 <packing>
133 <property name="left_attach">2</property>
134 <property name="right_attach">3</property>
135 <property name="top_attach">2</property>
136 <property name="bottom_attach">3</property>
137 </packing>
138 </child>
139 <child>
140 <placeholder/>
141 </child>
142 <child>
143 <placeholder/>
144 </child>
145 <child>
146 <placeholder/>
147 </child>
148 <child>
149 <placeholder/>
150 </child>
151 </object>
152 <packing>
153 <property name="expand">False</property>
154 <property name="fill">True</property>
155 <property name="position">0</property>
156 </packing>
157 </child>
158 <child>
159 <object class="GtkImage" id="files_example">
160 <property name="visible">True</property>
161 <property name="can_focus">False</property>
162 </object>
163 <packing>
164 <property name="expand">False</property>
165 <property name="fill">True</property>
166 <property name="pack_type">end</property>
167 <property name="position">1</property>
168 </packing>
169 </child>
170 </object>
171 </child>
172 </object>
173 </child>
174 <child type="label_item">
175 <placeholder/>
176 </child>
177 </object>
178 <packing>
179 <property name="expand">False</property>
180 <property name="fill">True</property>
181 <property name="position">0</property>
182 </packing>
183 </child>
184 <child>
185 <object class="GtkFrame" id="replications">
186 <property name="visible">True</property>
187 <property name="can_focus">False</property>
188 <property name="label_xalign">0</property>
189 <property name="shadow_type">out</property>
190 <child>
191 <object class="GtkAlignment" id="alignment3">
192 <property name="visible">True</property>
193 <property name="can_focus">False</property>
194 <property name="top_padding">6</property>
195 <child>
196 <object class="GtkHBox" id="hbox3">
197 <property name="visible">True</property>
198 <property name="can_focus">False</property>
199 <property name="border_width">5</property>
200 <child>
201 <object class="GtkVBox" id="contacts">
202 <property name="visible">True</property>
203 <property name="can_focus">False</property>
204 <child>
205 <object class="GtkTable" id="contacts_sync">
206 <property name="visible">True</property>
207 <property name="can_focus">False</property>
208 <property name="n_rows">2</property>
209 <property name="n_columns">3</property>
210 <property name="column_spacing">5</property>
211 <property name="row_spacing">5</property>
212 <child>
213 <object class="GtkCheckButton" id="contacts_check">
214 <property name="visible">True</property>
215 <property name="can_focus">True</property>
216 <property name="receives_default">False</property>
217 <property name="use_action_appearance">False</property>
218 <property name="draw_indicator">True</property>
219 </object>
220 <packing>
221 <property name="x_options"></property>
222 <property name="y_options"></property>
223 </packing>
224 </child>
225 <child>
226 <object class="GtkImage" id="contacts_icon">
227 <property name="visible">True</property>
228 <property name="can_focus">False</property>
229 </object>
230 <packing>
231 <property name="left_attach">1</property>
232 <property name="right_attach">2</property>
233 <property name="x_options"></property>
234 <property name="y_options"></property>
235 </packing>
236 </child>
237 <child>
238 <object class="GtkLabel" id="label4">
239 <property name="visible">True</property>
240 <property name="can_focus">False</property>
241 <property name="xalign">0</property>
242 <property name="label" translatable="yes">Enable Contacts Sync</property>
243 </object>
244 <packing>
245 <property name="left_attach">2</property>
246 <property name="right_attach">3</property>
247 </packing>
248 </child>
249 <child>
250 <object class="GtkLabel" id="label5">
251 <property name="visible">True</property>
252 <property name="can_focus">True</property>
253 <property name="xalign">0</property>
254 <property name="yalign">0</property>
255 <property name="label" translatable="yes">&lt;span font_size="small"&gt;Once enabled, visit the &lt;a href="https://one.ubuntu.com"&gt;Ubuntu One website&lt;/a&gt; to manage your contacts, including Gmail and Facebook import&lt;/span&gt;</property>
256 <property name="use_markup">True</property>
257 <property name="wrap">True</property>
258 </object>
259 <packing>
260 <property name="left_attach">2</property>
261 <property name="right_attach">3</property>
262 <property name="top_attach">1</property>
263 <property name="bottom_attach">2</property>
264 </packing>
265 </child>
266 <child>
267 <placeholder/>
268 </child>
269 <child>
270 <placeholder/>
271 </child>
272 </object>
273 <packing>
274 <property name="expand">False</property>
275 <property name="fill">True</property>
276 <property name="position">0</property>
277 </packing>
278 </child>
279 </object>
280 <packing>
281 <property name="expand">True</property>
282 <property name="fill">True</property>
283 <property name="position">0</property>
284 </packing>
285 </child>
286 <child>
287 <placeholder/>
288 </child>
289 </object>
290 </child>
291 </object>
292 </child>
293 <child type="label_item">
294 <placeholder/>
295 </child>
296 </object>
297 <packing>
298 <property name="expand">False</property>
299 <property name="fill">True</property>
300 <property name="position">1</property>
301 </packing>
302 </child>
303 </object>
304 </child>
305 </object>
306 </child>
307 </object>
308 </child>
309 </object>
310 <packing>
311 <property name="expand">True</property>
312 <property name="fill">True</property>
313 <property name="position">0</property>
314 </packing>
315 </child>
316 </object>
317</interface>
3180
=== removed file 'data/gtk/volumes.ui'
--- data/gtk/volumes.ui 2011-07-11 11:19:09 +0000
+++ data/gtk/volumes.ui 1970-01-01 00:00:00 +0000
@@ -1,98 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <requires lib="gtk+" version="2.22"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkAlignment" id="itself">
6 <property name="visible">True</property>
7 <property name="can_focus">False</property>
8 <child>
9 <object class="GtkScrolledWindow" id="scrolledwindow1">
10 <property name="visible">True</property>
11 <property name="can_focus">True</property>
12 <property name="hscrollbar_policy">automatic</property>
13 <property name="vscrollbar_policy">automatic</property>
14 <property name="shadow_type">in</property>
15 <child>
16 <object class="GtkTreeView" id="volumes_view">
17 <property name="visible">True</property>
18 <property name="can_focus">True</property>
19 <property name="model">volumes_store</property>
20 <property name="rules_hint">True</property>
21 <property name="tooltip_column">0</property>
22 <signal name="row-activated" handler="on_volumes_view_row_activated" swapped="no"/>
23 <child>
24 <object class="GtkTreeViewColumn" id="treeviewcolumn2">
25 <property name="resizable">True</property>
26 <property name="sizing">autosize</property>
27 <property name="expand">True</property>
28 <child>
29 <object class="GtkCellRendererPixbuf" id="cellrendererpixbuf1"/>
30 <attributes>
31 <attribute name="sensitive">1</attribute>
32 <attribute name="icon-name">2</attribute>
33 <attribute name="stock-size">5</attribute>
34 </attributes>
35 </child>
36 <child>
37 <object class="GtkCellRendererText" id="text_renderer">
38 <property name="ellipsize">end</property>
39 <property name="width_chars">80</property>
40 </object>
41 <attributes>
42 <attribute name="markup">0</attribute>
43 <attribute name="text">0</attribute>
44 </attributes>
45 </child>
46 </object>
47 </child>
48 <child>
49 <object class="GtkTreeViewColumn" id="treeviewcolumn3">
50 <property name="sizing">autosize</property>
51 <property name="title" translatable="yes">Sync locally?</property>
52 <child>
53 <object class="GtkCellRendererToggle" id="cellrenderertoggle1">
54 <property name="indicator_size">15</property>
55 <signal name="toggled" handler="on_subscribed_toggled" swapped="no"/>
56 </object>
57 <attributes>
58 <attribute name="sensitive">4</attribute>
59 <attribute name="visible">3</attribute>
60 <attribute name="active">1</attribute>
61 </attributes>
62 </child>
63 <child>
64 <object class="GtkCellRendererText" id="cellrenderertext1">
65 <property name="visible">False</property>
66 </object>
67 <attributes>
68 <attribute name="text">6</attribute>
69 </attributes>
70 </child>
71 </object>
72 </child>
73 </object>
74 </child>
75 </object>
76 </child>
77 </object>
78 <object class="GtkTreeStore" id="volumes_store">
79 <columns>
80 <!-- column-name description -->
81 <column type="gchararray"/>
82 <!-- column-name subscribed -->
83 <column type="gboolean"/>
84 <!-- column-name icon-name -->
85 <column type="gchararray"/>
86 <!-- column-name subscribed-visible -->
87 <column type="gboolean"/>
88 <!-- column-name subscribed-sensitive -->
89 <column type="gboolean"/>
90 <!-- column-name icon-size -->
91 <column type="gint"/>
92 <!-- column-name identifier -->
93 <column type="gchararray"/>
94 <!-- column-name path -->
95 <column type="gchararray"/>
96 </columns>
97 </object>
98</interface>
990
=== removed file 'docs/ubuntuone-control-panel-gtk.1'
--- docs/ubuntuone-control-panel-gtk.1 2011-09-07 13:50:37 +0000
+++ docs/ubuntuone-control-panel-gtk.1 1970-01-01 00:00:00 +0000
@@ -1,15 +0,0 @@
1.TH UBUNTUONE-CONTROL-PANEL-GTK 1
2
3.SH NAME
4ubuntuone-control-panel-gtk \- A GTK UI for managing an Ubuntu One account
5
6.SH SYNOPSYS
7.B ubutuone-control-panel-gtk
8
9.SH DESCRIPTION
10This manual page briefly documents the
11.BR ubuntuone-control-panel-gtk
12process, which provides a desktop application to manage an Ubuntu One account.
13
14.SH AUTHOR
15This manual page was written by Natalia Bidart <natalia.bidart@canonical.com>
160
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2011-09-16 14:37:20 +0000
+++ po/POTFILES.in 2012-03-02 19:03:20 +0000
@@ -1,9 +1,1 @@
1ubuntuone/controlpanel/gui/__init__.py1ubuntuone/controlpanel/gui/__init__.py
2[type: gettext/glade] data/gtk/dashboard.ui
3[type: gettext/glade] data/gtk/device.ui
4[type: gettext/glade] data/gtk/devices.ui
5[type: gettext/glade] data/gtk/install.ui
6[type: gettext/glade] data/gtk/management.ui
7[type: gettext/glade] data/gtk/overview.ui
8[type: gettext/glade] data/gtk/services.ui
9[type: gettext/glade] data/gtk/volumes.ui
102
=== modified file 'run-tests'
--- run-tests 2012-02-24 21:00:56 +0000
+++ run-tests 2012-03-02 19:03:20 +0000
@@ -17,7 +17,6 @@
17# with this program. If not, see <http://www.gnu.org/licenses/>.17# with this program. If not, see <http://www.gnu.org/licenses/>.
1818
19QT_TESTS_PATH="ubuntuone/controlpanel/gui/qt/tests, ubuntuone/controlpanel/gui/qt/main/tests"19QT_TESTS_PATH="ubuntuone/controlpanel/gui/qt/tests, ubuntuone/controlpanel/gui/qt/main/tests"
20GTK_TESTS_PATH=ubuntuone/controlpanel/gui/gtk/tests
21DBUS_TESTS_PATH=ubuntuone/controlpanel/dbustests20DBUS_TESTS_PATH=ubuntuone/controlpanel/dbustests
22WINDOWS_TESTS=test_windows.py21WINDOWS_TESTS=test_windows.py
2322
@@ -39,7 +38,6 @@
39 echo "Please install the 'pep8' package."38 echo "Please install the 'pep8' package."
40 fi39 fi
41}40}
42unset GTK_MODULES
4341
44XVFB_CMDLINE=""42XVFB_CMDLINE=""
45XVFB=$(which xvfb-run)43XVFB=$(which xvfb-run)
@@ -48,17 +46,14 @@
48fi46fi
4947
50echo "*** Running test suite for ""$MODULE"" ***"48echo "*** Running test suite for ""$MODULE"" ***"
51u1trial --reactor=gi -p "$DBUS_TESTS_PATH, $QT_TESTS_PATH, $GTK_TESTS_PATH" -i "$WINDOWS_TESTS" "$MODULE"49u1trial --reactor=gi -p "$DBUS_TESTS_PATH, $QT_TESTS_PATH" -i "$WINDOWS_TESTS" "$MODULE"
5250
53echo "*** Running DBus test suite ***"51echo "*** Running DBus test suite ***"
54u1trial --reactor=glib "$DBUS_TESTS_PATH"52u1trial --reactor=glib "$DBUS_TESTS_PATH"
5553
56echo "*** Running GTK test suite ***"
57$XVFB_CMDLINE u1trial --reactor=glib "$GTK_TESTS_PATH"
58
59echo "*** Running QT test suite for ""$MODULE"" ***"54echo "*** Running QT test suite for ""$MODULE"" ***"
60./setup.py build55./setup.py build
61$XVFB_CMDLINE u1trial -p "$GTK_TESTS_PATH" -i "$WINDOWS_TESTS" --reactor=qt4 --gui "$MODULE"56$XVFB_CMDLINE u1trial -i "$WINDOWS_TESTS" --reactor=qt4 --gui "$MODULE"
62rm -rf _trial_temp57rm -rf _trial_temp
63rm -rf build58rm -rf build
6459
6560
=== modified file 'run-tests.bat'
--- run-tests.bat 2012-01-31 16:55:33 +0000
+++ run-tests.bat 2012-03-02 19:03:20 +0000
@@ -18,7 +18,7 @@
1818
19SET MODULE="ubuntuone"19SET MODULE="ubuntuone"
20SET PYTHONEXEPATH="C:\Python27"20SET PYTHONEXEPATH="C:\Python27"
21SET IGNORE_PATHS="ubuntuone\controlpanel\gui\gtk, ubuntuone\controlpanel\dbustests"21SET IGNORE_PATHS="ubuntuone\controlpanel\dbustests"
22SET IGNORE_MODULES="test_linux.py, test_libsoup.py"22SET IGNORE_MODULES="test_linux.py, test_libsoup.py"
2323
24:: throw the first parameter away if is /skip-lint,24:: throw the first parameter away if is /skip-lint,
2525
=== modified file 'setup.py'
--- setup.py 2012-02-22 11:47:17 +0000
+++ setup.py 2012-03-02 19:03:20 +0000
@@ -39,12 +39,11 @@
3939
40POT_FILE = 'po/ubuntuone-control-panel.pot'40POT_FILE = 'po/ubuntuone-control-panel.pot'
41SERVICE_FILE = 'com.ubuntuone.controlpanel.service'41SERVICE_FILE = 'com.ubuntuone.controlpanel.service'
42GUI_SERVICE_FILE = 'com.ubuntuone.controlpanel.gui.service'
43MESSAGE_ENTRY = 'ubuntuone-control-panel'42MESSAGE_ENTRY = 'ubuntuone-control-panel'
44CONSTANTS = 'ubuntuone/controlpanel/constants.py'43CONSTANTS = 'ubuntuone/controlpanel/constants.py'
4544
46CLEANFILES = [45CLEANFILES = [
47 SERVICE_FILE, GUI_SERVICE_FILE, MESSAGE_ENTRY, CONSTANTS, POT_FILE,46 SERVICE_FILE, MESSAGE_ENTRY, CONSTANTS, POT_FILE,
48 'MANIFEST']47 'MANIFEST']
49QT_UI_DIR = os.path.join('ubuntuone', 'controlpanel', 'gui', 'qt', 'ui')48QT_UI_DIR = os.path.join('ubuntuone', 'controlpanel', 'gui', 'qt', 'ui')
5049
@@ -52,7 +51,7 @@
52def replace_prefix(prefix):51def replace_prefix(prefix):
53 """Replace every '@prefix@' with prefix within 'filename' content."""52 """Replace every '@prefix@' with prefix within 'filename' content."""
54 # replace .service file, DATA_DIR constant53 # replace .service file, DATA_DIR constant
55 for filename in (SERVICE_FILE, GUI_SERVICE_FILE, MESSAGE_ENTRY, CONSTANTS):54 for filename in (SERVICE_FILE, MESSAGE_ENTRY, CONSTANTS):
56 with open(filename + '.in') as in_file:55 with open(filename + '.in') as in_file:
57 content = in_file.read()56 content = in_file.read()
58 with open(filename, 'w') as out_file:57 with open(filename, 'w') as out_file:
@@ -206,7 +205,6 @@
206 'ubuntuone',205 'ubuntuone',
207 'ubuntuone.controlpanel',206 'ubuntuone.controlpanel',
208 'ubuntuone.controlpanel.gui',207 'ubuntuone.controlpanel.gui',
209 'ubuntuone.controlpanel.gui.gtk',
210 'ubuntuone.controlpanel.gui.qt',208 'ubuntuone.controlpanel.gui.qt',
211 'ubuntuone.controlpanel.gui.qt.main',209 'ubuntuone.controlpanel.gui.qt.main',
212 'ubuntuone.controlpanel.gui.qt.ui',210 'ubuntuone.controlpanel.gui.qt.ui',
@@ -218,7 +216,7 @@
218 data_files=[216 data_files=[
219 ('lib/ubuntuone-control-panel',217 ('lib/ubuntuone-control-panel',
220 ['bin/ubuntuone-control-panel-backend']),218 ['bin/ubuntuone-control-panel-backend']),
221 ('share/dbus-1/services/', [SERVICE_FILE, GUI_SERVICE_FILE]),219 ('share/dbus-1/services/', [SERVICE_FILE]),
222 ('share/indicators/messages/applications/', [MESSAGE_ENTRY]),220 ('share/indicators/messages/applications/', [MESSAGE_ENTRY]),
223 ('share/apport/package-hooks/',221 ('share/apport/package-hooks/',
224 ['data/source_ubuntuone-control-panel.py']),222 ['data/source_ubuntuone-control-panel.py']),
225223
=== removed file 'ubuntuone/controlpanel/dbustests/test_gui_service.py'
--- ubuntuone/controlpanel/dbustests/test_gui_service.py 2011-11-21 13:37:07 +0000
+++ ubuntuone/controlpanel/dbustests/test_gui_service.py 1970-01-01 00:00:00 +0000
@@ -1,104 +0,0 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Alejandro J. Cura <alecu@canonical.com>
4# Natalia B. Bidart <nataliabidart@canonical.com>
5# Eric Casteleijn <eric.casteleijn@canonical.com>
6#
7# Copyright 2011 Canonical Ltd.
8#
9# This program is free software: you can redistribute it and/or modify it
10# under the terms of the GNU General Public License version 3, as published
11# by the Free Software Foundation.
12#
13# This program is distributed in the hope that it will be useful, but
14# WITHOUT ANY WARRANTY; without even the implied warranties of
15# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
16# PURPOSE. See the GNU General Public License for more details.
17#
18# You should have received a copy of the GNU General Public License along
19# with this program. If not, see <http://www.gnu.org/licenses/>.
20
21"""Tests for the control panel backend DBus service."""
22
23import dbus
24import mocker
25
26from dbus.mainloop.glib import DBusGMainLoop
27from twisted.internet import defer
28
29from ubuntuone.controlpanel.gui.gtk import gui
30from ubuntuone.devtools.testcases.dbus import DBusTestCase
31from twisted.trial.unittest import TestCase
32
33
34class MockWindow(object):
35 """A mock backend."""
36
37 exception = None
38
39 def __init__(self, switch_to=None, alert=False):
40 self.called = []
41
42 def draw_attention(self):
43 """Draw attention to the control panel."""
44 self.called.append('draw_attention')
45
46 def switch_to(self, panel):
47 """Switch to named panel."""
48 self.called.append(('switch_to', panel))
49
50
51class DBusServiceMockTestCase(TestCase):
52 """Tests for the main function."""
53
54 @defer.inlineCallbacks
55 def setUp(self):
56 yield super(DBusServiceMockTestCase, self).setUp()
57 self.mocker = mocker.Mocker()
58
59 @defer.inlineCallbacks
60 def tearDown(self):
61 yield super(DBusServiceMockTestCase, self).tearDown()
62 self.mocker.restore()
63 self.mocker.verify()
64
65 def test_dbus_service_main(self):
66 """The main method starts the loop and hooks up to DBus."""
67 self.patch(gui, 'ControlPanelWindow', MockWindow)
68 dbus_gmain_loop = self.mocker.replace(
69 "dbus.mainloop.glib.DBusGMainLoop")
70 register_service = self.mocker.replace(
71 "ubuntuone.controlpanel.gui.gtk.gui.register_service")
72 publish_service = self.mocker.replace(
73 "ubuntuone.controlpanel.gui.gtk.gui.publish_service")
74 main = self.mocker.replace("gtk.main")
75 dbus_gmain_loop(set_as_default=True)
76 loop = self.mocker.mock()
77 self.mocker.result(loop)
78 register_service(mocker.ANY)
79 self.mocker.result(True)
80 publish_service(switch_to='', alert=False)
81 main()
82 self.mocker.replay()
83 gui.main()
84
85
86class DBusServiceTestCase(DBusTestCase):
87 """Test for the DBus service."""
88
89 def _set_called(self, *args, **kwargs):
90 """Keep track of function calls, useful for monkeypatching."""
91 self._called = (args, kwargs)
92
93 @defer.inlineCallbacks
94 def setUp(self):
95 """Initialize each test run."""
96 yield super(DBusServiceTestCase, self).setUp()
97 DBusGMainLoop(set_as_default=True)
98 self._called = False
99
100 def test_register_service(self):
101 """The DBus service is successfully registered."""
102 bus = dbus.SessionBus()
103 ret = gui.register_service(bus)
104 self.assertTrue(ret)
1050
=== removed directory 'ubuntuone/controlpanel/gui/gtk'
=== removed file 'ubuntuone/controlpanel/gui/gtk/__init__.py'
--- ubuntuone/controlpanel/gui/gtk/__init__.py 2011-05-24 14:20:18 +0000
+++ ubuntuone/controlpanel/gui/gtk/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,28 +0,0 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""The GTK graphical interface for the control panel for Ubuntu One."""
20
21DBUS_BUS_NAME = 'com.ubuntuone.controlpanel.gui'
22DBUS_PATH = '/gui'
23DBUS_IFACE_GUI = 'com.ubuntuone.controlpanel.gui'
24
25# Unused import main
26# pylint: disable=W0611
27
28from ubuntuone.controlpanel.gui.gtk.gui import main
290
=== removed file 'ubuntuone/controlpanel/gui/gtk/gui.py'
--- ubuntuone/controlpanel/gui/gtk/gui.py 2012-02-06 21:02:54 +0000
+++ ubuntuone/controlpanel/gui/gtk/gui.py 1970-01-01 00:00:00 +0000
@@ -1,1667 +0,0 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
4# Eric Casteleijn <eric.casteleijn@canonical.com>
5#
6# Copyright 2010 Canonical Ltd.
7#
8# This program is free software: you can redistribute it and/or modify it
9# under the terms of the GNU General Public License version 3, as published
10# by the Free Software Foundation.
11#
12# This program is distributed in the hope that it will be useful, but
13# WITHOUT ANY WARRANTY; without even the implied warranties of
14# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15# PURPOSE. See the GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License along
18# with this program. If not, see <http://www.gnu.org/licenses/>.
19
20"""The user interface for the control panel for Ubuntu One."""
21
22from __future__ import division
23
24import os
25
26from functools import wraps
27
28import dbus
29import gtk
30import gobject
31
32from dbus.mainloop.glib import DBusGMainLoop
33from ubuntu_sso import networkstate
34# pylint: disable=E0611,F0401
35from ubuntuone.platform.credentials import (
36 APP_NAME as U1_APP_NAME,
37 CredentialsManagementTool,
38)
39# pylint: enable=E0611,F0401
40
41# Wildcard import ubuntuone.controlpanel.gui
42# pylint: disable=W0401, W0614
43from ubuntuone.controlpanel.gui import *
44# pylint: enable=W0401, W0614
45from ubuntuone.controlpanel.gui.gtk import (
46 DBUS_IFACE_GUI, DBUS_BUS_NAME as DBUS_BUS_NAME_GUI,
47 DBUS_PATH as DBUS_PATH_GUI, package_manager)
48from ubuntuone.controlpanel.gui.gtk.widgets import LabelLoading, PanelTitle
49# Use ubiquity package when ready (LP: #673665)
50from ubuntuone.controlpanel.gui.gtk.widgets import GreyableBin
51
52from ubuntuone.controlpanel import (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH,
53 DBUS_PREFERENCES_IFACE, TRANSLATION_DOMAIN, backend)
54from ubuntuone.controlpanel.backend import (DEVICE_TYPE_PHONE,
55 DEVICE_TYPE_COMPUTER)
56from ubuntuone.controlpanel.dbus_service import bool_str
57from ubuntuone.controlpanel.logger import setup_logging, log_call
58from ubuntuone.controlpanel.utils import (get_data_file,
59 ERROR_TYPE, ERROR_MESSAGE)
60
61
62try:
63 from gi.repository import Unity # pylint: disable=E0611
64 USE_LIBUNITY = True
65 U1_DOTDESKTOP = "ubuntuone-installer.desktop"
66except ImportError:
67 USE_LIBUNITY = False
68
69logger = setup_logging('gtk.gui')
70
71
72WARNING_MARKUP = '<span foreground="%s"><b>%%s</b></span>' % ERROR_COLOR
73
74CP_WMCLASS_NAME = 'ubuntuone-control-panel-gtk'
75CP_WMCLASS_CLASS = 'ubuntuone-installer'
76
77
78def error_handler(*args, **kwargs):
79 """Log errors when calling D-Bus methods in a async way."""
80 logger.error('Error handler received: %r, %r', args, kwargs)
81
82
83def register_service(bus):
84 """Try to register DBus service for making sure we run only one instance.
85
86 Return True if succesfully registered, False if already running.
87 """
88 name = bus.request_name(DBUS_BUS_NAME_GUI,
89 dbus.bus.NAME_FLAG_DO_NOT_QUEUE)
90 return name != dbus.bus.REQUEST_NAME_REPLY_EXISTS
91
92
93def publish_service(window=None, switch_to='', alert=False):
94 """Publish the service on DBus."""
95 if window is None:
96 window = ControlPanelWindow(switch_to=switch_to, alert=alert)
97 return ControlPanelService(window)
98
99
100def main(switch_to='', alert=False):
101 """Hook the DBus listeners and start the main loop."""
102 DBusGMainLoop(set_as_default=True)
103 bus = dbus.SessionBus()
104 if register_service(bus):
105 publish_service(switch_to=switch_to, alert=alert)
106 else:
107 obj = bus.get_object(DBUS_BUS_NAME_GUI, DBUS_PATH_GUI)
108 service = dbus.Interface(obj, dbus_interface=DBUS_IFACE_GUI)
109
110 def gui_error_handler(*args, **kwargs):
111 """Log errors when calling D-Bus methods in a async way."""
112 logger.error('Error handler received: %r, %r', args, kwargs)
113 gtk.main_quit()
114
115 def gui_reply_handler(*args, **kwargs):
116 """Exit when done."""
117 gtk.main_quit()
118
119 service.switch_to_alert(
120 switch_to, alert, reply_handler=gui_reply_handler,
121 error_handler=gui_error_handler)
122
123 gtk.main()
124
125
126def on_size_allocate(widget, allocation, label):
127 """Resize labels according to who 'widget' is being resized."""
128 label.set_size_request(allocation.width - 2, -1)
129
130
131@log_call(logger.debug)
132def uri_hook(button, uri, *args, **kwargs):
133 """Open an URI or do nothing if URI is not an URL."""
134 if uri.startswith('http') or uri.startswith(FILE_URI_PREFIX):
135 gtk.show_uri(None, uri, gtk.gdk.CURRENT_TIME)
136
137
138class ControlPanelMixin(object):
139 """A basic mixin class to provide common functionality to widgets."""
140
141 def __init__(self, filename=None, backend_instance=None):
142 if backend_instance is not None:
143 self.backend = backend_instance
144 else:
145 bus = dbus.SessionBus()
146 try:
147 obj = bus.get_object(DBUS_BUS_NAME,
148 DBUS_PREFERENCES_PATH,
149 follow_name_owner_changes=True)
150 iface = DBUS_PREFERENCES_IFACE
151 self.backend = dbus.Interface(obj, dbus_interface=iface)
152 except dbus.exceptions.DBusException:
153 logger.exception('Can not connect to DBus at %r',
154 (DBUS_BUS_NAME, DBUS_PREFERENCES_PATH))
155 raise
156
157 if filename is not None:
158 builder = gtk.Builder()
159 builder.set_translation_domain(TRANSLATION_DOMAIN)
160 builder.add_from_file(get_data_file(os.path.join('gtk', filename)))
161 builder.connect_signals(self)
162
163 # untested directly
164 for obj in builder.get_objects():
165 name = getattr(obj, 'name', None)
166 if name is None and isinstance(obj, gtk.Buildable):
167 # work around bug lp:507739
168 name = gtk.Buildable.get_name(obj)
169 if name is None:
170 logger.warning("%s has no name (??)", obj)
171 else:
172 setattr(self, name, obj)
173
174 logger.debug('%s: started.', self.__class__.__name__)
175
176 def _set_warning(self, message, label):
177 """Set 'message' as warning in 'label'."""
178 label.set_markup(WARNING_MARKUP % message)
179 label.show()
180
181 def destroy(self):
182 """Cleanup."""
183
184
185class UbuntuOneBin(gtk.VBox):
186 """A Ubuntu One bin."""
187
188 TITLE = ''
189
190 def __init__(self, title=None):
191 gtk.VBox.__init__(self)
192 self._is_processing = False
193
194 if title is None:
195 title = self.TITLE
196
197 title = '<span font_size="large">%s</span>' % title
198 self.title = PanelTitle(markup=title)
199 self.pack_start(self.title, expand=False)
200
201 self.message = LabelLoading(LOADING)
202 self.pack_start(self.message, expand=False)
203
204 self.connect('size-allocate', on_size_allocate, self.title)
205 self.show_all()
206
207 def _get_is_processing(self):
208 """Is this panel processing a request?"""
209 return self._is_processing
210
211 def _set_is_processing(self, new_value):
212 """Set if this panel is processing a request."""
213 if new_value:
214 self.message.start()
215 self.set_sensitive(False)
216 else:
217 self.message.stop()
218 self.set_sensitive(True)
219
220 self._is_processing = new_value
221
222 is_processing = property(fget=_get_is_processing, fset=_set_is_processing)
223
224 @log_call(logger.debug)
225 def on_success(self, message=''):
226 """Use this callback to stop the Loading and show 'message'."""
227 self.message.stop()
228 self.message.set_markup(message)
229
230 @log_call(logger.error)
231 def on_error(self, message=None, error_dict=None):
232 """Use this callback to stop the Loading and set a warning message."""
233 if message is None and error_dict is None:
234 message = VALUE_ERROR
235 elif message is None and error_dict is not None:
236 error_type = error_dict.get(ERROR_TYPE, UNKNOWN_ERROR)
237 error_msg = error_dict.get(ERROR_MESSAGE)
238 if error_msg:
239 message = "%s (%s: %s)" % (VALUE_ERROR, error_type, error_msg)
240 else:
241 message = "%s (%s)" % (VALUE_ERROR, error_type)
242
243 assert message is not None
244
245 self.message.stop()
246 self.message.set_markup(WARNING_MARKUP % message)
247
248
249class OverviewPanel(GreyableBin, ControlPanelMixin):
250 """The overview panel. Introduces Ubuntu One to the not logged user."""
251
252 __gsignals__ = {
253 'credentials-found': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
254 (gobject.TYPE_BOOLEAN,)),
255 }
256
257 def __init__(self, main_window):
258 GreyableBin.__init__(self)
259 creds_backend = CredentialsManagementTool()
260 ControlPanelMixin.__init__(self, filename='overview.ui',
261 backend_instance=creds_backend)
262 self.add(self.itself)
263 self.banner.set_from_file(get_data_file(OVERVIEW_BANNER))
264 self.files_icon.set_from_file(get_data_file(FILES_ICON))
265 self.music_stream_icon.set_from_file(get_data_file(MUSIC_STREAM_ICON))
266 self.contacts_icon.set_from_file(get_data_file(CONTACTS_ICON))
267 self.notes_icon.set_from_file(get_data_file(NOTES_ICON))
268
269 self.warning_label.set_text('')
270 self.warning_label.set_property('xalign', 0.5)
271
272 self.connect_button.set_uri(CONNECT_BUTTON_LABEL)
273
274 self.main_window = main_window
275 self._credentials_are_new = False
276 self.show()
277
278 kw = dict(result_cb=self.on_network_state_changed)
279 self.network_manager_state = networkstate.NetworkManagerState(**kw)
280 self.network_manager_state.find_online_state()
281
282 def _set_warning(self, message, label=None):
283 """Set 'message' as global warning."""
284 ControlPanelMixin._set_warning(self, message,
285 label=self.warning_label)
286
287 def _window_xid(self):
288 """Return settings for credentials backend."""
289 if self.main_window.window is not None:
290 settings = {'window_id': str(self.main_window.window.xid)}
291 else:
292 settings = {}
293 return settings
294
295 def set_property(self, prop_name, new_value):
296 """Override 'set_property' to disable buttons if prop is 'greyed'."""
297 if prop_name == 'greyed':
298 self.set_sensitive(not new_value)
299 GreyableBin.set_property(self, prop_name, new_value)
300
301 def set_sensitive(self, value):
302 """Set the sensitiveness as per 'value'."""
303 self.join_now_button.set_sensitive(value)
304 self.connect_button.set_sensitive(value)
305
306 def get_sensitive(self):
307 """Return the sensitiveness."""
308 result = self.join_now_button.get_sensitive() and \
309 self.connect_button.get_sensitive()
310 return result
311
312 def on_join_now_button_clicked(self, *a, **kw):
313 """User wants to join now."""
314 d = self.backend.register(**self._window_xid())
315 d.addCallback(self.on_credentials_result)
316 d.addErrback(self.on_credentials_error)
317 self.set_property('greyed', True)
318 self.warning_label.set_text('')
319
320 def on_connect_button_clicked(self, *a, **kw):
321 """User wants to connect now."""
322 d = self.backend.login(**self._window_xid())
323 d.addCallback(self.on_credentials_result)
324 d.addErrback(self.on_credentials_error)
325 self.set_property('greyed', True)
326 self.warning_label.set_text('')
327
328 def on_learn_more_button_clicked(self, *a, **kw):
329 """User wants to learn more."""
330 uri_hook(self.learn_more_button, LEARN_MORE_LINK)
331
332 def on_credentials_result(self, result):
333 """Process the credentials response.
334
335 If 'result' is a non empty dict, they were found.
336 If 'result' is an empty dict, they were not found.
337 If 'result' is None, the user cancelled the process.
338
339 """
340 if result is None:
341 self.on_authorization_denied()
342 elif result == {}:
343 self.on_credentials_not_found()
344 else:
345 self.on_credentials_found(result)
346
347 @log_call(logger.info, with_args=False)
348 def on_credentials_found(self, credentials):
349 """Credentials backend notifies of credentials found."""
350 self.set_property('greyed', False)
351 self.emit('credentials-found', self._credentials_are_new)
352
353 @log_call(logger.info)
354 def on_credentials_not_found(self):
355 """Creds backend notifies of credentials not found."""
356 self._credentials_are_new = True
357 self.set_property('greyed', False)
358
359 @log_call(logger.error)
360 def on_credentials_error(self, error_dict):
361 """Creds backend notifies of an error when fetching credentials."""
362 self.set_property('greyed', False)
363 self._set_warning(CREDENTIALS_ERROR)
364
365 @log_call(logger.info)
366 def on_authorization_denied(self):
367 """Creds backend notifies that user refused auth for 'app_name'."""
368 self.set_property('greyed', False)
369
370 @log_call(logger.info)
371 def on_network_state_changed(self, state):
372 """Network state is reported."""
373 msg = ''
374 if state is networkstate.OFFLINE:
375 msg = NETWORK_OFFLINE % {'app_name': U1_APP_NAME}
376 self.set_sensitive(False)
377 self._set_warning(msg)
378 else:
379 self.set_sensitive(True)
380 self.warning_label.set_text(msg)
381 d = self.backend.find_credentials()
382 d.addCallback(self.on_credentials_result)
383 d.addErrback(self.on_credentials_error)
384
385
386class DashboardPanel(UbuntuOneBin, ControlPanelMixin):
387 """The dashboard panel. The user can manage the subscription."""
388
389 TITLE = DASHBOARD_TITLE
390 VALUE_ERROR = DASHBOARD_VALUE_ERROR
391
392 def __init__(self, main_window=None):
393 UbuntuOneBin.__init__(self)
394 ControlPanelMixin.__init__(self, filename='dashboard.ui')
395 self.add(self.itself)
396 self.show()
397
398 self.is_processing = True
399
400 self.backend.connect_to_signal('AccountInfoReady',
401 self.on_account_info_ready)
402 self.backend.connect_to_signal('AccountInfoError',
403 self.on_account_info_error)
404 self.account.hide()
405
406 @log_call(logger.debug)
407 def on_account_info_ready(self, info):
408 """Backend notifies of account info."""
409 self.on_success()
410
411 for i in (u'name', u'type', u'email'):
412 label = getattr(self, '%s_label' % i)
413 label.set_markup('%s' % (info[i]))
414 self.account.show()
415
416 self.is_processing = False
417
418 @log_call(logger.error)
419 def on_account_info_error(self, error_dict=None):
420 """Backend notifies of an error when fetching account info."""
421 self.on_error(message=self.VALUE_ERROR)
422 self.is_processing = False
423
424
425class VolumesPanel(UbuntuOneBin, ControlPanelMixin):
426 """The volumes panel."""
427
428 TITLE = FOLDERS_TITLE
429 MAX_COLS = 8
430 FREE_SPACE = '<span foreground="grey">%s</span>' % FREE_SPACE_TEXT
431 NO_FREE_SPACE = '<span foreground="red"><b>%s</b></span>' % FREE_SPACE_TEXT
432 ROW_HEADER = '<span font_size="large"><b>%s</b></span> %s'
433 ROOT = '%s - <span foreground="%s" font_size="small">%s</span>'
434
435 def __init__(self, main_window=None):
436 UbuntuOneBin.__init__(self)
437 ControlPanelMixin.__init__(self, filename='volumes.ui')
438 self.add(self.itself)
439 self.show_all()
440
441 kw = dict(parent=main_window,
442 flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
443 type=gtk.MESSAGE_WARNING,
444 buttons=gtk.BUTTONS_YES_NO)
445 self.confirm_dialog = gtk.MessageDialog(**kw)
446
447 # name, subscribed, icon name, show toggle, sensitive, icon size,
448 # id, path
449 self._empty_row = ('', False, '', False, False, gtk.ICON_SIZE_MENU,
450 None, None)
451
452 self.backend.connect_to_signal('VolumesInfoReady',
453 self.on_volumes_info_ready)
454 self.backend.connect_to_signal('VolumesInfoError',
455 self.on_volumes_info_error)
456 self.backend.connect_to_signal('VolumeSettingsChanged',
457 self.on_volume_settings_changed)
458 self.backend.connect_to_signal('VolumeSettingsChangeError',
459 self.on_volume_settings_change_error)
460
461 def _process_name(self, name):
462 """Tweak 'name' with a translatable music folder name."""
463 if name == MUSIC_REAL_PATH:
464 result = MUSIC_DISPLAY_NAME
465 else:
466 result = name
467 return result
468
469 def on_volumes_info_ready(self, info):
470 """Backend notifies of volumes info."""
471
472 self.volumes_store.clear()
473 if not info:
474 self.on_success(NO_FOLDERS)
475 return
476 else:
477 self.on_success()
478
479 for name, free_bytes, volumes in info:
480 if backend.ControlBackend.NAME_NOT_SET in name:
481 name = NAME_NOT_SET
482
483 if name:
484 name = name + "'s"
485 # we already added user folders, let's add an empty row
486 treeiter = self.volumes_store.append(None, self._empty_row)
487 else:
488 name = MY_FOLDERS
489
490 scroll_to_cell = False
491 if free_bytes == backend.ControlBackend.FREE_BYTES_NOT_AVAILABLE:
492 free_bytes = ''
493 else:
494 free_bytes = int(free_bytes)
495 if free_bytes < SHARES_MIN_SIZE_FULL:
496 free_bytes_str = self.NO_FREE_SPACE
497 scroll_to_cell = True
498 else:
499 free_bytes_str = self.FREE_SPACE
500 free_bytes_args = {'free_space': humanize(free_bytes)}
501 free_bytes = free_bytes_str % free_bytes_args
502
503 row = (self.ROW_HEADER % (name, free_bytes),
504 True, CONTACT_ICON_NAME, False, False,
505 gtk.ICON_SIZE_LARGE_TOOLBAR, None, None)
506 treeiter = self.volumes_store.append(None, row)
507
508 if scroll_to_cell:
509 path = self.volumes_store.get_string_from_iter(treeiter)
510 self.volumes_view.scroll_to_cell(path)
511
512 for volume in volumes:
513 sensitive = True
514 name = self._process_name(volume[u'display_name'])
515 icon_name = FOLDER_ICON_NAME
516
517 is_root = volume[u'type'] == backend.ControlBackend.ROOT_TYPE
518 is_share = volume[u'type'] == backend.ControlBackend.SHARE_TYPE
519
520 if is_root:
521 sensitive = False
522 name = self.ROOT % (name, ORANGE, ALWAYS_SUBSCRIBED)
523 elif is_share:
524 icon_name = SHARE_ICON_NAME
525 elif name == MUSIC_DISPLAY_NAME:
526 icon_name = MUSIC_ICON_NAME
527
528 if volume[u'path'] is None:
529 logger.warning('on_volumes_info_ready: about to store a '
530 'volume with None path: %r', volume)
531
532 row = (name, bool(volume[u'subscribed']), icon_name, True,
533 sensitive, gtk.ICON_SIZE_MENU, volume['volume_id'],
534 volume[u'path'])
535
536 if is_root: # root should go first!
537 self.volumes_store.prepend(treeiter, row)
538 else:
539 self.volumes_store.append(treeiter, row)
540
541 self.volumes_view.expand_all()
542 self.volumes_view.show_all()
543
544 self.is_processing = False
545
546 @log_call(logger.error)
547 def on_volumes_info_error(self, error_dict=None):
548 """Backend notifies of an error when fetching volumes info."""
549 self.on_error(error_dict=error_dict)
550
551 @log_call(logger.info)
552 def on_volume_settings_changed(self, volume_id):
553 """The settings for 'volume_id' were changed."""
554 self.is_processing = False
555
556 @log_call(logger.error)
557 def on_volume_settings_change_error(self, volume_id, error_dict=None):
558 """The settings for 'volume_id' were not changed."""
559 self.load()
560
561 def on_subscribed_toggled(self, widget, path, *args, **kwargs):
562 """The user toggled 'widget'."""
563 treeiter = self.volumes_store.get_iter(path)
564 volume_id = self.volumes_store.get_value(treeiter, 6)
565 volume_path = self.volumes_store.get_value(treeiter, 7)
566 subscribed = self.volumes_store.get_value(treeiter, 1)
567
568 response = gtk.RESPONSE_YES
569 if not subscribed and os.path.exists(volume_path):
570 self.confirm_dialog.set_markup(FOLDERS_CONFIRM_MERGE %
571 {'folder_path': volume_path})
572 response = self.confirm_dialog.run()
573 self.confirm_dialog.hide()
574
575 if response == gtk.RESPONSE_YES:
576 subscribed = not subscribed
577 self.volumes_store.set_value(treeiter, 1, subscribed)
578 self.backend.change_volume_settings(volume_id,
579 {'subscribed': bool_str(subscribed)},
580 reply_handler=NO_OP, error_handler=error_handler)
581
582 self.is_processing = True
583
584 def on_volumes_view_row_activated(self, widget, path, *args, **kwargs):
585 """The user double clicked on a row."""
586 treeiter = self.volumes_store.get_iter(path)
587 volume_path = self.volumes_store.get_value(treeiter, 7)
588 if volume_path is None:
589 logger.warning('on_volumes_view_row_activated: volume_path for '
590 'tree_path %r is None', path)
591 elif not os.path.exists(volume_path):
592 logger.warning('on_volumes_view_row_activated: path %r '
593 'does not exist', volume_path)
594 else:
595 uri_hook(None, FILE_URI_PREFIX + volume_path)
596
597 def load(self):
598 """Load the volume list."""
599 self.backend.volumes_info(reply_handler=NO_OP,
600 error_handler=error_handler)
601 self.is_processing = True
602
603
604class SharesPanel(UbuntuOneBin, ControlPanelMixin):
605 """The shares panel - NOT IMPLEMENTED YET."""
606
607 TITLE = SHARES_TITLE
608
609 def __init__(self, main_window=None):
610 UbuntuOneBin.__init__(self)
611 ControlPanelMixin.__init__(self)
612 self.show_all()
613 self.on_success('Not implemented yet.')
614
615
616class Device(gtk.EventBox, ControlPanelMixin):
617 """The device widget."""
618
619 def __init__(self, confirm_remove_dialog=None):
620 gtk.EventBox.__init__(self)
621 ControlPanelMixin.__init__(self, filename='device.ui')
622
623 self.confirm_dialog = confirm_remove_dialog
624 self._updating = False
625 self._last_settings = {}
626 self.id = None
627 self.is_local = False
628 self.configurable = False
629
630 self.update(device_id=None, device_name='',
631 is_local=False, configurable=False, limit_bandwidth=False,
632 max_upload_speed=0, max_download_speed=0,
633 show_all_notifications=True)
634
635 self.add(self.itself)
636 self.show()
637
638 self.backend.connect_to_signal('DeviceSettingsChanged',
639 self.on_device_settings_changed)
640 self.backend.connect_to_signal('DeviceSettingsChangeError',
641 self.on_device_settings_change_error)
642 self.backend.connect_to_signal('DeviceRemoved',
643 self.on_device_removed)
644 self.backend.connect_to_signal('DeviceRemovalError',
645 self.on_device_removal_error)
646
647 def _change_device_settings(self, *args):
648 """Update backend settings for this device."""
649 if self._updating:
650 return
651
652 # Not disabling the GUI to avoid annyong twitchings
653 #self.set_sensitive(False)
654 self.warning_label.set_text('')
655 self.backend.change_device_settings(self.id, self.__dict__,
656 reply_handler=NO_OP, error_handler=error_handler)
657
658 def _block_signals(f):
659 """Execute 'f' while having the _updating flag set."""
660
661 # pylint: disable=E0213,W0212,E1102
662
663 @wraps(f)
664 def inner(self, *args, **kwargs):
665 """Execute 'f' while having the _updating flag set."""
666 old = self._updating
667 self._updating = True
668
669 result = f(self, *args, **kwargs)
670
671 self._updating = old
672 return result
673
674 return inner
675
676 on_show_all_notifications_toggled = _change_device_settings
677 on_max_upload_speed_value_changed = _change_device_settings
678 on_max_download_speed_value_changed = _change_device_settings
679
680 def on_limit_bandwidth_toggled(self, *args, **kwargs):
681 """The limit bandwidth checkbox was toggled."""
682 self.throttling_limits.set_sensitive(self.limit_bandwidth.get_active())
683 self._change_device_settings()
684
685 def on_remove_clicked(self, widget):
686 """Remove button was clicked or activated."""
687 response = gtk.RESPONSE_YES
688 if self.confirm_dialog is not None:
689 response = self.confirm_dialog.run()
690 self.confirm_dialog.hide()
691
692 if response == gtk.RESPONSE_YES:
693 self.backend.remove_device(self.id,
694 reply_handler=NO_OP, error_handler=error_handler)
695 self.set_sensitive(False)
696
697 @_block_signals
698 def update(self, **kwargs):
699 """Update according to named parameters.
700
701 Possible settings are:
702 * device_id (string, not shown to the user)
703 * device_name (string)
704 * type (either DEVICE_TYPE_PHONE or DEVICE_TYPE_COMPUTER)
705 * is_local (True/False)
706 * configurable (True/False)
707 * if configurable, the following can be set:
708 * show_all_notifications (True/False)
709 * limit_bandwidth (True/False)
710 * max_upload_speed (bytes)
711 * max_download_speed (bytes)
712
713 """
714 if 'device_id' in kwargs:
715 self.id = kwargs['device_id']
716
717 if 'device_name' in kwargs:
718 name = kwargs['device_name'].replace(DEVICE_REMOVABLE_PREFIX, '')
719 name = '<span font_size="large"><b>%s</b></span>' % name
720 self.device_name.set_markup(name)
721
722 if 'device_type' in kwargs:
723 dtype = kwargs['device_type']
724 if dtype in (DEVICE_TYPE_COMPUTER, DEVICE_TYPE_PHONE):
725 self.device_type.set_from_icon_name(dtype.lower(),
726 gtk.ICON_SIZE_LARGE_TOOLBAR)
727
728 if 'is_local' in kwargs:
729 self.is_local = bool(kwargs['is_local'])
730
731 if 'configurable' in kwargs:
732 self.configurable = bool(kwargs['configurable'])
733 self.config_settings.set_visible(self.configurable)
734
735 if 'show_all_notifications' in kwargs:
736 value = bool(kwargs['show_all_notifications'])
737 self.show_all_notifications.set_active(value)
738
739 if 'limit_bandwidth' in kwargs:
740 enabled = bool(kwargs['limit_bandwidth'])
741 self.limit_bandwidth.set_active(enabled)
742 self.throttling_limits.set_sensitive(enabled)
743
744 for speed in ('max_upload_speed', 'max_download_speed'):
745 if speed in kwargs:
746 value = int(kwargs[speed]) // KILOBYTES
747 getattr(self, speed).set_value(value)
748
749 self._last_settings = self.__dict__
750
751 @property
752 def __dict__(self):
753 result = {
754 'device_id': self.id,
755 'device_name': self.device_name.get_text(),
756 'device_type': self.device_type.get_icon_name()[0].capitalize(),
757 'is_local': bool_str(self.is_local),
758 'configurable': bool_str(self.configurable),
759 'show_all_notifications': \
760 bool_str(self.show_all_notifications.get_active()),
761 'limit_bandwidth': bool_str(self.limit_bandwidth.get_active()),
762 'max_upload_speed': \
763 str(self.max_upload_speed.get_value_as_int() * KILOBYTES),
764 'max_download_speed': \
765 str(self.max_download_speed.get_value_as_int() * KILOBYTES),
766 }
767 return result
768
769 @log_call(logger.info, with_args=False)
770 def on_device_settings_changed(self, device_id):
771 """The change of this device settings succeded."""
772 if device_id != self.id:
773 return
774 self.set_sensitive(True)
775 self.warning_label.set_text('')
776 self._last_settings = self.__dict__
777
778 @log_call(logger.error)
779 def on_device_settings_change_error(self, device_id, error_dict=None):
780 """The change of this device settings failed."""
781 if device_id != self.id:
782 return
783 self.update(**self._last_settings)
784 self._set_warning(DEVICE_CHANGE_ERROR, self.warning_label)
785 self.set_sensitive(True)
786
787 # is safe to log the device_id since it was already removed
788 @log_call(logger.warning)
789 def on_device_removed(self, device_id):
790 """The removal of this device succeded."""
791 if device_id != self.id:
792 return
793 self.hide()
794
795 @log_call(logger.error)
796 def on_device_removal_error(self, device_id, error_dict=None):
797 """The removal of this device failed."""
798 if device_id != self.id:
799 return
800 self._set_warning(DEVICE_REMOVAL_ERROR, self.warning_label)
801 self.set_sensitive(True)
802
803
804class DevicesPanel(UbuntuOneBin, ControlPanelMixin):
805 """The devices panel."""
806
807 __gsignals__ = {
808 'local-device-removed': (gobject.SIGNAL_RUN_FIRST,
809 gobject.TYPE_NONE, ()),
810 }
811
812 TITLE = DEVICES_TITLE
813
814 def __init__(self, main_window=None):
815 UbuntuOneBin.__init__(self)
816 ControlPanelMixin.__init__(self, filename='devices.ui')
817 self.add(self.itself)
818 self.show()
819
820 self._devices = {}
821 kw = dict(parent=main_window,
822 flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
823 type=gtk.MESSAGE_WARNING,
824 buttons=gtk.BUTTONS_YES_NO,
825 message_format=DEVICE_CONFIRM_REMOVE)
826 self.confirm_remove_dialog = gtk.MessageDialog(**kw)
827
828 self.backend.connect_to_signal('DevicesInfoReady',
829 self.on_devices_info_ready)
830 self.backend.connect_to_signal('DevicesInfoError',
831 self.on_devices_info_error)
832 self.backend.connect_to_signal('DeviceRemoved',
833 self.on_device_removed)
834
835 @log_call(logger.info, with_args=False)
836 def on_devices_info_ready(self, info):
837 """Backend notifies of devices info."""
838 for child in self.devices.get_children():
839 self.devices.remove(child)
840
841 if not info:
842 self.on_success(NO_DEVICES)
843 else:
844 self.on_success()
845
846 # Class 'style' has no 'bg' member
847 # pylint: disable=E1101
848 odd_row_color = self.message.style.bg[gtk.STATE_NORMAL]
849 for i, device_info in enumerate(info):
850 device = Device(confirm_remove_dialog=self.confirm_remove_dialog)
851 device_info['device_name'] = device_info.pop('name', '')
852 device_info['device_type'] = device_info.pop('type',
853 DEVICE_TYPE_COMPUTER)
854 device.update(**device_info)
855
856 if i % 2 == 1:
857 device.modify_bg(gtk.STATE_NORMAL, odd_row_color)
858
859 self.devices.pack_start(device)
860 self._devices[device.id] = device
861
862 self.is_processing = False
863
864 @log_call(logger.error)
865 def on_devices_info_error(self, error_dict=None):
866 """Backend notifies of an error when fetching volumes info."""
867 self.on_error(error_dict=error_dict)
868 self.is_processing = False
869
870 @log_call(logger.warning)
871 def on_device_removed(self, device_id):
872 """The removal of a device succeded."""
873 if device_id in self._devices:
874 child = self._devices.pop(device_id)
875 self.devices.remove(child)
876
877 if child.is_local:
878 self.emit('local-device-removed')
879
880 def load(self):
881 """Load the device list."""
882 self.backend.devices_info(reply_handler=NO_OP,
883 error_handler=error_handler)
884 self.is_processing = True
885
886
887class InstallPackage(gtk.VBox, ControlPanelMixin):
888 """A widget to process the install of a package."""
889
890 __gsignals__ = {
891 'finished': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()),
892 }
893
894 def __init__(self, package_name, message=None):
895 gtk.VBox.__init__(self)
896 ControlPanelMixin.__init__(self, filename='install.ui')
897 self.add(self.itself)
898
899 self.package_name = package_name
900 self.package_manager = package_manager.PackageManager()
901 self.args = {'package_name': self.package_name}
902 self.transaction = None
903
904 self.progress_bar = None
905
906 self.message = message
907 if self.message is None:
908 self.message = INSTALL_PACKAGE % self.args
909 self.reset()
910
911 self.show()
912
913 def reset(self):
914 """Reset this interface."""
915 children = self.itself.get_children()
916 if self.progress_bar in children:
917 self.itself.remove(self.progress_bar)
918 if self.install_button_box not in children:
919 self.itself.pack_start(self.install_button_box)
920 self.install_label.set_markup(self.message)
921
922 @package_manager.inline_callbacks
923 def on_install_button_clicked(self, button):
924 """The install button was clicked."""
925 try:
926 # create the install transaction
927 self.transaction = yield self.package_manager.install(
928 self.package_name)
929
930 logger.debug('on_install_button_clicked: transaction is %r',
931 self.transaction)
932 success = package_manager.aptdaemon.enums.EXIT_SUCCESS
933 if self.transaction == success:
934 self.on_install_finished(None, self.transaction)
935 return
936
937 # create the progress bar and pack it to the box
938 self.progress_bar = package_manager.PackageManagerProgressBar(
939 self.transaction)
940 self.progress_bar.show()
941
942 self.itself.remove(self.install_button_box)
943 self.itself.pack_start(self.progress_bar)
944
945 self.transaction.connect('finished', self.on_install_finished)
946 self.install_label.set_markup(INSTALLING % self.args)
947 yield self.transaction.run()
948 except package_manager.aptdaemon.errors.NotAuthorizedError:
949 self.reset()
950 except: # pylint: disable=W0702
951 logger.exception('on_install_button_clicked')
952 self._set_warning(FAILED_INSTALL % self.args,
953 self.install_label)
954 if self.progress_bar is not None:
955 self.progress_bar.hide()
956
957 @log_call(logger.info)
958 def on_install_finished(self, transaction, exit_code):
959 """The installation finished."""
960 if self.progress_bar is not None:
961 self.progress_bar.set_sensitive(False)
962
963 logger.info('on_install_finished: installation of %r was %r',
964 self.package_name, exit_code)
965 if exit_code != package_manager.aptdaemon.enums.EXIT_SUCCESS:
966 if hasattr(transaction, 'error'):
967 logger.error('transaction failed: %r', transaction.error)
968 self._set_warning(FAILED_INSTALL % self.args,
969 self.install_label)
970 else:
971 self.install_label.set_markup(SUCCESS_INSTALL % self.args)
972 self.emit('finished')
973
974
975class Service(gtk.VBox, ControlPanelMixin):
976 """A service."""
977
978 def __init__(self, service_id, name,
979 container=None, check_button=None, action_button=None,
980 *args, **kwargs):
981 gtk.VBox.__init__(self)
982 ControlPanelMixin.__init__(self)
983 self.id = service_id
984 self.container = container
985 self.check_button = check_button
986 self.action_button = action_button
987
988 self.warning_label = gtk.Label()
989 self.pack_start(self.warning_label, expand=False)
990
991 self.button = gtk.CheckButton(label=name)
992 self.pack_start(self.button, expand=False)
993
994 self.show_all()
995
996
997class FileSyncService(Service):
998 """The file sync service."""
999
1000 def __init__(self, container, check_button, action_button):
1001 Service.__init__(self, service_id='file-sync',
1002 name=FILE_SYNC_SERVICE_NAME,
1003 container=container,
1004 check_button=check_button,
1005 action_button=action_button)
1006
1007 self.container.set_sensitive(False)
1008
1009 self.backend.connect_to_signal('FileSyncStatusChanged',
1010 self.on_file_sync_status_changed)
1011 self.backend.connect_to_signal('FilesEnabled', self.on_files_enabled)
1012 self.backend.connect_to_signal('FilesDisabled', self.on_files_disabled)
1013
1014 @log_call(logger.debug)
1015 def on_file_sync_status_changed(self, status):
1016 """File Sync status changed."""
1017 enabled = status != backend.FILE_SYNC_DISABLED
1018 logger.info('FileSyncService: on_file_sync_status_changed: '
1019 'status %r, enabled? %r', status, enabled)
1020 self.check_button.set_active(enabled)
1021 # if service is disabled, disable the action_button
1022 self.action_button.set_sensitive(enabled)
1023
1024 if not self.container.is_sensitive():
1025 # first time we're getting this event
1026 self.check_button.connect('toggled', self.on_button_toggled)
1027 self.container.set_sensitive(True)
1028
1029 def on_files_enabled(self):
1030 """Files service was enabled."""
1031 self.on_file_sync_status_changed('enabled!')
1032
1033 def on_files_disabled(self):
1034 """Files service was disabled."""
1035 self.on_file_sync_status_changed(backend.FILE_SYNC_DISABLED)
1036
1037 @log_call(logger.debug)
1038 def on_button_toggled(self, button):
1039 """Button was toggled, exclude/replicate the service properly."""
1040 logger.info('File Sync enabled? %r', self.check_button.get_active())
1041 if self.check_button.get_active():
1042 self.backend.enable_files(reply_handler=NO_OP,
1043 error_handler=error_handler)
1044 else:
1045 self.backend.disable_files(reply_handler=NO_OP,
1046 error_handler=error_handler)
1047
1048 def load(self):
1049 """Load the information."""
1050 self.backend.file_sync_status(reply_handler=NO_OP,
1051 error_handler=error_handler)
1052
1053
1054class DesktopcouchService(Service):
1055 """A desktopcouch service."""
1056
1057 def __init__(self, service_id, name, enabled,
1058 container, check_button,
1059 dependency=None, dependency_name=None):
1060 Service.__init__(self, service_id, name,
1061 container, check_button, action_button=None)
1062
1063 self.backend.connect_to_signal('ReplicationSettingsChanged',
1064 self.on_replication_settings_changed)
1065 self.backend.connect_to_signal('ReplicationSettingsChangeError',
1066 self.on_replication_settings_change_error)
1067
1068 self.check_button.set_active(enabled)
1069
1070 self.dependency = None
1071 if dependency is not None:
1072 if dependency_name is None:
1073 dependency_name = dependency
1074 args = {'plugin_name': dependency_name, 'service_name': service_id}
1075 message = INSTALL_PLUGIN % args
1076 self.dependency = InstallPackage(dependency, message)
1077 self.dependency.connect('finished', self.on_depedency_finished)
1078
1079 self.container.pack_end(self.dependency, expand=False)
1080 self.check_button.set_sensitive(False)
1081
1082 self.check_button.connect('toggled', self.on_button_toggled)
1083
1084 def on_depedency_finished(self, widget):
1085 """The dependency was installed."""
1086 self.check_button.set_sensitive(True)
1087 self.container.remove(self.dependency)
1088 self.dependency = None
1089
1090 @log_call(logger.debug)
1091 def on_button_toggled(self, button):
1092 """Button was toggled, exclude/replicate the service properly."""
1093 logger.info('Starting replication for %r? %r',
1094 self.id, self.check_button.get_active())
1095
1096 args = {'enabled': bool_str(self.check_button.get_active())}
1097 self.backend.change_replication_settings(self.id, args,
1098 reply_handler=NO_OP, error_handler=error_handler)
1099
1100 @log_call(logger.info)
1101 def on_replication_settings_changed(self, replication_id):
1102 """The change of settings for this replication succeded."""
1103 if replication_id != self.id:
1104 return
1105 self.warning_label.set_text('')
1106
1107 @log_call(logger.error)
1108 def on_replication_settings_change_error(self, replication_id,
1109 error_dict=None):
1110 """The change of settings for this replication failed."""
1111 if replication_id != self.id:
1112 return
1113 self.check_button.set_active(not self.check_button.get_active())
1114 self._set_warning(SETTINGS_CHANGE_ERROR, self.warning_label)
1115
1116
1117class ServicesPanel(UbuntuOneBin, ControlPanelMixin):
1118 """The services panel."""
1119
1120 TITLE = SERVICES_TITLE
1121
1122 def __init__(self, main_window=None):
1123 UbuntuOneBin.__init__(self)
1124 ControlPanelMixin.__init__(self, filename='services.ui')
1125 self.add(self.itself)
1126 self.files_icon.set_from_file(get_data_file(SERVICES_FILES_ICON))
1127 self.files_example.set_from_file(get_data_file(SERVICES_FILES_EXAMPLE))
1128 self.contacts_icon.set_from_file(get_data_file(SERVICES_CONTACTS_ICON))
1129
1130 self.plugin_names = {'contacts': CONTACTS}
1131
1132 self.package_manager = package_manager.PackageManager()
1133 self.install_box = None
1134
1135 self._replications_ready = False # hack to solve LP: #750309
1136 self.backend.connect_to_signal('ReplicationsInfoReady',
1137 self.on_replications_info_ready)
1138 self.backend.connect_to_signal('ReplicationsInfoError',
1139 self.on_replications_info_error)
1140
1141 self.file_sync_service = FileSyncService(container=self.files,
1142 check_button=self.file_sync_check,
1143 action_button=self.file_sync_button)
1144
1145 self.show()
1146
1147 @property
1148 def has_desktopcouch(self):
1149 """Is desktopcouch installed?"""
1150 return self.package_manager.is_installed(DESKTOPCOUCH_PKG)
1151
1152 def on_file_sync_button_clicked(self, *args, **kwargs):
1153 """The "Show me my U1 folder" button was clicked.
1154
1155 XXX: this should be part of the FileSyncService widget.
1156 XXX: the Ubuntu One folder should be the user's root.
1157
1158 """
1159 uri_hook(None, FILE_URI_PREFIX + os.path.expanduser('~/Ubuntu One'))
1160
1161 def on_contacts_button_clicked(self, *args, **kwargs):
1162 """The "Take me to the Ubuntu One website" button was clicked.
1163
1164 XXX: this should be part of the DesktopcouchService widget.
1165
1166 """
1167 uri_hook(None, CONTACTS)
1168
1169 @log_call(logger.debug)
1170 def load(self):
1171 """Load info."""
1172 self.file_sync_service.load()
1173 self.replications.hide()
1174 if self.install_box is not None:
1175 self.itself.remove(self.install_box)
1176 self.install_box = None
1177
1178 logger.info('load: has_desktopcouch? %r', self.has_desktopcouch)
1179 if not self.has_desktopcouch:
1180 self.message.set_text('')
1181
1182 self.install_box = InstallPackage(DESKTOPCOUCH_PKG)
1183 self.install_box.connect('finished', self.load_replications)
1184 self.itself.pack_end(self.install_box, expand=False)
1185 self.itself.reorder_child(self.install_box, 0)
1186 else:
1187 self.load_replications()
1188
1189 self.message.stop()
1190
1191 @log_call(logger.debug)
1192 def load_replications(self, *args):
1193 """Load replications info."""
1194 self._replications_ready = False # hack to solve LP: #750309
1195 # ask replications to the backend
1196 self.message.start()
1197 self.backend.replications_info(reply_handler=NO_OP,
1198 error_handler=error_handler)
1199
1200 @log_call(logger.debug)
1201 def on_replications_info_ready(self, info):
1202 """The replication info is ready."""
1203 self.on_success()
1204
1205 self.replications.show()
1206
1207 if self.install_box is not None:
1208 self.itself.remove(self.install_box)
1209 self.install_box = None
1210
1211 for item in info:
1212 pkg = item['dependency']
1213 if not pkg or self.package_manager.is_installed(pkg):
1214 pkg = None
1215
1216 sid = item['replication_id']
1217 container = getattr(self, sid, None)
1218 check_button = getattr(self, '%s_check' % sid, None)
1219 name = self.plugin_names.get(sid, None)
1220 child = DesktopcouchService(service_id=sid, name=item['name'],
1221 enabled=bool(item['enabled']), container=container,
1222 check_button=check_button,
1223 dependency=pkg, dependency_name=name)
1224 setattr(self, '%s_service' % sid, child)
1225 self._replications_ready = True # hack to solve LP: #750309
1226
1227 @log_call(logger.error)
1228 def on_replications_info_error(self, error_dict=None):
1229 """The replication info can not be retrieved."""
1230 if error_dict is not None and \
1231 error_dict.get('error_type', None) == 'NoPairingRecord':
1232 self.on_error(NO_PAIRING_RECORD)
1233 else:
1234 self.on_error(error_dict=error_dict)
1235
1236 def refresh(self):
1237 """If replication list has been loaded, hide and show them."""
1238 if self._replications_ready: # hack to solve LP: #750309
1239 self.replications.hide()
1240 self.replications.show()
1241
1242
1243class FileSyncStatus(gtk.HBox, ControlPanelMixin):
1244 """A file sync status widget."""
1245
1246 def __init__(self):
1247 gtk.HBox.__init__(self)
1248 ControlPanelMixin.__init__(self)
1249
1250 self.label = LabelLoading(LOADING)
1251 self.pack_start(self.label, expand=True)
1252
1253 self.button = gtk.LinkButton(uri='')
1254 self.button.connect('clicked', self._on_button_clicked)
1255 self.pack_start(self.button, expand=False)
1256
1257 self.show_all()
1258
1259 self.backend.connect_to_signal('FileSyncStatusDisabled',
1260 self.on_file_sync_status_disabled)
1261 self.backend.connect_to_signal('FileSyncStatusStarting',
1262 self.on_file_sync_status_starting)
1263 self.backend.connect_to_signal('FileSyncStatusStopped',
1264 self.on_file_sync_status_stopped)
1265 self.backend.connect_to_signal('FileSyncStatusDisconnected',
1266 self.on_file_sync_status_disconnected)
1267 self.backend.connect_to_signal('FileSyncStatusSyncing',
1268 self.on_file_sync_status_syncing)
1269 self.backend.connect_to_signal('FileSyncStatusIdle',
1270 self.on_file_sync_status_idle)
1271 self.backend.connect_to_signal('FileSyncStatusError',
1272 self.on_file_sync_status_error)
1273 self.backend.connect_to_signal('FilesStartError',
1274 self.on_files_start_error)
1275 self.backend.connect_to_signal('FilesEnabled',
1276 self.on_file_sync_status_starting)
1277 self.backend.connect_to_signal('FilesDisabled',
1278 self.on_file_sync_status_disabled)
1279
1280 def _update_status(self, msg, action, callback,
1281 icon=None, color=None, tooltip=None):
1282 """Update the status info."""
1283 if icon is not None:
1284 foreground = '' if color is None else 'foreground="%s"' % color
1285 msg = '<span %s>%s</span> %s' % (foreground, icon, msg)
1286 self.label.set_markup(msg)
1287 self.label.stop()
1288
1289 self.button.set_label(action)
1290 self.button.set_uri(action)
1291 self.button.set_sensitive(True)
1292 self.button.set_data('callback', callback)
1293 if tooltip is not None:
1294 self.button.set_tooltip_text(tooltip)
1295
1296 def _on_button_clicked(self, button):
1297 """Button was clicked, act accordingly the label."""
1298 button.set_visited(False)
1299 button.set_sensitive(False)
1300 button.get_data('callback')(button)
1301
1302 @log_call(logger.info)
1303 def on_file_sync_status_disabled(self, msg=None):
1304 """Backend notifies of file sync status being disabled."""
1305 self._update_status(FILE_SYNC_DISABLED,
1306 FILE_SYNC_ENABLE, self.on_enable_clicked,
1307 ERROR_ICON, ERROR_COLOR, FILE_SYNC_ENABLE_TOOLTIP)
1308
1309 @log_call(logger.info)
1310 def on_file_sync_status_starting(self, msg=None):
1311 """Backend notifies of file sync status being starting."""
1312 self._update_status(FILE_SYNC_STARTING,
1313 FILE_SYNC_STOP, self.on_stop_clicked,
1314 SYNCING_ICON, ORANGE, FILE_SYNC_STOP_TOOLTIP)
1315
1316 @log_call(logger.info)
1317 def on_file_sync_status_stopped(self, msg=None):
1318 """Backend notifies of file sync being stopped."""
1319 self._update_status(FILE_SYNC_STOPPED,
1320 FILE_SYNC_START, self.on_start_clicked,
1321 ERROR_ICON, ERROR_COLOR, FILE_SYNC_START_TOOLTIP)
1322
1323 @log_call(logger.info)
1324 def on_file_sync_status_disconnected(self, msg=None):
1325 """Backend notifies of file sync status being ready."""
1326 self._update_status(FILE_SYNC_DISCONNECTED,
1327 FILE_SYNC_CONNECT, self.on_connect_clicked,
1328 ERROR_ICON, ERROR_COLOR,
1329 FILE_SYNC_CONNECT_TOOLTIP,)
1330
1331 @log_call(logger.info)
1332 def on_file_sync_status_syncing(self, msg=None):
1333 """Backend notifies of file sync status being syncing."""
1334 self._update_status(FILE_SYNC_SYNCING,
1335 FILE_SYNC_DISCONNECT, self.on_disconnect_clicked,
1336 SYNCING_ICON, ORANGE, FILE_SYNC_DISCONNECT_TOOLTIP)
1337
1338 @log_call(logger.info)
1339 def on_file_sync_status_idle(self, msg=None):
1340 """Backend notifies of file sync status being idle."""
1341 self._update_status(FILE_SYNC_IDLE,
1342 FILE_SYNC_DISCONNECT, self.on_disconnect_clicked,
1343 IDLE_ICON, SUCCESS_COLOR,
1344 FILE_SYNC_DISCONNECT_TOOLTIP)
1345
1346 @log_call(logger.error)
1347 def on_file_sync_status_error(self, error_dict=None):
1348 """Backend notifies of an error when fetching file sync status."""
1349 msg = FILE_SYNC_ERROR
1350 reason = error_dict.get('error_msg', '') if error_dict else ''
1351 if reason:
1352 msg += ' (' + reason + ')'
1353 self._update_status(WARNING_MARKUP % msg,
1354 FILE_SYNC_RESTART, self.on_restart_clicked,
1355 tooltip=FILE_SYNC_RESTART_TOOLTIP)
1356
1357 @log_call(logger.error)
1358 def on_files_start_error(self, error_dict=None):
1359 """Backend notifies of an error when starting the files service."""
1360 # service is probably disabled, ask for status to backend
1361 self.backend.file_sync_status(reply_handler=NO_OP,
1362 error_handler=error_handler)
1363
1364 def on_connect_clicked(self, button=None):
1365 """User requested connection."""
1366 self.backend.connect_files(reply_handler=NO_OP,
1367 error_handler=error_handler)
1368
1369 def on_disconnect_clicked(self, button=None):
1370 """User requested disconnection."""
1371 self.backend.disconnect_files(reply_handler=NO_OP,
1372 error_handler=error_handler)
1373
1374 def on_enable_clicked(self, button=None):
1375 """User requested enable the service."""
1376 self.backend.enable_files(reply_handler=NO_OP,
1377 error_handler=error_handler)
1378
1379 def on_restart_clicked(self, button=None):
1380 """User requested restart the service."""
1381 self.backend.restart_files(reply_handler=NO_OP,
1382 error_handler=error_handler)
1383
1384 def on_start_clicked(self, button=None):
1385 """User requested start the service."""
1386 self.backend.start_files(reply_handler=NO_OP,
1387 error_handler=error_handler)
1388
1389 def on_stop_clicked(self, button=None):
1390 """User requested stop the service."""
1391 self.backend.stop_files(reply_handler=NO_OP,
1392 error_handler=error_handler)
1393
1394 def load(self):
1395 """Load the information."""
1396 self.backend.file_sync_status(reply_handler=NO_OP,
1397 error_handler=error_handler)
1398
1399
1400class ManagementPanel(gtk.VBox, ControlPanelMixin):
1401 """The management panel.
1402
1403 The user can manage dashboard, volumes, devices and services.
1404
1405 """
1406
1407 __gsignals__ = {
1408 'local-device-removed': (gobject.SIGNAL_RUN_FIRST,
1409 gobject.TYPE_NONE, ()),
1410 'unauthorized': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()),
1411 }
1412
1413 DASHBOARD_BUTTON_NAME = 'ModeLeft'
1414 SERVICES_BUTTON_NAME = 'ModeRight'
1415
1416 def __init__(self, main_window=None):
1417 gtk.VBox.__init__(self)
1418 ControlPanelMixin.__init__(self, filename='management.ui')
1419 self.add(self.itself)
1420 self.facebook_logo.set_from_file(get_data_file(FACEBOOK_LOGO))
1421 self.twitter_logo.set_from_file(get_data_file(TWITTER_LOGO))
1422 self.show()
1423
1424 self.backend.connect_to_signal('AccountInfoReady',
1425 self.on_account_info_ready)
1426 self.backend.connect_to_signal('AccountInfoError',
1427 self.on_account_info_error)
1428 self.backend.connect_to_signal('UnauthorizedError',
1429 self.on_unauthorized_error)
1430
1431 self.quota_progressbar.set_sensitive(False)
1432
1433 self.quota_label = LabelLoading(LOADING)
1434 self.quota_box.pack_start(self.quota_label, expand=False)
1435 self.quota_box.reorder_child(self.quota_label, 0)
1436
1437 self.status_label = FileSyncStatus()
1438 self.status_box.pack_end(self.status_label, expand=True)
1439
1440 self.dashboard = DashboardPanel(main_window=main_window)
1441 self.volumes = VolumesPanel(main_window=main_window)
1442 self.shares = SharesPanel(main_window=main_window)
1443 self.devices = DevicesPanel(main_window=main_window)
1444 self.services = ServicesPanel(main_window=main_window)
1445
1446 cb = lambda button, page_num: self.notebook.set_current_page(page_num)
1447 self.tabs = (u'dashboard', u'volumes', u'shares',
1448 u'devices', u'services')
1449 for page_num, tab in enumerate(self.tabs):
1450 setattr(self, ('%s_page' % tab).upper(), page_num)
1451 button = getattr(self, '%s_button' % tab)
1452 button.connect('clicked', cb, page_num)
1453 self.notebook.insert_page(getattr(self, tab), position=page_num)
1454
1455 self.dashboard_button.set_name(self.DASHBOARD_BUTTON_NAME)
1456 self.dashboard_button.set_tooltip_text(DASHBOARD_BUTTON_TOOLTIP)
1457
1458 self.volumes_button.set_tooltip_text(FOLDERS_BUTTON_TOOLTIP)
1459 self.volumes_button.connect('clicked', lambda b: self.volumes.load())
1460
1461 self.shares_button.set_tooltip_text(SHARES_BUTTON_TOOLTIP)
1462
1463 self.devices_button.set_tooltip_text(DEVICES_BUTTON_TOOLTIP)
1464 self.devices_button.connect('clicked', lambda b: self.devices.load())
1465 self.devices.connect('local-device-removed',
1466 lambda widget: self.emit('local-device-removed'))
1467
1468 self.services_button.set_name(self.SERVICES_BUTTON_NAME)
1469 self.services_button.set_tooltip_text(SERVICES_BUTTON_TOOLTIP)
1470 self.services_button.connect('clicked',
1471 lambda b: self.services.refresh())
1472
1473 self.enable_volumes = lambda: self.volumes_button.set_sensitive(True)
1474 self.disable_volumes = lambda: self.volumes_button.set_sensitive(False)
1475 self.backend.connect_to_signal('FilesEnabled', self.enable_volumes)
1476 self.backend.connect_to_signal('FilesDisabled', self.disable_volumes)
1477
1478 def _update_quota(self, msg, data=None):
1479 """Update the quota info."""
1480 fraction = 0.0
1481 if data is not None:
1482 fraction = data.get('percentage', 0.0) / 100
1483 if fraction > 0 and fraction < 0.05:
1484 fraction = 0.05
1485 else:
1486 fraction = round(fraction, 2)
1487
1488 logger.debug('ManagementPanel: updating quota to %r.', fraction)
1489 if fraction >= QUOTA_THRESHOLD:
1490 self.quota_label.set_markup(WARNING_MARKUP % msg)
1491 else:
1492 self.quota_label.set_markup(msg)
1493 self.quota_label.stop()
1494
1495 if fraction == 0.0:
1496 self.quota_progressbar.set_sensitive(False)
1497 else:
1498 self.quota_progressbar.set_sensitive(True)
1499
1500 self.quota_progressbar.set_fraction(min(fraction, 1))
1501
1502 def load(self):
1503 """Load the account info and file sync status list."""
1504 self.backend.account_info(reply_handler=NO_OP,
1505 error_handler=error_handler)
1506 self.status_label.load()
1507 self.services.load()
1508
1509 @log_call(logger.debug)
1510 def on_account_info_ready(self, info):
1511 """Backend notifies of account info."""
1512 used = int(info['quota_used'])
1513 total = int(info['quota_total'])
1514 data = {'used': humanize(used), 'total': humanize(total),
1515 'percentage': (used / total) * 100}
1516 self._update_quota(QUOTA_LABEL % data, data)
1517
1518 @log_call(logger.error)
1519 def on_account_info_error(self, error_dict=None):
1520 """Backend notifies of an error when fetching account info."""
1521 self._update_quota(msg='')
1522
1523 @log_call(logger.error)
1524 def on_unauthorized_error(self, error_dict=None):
1525 """Backend notifies that credentials are not valid."""
1526 self.emit('unauthorized')
1527
1528
1529class ControlPanel(gtk.Notebook, ControlPanelMixin):
1530 """The control panel per se, can be added into any other widget."""
1531
1532 # should not be any larger than 736x525
1533
1534 def __init__(self, main_window):
1535 gtk.Notebook.__init__(self)
1536 ControlPanelMixin.__init__(self)
1537 gtk.link_button_set_uri_hook(uri_hook)
1538 self.connect('destroy', self.shutdown)
1539
1540 self.main_window = main_window
1541
1542 self.set_show_tabs(False)
1543 self.set_show_border(False)
1544
1545 self.overview = OverviewPanel(main_window=main_window)
1546 self.insert_page(self.overview, position=0)
1547
1548 self.management = ManagementPanel(main_window=main_window)
1549 self.insert_page(self.management, position=1)
1550
1551 self.overview.connect('credentials-found',
1552 self.on_show_management_panel)
1553 self.management.connect('local-device-removed',
1554 self.on_show_overview_panel)
1555 self.management.connect('unauthorized',
1556 self.on_show_overview_panel)
1557
1558 self.show()
1559 self.on_show_overview_panel()
1560
1561 logger.debug('%s: started (window size %r).',
1562 self.__class__.__name__, self.get_size_request())
1563
1564 def shutdown(self, *args, **kwargs):
1565 """Shutdown backend."""
1566 logger.info('Shutting down...')
1567 self.backend.shutdown(reply_handler=NO_OP,
1568 error_handler=error_handler)
1569
1570 def on_show_overview_panel(self, widget=None):
1571 """Show the overview panel."""
1572 self.set_current_page(0)
1573
1574 def on_show_management_panel(self, widget=None, credentials_are_new=False):
1575 """Show the notebook (main panel)."""
1576 if self.get_current_page() == 0:
1577 self.management.load()
1578 if credentials_are_new:
1579 # redirect user to services page to start using Ubuntu One
1580 self.management.services_button.clicked()
1581 # instruct syncdaemon to connect
1582 self.backend.connect_files(reply_handler=NO_OP,
1583 error_handler=error_handler)
1584
1585 self.next_page()
1586
1587
1588class ControlPanelService(dbus.service.Object):
1589 """DBUS service that exposes some of the window's methods."""
1590
1591 def __init__(self, window):
1592 self.window = window
1593 bus_name = dbus.service.BusName(
1594 DBUS_BUS_NAME_GUI, bus=dbus.SessionBus())
1595 dbus.service.Object.__init__(
1596 self, bus_name=bus_name, object_path=DBUS_PATH_GUI)
1597
1598 @log_call(logger.debug)
1599 @dbus.service.method(dbus_interface=DBUS_IFACE_GUI, in_signature='sb')
1600 def switch_to_alert(self, panel='', alert=False):
1601 """Switch to named panel."""
1602 if panel:
1603 self.window.switch_to(panel)
1604 if alert:
1605 self.window.draw_attention()
1606
1607
1608class ControlPanelWindow(gtk.Window):
1609 """The main window for the Ubuntu One control panel."""
1610
1611 def __init__(self, switch_to='', alert=False):
1612 super(ControlPanelWindow, self).__init__()
1613
1614 # We need to set WMCLASS so Unity falls back and we only get one
1615 # launcher on the launcher panel
1616 self.set_wmclass(CP_WMCLASS_NAME, CP_WMCLASS_CLASS)
1617
1618 self.connect('focus-in-event', self.remove_urgency)
1619 self.set_title(MAIN_WINDOW_TITLE % {'app_name': U1_APP_NAME})
1620 self.set_position(gtk.WIN_POS_CENTER_ALWAYS)
1621 self.set_icon_name('ubuntuone')
1622 self.set_size_request(736, 525) # bug #683164
1623
1624 self.connect('delete-event', lambda w, e: gtk.main_quit())
1625 if alert:
1626 self.draw_attention()
1627 else:
1628 self.present()
1629
1630 self.control_panel = ControlPanel(main_window=self)
1631 self.add(self.control_panel)
1632
1633 logger.info('Starting %s pointing at panel: %r.',
1634 self.__class__.__name__, switch_to)
1635 if switch_to:
1636 self.switch_to(switch_to)
1637
1638 logger.debug('%s: started (window size %r).',
1639 self.__class__.__name__, self.get_size_request())
1640
1641 def remove_urgency(self, *args, **kwargs):
1642 """Remove urgency from the launcher entry."""
1643 if not USE_LIBUNITY:
1644 return
1645 entry = Unity.LauncherEntry.get_for_desktop_id(U1_DOTDESKTOP)
1646 if getattr(entry.props, 'urgent', False):
1647 self.switch_to('volumes')
1648 entry.props.urgent = False
1649
1650 def draw_attention(self):
1651 """Draw attention to the control panel."""
1652 self.present_with_time(1)
1653 self.set_urgency_hint(True)
1654
1655 def switch_to(self, panel):
1656 """Switch to named panel."""
1657 button = getattr(
1658 self.control_panel.management, '%s_button' % panel, None)
1659 if button is not None:
1660 button.clicked()
1661 else:
1662 logger.warning('Could not start at panel: %r.', panel)
1663
1664 def main(self):
1665 """Run the main loop of the widget toolkit."""
1666 logger.debug('Starting GTK main loop.')
1667 gtk.main()
16680
=== removed file 'ubuntuone/controlpanel/gui/gtk/package_manager.py'
--- ubuntuone/controlpanel/gui/gtk/package_manager.py 2011-04-05 18:04:56 +0000
+++ ubuntuone/controlpanel/gui/gtk/package_manager.py 1970-01-01 00:00:00 +0000
@@ -1,62 +0,0 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Natalia B. Bidart <nataliabidart@canonical.com>
4#
5# Copyright 2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""Client to manage packages."""
20
21import apt
22import aptdaemon.client
23# pylint: disable=W0404
24import aptdaemon.enums
25
26try:
27 # Unable to import 'defer', pylint: disable=F0401,E0611,W0404
28 from aptdaemon.defer import inline_callbacks, return_value
29except ImportError:
30 # Unable to import 'defer', pylint: disable=F0401,E0611,W0404
31 from defer import inline_callbacks, return_value
32from aptdaemon.gtkwidgets import AptProgressBar
33
34from ubuntuone.controlpanel.logger import setup_logging
35
36
37logger = setup_logging('package_manager')
38
39
40class PackageManagerProgressBar(AptProgressBar):
41 """A progress bar for a transaction."""
42
43
44class PackageManager(object):
45 """Manage packages (check if is installed, install)."""
46
47 def is_installed(self, package_name):
48 """Return whether 'package_name' is installed in this system."""
49 cache = apt.Cache()
50 result = package_name in cache and cache[package_name].is_installed
51 logger.debug('is %r installed? %r', package_name, result)
52 return result
53
54 @inline_callbacks
55 def install(self, package_name):
56 """Install 'package_name' if is not installed in this system."""
57 if self.is_installed(package_name):
58 return_value(aptdaemon.enums.EXIT_SUCCESS)
59
60 client = aptdaemon.client.AptClient()
61 transaction = yield client.install_packages([package_name])
62 return_value(transaction)
630
=== removed directory 'ubuntuone/controlpanel/gui/gtk/tests'
=== removed file 'ubuntuone/controlpanel/gui/gtk/tests/__init__.py'
--- ubuntuone/controlpanel/gui/gtk/tests/__init__.py 2012-02-06 21:02:54 +0000
+++ ubuntuone/controlpanel/gui/gtk/tests/__init__.py 1970-01-01 00:00:00 +0000
@@ -1,232 +0,0 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""The test suite for the GTK UI for the control panel for Ubuntu One."""
20
21import logging
22
23from collections import defaultdict
24
25from twisted.internet import defer
26from ubuntuone.devtools.handlers import MementoHandler
27
28from ubuntuone.controlpanel.gui.gtk import gui
29from ubuntuone.controlpanel.gui.gtk.tests.test_package_manager import (
30 FakedTransaction)
31# Unused imports, they are here to maintain old module API
32# pylint: disable=W0611
33from ubuntuone.controlpanel.gui.tests import (FakedObject,
34 FAKE_ACCOUNT_INFO,
35 FAKE_DEVICE_INFO,
36 FAKE_DEVICES_INFO,
37 FAKE_FOLDERS_INFO,
38 FAKE_SHARES_INFO,
39 FAKE_VOLUMES_INFO,
40 FAKE_VOLUMES_NO_FREE_SPACE_INFO,
41 MUSIC_FOLDER,
42 ROOT,
43 USER_HOME,
44)
45# pylint: enable=W0611
46from ubuntuone.controlpanel.tests import TestCase
47
48
49# Attribute 'yyy' defined outside __init__, access to a protected member
50# pylint: disable=W0201, W0212
51
52
53FAKE_REPLICATIONS_INFO = [
54 {'replication_id': 'foo', 'name': 'Bar',
55 'enabled': 'True', 'dependency': ''},
56 {'replication_id': 'yadda', 'name': 'Foo',
57 'enabled': '', 'dependency': 'a very weird one'},
58 {'replication_id': 'yoda', 'name': 'Figthers',
59 'enabled': 'True', 'dependency': 'other dep'},
60]
61
62
63class FakedNMState(FakedObject):
64 """Fake a NetworkManagerState."""
65
66 exposed_methods = ['find_online_state']
67
68
69class FakedDBusBackend(FakedObject):
70 """Fake a DBus Backend."""
71
72 bus_name = None
73 object_path = None
74 iface = None
75
76 def __init__(self, obj, dbus_interface, *args, **kwargs):
77 if dbus_interface != self.iface:
78 raise TypeError()
79 self._signals = defaultdict(list)
80 super(FakedDBusBackend, self).__init__(*args, **kwargs)
81
82 def connect_to_signal(self, signal, handler):
83 """Bind 'handler' to be callback'd when 'signal' is fired."""
84 self._signals[signal].append(handler)
85
86
87class FakedCredentialsBackend(FakedObject):
88 """Fake a credentials backend."""
89
90 exposed_methods = ['find_credentials', 'clear_credentials',
91 'login', 'register']
92 next_result = defer.succeed(None)
93
94
95class FakedControlPanelBackend(FakedDBusBackend):
96 """Fake a Control Panel Backend, act as a dbus.Interface."""
97
98 bus_name = gui.DBUS_BUS_NAME
99 object_path = gui.DBUS_PREFERENCES_PATH
100 iface = gui.DBUS_PREFERENCES_IFACE
101 exposed_methods = [
102 'account_info', # account
103 'devices_info', 'change_device_settings', 'remove_device', # devices
104 'volumes_info', 'change_volume_settings', # volumes
105 'replications_info', 'change_replication_settings', # replications
106 'file_sync_status', 'enable_files', 'disable_files', # files
107 'connect_files', 'disconnect_files',
108 'restart_files', 'start_files', 'stop_files', 'shutdown',
109 ]
110
111
112class FakedGUIBackend(FakedDBusBackend):
113 """Fake a Control Panel GUI Service, act as a dbus.Interface."""
114
115 bus_name = gui.DBUS_BUS_NAME_GUI
116 object_path = gui.DBUS_PATH_GUI
117 iface = gui.DBUS_IFACE_GUI
118 exposed_methods = ['draw_attention', 'switch_to']
119
120
121class FakedSessionBus(object):
122 """Fake a session bus."""
123
124 def get_object(self, bus_name, object_path, introspect=True,
125 follow_name_owner_changes=False, **kwargs):
126 """Return a faked proxy for the given remote object."""
127 return None
128
129
130class FakedInterface(object):
131 """Fake a dbus interface."""
132
133 def __new__(cls, obj, dbus_interface, *args, **kwargs):
134 if dbus_interface == gui.DBUS_PREFERENCES_IFACE:
135 return FakedControlPanelBackend(obj, dbus_interface,
136 *args, **kwargs)
137 if dbus_interface == gui.DBUS_IFACE_GUI:
138 return FakedGUIBackend(
139 obj, dbus_interface, *args, **kwargs)
140
141
142class FakedPackageManager(object):
143 """Faked a package manager."""
144
145 def __init__(self):
146 self._installed = {}
147 self.is_installed = lambda package_name: \
148 self._installed.setdefault(package_name, False)
149
150 @gui.package_manager.inline_callbacks
151 def install(self, package_name):
152 """Install 'package_name' if is not installed in this system."""
153 yield
154 self._installed[package_name] = True
155 gui.package_manager.return_value(FakedTransaction([package_name]))
156
157
158class FakedConfirmDialog(object):
159 """Fake a confirmation dialog."""
160
161 def __init__(self, *args, **kwargs):
162 self._args = args
163 self._kwargs = kwargs
164 self.was_run = False
165 self.is_visible = False
166 self.markup = kwargs.get('message_format', None)
167 self.show = lambda: setattr(self, 'is_visible', True)
168 self.hide = lambda: setattr(self, 'is_visible', False)
169 self.response_code = None
170
171 def run(self):
172 """Set flag and return 'self.response_code'."""
173 self.was_run = True
174 return self.response_code
175
176 def set_markup(self, msg):
177 """Set the markup."""
178 self.markup = msg
179
180
181class BaseTestCase(TestCase):
182 """Basics for testing."""
183
184 # self.klass is not callable
185 # pylint: disable=E1102
186 klass = None
187 kwargs = {}
188 backend_is_dbus = True
189
190 @defer.inlineCallbacks
191 def setUp(self):
192 yield super(BaseTestCase, self).setUp()
193 self.patch(gui, 'CredentialsManagementTool', FakedCredentialsBackend)
194 self.patch(gui.os.path, 'expanduser',
195 lambda path: path.replace('~', USER_HOME))
196 self.patch(gui.gtk, 'main', lambda: None)
197 self.patch(gui.gtk, 'MessageDialog', FakedConfirmDialog)
198 self.patch(gui.dbus, 'SessionBus', FakedSessionBus)
199 self.patch(gui.dbus, 'Interface', FakedInterface)
200 self.patch(gui.networkstate, 'NetworkManagerState', FakedNMState)
201 self.patch(gui.package_manager, 'PackageManager', FakedPackageManager)
202
203 if self.klass is not None:
204 self.ui = self.klass(**self.kwargs)
205 self.addCleanup(self.ui.destroy)
206
207 self.memento = MementoHandler()
208 self.memento.setLevel(logging.DEBUG)
209 gui.logger.addHandler(self.memento)
210
211 def assert_image_equal(self, image, filename):
212 """Check that expected and actual represent the same image."""
213 pb = gui.gtk.gdk.pixbuf_new_from_file(gui.get_data_file(filename))
214 self.assertEqual(image.get_pixbuf().get_pixels(), pb.get_pixels())
215
216 def assert_backend_called(self, method_name, *args, **kwargs):
217 """Check that the control panel backend 'method_name' was called."""
218 if self.backend_is_dbus:
219 kwargs = {'reply_handler': gui.NO_OP,
220 'error_handler': gui.error_handler}
221 self.assertIn(method_name, self.ui.backend._called)
222 self.assertEqual(self.ui.backend._called[method_name], (args, kwargs))
223
224 def assert_warning_correct(self, warning, text):
225 """Check that 'warning' is visible, showing 'text'."""
226 self.assertTrue(warning.get_visible(), 'Must be visible.')
227 self.assertEqual(warning.get_label(), gui.WARNING_MARKUP % text)
228
229 def assert_function_decorated(self, decorator, func):
230 """Check that 'func' is decorated with 'decorator'."""
231 expected = decorator(lambda: None)
232 self.assertEqual(expected.func_code, func.im_func.func_code)
2330
=== removed file 'ubuntuone/controlpanel/gui/gtk/tests/test_gui.py'
--- ubuntuone/controlpanel/gui/gtk/tests/test_gui.py 2011-10-24 21:48:27 +0000
+++ ubuntuone/controlpanel/gui/gtk/tests/test_gui.py 1970-01-01 00:00:00 +0000
@@ -1,2179 +0,0 @@
1# -*- coding: utf-8 -*-
2
3# Authors: Natalia B Bidart <natalia.bidart@canonical.com>
4#
5# Copyright 2010 Canonical Ltd.
6#
7# This program is free software: you can redistribute it and/or modify it
8# under the terms of the GNU General Public License version 3, as published
9# by the Free Software Foundation.
10#
11# This program is distributed in the hope that it will be useful, but
12# WITHOUT ANY WARRANTY; without even the implied warranties of
13# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14# PURPOSE. See the GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License along
17# with this program. If not, see <http://www.gnu.org/licenses/>.
18
19"""The test suite for the control panel user interface."""
20
21from __future__ import division
22
23import operator
24
25from twisted.internet import defer
26
27from ubuntuone.controlpanel.gui.gtk import gui
28from ubuntuone.controlpanel.gui.gtk.tests import (
29 FakedConfirmDialog,
30 FAKE_REPLICATIONS_INFO,
31)
32from ubuntuone.controlpanel.gui.tests import (
33 FAKE_ACCOUNT_INFO,
34 FAKE_DEVICE_INFO,
35 FAKE_DEVICES_INFO,
36 FAKE_FOLDERS_INFO,
37 FAKE_VOLUMES_INFO,
38 FAKE_VOLUMES_MINIMAL_INFO,
39 FAKE_VOLUMES_NO_FREE_SPACE_INFO,
40 MUSIC_FOLDER, ROOT, USER_HOME,
41)
42from ubuntuone.controlpanel.gui.gtk.tests.test_gui_basic import (
43 ControlPanelMixinTestCase,
44)
45from ubuntuone.controlpanel.gui.gtk.tests.test_package_manager import (
46 SUCCESS, FAILURE)
47from ubuntuone.controlpanel.gui import humanize
48
49
50# Attribute 'yyy' defined outside __init__, access to a protected member
51# pylint: disable=W0201, W0212
52
53# Unused variable 'skip'
54#pylint: disable=W0612
55
56
57class DashboardTestCase(ControlPanelMixinTestCase):
58 """The test suite for the dashboard panel."""
59
60 klass = gui.DashboardPanel
61 ui_filename = 'dashboard.ui'
62
63 def assert_account_info_correct(self, info):
64 """Check that the displayed account info matches 'info'."""
65 self.assertEqual(self.ui.name_label.get_label(),
66 FAKE_ACCOUNT_INFO['name'])
67 self.assertEqual(self.ui.type_label.get_label(),
68 FAKE_ACCOUNT_INFO['type'])
69 self.assertEqual(self.ui.email_label.get_label(),
70 FAKE_ACCOUNT_INFO['email'])
71
72 def test_is_an_ubuntuone_bin(self):
73 """Inherits from UbuntuOneBin."""
74 self.assertIsInstance(self.ui, gui.UbuntuOneBin)
75
76 def test_inner_widget_is_packed(self):
77 """The 'itself' vbox is packed into the widget."""
78 self.assertIn(self.ui.itself, self.ui.get_children())
79
80 def test_is_visible(self):
81 """Is visible."""
82 self.assertTrue(self.ui.get_visible())
83
84 def test_account_info_is_not_visible(self):
85 """Account info is not visible."""
86 self.assertFalse(self.ui.account.get_visible())
87
88 def test_backend_signals(self):
89 """The proper signals are connected to the backend."""
90 self.assertEqual(self.ui.backend._signals['AccountInfoReady'],
91 [self.ui.on_account_info_ready])
92 self.assertEqual(self.ui.backend._signals['AccountInfoError'],
93 [self.ui.on_account_info_error])
94
95 def test_is_processing_at_startup(self):
96 """The ui is processing when info is being loaded."""
97 self.assertTrue(self.ui.is_processing)
98
99 def test_is_not_processing_on_info_ready(self):
100 """The ui is not processing when info is ready."""
101 self.ui.on_account_info_ready(FAKE_ACCOUNT_INFO)
102
103 self.assertFalse(self.ui.is_processing)
104
105 def test_on_account_info_ready(self):
106 """The account info is processed when ready."""
107 self.ui.on_account_info_ready(FAKE_ACCOUNT_INFO)
108
109 self.assert_account_info_correct(FAKE_ACCOUNT_INFO)
110 self.assertTrue(self.ui.account.get_visible())
111
112 def test_is_not_processing_on_info_error(self):
113 """The ui is not processing when info is ready."""
114 self.ui.on_account_info_error()
115
116 self.assertFalse(self.ui.is_processing)
117
118 def test_on_account_info_error(self):
119 """The account info couldn't be retrieved."""
120 self.ui.on_account_info_error()
121
122 self.assertFalse(self.ui.account.get_visible())
123 self.assert_warning_correct(self.ui.message, self.ui.VALUE_ERROR)
124
125
126class VolumesTestCase(ControlPanelMixinTestCase):
127 """The test suite for the volumes panel."""
128
129 klass = gui.VolumesPanel
130 ui_filename = 'volumes.ui'
131
132 @defer.inlineCallbacks
133 def setUp(self):
134 yield super(VolumesTestCase, self).setUp()
135 self.ui.load()
136
137 def test_is_an_ubuntuone_bin(self):
138 """Inherits from UbuntuOneBin."""
139 self.assertIsInstance(self.ui, gui.UbuntuOneBin)
140
141 def test_inner_widget_is_packed(self):
142 """The 'itself' vbox is packed into the widget."""
143 self.assertIn(self.ui.itself, self.ui.get_children())
144
145 def test_is_visible(self):
146 """Is visible."""
147 self.assertTrue(self.ui.get_visible())
148
149 def test_backend_signals(self):
150 """The proper signals are connected to the backend."""
151 self.assertEqual(self.ui.backend._signals['VolumesInfoReady'],
152 [self.ui.on_volumes_info_ready])
153 self.assertEqual(self.ui.backend._signals['VolumesInfoError'],
154 [self.ui.on_volumes_info_error])
155 self.assertEqual(self.ui.backend._signals['VolumeSettingsChanged'],
156 [self.ui.on_volume_settings_changed])
157 self.assertEqual(self.ui.backend._signals['VolumeSettingsChangeError'],
158 [self.ui.on_volume_settings_change_error])
159
160 def test_volumes_info_is_requested_on_load(self):
161 """The volumes info is requested to the backend."""
162 # clean backend calls
163 self.ui.backend._called.pop('volumes_info', None)
164 self.ui.load()
165
166 self.assert_backend_called('volumes_info')
167
168 def test_is_processing_after_load(self):
169 """The ui is processing when contents are load."""
170 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
171 self.ui.load()
172
173 self.assertTrue(self.ui.is_processing)
174
175 def test_is_not_processing_after_volumes_info_ready(self):
176 """The ui is processing when contents are load."""
177 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
178
179 self.assertFalse(self.ui.is_processing)
180
181 def test_message_after_non_empty_volumes_info_ready(self):
182 """The volumes label is a LabelLoading."""
183 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
184
185 self.assertFalse(self.ui.message.active)
186
187 def test_message_after_empty_volumes_info_ready(self):
188 """When there are no volumes, a notification is shown."""
189 self.ui.on_volumes_info_ready([])
190
191 self.assertFalse(self.ui.message.active)
192 self.assertEqual(self.ui.message.get_text(), gui.NO_FOLDERS)
193
194 def test_on_volumes_info_ready(self):
195 """The volumes info is processed when ready."""
196 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
197
198 self.assertEqual(len(FAKE_VOLUMES_INFO) + 1, # count the empty row
199 len(self.ui.volumes_store))
200 treeiter = self.ui.volumes_store.get_iter_root()
201 for name, free_bytes, volumes in FAKE_VOLUMES_INFO:
202 name = "%s's" % name if name else gui.MY_FOLDERS
203 free_bytes = humanize(int(free_bytes))
204 header = (name, self.ui.FREE_SPACE % {'free_space': free_bytes})
205
206 # check parent row
207 row = self.ui.volumes_store.get(treeiter,
208 *xrange(self.ui.MAX_COLS))
209
210 self.assertEqual(row[0], self.ui.ROW_HEADER % header)
211 self.assertTrue(row[1], 'parent will always be subscribed')
212 self.assertEqual(row[2], gui.CONTACT_ICON_NAME)
213 self.assertFalse(row[3], 'no toggle should be shown on parent!')
214 self.assertFalse(row[4], 'toggle should be non sensitive.')
215 self.assertEqual(row[5], gui.gtk.ICON_SIZE_LARGE_TOOLBAR)
216 self.assertEqual(row[6], None)
217 self.assertEqual(row[7], None)
218
219 # check children
220 self.assertEqual(len(volumes),
221 self.ui.volumes_store.iter_n_children(treeiter))
222 childiter = self.ui.volumes_store.iter_children(treeiter)
223
224 sorted_vols = sorted(volumes, key=operator.itemgetter('path'))
225 for volume in sorted_vols:
226 row = self.ui.volumes_store.get(childiter,
227 *xrange(self.ui.MAX_COLS))
228
229 sensitive = True
230 path = volume['path'].replace(USER_HOME + '/', '')
231 if volume['type'] == 'ROOT':
232 sensitive = False
233 path = self.ui.ROOT % (path, gui.ORANGE,
234 gui.ALWAYS_SUBSCRIBED)
235 elif volume['type'] == 'SHARE':
236 path = volume['name']
237
238 self.assertEqual(row[0], path)
239 self.assertEqual(row[1], bool(volume['subscribed']))
240 if volume['type'] != 'SHARE':
241 self.assertEqual(row[2], gui.FOLDER_ICON_NAME)
242 else:
243 self.assertEqual(row[2], gui.SHARE_ICON_NAME)
244 self.assertTrue(row[3], 'toggle should be shown on child!')
245 self.assertEqual(row[4], sensitive)
246 self.assertEqual(row[5], gui.gtk.ICON_SIZE_MENU)
247 self.assertEqual(row[6], volume['volume_id'])
248 self.assertEqual(row[7], volume['path'])
249
250 childiter = self.ui.volumes_store.iter_next(childiter)
251
252 treeiter = self.ui.volumes_store.iter_next(treeiter)
253
254 if treeiter is not None:
255 # skip the empty row
256 row = self.ui.volumes_store.get(treeiter,
257 *xrange(self.ui.MAX_COLS))
258 self.assertEqual(row, self.ui._empty_row)
259
260 # grab next non-empty row
261 treeiter = self.ui.volumes_store.iter_next(treeiter)
262
263 def test_on_volumes_info_ready_clears_the_list(self):
264 """The old volumes info is cleared before updated."""
265 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
266 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
267
268 self.assertEqual(len(FAKE_VOLUMES_INFO) + 1,
269 len(self.ui.volumes_store))
270
271 def test_on_volumes_info_ready_with_no_volumes(self):
272 """When there are no volumes, a notification is shown."""
273 self.ui.on_volumes_info_ready([])
274
275 self.assertEqual(len(self.ui.volumes_store), 0)
276
277 def test_on_volumes_info_ready_highlights_little_free_space(self):
278 """The free space is red if is zero (or close to 0)."""
279 self.ui.on_volumes_info_ready(FAKE_VOLUMES_NO_FREE_SPACE_INFO)
280
281 treeiter = self.ui.volumes_store.get_iter_root()
282 for name, free_bytes, volumes in FAKE_VOLUMES_NO_FREE_SPACE_INFO:
283 name = "%s's" % name if name else gui.MY_FOLDERS
284 free_bytes = humanize(int(free_bytes))
285 free_bytes = self.ui.NO_FREE_SPACE % {'free_space': free_bytes}
286
287 # check parent row
288 row = self.ui.volumes_store.get(treeiter,
289 *xrange(self.ui.MAX_COLS))
290
291 self.assertEqual(row[0], self.ui.ROW_HEADER % (name, free_bytes))
292
293 treeiter = self.ui.volumes_store.iter_next(treeiter)
294
295 if treeiter is not None:
296 # skip the empty row
297 row = self.ui.volumes_store.get(treeiter,
298 *xrange(self.ui.MAX_COLS))
299 self.assertEqual(row, self.ui._empty_row)
300
301 # grab next non-empty row
302 treeiter = self.ui.volumes_store.iter_next(treeiter)
303
304 def test_on_volumes_info_ready_handles_no_quota_info(self):
305 """The lack of free space is handled."""
306 info = [
307 (u'', gui.backend.ControlBackend.FREE_BYTES_NOT_AVAILABLE, [ROOT]),
308 (u'No free space available',
309 gui.backend.ControlBackend.FREE_BYTES_NOT_AVAILABLE,
310 [{u'volume_id': u'0', u'name': u'full', u'display_name': u'test',
311 u'path': u'full-share', u'subscribed': u'',
312 u'type': gui.backend.ControlBackend.SHARE_TYPE}]),
313 ]
314 self.ui.on_volumes_info_ready(info)
315
316 treeiter = self.ui.volumes_store.get_iter_root()
317 for name, free_bytes, volumes in info:
318 name = "%s's" % name if name else gui.MY_FOLDERS
319
320 # check parent row
321 row = self.ui.volumes_store.get(treeiter,
322 *xrange(self.ui.MAX_COLS))
323
324 self.assertEqual(row[0], self.ui.ROW_HEADER % (name, ''))
325
326 treeiter = self.ui.volumes_store.iter_next(treeiter)
327
328 if treeiter is not None:
329 # skip the empty row
330 row = self.ui.volumes_store.get(treeiter,
331 *xrange(self.ui.MAX_COLS))
332 self.assertEqual(row, self.ui._empty_row)
333
334 # grab next non-empty row
335 treeiter = self.ui.volumes_store.iter_next(treeiter)
336
337 def test_on_volumes_info_error(self):
338 """The volumes info couldn't be retrieved."""
339 self.ui.on_volumes_info_error()
340 self.assert_warning_correct(warning=self.ui.message,
341 text=gui.VALUE_ERROR)
342 self.assertFalse(self.ui.message.active)
343
344 def test_on_volumes_info_error_after_success(self):
345 """The volumes info couldn't be retrieved after a prior success."""
346 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
347
348 self.ui.on_volumes_info_error()
349
350 self.test_on_volumes_info_error()
351 self.test_on_volumes_info_ready_with_no_volumes()
352
353 def test_clicking_on_row_opens_folder(self):
354 """The folder activated is opened."""
355 self.patch(gui.os.path, 'exists', lambda *a: True)
356 self.patch(gui, 'uri_hook', self._set_called)
357 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
358
359 self.ui.volumes_view.row_activated('0:0',
360 self.ui.volumes_view.get_column(0))
361
362 self.assertEqual(self._called,
363 ((None, gui.FILE_URI_PREFIX + ROOT['path']), {}))
364
365 def test_clicking_on_row_handles_path_none(self):
366 """None paths are properly handled."""
367 self.patch(gui, 'uri_hook', self._set_called)
368 self.patch(self.ui.volumes_store, 'get_value', lambda *a: None)
369 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
370
371 self.ui.volumes_view.row_activated('0:0',
372 self.ui.volumes_view.get_column(0))
373
374 self.assertTrue(self.memento.check_warning('tree_path (0, 0)',
375 'volume_path', 'is None'))
376 self.assertEqual(self._called, False)
377
378 def test_clicking_on_row_handles_path_non_existent(self):
379 """Not-existent paths are properly handled."""
380 self.patch(gui.os.path, 'exists', lambda *a: False)
381 self.patch(gui, 'uri_hook', self._set_called)
382 path = 'not-in-disk'
383 self.patch(self.ui.volumes_store, 'get_value', lambda *a: path)
384 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
385
386 self.ui.volumes_view.row_activated('0:0',
387 self.ui.volumes_view.get_column(0))
388
389 self.assertTrue(self.memento.check_warning(path, 'does not exist'))
390 self.assertEqual(self._called, False)
391
392 def test_on_volumes_info_ready_with_music_folder(self):
393 """The volumes info is processed when ready."""
394 self.ui.on_volumes_info_ready(FAKE_VOLUMES_MINIMAL_INFO)
395
396 treeiter = self.ui.volumes_store.get_iter_root()
397 row = self.ui.volumes_store.get(treeiter, *xrange(self.ui.MAX_COLS))
398
399 # walk 'Mine' folders children
400 treeiter = self.ui.volumes_store.iter_children(treeiter)
401
402 # grab next row since first one is root
403 treeiter = self.ui.volumes_store.iter_next(treeiter)
404 row = self.ui.volumes_store.get(treeiter, *xrange(self.ui.MAX_COLS))
405
406 volume = MUSIC_FOLDER
407 self.assertEqual(row[0], gui.MUSIC_DISPLAY_NAME)
408 self.assertEqual(row[1], bool(volume['subscribed']))
409 self.assertEqual(row[2], gui.MUSIC_ICON_NAME)
410 self.assertTrue(row[3], 'toggle should be shown on child!')
411 self.assertTrue(row[4], 'toggle should be sensitive')
412 self.assertEqual(row[5], gui.gtk.ICON_SIZE_MENU)
413 self.assertEqual(row[6], volume['volume_id'])
414 self.assertEqual(row[7], volume['path'])
415
416
417class VolumesSubscriptionTestCase(VolumesTestCase):
418 """The test suite for the volumes panel."""
419
420 kwargs = {'main_window': object()}
421 tree_path = '0:3' # this is the /home/tester/foo folder, not subscribed
422
423 @defer.inlineCallbacks
424 def setUp(self):
425 yield super(VolumesSubscriptionTestCase, self).setUp()
426 self.patch(gui.os.path, 'exists', lambda path: True)
427 self.ui.confirm_dialog.response_code = gui.gtk.RESPONSE_YES
428 self.ui.on_volumes_info_ready(FAKE_VOLUMES_INFO)
429
430 def test_on_subscribed_toggled(self):
431 """Clicking on 'subscribed' updates the folder subscription."""
432 real_rows = len(FAKE_VOLUMES_INFO)
433 data = zip(range(real_rows)[::2], FAKE_VOLUMES_INFO) # skip emtpy rows
434 for parent, (_, _, volumes) in data:
435
436 sorted_vols = sorted(volumes, key=operator.itemgetter('path'))
437 for child, volume in enumerate(sorted_vols):
438 if volume['type'] == 'ROOT':
439 continue # not editable
440
441 path = '%s:%s' % (parent, child)
442 self.ui.on_subscribed_toggled(widget=None, path=path)
443
444 fid = volume['volume_id']
445 subscribed = gui.bool_str(not bool(volume['subscribed']))
446 # backend was called
447 self.assert_backend_called('change_volume_settings',
448 fid, {'subscribed': subscribed})
449 # store was updated
450 it = self.ui.volumes_store.get_iter(path)
451 value = self.ui.volumes_store.get_value(it, 1)
452 self.assertEqual(value, bool(subscribed))
453
454 # the ui is processing
455 self.assertTrue(self.ui.is_processing, 'ui must be processing')
456
457 # simulate success for setting change
458 self.ui.on_volume_settings_changed(volume_id=fid)
459
460 def test_on_volume_setting_changed(self):
461 """The setting for a volume was successfully changed."""
462 self.ui.on_subscribed_toggled(None, self.tree_path)
463
464 self.ui.on_volume_settings_changed(volume_id=None) # id not used
465
466 # the ui is no longer processing
467 self.assertFalse(self.ui.is_processing, 'ui must not be processing')
468
469 def test_on_volume_setting_change_error(self):
470 """The setting for a volume was not successfully changed."""
471 self.ui.on_subscribed_toggled(None, self.tree_path)
472
473 self.patch(self.ui, 'load', self._set_called)
474 self.ui.on_volume_settings_change_error(volume_id=None,
475 error_dict=None) # id not used
476 # reload folders list to sanitize the info in volumes_store
477 self.assertTrue(self._called, ((), {}))
478
479 def test_confirm_dialog(self):
480 """The confirmation dialog is correct."""
481 dialog = self.ui.confirm_dialog
482
483 self.assertEqual(dialog._args, ())
484 flags = gui.gtk.DIALOG_MODAL | gui.gtk.DIALOG_DESTROY_WITH_PARENT
485 kwargs = dict(parent=self.kwargs['main_window'],
486 flags=flags, type=gui.gtk.MESSAGE_WARNING,
487 buttons=gui.gtk.BUTTONS_YES_NO)
488 self.assertEqual(dialog._kwargs, kwargs)
489
490 def test_subscribe_shows_confirmation_dialog(self):
491 """Clicking on subscribe displays a confirmation dialog."""
492 self.ui.on_subscribed_toggled(None, self.tree_path)
493
494 path = FAKE_FOLDERS_INFO[-1]['path']
495 self.assertTrue(self.ui.confirm_dialog.was_run, 'dialog was run')
496 self.assertEqual(self.ui.confirm_dialog.markup,
497 gui.FOLDERS_CONFIRM_MERGE % {'folder_path': path})
498 self.assertFalse(self.ui.confirm_dialog.is_visible, 'dialog was hid')
499
500 def test_subscribe_does_not_call_backend_if_dialog_closed(self):
501 """Backend is not called if users closes the confirmation dialog."""
502 self.ui.confirm_dialog.response_code = gui.gtk.RESPONSE_DELETE_EVENT
503 self.ui.on_subscribed_toggled(None, self.tree_path)
504
505 self.assertNotIn('change_volume_settings', self.ui.backend._called)
506 self.assertFalse(self.ui.is_processing)
507
508 def test_subscribe_does_not_call_backend_if_answer_is_no(self):
509 """Backend is not called if users clicks on 'No'."""
510 self.ui.confirm_dialog.response_code = gui.gtk.RESPONSE_NO
511 self.ui.on_subscribed_toggled(None, self.tree_path)
512
513 self.assertNotIn('change_volume_settings', self.ui.backend._called)
514 self.assertFalse(self.ui.is_processing)
515
516 def test_no_confirmation_if_no_local_folder(self):
517 """The confirmation dialog is not shown if local folder not present."""
518 self.patch(gui.os.path, 'exists', lambda path: False)
519 self.ui.on_subscribed_toggled(None, self.tree_path)
520
521 self.assertFalse(self.ui.confirm_dialog.was_run, 'dialog was not run')
522 self.assertFalse(self.ui.confirm_dialog.is_visible, 'dialog was hid')
523
524 def test_no_confirmation_if_unsubscribing(self):
525 """The confirmation dialog is not shown if unsubscribing."""
526 self.ui.on_subscribed_toggled(None, self.tree_path)
527
528 treeiter = self.ui.volumes_store.get_iter(self.tree_path)
529 assert self.ui.volumes_store.get_value(treeiter, 1)
530
531 # reset flags
532 self.ui.confirm_dialog.was_run = False
533 self.ui.confirm_dialog.is_visible = False
534
535 self.ui.on_subscribed_toggled(None, self.tree_path)
536
537 self.assertFalse(self.ui.confirm_dialog.was_run, 'dialog was not run')
538 self.assertFalse(self.ui.confirm_dialog.is_visible, 'dialog was hid')
539
540
541class DeviceTestCase(ControlPanelMixinTestCase):
542 """The test suite for the device widget."""
543
544 klass = gui.Device
545 ui_filename = 'device.ui'
546
547 def assert_device_equal(self, device, expected):
548 """Assert that the device has the values from expected."""
549 self.assertEqual(device.id,
550 expected['device_id'])
551 value = expected['device_name'].replace(gui.DEVICE_REMOVABLE_PREFIX,
552 '')
553 self.assertEqual(device.device_name.get_text(), value)
554 self.assertEqual(device.device_type.get_icon_name()[0],
555 expected['device_type'].lower())
556 self.assertEqual(device.is_local,
557 bool(expected['is_local']))
558 self.assertEqual(device.configurable,
559 bool(expected['configurable']))
560 self.assertEqual(device.show_all_notifications.get_active(),
561 bool(expected['show_all_notifications']))
562 self.assertEqual(device.limit_bandwidth.get_active(),
563 bool(expected['limit_bandwidth']))
564
565 config_enabled = self.ui.config_settings.get_sensitive()
566 self.assertEqual(device.configurable, config_enabled)
567
568 limit_enabled = self.ui.throttling_limits.get_sensitive()
569 self.assertEqual(device.limit_bandwidth.get_active(), limit_enabled)
570
571 value = int(expected['max_upload_speed']) // gui.KILOBYTES
572 self.assertEqual(device.max_upload_speed.get_value_as_int(), value)
573 value = int(expected['max_download_speed']) // gui.KILOBYTES
574 self.assertEqual(device.max_download_speed.get_value_as_int(), value)
575
576 def assert_device_settings_changed(self):
577 """Changing throttling settings updates the backend properly."""
578 expected = self.ui.__dict__
579 self.assert_backend_called('change_device_settings',
580 self.ui.id, expected)
581 self.assertEqual(self.ui.warning_label.get_text(), '')
582
583 limit_enabled = self.ui.throttling_limits.get_sensitive()
584 self.assertEqual(self.ui.limit_bandwidth.get_active(), limit_enabled)
585
586 def modify_settings(self):
587 """Modify settings so values actually change."""
588 new_val = not self.ui.show_all_notifications.get_active()
589 self.ui.show_all_notifications.set_active(new_val)
590
591 new_val = not self.ui.limit_bandwidth.get_active()
592 self.ui.limit_bandwidth.set_active(new_val)
593
594 new_val = self.ui.max_upload_speed.get_value_as_int() + 1
595 self.ui.max_upload_speed.set_value(new_val)
596
597 new_val = self.ui.max_download_speed.get_value_as_int() + 1
598 self.ui.max_download_speed.set_value(new_val)
599
600 def test_is_a_container(self):
601 """Inherits from a container class."""
602 self.assertIsInstance(self.ui, gui.gtk.Bin)
603
604 def test_inner_widget_is_packed(self):
605 """The 'itself' vbox is packed into the widget."""
606 self.assertIn(self.ui.itself, self.ui.get_children())
607
608 def test_is_visible(self):
609 """Is visible."""
610 self.assertTrue(self.ui.get_visible())
611
612 def test_is_sensitive(self):
613 """Is sensitive."""
614 self.assertTrue(self.ui.get_sensitive())
615
616 def test_warning_label_is_cleared(self):
617 """The warning label is cleared."""
618 self.assertEqual(self.ui.warning_label.get_text(), '')
619
620 def test_default_values(self):
621 """Default values are correct."""
622 self.assertEqual(self.ui.id, None)
623 self.assertEqual(self.ui.device_name.get_text(), '')
624 self.assertEqual(self.ui.device_type.get_icon_name()[0],
625 gui.DEVICE_TYPE_COMPUTER.lower())
626 self.assertEqual(self.ui.is_local, False)
627 self.assertEqual(self.ui.configurable, False)
628 self.assertEqual(self.ui.show_all_notifications.get_active(), True)
629 self.assertEqual(self.ui.limit_bandwidth.get_active(), False)
630 self.assertEqual(self.ui.max_upload_speed.get_value_as_int(), 0)
631 self.assertEqual(self.ui.max_download_speed.get_value_as_int(), 0)
632
633 def test_init_does_not_call_backend(self):
634 """When updating, the backend is not called."""
635 self.assertEqual(self.ui.backend._called, {})
636
637 def test_update_device_name(self):
638 """A device can be updated from a dict."""
639 value = 'The death star'
640 self.ui.update(device_name=gui.DEVICE_REMOVABLE_PREFIX + value)
641 self.assertEqual(value, self.ui.device_name.get_text())
642
643 def test_update_unicode_device_name(self):
644 """A device can be updated from a dict."""
645 value = u'Ñoño Ñandú'
646 self.ui.update(device_name=gui.DEVICE_REMOVABLE_PREFIX + value)
647 self.assertEqual(value, self.ui.device_name.get_text())
648
649 def test_update_device_type_computer(self):
650 """A device can be updated from a dict."""
651 dtype = gui.DEVICE_TYPE_COMPUTER
652 self.ui.update(device_type=dtype)
653 self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_LARGE_TOOLBAR),
654 self.ui.device_type.get_icon_name())
655
656 def test_update_device_type_phone(self):
657 """A device can be updated from a dict."""
658 dtype = gui.DEVICE_TYPE_PHONE
659 self.ui.update(device_type=dtype)
660 self.assertEqual((dtype.lower(), gui.gtk.ICON_SIZE_LARGE_TOOLBAR),
661 self.ui.device_type.get_icon_name())
662
663 def test_update_is_not_local(self):
664 """A device can be updated from a dict."""
665 self.ui.update(is_local='')
666 self.assertFalse(self.ui.is_local)
667
668 def test_update_is_local(self):
669 """A device can be updated from a dict."""
670 self.ui.update(is_local='True')
671 self.assertTrue(self.ui.is_local)
672
673 def test_update_non_configurable(self):
674 """A device can be updated from a dict."""
675 self.ui.update(configurable='')
676 self.assertFalse(self.ui.configurable)
677 self.assertFalse(self.ui.config_settings.get_visible())
678
679 def test_update_configurable(self):
680 """A device can be updated from a dict."""
681 self.ui.update(configurable='True')
682 self.assertTrue(self.ui.configurable)
683 self.assertTrue(self.ui.config_settings.get_visible())
684
685 def test_update_show_all_notifications(self):
686 """A device can be updated from a dict."""
687 self.ui.update(show_all_notifications='')
688 self.assertFalse(self.ui.show_all_notifications.get_active())
689
690 self.ui.update(show_all_notifications='True')
691 self.assertTrue(self.ui.show_all_notifications.get_active())
692
693 def test_update_limit_bandwidth(self):
694 """A device can be updated from a dict."""
695 self.ui.update(limit_bandwidth='')
696 self.assertFalse(self.ui.limit_bandwidth.get_active())
697 self.assertFalse(self.ui.throttling_limits.get_sensitive())
698
699 self.ui.update(limit_bandwidth='True')
700 self.assertTrue(self.ui.limit_bandwidth.get_active())
701 self.assertTrue(self.ui.throttling_limits.get_sensitive())
702
703 def test_update_upload_speed(self):
704 """A device can be updated from a dict."""
705 value = '12345'
706 self.ui.update(max_upload_speed=value)
707 self.assertEqual(int(value) // gui.KILOBYTES,
708 self.ui.max_upload_speed.get_value_as_int())
709
710 def test_update_download_speed(self):
711 """A device can be updated from a dict."""
712 value = '987654'
713 self.ui.update(max_download_speed=value)
714 self.assertEqual(int(value) // gui.KILOBYTES,
715 self.ui.max_download_speed.get_value_as_int())
716
717 def test_update_does_not_call_backend(self):
718 """When updating, the backend is not called."""
719 self.ui.update(**FAKE_DEVICE_INFO)
720 self.assertEqual(self.ui.backend._called, {})
721 self.assert_device_equal(self.ui, FAKE_DEVICE_INFO)
722
723 def test_on_show_all_notifications_toggled(self):
724 """When toggling show_all_notifications, backend is updated."""
725 value = not self.ui.show_all_notifications.get_active()
726 self.ui.show_all_notifications.set_active(value)
727 self.assert_device_settings_changed()
728
729 def test_on_limit_bandwidth_toggled(self):
730 """When toggling limit_bandwidth, backend is updated."""
731 value = not self.ui.limit_bandwidth.get_active()
732 self.ui.limit_bandwidth.set_active(value)
733 self.assert_device_settings_changed()
734
735 def test_on_max_upload_speed_value_changed(self):
736 """When setting max_upload_speed, backend is updated."""
737 self.ui.max_upload_speed.set_value(25)
738 self.assert_device_settings_changed()
739
740 def test_on_max_download_speed_value_changed(self):
741 """When setting max_download_speed, backend is updated."""
742 self.ui.max_download_speed.set_value(52)
743 self.assert_device_settings_changed()
744
745 def test_backend_signals(self):
746 """The proper signals are connected to the backend."""
747 self.assertEqual(self.ui.backend._signals['DeviceSettingsChanged'],
748 [self.ui.on_device_settings_changed])
749 self.assertEqual(self.ui.backend._signals['DeviceSettingsChangeError'],
750 [self.ui.on_device_settings_change_error])
751 self.assertEqual(self.ui.backend._signals['DeviceRemoved'],
752 [self.ui.on_device_removed])
753 self.assertEqual(self.ui.backend._signals['DeviceRemovalError'],
754 [self.ui.on_device_removal_error])
755
756 def test_on_device_settings_changed(self):
757 """When settings were changed for this device, enable it."""
758 self.modify_settings()
759 self.ui.on_device_settings_changed(device_id=self.ui.id)
760
761 self.assertTrue(self.ui.get_sensitive())
762 self.assertEqual(self.ui.warning_label.get_text(), '')
763 self.assertEqual(self.ui.__dict__, self.ui._last_settings)
764
765 def test_on_device_settings_change_after_error(self):
766 """Change success after error."""
767 self.modify_settings()
768 self.ui.on_device_settings_change_error(device_id=self.ui.id) # error
769
770 self.test_on_device_settings_changed()
771
772 def test_on_device_settings_changed_different_id(self):
773 """When settings were changed for other device, nothing changes."""
774 self.modify_settings()
775 self.ui.on_device_settings_changed(device_id='yadda')
776
777 self.assertEqual(self.ui.warning_label.get_text(), '')
778
779 def test_on_device_settings_change_error(self):
780 """When settings were not changed for this device, notify the user.
781
782 Also, confirm that old values were restored.
783
784 """
785 self.ui.update(**FAKE_DEVICE_INFO) # use known values
786
787 self.modify_settings()
788
789 self.ui.on_device_settings_change_error(device_id=self.ui.id) # error
790
791 self.assertTrue(self.ui.get_sensitive())
792 self.assert_warning_correct(self.ui.warning_label,
793 gui.DEVICE_CHANGE_ERROR)
794 self.assert_device_equal(self.ui, FAKE_DEVICE_INFO) # restored info
795
796 def test_on_device_settings_change_error_after_success(self):
797 """Change error after success."""
798 self.modify_settings()
799 self.ui.on_device_settings_changed(device_id=self.ui.id)
800
801 self.test_on_device_settings_change_error()
802
803 def test_on_device_settings_change_error_different_id(self):
804 """When settings were not changed for other device, do nothing."""
805 self.modify_settings()
806 self.ui.on_device_settings_change_error(device_id='yudo')
807 self.assertEqual(self.ui.warning_label.get_text(), '')
808
809 def test_remove(self):
810 """Clicking on remove calls the backend properly."""
811 self.ui.is_local = False
812 self.ui.remove.clicked()
813
814 self.assert_backend_called('remove_device', self.ui.id)
815 self.assertFalse(self.ui.get_sensitive(),
816 'Must be disabled while removing.')
817
818 def test_on_device_removed(self):
819 """On this device removed, hide and destroy."""
820 self.ui.remove.clicked()
821 self.ui.on_device_removed(device_id=self.ui.id)
822
823 self.assertFalse(self.ui.get_visible(),
824 'Must not be visible after removed.')
825
826 def test_on_device_removed_other_id(self):
827 """On other device removed, do nothing."""
828 self.ui.remove.clicked()
829 self.ui.on_device_removed(device_id='foo')
830
831 self.assertTrue(self.ui.get_visible(),
832 'Must be visible after other device was removed.')
833
834 def test_on_device_removal_error(self):
835 """On this device removal error, re-enable and show error."""
836 self.ui.remove.clicked()
837 self.ui.on_device_removal_error(device_id=self.ui.id)
838
839 self.assertTrue(self.ui.get_sensitive(),
840 'Must be enabled after removal error.')
841 self.assert_warning_correct(self.ui.warning_label,
842 gui.DEVICE_REMOVAL_ERROR)
843
844 def test_on_device_removal_error_other_id(self):
845 """On other device removal error, do nothing."""
846 self.ui.remove.clicked()
847 self.ui.on_device_removal_error(device_id='foo')
848
849 self.assertFalse(self.ui.get_sensitive(),
850 'Must be disabled after other device removal error.')
851
852
853class RemoveDeviceTestCase(DeviceTestCase):
854 """The test suite for the device widget when prompting for removal."""
855
856 confirm_dialog = FakedConfirmDialog()
857 kwargs = {'confirm_remove_dialog': confirm_dialog}
858
859 def test_remove(self):
860 """Clicking on remove calls the backend properly."""
861 self.confirm_dialog.response_code = gui.gtk.RESPONSE_YES
862 super(RemoveDeviceTestCase, self).test_remove()
863
864 def test_on_device_removal_error_other_id(self):
865 """On other device removal error, do nothing."""
866 self.confirm_dialog.response_code = gui.gtk.RESPONSE_YES
867 parent_test = super(RemoveDeviceTestCase, self)
868 parent_test.test_on_device_removal_error_other_id()
869
870 def test_remove_shows_confirmation_dialog(self):
871 """Clicking on remove displays a confirmation dialog."""
872 self.ui.remove.clicked()
873
874 self.assertTrue(self.confirm_dialog.was_run, 'dialog was run')
875 self.assertFalse(self.confirm_dialog.is_visible, 'dialog was hid')
876
877 def test_remove_does_not_call_backend_if_dialog_closed(self):
878 """Backend is not called if users closes the confirmation dialog."""
879 self.confirm_dialog.response_code = gui.gtk.RESPONSE_DELETE_EVENT
880 self.ui.remove.clicked()
881
882 self.assertNotIn('remove_device', self.ui.backend._called)
883 self.assertTrue(self.ui.is_sensitive())
884
885 def test_remove_does_not_call_backend_if_answer_is_no(self):
886 """Backend is not called if users clicks on 'No'."""
887 self.confirm_dialog.response_code = gui.gtk.RESPONSE_NO
888 self.ui.remove.clicked()
889
890 self.assertNotIn('remove_device', self.ui.backend._called)
891 self.assertTrue(self.ui.is_sensitive())
892
893
894class DevicesTestCase(ControlPanelMixinTestCase):
895 """The test suite for the devices panel."""
896
897 klass = gui.DevicesPanel
898 ui_filename = 'devices.ui'
899 kwargs = {'main_window': object()}
900
901 @defer.inlineCallbacks
902 def setUp(self):
903 yield super(DevicesTestCase, self).setUp()
904 self.ui.load()
905
906 def test_is_an_ubuntuone_bin(self):
907 """Inherits from UbuntuOneBin."""
908 self.assertIsInstance(self.ui, gui.UbuntuOneBin)
909
910 def test_inner_widget_is_packed(self):
911 """The 'itself' vbox is packed into the widget."""
912 self.assertIn(self.ui.itself, self.ui.get_children())
913
914 def test_is_visible(self):
915 """Is visible."""
916 self.assertTrue(self.ui.get_visible())
917
918 def test_confirm_remove_dialog(self):
919 """The confirmation dialog is correct."""
920 dialog = self.ui.confirm_remove_dialog
921
922 self.assertEqual(dialog._args, ())
923 flags = gui.gtk.DIALOG_MODAL | gui.gtk.DIALOG_DESTROY_WITH_PARENT
924 kwargs = dict(parent=self.kwargs['main_window'],
925 flags=flags, type=gui.gtk.MESSAGE_WARNING,
926 buttons=gui.gtk.BUTTONS_YES_NO,
927 message_format=gui.DEVICE_CONFIRM_REMOVE)
928 self.assertEqual(dialog._kwargs, kwargs)
929
930 def test_backend_signals(self):
931 """The proper signals are connected to the backend."""
932 self.assertEqual(self.ui.backend._signals['DevicesInfoReady'],
933 [self.ui.on_devices_info_ready])
934 self.assertEqual(self.ui.backend._signals['DevicesInfoError'],
935 [self.ui.on_devices_info_error])
936 self.assertEqual(self.ui.backend._signals['DeviceRemoved'],
937 [self.ui.on_device_removed])
938
939 def test_devices_info_is_requested_on_load(self):
940 """The devices info is requested to the backend."""
941 # clean backend calls
942 self.ui.backend._called.pop('devices_info', None)
943 self.ui.load()
944
945 self.assert_backend_called('devices_info')
946
947 def test_is_processing_after_load(self):
948 """The ui is processing when contents are load."""
949 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
950 self.ui.load()
951
952 self.assertTrue(self.ui.is_processing)
953
954 def test_is_not_processing_after_non_empty_devices_info_ready(self):
955 """The ui is no longer processing after a non empty device list."""
956 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
957
958 self.assertFalse(self.ui.is_processing)
959
960 def test_is_not_processing_after_empty_devices_info_ready(self):
961 """The ui is no longer processing after a empty device list."""
962 self.ui.on_devices_info_ready([])
963
964 self.assertFalse(self.ui.is_processing)
965
966 def test_show_message_after_empty_devices_info_ready(self):
967 """When there are no devices, a notification is shown."""
968 self.ui.on_devices_info_ready([])
969
970 self.assertEqual(self.ui.message.get_text(), gui.NO_DEVICES)
971
972 def test_on_devices_info_ready(self):
973 """The devices info is processed when ready."""
974 self.ui.on_devices_info_ready(FAKE_DEVICES_INFO)
975
976 children = self.ui.devices.get_children()
977 self.assertEqual(len(children), len(FAKE_DEVICES_INFO))
978
979 for child, device in zip(children, FAKE_DEVICES_INFO):
980 self.assertIsInstance(child, gui.Device)
981
982 self.assertEqual(device['device_id'], child.id)
983 value = device['device_name'].replace(gui.DEVICE_REMOVABLE_PREFIX,
984 '')
985 self.assertEqual(value, child.device_name.get_text())
986 self.assertEqual(device['device_type'].lower(),
987 child.device_type.get_icon_name()[0])
988 self.assertEqual(bool(device['is_local']),
989 child.is_local)
990 self.assertEqual(bool(device['configurable']),
991 child.configurable)
992
993 if bool(device['configurable']):
994 self.assertEqual(bool(device['show_all_notifications']),
995 child.show_all_notifications.get_active())
996 self.assertEqual(bool(device['limit_bandwidth']),
997 child.limit_bandwidth.get_active())
998 value = int(device['max_upload_speed']) // gui.KILOBYTES
999 self.assertEqual(value,
1000 child.max_upload_speed.get_value_as_int())
1001 value = int(device['max_download_speed']) // gui.KILOBYTES
1002 self.assertEqual(value,
1003 child.max_download_speed.get_value_as_int())
1004
1005 self.assertIs(child.confirm_dialog, self.ui.confirm_remove_dialog)
1006
1007 def test_on_devices_info_ready_have_devices_cached(self):
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: